Changed HTML to use djlint

This commit is contained in:
Adrien Bouvais 2024-08-04 10:09:15 +02:00
parent 5384383908
commit fa1bd44a99
13 changed files with 1004 additions and 839 deletions

View File

@ -1,4 +1,7 @@
<div class="chat-container mt-5" style="padding-bottom: 155px;" hx-indicator="#textarea-control" hx-ext="sse"> <div class="chat-container mt-5"
<hx hx-get="/loadChat" hx-trigger="load once" hx-swap="outerHTML"></hx> style="padding-bottom: 155px"
<hx hx-get="/loadChatInput" hx-trigger="load once" hx-swap="outerHTML" id="textarea-control"></hx> hx-indicator="#textarea-control"
</div> hx-ext="sse">
<hx hx-get="/loadChat" hx-trigger="load once" hx-swap="outerHTML"></hx>
<hx hx-get="/loadChatInput" hx-trigger="load once" hx-swap="outerHTML" id="textarea-control"></hx>
</div>

View File

@ -1,13 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="dark" lang="en"> <html data-theme="dark" lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"> <meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<title>JADE</title> <title>JADE</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css"> <link rel="stylesheet"
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"> href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
<link href="https://fonts.googleapis.com/css?family=Russo+One" rel="stylesheet"> <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
<link href="https://fonts.googleapis.com/css?family=Russo+One"
rel="stylesheet">
<!--link rel="stylesheet" href="/animations.css"--> <!--link rel="stylesheet" href="/animations.css"-->
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" href="/style.css">
@ -19,7 +23,8 @@
<script async src="https://js.stripe.com/v3/pricing-table.js"></script> <script async src="https://js.stripe.com/v3/pricing-table.js"></script>
<!-- highlight.js --> <!-- highlight.js -->
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/default.min.css"> <link rel="stylesheet"
href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/default.min.css">
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script> <script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script> <script>hljs.highlightAll();</script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
@ -31,9 +36,9 @@
go.run(result.instance); go.run(result.instance);
}); });
</script> </script>
</head> </head>
<body> <body>
{{ embed }} {{ embed }}
@ -197,6 +202,6 @@
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,123 +1,133 @@
{% if IsLogin %} {% if IsLogin %}
{% if IsSubscribed or not IsLimiteReached %} {% if IsSubscribed or not IsLimiteReached %}
<div class="chat-input-container mb-5"> <div class="chat-input-container mb-5">
<div class="textarea-wrapper"> <div class="textarea-wrapper">
<div class="control" id="textarea-control"> <div class="control" id="textarea-control">
<textarea {% if not IsLogin or not HaveKey %}disabled{% endif %} class="textarea has-fixed-size" <textarea {% if not IsLogin or not HaveKey %}disabled{% endif %}
placeholder="Type your message here..." name="message" oninput="toggleSendButton()" class="textarea has-fixed-size"
id="chat-input-textarea"></textarea> placeholder="Type your message here..."
name="message"
oninput="toggleSendButton()"
id="chat-input-textarea"></textarea>
</div> </div>
<div class="button-group"> <div class="button-group">
<hx hx-get="/loadSettings" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx> <hx hx-get="/loadSettings" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<hx hx-get="/loadUsageKPI" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx> <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="/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> <hx hx-get="/loadModelSelection" hx-trigger="load" hx-swap="outerHTML" hx-target="this"></hx>
<button disabled type="submit" class="send-button button is-primary is-small" hx-post="/generatePlaceholder" <button disabled
hx-swap="beforeend" hx-target="#chat-messages" id="chat-input-send-btn" class="chat-input" type="submit"
hx-include="[name='message']" hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}" class="send-button button is-primary is-small"
onclick="onClickSendButton()"> hx-post="/generatePlaceholder"
<span class="icon"> hx-swap="beforeend"
<i class="fa-solid fa-chevron-right"></i> hx-target="#chat-messages"
</span> id="chat-input-send-btn"
</button> class="chat-input"
hx-include="[name='message']"
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}"
onclick="onClickSendButton()">
<span class="icon">
<i class="fa-solid fa-chevron-right"></i>
</span>
</button>
</div> </div>
</div>
</div> </div>
</div> {% endif %}
{% endif %}
{% endif %} {% endif %}
<script> <script>
var textareaControl = document.getElementById('textarea-control'); var textareaControl = document.getElementById('textarea-control');
// Every 0.01s check if the text area have htmx-request class, if yes, add the class is-loading // Every 0.01s check if the text area have htmx-request class, if yes, add the class is-loading
setInterval(function () { setInterval(function () {
if (textareaControl === null) { if (textareaControl === null) {
return; return;
}
if (textareaControl.classList.contains('htmx-request')) {
textareaControl.classList.add('is-loading');
} else {
textareaControl.classList.remove('is-loading');
}
}, 10);
function updateIcons() {
if (window.innerWidth < 450) {
var elements = document.getElementsByClassName('message-icon');
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('is-48x48');
elements[i].classList.add('is-32x32');
elements[i].parentElement.style = "padding-right: 0;";
}
} else {
var elements = document.getElementsByClassName('message-icon');
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('is-32x32');
elements[i].classList.add('is-48x48');
elements[i].parentElement.style = "";
}
}
} }
if (textareaControl.classList.contains('htmx-request')) {
// Run when the element is added textareaControl.classList.add('is-loading');
document.addEventListener('htmx:afterSwap', updateIcons); } else {
textareaControl.classList.remove('is-loading');
// Run every 0.01s
setInterval(updateIcons, 100);
document.getElementById('chat-input-textarea').addEventListener('focus', function () {
var buttons = document.getElementsByClassName('to-reduce-opacity');
for (var i = 0; i < buttons.length; i++) {
buttons[i].style.opacity = 0.2;
}
});
document.getElementById('chat-input-textarea').addEventListener('blur', function () {
var buttons = document.getElementsByClassName('to-reduce-opacity');
for (var i = 0; i < buttons.length; i++) {
buttons[i].style.opacity = 1;
}
});
const textarea = document.querySelector('#chat-input-textarea');
textarea.addEventListener('keydown', handleTextareaKeydown);
document.addEventListener('htmx:afterSwap', toggleSendButton)
function toggleSendButton() {
// check if generate-multiple-messages exists
var generateMultipleMessages = document.getElementById('generate-multiple-messages');
document.getElementById('chat-input-send-btn').disabled = textarea.value.trim().length === 0 || document.getElementsByClassName('selected icon-llm').length === 0 || generateMultipleMessages !== null;
// Do the same for all element with an id that start with redo-button- or edit-button-
const buttons = document.querySelectorAll('[id^="redo-button-"], [id^="edit-button-"]');
if (document.getElementsByClassName('selected icon-llm').length === 0 || generateMultipleMessages !== null) {
buttons.forEach(button => button.classList.add('is-static'));
} else {
buttons.forEach(button => button.classList.remove('is-static'));
}
} }
}, 10);
function onClickSendButton() { function updateIcons() {
// TODO: Add the message placeholder using WASM if (window.innerWidth < 450) {
messagesPlaceholderHTML = generatePlaceholder(textarea.value); var elements = document.getElementsByClassName('message-icon');
document.getElementById('chat-messages').insertAdjacentHTML('beforeend', messagesPlaceholderHTML); for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('is-48x48');
setTimeout(function () { elements[i].classList.add('is-32x32');
textarea.value = ''; elements[i].parentElement.style = "padding-right: 0;";
toggleSendButton(); }
}, 20); } else {
var elements = document.getElementsByClassName('message-icon');
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('is-32x32');
elements[i].classList.add('is-48x48');
elements[i].parentElement.style = "";
}
} }
}
function updateIcon(icon, ConversationAreaId) { // Run when the element is added
var selectedIcon = document.getElementById('selectedIcon-' + ConversationAreaId); document.addEventListener('htmx:afterSwap', updateIcons);
selectedIcon.src = icon;
}
function handleTextareaKeydown(event) { // Run every 0.01s
if (event.metaKey && event.key === 'Enter' && event.target === textarea && textarea.value.trim() !== '' && document.getElementsByClassName('selected icon-llm').length !== 0) { setInterval(updateIcons, 100);
// Check if the cursor is in the textarea
// Trigger the same action as the send button document.getElementById('chat-input-textarea').addEventListener('focus', function () {
document.getElementById('chat-input-send-btn').click(); var buttons = document.getElementsByClassName('to-reduce-opacity');
} for (var i = 0; i < buttons.length; i++) {
buttons[i].style.opacity = 0.2;
} }
</script> });
document.getElementById('chat-input-textarea').addEventListener('blur', function () {
var buttons = document.getElementsByClassName('to-reduce-opacity');
for (var i = 0; i < buttons.length; i++) {
buttons[i].style.opacity = 1;
}
});
const textarea = document.querySelector('#chat-input-textarea');
textarea.addEventListener('keydown', handleTextareaKeydown);
document.addEventListener('htmx:afterSwap', toggleSendButton)
function toggleSendButton() {
// check if generate-multiple-messages exists
var generateMultipleMessages = document.getElementById('generate-multiple-messages');
document.getElementById('chat-input-send-btn').disabled = textarea.value.trim().length === 0 || document.getElementsByClassName('selected icon-llm').length === 0 || generateMultipleMessages !== null;
// Do the same for all element with an id that start with redo-button- or edit-button-
const buttons = document.querySelectorAll('[id^="redo-button-"], [id^="edit-button-"]');
if (document.getElementsByClassName('selected icon-llm').length === 0 || generateMultipleMessages !== null) {
buttons.forEach(button => button.classList.add('is-static'));
} else {
buttons.forEach(button => button.classList.remove('is-static'));
}
}
function onClickSendButton() {
// TODO: Add the message placeholder using WASM
messagesPlaceholderHTML = generatePlaceholder(textarea.value);
document.getElementById('chat-messages').insertAdjacentHTML('beforeend', messagesPlaceholderHTML);
setTimeout(function () {
textarea.value = '';
toggleSendButton();
}, 20);
}
function updateIcon(icon, ConversationAreaId) {
var selectedIcon = document.getElementById('selectedIcon-' + ConversationAreaId);
selectedIcon.src = icon;
}
function handleTextareaKeydown(event) {
if (event.metaKey && event.key === 'Enter' && event.target === textarea && textarea.value.trim() !== '' && document.getElementsByClassName('selected icon-llm').length !== 0) {
// Check if the cursor is in the textarea
// Trigger the same action as the send button
document.getElementById('chat-input-send-btn').click();
}
}
</script>

View File

@ -3,55 +3,30 @@
To use JADE, you need to sign up for an API key. You can enter an API key in To use JADE, you need to sign up for an API key. You can enter an API key in
the settings menu. Once enter you get access to all models from this provider. the settings menu. Once enter you get access to all models from this provider.
</p> </p>
<a <a class="button is-small is-primary is-outlined mt-1"
class="button is-small is-primary is-outlined mt-1" href="https://openai.com/index/openai-api/"
href="https://openai.com/index/openai-api/" target="_blank">Get OpenAI API key</a>
target="_blank" <a class="button is-small is-primary is-outlined mt-1"
> href="https://console.anthropic.com/"
Get OpenAI API key target="_blank">Get Anthropic API key</a>
</a> <a class="button is-small is-primary is-outlined mt-1"
<a href="https://console.mistral.ai/"
class="button is-small is-primary is-outlined mt-1" target="_blank">Get Mistral API key</a>
href="https://console.anthropic.com/" <a class="button is-small is-primary is-outlined mt-1"
target="_blank" href="https://console.groq.com/"
> target="_blank">Get Groq API key</a>
Get Anthropic API key <a class="button is-small is-primary is-outlined mt-1"
</a> href="https://aistudio.google.com/app/apikey"
<a target="_blank">Get Google API key</a>
class="button is-small is-primary is-outlined mt-1" <a class="button is-small is-primary is-outlined mt-1"
href="https://console.mistral.ai/" href="https://build.nvidia.com/explore/discover"
target="_blank" target="_blank">Get Nvidia NIM API key</a>
> <a class="button is-small is-primary is-outlined mt-1"
Get Mistral API key href="https://docs.perplexity.ai/docs/getting-started"
</a> target="_blank">Get Perplexity API key</a>
<a <a class="button is-small is-primary is-outlined mt-1"
class="button is-small is-primary is-outlined mt-1" href="https://fireworks.ai/login"
href="https://console.groq.com/" target="_blank">Get Fireworks API key</a>
target="_blank"
>
Get Groq API key
</a>
<a
class="button is-small is-primary is-outlined mt-1"
href="https://aistudio.google.com/app/apikey"
target="_blank"
>
Get Google API key
</a>
<a
class="button is-small is-primary is-outlined mt-1"
href="https://docs.perplexity.ai/docs/getting-started"
target="_blank"
>
Get Perplexity API key
</a>
<a
class="button is-small is-primary is-outlined mt-1"
href="https://fireworks.ai/login"
target="_blank"
>
Get Fireworks API key
</a>
<h2>Conversations</h2> <h2>Conversations</h2>
<p> <p>
@ -68,10 +43,10 @@
</p> </p>
<p> <p>
To create a new bot, click the "+" button, enter a name and a model, and To create a new bot, click the "+" button, enter a name and a model, and
optionally set a temperature and a system prompt. Once created, you can select optionally set a temperature, a max tokens and a system prompt. Once created,
a bot by clicking on it and reorder it by dragging. Hold SHIFT and click to you can select a bot by clicking on it and reorder it by dragging. Hold SHIFT
select multiple bots. You can delete selected bots by clicking the trash can and click to select multiple bots. You can delete selected bots by clicking
icon. the trash can icon.
</p> </p>
<h2>Multi-Models</h2> <h2>Multi-Models</h2>

View File

@ -1,114 +1,124 @@
<div class="message-bot mt-3" id="msg-{{ ConversationAreaId }}"> <div class="message-bot mt-3" id="msg-{{ ConversationAreaId }}">
<div class="columns is-mobile"> <div class="columns is-mobile">
{% if not DontShowName %} {% if not DontShowName %}
<div class="column is-narrow" id="icon-column"> <div class="column is-narrow" id="icon-column">
<!-- Left column with the icon --> <!-- Left column with the icon -->
{% if IsPlaceholder %} {% if IsPlaceholder %}
<figure class="image is-48x48 message-icon" style="flex-shrink: 0;" <figure class="image is-48x48 message-icon"
sse-swap="swapIcon-{{ ConversationAreaId }}"> style="flex-shrink: 0"
<img src="icons/bouvai2.png" alt="User Image" id="selectedIcon-{{ ConversationAreaId }}"> sse-swap="swapIcon-{{ ConversationAreaId }}">
</figure> <img src="icons/bouvai2.png"
alt="User Image"
id="selectedIcon-{{ ConversationAreaId }}">
</figure>
{% else %} {% else %}
{% for message in Messages %}
{% if not message.Hidden %}
<figure class="image is-48x48 message-icon" style="flex-shrink: 0;">
<img src="{{ message.Icon }}"
alt="User Image"
id="selectedIcon-{{ ConversationAreaId }}">
</figure>
{% endif %}
{% endfor %}
{% endif %}
</div>
{% endif %}
<div class="column" id="content-column" style="width: 100px;">
{% if not IsPlaceholder %}
<div class="{% if not notFlex %} is-flex {% endif %} is-align-items-start">
<div class="message-content"
style="width: 100%;
overflow-y: hidden"
id="content-{{ ConversationAreaId }}">
{% for message in Messages %}
{% if not message.Hidden %}
{% if not DontShowName %}
<div class="message-header">
<p>
<strong>{{ message.Name }}</strong> <small>{{ message.ModelID }}</small>
</p>
</div>
{% endif %}
<div class="message-body">
<div class="content" style="overflow-x: auto; width: 100%;">{{ message.Content | safe }}</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
<div class="is-flex is-justify-content mt-2">
{% if not NotClickable %}
<button class="button is-small is-primary message-button is-outlined mr-5"
onclick="copyToClipboard(this)">
<span class="icon">
<i class="fa-solid fa-copy"></i>
</span>
</button>
{% for message in Messages %} {% for message in Messages %}
{% if not message.Hidden %} <button class="button is-small is-primary message-button is-outlined mr-1"
<figure class="image is-48x48 message-icon" style="flex-shrink: 0;"> hx-get="/messageContent?id={{ message.Id }}"
<img src="{{ message.Icon }}" alt="User Image" id="selectedIcon-{{ ConversationAreaId }}"> hx-target="#content-{{ ConversationAreaId }}"
</figure> onclick="updateIcon('{{ message.Icon }}', '{{ ConversationAreaId }}')"
{% endif %} title="{{ message.Name }}">
<span class="icon is-small">
<img src="{{ message.Icon }}"
alt="{{ message.Name }}"
style="max-height: 100%;
max-width: 100%">
</span>
</button>
{% endfor %} {% endfor %}
{% endif %}
{% endif %}
</div> </div>
{% endif %}
<div class="column" id="content-column" style="width: 100px;"> {% elif IsPlaceholder %}
{% if not IsPlaceholder %} <div class="is-flex is-align-items-start">
<div class="{% if not notFlex%} is-flex {% endif %} is-align-items-start"> <div class="message-content"
<div class="message-content" style="width: 100%; overflow-y: hidden;" id="content-{{ ConversationAreaId }}"
id="content-{{ ConversationAreaId }}"> sse-swap="swapContent-{{ ConversationAreaId }}">
{% for message in Messages %} <hx hx-trigger="load" hx-get="/generateMultipleMessages" id="generate-multiple-messages"></hx>
{% if not message.Hidden %} <div class='message-header'>
{% if not DontShowName %} <p>Waiting...</p>
<div class="message-header">
<p>
<strong>{{ message.Name }}</strong> <small>{{ message.ModelID }}</small>
</p>
</div>
{% endif %}
<div class="message-body">
<div class="content" style="overflow-x: auto; width: 100%;">
{{ message.Content | safe }}
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div> </div>
<div class="is-flex is-justify-content mt-2"> <div class="message-body">
{% if not NotClickable %} <div class="content">
<button class="button is-small is-primary message-button is-outlined mr-5" <br>
onclick="copyToClipboard(this)"> <img src="/puff.svg" />
<span class="icon"> </div>
<i class="fa-solid fa-copy"></i>
</span>
</button>
{% for message in Messages %}
<button class="button is-small is-primary message-button is-outlined mr-1"
hx-get="/messageContent?id={{ message.Id }}" hx-target="#content-{{ ConversationAreaId }}"
onclick="updateIcon('{{ message.Icon }}', '{{ ConversationAreaId }}')" title="{{ message.Name }}">
<span class="icon is-small">
<img src="{{ message.Icon }}" alt="{{ message.Name }}"
style="max-height: 100%; max-width: 100%;">
</span>
</button>
{% endfor %}
{% endif %}
</div> </div>
</div>
</div>
{% elif IsPlaceholder %} <div class="is-flex is-justify-content mt-2">
<div class="is-flex is-align-items-start"> <button class="button is-small is-primary message-button is-outlined mr-5"
<div class="message-content" id="content-{{ ConversationAreaId }}" onclick="copyToClipboard(this)">
sse-swap="swapContent-{{ ConversationAreaId }}"> <span class="icon">
<hx hx-trigger="load" hx-get="/generateMultipleMessages" id="generate-multiple-messages"></hx> <i class="fa-solid fa-copy"></i>
<div class='message-header'> </span>
<p> </button>
Waiting...
</p>
</div>
<div class="message-body">
<div class="content">
<br>
<img src="/puff.svg" />
</div>
</div>
</div>
</div>
<div class="is-flex is-justify-content mt-2"> {% for selectedLLM in SelectedLLMs %}
<button class="button is-small is-primary message-button is-outlined mr-5" <button disable
onclick="copyToClipboard(this)"> class="button is-small is-primary message-button is-outlined mr-1"
<span class="icon"> sse-swap="swapSelectionBtn-{{ selectedLLM.ID.String() }}"
<i class="fa-solid fa-copy"></i> hx-swap="outerHTML"
</span>
</button>
{% for selectedLLM in SelectedLLMs %}
<button disable class="button is-small is-primary message-button is-outlined mr-1"
sse-swap="swapSelectionBtn-{{ selectedLLM.ID.String() }}" hx-swap="outerHTML"
hx-target="this"> hx-target="this">
<span class="icon is-small"> <span class="icon is-small">
<!--img src="icons/{{ selectedLLM.Company }}.png" alt="{{ selectedLLM.Name }}" <!--img src="icons/{{ selectedLLM.Company }}.png" alt="{{ selectedLLM.Name }}"
style="max-height: 100%; max-width: 100%;"--> style="max-height: 100%; max-width: 100%;"-->
<img src="/puff.svg" /> <img src="/puff.svg" />
</span> </span>
</button> </button>
{% endfor %} {% endfor %}
</div>
{% endif %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div>

View File

@ -1,56 +1,63 @@
<div class="message-user mt-3" id="{{ ID }}"> <div class="message-user mt-3" id="{{ ID }}">
<div class="columns is-mobile"> <div class="columns is-mobile">
<div class="column is-narrow" id="icon-column"> <div class="column is-narrow" id="icon-column">
<figure class="image is-48x48" style="flex-shrink: 0;"> <figure class="image is-48x48" style="flex-shrink: 0;">
<img src="icons/bouvai2.png" alt="User Image"> <img src="icons/bouvai2.png" alt="User Image">
</figure> </figure>
</div>
<div class="column" id="content-column">
<div class="is-flex is-align-items-start mr-5" style="width: 100%;">
<div class="message-content" style="width: 100%;">
<div class="message-header">
<p>
You
</p>
</div>
<div class="message-body" style="width: 100%;">
<div class="content" id="content-{{ ID }}" style="width: 100%;">
<div class="field" style="width: 100%;">
<div class="control" style="width: 100%;">
<textarea class="textarea is-small has-fixed-size mt-2"
placeholder="Enter your message here" rows="{{ Rows }}"
style="background-color: transparent; width: 100%;"
name="message">{{ Content }}</textarea>
</div>
</div>
<div class="field is-grouped is-flex is-justify-content-flex-end mb-3">
<div class="control">
<button hx-get="/userMessage?id={{ ID }}" hx-target="closest .message-user"
class="button is-danger is-outlined is-small">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</button>
</div>
<div class="control">
<div style="display: none;" hx-trigger="click from:#edit-button-{{ ID }}"
hx-target="next .message-bot" hx-swap="outerHTML" hx-get="/empty"></div>
<button class="button is-success is-outlined is-small"
hx-post="/editMessage?id={{ ID }}" hx-target="closest .message-user"
hx-include="[name='message']"
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}" hx-swap="outerHTML"
id="edit-button-{{ ID }}" hx-trigger="click">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div>
<div class="column" id="content-column">
<div class="is-flex is-align-items-start mr-5" style="width: 100%;">
<div class="message-content" style="width: 100%;">
<div class="message-header">
<p>You</p>
</div>
<div class="message-body" style="width: 100%;">
<div class="content" id="content-{{ ID }}" style="width: 100%;">
<div class="field" style="width: 100%;">
<div class="control" style="width: 100%;">
<textarea class="textarea is-small has-fixed-size mt-2"
placeholder="Enter your message here"
rows="{{ Rows }}"
style="background-color: transparent;
width: 100%"
name="message">{{ Content }}</textarea>
</div>
</div>
<div class="field is-grouped is-flex is-justify-content-flex-end mb-3">
<div class="control">
<button hx-get="/userMessage?id={{ ID }}"
hx-target="closest .message-user"
class="button is-danger is-outlined is-small">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</button>
</div>
<div class="control">
<div style="display: none"
hx-trigger="click from:#edit-button-{{ ID }}"
hx-target="next .message-bot"
hx-swap="outerHTML"
hx-get="/empty"></div>
<button class="button is-success is-outlined is-small"
hx-post="/editMessage?id={{ ID }}"
hx-target="closest .message-user"
hx-include="[name='message']"
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}"
hx-swap="outerHTML"
id="edit-button-{{ ID }}"
hx-trigger="click">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,47 +1,54 @@
<div class="message-user mt-3" id="msg-{{ ID }}"> <div class="message-user mt-3" id="msg-{{ ID }}">
<div class="columns is-mobile"> <div class="columns is-mobile">
<div class="column is-narrow" id="icon-column"> <div class="column is-narrow" id="icon-column">
<figure class="image is-48x48 message-icon" style="flex-shrink: 0;"> <figure class="image is-48x48 message-icon" style="flex-shrink: 0;">
<img src="icons/bouvai2.png" alt="User Image"> <img src="icons/bouvai2.png" alt="User Image">
</figure> </figure>
</div>
<div class="column" id="content-column" style="width: 100px;">
<div class="is-flex is-align-items-start">
<div class="message-content" style="width: 100%; overflow-y: hidden;">
<div class="message-header">
<p>
You
</p>
</div>
<div class="message-body">
<div class="content" style="overflow-x: auto; width: 100%;" id="content-{{ ID }}">
{{ Content | safe }}
</div>
</div>
</div>
</div>
<div class="is-flex is-justify-content mt-2">
<button id="delete-button-{{ ID }}" hx-post="/deleteMessage?id={{ ID }}" hx-swap="outerHTML"
hx-target="#chat-container" class="button is-small is-danger message-button is-outlined mr-5">
<span class="icon">
<i class="fa-solid fa-trash"></i>
</span>
</button>
<button id="redo-button-{{ ID }}" class="button is-primary is-small message-button is-outlined mr-1 is-static"
hx-post="/redoMessage?id={{ ID }}" hx-swap="innerHTML settle:200ms" hx-target="next .message-bot"
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}">
<span class="icon">
<i class="fa-solid fa-arrows-rotate"></i>
</span>
</button>
<button hx-get="/editMessageForm?id={{ ID }}" hx-target="closest .message-user"
id="edit-button-{{ ID }}" class="button is-primary is-small message-button is-outlined is-static mr-5">
<span class="icon">
<i class="fa-solid fa-pen"></i>
</span>
</button>
</div>
</div>
</div> </div>
</div>
<div class="column" id="content-column" style="width: 100px;">
<div class="is-flex is-align-items-start">
<div class="message-content" style="width: 100%; overflow-y: hidden;">
<div class="message-header">
<p>You</p>
</div>
<div class="message-body">
<div class="content"
style="overflow-x: auto;
width: 100%"
id="content-{{ ID }}">{{ Content | safe }}</div>
</div>
</div>
</div>
<div class="is-flex is-justify-content mt-2">
<button id="delete-button-{{ ID }}"
hx-post="/deleteMessage?id={{ ID }}"
hx-swap="outerHTML"
hx-target="#chat-container"
class="button is-small is-danger message-button is-outlined mr-5">
<span class="icon">
<i class="fa-solid fa-trash"></i>
</span>
</button>
<button id="redo-button-{{ ID }}"
class="button is-primary is-small message-button is-outlined mr-1 is-static"
hx-post="/redoMessage?id={{ ID }}"
hx-swap="innerHTML settle:200ms"
hx-target="next .message-bot"
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}">
<span class="icon">
<i class="fa-solid fa-arrows-rotate"></i>
</span>
</button>
<button hx-get="/editMessageForm?id={{ ID }}"
hx-target="closest .message-user"
id="edit-button-{{ ID }}"
class="button is-primary is-small message-button is-outlined is-static mr-5">
<span class="icon">
<i class="fa-solid fa-pen"></i>
</span>
</button>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,12 @@
<button class="button is-small is-primary message-button is-outlined mr-1" hx-get="/messageContent?id={{ message.Id }}" <button class="button is-small is-primary message-button is-outlined mr-1"
hx-target="#content-{{ ConversationAreaId }}" onclick="updateIcon('{{ message.Icon }}', '{{ ConversationAreaId }}')" hx-get="/messageContent?id={{ message.Id }}"
title="{{ message.Name }}"> hx-target="#content-{{ ConversationAreaId }}"
<span class="icon is-small"> onclick="updateIcon('{{ message.Icon }}', '{{ ConversationAreaId }}')"
<img src="{{ message.Icon }}" alt="{{ message.Name }}" style="max-height: 100%; max-width: 100%;"> title="{{ message.Name }}">
</span> <span class="icon is-small">
</button> <img src="{{ message.Icon }}"
alt="{{ message.Name }}"
style="max-height: 100%;
max-width: 100%">
</span>
</button>

View File

@ -1,78 +1,96 @@
<div class="dropdown is-up is-right {% if IsActive %} is-active {% endif %}" id="conversation-dropdown"> <div class="dropdown is-up is-right {% if IsActive %}is-active{% endif %}"
<div class="dropdown-trigger"> id="conversation-dropdown">
<button class="button is-small to-reduce-opacity" aria-haspopup="true" aria-controls="dropdown-menu3" <div class="dropdown-trigger">
<button class="button is-small to-reduce-opacity"
aria-haspopup="true"
aria-controls="dropdown-menu3"
onclick="this.parentElement.parentElement.classList.toggle('is-active')"> onclick="this.parentElement.parentElement.classList.toggle('is-active')">
<span class="icon"> <span class="icon">
<i class="fa-solid fa-comments"></i> <i class="fa-solid fa-comments"></i>
</span> </span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu"> <div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<div class="dropdown-item"> <div class="dropdown-item">
<div id="conversation-list"> <div id="conversation-list">
{% for Conversation in Conversations %} {% for Conversation in Conversations %}
<div class="icon-text has-text unselected icon-conv {% if Conversation.Selected %} selected {% endif %}" <div class="icon-text has-text unselected icon-conv {% if Conversation.Selected %}selected{% endif %}"
data-id="{{ Conversation.ID.String() }}" style="cursor: pointer;" data-id="{{ Conversation.ID.String() }}"
onclick="toggleConversationSelection(this, '{{ Conversation.Name }}')" style="cursor: pointer"
hx-get="/selectConversation?conversation-id={{ Conversation.ID.String() }}" hx-swap="outerHTML" onclick="toggleConversationSelection(this, '{{ Conversation.Name }}')"
hx-target="#chat-container" name="{{ Conversation.Name }}"> hx-get="/selectConversation?conversation-id={{ Conversation.ID.String() }}"
<span>{{ Conversation.Name }}</span> hx-swap="outerHTML"
</div> hx-target="#chat-container"
{% endfor %} name="{{ Conversation.Name }}">
</div> <span>{{ Conversation.Name }}</span>
<input class="input is-small mt-2 is-hidden" type="text" id="conversation-name-input"
name="conversation-name-input" placeholder="Conversation name" autocomplete="off">
<div class="is-flex is-justify-content-space-between mt-4 ">
<button class="button is-small is-danger {% if SelectedIsDefault %} is-hidden {% endif %}"
id="delete-conversation-button" hx-get="/deleteConversation" hx-swap="outerHTML"
hx-target="#conversation-dropdown" hx-vals="js:{conversationId: findSelectedConversationID()}">
<span class="icon">
<i class="fa-solid fa-trash"></i>
</span>
</button>
<button
class="button is-small is-primary is-outlined {% if not SelectedIsDefault %} is-hidden {% endif %}"
id="archive-default-conversation-button">
<span class="icon">
<i class="fa-solid fa-box-archive"></i>
</span>
</button>
<button class="button is-small is-danger is-outlined is-hidden" id="cancel-conversation-button">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</button>
<div class="is-flex is-justify-content-flex-end">
<button class="button is-small is-success is-outlined" id="create-conversation-button">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
</button>
<button class="button is-small is-success is-outlined is-hidden"
id="confirm-conversation-button" hx-get="/createConversation"
hx-include="[name='conversation-name-input']" hx-swap="outerHTML"
hx-target="#conversation-dropdown">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
<button class="button is-small is-success is-outlined is-hidden"
id="confirm-archive-default-conversation-button" hx-post="/archiveDefaultConversation"
hx-include="[name='conversation-name-input']" hx-swap="outerHTML"
hx-target="#conversation-dropdown">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
</div>
</div>
</div> </div>
{% endfor %}
</div> </div>
</div> <input class="input is-small mt-2 is-hidden"
type="text"
id="conversation-name-input"
name="conversation-name-input"
placeholder="Conversation name"
autocomplete="off">
<div class="is-flex is-justify-content-space-between mt-4 ">
<button class="button is-small is-danger {% if SelectedIsDefault %}is-hidden{% endif %}"
id="delete-conversation-button"
hx-get="/deleteConversation"
hx-swap="outerHTML"
hx-target="#conversation-dropdown"
hx-vals="js:{conversationId: findSelectedConversationID()}">
<span class="icon">
<i class="fa-solid fa-trash"></i>
</span>
</button>
<button class="button is-small is-primary is-outlined {% if not SelectedIsDefault %}is-hidden{% endif %}"
id="archive-default-conversation-button">
<span class="icon">
<i class="fa-solid fa-box-archive"></i>
</span>
</button>
<button class="button is-small is-danger is-outlined is-hidden"
id="cancel-conversation-button">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</button>
<div class="is-flex is-justify-content-flex-end">
<script> <button class="button is-small is-success is-outlined"
id="create-conversation-button">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
</button>
<button class="button is-small is-success is-outlined is-hidden"
id="confirm-conversation-button"
hx-get="/createConversation"
hx-include="[name='conversation-name-input']"
hx-swap="outerHTML"
hx-target="#conversation-dropdown">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
<button class="button is-small is-success is-outlined is-hidden"
id="confirm-archive-default-conversation-button"
hx-post="/archiveDefaultConversation"
hx-include="[name='conversation-name-input']"
hx-swap="outerHTML"
hx-target="#conversation-dropdown">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
function findSelectedConversationID() { function findSelectedConversationID() {
return document.getElementsByClassName('icon-conv selected')[0].getAttribute('data-id'); return document.getElementsByClassName('icon-conv selected')[0].getAttribute('data-id');
} }
@ -172,5 +190,5 @@
document.getElementById('confirm-archive-default-conversation-button').classList.add('is-hidden'); document.getElementById('confirm-archive-default-conversation-button').classList.add('is-hidden');
} }
}) })
</script> </script>
</div> </div>

View File

@ -1,96 +1,148 @@
<div class="dropdown is-up is-right" id="models-dropdown"> <div class="dropdown is-up is-right" id="models-dropdown">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<button class="button is-small to-reduce-opacity" aria-haspopup="true" aria-controls="dropdown-menu3" <button class="button is-small to-reduce-opacity"
aria-haspopup="true"
aria-controls="dropdown-menu3"
onclick="this.parentElement.parentElement.classList.toggle('is-active')"> onclick="this.parentElement.parentElement.classList.toggle('is-active')">
<span class="icon"><i class="fa-solid fa-robot"></i></span> <span class="icon"><i class="fa-solid fa-robot"></i></span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu"> <div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<div class="dropdown-item" id="models-list"> <div class="dropdown-item" id="models-list">
<div id="llm-list"> <div id="llm-list">
{% for LLM in LLMs %} {% for LLM in LLMs %}
<div class="icon-text has-text unselected icon-llm" data-id="{{ LLM.ID.String() }}" <div class="icon-text has-text unselected icon-llm"
style="cursor: pointer;" onclick="toggleSelection(this)"> data-id="{{ LLM.ID.String() }}"
<span class="icon"> style="cursor: pointer"
<img src="{{ LLM.Model.Company.Icon }}" /> onclick="toggleSelection(this)">
</span> <span class="icon">
<span>{{ LLM.Name }}</span> <img src="{{ LLM.Model.Company.Icon }}" />
</div> </span>
{% endfor %} <span>{{ LLM.Name }}</span>
</div> </div>
<div class="is-flex is-justify-content-space-between mt-4"> {% endfor %}
<button disabled class="button is-small is-danger" hx-get="/deleteLLM" hx-swap="outerHTML" </div>
hx-target="#models-dropdown" hx-confirm="Are you sure?" hx-trigger="click" <div class="is-flex is-justify-content-space-between mt-4">
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}" id="delete-model-button"> <button disabled
<span class="icon"> class="button is-small is-danger"
<i class="fa-solid fa-trash"></i> hx-get="/deleteLLM"
</span> hx-swap="outerHTML"
</button> hx-target="#models-dropdown"
<div> hx-confirm="Are you sure?"
<!--button disabled class="button is-small is-primary mr-2 ml-5"> hx-trigger="click"
hx-vals="js:{selectedLLMIds: getSelectedModelsIDs()}"
id="delete-model-button">
<span class="icon">
<i class="fa-solid fa-trash"></i>
</span>
</button>
<div>
<!--button disabled class="button is-small is-primary mr-2 ml-5">
<span class="icon"> <span class="icon">
<i class="fa-solid fa-pen"></i> <i class="fa-solid fa-pen"></i>
</span> </span>
</button--> </button -->
<button class="button is-small is-success is-outlined" id="create-model-button"> <button class="button is-small is-success is-outlined"
<span class="icon"> id="create-model-button">
<i class="fa-solid fa-plus"></i> <span class="icon">
</span> <i class="fa-solid fa-plus"></i>
</button> </span>
</div> </button>
</div> </div>
</div>
<div class="dropdown-item is-hidden" id="models-creation">
<form id="create-model-form" hx-post="/createLLM" hx-target="#models-dropdown" hx-swap="outerHTML">
<input class="input is-small mb-3" type="text" id="model-name-input" name="model-name-input"
placeholder="Model name" autocomplete="off">
<div class="select is-fullwidth is-small mb-3" id="model-id-input">
<select name="selectedLLMId">
{% for modelInfo in ModelInfos %}
<option value="{{ modelInfo.ModelID }}">{{ modelInfo.Company.Name }} - {{ modelInfo.Name }}</option>
{% endfor %}
<option value="{% if IsPremium %}custom{% else %}none{% endif %}">Custom Endpoints</option>
</select>
</div>
<p class="is-hidden" style="color: red;" id="endpoint-error">
<small>Need premium subscription</small>
</p>
<input class="input is-small mb-3 is-hidden" type="text" id="model-cid-input" name="model-cid-input"
placeholder="Model id" autocomplete="off">
<input class="input is-small mb-3 is-hidden" type="text" id="model-url-input" name="model-url-input"
placeholder="URL (with /v1/chat/completions)" autocomplete="off">
<input class="input is-small mb-3 is-hidden" type="text" id="model-key-input" name="model-key-input"
placeholder="Token" autocomplete="off">
<p><small>Temperature:</small></p>
<input class="slider is-small mb-3" step="0.05" min="0" max="2" value="0" type="range"
id="temperature-slider" name="temperature-slider">
<output id="temperature-slider-output">0</output>
<p><small>Max token (optional):</small></p>
<input class="input is-small mb-3" type="number" id="max-token-input" name="max-token-input"
placeholder="" autocomplete="off">
<p><small>System prompt (optional):</small></p>
<textarea class="textarea is-small mb-5 has-fixed-size" id="model-prompt-input"
name="model-prompt-input"></textarea>
<div class="is-flex is-justify-content-flex-end">
<button class="button is-small is-danger is-outlined mr-3" id="cancel-create-model-button"
type="button">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</button>
<button disabled class="button is-small is-success" id="confirm-create-model-button"
type="submit">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
</div>
</form>
</div>
</div> </div>
</div>
<div class="dropdown-item is-hidden" id="models-creation">
<form id="create-model-form"
hx-post="/createLLM"
hx-target="#models-dropdown"
hx-swap="outerHTML">
<input class="input is-small mb-3"
type="text"
id="model-name-input"
name="model-name-input"
placeholder="Model name"
autocomplete="off">
<div class="select is-fullwidth is-small mb-3" id="model-id-input">
<select name="selectedLLMId">
{% for modelInfo in ModelInfos %}
<option value="{{ modelInfo.ModelID }}">
{{ modelInfo.Company.Name }} - {{ modelInfo.Name }}
</option>
{% endfor %}
<option value="{% if IsPremium %}custom{% else %}none{% endif %}">Custom Endpoints</option>
</select>
</div>
<p class="is-hidden" style="color: red;" id="endpoint-error">
<small>Need premium subscription</small>
</p>
<input class="input is-small mb-3 is-hidden"
type="text"
id="model-cid-input"
name="model-cid-input"
placeholder="Model id"
autocomplete="off">
<input class="input is-small mb-3 is-hidden"
type="text"
id="model-url-input"
name="model-url-input"
placeholder="URL (with /v1/chat/completions)"
autocomplete="off">
<input class="input is-small mb-3 is-hidden"
type="text"
id="model-key-input"
name="model-key-input"
placeholder="Token"
autocomplete="off">
<p>
<small>Temperature:</small>
</p>
<input class="slider is-small mb-3"
step="0.05"
min="0"
max="2"
value="0"
type="range"
id="temperature-slider"
name="temperature-slider">
<output id="temperature-slider-output">0</output>
<p>
<small>Max token (optional):</small>
</p>
<input class="input is-small mb-3"
type="number"
id="max-token-input"
name="max-token-input"
placeholder=""
autocomplete="off">
<p>
<small>System prompt (optional):</small>
</p>
<textarea class="textarea is-small mb-5 has-fixed-size"
id="model-prompt-input"
name="model-prompt-input"></textarea>
<div class="is-flex is-justify-content-flex-end">
<button class="button is-small is-danger is-outlined mr-3"
id="cancel-create-model-button"
type="button">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</button>
<button disabled
class="button is-small is-success"
id="confirm-create-model-button"
type="submit">
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
</button>
</div>
</form>
</div>
</div> </div>
<script> </div>
<script>
var sortable = new Sortable(document.getElementById('llm-list'), { var sortable = new Sortable(document.getElementById('llm-list'), {
animation: 150, animation: 150,
onEnd: function (evt) { onEnd: function (evt) {
@ -173,5 +225,5 @@
event.preventDefault(); event.preventDefault();
} }
}); });
</script> </script>
</div> </div>

View File

@ -1,170 +1,215 @@
<div class="dropdown is-up is-right" id="settings-dropdown"> <div class="dropdown is-up is-right" id="settings-dropdown">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<button class="button is-small {% if not AnyExists %} is-danger{% endif %} to-reduce-opacity" <button class="button is-small {% if not AnyExists %}is-danger{% endif %} to-reduce-opacity"
aria-haspopup="true" aria-controls="dropdown-menu3" aria-haspopup="true"
aria-controls="dropdown-menu3"
onclick="this.parentElement.parentElement.classList.toggle('is-active')"> onclick="this.parentElement.parentElement.classList.toggle('is-active')">
<span class="icon"><i class="fa-solid fa-bars"></i></i></span> <span class="icon"><i class="fa-solid fa-bars"></i></i></span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu"> <div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<div class="dropdown-item"> <div class="dropdown-item">
<div class="field"> <div class="field">
<form id="api-keys-form" hx-post="/addKeys" hx-trigger="submit" hx-target="#api-keys-status"> <form id="api-keys-form"
<div class="field has-addons is-hidden" id="openai-field"> hx-post="/addKeys"
<p class="control has-icons-left is-expanded"> hx-trigger="submit"
<input class="input is-small {% if OpenaiExists %}is-success{% endif %}" type="text" hx-target="#api-keys-status">
{%if not IsLogin %}disabled{% endif %} placeholder="OpenAI API key" <div class="field has-addons is-hidden" id="openai-field">
name="openai_key" autocomplete="off"> <p class="control has-icons-left is-expanded">
<span class="icon is-small is-left"> <input class="input is-small {% if OpenaiExists %}is-success{% endif %}"
<i class="fas fa-lock"></i> type="text"
</span> {% if not IsLogin %}disabled{% endif %}
</p> placeholder="OpenAI API key"
</div> name="openai_key"
<div class="field has-addons is-hidden" id="anthropic-field"> autocomplete="off">
<p class="control has-icons-left is-expanded"> <span class="icon is-small is-left">
<input class="input is-small {% if AnthropicExists %}is-success{% endif %}" type="text" <i class="fas fa-lock"></i>
{% if not IsLogin %}disabled{% endif %} placeholder="Anthropic API key" </span>
name="anthropic_key" autocomplete="off"> </p>
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="mistral-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if MistralExists %}is-success{% endif %}" type="text"
{%if not IsLogin %}disabled{% endif %} placeholder="Mistral API key"
name="mistral_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="groq-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GroqExists %}is-success{% endif %}" type="text"
placeholder="Groq API key" {%if not IsLogin %}disabled{% endif %} name="groq_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="gemini-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GoogleExists %}is-success{% endif %}" type="text"
placeholder="Google API key" name="google_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="nim-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if NimExists %}is-success{% endif %}" type="text"
placeholder="NIM API key" name="nim_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="perplexity-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if PerplexityExists %}is-success{% endif %}" type="text"
placeholder="Perplexity API key" name="perplexity_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="fireworks-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if FireworksExists %}is-success{% endif %}" type="text"
placeholder="Fireworks API key" name="fireworks_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="goose-field"
title="GooseAI chat API will be available soon">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GooseaiExists %}is-success{% endif %}" type="text"
{%if not IsLogin %}disabled{% endif %} placeholder="Gooseai API key"
name="goose_key" autocomplete="off" disabled>
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons" id="save-field">
<p class="control">
<button disabled id="save-keys-button" type="submit" class="button is-small">
<span class="icon is-small">
<i class="fas fa-check"></i>
</span>
<span>Save keys</span>
</button>
</p>
<p class="control">
<button id="toggle-keys-button" type="button"
class="button is-small {% if not AnyExists %}is-danger{% endif %}">
<span class="icon is-small">
<i class="fa-solid fa-chevron-down"></i>
</span>
</button>
</p>
</div>
</form>
<p id="api-keys-status"></p>
</div>
{% if isBasic or isPremium %}
<a class="button is-small mt-1" href="{{ StripeSubLink }}" target="_blank">
<span class="icon is-small" {% if isPremium %}style="color: #b00202" {%else%}style="color: #126d0f"
{% endif %}>
<i class="fa-solid fa-heart"></i>
</span>
<span>Manage subscription</span>
</a>
{% else %}
<a class="button is-small mt-1" hx-get="/pricingTable" hx-target="#chat-container" hx-swap="outerHTML"
hx-trigger="click">
<span class="icon is-small">
<i class="fa-solid fa-heart"></i>
</span>
<span>Subscribe to JADE</span>
</a>
{% endif %}
<a class="button is-small mt-1" hx-get="/generateTermAndService" hx-target="#chat-container"
hx-swap="outerHTML" hx-trigger="click">
<span class="icon is-small">
<i class="fa-regular fa-file-lines"></i>
</span>
<span>Terms and conditions</span>
</a>
<a class="button is-small mt-1" hx-get="/help" hx-target="#chat-container" hx-swap="outerHTML"
hx-trigger="click">
<span class="icon is-small">
<i class="fa-solid fa-info"></i>
</span>
<span>Help</span>
</a>
<a class="button is-small mt-1" href="https://www.bouvai.com/contact">
<span class="icon is-small">
<i class="fa-solid fa-address-card"></i>
</span>
<span>Contact</span>
</a>
<a class="button is-small mt-1" href="/signout">
<span class="icon is-small">
<i class="fa-solid fa-right-from-bracket"></i>
</span>
<span>Sign out</span>
</a>
</div> </div>
<div class="field has-addons is-hidden" id="anthropic-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if AnthropicExists %}is-success{% endif %}"
type="text"
{% if not IsLogin %}disabled{% endif %}
placeholder="Anthropic API key"
name="anthropic_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="mistral-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if MistralExists %}is-success{% endif %}"
type="text"
{% if not IsLogin %}disabled{% endif %}
placeholder="Mistral API key"
name="mistral_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="groq-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GroqExists %}is-success{% endif %}"
type="text"
placeholder="Groq API key"
{% if not IsLogin %}disabled{% endif %}
name="groq_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="gemini-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GoogleExists %}is-success{% endif %}"
type="text"
placeholder="Google API key"
name="google_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="nim-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if NimExists %}is-success{% endif %}"
type="text"
placeholder="NIM API key"
name="nim_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="perplexity-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if PerplexityExists %}is-success{% endif %}"
type="text"
placeholder="Perplexity API key"
name="perplexity_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden" id="fireworks-field">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if FireworksExists %}is-success{% endif %}"
type="text"
placeholder="Fireworks API key"
name="fireworks_key"
autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons is-hidden"
id="goose-field"
title="GooseAI chat API will be available soon">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GooseaiExists %}is-success{% endif %}"
type="text"
{% if not IsLogin %}disabled{% endif %}
placeholder="Gooseai API key"
name="goose_key"
autocomplete="off"
disabled>
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons" id="save-field">
<p class="control">
<button disabled id="save-keys-button" type="submit" class="button is-small">
<span class="icon is-small">
<i class="fas fa-check"></i>
</span>
<span>Save keys</span>
</button>
</p>
<p class="control">
<button id="toggle-keys-button"
type="button"
class="button is-small {% if not AnyExists %}is-danger{% endif %}">
<span class="icon is-small">
<i class="fa-solid fa-chevron-down"></i>
</span>
</button>
</p>
</div>
</form>
<p id="api-keys-status"></p>
</div> </div>
{% if isBasic or isPremium %}
<a class="button is-small mt-1"
href="{{ StripeSubLink }}"
target="_blank">
<span class="icon is-small"
{% if isPremium %}style="color: #b00202" {% else %}style="color: #126d0f" {% endif %}>
<i class="fa-solid fa-heart"></i>
</span>
<span>Manage subscription</span>
</a>
{% else %}
<a class="button is-small mt-1"
hx-get="/pricingTable"
hx-target="#chat-container"
hx-swap="outerHTML"
hx-trigger="click">
<span class="icon is-small">
<i class="fa-solid fa-heart"></i>
</span>
<span>Subscribe to JADE</span>
</a>
{% endif %}
<a class="button is-small mt-1"
hx-get="/generateTermAndService"
hx-target="#chat-container"
hx-swap="outerHTML"
hx-trigger="click">
<span class="icon is-small">
<i class="fa-regular fa-file-lines"></i>
</span>
<span>Terms and conditions</span>
</a>
<a class="button is-small mt-1"
hx-get="/help"
hx-target="#chat-container"
hx-swap="outerHTML"
hx-trigger="click">
<span class="icon is-small">
<i class="fa-solid fa-info"></i>
</span>
<span>Help</span>
</a>
<a class="button is-small mt-1" href="https://www.bouvai.com/contact">
<span class="icon is-small">
<i class="fa-solid fa-address-card"></i>
</span>
<span>Contact</span>
</a>
<a class="button is-small mt-1" href="/signout">
<span class="icon is-small">
<i class="fa-solid fa-right-from-bracket"></i>
</span>
<span>Sign out</span>
</a>
</div>
</div> </div>
</div>
</div> </div>
<script> <script>

View File

@ -1,81 +1,92 @@
<div class="dropdown is-up is-right {% if IsActive %} is-active{% endif %}" id="usage-dropdown"> <div class="dropdown is-up is-right {% if IsActive %}is-active{% endif %}"
<div class="dropdown-trigger"> id="usage-dropdown">
<button class="button is-small to-reduce-opacity" aria-haspopup="true" aria-controls="dropdown-menu3" <div class="dropdown-trigger">
<button class="button is-small to-reduce-opacity"
aria-haspopup="true"
aria-controls="dropdown-menu3"
onclick="this.parentElement.parentElement.classList.toggle('is-active')"> onclick="this.parentElement.parentElement.classList.toggle('is-active')">
<span class="icon"><i class="fa-regular fa-money-bill-1"></i></span> <span class="icon"><i class="fa-regular fa-money-bill-1"></i></span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu"> <div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<div class="dropdown-item"> <div class="dropdown-item">
<!-- Placeholder for additional text --> <!-- Placeholder for additional text -->
<div class="content" id="usage-content" style="max-height: 30vh; overflow-y: auto;"> <div class="content"
<table class="table is-narrow is-fullwidth is-striped" style="max-width: 200px;"> id="usage-content"
<tbody> style="max-height: 30vh;
{% for usage in usages %} overflow-y: auto">
<tr> <table class="table is-narrow is-fullwidth is-striped"
<td> style="max-width: 200px">
<small>{{ usage.Key.ModelID }}</small> <tbody>
</td> {% for usage in usages %}
<td class="has-text-right totalCostCell"> <tr>
<small>{{ usage.TotalCost|floatformat:2 }}$</small> <td>
</td> <small>{{ usage.Key.ModelID }}</small>
<td class="has-text-right is-hidden totalCountCell"> </td>
<small>{{ usage.TotalCount }}</small> <td class="has-text-right totalCostCell">
</td> <small>{{ usage.TotalCost|floatformat:2 }}$</small>
</tr> </td>
{% endfor %} <td class="has-text-right is-hidden totalCountCell">
</tbody> <small>{{ usage.TotalCount }}</small>
</table> </td>
</div> </tr>
<!-- Total cost --> {% endfor %}
<div class="content totalCostCell"> </tbody>
<p> </table>
<strong>Total: </strong>
<span class="totalCost">
{{ TotalCost|floatformat:2 }}$
</span>
</p>
</div>
<!-- Total count -->
<div class="content is-hidden totalCountCell">
<p>
<strong>Total: </strong>
<span class="totalCount">
{{ TotalCount }} messages
</span>
</p>
</div>
<!-- Top buttons -->
<div class="buttons has-addons is-centered">
<button class="button is-small is-primary" title="Money Spent" id="money-spent-button">
<span class="icon ml-4 mr-4"><i class="fa-regular fa-money-bill-1"></i></span>
</button>
<button class="button is-small" title="Messages Sent" id="messages-sent-button">
<span class="icon ml-4 mr-4"><i class="fa-regular fa-envelope"></i></span>
</button>
</div>
<!-- Row with text and buttons -->
<div class="level is-mobile">
<div class="level-left">
<button class="button is-small" hx-get="/loadUsageKPI?month={{ DateID }}&offset=-1"
hx-swap="outerHTML" hx-target="#usage-dropdown">
<span class="icon"><i class="fa-solid fa-chevron-left"></i></span>
</button>
</div>
<div class="level-item">
<p>{{ Date }}</p>
</div>
<div class="level-right">
<button class="button is-small" hx-get="/loadUsageKPI?month={{ DateID }}&offset=1"
hx-swap="outerHTML" hx-target="#usage-dropdown">
<span class="icon"><i class="fa-solid fa-chevron-right"></i></span>
</button>
</div>
</div>
</div>
</div> </div>
<!-- Total cost -->
<div class="content totalCostCell">
<p>
<strong>Total:</strong>
<span class="totalCost">{{ TotalCost|floatformat:2 }}$</span>
</p>
</div>
<!-- Total count -->
<div class="content is-hidden totalCountCell">
<p>
<strong>Total:</strong>
<span class="totalCount">{{ TotalCount }} messages</span>
</p>
</div>
<!-- Top buttons -->
<div class="buttons has-addons is-centered">
<button class="button is-small is-primary"
title="Money Spent"
id="money-spent-button">
<span class="icon ml-4 mr-4"><i class="fa-regular fa-money-bill-1"></i></span>
</button>
<button class="button is-small"
title="Messages Sent"
id="messages-sent-button">
<span class="icon ml-4 mr-4"><i class="fa-regular fa-envelope"></i></span>
</button>
</div>
<!-- Row with text and buttons -->
<div class="level is-mobile">
<div class="level-left">
<button class="button is-small"
hx-get="/loadUsageKPI?month={{ DateID }}&offset=-1"
hx-swap="outerHTML"
hx-target="#usage-dropdown">
<span class="icon"><i class="fa-solid fa-chevron-left"></i></span>
</button>
</div>
<div class="level-item">
<p>{{ Date }}</p>
</div>
<div class="level-right">
<button class="button is-small"
hx-get="/loadUsageKPI?month={{ DateID }}&offset=1"
hx-swap="outerHTML"
hx-target="#usage-dropdown">
<span class="icon"><i class="fa-solid fa-chevron-right"></i></span>
</button>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
<script> <script>
@ -142,4 +153,4 @@
-ms-overflow-style: none; -ms-overflow-style: none;
/* IE and Edge */ /* IE and Edge */
} }
</style> </style>

View File

@ -1,5 +1,6 @@
<h1 class="title is-1">JADE: Simple Multi-Model Chatbot</h1> <h1 class="title is-1">JADE: Simple Multi-Model Chatbot</h1>
<br /><br /> <br />
<br />
<p> <p>
I often use LLMs and quickly found myself asking GPT4, Gemini and Claude the I often use LLMs and quickly found myself asking GPT4, Gemini and Claude the
same question. I wanted to be able to ask the same question to multiple same question. I wanted to be able to ask the same question to multiple
@ -15,18 +16,15 @@
When asking a question, you can use multiple models and compare their When asking a question, you can use multiple models and compare their
responses to choose the best one. responses to choose the best one.
</li> </li>
<li> <li>The selected response can be used for the next message across all models.</li>
The selected response can be used for the next message across all models.
</li>
</ol> </ol>
<p>For example, a response from GPT-4 Omni can be used by Claude Haiku.</p> <p>For example, a response from GPT-4 Omni can be used by Claude Haiku.</p>
<a class="button is-primary mt-2 mb-2" href="/signin"> <a class="button is-primary mt-2 mb-2" href="/signin">Try JADE now for free!</a>
Try JADE now for free!
</a>
<br /><br /> <br />
<br />
<h2>More information</h2> <h2>More information</h2>
<p> <p>
@ -39,32 +37,33 @@
<ul> <ul>
<li> <li>
<h3> <h3>
Get access to all models.<button Get access to all models.
class="button ml-2 is-small is-primary is-outlined" <button class="button ml-2 is-small is-primary is-outlined"
onclick="toggleDetails('all-models-details')" onclick="toggleDetails('all-models-details')">
>
<span class="icon is-small"> <span class="icon is-small">
<i class="fa-solid fa-info"></i> <i class="fa-solid fa-info"></i>
</span> </span>
</button> </button>
</h3> </h3>
<p id="all-models-details" style="display: none"> <p id="all-models-details" style="display: none">
With JADE, you can easily switch between models like GPT 3.5 or 4o, With JADE, you can easily switch between models like GPT 3.5 or 4o, Gemini, Llama, Mistral, Claude, and more. Even custom endpoint.
Gemini, Llama, Mistral, Claude, and more. Even custom endpoint. <br />
<br /><br />This means you can choose the best model for your specific <br />
This means you can choose the best model for your specific
needs, whether it's for general knowledge, creative writing, or technical needs, whether it's for general knowledge, creative writing, or technical
expertise. Having access to multiple models allows you to take advantage expertise. Having access to multiple models allows you to take advantage
of their unique strengths and weaknesses, ensuring you get the most of their unique strengths and weaknesses, ensuring you get the most
accurate and relevant responses. (See all models available in the last accurate and relevant responses. (See all models available in the last
section)<br /><br /> section)
<br />
<br />
</p> </p>
</li> </li>
<li> <li>
<h3> <h3>
Get the best answer from multiple models.<button Get the best answer from multiple models.
class="button ml-2 is-small is-primary is-outlined" <button class="button ml-2 is-small is-primary is-outlined"
onclick="toggleDetails('multi-models-details')" onclick="toggleDetails('multi-models-details')">
>
<span class="icon is-small"> <span class="icon is-small">
<i class="fa-solid fa-info"></i> <i class="fa-solid fa-info"></i>
</span> </span>
@ -73,16 +72,19 @@
<p id="multi-models-details" style="display: none"> <p id="multi-models-details" style="display: none">
You can ask a question and receive responses from several models at once, You can ask a question and receive responses from several models at once,
enabling you to compare their answers and choose the most suitable one. enabling you to compare their answers and choose the most suitable one.
<br /><br />This feature is particularly useful for complex queries where <br />
different models might offer unique insights or solutions.<br /><br /> <br />
This feature is particularly useful for complex queries where
different models might offer unique insights or solutions.
<br />
<br />
</p> </p>
</li> </li>
<li> <li>
<h3> <h3>
Even from the same model.<button Even from the same model.
class="button ml-2 is-small is-primary is-outlined" <button class="button ml-2 is-small is-primary is-outlined"
onclick="toggleDetails('same-models-details')" onclick="toggleDetails('same-models-details')">
>
<span class="icon is-small"> <span class="icon is-small">
<i class="fa-solid fa-info"></i> <i class="fa-solid fa-info"></i>
</span> </span>
@ -90,17 +92,21 @@
</h3> </h3>
<p id="same-models-details" style="display: none"> <p id="same-models-details" style="display: none">
The core feature of JADE are the bots. Each bot have a name, model, The core feature of JADE are the bots. Each bot have a name, model,
temperature and system prompt. <br /><br />You can create as many bot as temperature and system prompt.
<br />
<br />
You can create as many bot as
you want and select as many to answer each question. An example is you want and select as many to answer each question. An example is
creating the same model that reponse in different language.<br /><br /> creating the same model that reponse in different language.
<br />
<br />
</p> </p>
</li> </li>
<li> <li>
<h3> <h3>
Reduce Hallucination.<button Reduce Hallucination.
class="button is-small ml-2 is-primary is-outlined" <button class="button is-small ml-2 is-primary is-outlined"
onclick="toggleDetails('reduce-hallucination-details')" onclick="toggleDetails('reduce-hallucination-details')">
>
<span class="icon is-small"> <span class="icon is-small">
<i class="fa-solid fa-info"></i> <i class="fa-solid fa-info"></i>
</span> </span>
@ -108,17 +114,21 @@
</h3> </h3>
<p id="reduce-hallucination-details" style="display: none"> <p id="reduce-hallucination-details" style="display: none">
AI models sometimes generate information that is inaccurate or misleading, AI models sometimes generate information that is inaccurate or misleading,
a phenomenon known as "hallucination." <br /><br />By using multiple a phenomenon known as "hallucination."
<br />
<br />
By using multiple
models, JADE reduces each model's bias. This ensures that the responses models, JADE reduces each model's bias. This ensures that the responses
you receive are more reliable and trustworthy.<br /><br /> you receive are more reliable and trustworthy.
<br />
<br />
</p> </p>
</li> </li>
<li> <li>
<h3> <h3>
Pay only for what you use or not at all.<button Pay only for what you use or not at all.
class="button ml-2 is-small is-primary is-outlined" <button class="button ml-2 is-small is-primary is-outlined"
onclick="toggleDetails('flexible-pricing-details')" onclick="toggleDetails('flexible-pricing-details')">
>
<span class="icon is-small"> <span class="icon is-small">
<i class="fa-solid fa-info"></i> <i class="fa-solid fa-info"></i>
</span> </span>
@ -126,57 +136,64 @@
</h3> </h3>
<p id="flexible-pricing-details" style="display: none"> <p id="flexible-pricing-details" style="display: none">
JADE use API, so you get access to free credits or tiers depending of the JADE use API, so you get access to free credits or tiers depending of the
provider (see next section). <br /><br />This is particularly beneficial provider (see next section).
<br />
<br />
This is particularly beneficial
for users who may not need to use the chatbot extensively. Once the free for users who may not need to use the chatbot extensively. Once the free
credit use, you pay based on the length of you message and the response credit use, you pay based on the length of you message and the response
generated in tokens (a token is around 3 characters). Groq and Google also generated in tokens (a token is around 3 characters). Groq and Google also
offer free tiers that are enough for conversation.<<br /><br />The app itself is free until 200 messages per month then 0.95$ per month.<br /><br /> offer free tiers that are enough for conversation.<
<br />
<br />
The app itself is free until 200 messages per month then 0.95$ per month.
<br />
<br />
</p> </p>
</li> </li>
<li> <li>
<h3> <h3>
All providers and models.<button All providers and models.
class="button ml-2 is-small is-primary is-outlined" <button class="button ml-2 is-small is-primary is-outlined"
onclick="toggleDetails('provider-details')" onclick="toggleDetails('provider-details')">
>
<span class="icon is-small"> <span class="icon is-small">
<i class="fa-solid fa-info"></i> <i class="fa-solid fa-info"></i>
</span> </span>
</button> </button>
</h3> </h3>
<div id="provider-details" style="display: none; overflow-x: hidden"> <div id="provider-details" style="display: none; overflow-x: hidden">
<strong>OpenAI</strong> - OpenAI offer 5$ credits when creating an API <strong>OpenAI</strong> - OpenAI offer 5$ credits when creating an API
account. Around 10 000 small question to GPT-4 Omni or 100 000 to account. Around 10 000 small question to GPT-4 Omni or 100 000 to
GPT-3.5 Turbo. GPT-3.5 Turbo.
<br /> <br />
<br /> <br />
<strong>Anthropic</strong> - Anthropic offer 5$ credits when creating an <strong>Anthropic</strong> - Anthropic offer 5$ credits when creating an
API account. Around 2 000 small question to Claude 3 Opus or 120 000 to API account. Around 2 000 small question to Claude 3 Opus or 120 000 to
Claude Haiku. Claude Haiku.
<br /> <br />
<br /> <br />
<strong>Mistral</strong> - Mistral do not offer free credits. <strong>Mistral</strong> - Mistral do not offer free credits.
<br /> <br />
<br /> <br />
<strong>Groq</strong> - Groq offer a free tier with limit of tokens and <strong>Groq</strong> - Groq offer a free tier with limit of tokens and
request per minutes. The rate is plenty for a chatbot. 30 messages and request per minutes. The rate is plenty for a chatbot. 30 messages and
between 6 000 and 30 000 tokens per minute. Per tokens coming soon. between 6 000 and 30 000 tokens per minute. Per tokens coming soon.
<br /> <br />
<br /> <br />
<strong>Google</strong> - Like Groq, Google offer a free tier with limit <strong>Google</strong> - Like Groq, Google offer a free tier with limit
of tokens and request per minutes. The rate is plenty for a chatbot. 15 of tokens and request per minutes. The rate is plenty for a chatbot. 15
messages and 1 000 000 tokens per minute. Per tokens also available. messages and 1 000 000 tokens per minute. Per tokens also available.
<br /> <br />
<br /> <br />
<strong>Perplexity</strong> - Perplexity do not offer a free tier or <strong>Perplexity</strong> - Perplexity do not offer a free tier or
credits. Perplexity offer what they call 'online' models that can search credits. Perplexity offer what they call 'online' models that can search
online. So you can ask for the current weather for example. Those models online. So you can ask for the current weather for example. Those models
have additional cost of 5$ per 1 000 requests. have additional cost of 5$ per 1 000 requests.
<br /> <br />
<br /> <br />
<strong>Fireworks</strong> - Fireworks AI offer 1$ of free credits when <strong>Fireworks</strong> - Fireworks AI offer 1$ of free credits when
creating an account. Firework AI have a lot of open source models. I may creating an account. Firework AI have a lot of open source models. I may
add fine tuned models in the future. add fine tuned models in the future.
<br /> <br />
<br /> <br />
<strong>Custom endpoint</strong> - You can also use custom endpoints as long as the key is valid and it use <strong>Custom endpoint</strong> - You can also use custom endpoints as long as the key is valid and it use