Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Thomas G. Lopes
commited on
Commit
·
c2603a4
1
Parent(s):
2931ca0
some more work
Browse files
plan.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Layout Redesign Improvements Plan
|
| 2 |
+
|
| 3 |
+
## Issues to Fix
|
| 4 |
+
|
| 5 |
+
### Top Bar Adjustments
|
| 6 |
+
|
| 7 |
+
- [x] Move model and provider select to be the first items in the top bar
|
| 8 |
+
- [x] Remove project name from the top bar (redundant since it's selected in sidebar)
|
| 9 |
+
- [x] Move collapse sidebar button from top bar to the sidebar itself
|
| 10 |
+
|
| 11 |
+
### Sidebar Improvements
|
| 12 |
+
|
| 13 |
+
- [x] Make sidebar resizable (use example from visual playground branch)
|
| 14 |
+
- [x] Position collapse button on the sidebar side
|
| 15 |
+
|
| 16 |
+
### Layout Fixes
|
| 17 |
+
|
| 18 |
+
- [x] Fix "View docs" and "Give feedback" links overlapping with trash icon
|
| 19 |
+
- Moved links to bottom bar next to trash icon
|
| 20 |
+
|
| 21 |
+
## Additional Potential Issues & Improvements
|
| 22 |
+
|
| 23 |
+
### Usability
|
| 24 |
+
|
| 25 |
+
- [x] Add keyboard shortcut for collapsing sidebar (Cmd+B / Ctrl+B)
|
| 26 |
+
- [x] Persist sidebar collapsed state in localStorage
|
| 27 |
+
- [x] Persist sidebar width when resizable
|
| 28 |
+
|
| 29 |
+
### Visual Polish
|
| 30 |
+
|
| 31 |
+
- [x] Add transition animation when sidebar collapses/expands
|
| 32 |
+
- [ ] Ensure proper spacing between top bar elements
|
| 33 |
+
- [ ] Check dark mode styling consistency
|
| 34 |
+
|
| 35 |
+
### Responsive Design
|
| 36 |
+
|
| 37 |
+
- [ ] Test and fix mobile layout
|
| 38 |
+
- [ ] Ensure settings popover doesn't overflow on smaller screens
|
| 39 |
+
- [ ] Handle long project names in sidebar gracefully
|
| 40 |
+
|
| 41 |
+
### Functionality
|
| 42 |
+
|
| 43 |
+
- [ ] Ensure system prompt changes are saved properly
|
| 44 |
+
- [ ] Verify model/provider selection works in new top bar position
|
| 45 |
+
- [ ] Test compare mode with new layout
|
| 46 |
+
|
| 47 |
+
### Accessibility
|
| 48 |
+
|
| 49 |
+
- [ ] Add proper ARIA labels for interactive elements
|
| 50 |
+
- [ ] Ensure keyboard navigation works properly
|
| 51 |
+
- [ ] Test with screen readers
|
| 52 |
+
|
| 53 |
+
## Implementation Order
|
| 54 |
+
|
| 55 |
+
1. **Top Bar Reorganization** (First Priority)
|
| 56 |
+
|
| 57 |
+
- Move model/provider select to first position
|
| 58 |
+
- Remove project name
|
| 59 |
+
- Relocate sidebar toggle
|
| 60 |
+
|
| 61 |
+
2. **Resizable Sidebar** (Second Priority)
|
| 62 |
+
|
| 63 |
+
- Implement resize functionality
|
| 64 |
+
- Add persistence
|
| 65 |
+
|
| 66 |
+
3. **Fix Overlapping Elements** (Third Priority)
|
| 67 |
+
|
| 68 |
+
- Resolve footer links position issue
|
| 69 |
+
|
| 70 |
+
4. **Polish & Testing** (Final)
|
| 71 |
+
- Add animations
|
| 72 |
+
- Test all functionality
|
| 73 |
+
- Fix any remaining issues
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
**Current Status**: Resizable sidebar with persistence implemented. Provider select now visible in top bar alongside model select.
|
src/lib/components/inference-playground/playground.svelte
CHANGED
|
@@ -5,7 +5,9 @@
|
|
| 5 |
import { isHFModel } from "$lib/types.js";
|
| 6 |
import { iterate } from "$lib/utils/array.js";
|
| 7 |
import { atLeastNDecimals } from "$lib/utils/number.js";
|
|
|
|
| 8 |
import { Popover } from "melt/builders";
|
|
|
|
| 9 |
import IconExternal from "~icons/carbon/arrow-up-right";
|
| 10 |
import IconWaterfall from "~icons/carbon/chart-waterfall";
|
| 11 |
import IconCode from "~icons/carbon/code";
|
|
@@ -13,8 +15,6 @@
|
|
| 13 |
import IconInfo from "~icons/carbon/information";
|
| 14 |
import IconSettings from "~icons/carbon/settings";
|
| 15 |
import IconShare from "~icons/carbon/share";
|
| 16 |
-
import IconSidebarCollapse from "~icons/carbon/side-panel-close";
|
| 17 |
-
import IconSidebarExpand from "~icons/carbon/side-panel-open";
|
| 18 |
import { default as IconDelete } from "~icons/carbon/trash-can";
|
| 19 |
import BillingIndicator from "../billing-indicator.svelte";
|
| 20 |
import { showShareModal } from "../share-modal.svelte";
|
|
@@ -27,17 +27,65 @@
|
|
| 27 |
import MessageTextarea from "./message-textarea.svelte";
|
| 28 |
import ModelSelectorModal from "./model-selector-modal.svelte";
|
| 29 |
import ModelSelector from "./model-selector.svelte";
|
|
|
|
| 30 |
import ProjectTreeSidebar from "./project-tree-sidebar.svelte";
|
| 31 |
import CheckpointsMenu from "./checkpoints-menu.svelte";
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
let viewCode = $state(false);
|
| 34 |
-
let sidebarCollapsed = $state(
|
|
|
|
| 35 |
let billingModalOpen = $state(false);
|
| 36 |
let selectCompareModelOpen = $state(false);
|
| 37 |
let settingsPopoverOpen = $state(false);
|
| 38 |
|
| 39 |
const compareActive = $derived(conversations.active.length === 2);
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
// Settings popover
|
| 42 |
const settingsPopover = new Popover({
|
| 43 |
open: () => settingsPopoverOpen,
|
|
@@ -54,56 +102,44 @@
|
|
| 54 |
]}
|
| 55 |
>
|
| 56 |
<!-- Project tree sidebar -->
|
| 57 |
-
<ProjectTreeSidebar
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
<!-- Main content area -->
|
| 60 |
<div class="relative flex flex-1 flex-col overflow-hidden">
|
| 61 |
<!-- Top bar -->
|
| 62 |
<header
|
| 63 |
-
class="flex
|
| 64 |
>
|
|
|
|
| 65 |
<div class="flex items-center gap-3">
|
| 66 |
-
<!-- Sidebar toggle -->
|
| 67 |
-
<Tooltip>
|
| 68 |
-
{#snippet trigger(tooltip)}
|
| 69 |
-
<button
|
| 70 |
-
onclick={() => (sidebarCollapsed = !sidebarCollapsed)}
|
| 71 |
-
class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
|
| 72 |
-
aria-label={sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
| 73 |
-
{...tooltip.trigger}
|
| 74 |
-
>
|
| 75 |
-
{#if sidebarCollapsed}
|
| 76 |
-
<IconSidebarExpand class="size-5" />
|
| 77 |
-
{:else}
|
| 78 |
-
<IconSidebarCollapse class="size-5" />
|
| 79 |
-
{/if}
|
| 80 |
-
</button>
|
| 81 |
-
{/snippet}
|
| 82 |
-
{sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
| 83 |
-
</Tooltip>
|
| 84 |
-
|
| 85 |
-
<!-- Project name and checkpoints -->
|
| 86 |
-
<div class="flex items-center gap-2">
|
| 87 |
-
<span class="text-sm font-semibold">{projects.current?.name}</span>
|
| 88 |
-
<CheckpointsMenu />
|
| 89 |
-
</div>
|
| 90 |
-
</div>
|
| 91 |
-
|
| 92 |
-
<!-- Right side of top bar -->
|
| 93 |
-
<div class="flex items-center gap-3">
|
| 94 |
-
<!-- Model selector -->
|
| 95 |
{#if !compareActive && conversations.active[0]}
|
| 96 |
-
|
|
|
|
| 97 |
<ModelSelector conversation={conversations.active[0]} compact />
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
<IconCompare class="size-4" />
|
| 103 |
-
Compare
|
| 104 |
-
</button>
|
| 105 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
<!-- Settings button with popover -->
|
| 109 |
<Tooltip>
|
|
@@ -165,7 +201,7 @@
|
|
| 165 |
<div
|
| 166 |
class="relative flex h-12 shrink-0 items-center justify-between border-t border-gray-200 bg-white px-4 dark:border-gray-800 dark:bg-gray-900"
|
| 167 |
>
|
| 168 |
-
<div class="flex items-center gap-
|
| 169 |
<Tooltip>
|
| 170 |
{#snippet trigger(tooltip)}
|
| 171 |
<button
|
|
@@ -180,11 +216,40 @@
|
|
| 180 |
{/snippet}
|
| 181 |
Clear conversation
|
| 182 |
</Tooltip>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
</div>
|
| 184 |
|
| 185 |
<!-- Stats in center -->
|
| 186 |
<div
|
| 187 |
-
class="pointer-events-none absolute inset-x-0 flex items-center justify-center gap-x-8 text-center text-sm text-gray-500 max-
|
| 188 |
>
|
| 189 |
{#each iterate(conversations.generationStats) as [{ latency, tokens, cost }]}
|
| 190 |
<span>
|
|
@@ -201,35 +266,6 @@
|
|
| 201 |
</div>
|
| 202 |
</div>
|
| 203 |
</div>
|
| 204 |
-
|
| 205 |
-
<!-- Footer links -->
|
| 206 |
-
<div class="absolute bottom-3 left-4 flex items-center gap-2 text-xs">
|
| 207 |
-
<a
|
| 208 |
-
target="_blank"
|
| 209 |
-
href="https://huggingface.co/docs/inference-providers/tasks/chat-completion"
|
| 210 |
-
class="flex items-center gap-1 text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 211 |
-
>
|
| 212 |
-
<IconInfo class="size-3" />
|
| 213 |
-
View Docs
|
| 214 |
-
</a>
|
| 215 |
-
<span class="text-gray-500 dark:text-gray-500">·</span>
|
| 216 |
-
<a
|
| 217 |
-
target="_blank"
|
| 218 |
-
href="https://huggingface.co/spaces/huggingface/inference-playground/discussions/1"
|
| 219 |
-
class="flex items-center gap-1 text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 220 |
-
>
|
| 221 |
-
Give feedback
|
| 222 |
-
</a>
|
| 223 |
-
<span class="text-gray-500 dark:text-gray-500">·</span>
|
| 224 |
-
<a
|
| 225 |
-
href="https://huggingface.co/inference/models"
|
| 226 |
-
target="_blank"
|
| 227 |
-
class="flex items-center gap-1 text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 228 |
-
>
|
| 229 |
-
<IconWaterfall class="size-3" />
|
| 230 |
-
Metrics
|
| 231 |
-
</a>
|
| 232 |
-
</div>
|
| 233 |
</div>
|
| 234 |
</div>
|
| 235 |
|
|
|
|
| 5 |
import { isHFModel } from "$lib/types.js";
|
| 6 |
import { iterate } from "$lib/utils/array.js";
|
| 7 |
import { atLeastNDecimals } from "$lib/utils/number.js";
|
| 8 |
+
import { isMac } from "$lib/utils/platform.js";
|
| 9 |
import { Popover } from "melt/builders";
|
| 10 |
+
import { onMount } from "svelte";
|
| 11 |
import IconExternal from "~icons/carbon/arrow-up-right";
|
| 12 |
import IconWaterfall from "~icons/carbon/chart-waterfall";
|
| 13 |
import IconCode from "~icons/carbon/code";
|
|
|
|
| 15 |
import IconInfo from "~icons/carbon/information";
|
| 16 |
import IconSettings from "~icons/carbon/settings";
|
| 17 |
import IconShare from "~icons/carbon/share";
|
|
|
|
|
|
|
| 18 |
import { default as IconDelete } from "~icons/carbon/trash-can";
|
| 19 |
import BillingIndicator from "../billing-indicator.svelte";
|
| 20 |
import { showShareModal } from "../share-modal.svelte";
|
|
|
|
| 27 |
import MessageTextarea from "./message-textarea.svelte";
|
| 28 |
import ModelSelectorModal from "./model-selector-modal.svelte";
|
| 29 |
import ModelSelector from "./model-selector.svelte";
|
| 30 |
+
import ProviderSelect from "./provider-select.svelte";
|
| 31 |
import ProjectTreeSidebar from "./project-tree-sidebar.svelte";
|
| 32 |
import CheckpointsMenu from "./checkpoints-menu.svelte";
|
| 33 |
|
| 34 |
+
// LocalStorage keys
|
| 35 |
+
const SIDEBAR_COLLAPSED_KEY = "playground:sidebar:collapsed";
|
| 36 |
+
const SIDEBAR_WIDTH_KEY = "playground:sidebar:width";
|
| 37 |
+
const DEFAULT_SIDEBAR_WIDTH = 256;
|
| 38 |
+
|
| 39 |
+
// Initialize from localStorage or defaults
|
| 40 |
+
function get_initial_collapsed(): boolean {
|
| 41 |
+
if (typeof localStorage === "undefined") return false;
|
| 42 |
+
const stored = localStorage.getItem(SIDEBAR_COLLAPSED_KEY);
|
| 43 |
+
return stored === "true";
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
function get_initial_width(): number {
|
| 47 |
+
if (typeof localStorage === "undefined") return DEFAULT_SIDEBAR_WIDTH;
|
| 48 |
+
const stored = localStorage.getItem(SIDEBAR_WIDTH_KEY);
|
| 49 |
+
const parsed = stored ? parseInt(stored, 10) : NaN;
|
| 50 |
+
return isNaN(parsed) ? DEFAULT_SIDEBAR_WIDTH : parsed;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
let viewCode = $state(false);
|
| 54 |
+
let sidebarCollapsed = $state(get_initial_collapsed());
|
| 55 |
+
let sidebarWidth = $state(get_initial_width());
|
| 56 |
let billingModalOpen = $state(false);
|
| 57 |
let selectCompareModelOpen = $state(false);
|
| 58 |
let settingsPopoverOpen = $state(false);
|
| 59 |
|
| 60 |
const compareActive = $derived(conversations.active.length === 2);
|
| 61 |
|
| 62 |
+
// Persist sidebar state to localStorage
|
| 63 |
+
function toggle_sidebar_collapsed() {
|
| 64 |
+
sidebarCollapsed = !sidebarCollapsed;
|
| 65 |
+
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(sidebarCollapsed));
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
function handle_sidebar_width_change(width: number) {
|
| 69 |
+
sidebarWidth = width;
|
| 70 |
+
localStorage.setItem(SIDEBAR_WIDTH_KEY, String(width));
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// Keyboard shortcut for toggling sidebar (Cmd+B / Ctrl+B)
|
| 74 |
+
function handle_keydown(e: KeyboardEvent) {
|
| 75 |
+
const mod_key = isMac() ? e.metaKey : e.ctrlKey;
|
| 76 |
+
if (mod_key && e.key === "b") {
|
| 77 |
+
e.preventDefault();
|
| 78 |
+
toggle_sidebar_collapsed();
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
onMount(() => {
|
| 83 |
+
document.addEventListener("keydown", handle_keydown);
|
| 84 |
+
return () => {
|
| 85 |
+
document.removeEventListener("keydown", handle_keydown);
|
| 86 |
+
};
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
// Settings popover
|
| 90 |
const settingsPopover = new Popover({
|
| 91 |
open: () => settingsPopoverOpen,
|
|
|
|
| 102 |
]}
|
| 103 |
>
|
| 104 |
<!-- Project tree sidebar -->
|
| 105 |
+
<ProjectTreeSidebar
|
| 106 |
+
collapsed={sidebarCollapsed}
|
| 107 |
+
width={sidebarWidth}
|
| 108 |
+
onToggleCollapse={toggle_sidebar_collapsed}
|
| 109 |
+
onWidthChange={handle_sidebar_width_change}
|
| 110 |
+
/>
|
| 111 |
|
| 112 |
<!-- Main content area -->
|
| 113 |
<div class="relative flex flex-1 flex-col overflow-hidden">
|
| 114 |
<!-- Top bar -->
|
| 115 |
<header
|
| 116 |
+
class="flex items-center justify-between border-b border-gray-200 bg-white px-4 py-2 dark:border-gray-800 dark:bg-gray-900"
|
| 117 |
>
|
| 118 |
+
<!-- Left side: Model selector, provider selector, and compare -->
|
| 119 |
<div class="flex items-center gap-3">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
{#if !compareActive && conversations.active[0]}
|
| 121 |
+
<!-- Model and provider stacked vertically -->
|
| 122 |
+
<div class="flex flex-col gap-1">
|
| 123 |
<ModelSelector conversation={conversations.active[0]} compact />
|
| 124 |
+
{#if isHFModel(conversations.active[0].model)}
|
| 125 |
+
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
|
| 126 |
+
<ProviderSelect conversation={conversations.active[0] as any} compact />
|
| 127 |
+
{/if}
|
|
|
|
|
|
|
|
|
|
| 128 |
</div>
|
| 129 |
+
<button
|
| 130 |
+
class="flex items-center gap-1 rounded px-2 py-1 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-300"
|
| 131 |
+
onclick={() => (selectCompareModelOpen = true)}
|
| 132 |
+
>
|
| 133 |
+
<IconCompare class="size-4" />
|
| 134 |
+
Compare
|
| 135 |
+
</button>
|
| 136 |
{/if}
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<!-- Right side: Actions -->
|
| 140 |
+
<div class="flex items-center gap-3">
|
| 141 |
+
<!-- Checkpoints menu -->
|
| 142 |
+
<CheckpointsMenu />
|
| 143 |
|
| 144 |
<!-- Settings button with popover -->
|
| 145 |
<Tooltip>
|
|
|
|
| 201 |
<div
|
| 202 |
class="relative flex h-12 shrink-0 items-center justify-between border-t border-gray-200 bg-white px-4 dark:border-gray-800 dark:bg-gray-900"
|
| 203 |
>
|
| 204 |
+
<div class="flex items-center gap-4">
|
| 205 |
<Tooltip>
|
| 206 |
{#snippet trigger(tooltip)}
|
| 207 |
<button
|
|
|
|
| 216 |
{/snippet}
|
| 217 |
Clear conversation
|
| 218 |
</Tooltip>
|
| 219 |
+
|
| 220 |
+
<!-- Footer links - moved here to avoid overlap -->
|
| 221 |
+
<div class="flex items-center gap-2 text-xs max-md:hidden">
|
| 222 |
+
<a
|
| 223 |
+
target="_blank"
|
| 224 |
+
href="https://huggingface.co/docs/inference-providers/tasks/chat-completion"
|
| 225 |
+
class="flex items-center gap-1 text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 226 |
+
>
|
| 227 |
+
<IconInfo class="size-3" />
|
| 228 |
+
Docs
|
| 229 |
+
</a>
|
| 230 |
+
<span class="text-gray-400 dark:text-gray-600">·</span>
|
| 231 |
+
<a
|
| 232 |
+
target="_blank"
|
| 233 |
+
href="https://huggingface.co/spaces/huggingface/inference-playground/discussions/1"
|
| 234 |
+
class="text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 235 |
+
>
|
| 236 |
+
Feedback
|
| 237 |
+
</a>
|
| 238 |
+
<span class="text-gray-400 dark:text-gray-600">·</span>
|
| 239 |
+
<a
|
| 240 |
+
href="https://huggingface.co/inference/models"
|
| 241 |
+
target="_blank"
|
| 242 |
+
class="flex items-center gap-1 text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 243 |
+
>
|
| 244 |
+
<IconWaterfall class="size-3" />
|
| 245 |
+
Metrics
|
| 246 |
+
</a>
|
| 247 |
+
</div>
|
| 248 |
</div>
|
| 249 |
|
| 250 |
<!-- Stats in center -->
|
| 251 |
<div
|
| 252 |
+
class="pointer-events-none absolute inset-x-0 flex items-center justify-center gap-x-8 text-center text-sm text-gray-500 max-xl:hidden"
|
| 253 |
>
|
| 254 |
{#each iterate(conversations.generationStats) as [{ latency, tokens, cost }]}
|
| 255 |
<span>
|
|
|
|
| 266 |
</div>
|
| 267 |
</div>
|
| 268 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
</div>
|
| 270 |
</div>
|
| 271 |
|
src/lib/components/inference-playground/project-tree-sidebar.svelte
CHANGED
|
@@ -15,7 +15,10 @@
|
|
| 15 |
import IconEdit from "~icons/carbon/edit";
|
| 16 |
import IconDelete from "~icons/carbon/trash-can";
|
| 17 |
import IconHistory from "~icons/carbon/recently-viewed";
|
|
|
|
|
|
|
| 18 |
import { prompt } from "../prompts.svelte";
|
|
|
|
| 19 |
|
| 20 |
interface ProjectTreeItem extends TreeItem {
|
| 21 |
id: string;
|
|
@@ -24,11 +27,50 @@
|
|
| 24 |
children?: ProjectTreeItem[];
|
| 25 |
}
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
interface Props {
|
| 28 |
collapsed?: boolean;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
// Build tree structure from projects
|
| 34 |
const tree_items = $derived.by((): ProjectTreeItem[] => {
|
|
@@ -142,20 +184,42 @@
|
|
| 142 |
|
| 143 |
<aside
|
| 144 |
class={cn(
|
| 145 |
-
"flex h-full flex-col overflow-hidden border-r border-gray-200 bg-gray-50/50
|
| 146 |
-
|
| 147 |
)}
|
|
|
|
| 148 |
>
|
| 149 |
{#if !collapsed}
|
| 150 |
-
<div class="flex items-center justify-between border-b border-gray-200 px-
|
| 151 |
<h2 class="text-sm font-semibold text-gray-700 uppercase dark:text-gray-300">Projects</h2>
|
| 152 |
-
<
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
</div>
|
| 160 |
|
| 161 |
<div class="flex-1 overflow-y-auto p-2" {...tree.root}>
|
|
@@ -175,30 +239,59 @@
|
|
| 175 |
{/if}
|
| 176 |
</div>
|
| 177 |
{:else}
|
| 178 |
-
<!-- Collapsed state -
|
| 179 |
-
<div class="flex flex-col items-center
|
| 180 |
-
|
| 181 |
-
{
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
{
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
</div>
|
| 201 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
</aside>
|
| 203 |
|
| 204 |
{#snippet tree_node(items: typeof tree.children)}
|
|
|
|
| 15 |
import IconEdit from "~icons/carbon/edit";
|
| 16 |
import IconDelete from "~icons/carbon/trash-can";
|
| 17 |
import IconHistory from "~icons/carbon/recently-viewed";
|
| 18 |
+
import IconSidebarCollapse from "~icons/carbon/side-panel-close";
|
| 19 |
+
import IconSidebarExpand from "~icons/carbon/side-panel-open";
|
| 20 |
import { prompt } from "../prompts.svelte";
|
| 21 |
+
import Tooltip from "../tooltip.svelte";
|
| 22 |
|
| 23 |
interface ProjectTreeItem extends TreeItem {
|
| 24 |
id: string;
|
|
|
|
| 27 |
children?: ProjectTreeItem[];
|
| 28 |
}
|
| 29 |
|
| 30 |
+
const MIN_WIDTH = 200;
|
| 31 |
+
const MAX_WIDTH = 400;
|
| 32 |
+
const DEFAULT_WIDTH = 256;
|
| 33 |
+
const COLLAPSED_WIDTH = 48;
|
| 34 |
+
|
| 35 |
interface Props {
|
| 36 |
collapsed?: boolean;
|
| 37 |
+
width?: number;
|
| 38 |
+
onToggleCollapse?: () => void;
|
| 39 |
+
onWidthChange?: (width: number) => void;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
let { collapsed = false, width = DEFAULT_WIDTH, onToggleCollapse, onWidthChange }: Props = $props();
|
| 43 |
+
|
| 44 |
+
// Resize state
|
| 45 |
+
let is_resizing = $state(false);
|
| 46 |
+
let resize_start_x = $state(0);
|
| 47 |
+
let resize_start_width = $state(0);
|
| 48 |
+
|
| 49 |
+
function handle_resize_start(e: MouseEvent) {
|
| 50 |
+
if (collapsed) return;
|
| 51 |
+
is_resizing = true;
|
| 52 |
+
resize_start_x = e.clientX;
|
| 53 |
+
resize_start_width = width;
|
| 54 |
+
document.addEventListener("mousemove", handle_resize_move);
|
| 55 |
+
document.addEventListener("mouseup", handle_resize_end);
|
| 56 |
+
document.body.style.cursor = "ew-resize";
|
| 57 |
+
document.body.style.userSelect = "none";
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
function handle_resize_move(e: MouseEvent) {
|
| 61 |
+
if (!is_resizing) return;
|
| 62 |
+
const delta = e.clientX - resize_start_x;
|
| 63 |
+
const new_width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, resize_start_width + delta));
|
| 64 |
+
onWidthChange?.(new_width);
|
| 65 |
}
|
| 66 |
|
| 67 |
+
function handle_resize_end() {
|
| 68 |
+
is_resizing = false;
|
| 69 |
+
document.removeEventListener("mousemove", handle_resize_move);
|
| 70 |
+
document.removeEventListener("mouseup", handle_resize_end);
|
| 71 |
+
document.body.style.cursor = "";
|
| 72 |
+
document.body.style.userSelect = "";
|
| 73 |
+
}
|
| 74 |
|
| 75 |
// Build tree structure from projects
|
| 76 |
const tree_items = $derived.by((): ProjectTreeItem[] => {
|
|
|
|
| 184 |
|
| 185 |
<aside
|
| 186 |
class={cn(
|
| 187 |
+
"relative flex h-full flex-col overflow-hidden border-r border-gray-200 bg-gray-50/50 dark:border-gray-800 dark:bg-gray-900/50",
|
| 188 |
+
!is_resizing && "transition-[width] duration-200 ease-out",
|
| 189 |
)}
|
| 190 |
+
style="width: {collapsed ? COLLAPSED_WIDTH : width}px"
|
| 191 |
>
|
| 192 |
{#if !collapsed}
|
| 193 |
+
<div class="flex items-center justify-between border-b border-gray-200 px-3 py-2 dark:border-gray-800">
|
| 194 |
<h2 class="text-sm font-semibold text-gray-700 uppercase dark:text-gray-300">Projects</h2>
|
| 195 |
+
<div class="flex items-center gap-1">
|
| 196 |
+
<Tooltip>
|
| 197 |
+
{#snippet trigger(tooltip)}
|
| 198 |
+
<button
|
| 199 |
+
onclick={handle_new_project}
|
| 200 |
+
class="rounded p-1 text-gray-500 hover:bg-gray-200 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200"
|
| 201 |
+
aria-label="New project"
|
| 202 |
+
{...tooltip.trigger}
|
| 203 |
+
>
|
| 204 |
+
<IconPlus class="size-4" />
|
| 205 |
+
</button>
|
| 206 |
+
{/snippet}
|
| 207 |
+
New project
|
| 208 |
+
</Tooltip>
|
| 209 |
+
<Tooltip>
|
| 210 |
+
{#snippet trigger(tooltip)}
|
| 211 |
+
<button
|
| 212 |
+
onclick={onToggleCollapse}
|
| 213 |
+
class="rounded p-1 text-gray-500 hover:bg-gray-200 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200"
|
| 214 |
+
aria-label="Collapse sidebar"
|
| 215 |
+
{...tooltip.trigger}
|
| 216 |
+
>
|
| 217 |
+
<IconSidebarCollapse class="size-4" />
|
| 218 |
+
</button>
|
| 219 |
+
{/snippet}
|
| 220 |
+
Collapse sidebar
|
| 221 |
+
</Tooltip>
|
| 222 |
+
</div>
|
| 223 |
</div>
|
| 224 |
|
| 225 |
<div class="flex-1 overflow-y-auto p-2" {...tree.root}>
|
|
|
|
| 239 |
{/if}
|
| 240 |
</div>
|
| 241 |
{:else}
|
| 242 |
+
<!-- Collapsed state - show expand button and project icons -->
|
| 243 |
+
<div class="flex flex-col items-center py-2">
|
| 244 |
+
<Tooltip>
|
| 245 |
+
{#snippet trigger(tooltip)}
|
| 246 |
+
<button
|
| 247 |
+
onclick={onToggleCollapse}
|
| 248 |
+
class="mb-2 grid size-8 place-items-center rounded-md text-gray-500 hover:bg-gray-200 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200"
|
| 249 |
+
aria-label="Expand sidebar"
|
| 250 |
+
{...tooltip.trigger}
|
| 251 |
+
>
|
| 252 |
+
<IconSidebarExpand class="size-4" />
|
| 253 |
+
</button>
|
| 254 |
+
{/snippet}
|
| 255 |
+
Expand sidebar
|
| 256 |
+
</Tooltip>
|
| 257 |
+
<div class="h-px w-6 bg-gray-200 dark:bg-gray-700"></div>
|
| 258 |
+
<div class="mt-2 flex flex-col items-center gap-1">
|
| 259 |
+
{#each tree_items as item}
|
| 260 |
+
{@const is_active = tree.isSelected(item.id)}
|
| 261 |
+
{@const is_branch = item.project.branchedFromId !== null}
|
| 262 |
+
<button
|
| 263 |
+
onclick={() => tree.toggleSelect(item.id)}
|
| 264 |
+
class={cn(
|
| 265 |
+
"grid size-8 place-items-center rounded-md transition-colors",
|
| 266 |
+
is_active
|
| 267 |
+
? "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
| 268 |
+
: "text-gray-600 hover:bg-gray-200 dark:text-gray-400 dark:hover:bg-gray-700",
|
| 269 |
+
)}
|
| 270 |
+
title={item.project.name}
|
| 271 |
+
>
|
| 272 |
+
{#if is_branch}
|
| 273 |
+
<IconBranch class="size-4" />
|
| 274 |
+
{:else}
|
| 275 |
+
<IconFolder class="size-4" />
|
| 276 |
+
{/if}
|
| 277 |
+
</button>
|
| 278 |
+
{/each}
|
| 279 |
+
</div>
|
| 280 |
</div>
|
| 281 |
{/if}
|
| 282 |
+
|
| 283 |
+
<!-- Resize handle -->
|
| 284 |
+
{#if !collapsed}
|
| 285 |
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
| 286 |
+
<div
|
| 287 |
+
class={cn(
|
| 288 |
+
"absolute top-0 right-0 h-full w-1 cursor-ew-resize transition-colors",
|
| 289 |
+
"hover:bg-blue-400 dark:hover:bg-blue-500",
|
| 290 |
+
is_resizing && "bg-blue-500 dark:bg-blue-400",
|
| 291 |
+
)}
|
| 292 |
+
onmousedown={handle_resize_start}
|
| 293 |
+
></div>
|
| 294 |
+
{/if}
|
| 295 |
</aside>
|
| 296 |
|
| 297 |
{#snippet tree_node(items: typeof tree.children)}
|
src/lib/components/inference-playground/provider-select.svelte
CHANGED
|
@@ -13,9 +13,10 @@
|
|
| 13 |
interface Props {
|
| 14 |
conversation: ConversationClass & { model: Model };
|
| 15 |
class?: string | undefined;
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
-
const { conversation, class: classes = undefined }: Props = $props();
|
| 19 |
|
| 20 |
function reset(providers: typeof conversation.model.inferenceProviderMapping) {
|
| 21 |
const validProvider = providers.find(p => p.provider === conversation.data.provider);
|
|
@@ -102,14 +103,14 @@
|
|
| 102 |
});
|
| 103 |
</script>
|
| 104 |
|
| 105 |
-
{#snippet providerDisplay(provider: string)}
|
| 106 |
{@const providerPricing = getProviderPricing(provider)}
|
| 107 |
<div class="flex flex-col items-start gap-0.5">
|
| 108 |
<div class="flex items-center gap-2 text-sm">
|
| 109 |
<IconProvider {provider} />
|
| 110 |
<span>{getProviderName(provider) ?? "loading"}</span>
|
| 111 |
</div>
|
| 112 |
-
{#if providerPricing}
|
| 113 |
<span class="text-xs text-gray-500 dark:text-gray-400">
|
| 114 |
In: {providerPricing.input} • Out: {providerPricing.output}
|
| 115 |
</span>
|
|
@@ -117,68 +118,70 @@
|
|
| 117 |
</div>
|
| 118 |
{/snippet}
|
| 119 |
|
| 120 |
-
|
| 121 |
-
<
|
| 122 |
-
{
|
| 123 |
-
class={
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
>
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
| 144 |
</div>
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
{
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
</div>
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
<
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
>
|
| 173 |
-
{autoPolicyLabels[autoPolicyValue]}
|
| 174 |
-
<div
|
| 175 |
-
class="absolute right-2 grid size-4 flex-none place-items-center rounded-sm bg-gray-100 text-xs dark:bg-gray-600"
|
| 176 |
-
>
|
| 177 |
-
<IconCaret />
|
| 178 |
-
</div>
|
| 179 |
-
</button>
|
| 180 |
|
| 181 |
-
<div
|
|
|
|
|
|
|
|
|
|
| 182 |
{#snippet policyOption(policy: "default" | "fastest" | "cheapest", label: string)}
|
| 183 |
<div {...autoPolicySelect.getOption(policy)} class="group block w-full p-1 text-sm dark:text-white">
|
| 184 |
<div
|
|
@@ -197,6 +200,91 @@
|
|
| 197 |
{@render policyOption("fastest", "Fastest")}
|
| 198 |
{@render policyOption("cheapest", "Cheapest")}
|
| 199 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
</div>
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
interface Props {
|
| 14 |
conversation: ConversationClass & { model: Model };
|
| 15 |
class?: string | undefined;
|
| 16 |
+
compact?: boolean;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
const { conversation, class: classes = undefined, compact = false }: Props = $props();
|
| 20 |
|
| 21 |
function reset(providers: typeof conversation.model.inferenceProviderMapping) {
|
| 22 |
const validProvider = providers.find(p => p.provider === conversation.data.provider);
|
|
|
|
| 103 |
});
|
| 104 |
</script>
|
| 105 |
|
| 106 |
+
{#snippet providerDisplay(provider: string, showPricing: boolean = true)}
|
| 107 |
{@const providerPricing = getProviderPricing(provider)}
|
| 108 |
<div class="flex flex-col items-start gap-0.5">
|
| 109 |
<div class="flex items-center gap-2 text-sm">
|
| 110 |
<IconProvider {provider} />
|
| 111 |
<span>{getProviderName(provider) ?? "loading"}</span>
|
| 112 |
</div>
|
| 113 |
+
{#if showPricing && providerPricing}
|
| 114 |
<span class="text-xs text-gray-500 dark:text-gray-400">
|
| 115 |
In: {providerPricing.input} • Out: {providerPricing.output}
|
| 116 |
</span>
|
|
|
|
| 118 |
</div>
|
| 119 |
{/snippet}
|
| 120 |
|
| 121 |
+
{#snippet compactProviderDisplay(provider: string)}
|
| 122 |
+
<div class="flex items-center gap-1.5">
|
| 123 |
+
<IconProvider {provider} />
|
| 124 |
+
<span class="text-sm">{getProviderName(provider) ?? "loading"}</span>
|
| 125 |
+
</div>
|
| 126 |
+
{/snippet}
|
| 127 |
+
|
| 128 |
+
{#if compact}
|
| 129 |
+
<!-- Compact mode for top bar - provider and auto policy side by side -->
|
| 130 |
+
<div class="flex items-center gap-2">
|
| 131 |
+
<!-- Provider select -->
|
| 132 |
+
<button
|
| 133 |
+
{...select.trigger}
|
| 134 |
+
class={cn(
|
| 135 |
+
"focus-outline relative flex items-center gap-2 overflow-hidden rounded-lg border",
|
| 136 |
+
"bg-gray-100/80 px-3 py-1.5 text-sm leading-tight whitespace-nowrap shadow-sm",
|
| 137 |
+
"hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
|
| 138 |
+
classes,
|
| 139 |
+
)}
|
| 140 |
>
|
| 141 |
+
{@render compactProviderDisplay(conversation.data.provider ?? "")}
|
| 142 |
+
<IconCaret class="size-4 flex-none text-gray-500" />
|
| 143 |
+
</button>
|
| 144 |
|
| 145 |
+
<div {...select.content} class="z-50 rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
|
| 146 |
+
{#snippet option(provider: string)}
|
| 147 |
+
<div {...select.getOption(provider)} class="group block w-full p-1 text-sm dark:text-white">
|
| 148 |
+
<div
|
| 149 |
+
class="rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
| 150 |
+
>
|
| 151 |
+
{@render providerDisplay(provider)}
|
| 152 |
+
</div>
|
| 153 |
</div>
|
| 154 |
+
{/snippet}
|
| 155 |
+
{#each conversation.model.inferenceProviderMapping as { provider, providerId } (provider + providerId)}
|
| 156 |
+
{@render option(provider)}
|
| 157 |
+
{/each}
|
| 158 |
+
{@render option("auto")}
|
| 159 |
+
</div>
|
|
|
|
| 160 |
|
| 161 |
+
<!-- Auto policy select (only shown when provider is auto) -->
|
| 162 |
+
{#if conversation.data.provider === "auto"}
|
| 163 |
+
<Tooltip>
|
| 164 |
+
{#snippet trigger(tooltip)}
|
| 165 |
+
<button
|
| 166 |
+
{...autoPolicySelect.trigger}
|
| 167 |
+
class={cn(
|
| 168 |
+
"focus-outline relative flex items-center gap-2 overflow-hidden rounded-lg border",
|
| 169 |
+
"bg-gray-100/80 px-3 py-1.5 text-sm leading-tight whitespace-nowrap shadow-sm",
|
| 170 |
+
"hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
|
| 171 |
+
)}
|
| 172 |
+
{...tooltip.trigger}
|
| 173 |
+
>
|
| 174 |
+
{autoPolicyLabels[autoPolicyValue]}
|
| 175 |
+
<IconCaret class="size-4 flex-none text-gray-500" />
|
| 176 |
+
</button>
|
| 177 |
+
{/snippet}
|
| 178 |
+
{autoPolicyDescriptions[autoPolicyValue]}
|
| 179 |
+
</Tooltip>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
<div
|
| 182 |
+
{...autoPolicySelect.content}
|
| 183 |
+
class="z-50 rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800"
|
| 184 |
+
>
|
| 185 |
{#snippet policyOption(policy: "default" | "fastest" | "cheapest", label: string)}
|
| 186 |
<div {...autoPolicySelect.getOption(policy)} class="group block w-full p-1 text-sm dark:text-white">
|
| 187 |
<div
|
|
|
|
| 200 |
{@render policyOption("fastest", "Fastest")}
|
| 201 |
{@render policyOption("cheapest", "Cheapest")}
|
| 202 |
</div>
|
| 203 |
+
{/if}
|
| 204 |
+
</div>
|
| 205 |
+
{:else}
|
| 206 |
+
<!-- Full mode for settings panel -->
|
| 207 |
+
<div class="flex flex-col gap-2">
|
| 208 |
+
<button
|
| 209 |
+
{...select.trigger}
|
| 210 |
+
class={cn(
|
| 211 |
+
"relative flex items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm",
|
| 212 |
+
"hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
|
| 213 |
+
classes,
|
| 214 |
+
)}
|
| 215 |
+
>
|
| 216 |
+
{@render providerDisplay(conversation.data.provider ?? "")}
|
| 217 |
+
<div
|
| 218 |
+
class="absolute right-2 grid size-4 flex-none place-items-center rounded-sm bg-gray-100 text-xs dark:bg-gray-600"
|
| 219 |
+
>
|
| 220 |
+
<IconCaret />
|
| 221 |
+
</div>
|
| 222 |
+
</button>
|
| 223 |
+
|
| 224 |
+
<div {...select.content} class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
|
| 225 |
+
{#snippet option(provider: string)}
|
| 226 |
+
<div {...select.getOption(provider)} class="group block w-full p-1 text-sm dark:text-white">
|
| 227 |
+
<div
|
| 228 |
+
class="rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
| 229 |
+
>
|
| 230 |
+
{@render providerDisplay(provider)}
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
{/snippet}
|
| 234 |
+
{#each conversation.model.inferenceProviderMapping as { provider, providerId } (provider + providerId)}
|
| 235 |
+
{@render option(provider)}
|
| 236 |
+
{/each}
|
| 237 |
+
{@render option("auto")}
|
| 238 |
</div>
|
| 239 |
+
|
| 240 |
+
{#if conversation.data.provider === "auto"}
|
| 241 |
+
<div class="flex flex-col gap-1.5">
|
| 242 |
+
<div class="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-400">
|
| 243 |
+
<span>Auto Policy</span>
|
| 244 |
+
<Tooltip>
|
| 245 |
+
{#snippet trigger(tooltip)}
|
| 246 |
+
<button class="flex items-center" {...tooltip.trigger}>
|
| 247 |
+
<IconInfo class="size-3" />
|
| 248 |
+
</button>
|
| 249 |
+
{/snippet}
|
| 250 |
+
{autoPolicyDescriptions[autoPolicyValue]}
|
| 251 |
+
</Tooltip>
|
| 252 |
+
</div>
|
| 253 |
+
<button
|
| 254 |
+
{...autoPolicySelect.trigger}
|
| 255 |
+
class={cn(
|
| 256 |
+
"relative flex items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 text-sm leading-tight whitespace-nowrap shadow-sm",
|
| 257 |
+
"hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
|
| 258 |
+
)}
|
| 259 |
+
>
|
| 260 |
+
{autoPolicyLabels[autoPolicyValue]}
|
| 261 |
+
<div
|
| 262 |
+
class="absolute right-2 grid size-4 flex-none place-items-center rounded-sm bg-gray-100 text-xs dark:bg-gray-600"
|
| 263 |
+
>
|
| 264 |
+
<IconCaret />
|
| 265 |
+
</div>
|
| 266 |
+
</button>
|
| 267 |
+
|
| 268 |
+
<div {...autoPolicySelect.content} class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
|
| 269 |
+
{#snippet policyOption(policy: "default" | "fastest" | "cheapest", label: string)}
|
| 270 |
+
<div {...autoPolicySelect.getOption(policy)} class="group block w-full p-1 text-sm dark:text-white">
|
| 271 |
+
<div
|
| 272 |
+
class="rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
| 273 |
+
>
|
| 274 |
+
<div class="flex flex-col items-start gap-0.5">
|
| 275 |
+
<span>{label}</span>
|
| 276 |
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
| 277 |
+
{autoPolicyDescriptions[policy]}
|
| 278 |
+
</span>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
{/snippet}
|
| 283 |
+
{@render policyOption("default", "Default")}
|
| 284 |
+
{@render policyOption("fastest", "Fastest")}
|
| 285 |
+
{@render policyOption("cheapest", "Cheapest")}
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
{/if}
|
| 289 |
+
</div>
|
| 290 |
+
{/if}
|