fix: improve light mode theming and text contrast
- Increase light mode text contrast in app.css (slate.600→700, slate.500→600) - Add light/dark mode prose styles in MessageContent.svelte for proper markdown rendering - Convert hardcoded slate-* classes to theme utilities across 37 components - Fix code block copy button and scrollbar theming for both modes - Update all route pages (models, tools, knowledge, prompts) with theme classes - Ensure consistent theming in modals, dialogs, and form inputs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,9 @@
|
||||
--color-bg-message-user: theme('colors.slate.200');
|
||||
--color-bg-message-assistant: transparent;
|
||||
--color-text-primary: theme('colors.slate.900');
|
||||
--color-text-secondary: theme('colors.slate.600');
|
||||
--color-text-muted: theme('colors.slate.500');
|
||||
--color-text-placeholder: theme('colors.slate.400');
|
||||
--color-text-secondary: theme('colors.slate.700');
|
||||
--color-text-muted: theme('colors.slate.600');
|
||||
--color-text-placeholder: theme('colors.slate.500');
|
||||
--color-border: theme('colors.slate.300');
|
||||
--color-border-subtle: theme('colors.slate.200');
|
||||
}
|
||||
|
||||
@@ -18,14 +18,20 @@
|
||||
isStreaming?: boolean;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
/** Hide the attach button in FileUpload (when shown elsewhere) */
|
||||
hideAttachButton?: boolean;
|
||||
/** Bindable function to trigger file picker from parent */
|
||||
triggerFilePicker?: () => void;
|
||||
}
|
||||
|
||||
const {
|
||||
let {
|
||||
onSend,
|
||||
onStop,
|
||||
isStreaming = false,
|
||||
disabled = false,
|
||||
placeholder = 'Type a message...'
|
||||
placeholder = 'Type a message...',
|
||||
hideAttachButton = false,
|
||||
triggerFilePicker = $bindable()
|
||||
}: Props = $props();
|
||||
|
||||
// Input state
|
||||
@@ -292,6 +298,8 @@
|
||||
supportsVision={isVisionModel}
|
||||
{disabled}
|
||||
hideDropZone={true}
|
||||
hideButton={hideAttachButton}
|
||||
bind:triggerPicker={triggerFilePicker}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
@@ -75,6 +75,9 @@
|
||||
// System prompt for new conversations (before a conversation is created)
|
||||
let newChatPromptId = $state<string | null>(null);
|
||||
|
||||
// File picker trigger function (bound from ChatInput -> FileUpload)
|
||||
let triggerFilePicker: (() => void) | undefined = $state();
|
||||
|
||||
// Derived: Check if selected model supports thinking
|
||||
const supportsThinking = $derived.by(() => {
|
||||
const caps = modelsState.selectedCapabilities;
|
||||
@@ -843,7 +846,7 @@
|
||||
<StreamingStats />
|
||||
</div>
|
||||
|
||||
<!-- Chat options bar (settings toggle + system prompt + thinking mode toggle) -->
|
||||
<!-- Chat options bar: [Custom] [System Prompt] ... [Attach] [Thinking] -->
|
||||
<div class="flex items-center justify-between gap-3 px-4 pt-3">
|
||||
<!-- Left side: Settings gear + System prompt selector -->
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -879,26 +882,43 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Right side: Thinking mode toggle -->
|
||||
{#if supportsThinking}
|
||||
<label class="flex cursor-pointer items-center gap-2 text-xs text-theme-muted">
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="text-amber-400">🧠</span>
|
||||
Thinking mode
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={thinkingEnabled}
|
||||
onclick={() => (thinkingEnabled = !thinkingEnabled)}
|
||||
class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 focus:ring-offset-slate-900 {thinkingEnabled ? 'bg-amber-600' : 'bg-slate-600'}"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out {thinkingEnabled ? 'translate-x-4' : 'translate-x-0'}"
|
||||
></span>
|
||||
</button>
|
||||
</label>
|
||||
{/if}
|
||||
<!-- Right side: Attach files + Thinking mode toggle -->
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Attach files button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => triggerFilePicker?.()}
|
||||
disabled={!modelsState.selectedId}
|
||||
class="flex items-center gap-1.5 rounded px-2 py-1 text-xs text-theme-muted transition-colors hover:bg-theme-hover hover:text-theme-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
aria-label="Attach files"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4">
|
||||
<path fill-rule="evenodd" d="M15.621 4.379a3 3 0 0 0-4.242 0l-7 7a3 3 0 0 0 4.241 4.243h.001l.497-.5a.75.75 0 0 1 1.064 1.057l-.498.501-.002.002a4.5 4.5 0 0 1-6.364-6.364l7-7a4.5 4.5 0 0 1 6.368 6.36l-3.455 3.553A2.625 2.625 0 1 1 9.52 9.52l3.45-3.451a.75.75 0 1 1 1.061 1.06l-3.45 3.451a1.125 1.125 0 0 0 1.587 1.595l3.454-3.553a3 3 0 0 0 0-4.242Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span>Attach</span>
|
||||
</button>
|
||||
|
||||
<!-- Thinking mode toggle -->
|
||||
{#if supportsThinking}
|
||||
<label class="flex cursor-pointer items-center gap-2 text-xs text-theme-muted">
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="text-amber-400">🧠</span>
|
||||
Thinking
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={thinkingEnabled}
|
||||
onclick={() => (thinkingEnabled = !thinkingEnabled)}
|
||||
class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 focus:ring-offset-theme-primary {thinkingEnabled ? 'bg-amber-600' : 'bg-theme-tertiary'}"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out {thinkingEnabled ? 'translate-x-4' : 'translate-x-0'}"
|
||||
></span>
|
||||
</button>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model parameters panel -->
|
||||
@@ -912,6 +932,8 @@
|
||||
onStop={handleStopStreaming}
|
||||
isStreaming={chatState.isStreaming}
|
||||
disabled={!modelsState.selectedId}
|
||||
hideAttachButton={true}
|
||||
bind:triggerFilePicker
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -209,10 +209,10 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="group relative overflow-hidden rounded-xl border border-slate-700/50" style="contain: layout;">
|
||||
<div class="group relative overflow-hidden rounded-xl border border-theme/50" style="contain: layout;">
|
||||
<!-- Header with language label, run button, and copy button -->
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-slate-700/50 bg-slate-800/80 px-3 py-1.5 text-xs text-slate-400"
|
||||
class="flex items-center justify-between border-b border-theme/50 bg-theme-secondary/80 px-3 py-1.5 text-xs text-theme-muted"
|
||||
>
|
||||
<span class="font-mono uppercase">{language}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -249,7 +249,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleCopy}
|
||||
class="flex items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-gray-700 hover:text-gray-200"
|
||||
class="flex items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
aria-label={copied ? 'Copied!' : 'Copy code'}
|
||||
>
|
||||
{#if copied}
|
||||
@@ -283,9 +283,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Code content - use same styling for loading/loaded to prevent layout shift -->
|
||||
<div class="code-block-content overflow-x-auto bg-slate-900/90">
|
||||
<div class="code-block-content overflow-x-auto bg-theme-primary/90">
|
||||
{#if isLoading}
|
||||
<pre class="m-0 overflow-x-auto bg-transparent px-4 py-3" style="line-height: 1.5;"><code class="font-mono text-[13px] text-slate-300" style="line-height: inherit;">{code}</code></pre>
|
||||
<pre class="m-0 overflow-x-auto bg-transparent px-4 py-3" style="line-height: 1.5;"><code class="font-mono text-[13px] text-theme-secondary" style="line-height: inherit;">{code}</code></pre>
|
||||
{:else}
|
||||
{@html highlightedHtml}
|
||||
{/if}
|
||||
@@ -293,15 +293,15 @@
|
||||
|
||||
<!-- Execution output -->
|
||||
{#if showOutput && (isExecuting || executionResult)}
|
||||
<div class="border-t border-slate-700/50 bg-slate-950/50">
|
||||
<div class="border-t border-theme/50 bg-theme-primary/50">
|
||||
<!-- Output header -->
|
||||
<div class="flex items-center justify-between px-3 py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Terminal icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4 text-slate-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4 text-theme-muted">
|
||||
<path fill-rule="evenodd" d="M3.25 3A2.25 2.25 0 001 5.25v9.5A2.25 2.25 0 003.25 17h13.5A2.25 2.25 0 0019 14.75v-9.5A2.25 2.25 0 0016.75 3H3.25zm.943 8.752a.75.75 0 01.055-1.06L6.128 9l-1.88-1.693a.75.75 0 111.004-1.114l2.5 2.25a.75.75 0 010 1.114l-2.5 2.25a.75.75 0 01-1.06-.055zM9.75 10.25a.75.75 0 000 1.5h2.5a.75.75 0 000-1.5h-2.5z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span class="text-xs font-medium text-slate-400">Output</span>
|
||||
<span class="text-xs font-medium text-theme-muted">Output</span>
|
||||
|
||||
{#if isExecuting}
|
||||
<span class="flex items-center gap-1.5 rounded-full bg-blue-500/10 px-2 py-0.5 text-[11px] text-blue-400">
|
||||
@@ -313,7 +313,7 @@
|
||||
</span>
|
||||
{:else if executionResult}
|
||||
{#if executionResult.status === 'success'}
|
||||
<span class="text-[11px] text-slate-500">Completed</span>
|
||||
<span class="text-[11px] text-theme-muted">Completed</span>
|
||||
{:else}
|
||||
<span class="flex items-center gap-1 rounded-full bg-red-500/10 px-2 py-0.5 text-[11px] text-red-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="h-3 w-3">
|
||||
@@ -322,13 +322,13 @@
|
||||
Error
|
||||
</span>
|
||||
{/if}
|
||||
<span class="text-[11px] text-slate-600">{executionResult.duration}ms</span>
|
||||
<span class="text-[11px] text-theme-muted">{executionResult.duration}ms</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={clearOutput}
|
||||
class="rounded-md px-2 py-1 text-[11px] text-slate-500 transition-colors hover:bg-slate-800 hover:text-slate-300"
|
||||
class="rounded-md px-2 py-1 text-[11px] text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-secondary"
|
||||
aria-label="Clear output"
|
||||
>
|
||||
Clear
|
||||
@@ -336,11 +336,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Output content -->
|
||||
<div class="max-h-48 overflow-auto border-t border-slate-800/50 bg-slate-950/30 px-3 py-2">
|
||||
<div class="max-h-48 overflow-auto border-t border-theme/50 bg-theme-primary/30 px-3 py-2">
|
||||
{#if executionResult?.outputs.length}
|
||||
<pre class="font-mono text-[12px] leading-relaxed">{#each executionResult.outputs as output}<span class={getOutputClass(output.type)}>{output.content}</span>{/each}</pre>
|
||||
{:else if !isExecuting}
|
||||
<p class="text-[12px] italic text-slate-600">No output</p>
|
||||
<p class="text-[12px] italic text-theme-muted">No output</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -373,11 +373,21 @@
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
/* Light mode scrollbar */
|
||||
.code-block-content :global(pre)::-webkit-scrollbar-thumb {
|
||||
@apply rounded-full bg-slate-600/50;
|
||||
@apply rounded-full bg-slate-400/50;
|
||||
}
|
||||
|
||||
.code-block-content :global(pre)::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-slate-500/70;
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:global(.dark) .code-block-content :global(pre)::-webkit-scrollbar-thumb {
|
||||
@apply bg-slate-600/50;
|
||||
}
|
||||
|
||||
:global(.dark) .code-block-content :global(pre)::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-slate-500/70;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- Backdrop -->
|
||||
<div class="fixed inset-0 z-[200] flex items-center justify-center bg-black/70 backdrop-blur-sm">
|
||||
<!-- Modal -->
|
||||
<div class="mx-4 w-full max-w-md rounded-2xl border border-red-500/30 bg-slate-900 p-6 shadow-2xl">
|
||||
<div class="mx-4 w-full max-w-md rounded-2xl border border-red-500/30 bg-theme-primary p-6 shadow-2xl">
|
||||
<!-- Header with warning icon -->
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-red-500/20">
|
||||
@@ -44,20 +44,20 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">Context Window Full</h2>
|
||||
<p class="text-sm text-slate-400">
|
||||
<h2 class="text-lg font-semibold text-theme-primary">Context Window Full</h2>
|
||||
<p class="text-sm text-theme-muted">
|
||||
{formatTokenCount(usage.usedTokens)} / {formatContextSize(usage.maxTokens)} tokens used
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Explanation -->
|
||||
<div class="mb-6 rounded-lg bg-slate-800/50 p-4 text-sm text-slate-300">
|
||||
<div class="mb-6 rounded-lg bg-theme-secondary/50 p-4 text-sm text-theme-secondary">
|
||||
<p class="mb-2">
|
||||
The conversation has exceeded the model's context window by
|
||||
<span class="font-medium text-red-400">{formatTokenCount(overflowAmount)} tokens</span>.
|
||||
</p>
|
||||
<p class="text-slate-400">
|
||||
<p class="text-theme-muted">
|
||||
The model cannot process more text until space is freed. Choose how to proceed:
|
||||
</p>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@
|
||||
<div class="font-medium text-emerald-300">
|
||||
{isSummarizing ? 'Summarizing...' : 'Summarize & Continue'}
|
||||
</div>
|
||||
<div class="text-xs text-slate-400">
|
||||
<div class="text-xs text-theme-muted">
|
||||
Compress older messages into a summary to free space
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,16 +102,16 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={onNewChat}
|
||||
class="flex w-full items-center gap-3 rounded-xl border border-slate-600/50 bg-slate-800/50 p-4 text-left transition-colors hover:bg-slate-700/50"
|
||||
class="flex w-full items-center gap-3 rounded-xl border border-theme-subtle/50 bg-theme-secondary/50 p-4 text-left transition-colors hover:bg-theme-tertiary/50"
|
||||
>
|
||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-slate-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-theme-tertiary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-theme-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-slate-200">Start New Chat</div>
|
||||
<div class="text-xs text-slate-400">
|
||||
<div class="font-medium text-theme-secondary">Start New Chat</div>
|
||||
<div class="text-xs text-theme-muted">
|
||||
Begin a fresh conversation (current chat is saved)
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,16 +121,16 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={onDismiss}
|
||||
class="flex w-full items-center gap-3 rounded-xl border border-slate-700/50 p-4 text-left transition-colors hover:bg-slate-800/50"
|
||||
class="flex w-full items-center gap-3 rounded-xl border border-theme/50 p-4 text-left transition-colors hover:bg-theme-secondary/50"
|
||||
>
|
||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-slate-800">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-theme-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-slate-400">Continue Anyway</div>
|
||||
<div class="text-xs text-slate-500">
|
||||
<div class="font-medium text-theme-muted">Continue Anyway</div>
|
||||
<div class="text-xs text-theme-muted">
|
||||
Try to send (may result in errors or truncated responses)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
|
||||
{#if compact}
|
||||
<!-- Compact mode: just a thin bar -->
|
||||
<div class="group relative h-1 w-24 overflow-hidden rounded-full bg-slate-700">
|
||||
<div class="group relative h-1 w-24 overflow-hidden rounded-full bg-theme-tertiary">
|
||||
<div
|
||||
class="h-full transition-all duration-300 {barColorClass}"
|
||||
style="width: {Math.min(100, usage.percentage)}%"
|
||||
></div>
|
||||
<!-- Tooltip on hover -->
|
||||
<div
|
||||
class="pointer-events-none absolute bottom-full left-1/2 mb-2 -translate-x-1/2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-300 opacity-0 shadow-lg transition-opacity group-hover:opacity-100"
|
||||
class="pointer-events-none absolute bottom-full left-1/2 mb-2 -translate-x-1/2 whitespace-nowrap rounded bg-theme-secondary px-2 py-1 text-xs text-theme-secondary opacity-0 shadow-lg transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
{contextManager.statusMessage}
|
||||
</div>
|
||||
@@ -36,7 +36,7 @@
|
||||
{:else}
|
||||
<!-- Full mode: bar with label -->
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-2 flex-1 overflow-hidden rounded-full bg-slate-700">
|
||||
<div class="h-2 flex-1 overflow-hidden rounded-full bg-theme-tertiary">
|
||||
<div
|
||||
class="h-full transition-all duration-300 {barColorClass}"
|
||||
style="width: {Math.min(100, usage.percentage)}%"
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="group relative flex items-start gap-3 rounded-lg border border-slate-700/50 bg-slate-800/50 p-3 transition-colors hover:bg-slate-800"
|
||||
class="group relative flex items-start gap-3 rounded-lg border border-theme/50 bg-theme-secondary/50 p-3 transition-colors hover:bg-theme-secondary"
|
||||
>
|
||||
<!-- File icon -->
|
||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-slate-700/50 text-lg">
|
||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-theme-tertiary/50 text-lg">
|
||||
{getFileIcon(attachment.type)}
|
||||
</div>
|
||||
|
||||
@@ -51,13 +51,13 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium text-slate-200" title={attachment.filename}>
|
||||
<p class="truncate text-sm font-medium text-theme-secondary" title={attachment.filename}>
|
||||
{attachment.filename}
|
||||
</p>
|
||||
<p class="text-xs text-slate-500">
|
||||
<p class="text-xs text-theme-muted">
|
||||
{formatFileSize(attachment.size)}
|
||||
{#if attachment.type === 'pdf'}
|
||||
<span class="text-slate-600">·</span>
|
||||
<span class="text-theme-muted">·</span>
|
||||
<span class="text-violet-400">PDF</span>
|
||||
{/if}
|
||||
</p>
|
||||
@@ -68,7 +68,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleRemove}
|
||||
class="shrink-0 rounded p-1 text-slate-500 opacity-0 transition-all hover:bg-red-900/30 hover:text-red-400 group-hover:opacity-100"
|
||||
class="shrink-0 rounded p-1 text-theme-muted opacity-0 transition-all hover:bg-red-900/30 hover:text-red-400 group-hover:opacity-100"
|
||||
aria-label="Remove file"
|
||||
title="Remove"
|
||||
>
|
||||
@@ -94,14 +94,14 @@
|
||||
class="mt-2 w-full text-left"
|
||||
>
|
||||
<div
|
||||
class="rounded border border-slate-700/50 bg-slate-900/50 p-2 text-xs text-slate-400 transition-colors hover:border-slate-600"
|
||||
class="rounded border border-theme/50 bg-theme-primary/50 p-2 text-xs text-theme-muted transition-colors hover:border-theme-subtle"
|
||||
>
|
||||
{#if isExpanded}
|
||||
<pre class="max-h-60 overflow-auto whitespace-pre-wrap break-words font-mono">{attachment.textContent}</pre>
|
||||
{:else}
|
||||
<p class="truncate font-mono">{previewText}</p>
|
||||
{/if}
|
||||
<p class="mt-1 text-[10px] text-slate-600">
|
||||
<p class="mt-1 text-[10px] text-theme-muted">
|
||||
{isExpanded ? 'Click to collapse' : 'Click to expand'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -24,18 +24,29 @@
|
||||
disabled?: boolean;
|
||||
/** Hide the drop zone (when drag overlay is handled by parent) */
|
||||
hideDropZone?: boolean;
|
||||
/** Hide the attach button (when button is rendered elsewhere) */
|
||||
hideButton?: boolean;
|
||||
/** Bindable function to trigger file picker from parent */
|
||||
triggerPicker?: () => void;
|
||||
}
|
||||
|
||||
const {
|
||||
let {
|
||||
images,
|
||||
onImagesChange,
|
||||
attachments,
|
||||
onAttachmentsChange,
|
||||
supportsVision = false,
|
||||
disabled = false,
|
||||
hideDropZone = false
|
||||
hideDropZone = false,
|
||||
hideButton = false,
|
||||
triggerPicker = $bindable()
|
||||
}: Props = $props();
|
||||
|
||||
// Expose the picker trigger to parent
|
||||
$effect(() => {
|
||||
triggerPicker = openFilePicker;
|
||||
});
|
||||
|
||||
// Processing state
|
||||
let isProcessing = $state(false);
|
||||
let errorMessage = $state<string | null>(null);
|
||||
@@ -249,74 +260,76 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Add files button -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Hidden file input -->
|
||||
<input
|
||||
bind:this={fileInputRef}
|
||||
type="file"
|
||||
accept={fileAccept}
|
||||
multiple
|
||||
class="hidden"
|
||||
onchange={handleFileSelect}
|
||||
{disabled}
|
||||
/>
|
||||
<!-- Hidden file input (always present for programmatic triggering) -->
|
||||
<input
|
||||
bind:this={fileInputRef}
|
||||
type="file"
|
||||
accept={fileAccept}
|
||||
multiple
|
||||
class="hidden"
|
||||
onchange={handleFileSelect}
|
||||
{disabled}
|
||||
/>
|
||||
|
||||
<!-- Attach files button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={openFilePicker}
|
||||
disabled={disabled || isProcessing}
|
||||
class="flex items-center gap-1.5 rounded-lg border border-slate-700/50 bg-slate-800/50 px-3 py-1.5 text-xs text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-300 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#if isProcessing}
|
||||
<svg
|
||||
class="h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
<!-- Add files button (optional - can be hidden when button is elsewhere) -->
|
||||
{#if !hideButton}
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Attach files button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={openFilePicker}
|
||||
disabled={disabled || isProcessing}
|
||||
class="flex items-center gap-1.5 rounded-lg border border-theme/50 bg-theme-secondary/50 px-3 py-1.5 text-xs text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#if isProcessing}
|
||||
<svg
|
||||
class="h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Processing...</span>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Processing...</span>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.621 4.379a3 3 0 0 0-4.242 0l-7 7a3 3 0 0 0 4.241 4.243h.001l.497-.5a.75.75 0 0 1 1.064 1.057l-.498.501-.002.002a4.5 4.5 0 0 1-6.364-6.364l7-7a4.5 4.5 0 0 1 6.368 6.36l-3.455 3.553A2.625 2.625 0 1 1 9.52 9.52l3.45-3.451a.75.75 0 1 1 1.061 1.06l-3.45 3.451a1.125 1.125 0 0 0 1.587 1.595l3.454-3.553a3 3 0 0 0 0-4.242Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Attach files</span>
|
||||
{/if}
|
||||
</button>
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.621 4.379a3 3 0 0 0-4.242 0l-7 7a3 3 0 0 0 4.241 4.243h.001l.497-.5a.75.75 0 0 1 1.064 1.057l-.498.501-.002.002a4.5 4.5 0 0 1-6.364-6.364l7-7a4.5 4.5 0 0 1 6.368 6.36l-3.455 3.553A2.625 2.625 0 1 1 9.52 9.52l3.45-3.451a.75.75 0 1 1 1.061 1.06l-3.45 3.451a1.125 1.125 0 0 0 1.587 1.595l3.454-3.553a3 3 0 0 0 0-4.242Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Attach files</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- File type hint -->
|
||||
<span class="text-[10px] text-slate-600">
|
||||
{#if supportsVision}
|
||||
Images, text files, PDFs
|
||||
{:else}
|
||||
Text files, PDFs (content will be included in message)
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<!-- File type hint -->
|
||||
<span class="text-[10px] text-theme-muted">
|
||||
{#if supportsVision}
|
||||
Images, text files, PDFs
|
||||
{:else}
|
||||
Text files, PDFs (content will be included in message)
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Error message -->
|
||||
{#if errorMessage}
|
||||
|
||||
@@ -394,9 +394,9 @@
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* Prose styling overrides for chat context (dark theme) */
|
||||
/* Prose styling overrides for chat context */
|
||||
.message-content :global(.prose) {
|
||||
@apply leading-relaxed text-slate-200;
|
||||
@apply leading-relaxed;
|
||||
}
|
||||
|
||||
.message-content :global(.prose p) {
|
||||
@@ -415,12 +415,8 @@
|
||||
@apply my-2 rounded-lg;
|
||||
}
|
||||
|
||||
.message-content :global(.prose code:not(pre code)) {
|
||||
@apply rounded bg-slate-700 px-1.5 py-0.5 text-sm text-emerald-400;
|
||||
}
|
||||
|
||||
.message-content :global(.prose a) {
|
||||
@apply text-blue-400 hover:text-blue-300 hover:underline;
|
||||
@apply hover:underline;
|
||||
}
|
||||
|
||||
.message-content :global(.prose ul),
|
||||
@@ -432,37 +428,55 @@
|
||||
@apply my-0.5;
|
||||
}
|
||||
|
||||
.message-content :global(.prose blockquote) {
|
||||
@apply border-l-4 border-slate-600 pl-4 italic text-slate-400;
|
||||
}
|
||||
|
||||
.message-content :global(.prose table) {
|
||||
@apply my-2 w-full border-collapse text-sm;
|
||||
}
|
||||
|
||||
.message-content :global(.prose th),
|
||||
.message-content :global(.prose td) {
|
||||
@apply border border-slate-600 px-3 py-2;
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
|
||||
/* Light mode prose styles */
|
||||
.message-content :global(.prose) {
|
||||
@apply text-slate-800;
|
||||
}
|
||||
|
||||
.message-content :global(.prose code:not(pre code)) {
|
||||
@apply rounded bg-slate-200 px-1.5 py-0.5 text-sm text-emerald-700;
|
||||
}
|
||||
|
||||
.message-content :global(.prose a) {
|
||||
@apply text-blue-600 hover:text-blue-700;
|
||||
}
|
||||
|
||||
.message-content :global(.prose blockquote) {
|
||||
@apply border-l-4 border-slate-300 pl-4 italic text-slate-600;
|
||||
}
|
||||
|
||||
.message-content :global(.prose th),
|
||||
.message-content :global(.prose td) {
|
||||
@apply border border-slate-300;
|
||||
}
|
||||
|
||||
.message-content :global(.prose th) {
|
||||
@apply bg-slate-700 font-semibold text-slate-200;
|
||||
@apply bg-slate-100 font-semibold text-slate-800;
|
||||
}
|
||||
|
||||
.message-content :global(.prose td) {
|
||||
@apply bg-slate-800/50 text-slate-300;
|
||||
@apply bg-white text-slate-700;
|
||||
}
|
||||
|
||||
.message-content :global(.prose tr:hover td) {
|
||||
@apply bg-slate-700/50;
|
||||
@apply bg-slate-50;
|
||||
}
|
||||
|
||||
.message-content :global(.prose strong) {
|
||||
@apply text-slate-100;
|
||||
@apply text-slate-900;
|
||||
}
|
||||
|
||||
.message-content :global(.prose em) {
|
||||
@apply text-slate-300;
|
||||
@apply text-slate-700;
|
||||
}
|
||||
|
||||
.message-content :global(.prose h1),
|
||||
@@ -471,10 +485,65 @@
|
||||
.message-content :global(.prose h4),
|
||||
.message-content :global(.prose h5),
|
||||
.message-content :global(.prose h6) {
|
||||
@apply text-slate-100;
|
||||
@apply text-slate-900;
|
||||
}
|
||||
|
||||
.message-content :global(.prose hr) {
|
||||
@apply border-slate-300;
|
||||
}
|
||||
|
||||
/* Dark mode prose styles */
|
||||
:global(.dark) .message-content :global(.prose) {
|
||||
@apply text-slate-200;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose code:not(pre code)) {
|
||||
@apply bg-slate-700 text-emerald-400;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose a) {
|
||||
@apply text-blue-400 hover:text-blue-300;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose blockquote) {
|
||||
@apply border-slate-600 text-slate-400;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose th),
|
||||
:global(.dark) .message-content :global(.prose td) {
|
||||
@apply border-slate-600;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose th) {
|
||||
@apply bg-slate-700 text-slate-200;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose td) {
|
||||
@apply bg-slate-800/50 text-slate-300;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose tr:hover td) {
|
||||
@apply bg-slate-700/50;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose strong) {
|
||||
@apply text-slate-100;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose em) {
|
||||
@apply text-slate-300;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose h1),
|
||||
:global(.dark) .message-content :global(.prose h2),
|
||||
:global(.dark) .message-content :global(.prose h3),
|
||||
:global(.dark) .message-content :global(.prose h4),
|
||||
:global(.dark) .message-content :global(.prose h5),
|
||||
:global(.dark) .message-content :global(.prose h6) {
|
||||
@apply text-slate-100;
|
||||
}
|
||||
|
||||
:global(.dark) .message-content :global(.prose hr) {
|
||||
@apply border-slate-600;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={scrollToBottom}
|
||||
class="absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full bg-slate-700 px-4 py-2 text-sm text-slate-200 shadow-lg transition-all hover:bg-slate-600"
|
||||
class="absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full bg-theme-tertiary px-4 py-2 text-sm text-theme-secondary shadow-lg transition-all hover:bg-theme-secondary"
|
||||
aria-label="Scroll to latest message"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
aria-label="Generating response"
|
||||
>
|
||||
<span
|
||||
class="animate-bounce rounded-full bg-slate-400 dark:bg-slate-500 {sizeClasses}"
|
||||
class="animate-bounce rounded-full bg-theme-muted {sizeClasses}"
|
||||
style="animation-delay: 0ms; animation-duration: 1s;"
|
||||
></span>
|
||||
<span
|
||||
class="animate-bounce rounded-full bg-slate-400 dark:bg-slate-500 {sizeClasses}"
|
||||
class="animate-bounce rounded-full bg-theme-muted {sizeClasses}"
|
||||
style="animation-delay: 150ms; animation-duration: 1s;"
|
||||
></span>
|
||||
<span
|
||||
class="animate-bounce rounded-full bg-slate-400 dark:bg-slate-500 {sizeClasses}"
|
||||
class="animate-bounce rounded-full bg-theme-muted {sizeClasses}"
|
||||
style="animation-delay: 300ms; animation-duration: 1s;"
|
||||
></span>
|
||||
<span class="sr-only">Generating response...</span>
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
onclick={toggleDropdown}
|
||||
class="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors {currentPrompt
|
||||
? 'bg-violet-500/20 text-violet-300 hover:bg-violet-500/30'
|
||||
: 'text-slate-400 hover:bg-slate-800 hover:text-slate-200'}"
|
||||
: 'text-theme-muted hover:bg-theme-secondary hover:text-theme-secondary'}"
|
||||
title={currentPrompt ? `System prompt: ${currentPrompt.name}` : 'Set system prompt'}
|
||||
>
|
||||
<svg
|
||||
@@ -133,15 +133,15 @@
|
||||
<!-- Dropdown menu -->
|
||||
{#if isOpen}
|
||||
<div
|
||||
class="absolute left-0 top-full z-50 mt-1 w-64 rounded-lg border border-slate-700 bg-slate-800 py-1 shadow-xl"
|
||||
class="absolute left-0 top-full z-50 mt-1 w-64 rounded-lg border border-theme bg-theme-secondary py-1 shadow-xl"
|
||||
>
|
||||
<!-- No prompt option -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSelect(null)}
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors hover:bg-slate-700 {!currentPromptId
|
||||
? 'bg-slate-700/50 text-slate-100'
|
||||
: 'text-slate-300'}"
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors hover:bg-theme-tertiary {!currentPromptId
|
||||
? 'bg-theme-tertiary/50 text-theme-primary'
|
||||
: 'text-theme-secondary'}"
|
||||
>
|
||||
<span class="flex-1">No system prompt</span>
|
||||
{#if !currentPromptId}
|
||||
@@ -161,23 +161,23 @@
|
||||
</button>
|
||||
|
||||
{#if prompts.length > 0}
|
||||
<div class="my-1 border-t border-slate-700"></div>
|
||||
<div class="my-1 border-t border-theme"></div>
|
||||
|
||||
<!-- Available prompts -->
|
||||
{#each prompts as prompt}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSelect(prompt.id)}
|
||||
class="flex w-full flex-col gap-0.5 px-3 py-2 text-left transition-colors hover:bg-slate-700 {currentPromptId ===
|
||||
class="flex w-full flex-col gap-0.5 px-3 py-2 text-left transition-colors hover:bg-theme-tertiary {currentPromptId ===
|
||||
prompt.id
|
||||
? 'bg-slate-700/50'
|
||||
? 'bg-theme-tertiary/50'
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="flex-1 text-sm font-medium {currentPromptId === prompt.id
|
||||
? 'text-slate-100'
|
||||
: 'text-slate-300'}"
|
||||
? 'text-theme-primary'
|
||||
: 'text-theme-secondary'}"
|
||||
>
|
||||
{prompt.name}
|
||||
{#if prompt.isDefault}
|
||||
@@ -200,12 +200,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if prompt.description}
|
||||
<span class="line-clamp-1 text-xs text-slate-500">{prompt.description}</span>
|
||||
<span class="line-clamp-1 text-xs text-theme-muted">{prompt.description}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="px-3 py-2 text-xs text-slate-500">
|
||||
<div class="px-3 py-2 text-xs text-theme-muted">
|
||||
No prompts available. <a href="/prompts" class="text-violet-400 hover:underline"
|
||||
>Create one</a
|
||||
>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
const defaultMeta = {
|
||||
icon: '⚙️',
|
||||
color: 'from-slate-500 to-slate-600',
|
||||
color: 'from-gray-500 to-gray-600',
|
||||
label: 'Tool'
|
||||
};
|
||||
|
||||
@@ -187,14 +187,14 @@
|
||||
{@const isExpanded = expandedCalls.has(call.id)}
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded-xl border border-slate-700/50 bg-gradient-to-r {meta.color} p-[1px] shadow-lg"
|
||||
class="overflow-hidden rounded-xl border border-theme/50 bg-gradient-to-r {meta.color} p-[1px] shadow-lg"
|
||||
>
|
||||
<div class="rounded-xl bg-slate-900/95 backdrop-blur">
|
||||
<div class="rounded-xl bg-theme-primary/95 backdrop-blur">
|
||||
<!-- Header -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => toggleExpand(call.id)}
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-slate-800/50"
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-theme-secondary/50"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<span class="text-xl" role="img" aria-label={meta.label}>{meta.icon}</span>
|
||||
@@ -202,14 +202,14 @@
|
||||
<!-- Tool name and summary -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-slate-100">{meta.label}</span>
|
||||
<span class="font-mono text-xs text-slate-500">{call.name}</span>
|
||||
<span class="font-medium text-theme-primary">{meta.label}</span>
|
||||
<span class="font-mono text-xs text-theme-muted">{call.name}</span>
|
||||
</div>
|
||||
|
||||
<!-- Quick preview of main argument -->
|
||||
{#if argEntries.length > 0}
|
||||
{@const [firstKey, firstValue] = argEntries[0]}
|
||||
<p class="mt-0.5 truncate text-sm text-slate-400">
|
||||
<p class="mt-0.5 truncate text-sm text-theme-muted">
|
||||
{#if call.name === 'web_search' && typeof firstValue === 'string'}
|
||||
Searching: "{firstValue}"
|
||||
{:else if call.name === 'fetch_url' && typeof firstValue === 'string'}
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
<!-- Expand indicator -->
|
||||
<svg
|
||||
class="h-5 w-5 flex-shrink-0 text-slate-500 transition-transform duration-200"
|
||||
class="h-5 w-5 flex-shrink-0 text-theme-muted transition-transform duration-200"
|
||||
class:rotate-180={isExpanded}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -240,14 +240,14 @@
|
||||
|
||||
<!-- Expanded arguments -->
|
||||
{#if isExpanded && argEntries.length > 0}
|
||||
<div class="border-t border-slate-800 px-4 py-3">
|
||||
<div class="border-t border-theme px-4 py-3">
|
||||
<div class="space-y-2">
|
||||
{#each argEntries as [key, value]}
|
||||
<div class="flex items-start gap-3 text-sm">
|
||||
<span class="w-24 flex-shrink-0 font-medium text-slate-500">
|
||||
<span class="w-24 flex-shrink-0 font-medium text-theme-muted">
|
||||
{argLabel(key)}
|
||||
</span>
|
||||
<span class="break-all font-mono text-slate-300">
|
||||
<span class="break-all font-mono text-theme-secondary">
|
||||
{formatValue(value)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -262,11 +262,11 @@
|
||||
{@const parsed = parseResult(call.result)}
|
||||
{@const isResultExpanded = expandedResults.has(call.id)}
|
||||
|
||||
<div class="border-t border-slate-800">
|
||||
<div class="border-t border-theme">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => toggleResult(call.id)}
|
||||
class="flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition-colors hover:bg-slate-800/50"
|
||||
class="flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition-colors hover:bg-theme-secondary/50"
|
||||
>
|
||||
<!-- Status icon -->
|
||||
{#if call.error}
|
||||
@@ -274,13 +274,13 @@
|
||||
<span class="flex-1 text-red-300">Error: {call.error}</span>
|
||||
{:else}
|
||||
<span class="text-emerald-400">✓</span>
|
||||
<span class="flex-1 text-slate-400">{parsed.summary}</span>
|
||||
<span class="flex-1 text-theme-muted">{parsed.summary}</span>
|
||||
{/if}
|
||||
|
||||
<!-- Expand arrow -->
|
||||
{#if hasResult && parsed.full}
|
||||
<svg
|
||||
class="h-4 w-4 flex-shrink-0 text-slate-500 transition-transform duration-200"
|
||||
class="h-4 w-4 flex-shrink-0 text-theme-muted transition-transform duration-200"
|
||||
class:rotate-180={isResultExpanded}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -294,8 +294,8 @@
|
||||
|
||||
<!-- Expanded result content -->
|
||||
{#if isResultExpanded && hasResult && parsed.full}
|
||||
<div class="max-h-96 overflow-auto border-t border-slate-800/50 bg-slate-950/50 px-4 py-3">
|
||||
<pre class="whitespace-pre-wrap break-words text-xs text-slate-400">{parsed.full.length > 10000 ? parsed.full.substring(0, 10000) + '\n\n... (truncated)' : parsed.full}</pre>
|
||||
<div class="max-h-96 overflow-auto border-t border-theme/50 bg-theme-primary/50 px-4 py-3">
|
||||
<pre class="whitespace-pre-wrap break-words text-xs text-theme-muted">{parsed.full.length > 10000 ? parsed.full.substring(0, 10000) + '\n\n... (truncated)' : parsed.full}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<div class="flex items-center gap-3 px-4 py-3">
|
||||
<span class="text-2xl">📍</span>
|
||||
<div>
|
||||
<p class="font-medium text-slate-100">
|
||||
<p class="font-medium text-theme-primary">
|
||||
{#if loc.location?.city}
|
||||
{loc.location.city}{#if loc.location.country}, {loc.location.country}{/if}
|
||||
{:else if loc.message}
|
||||
@@ -116,9 +116,9 @@
|
||||
{/if}
|
||||
</p>
|
||||
{#if loc.source === 'ip'}
|
||||
<p class="text-xs text-slate-500">Based on IP address (approximate)</p>
|
||||
<p class="text-xs text-theme-muted">Based on IP address (approximate)</p>
|
||||
{:else if loc.source === 'gps'}
|
||||
<p class="text-xs text-slate-500">From device GPS</p>
|
||||
<p class="text-xs text-theme-muted">From device GPS</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@
|
||||
{:else if parsed.type === 'search'}
|
||||
{@const search = parsed.data as SearchData}
|
||||
<div class="my-3 space-y-2">
|
||||
<div class="flex items-center gap-2 text-sm text-slate-400">
|
||||
<div class="flex items-center gap-2 text-sm text-theme-muted">
|
||||
<span>🔍</span>
|
||||
<span>Found {search.resultCount || search.results?.length || 0} results for "{search.query}"</span>
|
||||
</div>
|
||||
@@ -139,15 +139,15 @@
|
||||
href={result.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block rounded-lg border border-slate-700/50 bg-slate-800/50 p-3 transition-colors hover:border-blue-500/50 hover:bg-slate-800"
|
||||
class="block rounded-lg border border-theme/50 bg-theme-secondary/50 p-3 transition-colors hover:border-blue-500/50 hover:bg-theme-secondary"
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
<span class="mt-0.5 text-blue-400">#{result.rank}</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="font-medium text-blue-400 hover:underline">{result.title}</p>
|
||||
<p class="mt-0.5 truncate text-xs text-slate-500">{result.url}</p>
|
||||
<p class="mt-0.5 truncate text-xs text-theme-muted">{result.url}</p>
|
||||
{#if result.snippet && result.snippet !== '(no snippet available)'}
|
||||
<p class="mt-1 text-sm text-slate-400">{result.snippet}</p>
|
||||
<p class="mt-1 text-sm text-theme-muted">{result.snippet}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -172,32 +172,32 @@
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span>🌐</span>
|
||||
{#if data.title}
|
||||
<span class="font-medium text-slate-200">{data.title}</span>
|
||||
<span class="font-medium text-theme-secondary">{data.title}</span>
|
||||
{:else if data.url}
|
||||
<a href={String(data.url)} target="_blank" rel="noopener noreferrer" class="text-violet-400 hover:underline">
|
||||
{data.url}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="text-slate-400">Fetched content</span>
|
||||
<span class="text-theme-muted">Fetched content</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if data.text && typeof data.text === 'string'}
|
||||
<p class="mt-2 line-clamp-4 text-sm text-slate-400">{data.text.substring(0, 300)}{data.text.length > 300 ? '...' : ''}</p>
|
||||
<p class="mt-2 line-clamp-4 text-sm text-theme-muted">{data.text.substring(0, 300)}{data.text.length > 300 ? '...' : ''}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if parsed.type === 'json'}
|
||||
{@const data = parsed.data as Record<string, unknown>}
|
||||
<div class="my-3 rounded-xl border border-slate-700/50 bg-slate-800/50 p-3">
|
||||
<pre class="overflow-x-auto text-xs text-slate-400">{JSON.stringify(data, null, 2)}</pre>
|
||||
<div class="my-3 rounded-xl border border-theme/50 bg-theme-secondary/50 p-3">
|
||||
<pre class="overflow-x-auto text-xs text-theme-muted">{JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<!-- Fallback: just show the text (if not empty/whitespace) -->
|
||||
{#if typeof parsed.data === 'string' && parsed.data.trim().length > 0}
|
||||
<div class="my-3 rounded-xl border border-slate-700/50 bg-slate-800/50 p-3">
|
||||
<p class="text-sm text-slate-300">{parsed.data}</p>
|
||||
<div class="my-3 rounded-xl border border-theme/50 bg-theme-secondary/50 p-3">
|
||||
<p class="text-sm text-theme-secondary">{parsed.data}</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<a
|
||||
href="/chat/{conversation.id}"
|
||||
onclick={handleClick}
|
||||
class="group relative flex items-start gap-3 rounded-lg px-3 py-2.5 transition-colors {isSelected ? 'bg-slate-800' : 'hover:bg-slate-800/60'}"
|
||||
class="group relative flex items-start gap-3 rounded-lg px-3 py-2.5 transition-colors {isSelected ? 'bg-theme-secondary' : 'hover:bg-theme-secondary/60'}"
|
||||
>
|
||||
<!-- Chat icon -->
|
||||
<div class="mt-0.5 shrink-0">
|
||||
@@ -100,7 +100,7 @@
|
||||
<!-- Regular chat bubble -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-slate-500"
|
||||
class="h-4 w-4 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -120,15 +120,15 @@
|
||||
<!-- Title -->
|
||||
<p
|
||||
class="truncate text-sm font-medium"
|
||||
class:text-slate-100={isSelected}
|
||||
class:text-slate-300={!isSelected}
|
||||
class:text-theme-primary={isSelected}
|
||||
class:text-theme-secondary={!isSelected}
|
||||
title={conversation.title || 'New Conversation'}
|
||||
>
|
||||
{conversation.title || 'New Conversation'}
|
||||
</p>
|
||||
|
||||
<!-- Meta info (model + time) -->
|
||||
<div class="mt-0.5 flex items-center gap-2 text-xs text-slate-500">
|
||||
<div class="mt-0.5 flex items-center gap-2 text-xs text-theme-muted">
|
||||
<span class="truncate">{conversation.model}</span>
|
||||
<span class="shrink-0">-</span>
|
||||
<span class="shrink-0">{formatRelativeTime(conversation.updatedAt)}</span>
|
||||
@@ -141,7 +141,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handlePin}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-slate-700 hover:text-slate-200"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
aria-label={conversation.isPinned ? 'Unpin conversation' : 'Pin conversation'}
|
||||
title={conversation.isPinned ? 'Unpin' : 'Pin'}
|
||||
>
|
||||
@@ -178,7 +178,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleExport}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-slate-700 hover:text-slate-200"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
aria-label="Export conversation"
|
||||
title="Export"
|
||||
>
|
||||
@@ -202,7 +202,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleDelete}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-red-900/50 hover:text-red-400"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-red-900/50 hover:text-red-400"
|
||||
aria-label="Delete conversation"
|
||||
title="Delete"
|
||||
>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="flex flex-col items-center justify-center px-4 py-8 text-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mb-3 h-12 w-12 text-slate-600"
|
||||
class="mb-3 h-12 w-12 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -29,7 +29,7 @@
|
||||
/>
|
||||
</svg>
|
||||
{#if conversationsState.searchQuery}
|
||||
<p class="text-sm text-slate-500">No conversations match your search</p>
|
||||
<p class="text-sm text-theme-muted">No conversations match your search</p>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => conversationsState.clearSearch()}
|
||||
@@ -38,8 +38,8 @@
|
||||
Clear search
|
||||
</button>
|
||||
{:else}
|
||||
<p class="text-sm text-slate-500">No conversations yet</p>
|
||||
<p class="mt-1 text-xs text-slate-600">Start a new chat to begin</p>
|
||||
<p class="text-sm text-theme-muted">No conversations yet</p>
|
||||
<p class="mt-1 text-xs text-theme-muted">Start a new chat to begin</p>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
@@ -47,7 +47,7 @@
|
||||
{#each conversationsState.grouped as { group, conversations } (group)}
|
||||
<div class="mb-2">
|
||||
<!-- Group header -->
|
||||
<h3 class="sticky top-0 z-10 bg-slate-950 px-2 py-1.5 text-xs font-medium uppercase tracking-wider text-slate-500">
|
||||
<h3 class="sticky top-0 z-10 bg-theme-primary px-2 py-1.5 text-xs font-medium uppercase tracking-wider text-theme-muted">
|
||||
{group}
|
||||
</h3>
|
||||
|
||||
@@ -65,11 +65,11 @@
|
||||
|
||||
<!-- Archived section -->
|
||||
{#if conversationsState.archived.length > 0}
|
||||
<div class="mt-4 border-t border-slate-700/50 pt-2">
|
||||
<div class="mt-4 border-t border-theme/50 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showArchived = !showArchived)}
|
||||
class="flex w-full items-center justify-between px-2 py-1.5 text-xs font-medium uppercase tracking-wider text-slate-500 hover:text-slate-400"
|
||||
class="flex w-full items-center justify-between px-2 py-1.5 text-xs font-medium uppercase tracking-wider text-theme-muted hover:text-theme-muted"
|
||||
>
|
||||
<span>Archived ({conversationsState.archived.length})</span>
|
||||
<svg
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
amber: { bg: 'bg-amber-900/50', text: 'text-amber-300' },
|
||||
red: { bg: 'bg-red-900/50', text: 'text-red-300' },
|
||||
sky: { bg: 'bg-sky-900/50', text: 'text-sky-300' },
|
||||
slate: { bg: 'bg-slate-800', text: 'text-slate-400' }
|
||||
slate: { bg: 'bg-theme-secondary', text: 'text-theme-muted' }
|
||||
};
|
||||
return colors[color] ?? colors.slate;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
type="button"
|
||||
onclick={() => (isOpen = !isOpen)}
|
||||
disabled={modelsState.isLoading}
|
||||
class="flex min-w-[180px] items-center justify-between gap-2 rounded-lg border border-slate-700 bg-slate-800/50 px-3 py-2 text-sm transition-colors hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex min-w-[180px] items-center justify-between gap-2 rounded-lg border border-theme bg-theme-secondary/50 px-3 py-2 text-sm transition-colors hover:bg-theme-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Model icon -->
|
||||
@@ -76,27 +76,27 @@
|
||||
</svg>
|
||||
|
||||
{#if modelsState.isLoading}
|
||||
<span class="text-slate-400">Loading models...</span>
|
||||
<span class="text-theme-muted">Loading models...</span>
|
||||
{:else if modelsState.selected}
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-slate-200">{modelsState.selected.name}</span>
|
||||
<span class="text-theme-secondary">{modelsState.selected.name}</span>
|
||||
<!-- Capability icons -->
|
||||
<ModelCapabilityIcons modelName={modelsState.selected.name} />
|
||||
</div>
|
||||
<span class="text-xs text-slate-500">{modelsState.selected.details.parameter_size}</span>
|
||||
<span class="text-xs text-theme-muted">{modelsState.selected.details.parameter_size}</span>
|
||||
</div>
|
||||
{:else if modelsState.error}
|
||||
<span class="text-red-400">Error loading models</span>
|
||||
{:else}
|
||||
<span class="text-slate-400">Select a model</span>
|
||||
<span class="text-theme-muted">Select a model</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Chevron icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-slate-500 transition-transform"
|
||||
class="h-4 w-4 text-theme-muted transition-transform"
|
||||
class:rotate-180={isOpen}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -110,7 +110,7 @@
|
||||
<!-- Dropdown menu -->
|
||||
{#if isOpen && !modelsState.isLoading}
|
||||
<div
|
||||
class="absolute left-0 top-full z-[100] mt-1 max-h-80 min-w-[280px] overflow-y-auto rounded-lg border border-slate-700 bg-slate-900 py-1 shadow-xl"
|
||||
class="absolute left-0 top-full z-[100] mt-1 max-h-80 min-w-[280px] overflow-y-auto rounded-lg border border-theme bg-theme-primary py-1 shadow-xl"
|
||||
>
|
||||
{#if modelsState.error}
|
||||
<div class="px-3 py-4 text-center text-sm text-red-400">
|
||||
@@ -124,14 +124,14 @@
|
||||
</button>
|
||||
</div>
|
||||
{:else if modelsState.grouped.length === 0}
|
||||
<div class="px-3 py-4 text-center text-sm text-slate-500">
|
||||
<div class="px-3 py-4 text-center text-sm text-theme-muted">
|
||||
<p>No models available</p>
|
||||
<p class="mt-1 text-xs">Make sure Ollama is running</p>
|
||||
</div>
|
||||
{:else}
|
||||
{#each modelsState.grouped as group (group.family)}
|
||||
<!-- Group header -->
|
||||
<div class="sticky top-0 bg-slate-900 px-3 py-1.5 text-xs font-medium uppercase tracking-wider text-slate-500">
|
||||
<div class="sticky top-0 bg-theme-primary px-3 py-1.5 text-xs font-medium uppercase tracking-wider text-theme-muted">
|
||||
{group.family}
|
||||
</div>
|
||||
|
||||
@@ -140,17 +140,17 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => selectModel(model.name)}
|
||||
class="flex w-full items-center justify-between px-3 py-2 text-left transition-colors {modelsState.selectedId === model.name ? 'bg-emerald-900/30 text-emerald-400' : 'hover:bg-slate-800'}"
|
||||
class="flex w-full items-center justify-between px-3 py-2 text-left transition-colors {modelsState.selectedId === model.name ? 'bg-emerald-900/30 text-emerald-400' : 'hover:bg-theme-secondary'}"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm" class:text-slate-200={modelsState.selectedId !== model.name}>
|
||||
<span class="text-sm" class:text-theme-secondary={modelsState.selectedId !== model.name}>
|
||||
{model.name}
|
||||
</span>
|
||||
<!-- Capability icons for models in dropdown -->
|
||||
<ModelCapabilityIcons modelName={model.name} compact />
|
||||
</div>
|
||||
<span class="text-xs text-slate-500">
|
||||
<span class="text-xs text-theme-muted">
|
||||
{model.details.parameter_size}
|
||||
{#if model.details.quantization_level}
|
||||
- {model.details.quantization_level}
|
||||
@@ -158,7 +158,7 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 text-xs text-slate-500">
|
||||
<div class="flex items-center gap-2 text-xs text-theme-muted">
|
||||
<span>{formatSize(model.size)}</span>
|
||||
{#if modelsState.selectedId === model.name}
|
||||
<svg
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-slate-100">Ollama Chat</span>
|
||||
<span class="text-lg font-semibold text-theme-primary">Ollama Chat</span>
|
||||
</div>
|
||||
|
||||
<!-- Close sidenav button (visible on mobile) -->
|
||||
@@ -44,7 +44,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => uiState.closeSidenav()}
|
||||
class="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg p-1.5 text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-primary"
|
||||
aria-label="Close sidebar"
|
||||
>
|
||||
<svg
|
||||
@@ -86,7 +86,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showImportDialog = true)}
|
||||
class="rounded-lg border border-slate-600 p-2.5 text-slate-400 transition-colors hover:border-slate-500 hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg border border-theme-subtle p-2.5 text-theme-muted transition-colors hover:border-theme-subtle hover:bg-theme-secondary hover:text-theme-primary"
|
||||
aria-label="Import conversation"
|
||||
title="Import conversation"
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<!-- Search icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-500"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -42,7 +42,7 @@
|
||||
oninput={handleInput}
|
||||
placeholder="Search conversations..."
|
||||
data-search-input
|
||||
class="w-full rounded-lg border border-slate-700 bg-slate-800/50 py-2 pl-10 pr-9 text-sm text-slate-200 placeholder-slate-500 transition-colors focus:border-emerald-500/50 focus:bg-slate-800 focus:outline-none focus:ring-1 focus:ring-emerald-500/50"
|
||||
class="w-full rounded-lg border border-theme bg-theme-secondary/50 py-2 pl-10 pr-9 text-sm text-theme-secondary placeholder-theme-muted transition-colors focus:border-emerald-500/50 focus:bg-theme-secondary focus:outline-none focus:ring-1 focus:ring-emerald-500/50"
|
||||
/>
|
||||
|
||||
<!-- Clear button (visible when there's text) -->
|
||||
@@ -50,7 +50,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleClear}
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-slate-500 transition-colors hover:bg-slate-700 hover:text-slate-300"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-secondary"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -25,17 +25,17 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => onSelect?.(model)}
|
||||
class="group w-full rounded-lg border border-slate-700 bg-slate-800 p-4 text-left transition-all hover:border-slate-600 hover:bg-slate-750"
|
||||
class="group w-full rounded-lg border border-theme bg-theme-secondary p-4 text-left transition-all hover:border-theme-subtle hover:bg-theme-tertiary"
|
||||
>
|
||||
<!-- Header: Name and Type Badge -->
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<h3 class="font-medium text-white group-hover:text-blue-400">
|
||||
<h3 class="font-medium text-theme-primary group-hover:text-blue-400">
|
||||
{model.name}
|
||||
</h3>
|
||||
<span
|
||||
class="shrink-0 rounded px-2 py-0.5 text-xs {model.modelType === 'official'
|
||||
? 'bg-blue-900/50 text-blue-300'
|
||||
: 'bg-slate-700 text-slate-400'}"
|
||||
: 'bg-theme-tertiary text-theme-muted'}"
|
||||
>
|
||||
{model.modelType}
|
||||
</span>
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<!-- Description -->
|
||||
{#if model.description}
|
||||
<p class="mt-2 line-clamp-2 text-sm text-slate-400">
|
||||
<p class="mt-2 line-clamp-2 text-sm text-theme-muted">
|
||||
{model.description}
|
||||
</p>
|
||||
{/if}
|
||||
@@ -64,7 +64,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Stats Row -->
|
||||
<div class="mt-3 flex items-center gap-4 text-xs text-slate-500">
|
||||
<div class="mt-3 flex items-center gap-4 text-xs text-theme-muted">
|
||||
<!-- Pull Count -->
|
||||
<div class="flex items-center gap-1" title="{model.pullCount.toLocaleString()} pulls">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
|
||||
@@ -45,14 +45,14 @@
|
||||
aria-labelledby="pull-dialog-title"
|
||||
>
|
||||
<!-- Dialog -->
|
||||
<div class="w-full max-w-md rounded-xl bg-slate-800 p-6 shadow-xl">
|
||||
<h2 id="pull-dialog-title" class="mb-4 text-lg font-semibold text-slate-100">
|
||||
<div class="w-full max-w-md rounded-xl bg-theme-secondary p-6 shadow-xl">
|
||||
<h2 id="pull-dialog-title" class="mb-4 text-lg font-semibold text-theme-primary">
|
||||
Pull Model
|
||||
</h2>
|
||||
|
||||
<form onsubmit={handleSubmit}>
|
||||
<div class="mb-4">
|
||||
<label for="model-name" class="mb-2 block text-sm text-slate-300">
|
||||
<label for="model-name" class="mb-2 block text-sm text-theme-secondary">
|
||||
Model Name
|
||||
</label>
|
||||
<input
|
||||
@@ -60,13 +60,13 @@
|
||||
type="text"
|
||||
bind:value={modelOperationsState.pullModelInput}
|
||||
placeholder="e.g., llama3.2, mistral:7b, codellama:13b"
|
||||
class="w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-slate-100 placeholder-slate-400 focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
|
||||
class="w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-primary placeholder-theme-muted focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-slate-400">
|
||||
<p class="mt-1 text-xs text-theme-muted">
|
||||
Enter the model name with optional tag (e.g., llama3.2:latest)
|
||||
</p>
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => modelOperationsState.closePullDialog()}
|
||||
class="rounded-lg px-4 py-2 text-sm text-slate-300 hover:bg-slate-700"
|
||||
class="rounded-lg px-4 py-2 text-sm text-theme-secondary hover:bg-theme-tertiary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -51,17 +51,17 @@
|
||||
|
||||
{#if settingsState.isPanelOpen}
|
||||
<div
|
||||
class="rounded-lg border border-slate-700/50 bg-slate-800/50 p-4"
|
||||
class="rounded-lg border border-theme/50 bg-theme-secondary/50 p-4"
|
||||
role="region"
|
||||
aria-label="Model parameters"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h3 class="text-sm font-medium text-slate-200">Model Parameters</h3>
|
||||
<h3 class="text-sm font-medium text-theme-secondary">Model Parameters</h3>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => settingsState.closePanel()}
|
||||
class="rounded p-1 text-slate-400 hover:bg-slate-700 hover:text-slate-200"
|
||||
class="rounded p-1 text-theme-muted hover:bg-theme-tertiary hover:text-theme-secondary"
|
||||
aria-label="Close settings panel"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
<!-- Enable custom parameters toggle -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<label class="flex items-center gap-2 text-sm text-slate-300">
|
||||
<label class="flex items-center gap-2 text-sm text-theme-secondary">
|
||||
<span>Use custom parameters</span>
|
||||
</label>
|
||||
<button
|
||||
@@ -80,7 +80,7 @@
|
||||
role="switch"
|
||||
aria-checked={settingsState.useCustomParameters}
|
||||
onclick={() => settingsState.toggleCustomParameters()}
|
||||
class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-slate-800 {settingsState.useCustomParameters ? 'bg-sky-600' : 'bg-slate-600'}"
|
||||
class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-theme-secondary {settingsState.useCustomParameters ? 'bg-sky-600' : 'bg-theme-tertiary'}"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out {settingsState.useCustomParameters ? 'translate-x-4' : 'translate-x-0'}"
|
||||
@@ -97,13 +97,13 @@
|
||||
|
||||
<div>
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<label for="param-{key}" class="text-xs font-medium text-slate-300">
|
||||
<label for="param-{key}" class="text-xs font-medium text-theme-secondary">
|
||||
{PARAMETER_LABELS[key]}
|
||||
{#if !isDefault}
|
||||
<span class="ml-1 text-sky-400">*</span>
|
||||
{/if}
|
||||
</label>
|
||||
<span class="font-mono text-xs text-slate-400">
|
||||
<span class="font-mono text-xs text-theme-muted">
|
||||
{formatValue(key, value)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -117,11 +117,11 @@
|
||||
value={value}
|
||||
oninput={(e) => handleSliderChange(key, e)}
|
||||
disabled={!settingsState.useCustomParameters}
|
||||
class="h-1.5 w-full cursor-pointer appearance-none rounded-lg bg-slate-700 disabled:cursor-not-allowed [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-sky-500 disabled:[&::-webkit-slider-thumb]:bg-slate-500"
|
||||
class="h-1.5 w-full cursor-pointer appearance-none rounded-lg bg-theme-tertiary disabled:cursor-not-allowed [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-sky-500 disabled:[&::-webkit-slider-thumb]:bg-theme-muted"
|
||||
title={PARAMETER_DESCRIPTIONS[key]}
|
||||
/>
|
||||
|
||||
<p class="mt-0.5 text-[10px] text-slate-500">
|
||||
<p class="mt-0.5 text-[10px] text-theme-muted">
|
||||
{PARAMETER_DESCRIPTIONS[key]}
|
||||
</p>
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => settingsState.resetToDefaults()}
|
||||
class="rounded px-2 py-1 text-xs text-slate-400 hover:bg-slate-700 hover:text-slate-200"
|
||||
class="rounded px-2 py-1 text-xs text-theme-muted hover:bg-theme-tertiary hover:text-theme-secondary"
|
||||
>
|
||||
Reset to defaults
|
||||
</button>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="mx-4 w-full max-w-md rounded-xl border border-slate-700 bg-slate-900 shadow-2xl"
|
||||
class="mx-4 w-full max-w-md rounded-xl border border-theme bg-theme-primary shadow-2xl"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<!-- Content -->
|
||||
@@ -155,10 +155,10 @@
|
||||
|
||||
<!-- Text content -->
|
||||
<div class="flex-1">
|
||||
<h3 id="confirm-dialog-title" class="text-lg font-semibold text-slate-100">
|
||||
<h3 id="confirm-dialog-title" class="text-lg font-semibold text-theme-primary">
|
||||
{title}
|
||||
</h3>
|
||||
<p id="confirm-dialog-description" class="mt-2 text-sm text-slate-400">
|
||||
<p id="confirm-dialog-description" class="mt-2 text-sm text-theme-muted">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
@@ -166,18 +166,18 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-end gap-3 border-t border-slate-700 px-6 py-4">
|
||||
<div class="flex justify-end gap-3 border-t border-theme px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={onCancel}
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-slate-300 transition-colors hover:bg-slate-800 hover:text-slate-100"
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-theme-secondary transition-colors hover:bg-theme-secondary hover:text-theme-primary"
|
||||
>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleConfirm}
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-white transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-900 {confirmButtonStyles}"
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-white transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-theme {confirmButtonStyles}"
|
||||
>
|
||||
{confirmText}
|
||||
</button>
|
||||
|
||||
@@ -189,18 +189,18 @@
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="mx-4 w-full max-w-lg rounded-xl border border-slate-700 bg-slate-900 shadow-2xl"
|
||||
class="mx-4 w-full max-w-lg rounded-xl border border-theme bg-theme-primary shadow-2xl"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-slate-700 px-6 py-4">
|
||||
<h2 id="export-dialog-title" class="text-lg font-semibold text-slate-100">
|
||||
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
||||
<h2 id="export-dialog-title" class="text-lg font-semibold text-theme-primary">
|
||||
Export Conversation
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg p-1.5 text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-secondary"
|
||||
aria-label="Close dialog"
|
||||
>
|
||||
<svg
|
||||
@@ -221,7 +221,7 @@
|
||||
{#if isLoading}
|
||||
<!-- Loading state -->
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<svg class="h-6 w-6 animate-spin text-slate-400" viewBox="0 0 24 24">
|
||||
<svg class="h-6 w-6 animate-spin text-theme-muted" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
@@ -237,7 +237,7 @@
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="ml-2 text-sm text-slate-400">Loading conversation...</span>
|
||||
<span class="ml-2 text-sm text-theme-muted">Loading conversation...</span>
|
||||
</div>
|
||||
{:else if loadError}
|
||||
<!-- Load error state -->
|
||||
@@ -247,12 +247,12 @@
|
||||
{:else}
|
||||
<!-- Format selection -->
|
||||
<fieldset class="space-y-3">
|
||||
<legend class="text-sm font-medium text-slate-300">Export Format</legend>
|
||||
<legend class="text-sm font-medium text-theme-secondary">Export Format</legend>
|
||||
<div class="flex gap-4">
|
||||
<label
|
||||
class="flex cursor-pointer items-center gap-2 rounded-lg border px-4 py-3 transition-colors {selectedFormat === 'markdown'
|
||||
? 'border-emerald-500 bg-emerald-500/10 text-emerald-400'
|
||||
: 'border-slate-700 bg-slate-800/50 text-slate-300 hover:border-slate-600'}"
|
||||
: 'border-theme bg-theme-secondary/50 text-theme-secondary hover:border-theme-subtle'}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
@@ -280,7 +280,7 @@
|
||||
<label
|
||||
class="flex cursor-pointer items-center gap-2 rounded-lg border px-4 py-3 transition-colors {selectedFormat === 'json'
|
||||
? 'border-emerald-500 bg-emerald-500/10 text-emerald-400'
|
||||
: 'border-slate-700 bg-slate-800/50 text-slate-300 hover:border-slate-600'}"
|
||||
: 'border-theme bg-theme-secondary/50 text-theme-secondary hover:border-theme-subtle'}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
@@ -310,9 +310,9 @@
|
||||
|
||||
<!-- Preview -->
|
||||
<div class="space-y-2">
|
||||
<span class="text-sm font-medium text-slate-300">Preview</span>
|
||||
<span class="text-sm font-medium text-theme-secondary">Preview</span>
|
||||
<div
|
||||
class="max-h-48 overflow-auto rounded-lg border border-slate-700 bg-slate-950 p-3 font-mono text-xs text-slate-400"
|
||||
class="max-h-48 overflow-auto rounded-lg border border-theme bg-theme-primary p-3 font-mono text-xs text-theme-muted"
|
||||
aria-label="Export preview"
|
||||
>
|
||||
<pre class="whitespace-pre-wrap">{preview}</pre>
|
||||
@@ -330,13 +330,13 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<div
|
||||
class="flex flex-col gap-3 border-t border-slate-700 px-6 py-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
class="flex flex-col gap-3 border-t border-theme px-6 py-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<!-- Share button -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleShare}
|
||||
class="flex items-center justify-center gap-2 rounded-lg border border-slate-600 px-4 py-2.5 text-sm font-medium text-slate-300 transition-colors hover:bg-slate-800 hover:text-slate-100"
|
||||
class="flex items-center justify-center gap-2 rounded-lg border border-theme-subtle px-4 py-2.5 text-sm font-medium text-theme-secondary transition-colors hover:bg-theme-secondary hover:text-theme-primary"
|
||||
>
|
||||
{#if shareCopied}
|
||||
<svg
|
||||
@@ -374,7 +374,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-secondary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -173,16 +173,16 @@
|
||||
aria-labelledby="import-dialog-title"
|
||||
>
|
||||
<!-- Dialog -->
|
||||
<div class="mx-4 w-full max-w-lg rounded-xl border border-slate-700 bg-slate-900 shadow-2xl">
|
||||
<div class="mx-4 w-full max-w-lg rounded-xl border border-theme bg-theme-primary shadow-2xl">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-slate-700 px-6 py-4">
|
||||
<h2 id="import-dialog-title" class="text-lg font-semibold text-slate-100">
|
||||
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
||||
<h2 id="import-dialog-title" class="text-lg font-semibold text-theme-primary">
|
||||
Import Conversation
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleClose}
|
||||
class="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg p-1.5 text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-secondary"
|
||||
aria-label="Close dialog"
|
||||
>
|
||||
<svg
|
||||
@@ -218,10 +218,10 @@
|
||||
ondrop={handleDrop}
|
||||
class="w-full rounded-lg border-2 border-dashed p-8 text-center transition-colors {isDragOver
|
||||
? 'border-emerald-500 bg-emerald-500/10'
|
||||
: 'border-slate-600 hover:border-slate-500'}"
|
||||
: 'border-theme-subtle hover:border-theme-subtle'}"
|
||||
>
|
||||
<svg
|
||||
class="mx-auto h-12 w-12 text-slate-500"
|
||||
class="mx-auto h-12 w-12 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -233,20 +233,20 @@
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
{#if isDragOver}
|
||||
Drop file here
|
||||
{:else}
|
||||
Drag and drop a JSON file, or <span class="text-emerald-400">click to browse</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-slate-500">Only JSON files exported from this app</p>
|
||||
<p class="mt-1 text-xs text-theme-muted">Only JSON files exported from this app</p>
|
||||
</button>
|
||||
|
||||
{#if isValidating}
|
||||
<!-- Validating state -->
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<svg class="h-5 w-5 animate-spin text-slate-400" viewBox="0 0 24 24">
|
||||
<svg class="h-5 w-5 animate-spin text-theme-muted" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
@@ -262,14 +262,14 @@
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="ml-2 text-sm text-slate-400">Validating file...</span>
|
||||
<span class="ml-2 text-sm text-theme-muted">Validating file...</span>
|
||||
</div>
|
||||
{:else if selectedFile && validationResult}
|
||||
<!-- Validation results -->
|
||||
<div class="space-y-3">
|
||||
<!-- File info -->
|
||||
<div class="flex items-center gap-3 rounded-lg bg-slate-800 p-3">
|
||||
<svg class="h-8 w-8 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div class="flex items-center gap-3 rounded-lg bg-theme-secondary p-3">
|
||||
<svg class="h-8 w-8 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -278,8 +278,8 @@
|
||||
/>
|
||||
</svg>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-slate-200">{selectedFile.name}</p>
|
||||
<p class="text-xs text-slate-500">{formatFileSize(selectedFile.size)}</p>
|
||||
<p class="truncate text-sm font-medium text-theme-secondary">{selectedFile.name}</p>
|
||||
<p class="text-xs text-theme-muted">{formatFileSize(selectedFile.size)}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -287,7 +287,7 @@
|
||||
selectedFile = null;
|
||||
validationResult = null;
|
||||
}}
|
||||
class="rounded p-1 text-slate-400 hover:bg-slate-700 hover:text-slate-200"
|
||||
class="rounded p-1 text-theme-muted hover:bg-theme-tertiary hover:text-theme-secondary"
|
||||
aria-label="Remove file"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -305,10 +305,10 @@
|
||||
</svg>
|
||||
<span class="font-medium">Valid conversation file</span>
|
||||
</div>
|
||||
<div class="mt-3 space-y-1 text-sm text-slate-300">
|
||||
<p><span class="text-slate-500">Title:</span> {validationResult.data.title}</p>
|
||||
<p><span class="text-slate-500">Model:</span> {validationResult.data.model}</p>
|
||||
<p><span class="text-slate-500">Messages:</span> {validationResult.data.messages.length}</p>
|
||||
<div class="mt-3 space-y-1 text-sm text-theme-secondary">
|
||||
<p><span class="text-theme-muted">Title:</span> {validationResult.data.title}</p>
|
||||
<p><span class="text-theme-muted">Model:</span> {validationResult.data.model}</p>
|
||||
<p><span class="text-theme-muted">Messages:</span> {validationResult.data.messages.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -351,11 +351,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-end gap-2 border-t border-slate-700 px-6 py-4">
|
||||
<div class="flex justify-end gap-2 border-t border-theme px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleClose}
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg px-4 py-2.5 text-sm font-medium text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-secondary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<!-- Message content skeleton -->
|
||||
<div class="flex-1 max-w-[80%]" class:max-w-[70%]={isUser}>
|
||||
<div
|
||||
class="rounded-2xl px-4 py-3 {isUser ? 'bg-blue-500/20 rounded-br-md' : 'bg-slate-800 rounded-bl-md'}"
|
||||
class="rounded-2xl px-4 py-3 {isUser ? 'bg-blue-500/20 rounded-br-md' : 'bg-theme-secondary rounded-bl-md'}"
|
||||
>
|
||||
<Skeleton variant="text" {lines} height="0.875rem" />
|
||||
</div>
|
||||
|
||||
@@ -158,12 +158,12 @@
|
||||
aria-labelledby="search-dialog-title"
|
||||
>
|
||||
<!-- Dialog -->
|
||||
<div class="mx-4 w-full max-w-2xl rounded-xl border border-slate-700 bg-slate-900 shadow-2xl">
|
||||
<div class="mx-4 w-full max-w-2xl rounded-xl border border-theme bg-theme-primary shadow-2xl">
|
||||
<!-- Search input -->
|
||||
<div class="flex items-center gap-3 border-b border-slate-700 px-4 py-3">
|
||||
<div class="flex items-center gap-3 border-b border-theme px-4 py-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 text-slate-400"
|
||||
class="h-5 w-5 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -181,10 +181,10 @@
|
||||
oninput={handleSearch}
|
||||
type="text"
|
||||
placeholder="Search conversations and messages..."
|
||||
class="flex-1 bg-transparent text-slate-100 placeholder-slate-500 focus:outline-none"
|
||||
class="flex-1 bg-transparent text-theme-primary placeholder-theme-muted focus:outline-none"
|
||||
/>
|
||||
{#if isSearching}
|
||||
<svg class="h-5 w-5 animate-spin text-slate-400" viewBox="0 0 24 24">
|
||||
<svg class="h-5 w-5 animate-spin text-theme-muted" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
@@ -209,7 +209,7 @@
|
||||
messageResults = [];
|
||||
inputElement?.focus();
|
||||
}}
|
||||
class="rounded p-1 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded p-1 text-theme-muted hover:bg-theme-secondary hover:text-theme-secondary"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -222,21 +222,21 @@
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
<kbd class="rounded bg-slate-800 px-2 py-0.5 text-xs text-slate-500">Esc</kbd>
|
||||
<kbd class="rounded bg-theme-secondary px-2 py-0.5 text-xs text-theme-muted">Esc</kbd>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="flex border-b border-slate-700">
|
||||
<div class="flex border-b border-theme">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (activeTab = 'titles')}
|
||||
class="flex-1 px-4 py-2 text-sm font-medium transition-colors {activeTab === 'titles'
|
||||
? 'border-b-2 border-violet-500 text-violet-400'
|
||||
: 'text-slate-400 hover:text-slate-200'}"
|
||||
: 'text-theme-muted hover:text-theme-secondary'}"
|
||||
>
|
||||
Titles
|
||||
{#if titleResults.length > 0}
|
||||
<span class="ml-1.5 rounded-full bg-slate-800 px-1.5 py-0.5 text-xs"
|
||||
<span class="ml-1.5 rounded-full bg-theme-secondary px-1.5 py-0.5 text-xs"
|
||||
>{titleResults.length}</span
|
||||
>
|
||||
{/if}
|
||||
@@ -246,11 +246,11 @@
|
||||
onclick={() => (activeTab = 'messages')}
|
||||
class="flex-1 px-4 py-2 text-sm font-medium transition-colors {activeTab === 'messages'
|
||||
? 'border-b-2 border-violet-500 text-violet-400'
|
||||
: 'text-slate-400 hover:text-slate-200'}"
|
||||
: 'text-theme-muted hover:text-theme-secondary'}"
|
||||
>
|
||||
Messages
|
||||
{#if messageResults.length > 0}
|
||||
<span class="ml-1.5 rounded-full bg-slate-800 px-1.5 py-0.5 text-xs"
|
||||
<span class="ml-1.5 rounded-full bg-theme-secondary px-1.5 py-0.5 text-xs"
|
||||
>{messageResults.length}</span
|
||||
>
|
||||
{/if}
|
||||
@@ -261,7 +261,7 @@
|
||||
<div class="max-h-[50vh] overflow-y-auto">
|
||||
{#if !searchQuery.trim()}
|
||||
<!-- Empty state -->
|
||||
<div class="flex flex-col items-center justify-center py-12 text-slate-500">
|
||||
<div class="flex flex-col items-center justify-center py-12 text-theme-muted">
|
||||
<svg class="mb-3 h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -274,18 +274,18 @@
|
||||
</div>
|
||||
{:else if activeTab === 'titles'}
|
||||
{#if titleResults.length === 0 && !isSearching}
|
||||
<div class="py-8 text-center text-sm text-slate-500">
|
||||
<div class="py-8 text-center text-sm text-theme-muted">
|
||||
No conversations found matching "{searchQuery}"
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-slate-800">
|
||||
<div class="divide-y divide-theme-secondary">
|
||||
{#each titleResults as result}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => navigateToConversation(result.id)}
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-slate-800"
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-theme-secondary"
|
||||
>
|
||||
<svg class="h-4 w-4 flex-shrink-0 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg class="h-4 w-4 flex-shrink-0 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -294,10 +294,10 @@
|
||||
/>
|
||||
</svg>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-slate-200">
|
||||
<p class="truncate text-sm font-medium text-theme-secondary">
|
||||
{result.title}
|
||||
</p>
|
||||
<p class="text-xs text-slate-500">
|
||||
<p class="text-xs text-theme-muted">
|
||||
{result.messageCount} messages · {result.model}
|
||||
</p>
|
||||
</div>
|
||||
@@ -314,16 +314,16 @@
|
||||
{/if}
|
||||
{:else}
|
||||
{#if messageResults.length === 0 && !isSearching}
|
||||
<div class="py-8 text-center text-sm text-slate-500">
|
||||
<div class="py-8 text-center text-sm text-theme-muted">
|
||||
No messages found matching "{searchQuery}"
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-slate-800">
|
||||
<div class="divide-y divide-theme-secondary">
|
||||
{#each messageResults as result}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => navigateToConversation(result.conversationId)}
|
||||
class="flex w-full flex-col gap-1 px-4 py-3 text-left transition-colors hover:bg-slate-800"
|
||||
class="flex w-full flex-col gap-1 px-4 py-3 text-left transition-colors hover:bg-theme-secondary"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
@@ -334,11 +334,11 @@
|
||||
>
|
||||
{result.role}
|
||||
</span>
|
||||
<span class="truncate text-xs text-slate-500">
|
||||
<span class="truncate text-xs text-theme-muted">
|
||||
{getConversationTitle(result.conversationId)}
|
||||
</span>
|
||||
</div>
|
||||
<p class="line-clamp-2 text-sm text-slate-300">
|
||||
<p class="line-clamp-2 text-sm text-theme-secondary">
|
||||
{getSnippet(result.content, result.matchIndex, searchQuery)}
|
||||
</p>
|
||||
</button>
|
||||
@@ -349,10 +349,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer hint -->
|
||||
<div class="border-t border-slate-700 px-4 py-2">
|
||||
<p class="text-center text-xs text-slate-500">
|
||||
<kbd class="rounded bg-slate-800 px-1.5 py-0.5 font-mono">Enter</kbd> to select ·
|
||||
<kbd class="rounded bg-slate-800 px-1.5 py-0.5 font-mono">Tab</kbd> to switch tabs
|
||||
<div class="border-t border-theme px-4 py-2">
|
||||
<p class="text-center text-xs text-theme-muted">
|
||||
<kbd class="rounded bg-theme-secondary px-1.5 py-0.5 font-mono">Enter</kbd> to select ·
|
||||
<kbd class="rounded bg-theme-secondary px-1.5 py-0.5 font-mono">Tab</kbd> to switch tabs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,18 +65,18 @@
|
||||
>
|
||||
<!-- Modal -->
|
||||
<div
|
||||
class="w-full max-w-lg rounded-xl bg-slate-800 shadow-2xl"
|
||||
class="w-full max-w-lg rounded-xl bg-theme-secondary shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="settings-title"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-slate-700 px-6 py-4">
|
||||
<h2 id="settings-title" class="text-lg font-semibold text-slate-100">Settings</h2>
|
||||
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
||||
<h2 id="settings-title" class="text-lg font-semibold text-theme-primary">Settings</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-700 hover:text-slate-200"
|
||||
class="rounded-lg p-1.5 text-theme-muted hover:bg-theme-tertiary hover:text-theme-secondary"
|
||||
aria-label="Close settings"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
@@ -89,17 +89,17 @@
|
||||
<div class="space-y-6 p-6">
|
||||
<!-- Appearance Section -->
|
||||
<section>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-slate-400">Appearance</h3>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-theme-muted">Appearance</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-slate-200">Dark Mode</p>
|
||||
<p class="text-xs text-slate-400">Toggle between light and dark theme</p>
|
||||
<p class="text-sm font-medium text-theme-secondary">Dark Mode</p>
|
||||
<p class="text-xs text-theme-muted">Toggle between light and dark theme</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => uiState.toggleDarkMode()}
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 focus:ring-offset-slate-800 {uiState.darkMode ? 'bg-emerald-600' : 'bg-slate-600'}"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 focus:ring-offset-theme {uiState.darkMode ? 'bg-emerald-600' : 'bg-theme-tertiary'}"
|
||||
role="switch"
|
||||
aria-checked={uiState.darkMode}
|
||||
>
|
||||
@@ -110,13 +110,13 @@
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-slate-200">Use System Theme</p>
|
||||
<p class="text-xs text-slate-400">Match your OS light/dark preference</p>
|
||||
<p class="text-sm font-medium text-theme-secondary">Use System Theme</p>
|
||||
<p class="text-xs text-theme-muted">Match your OS light/dark preference</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => uiState.useSystemTheme()}
|
||||
class="rounded-lg bg-slate-700 px-3 py-1.5 text-xs font-medium text-slate-300 transition-colors hover:bg-slate-600"
|
||||
class="rounded-lg bg-theme-tertiary px-3 py-1.5 text-xs font-medium text-theme-secondary transition-colors hover:bg-theme-tertiary"
|
||||
>
|
||||
Sync with System
|
||||
</button>
|
||||
@@ -126,55 +126,55 @@
|
||||
|
||||
<!-- Model Section -->
|
||||
<section>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-slate-400">Default Model</h3>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-theme-muted">Default Model</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<select
|
||||
bind:value={defaultModel}
|
||||
class="w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-slate-200 focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
||||
class="w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-secondary focus:border-emerald-500 focus:outline-none focus:ring-1 focus:ring-emerald-500"
|
||||
>
|
||||
{#each modelsState.chatModels as model}
|
||||
<option value={model.name}>{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<p class="mt-1 text-sm text-slate-400">Model used for new conversations</p>
|
||||
<p class="mt-1 text-sm text-theme-muted">Model used for new conversations</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Keyboard Shortcuts Section -->
|
||||
<section>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-slate-400">Keyboard Shortcuts</h3>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-theme-muted">Keyboard Shortcuts</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between text-slate-300">
|
||||
<div class="flex justify-between text-theme-secondary">
|
||||
<span>New Chat</span>
|
||||
<kbd class="rounded bg-slate-700 px-2 py-0.5 font-mono text-slate-400">{modifierKey}+N</kbd>
|
||||
<kbd class="rounded bg-theme-tertiary px-2 py-0.5 font-mono text-theme-muted">{modifierKey}+N</kbd>
|
||||
</div>
|
||||
<div class="flex justify-between text-slate-300">
|
||||
<div class="flex justify-between text-theme-secondary">
|
||||
<span>Search</span>
|
||||
<kbd class="rounded bg-slate-700 px-2 py-0.5 font-mono text-slate-400">{modifierKey}+K</kbd>
|
||||
<kbd class="rounded bg-theme-tertiary px-2 py-0.5 font-mono text-theme-muted">{modifierKey}+K</kbd>
|
||||
</div>
|
||||
<div class="flex justify-between text-slate-300">
|
||||
<div class="flex justify-between text-theme-secondary">
|
||||
<span>Toggle Sidebar</span>
|
||||
<kbd class="rounded bg-slate-700 px-2 py-0.5 font-mono text-slate-400">{modifierKey}+B</kbd>
|
||||
<kbd class="rounded bg-theme-tertiary px-2 py-0.5 font-mono text-theme-muted">{modifierKey}+B</kbd>
|
||||
</div>
|
||||
<div class="flex justify-between text-slate-300">
|
||||
<div class="flex justify-between text-theme-secondary">
|
||||
<span>Send Message</span>
|
||||
<kbd class="rounded bg-slate-700 px-2 py-0.5 font-mono text-slate-400">Enter</kbd>
|
||||
<kbd class="rounded bg-theme-tertiary px-2 py-0.5 font-mono text-theme-muted">Enter</kbd>
|
||||
</div>
|
||||
<div class="flex justify-between text-slate-300">
|
||||
<div class="flex justify-between text-theme-secondary">
|
||||
<span>New Line</span>
|
||||
<kbd class="rounded bg-slate-700 px-2 py-0.5 font-mono text-slate-400">Shift+Enter</kbd>
|
||||
<kbd class="rounded bg-theme-tertiary px-2 py-0.5 font-mono text-theme-muted">Shift+Enter</kbd>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- About Section -->
|
||||
<section>
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-slate-400">About</h3>
|
||||
<div class="rounded-lg bg-slate-700/50 p-4">
|
||||
<p class="font-medium text-slate-200">Ollama Web UI</p>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<h3 class="mb-3 text-sm font-medium uppercase tracking-wide text-theme-muted">About</h3>
|
||||
<div class="rounded-lg bg-theme-tertiary/50 p-4">
|
||||
<p class="font-medium text-theme-secondary">Ollama Web UI</p>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
A feature-rich web interface for Ollama with chat, tools, and memory management.
|
||||
</p>
|
||||
</div>
|
||||
@@ -182,11 +182,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-end gap-3 border-t border-slate-700 px-6 py-4">
|
||||
<div class="flex justify-end gap-3 border-t border-theme px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-slate-300 hover:bg-slate-700"
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-theme-secondary hover:bg-theme-tertiary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -66,16 +66,16 @@
|
||||
aria-labelledby="shortcuts-dialog-title"
|
||||
>
|
||||
<!-- Dialog -->
|
||||
<div class="mx-4 w-full max-w-md rounded-xl border border-slate-700 bg-slate-900 shadow-2xl">
|
||||
<div class="mx-4 w-full max-w-md rounded-xl border border-theme bg-theme-primary shadow-2xl">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-slate-700 px-6 py-4">
|
||||
<h2 id="shortcuts-dialog-title" class="text-lg font-semibold text-slate-100">
|
||||
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
||||
<h2 id="shortcuts-dialog-title" class="text-lg font-semibold text-theme-primary">
|
||||
Keyboard Shortcuts
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-800 hover:text-slate-200"
|
||||
class="rounded-lg p-1.5 text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-primary"
|
||||
aria-label="Close dialog"
|
||||
>
|
||||
<svg
|
||||
@@ -96,14 +96,14 @@
|
||||
{#each Object.entries(groupedShortcuts) as [group, items]}
|
||||
{#if items.length > 0}
|
||||
<div class="mb-4 last:mb-0">
|
||||
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-theme-muted">
|
||||
{group}
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{#each items as shortcut}
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-300">{shortcut.description}</span>
|
||||
<kbd class="rounded bg-slate-800 px-2 py-1 font-mono text-xs text-slate-400">
|
||||
<span class="text-sm text-theme-secondary">{shortcut.description}</span>
|
||||
<kbd class="rounded bg-theme-secondary px-2 py-1 font-mono text-xs text-theme-muted">
|
||||
{formatShortcut(shortcut.key, shortcut.modifiers)}
|
||||
</kbd>
|
||||
</div>
|
||||
@@ -115,9 +115,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="border-t border-slate-700 px-6 py-3">
|
||||
<p class="text-center text-xs text-slate-500">
|
||||
Press <kbd class="rounded bg-slate-800 px-1.5 py-0.5 font-mono">Shift+?</kbd> to toggle this panel
|
||||
<div class="border-t border-theme px-6 py-3">
|
||||
<p class="text-center text-xs text-theme-muted">
|
||||
Press <kbd class="rounded bg-theme-secondary px-1.5 py-0.5 font-mono">Shift+?</kbd> to toggle this panel
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<div class="space-y-2 {className}">
|
||||
{#each lineWidths as lineWidth, i}
|
||||
<div
|
||||
class="animate-pulse bg-slate-700/50 {variantClasses}"
|
||||
class="animate-pulse bg-theme-tertiary/50 {variantClasses}"
|
||||
style="width: {lineWidth}; height: {height};"
|
||||
role="status"
|
||||
aria-label="Loading..."
|
||||
@@ -63,7 +63,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="animate-pulse bg-slate-700/50 {variantClasses} {className}"
|
||||
class="animate-pulse bg-theme-tertiary/50 {variantClasses} {className}"
|
||||
style="width: {width}; height: {height};"
|
||||
role="status"
|
||||
aria-label="Loading..."
|
||||
|
||||
@@ -171,19 +171,19 @@
|
||||
onkeydown={(e) => e.key === 'Escape' && onClose()}
|
||||
>
|
||||
<div
|
||||
class="w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-xl bg-slate-800 shadow-2xl"
|
||||
class="w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-xl bg-theme-secondary shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="sticky top-0 flex items-center justify-between border-b border-slate-700 bg-slate-800 px-6 py-4">
|
||||
<h2 class="text-lg font-semibold text-white">
|
||||
<div class="sticky top-0 flex items-center justify-between border-b border-theme bg-theme-secondary px-6 py-4">
|
||||
<h2 class="text-lg font-semibold text-theme-primary">
|
||||
{editingTool ? 'Edit Tool' : 'Create Custom Tool'}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-700 hover:text-white"
|
||||
class="rounded-lg p-1.5 text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
@@ -195,13 +195,13 @@
|
||||
<div class="space-y-6 p-6">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="tool-name" class="block text-sm font-medium text-slate-300">Tool Name</label>
|
||||
<label for="tool-name" class="block text-sm font-medium text-theme-secondary">Tool Name</label>
|
||||
<input
|
||||
id="tool-name"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder="my_custom_tool"
|
||||
class="mt-1 w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="mt-1 w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
{#if errors.name}
|
||||
<p class="mt-1 text-sm text-red-400">{errors.name}</p>
|
||||
@@ -210,13 +210,13 @@
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<label for="tool-description" class="block text-sm font-medium text-slate-300">Description</label>
|
||||
<label for="tool-description" class="block text-sm font-medium text-theme-secondary">Description</label>
|
||||
<textarea
|
||||
id="tool-description"
|
||||
bind:value={description}
|
||||
rows="2"
|
||||
placeholder="What does this tool do? The AI uses this to decide when to call it."
|
||||
class="mt-1 w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="mt-1 w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
></textarea>
|
||||
{#if errors.description}
|
||||
<p class="mt-1 text-sm text-red-400">{errors.description}</p>
|
||||
@@ -226,7 +226,7 @@
|
||||
<!-- Parameters -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-sm font-medium text-slate-300">Parameters</label>
|
||||
<label class="block text-sm font-medium text-theme-secondary">Parameters</label>
|
||||
<button
|
||||
type="button"
|
||||
onclick={addParameter}
|
||||
@@ -239,17 +239,17 @@
|
||||
{#if parameters.length > 0}
|
||||
<div class="mt-2 space-y-3">
|
||||
{#each parameters as param, index (index)}
|
||||
<div class="flex gap-2 items-start rounded-lg border border-slate-600 bg-slate-700/50 p-3">
|
||||
<div class="flex gap-2 items-start rounded-lg border border-theme-subtle bg-theme-tertiary/50 p-3">
|
||||
<div class="flex-1 grid grid-cols-2 gap-2">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={param.name}
|
||||
placeholder="param_name"
|
||||
class="rounded border border-slate-600 bg-slate-700 px-2 py-1 text-sm text-white"
|
||||
class="rounded border border-theme-subtle bg-theme-tertiary px-2 py-1 text-sm text-theme-primary"
|
||||
/>
|
||||
<select
|
||||
bind:value={param.type}
|
||||
class="rounded border border-slate-600 bg-slate-700 px-2 py-1 text-sm text-white"
|
||||
class="rounded border border-theme-subtle bg-theme-tertiary px-2 py-1 text-sm text-theme-primary"
|
||||
>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
@@ -260,9 +260,9 @@
|
||||
type="text"
|
||||
bind:value={param.description}
|
||||
placeholder="Description"
|
||||
class="col-span-2 rounded border border-slate-600 bg-slate-700 px-2 py-1 text-sm text-white"
|
||||
class="col-span-2 rounded border border-theme-subtle bg-theme-tertiary px-2 py-1 text-sm text-theme-primary"
|
||||
/>
|
||||
<label class="col-span-2 flex items-center gap-2 text-sm text-slate-300">
|
||||
<label class="col-span-2 flex items-center gap-2 text-sm text-theme-secondary">
|
||||
<input type="checkbox" bind:checked={param.required} class="rounded" />
|
||||
Required
|
||||
</label>
|
||||
@@ -270,7 +270,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => removeParameter(index)}
|
||||
class="text-slate-400 hover:text-red-400"
|
||||
class="text-theme-muted hover:text-red-400"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
@@ -283,15 +283,15 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="mt-2 text-sm text-slate-500">No parameters defined. Click "Add Parameter" to define inputs.</p>
|
||||
<p class="mt-2 text-sm text-theme-muted">No parameters defined. Click "Add Parameter" to define inputs.</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Implementation Type -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300">Implementation</label>
|
||||
<label class="block text-sm font-medium text-theme-secondary">Implementation</label>
|
||||
<div class="mt-2 flex gap-4">
|
||||
<label class="flex items-center gap-2 text-slate-300">
|
||||
<label class="flex items-center gap-2 text-theme-secondary">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={implementation}
|
||||
@@ -300,7 +300,7 @@
|
||||
/>
|
||||
JavaScript
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-slate-300">
|
||||
<label class="flex items-center gap-2 text-theme-secondary">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={implementation}
|
||||
@@ -315,15 +315,15 @@
|
||||
<!-- JavaScript Code -->
|
||||
{#if implementation === 'javascript'}
|
||||
<div>
|
||||
<label for="tool-code" class="block text-sm font-medium text-slate-300">JavaScript Code</label>
|
||||
<p class="mt-1 text-xs text-slate-500">
|
||||
Arguments are passed as an <code class="bg-slate-700 px-1 rounded">args</code> object. Return the result.
|
||||
<label for="tool-code" class="block text-sm font-medium text-theme-secondary">JavaScript Code</label>
|
||||
<p class="mt-1 text-xs text-theme-muted">
|
||||
Arguments are passed as an <code class="bg-theme-tertiary px-1 rounded">args</code> object. Return the result.
|
||||
</p>
|
||||
<textarea
|
||||
id="tool-code"
|
||||
bind:value={code}
|
||||
rows="8"
|
||||
class="mt-2 w-full rounded-lg border border-slate-600 bg-slate-900 px-3 py-2 font-mono text-sm text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="mt-2 w-full rounded-lg border border-theme-subtle bg-theme-primary px-3 py-2 font-mono text-sm text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
></textarea>
|
||||
{#if errors.code}
|
||||
<p class="mt-1 text-sm text-red-400">{errors.code}</p>
|
||||
@@ -335,26 +335,26 @@
|
||||
{#if implementation === 'http'}
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="tool-endpoint" class="block text-sm font-medium text-slate-300">Endpoint URL</label>
|
||||
<label for="tool-endpoint" class="block text-sm font-medium text-theme-secondary">Endpoint URL</label>
|
||||
<input
|
||||
id="tool-endpoint"
|
||||
type="url"
|
||||
bind:value={endpoint}
|
||||
placeholder="https://api.example.com/tool"
|
||||
class="mt-1 w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="mt-1 w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
{#if errors.endpoint}
|
||||
<p class="mt-1 text-sm text-red-400">{errors.endpoint}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300">HTTP Method</label>
|
||||
<label class="block text-sm font-medium text-theme-secondary">HTTP Method</label>
|
||||
<div class="mt-2 flex gap-4">
|
||||
<label class="flex items-center gap-2 text-slate-300">
|
||||
<label class="flex items-center gap-2 text-theme-secondary">
|
||||
<input type="radio" bind:group={httpMethod} value="GET" />
|
||||
GET
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-slate-300">
|
||||
<label class="flex items-center gap-2 text-theme-secondary">
|
||||
<input type="radio" bind:group={httpMethod} value="POST" />
|
||||
POST
|
||||
</label>
|
||||
@@ -365,11 +365,11 @@
|
||||
|
||||
<!-- Enabled Toggle -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-slate-300">Enable tool</span>
|
||||
<span class="text-sm font-medium text-theme-secondary">Enable tool</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => enabled = !enabled}
|
||||
class="relative inline-flex h-6 w-11 cursor-pointer rounded-full transition-colors {enabled ? 'bg-blue-600' : 'bg-slate-600'}"
|
||||
class="relative inline-flex h-6 w-11 cursor-pointer rounded-full transition-colors {enabled ? 'bg-blue-600' : 'bg-theme-tertiary'}"
|
||||
role="switch"
|
||||
aria-checked={enabled}
|
||||
>
|
||||
@@ -381,11 +381,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="sticky bottom-0 flex justify-end gap-3 border-t border-slate-700 bg-slate-800 px-6 py-4">
|
||||
<div class="sticky bottom-0 flex justify-end gap-3 border-t border-theme bg-theme-secondary px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={onClose}
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-slate-300 hover:bg-slate-700"
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-theme-secondary hover:bg-theme-tertiary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -253,7 +253,7 @@ export function getContextUsageColor(percentage: number): string {
|
||||
if (percentage >= WARNING_THRESHOLD * 100) {
|
||||
return 'text-yellow-500';
|
||||
}
|
||||
return 'text-slate-400';
|
||||
return 'text-theme-muted';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -128,39 +128,39 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full overflow-y-auto bg-slate-900 p-6">
|
||||
<div class="h-full overflow-y-auto bg-theme-primary p-6">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-white">Knowledge Base</h1>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<h1 class="text-2xl font-bold text-theme-primary">Knowledge Base</h1>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Upload documents to enhance AI responses with your own knowledge
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="mb-6 grid grid-cols-3 gap-4">
|
||||
<div class="rounded-lg border border-slate-700 bg-slate-800 p-4">
|
||||
<p class="text-sm text-slate-400">Documents</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-white">{stats.documentCount}</p>
|
||||
<div class="rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<p class="text-sm text-theme-muted">Documents</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-theme-primary">{stats.documentCount}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-slate-700 bg-slate-800 p-4">
|
||||
<p class="text-sm text-slate-400">Chunks</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-white">{stats.chunkCount}</p>
|
||||
<div class="rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<p class="text-sm text-theme-muted">Chunks</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-theme-primary">{stats.chunkCount}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-slate-700 bg-slate-800 p-4">
|
||||
<p class="text-sm text-slate-400">Total Tokens</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-white">{formatTokenCount(stats.totalTokens)}</p>
|
||||
<div class="rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<p class="text-sm text-theme-muted">Total Tokens</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-theme-primary">{formatTokenCount(stats.totalTokens)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Area -->
|
||||
<div class="mb-8">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-white">Upload Documents</h2>
|
||||
<h2 class="text-lg font-semibold text-theme-primary">Upload Documents</h2>
|
||||
<select
|
||||
bind:value={selectedModel}
|
||||
class="rounded-md border border-slate-600 bg-slate-700 px-3 py-1.5 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="rounded-md border border-theme-subtle bg-theme-tertiary px-3 py-1.5 text-sm text-theme-primary focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
{#each EMBEDDING_MODELS as model}
|
||||
<option value={model}>{model}</option>
|
||||
@@ -173,7 +173,7 @@
|
||||
type="button"
|
||||
class="w-full rounded-lg border-2 border-dashed p-8 text-center transition-colors {dragOver
|
||||
? 'border-blue-500 bg-blue-900/20'
|
||||
: 'border-slate-600 hover:border-slate-500'}"
|
||||
: 'border-theme-subtle hover:border-theme'}"
|
||||
ondragover={(e) => {
|
||||
e.preventDefault();
|
||||
dragOver = true;
|
||||
@@ -189,18 +189,18 @@
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="mt-3 text-sm text-slate-400">
|
||||
<p class="mt-3 text-sm text-theme-muted">
|
||||
Processing... ({uploadProgress.current}/{uploadProgress.total} chunks)
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
|
||||
</svg>
|
||||
<p class="mt-3 text-sm text-slate-400">
|
||||
<p class="mt-3 text-sm text-theme-muted">
|
||||
Drag and drop files here, or click to browse
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-slate-500">
|
||||
<p class="mt-1 text-xs text-theme-muted">
|
||||
Supports .txt, .md, .json, and other text files
|
||||
</p>
|
||||
{/if}
|
||||
@@ -218,36 +218,36 @@
|
||||
|
||||
<!-- Documents List -->
|
||||
<div>
|
||||
<h2 class="mb-4 text-lg font-semibold text-white">Documents</h2>
|
||||
<h2 class="mb-4 text-lg font-semibold text-theme-primary">Documents</h2>
|
||||
|
||||
{#if isLoading}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<svg class="h-8 w-8 animate-spin text-slate-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg class="h-8 w-8 animate-spin text-theme-muted" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
{:else if documents.length === 0}
|
||||
<div class="rounded-lg border border-dashed border-slate-700 bg-slate-800/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<div class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-slate-400">No documents yet</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">No documents yet</h3>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Upload documents to build your knowledge base
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each documents as doc (doc.id)}
|
||||
<div class="flex items-center justify-between rounded-lg border border-slate-700 bg-slate-800 p-4">
|
||||
<div class="flex items-center justify-between rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="font-medium text-white">{doc.name}</h3>
|
||||
<p class="text-xs text-slate-400">
|
||||
<h3 class="font-medium text-theme-primary">{doc.name}</h3>
|
||||
<p class="text-xs text-theme-muted">
|
||||
{formatSize(doc.size)} · {doc.chunkCount} chunks · Added {formatDate(doc.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -256,7 +256,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleDelete(doc)}
|
||||
class="rounded p-2 text-slate-400 transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
class="rounded p-2 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
aria-label="Delete document"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
@@ -270,21 +270,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Info Section -->
|
||||
<section class="mt-8 rounded-lg border border-slate-700 bg-slate-800/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-slate-300">
|
||||
<section class="mt-8 rounded-lg border border-theme bg-theme-secondary/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
How RAG Works
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
Documents are split into chunks and converted to embeddings (numerical representations).
|
||||
When you ask a question, relevant chunks are found by similarity search and included
|
||||
in the AI's context to provide more accurate, grounded responses.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<strong class="text-slate-300">Note:</strong> Requires an embedding model to be installed
|
||||
in Ollama (e.g., <code class="rounded bg-slate-700 px-1">ollama pull nomic-embed-text</code>).
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
<strong class="text-theme-secondary">Note:</strong> Requires an embedding model to be installed
|
||||
in Ollama (e.g., <code class="rounded bg-theme-tertiary px-1">ollama pull nomic-embed-text</code>).
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -223,15 +223,15 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-full overflow-hidden bg-slate-900">
|
||||
<div class="flex h-full overflow-hidden bg-theme-primary">
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-white">Models</h1>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<h1 class="text-2xl font-bold text-theme-primary">Models</h1>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Manage local models and browse ollama.com
|
||||
</p>
|
||||
</div>
|
||||
@@ -239,7 +239,7 @@
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-3">
|
||||
{#if activeTab === 'browse' && modelRegistry.syncStatus}
|
||||
<div class="text-right text-xs text-slate-500">
|
||||
<div class="text-right text-xs text-theme-muted">
|
||||
<div>{modelRegistry.syncStatus.modelCount} models cached</div>
|
||||
<div>Last sync: {formatDate(modelRegistry.syncStatus.lastSync ?? undefined)}</div>
|
||||
</div>
|
||||
@@ -249,7 +249,7 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.sync()}
|
||||
disabled={modelRegistry.syncing}
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#if modelRegistry.syncing}
|
||||
<svg class="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
@@ -269,7 +269,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => modelOperationsState.openPullDialog()}
|
||||
class="flex items-center gap-2 rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-sky-500"
|
||||
class="flex items-center gap-2 rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-sky-500"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
@@ -299,7 +299,7 @@
|
||||
type="button"
|
||||
onclick={() => localModelsState.refresh()}
|
||||
disabled={localModelsState.loading}
|
||||
class="flex items-center gap-2 rounded-lg border border-slate-700 bg-slate-800 px-4 py-2 text-sm font-medium text-slate-300 transition-colors hover:bg-slate-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex items-center gap-2 rounded-lg border border-theme bg-theme-secondary px-4 py-2 text-sm font-medium text-theme-secondary transition-colors hover:bg-theme-tertiary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#if localModelsState.loading}
|
||||
<svg class="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
@@ -318,21 +318,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="mb-6 flex border-b border-slate-700">
|
||||
<div class="mb-6 flex border-b border-theme">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => activeTab = 'local'}
|
||||
class="flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors {activeTab === 'local'
|
||||
? 'border-blue-500 text-blue-400'
|
||||
: 'border-transparent text-slate-400 hover:text-white'}"
|
||||
: 'border-transparent text-theme-muted hover:text-theme-primary'}"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
||||
</svg>
|
||||
Local Models
|
||||
<span class="rounded-full bg-slate-700 px-2 py-0.5 text-xs">{localModelsState.total}</span>
|
||||
<span class="rounded-full bg-theme-tertiary px-2 py-0.5 text-xs">{localModelsState.total}</span>
|
||||
{#if localModelsState.updatesAvailable > 0}
|
||||
<span class="rounded-full bg-amber-600 px-2 py-0.5 text-xs text-white" title="{localModelsState.updatesAvailable} update{localModelsState.updatesAvailable !== 1 ? 's' : ''} available">
|
||||
<span class="rounded-full bg-amber-600 px-2 py-0.5 text-xs text-theme-primary" title="{localModelsState.updatesAvailable} update{localModelsState.updatesAvailable !== 1 ? 's' : ''} available">
|
||||
{localModelsState.updatesAvailable}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -342,7 +342,7 @@
|
||||
onclick={() => activeTab = 'browse'}
|
||||
class="flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors {activeTab === 'browse'
|
||||
? 'border-blue-500 text-blue-400'
|
||||
: 'border-transparent text-slate-400 hover:text-white'}"
|
||||
: 'border-transparent text-theme-muted hover:text-theme-primary'}"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
||||
@@ -376,7 +376,7 @@
|
||||
<div class="relative flex-1 min-w-[200px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-500"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -389,7 +389,7 @@
|
||||
value={localSearchInput}
|
||||
oninput={handleLocalSearchInput}
|
||||
placeholder="Search local models..."
|
||||
class="w-full rounded-lg border border-slate-700 bg-slate-800 py-2 pl-10 pr-4 text-white placeholder-slate-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full rounded-lg border border-theme bg-theme-secondary py-2 pl-10 pr-4 text-theme-primary placeholder-theme-placeholder focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -398,7 +398,7 @@
|
||||
<select
|
||||
value={localModelsState.familyFilter}
|
||||
onchange={(e) => localModelsState.filterByFamily((e.target as HTMLSelectElement).value)}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-primary focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">All Families</option>
|
||||
{#each localModelsState.families as family}
|
||||
@@ -411,7 +411,7 @@
|
||||
<select
|
||||
value={localModelsState.sortBy}
|
||||
onchange={(e) => localModelsState.setSort((e.target as HTMLSelectElement).value as import('$lib/api/model-registry').LocalModelSortOption)}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-primary focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="name_asc">Name A-Z</option>
|
||||
<option value="name_desc">Name Z-A</option>
|
||||
@@ -426,7 +426,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { localModelsState.clearFilters(); localSearchInput = ''; }}
|
||||
class="text-sm text-slate-500 hover:text-white"
|
||||
class="text-sm text-theme-muted hover:text-theme-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
@@ -436,27 +436,27 @@
|
||||
{#if localModelsState.loading}
|
||||
<div class="space-y-3">
|
||||
{#each Array(3) as _}
|
||||
<div class="animate-pulse rounded-lg border border-slate-700 bg-slate-800 p-4">
|
||||
<div class="animate-pulse rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="h-5 w-48 rounded bg-slate-700"></div>
|
||||
<div class="h-5 w-20 rounded bg-slate-700"></div>
|
||||
<div class="h-5 w-48 rounded bg-theme-tertiary"></div>
|
||||
<div class="h-5 w-20 rounded bg-theme-tertiary"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if localModelsState.models.length === 0}
|
||||
<div class="rounded-lg border border-dashed border-slate-700 bg-slate-800/50 p-12 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<div class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-12 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-slate-400">
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">
|
||||
{#if localModelsState.searchQuery || localModelsState.familyFilter}
|
||||
No models match your filters
|
||||
{:else}
|
||||
No local models
|
||||
{/if}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
{#if localModelsState.searchQuery || localModelsState.familyFilter}
|
||||
Try adjusting your search or filters
|
||||
{:else}
|
||||
@@ -467,7 +467,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => activeTab = 'browse'}
|
||||
class="mt-4 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
||||
class="mt-4 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary hover:bg-blue-700"
|
||||
>
|
||||
Browse Models
|
||||
</button>
|
||||
@@ -476,21 +476,21 @@
|
||||
{:else}
|
||||
<div class="space-y-2">
|
||||
{#each localModelsState.models as model (model.name)}
|
||||
<div class="group rounded-lg border border-slate-700 bg-slate-800 p-4 transition-colors hover:border-slate-600">
|
||||
<div class="group rounded-lg border border-theme bg-theme-secondary p-4 transition-colors hover:border-theme-subtle">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="font-medium text-white">{model.name}</h3>
|
||||
<h3 class="font-medium text-theme-primary">{model.name}</h3>
|
||||
{#if model.name === modelsState.selectedId}
|
||||
<span class="rounded bg-blue-900/50 px-2 py-0.5 text-xs text-blue-300">Selected</span>
|
||||
{/if}
|
||||
{#if localModelsState.hasUpdate(model.name)}
|
||||
<span class="rounded bg-amber-600 px-2 py-0.5 text-xs font-medium text-white" title="Update available - re-pull to get the latest version">
|
||||
<span class="rounded bg-amber-600 px-2 py-0.5 text-xs font-medium text-theme-primary" title="Update available - re-pull to get the latest version">
|
||||
Update
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-1 flex items-center gap-4 text-xs text-slate-500">
|
||||
<div class="mt-1 flex items-center gap-4 text-xs text-theme-muted">
|
||||
<span>{formatBytes(model.size)}</span>
|
||||
<span>Family: {model.family}</span>
|
||||
<span>Parameters: {model.parameterSize}</span>
|
||||
@@ -499,12 +499,12 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if deleteConfirm === model.name}
|
||||
<span class="text-sm text-slate-400">Delete?</span>
|
||||
<span class="text-sm text-theme-muted">Delete?</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => deleteModel(model.name)}
|
||||
disabled={deleting}
|
||||
class="rounded bg-red-600 px-3 py-1 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-50"
|
||||
class="rounded bg-red-600 px-3 py-1 text-sm font-medium text-theme-primary hover:bg-red-700 disabled:opacity-50"
|
||||
>
|
||||
{deleting ? 'Deleting...' : 'Yes'}
|
||||
</button>
|
||||
@@ -512,7 +512,7 @@
|
||||
type="button"
|
||||
onclick={() => deleteConfirm = null}
|
||||
disabled={deleting}
|
||||
class="rounded bg-slate-700 px-3 py-1 text-sm font-medium text-slate-300 hover:bg-slate-600 disabled:opacity-50"
|
||||
class="rounded bg-theme-tertiary px-3 py-1 text-sm font-medium text-theme-secondary hover:bg-theme-secondary disabled:opacity-50"
|
||||
>
|
||||
No
|
||||
</button>
|
||||
@@ -520,7 +520,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => deleteConfirm = model.name}
|
||||
class="rounded p-2 text-slate-500 opacity-0 transition-opacity hover:bg-slate-700 hover:text-red-400 group-hover:opacity-100"
|
||||
class="rounded p-2 text-theme-muted opacity-0 transition-opacity hover:bg-theme-tertiary hover:text-red-400 group-hover:opacity-100"
|
||||
title="Delete model"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -541,19 +541,19 @@
|
||||
type="button"
|
||||
onclick={() => localModelsState.prevPage()}
|
||||
disabled={!localModelsState.hasPrevPage}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-white hover:bg-slate-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-primary hover:bg-theme-tertiary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
aria-label="Previous page"
|
||||
>
|
||||
← Prev
|
||||
</button>
|
||||
<span class="px-3 text-sm text-slate-400">
|
||||
<span class="px-3 text-sm text-theme-muted">
|
||||
Page {localModelsState.currentPage + 1} of {localModelsState.totalPages}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => localModelsState.nextPage()}
|
||||
disabled={!localModelsState.hasNextPage}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-white hover:bg-slate-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-primary hover:bg-theme-tertiary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
aria-label="Next page"
|
||||
>
|
||||
Next →
|
||||
@@ -569,7 +569,7 @@
|
||||
<div class="relative flex-1 min-w-[200px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-500"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -582,18 +582,18 @@
|
||||
value={searchInput}
|
||||
oninput={handleSearchInput}
|
||||
placeholder="Search models..."
|
||||
class="w-full rounded-lg border border-slate-700 bg-slate-800 py-2 pl-10 pr-4 text-white placeholder-slate-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full rounded-lg border border-theme bg-theme-secondary py-2 pl-10 pr-4 text-theme-primary placeholder-theme-placeholder focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Type Filter -->
|
||||
<div class="flex rounded-lg border border-slate-700 bg-slate-800 p-1">
|
||||
<div class="flex rounded-lg border border-theme bg-theme-secondary p-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleTypeFilter('')}
|
||||
class="rounded-md px-3 py-1.5 text-sm font-medium transition-colors {modelRegistry.modelType === ''
|
||||
? 'bg-slate-700 text-white'
|
||||
: 'text-slate-400 hover:text-white'}"
|
||||
? 'bg-theme-tertiary text-theme-primary'
|
||||
: 'text-theme-muted hover:text-theme-primary'}"
|
||||
>
|
||||
All
|
||||
</button>
|
||||
@@ -601,8 +601,8 @@
|
||||
type="button"
|
||||
onclick={() => handleTypeFilter('official')}
|
||||
class="rounded-md px-3 py-1.5 text-sm font-medium transition-colors {modelRegistry.modelType === 'official'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'text-slate-400 hover:text-white'}"
|
||||
? 'bg-blue-600 text-theme-primary'
|
||||
: 'text-theme-muted hover:text-theme-primary'}"
|
||||
>
|
||||
Official
|
||||
</button>
|
||||
@@ -610,8 +610,8 @@
|
||||
type="button"
|
||||
onclick={() => handleTypeFilter('community')}
|
||||
class="rounded-md px-3 py-1.5 text-sm font-medium transition-colors {modelRegistry.modelType === 'community'
|
||||
? 'bg-slate-600 text-white'
|
||||
: 'text-slate-400 hover:text-white'}"
|
||||
? 'bg-theme-tertiary text-theme-primary'
|
||||
: 'text-theme-muted hover:text-theme-primary'}"
|
||||
>
|
||||
Community
|
||||
</button>
|
||||
@@ -619,12 +619,12 @@
|
||||
|
||||
<!-- Sort Dropdown -->
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="sort-select" class="text-sm text-slate-500">Sort:</label>
|
||||
<label for="sort-select" class="text-sm text-theme-muted">Sort:</label>
|
||||
<select
|
||||
id="sort-select"
|
||||
value={modelRegistry.sortBy}
|
||||
onchange={(e) => modelRegistry.setSort((e.target as HTMLSelectElement).value as import('$lib/api/model-registry').ModelSortOption)}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-1.5 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-1.5 text-sm text-theme-primary focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="pulls_desc">Most Popular</option>
|
||||
<option value="pulls_asc">Least Popular</option>
|
||||
@@ -635,20 +635,20 @@
|
||||
</div>
|
||||
|
||||
<!-- Results Count -->
|
||||
<div class="text-sm text-slate-500">
|
||||
<div class="text-sm text-theme-muted">
|
||||
{modelRegistry.total} model{modelRegistry.total !== 1 ? 's' : ''} found
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Capability Filters (matches ollama.com capabilities) -->
|
||||
<div class="mb-6 flex flex-wrap items-center gap-2">
|
||||
<span class="text-sm text-slate-500">Capabilities:</span>
|
||||
<span class="text-sm text-theme-muted">Capabilities:</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => modelRegistry.toggleCapability('vision')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm transition-colors {modelRegistry.hasCapability('vision')
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
? 'bg-purple-600 text-theme-primary'
|
||||
: 'bg-theme-secondary text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
>
|
||||
<span>👁</span>
|
||||
<span>Vision</span>
|
||||
@@ -657,8 +657,8 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.toggleCapability('tools')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm transition-colors {modelRegistry.hasCapability('tools')
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
? 'bg-blue-600 text-theme-primary'
|
||||
: 'bg-theme-secondary text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
>
|
||||
<span>🔧</span>
|
||||
<span>Tools</span>
|
||||
@@ -667,8 +667,8 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.toggleCapability('thinking')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm transition-colors {modelRegistry.hasCapability('thinking')
|
||||
? 'bg-pink-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
? 'bg-pink-600 text-theme-primary'
|
||||
: 'bg-theme-secondary text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
>
|
||||
<span>🧠</span>
|
||||
<span>Thinking</span>
|
||||
@@ -677,8 +677,8 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.toggleCapability('embedding')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm transition-colors {modelRegistry.hasCapability('embedding')
|
||||
? 'bg-amber-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
? 'bg-amber-600 text-theme-primary'
|
||||
: 'bg-theme-secondary text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
>
|
||||
<span>📊</span>
|
||||
<span>Embedding</span>
|
||||
@@ -687,8 +687,8 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.toggleCapability('cloud')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm transition-colors {modelRegistry.hasCapability('cloud')
|
||||
? 'bg-cyan-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
? 'bg-cyan-600 text-theme-primary'
|
||||
: 'bg-theme-secondary text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
>
|
||||
<span>☁️</span>
|
||||
<span>Cloud</span>
|
||||
@@ -698,7 +698,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { modelRegistry.clearFilters(); searchInput = ''; }}
|
||||
class="ml-2 text-sm text-slate-500 hover:text-white"
|
||||
class="ml-2 text-sm text-theme-muted hover:text-theme-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
@@ -721,28 +721,28 @@
|
||||
{#if modelRegistry.loading}
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each Array(6) as _}
|
||||
<div class="animate-pulse rounded-lg border border-slate-700 bg-slate-800 p-4">
|
||||
<div class="animate-pulse rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="h-5 w-32 rounded bg-slate-700"></div>
|
||||
<div class="h-5 w-16 rounded bg-slate-700"></div>
|
||||
<div class="h-5 w-32 rounded bg-theme-tertiary"></div>
|
||||
<div class="h-5 w-16 rounded bg-theme-tertiary"></div>
|
||||
</div>
|
||||
<div class="mt-3 h-4 w-full rounded bg-slate-700"></div>
|
||||
<div class="mt-2 h-4 w-2/3 rounded bg-slate-700"></div>
|
||||
<div class="mt-3 h-4 w-full rounded bg-theme-tertiary"></div>
|
||||
<div class="mt-2 h-4 w-2/3 rounded bg-theme-tertiary"></div>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<div class="h-6 w-16 rounded bg-slate-700"></div>
|
||||
<div class="h-6 w-16 rounded bg-slate-700"></div>
|
||||
<div class="h-6 w-16 rounded bg-theme-tertiary"></div>
|
||||
<div class="h-6 w-16 rounded bg-theme-tertiary"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if modelRegistry.models.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="rounded-lg border border-dashed border-slate-700 bg-slate-800/50 p-12 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<div class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-12 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611l-.628.105a9.002 9.002 0 01-9.014 0l-.628-.105c-1.717-.293-2.3-2.379-1.067-3.61L5 14.5" />
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-slate-400">No models found</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">No models found</h3>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
{#if modelRegistry.searchQuery || modelRegistry.modelType}
|
||||
Try adjusting your search or filters
|
||||
{:else}
|
||||
@@ -765,14 +765,14 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.prevPage()}
|
||||
disabled={!modelRegistry.hasPrevPage}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-slate-400 transition-colors hover:bg-slate-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<span class="text-sm text-slate-400">
|
||||
<span class="text-sm text-theme-muted">
|
||||
Page {modelRegistry.currentPage + 1} of {modelRegistry.totalPages}
|
||||
</span>
|
||||
|
||||
@@ -780,7 +780,7 @@
|
||||
type="button"
|
||||
onclick={() => modelRegistry.nextPage()}
|
||||
disabled={!modelRegistry.hasNextPage}
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-slate-400 transition-colors hover:bg-slate-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
||||
@@ -795,14 +795,14 @@
|
||||
|
||||
<!-- Model Details Sidebar -->
|
||||
{#if selectedModel}
|
||||
<div class="w-96 flex-shrink-0 overflow-y-auto border-l border-slate-700 bg-slate-850 p-6">
|
||||
<div class="w-96 flex-shrink-0 overflow-y-auto border-l border-theme bg-theme-secondary p-6">
|
||||
<!-- Close Button -->
|
||||
<div class="mb-4 flex items-start justify-between">
|
||||
<h2 class="text-lg font-semibold text-white">{selectedModel.name}</h2>
|
||||
<h2 class="text-lg font-semibold text-theme-primary">{selectedModel.name}</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={closeDetails}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -812,7 +812,7 @@
|
||||
|
||||
<!-- Type Badge -->
|
||||
<div class="mb-4">
|
||||
<span class="rounded px-2 py-1 text-xs {selectedModel.modelType === 'official' ? 'bg-blue-900/50 text-blue-300' : 'bg-slate-700 text-slate-400'}">
|
||||
<span class="rounded px-2 py-1 text-xs {selectedModel.modelType === 'official' ? 'bg-blue-900/50 text-blue-300' : 'bg-theme-tertiary text-theme-muted'}">
|
||||
{selectedModel.modelType}
|
||||
</span>
|
||||
</div>
|
||||
@@ -820,18 +820,18 @@
|
||||
<!-- Description -->
|
||||
{#if selectedModel.description}
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-2 text-sm font-medium text-slate-300">Description</h3>
|
||||
<p class="text-sm text-slate-400">{selectedModel.description}</p>
|
||||
<h3 class="mb-2 text-sm font-medium text-theme-secondary">Description</h3>
|
||||
<p class="text-sm text-theme-muted">{selectedModel.description}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Capabilities -->
|
||||
{#if selectedModel.capabilities.length > 0}
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-2 text-sm font-medium text-slate-300">Capabilities</h3>
|
||||
<h3 class="mb-2 text-sm font-medium text-theme-secondary">Capabilities</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each selectedModel.capabilities as cap}
|
||||
<span class="rounded bg-slate-700 px-2 py-1 text-xs text-slate-300">{cap}</span>
|
||||
<span class="rounded bg-theme-tertiary px-2 py-1 text-xs text-theme-secondary">{cap}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -839,60 +839,60 @@
|
||||
|
||||
<!-- Technical Details -->
|
||||
<div class="mb-6 space-y-3">
|
||||
<h3 class="text-sm font-medium text-slate-300">Details</h3>
|
||||
<h3 class="text-sm font-medium text-theme-secondary">Details</h3>
|
||||
|
||||
{#if selectedModel.architecture}
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">Architecture</span>
|
||||
<span class="text-slate-300">{selectedModel.architecture}</span>
|
||||
<span class="text-theme-muted">Architecture</span>
|
||||
<span class="text-theme-secondary">{selectedModel.architecture}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if selectedModel.parameterSize}
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">Parameters</span>
|
||||
<span class="text-slate-300">{selectedModel.parameterSize}</span>
|
||||
<span class="text-theme-muted">Parameters</span>
|
||||
<span class="text-theme-secondary">{selectedModel.parameterSize}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if selectedModel.contextLength}
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">Context Length</span>
|
||||
<span class="text-slate-300">{selectedModel.contextLength.toLocaleString()}</span>
|
||||
<span class="text-theme-muted">Context Length</span>
|
||||
<span class="text-theme-secondary">{selectedModel.contextLength.toLocaleString()}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if selectedModel.embeddingLength}
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">Embedding Dim</span>
|
||||
<span class="text-slate-300">{selectedModel.embeddingLength.toLocaleString()}</span>
|
||||
<span class="text-theme-muted">Embedding Dim</span>
|
||||
<span class="text-theme-secondary">{selectedModel.embeddingLength.toLocaleString()}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if selectedModel.quantization}
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">Quantization</span>
|
||||
<span class="text-slate-300">{selectedModel.quantization}</span>
|
||||
<span class="text-theme-muted">Quantization</span>
|
||||
<span class="text-theme-secondary">{selectedModel.quantization}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if selectedModel.license}
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">License</span>
|
||||
<span class="text-slate-300">{selectedModel.license}</span>
|
||||
<span class="text-theme-muted">License</span>
|
||||
<span class="text-theme-secondary">{selectedModel.license}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-500">Downloads</span>
|
||||
<span class="text-slate-300">{selectedModel.pullCount.toLocaleString()}</span>
|
||||
<span class="text-theme-muted">Downloads</span>
|
||||
<span class="text-theme-secondary">{selectedModel.pullCount.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Available Sizes (Parameter counts + file sizes) -->
|
||||
{#if selectedModel.tags.length > 0}
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-2 flex items-center gap-2 text-sm font-medium text-slate-300">
|
||||
<h3 class="mb-2 flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<span>Available Sizes</span>
|
||||
{#if loadingSizes}
|
||||
<svg class="h-3 w-3 animate-spin text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
@@ -904,17 +904,17 @@
|
||||
<div class="space-y-1">
|
||||
{#each selectedModel.tags as tag}
|
||||
{@const size = selectedModel.tagSizes?.[tag]}
|
||||
<div class="flex items-center justify-between rounded bg-slate-800 px-2 py-1.5">
|
||||
<div class="flex items-center justify-between rounded bg-theme-secondary px-2 py-1.5">
|
||||
<span class="text-xs font-medium text-blue-300">{tag}</span>
|
||||
{#if size}
|
||||
<span class="text-xs text-slate-400">{formatBytes(size)}</span>
|
||||
<span class="text-xs text-theme-muted">{formatBytes(size)}</span>
|
||||
{:else if loadingSizes}
|
||||
<span class="text-xs text-slate-500">...</span>
|
||||
<span class="text-xs text-theme-muted">...</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-slate-500">
|
||||
<p class="mt-2 text-xs text-theme-muted">
|
||||
Parameter sizes (e.g., 8b = 8 billion parameters)
|
||||
</p>
|
||||
</div>
|
||||
@@ -922,12 +922,12 @@
|
||||
|
||||
<!-- Pull Model Section -->
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-2 text-sm font-medium text-slate-300">Pull Model</h3>
|
||||
<h3 class="mb-2 text-sm font-medium text-theme-secondary">Pull Model</h3>
|
||||
|
||||
<!-- Tag/Size Selector -->
|
||||
{#if selectedModel.tags.length > 0}
|
||||
<div class="mb-3">
|
||||
<label for="tag-select" class="mb-1 flex items-center gap-2 text-xs text-slate-500">
|
||||
<label for="tag-select" class="mb-1 flex items-center gap-2 text-xs text-theme-muted">
|
||||
<span>Select variant:</span>
|
||||
{#if loadingSizes}
|
||||
<svg class="h-3 w-3 animate-spin text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
@@ -940,7 +940,7 @@
|
||||
id="tag-select"
|
||||
bind:value={selectedTag}
|
||||
disabled={pulling}
|
||||
class="w-full rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
|
||||
class="w-full rounded-lg border border-theme bg-theme-secondary px-3 py-2 text-sm text-theme-primary focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
|
||||
>
|
||||
{#each selectedModel.tags as tag}
|
||||
{@const size = selectedModel.tagSizes?.[tag]}
|
||||
@@ -960,7 +960,7 @@
|
||||
type="button"
|
||||
onclick={pullModel}
|
||||
disabled={pulling}
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#if pulling}
|
||||
<svg class="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
@@ -979,16 +979,16 @@
|
||||
<!-- Progress Display -->
|
||||
{#if pullProgress}
|
||||
<div class="mt-3 space-y-2">
|
||||
<div class="text-xs text-slate-400">{pullProgress.status}</div>
|
||||
<div class="text-xs text-theme-muted">{pullProgress.status}</div>
|
||||
{#if pullProgress.completed !== undefined && pullProgress.total !== undefined && pullProgress.total > 0}
|
||||
{@const percent = Math.round((pullProgress.completed / pullProgress.total) * 100)}
|
||||
<div class="h-2 w-full overflow-hidden rounded-full bg-slate-700">
|
||||
<div class="h-2 w-full overflow-hidden rounded-full bg-theme-tertiary">
|
||||
<div
|
||||
class="h-full rounded-full bg-blue-500 transition-all duration-300"
|
||||
style="width: {percent}%"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-slate-500">
|
||||
<div class="flex justify-between text-xs text-theme-muted">
|
||||
<span>{formatBytes(pullProgress.completed)}</span>
|
||||
<span>{percent}%</span>
|
||||
<span>{formatBytes(pullProgress.total)}</span>
|
||||
@@ -1016,7 +1016,7 @@
|
||||
href={selectedModel.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg border border-slate-700 bg-slate-800 px-4 py-2 text-sm text-slate-300 transition-colors hover:bg-slate-700 hover:text-white"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg border border-theme bg-theme-secondary px-4 py-2 text-sm text-theme-secondary transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
@@ -1033,13 +1033,13 @@
|
||||
|
||||
<!-- Active Pulls Progress (fixed bottom bar) -->
|
||||
{#if modelOperationsState.activePulls.size > 0}
|
||||
<div class="fixed bottom-0 left-0 right-0 z-40 border-t border-slate-700 bg-slate-800/95 p-4 backdrop-blur-sm">
|
||||
<div class="fixed bottom-0 left-0 right-0 z-40 border-t border-theme bg-theme-secondary/95 p-4 backdrop-blur-sm">
|
||||
<div class="mx-auto max-w-4xl space-y-3">
|
||||
<h3 class="text-sm font-medium text-slate-300">Active Downloads</h3>
|
||||
<h3 class="text-sm font-medium text-theme-secondary">Active Downloads</h3>
|
||||
{#each [...modelOperationsState.activePulls.entries()] as [name, pull]}
|
||||
<div class="rounded-lg bg-slate-900/50 p-3">
|
||||
<div class="rounded-lg bg-theme-primary/50 p-3">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="font-medium text-slate-200">{name}</span>
|
||||
<span class="font-medium text-theme-secondary">{name}</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => modelOperationsState.cancelPull(name)}
|
||||
@@ -1049,15 +1049,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-1 flex items-center gap-3">
|
||||
<div class="h-2 flex-1 overflow-hidden rounded-full bg-slate-700">
|
||||
<div class="h-2 flex-1 overflow-hidden rounded-full bg-theme-tertiary">
|
||||
<div
|
||||
class="h-full bg-sky-500 transition-all duration-300"
|
||||
style="width: {pull.progress.percent}%"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400">{pull.progress.percent}%</span>
|
||||
<span class="text-xs text-theme-muted">{pull.progress.percent}%</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-slate-500">
|
||||
<div class="flex items-center justify-between text-xs text-theme-muted">
|
||||
<span>{pull.progress.status}</span>
|
||||
{#if pull.progress.speed}
|
||||
<span>{modelOperationsState.formatBytes(pull.progress.speed)}/s</span>
|
||||
|
||||
@@ -98,13 +98,13 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full overflow-y-auto bg-slate-900 p-6">
|
||||
<div class="h-full overflow-y-auto bg-theme-primary p-6">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<!-- Header -->
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-white">System Prompts</h1>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<h1 class="text-2xl font-bold text-theme-primary">System Prompts</h1>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Create and manage system prompt templates for conversations
|
||||
</p>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={openCreateEditor}
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
@@ -136,21 +136,21 @@
|
||||
<!-- Prompts list -->
|
||||
{#if promptsState.isLoading}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-2 border-slate-600 border-t-blue-500"></div>
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-2 border-theme-subtle border-t-blue-500"></div>
|
||||
</div>
|
||||
{:else if promptsState.prompts.length === 0}
|
||||
<div class="rounded-lg border border-dashed border-slate-700 bg-slate-800/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<div class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-slate-400">No system prompts yet</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">No system prompts yet</h3>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Create a system prompt to customize AI behavior
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onclick={openCreateEditor}
|
||||
class="mt-4 inline-flex items-center gap-2 rounded-lg bg-slate-700 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-slate-600"
|
||||
class="mt-4 inline-flex items-center gap-2 rounded-lg bg-theme-tertiary px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-theme-tertiary"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
@@ -162,12 +162,12 @@
|
||||
<div class="space-y-3">
|
||||
{#each promptsState.prompts as prompt (prompt.id)}
|
||||
<div
|
||||
class="rounded-lg border bg-slate-800 p-4 transition-colors {promptsState.activePromptId === prompt.id ? 'border-blue-500/50' : 'border-slate-700'}"
|
||||
class="rounded-lg border bg-theme-secondary p-4 transition-colors {promptsState.activePromptId === prompt.id ? 'border-blue-500/50' : 'border-theme'}"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="font-medium text-white">{prompt.name}</h3>
|
||||
<h3 class="font-medium text-theme-primary">{prompt.name}</h3>
|
||||
{#if prompt.isDefault}
|
||||
<span class="rounded bg-blue-900 px-2 py-0.5 text-xs text-blue-300">
|
||||
default
|
||||
@@ -180,12 +180,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if prompt.description}
|
||||
<p class="mt-1 text-sm text-slate-400">{prompt.description}</p>
|
||||
<p class="mt-1 text-sm text-theme-muted">{prompt.description}</p>
|
||||
{/if}
|
||||
<p class="mt-2 line-clamp-2 text-sm text-slate-500">
|
||||
<p class="mt-2 line-clamp-2 text-sm text-theme-muted">
|
||||
{prompt.content}
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-slate-600">
|
||||
<p class="mt-2 text-xs text-theme-muted">
|
||||
Updated {formatDate(prompt.updatedAt)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -195,7 +195,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSetActive(prompt)}
|
||||
class="rounded p-1.5 transition-colors {promptsState.activePromptId === prompt.id ? 'bg-emerald-600 text-white' : 'text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
class="rounded p-1.5 transition-colors {promptsState.activePromptId === prompt.id ? 'bg-emerald-600 text-theme-primary' : 'text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
title={promptsState.activePromptId === prompt.id ? 'Deactivate' : 'Use for new chats'}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -207,7 +207,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSetDefault(prompt)}
|
||||
class="rounded p-1.5 transition-colors {prompt.isDefault ? 'bg-blue-600 text-white' : 'text-slate-400 hover:bg-slate-700 hover:text-white'}"
|
||||
class="rounded p-1.5 transition-colors {prompt.isDefault ? 'bg-blue-600 text-theme-primary' : 'text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
title={prompt.isDefault ? 'Remove as default' : 'Set as default'}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill={prompt.isDefault ? 'currentColor' : 'none'} viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -219,7 +219,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => openEditEditor(prompt)}
|
||||
class="rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white"
|
||||
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
title="Edit"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -231,7 +231,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleDelete(prompt)}
|
||||
class="rounded p-1.5 text-slate-400 transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
title="Delete"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -246,21 +246,21 @@
|
||||
{/if}
|
||||
|
||||
<!-- Info section -->
|
||||
<section class="mt-8 rounded-lg border border-slate-700 bg-slate-800/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-slate-300">
|
||||
<section class="mt-8 rounded-lg border border-theme bg-theme-secondary/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
How System Prompts Work
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
System prompts define the AI's behavior, personality, and constraints. They're sent at the
|
||||
beginning of each conversation to set the context. Use them to create specialized assistants
|
||||
(e.g., code reviewer, writing helper) or to enforce specific response formats.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<strong class="text-slate-300">Default prompt:</strong> Automatically used for all new chats.
|
||||
<strong class="text-slate-300">Active prompt:</strong> Currently selected for your session.
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
<strong class="text-theme-secondary">Default prompt:</strong> Automatically used for all new chats.
|
||||
<strong class="text-theme-secondary">Active prompt:</strong> Currently selected for your session.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -276,15 +276,15 @@
|
||||
aria-modal="true"
|
||||
aria-labelledby="editor-title"
|
||||
>
|
||||
<div class="w-full max-w-2xl rounded-xl bg-slate-800 shadow-xl">
|
||||
<div class="flex items-center justify-between border-b border-slate-700 px-6 py-4">
|
||||
<h2 id="editor-title" class="text-lg font-semibold text-white">
|
||||
<div class="w-full max-w-2xl rounded-xl bg-theme-secondary shadow-xl">
|
||||
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
||||
<h2 id="editor-title" class="text-lg font-semibold text-theme-primary">
|
||||
{editingPrompt ? 'Edit Prompt' : 'Create Prompt'}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onclick={closeEditor}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -296,7 +296,7 @@
|
||||
<div class="space-y-4">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="prompt-name" class="mb-1 block text-sm font-medium text-slate-300">
|
||||
<label for="prompt-name" class="mb-1 block text-sm font-medium text-theme-secondary">
|
||||
Name <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
@@ -304,14 +304,14 @@
|
||||
type="text"
|
||||
bind:value={formName}
|
||||
placeholder="e.g., Code Reviewer"
|
||||
class="w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<label for="prompt-description" class="mb-1 block text-sm font-medium text-slate-300">
|
||||
<label for="prompt-description" class="mb-1 block text-sm font-medium text-theme-secondary">
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
@@ -319,13 +319,13 @@
|
||||
type="text"
|
||||
bind:value={formDescription}
|
||||
placeholder="Brief description of this prompt's purpose"
|
||||
class="w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div>
|
||||
<label for="prompt-content" class="mb-1 block text-sm font-medium text-slate-300">
|
||||
<label for="prompt-content" class="mb-1 block text-sm font-medium text-theme-secondary">
|
||||
System Prompt <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
@@ -333,10 +333,10 @@
|
||||
bind:value={formContent}
|
||||
placeholder="You are a helpful assistant that..."
|
||||
rows="8"
|
||||
class="w-full resize-none rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 font-mono text-sm text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
class="w-full resize-none rounded-lg border border-theme-subtle bg-theme-tertiary px-3 py-2 font-mono text-sm text-theme-primary placeholder-theme-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
required
|
||||
></textarea>
|
||||
<p class="mt-1 text-xs text-slate-500">
|
||||
<p class="mt-1 text-xs text-theme-muted">
|
||||
{formContent.length} characters
|
||||
</p>
|
||||
</div>
|
||||
@@ -347,9 +347,9 @@
|
||||
id="prompt-default"
|
||||
type="checkbox"
|
||||
bind:checked={formIsDefault}
|
||||
class="h-4 w-4 rounded border-slate-600 bg-slate-700 text-blue-600 focus:ring-blue-500 focus:ring-offset-slate-800"
|
||||
class="h-4 w-4 rounded border-theme-subtle bg-theme-tertiary text-blue-600 focus:ring-blue-500 focus:ring-offset-theme"
|
||||
/>
|
||||
<label for="prompt-default" class="text-sm text-slate-300">
|
||||
<label for="prompt-default" class="text-sm text-theme-secondary">
|
||||
Set as default for new chats
|
||||
</label>
|
||||
</div>
|
||||
@@ -360,14 +360,14 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={closeEditor}
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-slate-300 transition-colors hover:bg-slate-700"
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-theme-secondary transition-colors hover:bg-theme-tertiary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSaving || !formName.trim() || !formContent.trim()}
|
||||
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{isSaving ? 'Saving...' : editingPrompt ? 'Update' : 'Create'}
|
||||
</button>
|
||||
|
||||
@@ -77,24 +77,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full overflow-y-auto bg-slate-900 p-6">
|
||||
<div class="h-full overflow-y-auto bg-theme-primary p-6">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<!-- Header -->
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-white">Tools</h1>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<h1 class="text-2xl font-bold text-theme-primary">Tools</h1>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Manage tools available to the AI during conversations
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Global toggle -->
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm text-slate-400">Tools enabled</span>
|
||||
<span class="text-sm text-theme-muted">Tools enabled</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleGlobalTools}
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-slate-900 {toolsState.toolsEnabled ? 'bg-blue-600' : 'bg-slate-600'}"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-theme-primary {toolsState.toolsEnabled ? 'bg-blue-600' : 'bg-theme-tertiary'}"
|
||||
role="switch"
|
||||
aria-checked={toolsState.toolsEnabled}
|
||||
>
|
||||
@@ -107,7 +107,7 @@
|
||||
|
||||
<!-- Built-in Tools Section -->
|
||||
<section class="mb-8">
|
||||
<h2 class="mb-4 flex items-center gap-2 text-lg font-semibold text-white">
|
||||
<h2 class="mb-4 flex items-center gap-2 text-lg font-semibold text-theme-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
@@ -118,22 +118,22 @@
|
||||
<div class="space-y-3">
|
||||
{#each builtinTools as tool (tool.definition.function.name)}
|
||||
<div
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 p-4 transition-colors {tool.enabled ? '' : 'opacity-60'}"
|
||||
class="rounded-lg border border-theme bg-theme-secondary p-4 transition-colors {tool.enabled ? '' : 'opacity-60'}"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="font-medium text-white">
|
||||
<h3 class="font-medium text-theme-primary">
|
||||
{tool.definition.function.name}
|
||||
</h3>
|
||||
<span class="rounded bg-slate-700 px-2 py-0.5 text-xs text-slate-300">
|
||||
<span class="rounded bg-theme-tertiary px-2 py-0.5 text-xs text-theme-secondary">
|
||||
built-in
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
{tool.definition.function.description}
|
||||
</p>
|
||||
<p class="mt-2 font-mono text-xs text-slate-500">
|
||||
<p class="mt-2 font-mono text-xs text-theme-muted">
|
||||
Parameters: {formatParameters(tool.definition)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -141,7 +141,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => toggleTool(tool.definition.function.name)}
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-slate-800 {tool.enabled ? 'bg-blue-600' : 'bg-slate-600'}"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-theme {tool.enabled ? 'bg-blue-600' : 'bg-theme-tertiary'}"
|
||||
role="switch"
|
||||
aria-checked={tool.enabled}
|
||||
disabled={!toolsState.toolsEnabled}
|
||||
@@ -159,7 +159,7 @@
|
||||
<!-- Custom Tools Section -->
|
||||
<section>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h2 class="flex items-center gap-2 text-lg font-semibold text-white">
|
||||
<h2 class="flex items-center gap-2 text-lg font-semibold text-theme-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
@@ -169,7 +169,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={openCreateEditor}
|
||||
class="flex items-center gap-2 rounded-lg bg-emerald-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700"
|
||||
class="flex items-center gap-2 rounded-lg bg-emerald-600 px-3 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-emerald-700"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
@@ -179,12 +179,12 @@
|
||||
</div>
|
||||
|
||||
{#if toolsState.customTools.length === 0}
|
||||
<div class="rounded-lg border border-dashed border-slate-700 bg-slate-800/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<div class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-slate-400">No custom tools yet</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">No custom tools yet</h3>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Create custom tools to extend AI capabilities
|
||||
</p>
|
||||
</div>
|
||||
@@ -192,26 +192,26 @@
|
||||
<div class="space-y-3">
|
||||
{#each toolsState.customTools as tool (tool.id)}
|
||||
<div
|
||||
class="rounded-lg border border-slate-700 bg-slate-800 p-4 transition-colors {tool.enabled ? '' : 'opacity-60'}"
|
||||
class="rounded-lg border border-theme bg-theme-secondary p-4 transition-colors {tool.enabled ? '' : 'opacity-60'}"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="font-medium text-white">
|
||||
<h3 class="font-medium text-theme-primary">
|
||||
{tool.name}
|
||||
</h3>
|
||||
<span class="rounded bg-emerald-900 px-2 py-0.5 text-xs text-emerald-300">
|
||||
custom
|
||||
</span>
|
||||
<span class="rounded bg-slate-700 px-2 py-0.5 text-xs text-slate-400">
|
||||
<span class="rounded bg-theme-tertiary px-2 py-0.5 text-xs text-theme-muted">
|
||||
{tool.implementation}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-slate-400">
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
{tool.description}
|
||||
</p>
|
||||
{#if Object.keys(tool.parameters.properties ?? {}).length > 0}
|
||||
<p class="mt-1 font-mono text-xs text-slate-500">
|
||||
<p class="mt-1 font-mono text-xs text-theme-muted">
|
||||
Parameters: {Object.keys(tool.parameters.properties ?? {}).join(', ')}
|
||||
</p>
|
||||
{/if}
|
||||
@@ -221,7 +221,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => openEditEditor(tool)}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-slate-700 hover:text-white"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
aria-label="Edit tool"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -232,7 +232,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleDeleteTool(tool)}
|
||||
class="rounded p-1 text-slate-400 transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
aria-label="Delete tool"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
@@ -243,7 +243,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => toggleTool(tool.name)}
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 focus:ring-offset-slate-800 {tool.enabled ? 'bg-emerald-600' : 'bg-slate-600'}"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 focus:ring-offset-theme {tool.enabled ? 'bg-emerald-600' : 'bg-theme-tertiary'}"
|
||||
role="switch"
|
||||
aria-checked={tool.enabled}
|
||||
disabled={!toolsState.toolsEnabled}
|
||||
@@ -261,21 +261,21 @@
|
||||
</section>
|
||||
|
||||
<!-- Info Section -->
|
||||
<section class="mt-8 rounded-lg border border-slate-700 bg-slate-800/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-slate-300">
|
||||
<section class="mt-8 rounded-lg border border-theme bg-theme-secondary/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
How Tools Work
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
Tools extend the AI's capabilities by allowing it to perform actions like calculations,
|
||||
fetching web content, or getting the current time. When you ask a question that could
|
||||
benefit from a tool, the AI will automatically use the appropriate tool and include
|
||||
the results in its response.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-slate-400">
|
||||
<strong class="text-slate-300">Note:</strong> Not all models support tool calling.
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
<strong class="text-theme-secondary">Note:</strong> Not all models support tool calling.
|
||||
Models like Llama 3.1+ and Mistral 7B+ have built-in tool support.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user