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
|
||||
/*.code-workspace
|
||||
/.windsurf/
|
||||
/.agent/
|
||||
# emscripten
|
||||
a.out.*
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -118,17 +118,6 @@
|
|||
<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
||||
</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">
|
||||
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
|
||||
<Sidebar.GroupLabel>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<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 { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
|
|
@ -63,6 +63,18 @@
|
|||
<KeyboardShortcutInfo keys={['shift', 'cmd', 'o']} />
|
||||
</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
|
||||
class="w-full justify-between hover:[&>kbd]:opacity-100"
|
||||
onclick={() => {
|
||||
|
|
|
|||
|
|
@ -2,19 +2,22 @@
|
|||
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, Settings, Info } from '@lucide/svelte';
|
||||
import { Play, Square, Settings } 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';
|
||||
import { ModelsSelector } from '$lib/components/app';
|
||||
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 settingsOpen = $state(false);
|
||||
let modelInfoOpen = $state(false);
|
||||
|
||||
let inputContent = $state(content);
|
||||
|
||||
let isRouter = $derived(isRouterMode());
|
||||
|
||||
// Sync local input with store content
|
||||
$effect(() => {
|
||||
inputContent = notebookStore.content;
|
||||
|
|
@ -26,16 +29,70 @@
|
|||
}
|
||||
|
||||
async function handleGenerate() {
|
||||
await notebookStore.generate();
|
||||
if (notebookModel == null) {
|
||||
notebookModel = activeModelId;
|
||||
}
|
||||
await notebookStore.generate(notebookModel);
|
||||
}
|
||||
|
||||
function handleStop() {
|
||||
notebookStore.stop();
|
||||
}
|
||||
|
||||
let currentModel = $derived(
|
||||
modelsStore.models.find((m) => m.id === config().model) || modelsStore.models[0]
|
||||
);
|
||||
let activeModelId = $derived.by(() => {
|
||||
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>
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
|
|
@ -55,43 +112,55 @@
|
|||
value={inputContent}
|
||||
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"
|
||||
placeholder="Enter your prompt here..."
|
||||
placeholder="Enter your text here..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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 gap-2">
|
||||
<Button
|
||||
onclick={notebookStore.isGenerating ? handleStop : handleGenerate}
|
||||
size="sm"
|
||||
variant={notebookStore.isGenerating ? 'destructive' : 'default'}
|
||||
class="gap-2"
|
||||
>
|
||||
{#if notebookStore.isGenerating}
|
||||
<Square class="h-4 w-4 fill-current" />
|
||||
Stop
|
||||
{:else}
|
||||
<Play class="h-4 w-4 fill-current" />
|
||||
Generate
|
||||
{/if}
|
||||
</Button>
|
||||
{#snippet generateButton(props = {})}
|
||||
<Button
|
||||
disabled={isDisabled}
|
||||
onclick={notebookStore.isGenerating ? handleStop : handleGenerate}
|
||||
size="sm"
|
||||
variant={notebookStore.isGenerating ? 'destructive' : 'default'}
|
||||
class="gap-2"
|
||||
>
|
||||
{#if notebookStore.isGenerating}
|
||||
<Square class="h-4 w-4 fill-current" />
|
||||
Stop
|
||||
{:else}
|
||||
<Play class="h-4 w-4 fill-current" />
|
||||
Generate
|
||||
{/if}
|
||||
</Button>
|
||||
{/snippet}
|
||||
|
||||
<Button variant="ghost" size="icon" onclick={() => (modelInfoOpen = true)}>
|
||||
<Info class="h-4 w-4" />
|
||||
</Button>
|
||||
{#if generateTooltip}
|
||||
<Tooltip.Root>
|
||||
<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>
|
||||
|
||||
<ChatMessageStatistics
|
||||
predictedTokens={notebookStore.predictedTokens}
|
||||
predictedMs={notebookStore.predictedMs}
|
||||
promptTokens={notebookStore.promptTokens}
|
||||
promptMs={notebookStore.promptMs}
|
||||
isLive={notebookStore.isGenerating}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogChatSettings open={settingsOpen} onOpenChange={(open) => (settingsOpen = open)} />
|
||||
<DialogModelInformation open={modelInfoOpen} onOpenChange={(open) => (modelInfoOpen = open)} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,70 +2,74 @@ import { ChatService } from '$lib/services/chat';
|
|||
import { config } from '$lib/stores/settings.svelte';
|
||||
|
||||
export class NotebookStore {
|
||||
content = $state('');
|
||||
isGenerating = $state(false);
|
||||
abortController: AbortController | null = null;
|
||||
content = $state('');
|
||||
isGenerating = $state(false);
|
||||
abortController: AbortController | null = null;
|
||||
|
||||
// Statistics
|
||||
promptTokens = $state(0);
|
||||
promptMs = $state(0);
|
||||
predictedTokens = $state(0);
|
||||
predictedMs = $state(0);
|
||||
// Statistics
|
||||
promptTokens = $state(0);
|
||||
promptMs = $state(0);
|
||||
predictedTokens = $state(0);
|
||||
predictedMs = $state(0);
|
||||
|
||||
async generate(model?: string) {
|
||||
if (this.isGenerating) return;
|
||||
async generate(model?: string) {
|
||||
if (this.isGenerating) return;
|
||||
|
||||
this.isGenerating = true;
|
||||
this.abortController = new AbortController();
|
||||
this.isGenerating = true;
|
||||
this.abortController = new AbortController();
|
||||
|
||||
// Reset stats
|
||||
this.promptTokens = 0;
|
||||
this.promptMs = 0;
|
||||
this.predictedTokens = 0;
|
||||
this.predictedMs = 0;
|
||||
// Reset stats
|
||||
this.promptTokens = 0;
|
||||
this.promptMs = 0;
|
||||
this.predictedTokens = 0;
|
||||
this.predictedMs = 0;
|
||||
|
||||
try {
|
||||
const currentConfig = config();
|
||||
await ChatService.sendCompletion(
|
||||
this.content,
|
||||
{
|
||||
...currentConfig,
|
||||
model,
|
||||
stream: true,
|
||||
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;
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Notebook generation error:', error);
|
||||
this.isGenerating = false;
|
||||
}
|
||||
},
|
||||
this.abortController.signal
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Notebook generation failed:', error);
|
||||
this.isGenerating = false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const currentConfig = config();
|
||||
await ChatService.sendCompletion(
|
||||
this.content,
|
||||
{
|
||||
...currentConfig,
|
||||
model: model ?? currentConfig.model,
|
||||
stream: true,
|
||||
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;
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
// aborted by user
|
||||
} else {
|
||||
console.error('Notebook generation error:', error);
|
||||
}
|
||||
this.isGenerating = false;
|
||||
}
|
||||
},
|
||||
this.abortController.signal
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Notebook generation failed:', error);
|
||||
this.isGenerating = false;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
this.abortController = null;
|
||||
}
|
||||
this.isGenerating = false;
|
||||
}
|
||||
stop() {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
this.abortController = null;
|
||||
}
|
||||
this.isGenerating = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const notebookStore = new NotebookStore();
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ export interface ApiContextSizeError {
|
|||
|
||||
export interface ApiErrorResponse {
|
||||
error:
|
||||
| ApiContextSizeError
|
||||
| {
|
||||
code: number;
|
||||
message: string;
|
||||
type?: string;
|
||||
};
|
||||
| ApiContextSizeError
|
||||
| {
|
||||
code: number;
|
||||
message: string;
|
||||
type?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiChatMessageData {
|
||||
|
|
|
|||
Loading…
Reference in New Issue