Migrate frontend to Svelte 5 runes semantics
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { fromStore } from "svelte/store";
|
||||
import { t } from "$lib/i18n";
|
||||
import Icon from "$lib/ui/Icon.svelte";
|
||||
import { openDrawerForTask } from "$lib/stores/taskDrawer.js";
|
||||
@@ -64,28 +65,31 @@
|
||||
const HISTORY_PAGE_SIZE = 30;
|
||||
const CONVERSATIONS_PAGE_SIZE = 20;
|
||||
|
||||
let input = "";
|
||||
let loading = false;
|
||||
let loadingHistory = false;
|
||||
let loadingMoreHistory = false;
|
||||
let loadingConversations = false;
|
||||
let messages = [];
|
||||
let conversations = [];
|
||||
let conversationFilter = "active";
|
||||
let activeConversationsTotal = 0;
|
||||
let archivedConversationsTotal = 0;
|
||||
let historyPage = 1;
|
||||
let historyHasNext = false;
|
||||
let historyLoadVersion = 0;
|
||||
let conversationsPage = 1;
|
||||
let conversationsHasNext = false;
|
||||
let historyViewport = null;
|
||||
let initialized = false;
|
||||
let llmReady = true;
|
||||
let llmStatusReason = "";
|
||||
let input = $state("");
|
||||
let loading = $state(false);
|
||||
let loadingHistory = $state(false);
|
||||
let loadingMoreHistory = $state(false);
|
||||
let loadingConversations = $state(false);
|
||||
let messages = $state([]);
|
||||
let conversations = $state([]);
|
||||
let conversationFilter = $state("active");
|
||||
let activeConversationsTotal = $state(0);
|
||||
let archivedConversationsTotal = $state(0);
|
||||
let historyPage = $state(1);
|
||||
let historyHasNext = $state(false);
|
||||
let historyLoadVersion = $state(0);
|
||||
let conversationsPage = $state(1);
|
||||
let conversationsHasNext = $state(false);
|
||||
let historyViewport = $state(null);
|
||||
let initialized = $state(false);
|
||||
let llmReady = $state(true);
|
||||
let llmStatusReason = $state("");
|
||||
|
||||
$: isOpen = $assistantChatStore?.isOpen || false;
|
||||
$: conversationId = $assistantChatStore?.conversationId || null;
|
||||
const assistantChatState = fromStore(assistantChatStore);
|
||||
let isOpen = $derived(assistantChatState.current?.isOpen || false);
|
||||
let conversationId = $derived(
|
||||
assistantChatState.current?.conversationId || null,
|
||||
);
|
||||
|
||||
// [DEF:loadHistory:Function]
|
||||
/**
|
||||
@@ -191,9 +195,10 @@
|
||||
(c) => c.conversation_id !== conversationIdTemp,
|
||||
);
|
||||
if (conversationId === conversationIdTemp) {
|
||||
$assistantChatStore.conversationId = null;
|
||||
$assistantChatStore.messages = [];
|
||||
$assistantChatStore.state = "idle";
|
||||
assistantChatStore.update((state) => ({
|
||||
...state,
|
||||
conversationId: null,
|
||||
}));
|
||||
}
|
||||
addToast("Conversation deleted", "success");
|
||||
} catch (err) {
|
||||
@@ -246,21 +251,24 @@
|
||||
}
|
||||
// [/DEF:loadOlderMessages:Function]
|
||||
|
||||
$: if (isOpen && !initialized) {
|
||||
loadConversations(true);
|
||||
loadHistory();
|
||||
loadLlmStatus();
|
||||
}
|
||||
|
||||
$: if (isOpen && initialized && conversationId) {
|
||||
// Re-load only when user switched to another conversation.
|
||||
const currentFirstConversationId = messages.length
|
||||
? messages[0].conversation_id
|
||||
: conversationId;
|
||||
if (currentFirstConversationId !== conversationId) {
|
||||
loadHistory();
|
||||
$effect(() => {
|
||||
if (isOpen && !initialized) {
|
||||
void loadConversations(true);
|
||||
void loadHistory();
|
||||
void loadLlmStatus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (isOpen && initialized && conversationId) {
|
||||
const currentFirstConversationId = messages.length
|
||||
? messages[0].conversation_id
|
||||
: conversationId;
|
||||
if (currentFirstConversationId !== conversationId) {
|
||||
void loadHistory();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// [DEF:appendLocalUserMessage:Function]
|
||||
/**
|
||||
@@ -566,7 +574,7 @@
|
||||
{#if isOpen}
|
||||
<div
|
||||
class="fixed inset-0 z-[70] bg-slate-900/30"
|
||||
on:click={closeAssistantChat}
|
||||
onclick={closeAssistantChat}
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
@@ -584,7 +592,7 @@
|
||||
</div>
|
||||
<button
|
||||
class="rounded-md p-1 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900"
|
||||
on:click={closeAssistantChat}
|
||||
onclick={closeAssistantChat}
|
||||
aria-label={$t.assistant?.close}
|
||||
>
|
||||
<Icon name="close" size={18} />
|
||||
@@ -622,7 +630,7 @@
|
||||
>
|
||||
<button
|
||||
class="rounded-md border border-slate-300 px-2 py-1 text-[11px] font-medium text-slate-700 transition hover:bg-slate-100"
|
||||
on:click={startNewConversation}
|
||||
onclick={startNewConversation}
|
||||
>
|
||||
{$t.assistant?.new}
|
||||
</button>
|
||||
@@ -633,7 +641,7 @@
|
||||
'active'
|
||||
? 'border-sky-300 bg-sky-50 text-sky-900'
|
||||
: 'border-slate-300 bg-white text-slate-700 hover:bg-slate-100'}"
|
||||
on:click={() => setConversationFilter("active")}
|
||||
onclick={() => setConversationFilter("active")}
|
||||
>
|
||||
{$t.assistant?.active} ({activeConversationsTotal})
|
||||
</button>
|
||||
@@ -642,7 +650,7 @@
|
||||
'archived'
|
||||
? 'border-sky-300 bg-sky-50 text-sky-900'
|
||||
: 'border-slate-300 bg-white text-slate-700 hover:bg-slate-100'}"
|
||||
on:click={() => setConversationFilter("archived")}
|
||||
onclick={() => setConversationFilter("archived")}
|
||||
>
|
||||
{$t.assistant?.archived} ({archivedConversationsTotal})
|
||||
</button>
|
||||
@@ -655,7 +663,7 @@
|
||||
conversationId
|
||||
? 'border-sky-300 bg-sky-50 text-sky-900'
|
||||
: 'border-slate-200 bg-white text-slate-700 hover:bg-slate-50'}"
|
||||
on:click={() => selectConversation(convo)}
|
||||
onclick={() => selectConversation(convo)}
|
||||
title={formatConversationTime(convo.updated_at)}
|
||||
>
|
||||
<div class="truncate font-semibold pr-4">
|
||||
@@ -667,7 +675,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="absolute right-1.5 top-1.5 hidden group-hover:block p-1 text-slate-400 hover:text-red-500 rounded bg-white/80 hover:bg-red-50"
|
||||
on:click={(e) => removeConversation(e, convo.conversation_id)}
|
||||
onclick={(e) => removeConversation(e, convo.conversation_id)}
|
||||
title="Удалить диалог"
|
||||
>
|
||||
<Icon name="trash" size={12} />
|
||||
@@ -684,7 +692,7 @@
|
||||
{#if conversationsHasNext}
|
||||
<button
|
||||
class="rounded-lg border border-slate-300 px-2.5 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100"
|
||||
on:click={() => loadConversations(false)}
|
||||
onclick={() => loadConversations(false)}
|
||||
>
|
||||
{$t.assistant?.more}
|
||||
</button>
|
||||
@@ -695,7 +703,7 @@
|
||||
<div
|
||||
class="flex-1 space-y-3 overflow-y-auto p-4"
|
||||
bind:this={historyViewport}
|
||||
on:scroll={handleHistoryScroll}
|
||||
onscroll={handleHistoryScroll}
|
||||
>
|
||||
{#if loadingMoreHistory}
|
||||
<div
|
||||
@@ -761,7 +769,7 @@
|
||||
>
|
||||
<button
|
||||
class="text-xs font-medium text-sky-700 hover:text-sky-900"
|
||||
on:click={() => openDrawerForTask(message.task_id)}
|
||||
onclick={() => openDrawerForTask(message.task_id)}
|
||||
>
|
||||
{$t.assistant?.open_task_drawer}
|
||||
</button>
|
||||
@@ -778,7 +786,7 @@
|
||||
: action.type === 'cancel'
|
||||
? 'border-rose-300 bg-rose-50 text-rose-700 hover:bg-rose-100'
|
||||
: 'border-slate-300 bg-white text-slate-700 hover:bg-slate-100'}"
|
||||
on:click={() => handleAction(action, message)}
|
||||
onclick={() => handleAction(action, message)}
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
@@ -819,11 +827,11 @@
|
||||
class="min-h-[52px] w-full resize-y rounded-lg border px-3 py-2 text-sm outline-none transition {llmReady
|
||||
? 'border-slate-300 focus:border-sky-400 focus:ring-2 focus:ring-sky-100'
|
||||
: 'border-rose-300 bg-rose-50 focus:border-rose-400 focus:ring-2 focus:ring-rose-100'}"
|
||||
on:keydown={handleKeydown}
|
||||
onkeydown={handleKeydown}
|
||||
></textarea>
|
||||
<button
|
||||
class="rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white transition hover:bg-sky-700 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
on:click={handleSend}
|
||||
onclick={handleSend}
|
||||
disabled={loading || !input.trim()}
|
||||
>
|
||||
{loading ? "..." : $t.assistant?.send}
|
||||
|
||||
Reference in New Issue
Block a user