diff --git a/tools/server/public/index.html.gz b/tools/server/public/index.html.gz index b7b36176d8..30de1ebaf8 100644 Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte index d748dd868e..ac9ae8ae6c 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte @@ -124,7 +124,7 @@ class="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-medium hover:bg-accent hover:text-accent-foreground" onclick={handleMobileSidebarItemClick} > - + Notebook diff --git a/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte b/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte index 6871b1da2f..9fb4be8524 100644 --- a/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte +++ b/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte @@ -2,42 +2,96 @@ import { notebookStore } from '$lib/stores/notebook.svelte'; import Button from '$lib/components/ui/button/button.svelte'; import Textarea from '$lib/components/ui/textarea/textarea.svelte'; - import { Play, Square } from '@lucide/svelte'; + import { Play, Square, Settings, Info } from '@lucide/svelte'; import { config } from '$lib/stores/settings.svelte'; + import ChatMessageStatistics from '$lib/components/app/chat/ChatMessages/ChatMessageStatistics.svelte'; + import DialogChatSettings from '$lib/components/app/dialogs/DialogChatSettings.svelte'; + import DialogModelInformation from '$lib/components/app/dialogs/DialogModelInformation.svelte'; + import { modelsStore } from '$lib/stores/models.svelte'; let { content } = $state(notebookStore); + let settingsOpen = $state(false); + let modelInfoOpen = $state(false); + + let inputContent = $state(content); + + // Sync local input with store content + $effect(() => { + inputContent = notebookStore.content; + }); + + function handleInput(e: Event) { + const target = e.target as HTMLTextAreaElement; + notebookStore.content = target.value; + } + + async function handleGenerate() { + await notebookStore.generate(); + } + + function handleStop() { + notebookStore.stop(); + } + + let currentModel = $derived( + modelsStore.models.find((m) => m.id === config().model) || modelsStore.models[0] + ); -
-
-

Notebook

-
- {#if notebookStore.isGenerating} - + + +
+ -
- -
-

- Model: {config().model || 'Default'} | Temperature: {config().temperature ?? 0.8} | Max Tokens: {config() - .max_tokens ?? -1} -

-
+ (settingsOpen = open)} /> + (modelInfoOpen = open)} />
diff --git a/tools/server/webui/src/lib/stores/notebook.svelte.ts b/tools/server/webui/src/lib/stores/notebook.svelte.ts index 890e72c1fc..5179857d7d 100644 --- a/tools/server/webui/src/lib/stores/notebook.svelte.ts +++ b/tools/server/webui/src/lib/stores/notebook.svelte.ts @@ -6,12 +6,24 @@ export class NotebookStore { isGenerating = $state(false); abortController: AbortController | null = null; + // Statistics + promptTokens = $state(0); + promptMs = $state(0); + predictedTokens = $state(0); + predictedMs = $state(0); + async generate(model?: string) { if (this.isGenerating) return; this.isGenerating = true; this.abortController = new AbortController(); + // Reset stats + this.promptTokens = 0; + this.promptMs = 0; + this.predictedTokens = 0; + this.predictedMs = 0; + try { const currentConfig = config(); await ChatService.sendCompletion( @@ -23,6 +35,14 @@ export class NotebookStore { onChunk: (chunk) => { this.content += chunk; }, + onTimings: (timings) => { + if (timings) { + if (timings.prompt_n) this.promptTokens = timings.prompt_n; + if (timings.prompt_ms) this.promptMs = timings.prompt_ms; + if (timings.predicted_n) this.predictedTokens = timings.predicted_n; + if (timings.predicted_ms) this.predictedMs = timings.predicted_ms; + } + }, onComplete: () => { this.isGenerating = false; },