This commit is contained in:
Adrien Bouvais 2024-05-31 20:41:22 +02:00
parent 79e4639666
commit f8bb7ffc84
9 changed files with 99 additions and 117 deletions

69
Chat.go
View File

@ -271,7 +271,10 @@ func GetMessageContentHandler(c *fiber.Ctx) error {
}
func generateWelcomeChatHTML() string {
welcomeMessage := `To start using JADE, please login.`
welcomeMessage := `
No fancy stuff, easy to use, pay as you use, multi-models, work on low 3g...
To start using JADE, please login.`
loginButton := `
<a class="button is-primary is-small" href="/signin">
@ -283,7 +286,7 @@ func generateWelcomeChatHTML() string {
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Content: "<br>" + markdownToHTML(welcomeMessage) + loginButton,
Content: "<br>" + welcomeMessage + loginButton,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",
@ -305,12 +308,24 @@ func generateWelcomeChatHTML() string {
func generateEnterKeyChatHTML() string {
welcomeMessage := `
<p class="mt-2">To start using JADE, please enter at least one key in the settings.</p>
<p class="mt-2">JADE require at least one API key to work. Add one in the settings at the bottom right of the page.</p>
<p>API keys are unique codes that allow you to access and use the services provided by different Large Language Model (LLM) providers. When you sign up for an account with a provider like OpenAI, Anthropic, Groq, or MistralAI, you'll receive an API key that you can use to make requests to their LLMs.</p>
<p>Most providers offer free credits when you first sign up, which means you have a balance in your account that you can use to generate messages. You pay for the service based on the number of tokens generated. A token is a small unit of text, roughly equivalent to 3 characters. So, a small input and output text will cost very little.</p>
<p>To get an idea of how many tokens your text contains, you can use a tokenizer like the one provided by OpenAI: <a href="https://platform.openai.com/tokenizer" target="_blank">https://platform.openai.com/tokenizer</a>. Keep in mind that different models may have slightly different tokenizers.</p>
<h2>Pricing Examples</h2>
<ul>
<li>OpenAI: <a href="https://openai.com/index/openai-api" target="_blank">Link</a></li>
<li>Anthropic: <a href="https://www.anthropic.com/api" target="_blank">Link</a></li>
<li>Mistral: <a href="https://mistral.ai/news/la-plateforme" target="_blank">Link</a></li>
<li>Groq: <a href="https://console.groq.com/login" target="_blank">Link</a></li>
<li>If you ask a small question that generates around 100 tokens (approximately 300 characters), and the price is $5 per 1 million tokens, it would cost you $0.0005.</li>
<li>With this pricing, you can ask 2,000 small questions for just $1 using GPT-4o, or 20,000 questions using GPT-3.5 turbo.</li>
<li>For a larger text, like a PDF file with 30,000 characters (roughly 10,000 tokens), you would pay around $0.05 per message.</li>
</ul>
<p>Remember, prices and token limits may vary depending on the provider and the specific LLM you're using.</p>
<h2>Learn More</h2>
<p>To learn more about the different LLM providers and their offerings, check out their websites:</p>
<ul>
<li><a href="https://www.openai.com/" target="https://platform.openai.com/docs/overview">OpenAI</a></li>
<li><a href="https://www.anthropic.com/" target="https://console.anthropic.com/">Anthropic</a></li>
<li><a href="https://groq.com/" target="https://console.groq.com/login">Groq</a></li>
<li><a href="https://mistral.ai/" target="https://console.mistral.ai/">MistralAI</a></li>
</ul>
`
@ -345,7 +360,43 @@ func generateTermAndServiceHandler(c *fiber.Ctx) error {
func generateTermAndServiceChatHTML() string {
welcomeMessage := `
<p class="mt-2">TODO Add terms and service.</p>
<h1 class="mt-2">Terms of Service</h1>
<h2>1. Acceptance of Terms</h2>
<p>By using JADE (the "App"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, please do not use the App.</p>
<h2>2. Description of Service</h2>
<p>The App is a chatbot that makes requests to various third-party APIs to provide information and services. The App is provided "as is" and "as available" without any warranties of any kind.</p>
<h2>3. User Responsibilities</h2>
<p>- You are responsible for any content you generate or share using the App.</p>
<p>- You agree not to use the App for any unlawful or harmful activities.</p>
<h2>4. Authentication and Payment</h2>
<p>- Authentication for the App is managed by Google and GitHub. We are not responsible for any issues related to authentication, including but not limited to, unauthorized access or data breaches. Please refer to Google and GitHub's respective terms of service and privacy policies for more information.</p>
<p>- Payments for any services within the App are processed by Stripe. We are not responsible for any issues related to payment processing, including but not limited to, transaction errors or unauthorized transactions. Please refer to Stripe's terms of service and privacy policy for more information.</p>
<h2>5. Disclaimer of Warranties</h2>
<p>- The App is provided without warranties of any kind, either express or implied, including but not limited to, implied warranties of merchantability, fitness for a particular purpose, or non-infringement.</p>
<p>- We do not guarantee the accuracy, completeness, or usefulness of any information provided by the App.</p>
<h2>6. Limitation of Liability</h2>
<p>- In no event shall BouvAI, its affiliates, or its licensors be liable for any indirect, incidental, special, consequential, or punitive damages, including but not limited to, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your use or inability to use the App; (ii) any unauthorized access to or use of our servers and/or any personal information stored therein; (iii) any bugs, viruses, trojan horses, or the like that may be transmitted to or through our App by any third party; or (iv) any errors or omissions in any content or for any loss or damage incurred as a result of the use of any content posted, emailed, transmitted, or otherwise made available through the App, whether based on warranty, contract, tort (including negligence), or any other legal theory, whether or not we have been informed of the possibility of such damage.</p>
<h2>7. Data Privacy</h2>
<p>- We are not responsible for any data leaks or breaches that may occur. Users are advised to use the App at their own risk.</p>
<p>- We do not store any personal data unless explicitly stated otherwise.</p>
<h2>8. Changes to the Terms</h2>
<p>We reserve the right to modify these Terms at any time. Any changes will be effective immediately upon posting the updated Terms on our website or within the App. Your continued use of the App after any such changes constitutes your acceptance of the new Terms.</p>
<h2>9. Governing Law</h2>
<p>These Terms shall be governed and construed in accordance with the laws of [Your Country/State], without regard to its conflict of law principles.</p>
<h2>10. Contact Information</h2>
<p>If you have any questions about these Terms, please contact us at adrien.bouvais.pro@gmail.com.</p>
<p>**BouvAI**</p>
`
closeBtn := `
@ -408,7 +459,7 @@ func generateLimitReachedChatHTML() string {
NextMessages := []TemplateMessage{}
nextMsg := TemplateMessage{
Icon: "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
Content: "<br>" + markdownToHTML(welcomeMessage) + "<br>" + stripeTable,
Content: "<br>" + welcomeMessage + "<br>" + stripeTable,
Hidden: false, // Assuming Hidden is a field you want to include from Message
Id: "0",
Name: "JADE",

2
go.sum
View File

@ -25,6 +25,8 @@ github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

View File

@ -1,5 +1,3 @@
// Hohohoho. Bienvenue mes amis.
// I think you gonna be interested by this part.
package main
import (
@ -32,6 +30,7 @@ type TokenResponse struct {
ProviderToken string `json:"provider_token"`
}
// TODO add to env
const EDGEDB_AUTH_BASE_URL = "https://jade-hackathon--mrbounty.c-40.i.aws.edgedb.cloud/db/main/ext/auth"
func getGoogleUserProfile(providerToken string) (string, string, string) {

28
main.go
View File

@ -1,31 +1,3 @@
// Welcome welcome, I guess you started here
// Thank you for reviewing my code.
// As for context. This is the absolutely first time I use Golang or EdgeDB.
// So far I only did python. I have a master but in thermal engineering, not CS.
// My philosophy in coding is simplicity. A "clean code" is a simple code.
// For me you should need few comments. Variables, type and functions name should be enough to understand.
// And what's work, work.
// To build my app I took a simple approach.
// All data are stored in database. If I need it, I ask the DB.
// There is no state in the server nor in the client (or very little for the client).
// For example if I want to ask a question. Here a simplify version:
// 1. Add the user message to the DB.
// 2. Read all messages from the DB to send a request.
// 3. Add the bot message to the DB.
// 4. Read all messages from the DB to generate the chat HTML.
// So all query are simple query. That is my goal. In the future I may optimize it, but for dev, that is the best approach I think.
// Also if I want to change the order of a list (you will see later).
// I first send the new positions -> update the db -> read the db to generate the HTML.
// So Edgedb is the heart of the app. Everything pass by it.
// It need A LOT of optimization, I first do something that work then optimize it.
// I hope you will like it.
package main
import (

View File

@ -5,12 +5,25 @@ html {
/* Stuff for message boxes */
#chat-messages .message-content .code-container {
position: relative;
border-radius: 8px;
overflow: hidden;
}
#chat-messages .message-content .code-header {
background-color: #f0f0f0;
padding: 5px;
display: flex;
justify-content: flex-end;
align-items: center;
}
#chat-messages .message-content pre {
overflow-x: auto;
white-space: pre;
max-width: 662px;
position: relative;
border-radius: 8px;
margin: 0;
}
#chat-messages .message-content pre code {
@ -20,11 +33,10 @@ html {
}
.copy-button {
position: absolute;
top: 5px;
right: 5px;
margin-left: 5px;
}
.content {
font-size: 14px;
line-height: 1.5;

View File

@ -6,6 +6,7 @@ package main
import (
"bytes"
"fmt"
"html"
"regexp"
"github.com/yuin/goldmark"
@ -13,13 +14,15 @@ import (
)
func markdownToHTML(markdownText string) string {
escapedText := html.EscapeString(markdownText)
var buf bytes.Buffer
md := goldmark.New(
goldmark.WithExtensions(
highlighting.NewHighlighting(highlighting.WithStyle("monokai")),
),
)
if err := md.Convert([]byte(markdownText), &buf); err != nil {
if err := md.Convert([]byte(escapedText), &buf); err != nil {
fmt.Println("failed to convert markdown to HTML")
panic(err)
}
@ -28,16 +31,23 @@ func markdownToHTML(markdownText string) string {
}
func addCopyButtonsToCode(htmlContent string) string {
buttonHTML := `<button class="copy-button button is-small is-primary is-outlined" onclick="copyToClipboardCode(this)"><i class="fa-solid fa-copy"></i></button>`
buttonHTML := `&lt;button class=&#34;copy-button button is-small is-primary is-outlined&#34; onclick=&#34;copyToClipboardCode(this)&#34;&gt;&lt;i class=&#34;fa-solid fa-copy&#34;&gt;&lt;/i&gt;&lt;/button&gt;`
// Regular expression pattern to match <pre> elements and insert the button right before <code>
pattern := `(<pre[^>]*>)`
// Regular expression pattern to match &lt;pre&gt; elements
pattern := `(&lt;pre[^&gt;]*&gt;)`
// Compile the regular expression
re := regexp.MustCompile(pattern)
// Replace each matched <pre> element with the updated HTML
updatedHTML := re.ReplaceAllString(htmlContent, "$1"+buttonHTML)
// Replace each matched &lt;pre&gt; element with the updated HTML
updatedHTML := re.ReplaceAllStringFunc(htmlContent, func(match string) string {
return `&lt;div class=&#34;code-container&#34;&gt;
&lt;div class=&#34;code-header&#34;&gt;
` + buttonHTML + `
&lt;/div&gt;
` + match + `
&lt;/div&gt;`
})
return updatedHTML
}

View File

@ -14,13 +14,13 @@
<hx hx-get="/loadUsageKPI" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<hx hx-get="/loadConversationSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<hx hx-get="/loadModelSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<button {% if not IsLogin or not HaveKey %}style="display: none;" {%endif%}
<!--button {% if not IsLogin or not HaveKey %}style="display: none;" {%endif%}
class="button is-small to-reduce-opacity" onclick="clearTextArea()" id="clear-button"
hx-post="/clearChat" hx-swap="outerHTML" hx-target="#chat-container" title="Clear chat">
<span class="icon">
<i class="fa-solid fa-broom"></i>
</span>
</button>
</button-->
<!--button type="submit" class="button is-small" hx-get="/test" hx-swap="beforeend">
<span class="icon">
<i class="fa-solid fa-vials"></i>

View File

@ -1,64 +0,0 @@
<style>
svg {
font-family: 'Russo One', sans-serif;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
}
svg text {
text-transform: uppercase;
animation: stroke 7s forwards;
/* Play once and use 'forwards' to retain the final state */
stroke-width: 2;
stroke: #000;
font-size: 20vw;
opacity: 1;
/* Ensure the text is fully visible at the start */
}
@keyframes stroke {
0%,
14.3% {
fill: #fff;
stroke: #000;
stroke-dashoffset: 25%;
stroke-dasharray: 0 50%;
stroke-width: 8;
}
64.3%,
71.5% {
fill: #fff;
stroke: #000;
}
85.7%,
100% {
fill: #fff;
stroke: #000;
stroke-dashoffset: -25%;
stroke-dasharray: 50% 0;
stroke-width: 1;
/* Ensure stroke-width is 0 at the end */
opacity: 1;
/* Full opacity until 85.7% of the animation */
}
100% {
opacity: 0;
/* Fade out completely at the very end */
}
}
</style>
<svg viewBox="0 0 300 300">
<text x="50%" y="50%" dy=".35em" text-anchor="middle">
JADE
</text>
</svg>