ui: migrate Rerank and normalize remaining views to shadcn tokens

- RerankInterface uses Button/Input/Textarea/ToggleGroup
- normalize legacy color utilities and lucide imports across the
  remaining playground interfaces, Performance and CaptureDialog

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmuGqwNBJNEAMaWsdCDqUC
This commit is contained in:
Claude
2026-06-27 12:01:19 +00:00
parent 2b087dffb1
commit fc24722258
8 changed files with 139 additions and 155 deletions
+13 -13
View File
@@ -193,17 +193,17 @@
<dialog
bind:this={dialogEl}
onclose={handleDialogClose}
class="bg-surface text-txtmain rounded-lg shadow-xl max-w-[80%] w-full max-h-[90vh] p-0 backdrop:bg-black/50 m-auto"
class="bg-background text-foreground rounded-lg shadow-xl max-w-[80%] w-full max-h-[90vh] p-0 backdrop:bg-black/50 m-auto"
>
{#if capture}
<div class="flex flex-col max-h-[90vh]">
<div
class="flex justify-between items-center p-4 border-b border-card-border"
>
<h2 class="text-xl font-bold pb-0">Capture #{capture.id + 1}{#if capture.req_path} <span class="text-base font-mono font-normal text-txtsecondary">{capture.req_path}</span>{/if}</h2>
<h2 class="text-xl font-bold pb-0">Capture #{capture.id + 1}{#if capture.req_path} <span class="text-base font-mono font-normal text-muted-foreground">{capture.req_path}</span>{/if}</h2>
<button
onclick={() => dialogEl?.close()}
class="text-txtsecondary hover:text-txtmain text-2xl leading-none"
class="text-muted-foreground hover:text-foreground text-2xl leading-none"
>
&times;
</button>
@@ -213,7 +213,7 @@
<!-- Request Headers -->
<details class="group" open>
<summary
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-txtsecondary hover:text-txtmain"
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-muted-foreground hover:text-foreground"
>
Request Headers
</summary>
@@ -238,7 +238,7 @@
<!-- Request Body -->
<details class="group" open>
<summary
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-txtsecondary hover:text-txtmain"
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-muted-foreground hover:text-foreground"
>
Request Body
</summary>
@@ -290,7 +290,7 @@
<!-- Response Headers -->
<details class="group" open>
<summary
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-txtsecondary hover:text-txtmain"
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-muted-foreground hover:text-foreground"
>
Response Headers
</summary>
@@ -315,7 +315,7 @@
<!-- Response Body -->
<details class="group" open>
<summary
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-txtsecondary hover:text-txtmain"
class="cursor-pointer font-semibold text-sm uppercase tracking-wider text-muted-foreground hover:text-foreground"
>
Response Body
</summary>
@@ -375,19 +375,19 @@
{#if sseChat.reasoning}
<div>
<div
class="text-xs font-semibold uppercase tracking-wider text-txtsecondary mb-1"
class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-1"
>
Reasoning
</div>
<pre
class="font-mono whitespace-pre-wrap break-all text-txtsecondary">{sseChat.reasoning}</pre>
class="font-mono whitespace-pre-wrap break-all text-muted-foreground">{sseChat.reasoning}</pre>
</div>
{/if}
{#if sseChat.content}
<div>
{#if sseChat.reasoning}
<div
class="text-xs font-semibold uppercase tracking-wider text-txtsecondary mb-1"
class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-1"
>
Response
</div>
@@ -409,7 +409,7 @@
<div
class="mt-2 bg-background rounded border border-card-border overflow-auto max-h-96"
>
<div class="p-3 text-sm text-txtsecondary italic">
<div class="p-3 text-sm text-muted-foreground italic">
(binary data - {responseContentType || "unknown content type"})
</div>
</div>
@@ -429,8 +429,8 @@
</div>
{:else}
<div class="flex flex-col items-center justify-center p-12">
<p class="text-lg text-txtsecondary">Capture not found</p>
<p class="text-sm text-txtsecondary mt-1">The capture may have expired or was never recorded.</p>
<p class="text-lg text-muted-foreground">Capture not found</p>
<p class="text-sm text-muted-foreground mt-1">The capture may have expired or was never recorded.</p>
<div class="mt-4">
<button onclick={() => dialogEl?.close()} class="btn">Close</button>
</div>
@@ -150,14 +150,14 @@
<!-- Empty state for no models configured -->
{#if !hasModels}
<div class="flex-1 flex items-center justify-center text-txtsecondary">
<div class="flex-1 flex items-center justify-center text-muted-foreground">
<p>No models configured. Add models to your configuration to transcribe audio.</p>
</div>
{:else}
<!-- File upload / Result display area -->
<div class="flex-1 overflow-auto mb-4 flex items-center justify-center bg-surface border border-gray-200 dark:border-white/10 rounded">
<div class="flex-1 overflow-auto mb-4 flex items-center justify-center bg-background border border-border rounded">
{#if isTranscribing}
<div class="text-center text-txtsecondary">
<div class="text-center text-muted-foreground">
<div class="inline-block w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin mb-2"></div>
<p>Transcribing audio...</p>
</div>
@@ -186,12 +186,12 @@
{/if}
</button>
</div>
<div class="flex-1 overflow-auto p-3 rounded border border-gray-200 dark:border-white/10 bg-background whitespace-pre-wrap">
<div class="flex-1 overflow-auto p-3 rounded border border-border bg-background whitespace-pre-wrap">
{transcriptionResult}
</div>
</div>
{:else if selectedFile}
<div class="text-center text-txtsecondary p-4">
<div class="text-center text-muted-foreground p-4">
<p class="font-medium mb-2">File Selected</p>
<p class="text-sm">{selectedFile.name}</p>
<p class="text-xs mt-1">{formatFileSize(selectedFile.size)}</p>
@@ -200,7 +200,7 @@
<div
role="region"
aria-label="Audio file drop zone"
class="w-full h-full flex items-center justify-center text-center text-txtsecondary p-8 {isDragging ? 'bg-primary/10' : ''}"
class="w-full h-full flex items-center justify-center text-center text-muted-foreground p-8 {isDragging ? 'bg-primary/10' : ''}"
ondragover={handleDragOver}
ondragleave={handleDragLeave}
ondrop={handleDrop}
@@ -237,7 +237,7 @@
</button>
{:else}
<button
class="btn bg-primary text-btn-primary-text hover:opacity-90"
class="btn bg-primary text-primary-foreground hover:opacity-90"
onclick={transcribe}
disabled={!canTranscribe}
>
@@ -371,7 +371,7 @@
</button>
{:else}
<button
class="btn bg-primary text-btn-primary-text hover:opacity-90"
class="btn bg-primary text-primary-foreground hover:opacity-90"
onclick={run}
disabled={!canRun}
title={$testListStore.length === 0 ? "Add models from the list below" : "Run concurrent requests"}
@@ -386,18 +386,18 @@
<!-- Available models -->
<div class="flex flex-col min-h-0 flex-1">
<div class="text-xs font-medium text-txtsecondary mb-1">
<div class="text-xs font-medium text-muted-foreground mb-1">
Models <span class="text-[10px] font-normal">— click to queue (add the same model more than once to test parallel requests)</span>
</div>
<div class="flex-1 border border-gray-200 dark:border-white/10 rounded overflow-y-auto min-h-0">
<div class="flex-1 border border-border rounded overflow-y-auto min-h-0">
{#if !hasModels}
<div class="p-3 text-sm text-txtsecondary text-center">No models configured.</div>
<div class="p-3 text-sm text-muted-foreground text-center">No models configured.</div>
{:else}
<ul class="divide-y divide-gray-100 dark:divide-white/5">
{#each availableModels as m (m.id)}
<li>
<button
class="w-full text-left px-2 py-1.5 text-sm hover:bg-secondary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
class="w-full text-left px-2 py-1.5 text-sm hover:bg-accent transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
onclick={() => addModel(m.id)}
disabled={isRunning}
title="Add {m.id}"
@@ -413,11 +413,11 @@
</div>
<!-- Settings -->
<div class="flex flex-col gap-2 border-t border-gray-200 dark:border-white/10 pt-3">
<div class="flex flex-col gap-2 border-t border-border pt-3">
<div class="flex items-center justify-between">
<label for="concurrency-prompt" class="text-xs font-medium text-txtsecondary">Prompt</label>
<label for="concurrency-prompt" class="text-xs font-medium text-muted-foreground">Prompt</label>
<button
class="text-[10px] text-txtsecondary hover:text-txtmain underline"
class="text-[10px] text-muted-foreground hover:text-foreground underline"
onclick={resetDefaults}
disabled={isRunning}
>
@@ -426,17 +426,17 @@
</div>
<textarea
id="concurrency-prompt"
class="w-full px-2 py-1.5 text-sm rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none"
class="w-full px-2 py-1.5 text-sm rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none"
rows="3"
bind:value={$promptStore}
disabled={isRunning}
></textarea>
<label for="concurrency-max-tokens" class="text-xs font-medium text-txtsecondary">max_tokens</label>
<label for="concurrency-max-tokens" class="text-xs font-medium text-muted-foreground">max_tokens</label>
<input
id="concurrency-max-tokens"
type="number"
min="1"
class="w-full px-2 py-1.5 text-sm rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="w-full px-2 py-1.5 text-sm rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$maxTokensStore}
disabled={isRunning}
/>
@@ -447,8 +447,8 @@
<div class="flex-1 min-w-0 min-h-0 overflow-y-auto">
{#if $testListStore.length === 0}
<div class="h-full flex items-center justify-center px-6">
<div class="max-w-md text-sm text-txtsecondary space-y-4">
<h4 class="text-base font-semibold text-txtmain pb-0">Load Test</h4>
<div class="max-w-md text-sm text-muted-foreground space-y-4">
<h4 class="text-base font-semibold text-foreground pb-0">Load Test</h4>
<p>
Fire several streaming chat completions at llama-swap at the same time to see how it handles parallel
loading and concurrent inference. Each request streams into its own panel with a live timer and status.
@@ -456,16 +456,16 @@
<ol class="list-decimal list-inside space-y-1">
<li>Click models on the left to queue them — repeat a model to hit it with parallel requests.</li>
<li>Tweak the prompt and <code>max_tokens</code> if you want.</li>
<li>Press <span class="font-semibold text-txtmain">Go</span> to launch them concurrently.</li>
<li>Press <span class="font-semibold text-foreground">Go</span> to launch them concurrently.</li>
</ol>
<p class="text-xs">Tip: drag a result card's header to reorder, or hit × to drop it.</p>
</div>
</div>
{:else}
<!-- Gantt-style timeline -->
<div class="mb-3 border border-gray-200 dark:border-white/10 rounded">
<div class="mb-3 border border-border rounded">
<button
class="w-full flex items-center gap-2 px-2 py-1.5 text-xs font-medium text-txtsecondary hover:bg-secondary-hover transition-colors {$timelineCollapsedStore ? 'rounded' : 'rounded-t border-b border-gray-200 dark:border-white/10'}"
class="w-full flex items-center gap-2 px-2 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent transition-colors {$timelineCollapsedStore ? 'rounded' : 'rounded-t border-b border-border'}"
onclick={() => timelineCollapsedStore.update((v) => !v)}
aria-expanded={!$timelineCollapsedStore}
>
@@ -480,7 +480,7 @@
</svg>
<span>Timeline</span>
{#if !$timelineCollapsedStore}
<span class="flex items-center gap-3 text-[10px] text-txtsecondary font-normal ml-3" aria-hidden="true">
<span class="flex items-center gap-3 text-[10px] text-muted-foreground font-normal ml-3" aria-hidden="true">
<span class="flex items-center gap-1"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-slate-200 dark:bg-white/10 border border-gray-300 dark:border-white/10"></span>waiting</span>
<span class="flex items-center gap-1"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-slate-400 dark:bg-slate-500"></span>loading</span>
<span class="flex items-center gap-1"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-purple-500"></span>reasoning</span>
@@ -489,7 +489,7 @@
<span class="flex items-center gap-1"><span class="inline-block w-2.5 h-2.5 rounded-sm bg-red-500"></span>error</span>
</span>
{/if}
<span class="ml-auto tabular-nums text-txtsecondary">
<span class="ml-auto tabular-nums text-muted-foreground">
max {formatElapsed(timelineMaxMs)} · {$testListStore.length} request{$testListStore.length === 1 ? "" : "s"}
</span>
</button>
@@ -498,13 +498,13 @@
<!-- X axis ticks -->
<div class="flex" aria-hidden="true">
<div class="w-40 shrink-0"></div>
<div class="relative flex-1 h-4 border-b border-gray-200 dark:border-white/10">
<div class="relative flex-1 h-4 border-b border-border">
{#each timelineTicks as t (t)}
<div
class="absolute top-0 bottom-0 border-l border-gray-200 dark:border-white/10"
class="absolute top-0 bottom-0 border-l border-border"
style="left: {(t / timelineMaxMs) * 100}%;"
>
<span class="absolute -top-0.5 left-1 text-[10px] text-txtsecondary tabular-nums">{formatTickMs(t)}</span>
<span class="absolute -top-0.5 left-1 text-[10px] text-muted-foreground tabular-nums">{formatTickMs(t)}</span>
</div>
{/each}
</div>
@@ -519,14 +519,14 @@
{@const reasoningPct = run ? (run.reasoningMs / timelineMaxMs) * 100 : 0}
{@const contentPct = run ? (run.contentMs / timelineMaxMs) * 100 : 0}
<div class="flex items-center text-xs">
<div class="w-40 shrink-0 flex items-center gap-1 pr-2 text-txtsecondary">
<div class="w-40 shrink-0 flex items-center gap-1 pr-2 text-muted-foreground">
<span class="tabular-nums w-5 text-right">{i + 1}.</span>
<span class="truncate" title={entry.model}>{entry.model}</span>
</div>
<div class="relative flex-1 h-4">
{#each timelineTicks as t (t)}
<div
class="absolute top-0 bottom-0 border-l border-gray-100 dark:border-white/5"
class="absolute top-0 bottom-0 border-l border-border"
style="left: {(t / timelineMaxMs) * 100}%;"
aria-hidden="true"
></div>
@@ -560,7 +560,7 @@
></div>
{/if}
</div>
<div class="w-16 shrink-0 pl-2 tabular-nums text-txtsecondary text-right">
<div class="w-16 shrink-0 pl-2 tabular-nums text-muted-foreground text-right">
{run ? formatElapsed(run.elapsedMs) : "—"}
</div>
</div>
@@ -576,14 +576,14 @@
<div
class="border rounded flex flex-col min-h-0 transition-colors {dragOverIndex === i && dragIndex !== i
? 'border-primary ring-2 ring-primary/40'
: 'border-gray-200 dark:border-white/10'} {dragIndex === i ? 'opacity-40' : ''}"
: 'border-border'} {dragIndex === i ? 'opacity-40' : ''}"
style="height: 280px;"
role="listitem"
ondragover={(e) => onDragOver(i, e)}
ondrop={(e) => onDrop(i, e)}
>
<div
class="shrink-0 flex items-center gap-2 px-2 py-1.5 border-b border-gray-200 dark:border-white/10 bg-secondary/40 rounded-t"
class="shrink-0 flex items-center gap-2 px-2 py-1.5 border-b border-border bg-secondary/40 rounded-t"
draggable={!isRunning}
role="button"
tabindex="-1"
@@ -593,15 +593,15 @@
class:cursor-grab={!isRunning}
title={isRunning ? "" : "Drag to reorder"}
>
<span class="text-txtsecondary select-none" aria-hidden="true">⋮⋮</span>
<span class="text-txtsecondary tabular-nums text-xs w-5 text-right">{i + 1}.</span>
<span class="text-muted-foreground select-none" aria-hidden="true">⋮⋮</span>
<span class="text-muted-foreground tabular-nums text-xs w-5 text-right">{i + 1}.</span>
<span class="flex-1 truncate text-sm font-medium" title={entry.model}>{entry.model}</span>
<span class="text-xs tabular-nums text-txtsecondary">
<span class="text-xs tabular-nums text-muted-foreground">
{run ? formatElapsed(run.elapsedMs) : "—"}
</span>
<span class="status text-[10px] {statusBadgeClass(status)}">{status}</span>
<button
class="w-5 h-5 flex items-center justify-center text-txtsecondary hover:text-red-500 transition-colors rounded disabled:opacity-30 disabled:cursor-not-allowed"
class="w-5 h-5 flex items-center justify-center text-muted-foreground hover:text-red-500 transition-colors rounded disabled:opacity-30 disabled:cursor-not-allowed"
onclick={() => removeEntry(entry.id)}
disabled={isRunning}
aria-label="Remove"
@@ -612,7 +612,7 @@
</div>
<div class="flex-1 min-h-0 overflow-y-auto font-mono text-xs px-2 py-1.5">
{#if run?.loadingText}
<div class="bg-secondary/40 dark:bg-white/5 text-txtsecondary rounded px-2 py-1 mb-2 whitespace-pre-wrap">{run.loadingText.trim()}</div>
<div class="bg-secondary/40 dark:bg-white/5 text-muted-foreground rounded px-2 py-1 mb-2 whitespace-pre-wrap">{run.loadingText.trim()}</div>
{/if}
{#if run?.reasoningContent}
<div class="text-purple-700 dark:text-purple-300 whitespace-pre-wrap">{run.reasoningContent}</div>
@@ -196,7 +196,7 @@
<ModelSelector bind:value={$selectedModelStore} placeholder="Select an image model..." disabled={isGenerating} capabilities={["image_generation", "image_to_image"]} matchAny={true} />
<select
class="px-3 py-2 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-3 py-2 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$apiModeStore}
disabled={isGenerating}
>
@@ -205,7 +205,7 @@
</select>
<select
class="px-3 py-2 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-3 py-2 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$selectedSizeStore}
disabled={isGenerating}
>
@@ -227,7 +227,7 @@
{#if isSdapi}
<button
class="px-3 py-2 rounded border border-gray-200 dark:border-white/10 bg-surface hover:bg-secondary-hover transition-colors"
class="px-3 py-2 rounded border border-border bg-background hover:bg-accent transition-colors"
onclick={() => showSettings = !showSettings}
>
{showSettings ? "Hide Settings" : "Settings"}
@@ -237,23 +237,23 @@
<!-- SDAPI Settings Panel -->
{#if isSdapi && showSettings}
<div class="shrink-0 mb-4 p-4 rounded border border-gray-200 dark:border-white/10 bg-surface">
<div class="shrink-0 mb-4 p-4 rounded border border-border bg-background">
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-3">
<label class="flex flex-col gap-1">
<span class="text-xs text-txtsecondary">Steps</span>
<span class="text-xs text-muted-foreground">Steps</span>
<input
type="number"
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$sdStepsStore}
min="1"
max="150"
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-txtsecondary">CFG Scale</span>
<span class="text-xs text-muted-foreground">CFG Scale</span>
<input
type="number"
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$sdCfgScaleStore}
min="1"
max="30"
@@ -261,28 +261,28 @@
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-txtsecondary">Seed (-1 = random)</span>
<span class="text-xs text-muted-foreground">Seed (-1 = random)</span>
<input
type="number"
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$sdSeedStore}
min="-1"
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-txtsecondary">Batch Size</span>
<span class="text-xs text-muted-foreground">Batch Size</span>
<input
type="number"
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$sdBatchSizeStore}
min="1"
max="8"
/>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-txtsecondary">Sampler</span>
<span class="text-xs text-muted-foreground">Sampler</span>
<select
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$sdSamplerStore}
>
<option value="">Default</option>
@@ -301,9 +301,9 @@
</select>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-txtsecondary">Scheduler</span>
<span class="text-xs text-muted-foreground">Scheduler</span>
<select
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
bind:value={$sdSchedulerStore}
>
<option value="">Auto for model</option>
@@ -317,9 +317,9 @@
</div>
<label class="flex flex-col gap-1 mb-3">
<span class="text-xs text-txtsecondary">Negative Prompt</span>
<span class="text-xs text-muted-foreground">Negative Prompt</span>
<textarea
class="px-2 py-1 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-y text-sm"
class="px-2 py-1 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-y text-sm"
bind:value={$sdNegativePromptStore}
rows="2"
placeholder="Elements to avoid..."
@@ -328,10 +328,10 @@
<!-- LoRA Selection -->
<div>
<span class="text-xs text-txtsecondary block mb-1">LoRAs</span>
<span class="text-xs text-muted-foreground block mb-1">LoRAs</span>
<div class="flex items-center gap-2 mb-2">
<button
class="px-3 py-1.5 text-sm rounded border border-gray-200 dark:border-white/10 bg-surface hover:bg-secondary-hover transition-colors disabled:opacity-50"
class="px-3 py-1.5 text-sm rounded border border-border bg-background hover:bg-accent transition-colors disabled:opacity-50"
onclick={loadLoras}
disabled={!$selectedModelStore || isLoadingLoras}
>
@@ -339,7 +339,7 @@
</button>
{#if lorasLoaded && availableLoras.length > 0}
<select
class="flex-1 px-2 py-1.5 text-sm rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="flex-1 px-2 py-1.5 text-sm rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
onchange={addLora}
>
<option value="">Add a LoRA...</option>
@@ -353,7 +353,7 @@
<p class="text-xs text-red-500 mb-1">{loraError}</p>
{/if}
{#if lorasLoaded && availableLoras.length === 0}
<p class="text-xs text-txtsecondary">No LoRAs available</p>
<p class="text-xs text-muted-foreground">No LoRAs available</p>
{/if}
{#if selectedLoras.length > 0}
<div class="flex flex-col gap-1.5">
@@ -362,7 +362,7 @@
<span class="flex-1 truncate">{getLoraName(lora.path)}</span>
<input
type="number"
class="w-20 px-1.5 py-1 text-xs rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-1 focus:ring-primary"
class="w-20 px-1.5 py-1 text-xs rounded border border-border bg-background focus:outline-none focus:ring-1 focus:ring-primary"
value={lora.multiplier}
oninput={(e) => updateLoraMultiplier(lora.path, parseFloat((e.target as HTMLInputElement).value) || 1)}
min="0"
@@ -370,7 +370,7 @@
step="0.1"
/>
<button
class="px-1.5 py-0.5 text-xs rounded border border-gray-200 dark:border-white/10 hover:bg-red-500 hover:text-white hover:border-red-500 transition-colors"
class="px-1.5 py-0.5 text-xs rounded border border-border hover:bg-red-500 hover:text-white hover:border-red-500 transition-colors"
onclick={() => removeLora(lora.path)}
aria-label="Remove LoRA"
>
@@ -386,14 +386,14 @@
<!-- Empty state for no models configured -->
{#if !hasModels}
<div class="flex-1 flex items-center justify-center text-txtsecondary">
<div class="flex-1 flex items-center justify-center text-muted-foreground">
<p>No models configured. Add models to your configuration to generate images.</p>
</div>
{:else}
<!-- Image display area -->
<div class="flex-1 overflow-auto mb-4 flex items-center justify-center bg-surface border border-gray-200 dark:border-white/10 rounded">
<div class="flex-1 overflow-auto mb-4 flex items-center justify-center bg-background border border-border rounded">
{#if isGenerating}
<div class="text-center text-txtsecondary">
<div class="text-center text-muted-foreground">
<div class="inline-block w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin mb-2"></div>
<p>Generating image...</p>
</div>
@@ -454,7 +454,7 @@
</button>
</div>
{:else}
<div class="text-center text-txtsecondary">
<div class="text-center text-muted-foreground">
<p>Enter a prompt below to generate an image</p>
</div>
{/if}
@@ -476,7 +476,7 @@
</button>
{:else}
<button
class="btn bg-primary text-btn-primary-text hover:opacity-90 flex-1 md:flex-none"
class="btn bg-primary text-primary-foreground hover:opacity-90 flex-1 md:flex-none"
onclick={generate}
disabled={!prompt.trim() || !$selectedModelStore}
>
@@ -7,7 +7,7 @@
</script>
<div class="flex items-center justify-center h-full">
<div class="text-center text-txtsecondary">
<div class="text-muted-foreground text-center">
<p class="text-lg">{featureName}</p>
<p class="text-sm mt-2">To be implemented</p>
</div>
@@ -4,6 +4,10 @@
import { rerank } from "../../lib/rerankApi";
import { playgroundStores } from "../../stores/playgroundActivity";
import ModelSelector from "./ModelSelector.svelte";
import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Textarea } from "$lib/components/ui/textarea/index.js";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
type RerankRow = { doc: string; score: number | null };
type SortOrder = "none" | "asc" | "desc";
@@ -234,9 +238,9 @@
}
function scoreColor(score: number | null): string {
if (score === null) return "text-txtsecondary";
if (score === null) return "text-muted-foreground";
if (score > 0) return "text-green-600 dark:text-green-400";
return "text-red-500 dark:text-red-400";
return "text-destructive";
}
function formatScore(score: number | null): string {
@@ -266,9 +270,9 @@
<div class="shrink-0 flex flex-wrap gap-2 mb-4">
<ModelSelector bind:value={$selectedModelStore} placeholder="Select a rerank model..." disabled={isLoading} capabilities={["reranker"]} />
{#if editorMode === "table"}
<input
<Input
type="text"
class="min-w-0 flex-1 basis-48 px-3 py-2 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="min-w-0 flex-1 basis-48"
placeholder="Query..."
bind:value={query}
disabled={isLoading}
@@ -276,60 +280,50 @@
/>
{/if}
<!-- Table / JSON toggle -->
<div class="flex rounded border border-gray-200 dark:border-white/10 overflow-hidden shrink-0">
<button
class="px-3 py-1.5 text-sm transition-colors {editorMode === 'table'
? 'bg-primary text-btn-primary-text'
: 'bg-surface hover:bg-secondary-hover'}"
onclick={switchToTable}
disabled={isLoading}
>
Table
</button>
<button
class="px-3 py-1.5 text-sm border-l border-gray-200 dark:border-white/10 transition-colors {editorMode === 'json'
? 'bg-primary text-btn-primary-text'
: 'bg-surface hover:bg-secondary-hover'}"
onclick={switchToJson}
disabled={isLoading}
>
JSON
</button>
</div>
<ToggleGroup.Root
type="single"
variant="outline"
value={editorMode}
onValueChange={(v) => v && (v === "table" ? switchToTable() : switchToJson())}
class="shrink-0"
>
<ToggleGroup.Item value="table" disabled={isLoading}>Table</ToggleGroup.Item>
<ToggleGroup.Item value="json" disabled={isLoading}>JSON</ToggleGroup.Item>
</ToggleGroup.Root>
</div>
{#if !hasModels}
<div class="flex-1 flex items-center justify-center text-txtsecondary">
<div class="text-muted-foreground flex flex-1 items-center justify-center">
<p>No models configured. Add models to your configuration to use reranking.</p>
</div>
{:else if editorMode === "json"}
<!-- JSON editor -->
<div class="flex-1 flex flex-col min-h-0 mb-4">
<textarea
class="flex-1 w-full font-mono text-sm px-3 py-2 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none"
<div class="mb-4 flex min-h-0 flex-1 flex-col">
<Textarea
class="w-full flex-1 resize-none font-mono text-sm"
bind:value={jsonText}
disabled={isLoading}
placeholder={'{\n "query": "your search query",\n "documents": [\n "document one",\n "document two"\n ]\n}'}
spellcheck={false}
></textarea>
/>
{#if jsonError}
<p class="mt-1 text-sm text-red-500">{jsonError}</p>
<p class="text-destructive mt-1 text-sm">{jsonError}</p>
{/if}
</div>
{:else}
<!-- Document table -->
<div class="flex-1 overflow-y-auto mb-4 border border-gray-200 dark:border-white/10 rounded">
<table class="w-full border-collapse table-fixed">
<div class="mb-4 flex-1 overflow-y-auto rounded-lg border">
<table class="w-full table-fixed border-collapse">
<colgroup>
<col class="w-auto" />
<col style="width: 120px" />
<col style="width: 40px" />
</colgroup>
<thead class="sticky top-0 bg-surface border-b border-gray-200 dark:border-white/10">
<thead class="bg-card sticky top-0 border-b">
<tr>
<th class="px-3 py-2 text-left text-sm font-medium text-txtsecondary">Document</th>
<th class="text-muted-foreground px-3 py-2 text-left text-sm font-medium">Document</th>
<th
class="px-3 py-2 text-right text-sm font-medium text-txtsecondary cursor-pointer select-none hover:text-txtprimary transition-colors"
class="text-muted-foreground hover:text-foreground cursor-pointer select-none px-3 py-2 text-right text-sm font-medium transition-colors"
onclick={cycleSortOrder}
>
Score{sortIndicator()}
@@ -339,11 +333,11 @@
</thead>
<tbody>
{#each displayRows as { row, i } (i)}
<tr class="border-b border-gray-100 dark:border-white/5 last:border-0">
<tr class="border-b last:border-0">
<td class="px-3 py-1.5">
<input
type="text"
class="w-full bg-transparent focus:outline-none focus:ring-1 focus:ring-primary rounded px-1 py-0.5"
class="focus:ring-ring w-full rounded bg-transparent px-1 py-0.5 outline-none focus:ring-1"
placeholder={i === rows.length - 1 ? "Add document..." : "Document text..."}
value={row.doc}
oninput={(e) => updateDoc(i, (e.target as HTMLInputElement).value)}
@@ -353,14 +347,14 @@
</td>
<td class="px-3 py-1.5 text-right font-mono text-sm {scoreColor(row.score)}">
{#if isLoading && row.score === null && row.doc.trim() !== ""}
<span class="inline-block w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin align-middle"></span>
<span class="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent align-middle"></span>
{:else}
{formatScore(row.score)}
{/if}
</td>
<td class="px-2 py-1.5 text-center">
<button
class="w-7 h-7 flex items-center justify-center text-txtsecondary hover:text-red-500 transition-colors rounded disabled:opacity-30 disabled:cursor-not-allowed"
class="text-muted-foreground hover:text-destructive flex h-7 w-7 items-center justify-center rounded transition-colors disabled:cursor-not-allowed disabled:opacity-30"
onclick={() => deleteRow(i)}
disabled={rows.length <= 1}
tabindex="-1"
@@ -378,28 +372,18 @@
<!-- Bottom toolbar -->
{#if hasModels}
<div class="shrink-0 flex flex-wrap items-center gap-2">
<div class="flex shrink-0 flex-wrap items-center gap-2">
{#if isLoading}
<button class="btn bg-red-500 hover:bg-red-600 text-white" onclick={cancel}>
Cancel
</button>
<Button variant="destructive" onclick={cancel}>Cancel</Button>
{:else}
<button
class="btn bg-primary text-btn-primary-text hover:opacity-90"
onclick={submit}
disabled={!canSubmit}
>
Rerank
</button>
<button class="btn" onclick={clear} disabled={isCleared}>
Clear
</button>
<Button onclick={submit} disabled={!canSubmit}>Rerank</Button>
<Button variant="outline" onclick={clear} disabled={isCleared}>Clear</Button>
{/if}
{#if error}
<span class="text-sm text-red-500 ml-2">{error}</span>
<span class="text-destructive ml-2 text-sm">{error}</span>
{:else if usage}
<span class="text-sm text-txtsecondary ml-2">{usage.total_tokens} tokens</span>
<span class="text-muted-foreground ml-2 text-sm">{usage.total_tokens} tokens</span>
{/if}
</div>
{/if}
@@ -209,7 +209,7 @@
<ModelSelector bind:value={$selectedModelStore} placeholder="Select a speech model..." disabled={isGenerating} capabilities={["audio_speech"]} />
<div class="flex gap-2">
<select
class="shrink-0 px-3 py-2 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
class="shrink-0 px-3 py-2 rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
value={$selectedVoiceStore}
onchange={handleVoiceChange}
disabled={isGenerating || isLoadingVoices || !$selectedModelStore}
@@ -243,14 +243,14 @@
<!-- Empty state for no models configured -->
{#if !hasModels}
<div class="flex-1 flex items-center justify-center text-txtsecondary">
<div class="flex-1 flex items-center justify-center text-muted-foreground">
<p>No models configured. Add models to your configuration to generate speech.</p>
</div>
{:else}
<!-- Audio display area -->
<div class="shrink-0 mb-4 bg-surface border border-gray-200 dark:border-white/10 rounded p-4 md:p-6">
<div class="shrink-0 mb-4 bg-background border border-border rounded p-4 md:p-6">
{#if isGenerating}
<div class="flex items-center justify-center text-txtsecondary py-8">
<div class="flex items-center justify-center text-muted-foreground py-8">
<div class="text-center">
<div class="inline-block w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin mb-2"></div>
<p>Generating speech...</p>
@@ -267,7 +267,7 @@
<div class="flex flex-col gap-4">
<!-- Header with metadata and download -->
<div class="flex items-center justify-between gap-4">
<div class="flex flex-wrap gap-3 text-sm text-txtsecondary">
<div class="flex flex-wrap gap-3 text-sm text-muted-foreground">
{#if generatedVoice}
<span class="flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -305,7 +305,7 @@
</div>
</div>
{:else}
<div class="flex items-center justify-center text-txtsecondary py-8">
<div class="flex items-center justify-center text-muted-foreground py-8">
<div class="text-center">
<svg class="w-12 h-12 md:w-16 md:h-16 mx-auto mb-2 opacity-40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"></path>
@@ -332,7 +332,7 @@
</button>
{:else}
<button
class="btn bg-primary text-btn-primary-text hover:opacity-90 flex-1 md:flex-none"
class="btn bg-primary text-primary-foreground hover:opacity-90 flex-1 md:flex-none"
onclick={generate}
disabled={!inputText.trim() || !$selectedModelStore}
>
+12 -12
View File
@@ -352,14 +352,14 @@
<div class="space-y-6">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold text-txtmain">Performance (Experimental)</h2>
<h2 class="text-xl font-semibold text-foreground">Performance (Experimental)</h2>
<div class="flex items-center gap-4">
<div class="flex items-center gap-1">
{#each WINDOWS as win, i}
<button
class="btn btn--sm"
class:bg-primary={$selectedWindow === i}
class:text-btn-primary-text={$selectedWindow === i}
class:text-primary-foreground={$selectedWindow === i}
onclick={() => ($selectedWindow = i)}
>
{win.label}
@@ -367,12 +367,12 @@
{/each}
</div>
<div class="flex items-center gap-1">
<span class="text-xs text-txtsecondary mr-1">Refresh:</span>
<span class="text-xs text-muted-foreground mr-1">Refresh:</span>
{#each INTERVALS as intv, i}
<button
class="btn btn--sm"
class:bg-primary={$selectedInterval === i}
class:text-btn-primary-text={$selectedInterval === i}
class:text-primary-foreground={$selectedInterval === i}
onclick={() => handleIntervalChange(i)}
>
{intv.label}
@@ -399,18 +399,18 @@
</button>
</div>
</div>
<p class="text-sm text-txtsecondary">
<p class="text-sm text-muted-foreground">
This is an experimental feature. Please use <a
class="underline hover:text-txtmain"
class="underline hover:text-foreground"
href="https://github.com/mostlygeek/llama-swap/discussions/771">discussion #771</a
> for instructions and to share feedback.
</p>
<!-- GPU Section -->
<section class="space-y-4">
<h3 class="text-lg font-medium text-txtmain">GPU</h3>
<h3 class="text-lg font-medium text-foreground">GPU</h3>
{#if !hasGpuData}
<p class="text-txtsecondary card p-4">No GPU data available</p>
<p class="text-muted-foreground card p-4">No GPU data available</p>
{:else}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<PerformanceChart
@@ -458,7 +458,7 @@
<!-- System Section -->
<section class="space-y-4">
<h3 class="text-lg font-medium text-txtmain">System</h3>
<h3 class="text-lg font-medium text-foreground">System</h3>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<PerformanceChart
title="CPU Utilization (%)"
@@ -479,15 +479,15 @@
yLabel="%"
/>
{#if latestMemSwap}
<div class="flex items-center justify-center gap-4 text-xs text-txtsecondary mt-1 px-4">
<div class="flex items-center justify-center gap-4 text-xs text-muted-foreground mt-1 px-4">
<span
>Mem: <span class="text-txtmain font-medium"
>Mem: <span class="text-foreground font-medium"
>{latestMemSwap.mem_used_mb.toLocaleString()} / {latestMemSwap.mem_total_mb.toLocaleString()} MB ({latestMemSwap.mem_used_pct}%)</span
></span
>
{#if latestMemSwap.swap_used_pct !== null}
<span
>Swap: <span class="text-txtmain font-medium"
>Swap: <span class="text-foreground font-medium"
>{latestMemSwap.swap_used_mb.toLocaleString()} / {latestMemSwap.swap_total_mb.toLocaleString()} MB ({latestMemSwap.swap_used_pct}%)</span
></span
>