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:
2026-01-01 06:20:48 +01:00
parent 8fa6fdec1f
commit d54acb97a3
37 changed files with 746 additions and 624 deletions

View File

@@ -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');
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)}%"

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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
>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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"
>

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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"
>

View File

@@ -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

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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..."

View File

@@ -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>

View File

@@ -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';
}
/**

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>