Conversation menu
This commit is contained in:
parent
09193a227e
commit
01d04b3611
36
Chat.go
36
Chat.go
@ -594,6 +594,42 @@ func LoadModelSelectionHandler(c *fiber.Ctx) error {
|
|||||||
return c.SendString(GenerateModelPopoverHTML(false))
|
return c.SendString(GenerateModelPopoverHTML(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateConversationPopoverHTML(refresh bool) string {
|
||||||
|
var conversations []Conversation
|
||||||
|
err := edgeClient.Query(edgeCtx, `
|
||||||
|
SELECT Conversation {
|
||||||
|
name,
|
||||||
|
position
|
||||||
|
}
|
||||||
|
FILTER .user = global currentUser
|
||||||
|
ORDER BY .position
|
||||||
|
`, &conversations)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-conversation.html")).Execute(pongo2.Context{
|
||||||
|
"Conversations": conversations,
|
||||||
|
"IsActive": refresh,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConversationSelectionHandler(c *fiber.Ctx) error {
|
||||||
|
if !checkIfLogin() || !checkIfHaveKey() {
|
||||||
|
return c.SendString("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendString(GenerateConversationPopoverHTML(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RefreshConversationSelectionHandler(c *fiber.Ctx) error {
|
||||||
|
return c.SendString(GenerateConversationPopoverHTML(true))
|
||||||
|
}
|
||||||
|
|
||||||
func LoadSettingsHandler(c *fiber.Ctx) error {
|
func LoadSettingsHandler(c *fiber.Ctx) error {
|
||||||
if !checkIfLogin() {
|
if !checkIfLogin() {
|
||||||
return c.SendString("")
|
return c.SendString("")
|
||||||
|
17
database.go
17
database.go
@ -39,10 +39,11 @@ type Setting struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Conversation struct {
|
type Conversation struct {
|
||||||
ID edgedb.UUID `edgedb:"id"`
|
ID edgedb.UUID `edgedb:"id"`
|
||||||
Name string `edgedb:"name"`
|
Name string `edgedb:"name"`
|
||||||
Date time.Time `edgedb:"date"`
|
Position int32 `edgedb:"position"`
|
||||||
User User `edgedb:"user"`
|
Date time.Time `edgedb:"date"`
|
||||||
|
User User `edgedb:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Area struct {
|
type Area struct {
|
||||||
@ -155,9 +156,15 @@ func addUsage(inputCost float32, outputCost float32, inputToken int32, outputTok
|
|||||||
func insertNewConversation() edgedb.UUID {
|
func insertNewConversation() edgedb.UUID {
|
||||||
var inserted struct{ id edgedb.UUID }
|
var inserted struct{ id edgedb.UUID }
|
||||||
err := edgeClient.QuerySingle(edgeCtx, `
|
err := edgeClient.QuerySingle(edgeCtx, `
|
||||||
|
WITH
|
||||||
|
C := (
|
||||||
|
SELECT Conversation
|
||||||
|
FILTER .user = global currentUser
|
||||||
|
)
|
||||||
INSERT Conversation {
|
INSERT Conversation {
|
||||||
name := 'Default',
|
name := 'Default',
|
||||||
user := global currentUser
|
user := global currentUser,
|
||||||
|
position := count(C) + 1
|
||||||
}
|
}
|
||||||
`, &inserted)
|
`, &inserted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,6 +37,7 @@ module default {
|
|||||||
|
|
||||||
type Conversation {
|
type Conversation {
|
||||||
required name: str;
|
required name: str;
|
||||||
|
required position: int32;
|
||||||
required user: User {
|
required user: User {
|
||||||
on target delete delete source;
|
on target delete delete source;
|
||||||
};
|
};
|
||||||
|
9
dbschema/migrations/00042-m1ospnj.edgeql
Normal file
9
dbschema/migrations/00042-m1ospnj.edgeql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE MIGRATION m1ospnjzsatmkntvczm5eu65omytyezeg3lanxeogtdqz2372t6cuq
|
||||||
|
ONTO m1cmvjy3ikuh5ii6b7l7gckttjmqk554llocwqx7n4aibtzngybvoq
|
||||||
|
{
|
||||||
|
ALTER TYPE default::Conversation {
|
||||||
|
CREATE REQUIRED PROPERTY position: std::int32 {
|
||||||
|
SET REQUIRED USING (<std::int32>{0});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
2
main.go
2
main.go
@ -86,6 +86,8 @@ func main() {
|
|||||||
|
|
||||||
// Popovers
|
// Popovers
|
||||||
app.Get("/loadModelSelection", LoadModelSelectionHandler)
|
app.Get("/loadModelSelection", LoadModelSelectionHandler)
|
||||||
|
app.Get("/loadConversationSelection", LoadConversationSelectionHandler)
|
||||||
|
app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler)
|
||||||
app.Get("/loadUsageKPI", LoadUsageKPIHandler)
|
app.Get("/loadUsageKPI", LoadUsageKPIHandler)
|
||||||
app.Get("/loadKeys", LoadKeysHandler)
|
app.Get("/loadKeys", LoadKeysHandler)
|
||||||
app.Get("/loadSettings", LoadSettingsHandler)
|
app.Get("/loadSettings", LoadSettingsHandler)
|
||||||
|
BIN
static/icons/.DS_Store
vendored
BIN
static/icons/.DS_Store
vendored
Binary file not shown.
@ -118,4 +118,54 @@ svg text {
|
|||||||
|
|
||||||
.message-bot:hover .message-button {
|
.message-bot:hover .message-button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
border: 2px solid #126d0f;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselected {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiftselected {
|
||||||
|
background: #126d0f;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shift-pressed *::selection {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"].slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 80%;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #2c2c2c;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"].slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #126d0f;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"].slider::-moz-range-thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
@ -9,8 +9,8 @@
|
|||||||
id="chat-input-textarea"></textarea>
|
id="chat-input-textarea"></textarea>
|
||||||
<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="/loadKeys" 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="/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 {% if not IsLogin or not HaveKey %}style="display: none;" {%endif%} class="button is-small"
|
<button {% if not IsLogin or not HaveKey %}style="display: none;" {%endif%} class="button is-small"
|
||||||
onclick="clearTextArea()" id="clear-button" hx-post="/clearChat" hx-swap="outerHTML"
|
onclick="clearTextArea()" id="clear-button" hx-post="/clearChat" hx-swap="outerHTML"
|
||||||
|
52
views/partials/popover-conversation.html
Normal file
52
views/partials/popover-conversation.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<div class="dropdown is-up is-right {% if IsActive %} is-active {% endif %}" id="conversation-dropdown">
|
||||||
|
<div class="dropdown-trigger">
|
||||||
|
<button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu3"
|
||||||
|
hx-get="/refreshConversationSelection" hx-target="#conversation-dropdown" hx-swap="outerHTML">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-comments"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<div class="dropdown-item" id="models-list">
|
||||||
|
<div id="conversation-list">
|
||||||
|
{% for Conversation in Conversations %}
|
||||||
|
<div class="icon-text has-text unselected" data-id="{{ Converrsation.ID.String() }}"
|
||||||
|
style="cursor: pointer;" onclick="toggleSelection(this)">
|
||||||
|
<span>{{ Conversation.Name }}</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not IsActive %}
|
||||||
|
<script>
|
||||||
|
var sortable = new Sortable(document.getElementById('conversation-list'), {
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
var items = evt.to.children;
|
||||||
|
var updates = [];
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
var id = items[i].getAttribute('data-id');
|
||||||
|
var position = i + 1;
|
||||||
|
updates.push({ id: id, position: position });
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/updateConversationPositionBatch', true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||||
|
xhr.send(JSON.stringify(updates));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
if (!document.getElementById('conversation-dropdown').contains(event.target)) {
|
||||||
|
document.getElementById('conversation-dropdown').classList.remove('is-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
@ -1,27 +0,0 @@
|
|||||||
<div class="dropdown is-hoverable is-up">
|
|
||||||
<div class="dropdown-trigger">
|
|
||||||
<button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu4">
|
|
||||||
<span>Conversations</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
|
|
||||||
<div class="dropdown-content">
|
|
||||||
<div class="dropdown-item">
|
|
||||||
{% for conversation in Conversations %}
|
|
||||||
<div class="content">
|
|
||||||
<h5 class="subtitle is-5">{{ CompanyInfo.Name }}</h5>
|
|
||||||
{% for ModelInfo in CompanyInfo.ModelInfos %}
|
|
||||||
<div class="field">
|
|
||||||
<label class="checkbox">
|
|
||||||
<input {%if ModelInfo.ID in CheckedModels %}checked{% endif %} type="checkbox"
|
|
||||||
name="model-check-{{ ModelInfo.ID }}" value="{{ ModelInfo.ID }}" />
|
|
||||||
{{ ModelInfo.Name }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,88 +0,0 @@
|
|||||||
<div class="dropdown is-hoverable is-up is-right">
|
|
||||||
<div class="dropdown-trigger">
|
|
||||||
<button class="button is-small {% if not AnyExists %} is-danger{% endif %}" aria-haspopup="true"
|
|
||||||
aria-controls="dropdown-menu4">
|
|
||||||
<span class="icon"><i class="fa-solid fa-key"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
|
|
||||||
<div class="dropdown-content">
|
|
||||||
<div class="dropdown-item">
|
|
||||||
<div class="field">
|
|
||||||
<form id="api-keys-form" hx-post="/addKeys" hx-trigger="submit" hx-target="#api-keys-status">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<p class="control has-icons-left is-expanded">
|
|
||||||
<input class="input is-small {% if OpenaiExists %}is-success{% endif %}" type="text"
|
|
||||||
{%if not IsLogin %}disabled{% endif %} placeholder="OpenAI API key"
|
|
||||||
name="openai_key" autocomplete="off">
|
|
||||||
<span class="icon is-small is-left">
|
|
||||||
<i class="fas fa-lock"></i>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="field has-addons">
|
|
||||||
<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">
|
|
||||||
<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" title="Gemini is unavailable because of Europe">
|
|
||||||
<p class="control has-icons-left is-expanded">
|
|
||||||
<input class="input is-small {% if GoogleExists %}is-success{% endif %}" type="text"
|
|
||||||
disabled 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">
|
|
||||||
<p class="control has-icons-left is-expanded">
|
|
||||||
<input class="input is-small {% if GooseaiExists %}is-success{% endif %}" type="text"
|
|
||||||
placeholder="Gooseai API key" {%if not IsLogin %}disabled{% endif %}
|
|
||||||
name="goose_key" autocomplete="off">
|
|
||||||
<span class="icon is-small is-left">
|
|
||||||
<i class="fas fa-lock"></i>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="field has-addons">
|
|
||||||
<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">
|
|
||||||
<p class="control">
|
|
||||||
<button {% if not IsLogin %}disabled{% endif %} type="submit" class="button is-small">
|
|
||||||
<span class="icon is-small">
|
|
||||||
<i class="fas fa-check"></i>
|
|
||||||
</span>
|
|
||||||
<span>Save</span>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<p id="api-keys-status"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -163,56 +163,4 @@
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.selected {
|
|
||||||
border: 2px solid #126d0f;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 1px;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselected {
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shiftselected {
|
|
||||||
background: #126d0f;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 1px;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shift-pressed *::selection {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="range"].slider {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 80%;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #2c2c2c;
|
|
||||||
border-radius: 4px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="range"].slider::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: #126d0f;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="range"].slider::-moz-range-thumb {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,11 +1,11 @@
|
|||||||
<div class="dropdown is-hoverable is-up is-right">
|
<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 %}" aria-haspopup="true"
|
<button class="button is-small {% if not AnyExists %} is-danger{% endif %}" aria-haspopup="true"
|
||||||
aria-controls="dropdown-menu4">
|
aria-controls="dropdown-menu3" 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-menu4" 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">
|
||||||
@ -120,6 +120,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
if (!document.getElementById('settings-dropdown').contains(event.target)) {
|
||||||
|
document.getElementById('settings-dropdown').classList.remove('is-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('toggle-keys-button').addEventListener('click', function () {
|
document.getElementById('toggle-keys-button').addEventListener('click', function () {
|
||||||
// Do not take the id="save-field"
|
// Do not take the id="save-field"
|
||||||
var fields = document.querySelectorAll("#api-keys-form .field.has-addons");
|
var fields = document.querySelectorAll("#api-keys-form .field.has-addons");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user