Add .agent/ to gitignore
Fix buttons Fix model loading with router enabled remove stats for now lint
This commit is contained in:
parent
3af9b34aa2
commit
c9f9863268
|
|
@ -136,5 +136,6 @@ poetry.toml
|
||||||
# IDE
|
# IDE
|
||||||
/*.code-workspace
|
/*.code-workspace
|
||||||
/.windsurf/
|
/.windsurf/
|
||||||
|
/.agent/
|
||||||
# emscripten
|
# emscripten
|
||||||
a.out.*
|
a.out.*
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -118,17 +118,6 @@
|
||||||
<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
||||||
</Sidebar.Header>
|
</Sidebar.Header>
|
||||||
|
|
||||||
<div class="px-4 py-2">
|
|
||||||
<a
|
|
||||||
href="#/notebook"
|
|
||||||
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}
|
|
||||||
>
|
|
||||||
<span class="i-lucide-file-text h-4 w-4"></span>
|
|
||||||
Notebook
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Sidebar.Group class="mt-4 space-y-2 p-0 px-4">
|
<Sidebar.Group class="mt-4 space-y-2 p-0 px-4">
|
||||||
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
|
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
|
||||||
<Sidebar.GroupLabel>
|
<Sidebar.GroupLabel>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Search, SquarePen, X } from '@lucide/svelte';
|
import { NotepadText, Search, SquarePen, X } from '@lucide/svelte';
|
||||||
import { KeyboardShortcutInfo } from '$lib/components/app';
|
import { KeyboardShortcutInfo } from '$lib/components/app';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
|
|
@ -63,6 +63,18 @@
|
||||||
<KeyboardShortcutInfo keys={['shift', 'cmd', 'o']} />
|
<KeyboardShortcutInfo keys={['shift', 'cmd', 'o']} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
class="w-full justify-between hover:[&>kbd]:opacity-100"
|
||||||
|
href="#/notebook"
|
||||||
|
onclick={handleMobileSidebarItemClick}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<NotepadText class="h-4 w-4" />
|
||||||
|
Notebook
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
class="w-full justify-between hover:[&>kbd]:opacity-100"
|
class="w-full justify-between hover:[&>kbd]:opacity-100"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,22 @@
|
||||||
import { notebookStore } from '$lib/stores/notebook.svelte';
|
import { notebookStore } from '$lib/stores/notebook.svelte';
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
|
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
|
||||||
import { Play, Square, Settings, Info } from '@lucide/svelte';
|
import { Play, Square, Settings } from '@lucide/svelte';
|
||||||
import { config } from '$lib/stores/settings.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 DialogChatSettings from '$lib/components/app/dialogs/DialogChatSettings.svelte';
|
||||||
import DialogModelInformation from '$lib/components/app/dialogs/DialogModelInformation.svelte';
|
import { ModelsSelector } from '$lib/components/app';
|
||||||
import { modelsStore } from '$lib/stores/models.svelte';
|
import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte';
|
||||||
|
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||||
|
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||||
|
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||||
|
|
||||||
let { content } = $state(notebookStore);
|
let { content } = $state(notebookStore);
|
||||||
let settingsOpen = $state(false);
|
let settingsOpen = $state(false);
|
||||||
let modelInfoOpen = $state(false);
|
|
||||||
|
|
||||||
let inputContent = $state(content);
|
let inputContent = $state(content);
|
||||||
|
|
||||||
|
let isRouter = $derived(isRouterMode());
|
||||||
|
|
||||||
// Sync local input with store content
|
// Sync local input with store content
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
inputContent = notebookStore.content;
|
inputContent = notebookStore.content;
|
||||||
|
|
@ -26,16 +29,70 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleGenerate() {
|
async function handleGenerate() {
|
||||||
await notebookStore.generate();
|
if (notebookModel == null) {
|
||||||
|
notebookModel = activeModelId;
|
||||||
|
}
|
||||||
|
await notebookStore.generate(notebookModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStop() {
|
function handleStop() {
|
||||||
notebookStore.stop();
|
notebookStore.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentModel = $derived(
|
let activeModelId = $derived.by(() => {
|
||||||
modelsStore.models.find((m) => m.id === config().model) || modelsStore.models[0]
|
const options = modelOptions();
|
||||||
);
|
|
||||||
|
if (!isRouter) {
|
||||||
|
return options.length > 0 ? options[0].model : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedId = selectedModelId();
|
||||||
|
if (selectedId) {
|
||||||
|
const model = options.find((m) => m.id === selectedId);
|
||||||
|
if (model) return model.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
let hasModelSelected = $derived(!isRouter || !!selectedModelId());
|
||||||
|
|
||||||
|
let isSelectedModelInCache = $derived.by(() => {
|
||||||
|
if (!isRouter) return true;
|
||||||
|
|
||||||
|
const currentModelId = selectedModelId();
|
||||||
|
if (!currentModelId) return false;
|
||||||
|
|
||||||
|
return modelOptions().some((option) => option.id === currentModelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
let generateTooltip = $derived.by(() => {
|
||||||
|
if (!hasModelSelected) {
|
||||||
|
return 'Please select a model first';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSelectedModelInCache) {
|
||||||
|
return 'Selected model is not available, please select another';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputContent.length == 0) {
|
||||||
|
return 'Input some text first';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
let canGenerate = $derived(inputContent.length > 0 && hasModelSelected && isSelectedModelInCache);
|
||||||
|
let isDisabled = $derived(!canGenerate);
|
||||||
|
|
||||||
|
let notebookModel = $state(null);
|
||||||
|
|
||||||
|
const { handleModelChange } = useModelChangeValidation({
|
||||||
|
getRequiredModalities: () => ({ vision: false, audio: false }), // Notebook doesn't require modalities
|
||||||
|
onSuccess: async (modelName) => {
|
||||||
|
notebookModel = modelName;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex h-full flex-col">
|
||||||
|
|
@ -55,43 +112,55 @@
|
||||||
value={inputContent}
|
value={inputContent}
|
||||||
oninput={handleInput}
|
oninput={handleInput}
|
||||||
class="h-full min-h-[500px] w-full resize-none rounded-xl border-none bg-muted p-4 text-base focus-visible:ring-0 md:p-6"
|
class="h-full min-h-[500px] w-full resize-none rounded-xl border-none bg-muted p-4 text-base focus-visible:ring-0 md:p-6"
|
||||||
placeholder="Enter your prompt here..."
|
placeholder="Enter your text here..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-border/40 bg-background p-4 md:px-6 md:py-4">
|
<div class="border-t border-border/40 bg-background p-4 md:px-6 md:py-4">
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Button
|
{#snippet generateButton(props = {})}
|
||||||
onclick={notebookStore.isGenerating ? handleStop : handleGenerate}
|
<Button
|
||||||
size="sm"
|
disabled={isDisabled}
|
||||||
variant={notebookStore.isGenerating ? 'destructive' : 'default'}
|
onclick={notebookStore.isGenerating ? handleStop : handleGenerate}
|
||||||
class="gap-2"
|
size="sm"
|
||||||
>
|
variant={notebookStore.isGenerating ? 'destructive' : 'default'}
|
||||||
{#if notebookStore.isGenerating}
|
class="gap-2"
|
||||||
<Square class="h-4 w-4 fill-current" />
|
>
|
||||||
Stop
|
{#if notebookStore.isGenerating}
|
||||||
{:else}
|
<Square class="h-4 w-4 fill-current" />
|
||||||
<Play class="h-4 w-4 fill-current" />
|
Stop
|
||||||
Generate
|
{:else}
|
||||||
{/if}
|
<Play class="h-4 w-4 fill-current" />
|
||||||
</Button>
|
Generate
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
<Button variant="ghost" size="icon" onclick={() => (modelInfoOpen = true)}>
|
{#if generateTooltip}
|
||||||
<Info class="h-4 w-4" />
|
<Tooltip.Root>
|
||||||
</Button>
|
<Tooltip.Trigger>
|
||||||
|
{@render generateButton()}
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
|
||||||
|
<Tooltip.Content>
|
||||||
|
<p>{generateTooltip}</p>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
{:else}
|
||||||
|
{@render generateButton()}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<ModelsSelector
|
||||||
|
currentModel={notebookModel}
|
||||||
|
onModelChange={handleModelChange}
|
||||||
|
forceForegroundText={true}
|
||||||
|
useGlobalSelection={true}
|
||||||
|
disabled={notebookStore.isGenerating}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ChatMessageStatistics
|
|
||||||
predictedTokens={notebookStore.predictedTokens}
|
|
||||||
predictedMs={notebookStore.predictedMs}
|
|
||||||
promptTokens={notebookStore.promptTokens}
|
|
||||||
promptMs={notebookStore.promptMs}
|
|
||||||
isLive={notebookStore.isGenerating}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogChatSettings open={settingsOpen} onOpenChange={(open) => (settingsOpen = open)} />
|
<DialogChatSettings open={settingsOpen} onOpenChange={(open) => (settingsOpen = open)} />
|
||||||
<DialogModelInformation open={modelInfoOpen} onOpenChange={(open) => (modelInfoOpen = open)} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,70 +2,74 @@ import { ChatService } from '$lib/services/chat';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
|
|
||||||
export class NotebookStore {
|
export class NotebookStore {
|
||||||
content = $state('');
|
content = $state('');
|
||||||
isGenerating = $state(false);
|
isGenerating = $state(false);
|
||||||
abortController: AbortController | null = null;
|
abortController: AbortController | null = null;
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
promptTokens = $state(0);
|
promptTokens = $state(0);
|
||||||
promptMs = $state(0);
|
promptMs = $state(0);
|
||||||
predictedTokens = $state(0);
|
predictedTokens = $state(0);
|
||||||
predictedMs = $state(0);
|
predictedMs = $state(0);
|
||||||
|
|
||||||
async generate(model?: string) {
|
async generate(model?: string) {
|
||||||
if (this.isGenerating) return;
|
if (this.isGenerating) return;
|
||||||
|
|
||||||
this.isGenerating = true;
|
this.isGenerating = true;
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
// Reset stats
|
// Reset stats
|
||||||
this.promptTokens = 0;
|
this.promptTokens = 0;
|
||||||
this.promptMs = 0;
|
this.promptMs = 0;
|
||||||
this.predictedTokens = 0;
|
this.predictedTokens = 0;
|
||||||
this.predictedMs = 0;
|
this.predictedMs = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentConfig = config();
|
const currentConfig = config();
|
||||||
await ChatService.sendCompletion(
|
await ChatService.sendCompletion(
|
||||||
this.content,
|
this.content,
|
||||||
{
|
{
|
||||||
...currentConfig,
|
...currentConfig,
|
||||||
model,
|
model: model ?? currentConfig.model,
|
||||||
stream: true,
|
stream: true,
|
||||||
onChunk: (chunk) => {
|
onChunk: (chunk) => {
|
||||||
this.content += chunk;
|
this.content += chunk;
|
||||||
},
|
},
|
||||||
onTimings: (timings) => {
|
onTimings: (timings) => {
|
||||||
if (timings) {
|
if (timings) {
|
||||||
if (timings.prompt_n) this.promptTokens = timings.prompt_n;
|
if (timings.prompt_n) this.promptTokens = timings.prompt_n;
|
||||||
if (timings.prompt_ms) this.promptMs = timings.prompt_ms;
|
if (timings.prompt_ms) this.promptMs = timings.prompt_ms;
|
||||||
if (timings.predicted_n) this.predictedTokens = timings.predicted_n;
|
if (timings.predicted_n) this.predictedTokens = timings.predicted_n;
|
||||||
if (timings.predicted_ms) this.predictedMs = timings.predicted_ms;
|
if (timings.predicted_ms) this.predictedMs = timings.predicted_ms;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
this.isGenerating = false;
|
this.isGenerating = false;
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error('Notebook generation error:', error);
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
this.isGenerating = false;
|
// aborted by user
|
||||||
}
|
} else {
|
||||||
},
|
console.error('Notebook generation error:', error);
|
||||||
this.abortController.signal
|
}
|
||||||
);
|
this.isGenerating = false;
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Notebook generation failed:', error);
|
},
|
||||||
this.isGenerating = false;
|
this.abortController.signal
|
||||||
}
|
);
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('Notebook generation failed:', error);
|
||||||
|
this.isGenerating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (this.abortController) {
|
if (this.abortController) {
|
||||||
this.abortController.abort();
|
this.abortController.abort();
|
||||||
this.abortController = null;
|
this.abortController = null;
|
||||||
}
|
}
|
||||||
this.isGenerating = false;
|
this.isGenerating = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notebookStore = new NotebookStore();
|
export const notebookStore = new NotebookStore();
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,12 @@ export interface ApiContextSizeError {
|
||||||
|
|
||||||
export interface ApiErrorResponse {
|
export interface ApiErrorResponse {
|
||||||
error:
|
error:
|
||||||
| ApiContextSizeError
|
| ApiContextSizeError
|
||||||
| {
|
| {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiChatMessageData {
|
export interface ApiChatMessageData {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue