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))
|
||||
}
|
||||
|
||||
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 {
|
||||
if !checkIfLogin() {
|
||||
return c.SendString("")
|
||||
|
@ -41,6 +41,7 @@ type Setting struct {
|
||||
type Conversation struct {
|
||||
ID edgedb.UUID `edgedb:"id"`
|
||||
Name string `edgedb:"name"`
|
||||
Position int32 `edgedb:"position"`
|
||||
Date time.Time `edgedb:"date"`
|
||||
User User `edgedb:"user"`
|
||||
}
|
||||
@ -155,9 +156,15 @@ func addUsage(inputCost float32, outputCost float32, inputToken int32, outputTok
|
||||
func insertNewConversation() edgedb.UUID {
|
||||
var inserted struct{ id edgedb.UUID }
|
||||
err := edgeClient.QuerySingle(edgeCtx, `
|
||||
WITH
|
||||
C := (
|
||||
SELECT Conversation
|
||||
FILTER .user = global currentUser
|
||||
)
|
||||
INSERT Conversation {
|
||||
name := 'Default',
|
||||
user := global currentUser
|
||||
user := global currentUser,
|
||||
position := count(C) + 1
|
||||
}
|
||||
`, &inserted)
|
||||
if err != nil {
|
||||
|
@ -37,6 +37,7 @@ module default {
|
||||
|
||||
type Conversation {
|
||||
required name: str;
|
||||
required position: int32;
|
||||
required user: User {
|
||||
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
|
||||
app.Get("/loadModelSelection", LoadModelSelectionHandler)
|
||||
app.Get("/loadConversationSelection", LoadConversationSelectionHandler)
|
||||
app.Get("/refreshConversationSelection", RefreshConversationSelectionHandler)
|
||||
app.Get("/loadUsageKPI", LoadUsageKPIHandler)
|
||||
app.Get("/loadKeys", LoadKeysHandler)
|
||||
app.Get("/loadSettings", LoadSettingsHandler)
|
||||
|
BIN
static/icons/.DS_Store
vendored
BIN
static/icons/.DS_Store
vendored
Binary file not shown.
@ -119,3 +119,53 @@ svg text {
|
||||
.message-bot:hover .message-button {
|
||||
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>
|
||||
<div class="button-group">
|
||||
<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="/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%} class="button is-small"
|
||||
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>
|
@ -164,55 +164,3 @@
|
||||
}
|
||||
});
|
||||
</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">
|
||||
<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>
|
||||
</button>
|
||||
</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-item">
|
||||
<div class="field">
|
||||
@ -120,6 +120,12 @@
|
||||
</div>
|
||||
|
||||
<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 () {
|
||||
// Do not take the id="save-field"
|
||||
var fields = document.querySelectorAll("#api-keys-form .field.has-addons");
|
||||
|
Loading…
x
Reference in New Issue
Block a user