diff options
| author | Adam Malczewski <[email protected]> | 2026-05-21 19:06:15 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-21 19:06:15 +0900 |
| commit | 55633c90c0d96e62153a4b6655f3f833a3b46ad4 (patch) | |
| tree | a75f97b48071eb1ba9e8366d8053a376dde69af5 | |
| parent | d6b208342edf97bafa5b1dcc986b782f9879d141 (diff) | |
| download | dispatch-55633c90c0d96e62153a4b6655f3f833a3b46ad4.tar.gz dispatch-55633c90c0d96e62153a4b6655f3f833a3b46ad4.zip | |
refactor: gut model/tag/fallback/agent-template system, fix Docker setup
- Remove ModelResolver, model definitions, model tags, fallback order, agent templates
- Remove all [[models]], [agents], and fallback from dispatch.toml and config schema
- ModelRegistry is now a pure key-state manager
- dispatch.toml reduced to keys + permissions only
- Docker: fix entrypoint for existing UID, skip bun install in frontend container
- Docker: scoped build cache prune in bin/clean
| -rwxr-xr-x | bin/clean | 3 | ||||
| -rw-r--r-- | dispatch.toml | 46 | ||||
| -rw-r--r-- | docker-compose.yml | 4 | ||||
| -rw-r--r-- | docker/entrypoint.dev.sh | 6 | ||||
| -rw-r--r-- | packages/api/src/agent-manager.ts | 59 | ||||
| -rw-r--r-- | packages/api/src/routes/models.ts | 44 | ||||
| -rw-r--r-- | packages/core/src/config/schema.ts | 126 | ||||
| -rw-r--r-- | packages/core/src/index.ts | 2 | ||||
| -rw-r--r-- | packages/core/src/models/index.ts | 1 | ||||
| -rw-r--r-- | packages/core/src/models/registry.ts | 64 | ||||
| -rw-r--r-- | packages/core/src/models/resolver.ts | 88 | ||||
| -rw-r--r-- | packages/core/src/types/index.ts | 25 | ||||
| -rw-r--r-- | packages/frontend/src/App.svelte | 14 | ||||
| -rw-r--r-- | packages/frontend/src/lib/components/ModelStatus.svelte | 26 | ||||
| -rw-r--r-- | packages/frontend/src/lib/components/SidebarPanel.svelte | 8 | ||||
| -rw-r--r-- | packages/frontend/src/lib/types.ts | 6 |
16 files changed, 36 insertions, 486 deletions
@@ -4,5 +4,6 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" -# Stop containers, remove volumes, remove images +# Stop containers, remove volumes, remove images, and clear build cache sudo docker compose -f "$PROJECT_DIR/docker-compose.yml" down --volumes --rmi local "$@" +sudo docker builder prune -f --filter "label=com.docker.compose.project=dispatch" diff --git a/dispatch.toml b/dispatch.toml index 6789e95..4332462 100644 --- a/dispatch.toml +++ b/dispatch.toml @@ -1,10 +1,7 @@ -# Dispatch — Model & Key Configuration +# Dispatch — Key & Permission Configuration # Credentials and API keys are stored in the SQLite database. # Use the Model Status panel to import credentials, or run bin/import-credentials.ts. -# ─── Fallback Order (highest priority first) ──────────────────── -fallback = ["claude-pro", "claude-max", "opencode-1", "opencode-2", "copilot"] - # ─── API Keys ─────────────────────────────────────────────────── [[keys]] @@ -34,47 +31,6 @@ id = "copilot" provider = "github-copilot" base_url = "https://api.githubcopilot.com" -# ─── Models ───────────────────────────────────────────────────── - -[[models]] -id = "claude-sonnet-4-20250514" -provider = "anthropic" -tags = ["heavy", "coding", "review"] - -[[models]] -id = "claude-opus-4-20250514" -provider = "anthropic" -tags = ["heavy", "coding", "review"] - -[[models]] -id = "deepseek-v4-pro" -provider = "opencode-go" -tags = ["heavy", "coding"] - -[[models]] -id = "deepseek-v4-flash" -provider = "opencode-go" -tags = ["light", "quick"] - -[[models]] -id = "gpt-4o" -provider = "github-copilot" -tags = ["heavy", "coding"] - -[[models]] -id = "claude-3.5-sonnet" -provider = "github-copilot" -tags = ["heavy", "coding", "review"] - -# ─── Agent Templates ───────────────────────────────────────────── - -[agents.default] -name = "Dispatch" -description = "Default coding assistant" -system_prompt = "You are a helpful AI coding assistant." -tools = ["read_file", "write_file", "list_files", "run_shell", "task_list"] -model_tag = "heavy" - # ─── Permissions ───────────────────────────────────────────────── [permissions] diff --git a/docker-compose.yml b/docker-compose.yml index 3a019e8..2ce08a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,11 @@ services: - "5173:5173" volumes: - .:/app + depends_on: + api: + condition: service_started environment: HOST_UID: ${HOST_UID:-1000} HOST_GID: ${HOST_GID:-1000} HOST_USER: ${HOST_USER:-dispatch} + SKIP_INSTALL: "1" diff --git a/docker/entrypoint.dev.sh b/docker/entrypoint.dev.sh index 8378585..bbde09a 100644 --- a/docker/entrypoint.dev.sh +++ b/docker/entrypoint.dev.sh @@ -40,8 +40,10 @@ if [ -d /app/node_modules ]; then chown -R "$HOST_UID:$HOST_GID" /app/node_modules fi -# Install/update dependencies as the target user -su -s /bin/bash - "$USER_NAME" -c "export HOME=$USER_HOME && cd /app && bun install" +# Install/update dependencies as the target user (skip with SKIP_INSTALL=1) +if [ "${SKIP_INSTALL:-}" != "1" ]; then + su -s /bin/bash - "$USER_NAME" -c "export HOME=$USER_HOME && cd /app && bun install" +fi # Execute the main command as the target user exec su -s /bin/bash - "$USER_NAME" -c "export HOME=$USER_HOME && cd /app && exec $*" diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 92396b6..6e78ad7 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -16,7 +16,6 @@ import { loadSkills, createSkillsWatcher, ModelRegistry, - ModelResolver, TaskList, createTaskListTool, type ClaudeAccount, @@ -53,7 +52,6 @@ export class AgentManager { private config: DispatchConfig; private skillsData: { skills: SkillDefinition[]; mappings: AgentSkillMapping[] }; private modelRegistry: ModelRegistry | null = null; - private modelResolver: ModelResolver | null = null; private taskList: TaskList; private configWatcher: { close(): void } | null = null; @@ -89,7 +87,6 @@ export class AgentManager { setSkillsGetter(() => this.skillsData); setModelsGetter( () => this.modelRegistry, - () => this.modelResolver, ); setAccountsGetter(() => this.claudeAccounts); @@ -135,17 +132,14 @@ export class AgentManager { } private _initModelRegistry(config: DispatchConfig): void { - if (config.models && config.keys) { + if (config.keys) { if (this.modelRegistry) { - this.modelRegistry.updateConfig(config.models, config.keys, config.fallback ?? []); + this.modelRegistry.updateConfig(config.keys); } else { - this.modelRegistry = new ModelRegistry(config.models, config.keys, config.fallback ?? []); - this.modelResolver = new ModelResolver(this.modelRegistry); + this.modelRegistry = new ModelRegistry(config.keys); } } else { - // Models/keys removed from config — clear the registry this.modelRegistry = null; - this.modelResolver = null; } } @@ -263,7 +257,7 @@ export class AgentManager { } } } else { - console.warn(`dispatch: key "${effectiveKeyId}" not found in model registry, falling back to tag-based resolution`); + console.warn(`dispatch: key "${effectiveKeyId}" not found in model registry`); } } @@ -273,51 +267,6 @@ export class AgentManager { this.activeModelId = null; } - if (!useOverride && this.modelRegistry && this.modelResolver) { - // Try to get model_tag from default agent template, fall back to "heavy" - const defaultAgent = this.config.agents?.["default"]; - const tag = defaultAgent?.model_tag ?? "heavy"; - const resolved = this.modelResolver.resolve(tag); - if (resolved) { - model = resolved.model.id; - baseURL = resolved.key.base_url; - // Check if resolved key is anthropic - if (resolved.key.provider === "anthropic") { - const credFile = resolved.key.credentials_file; - const account = this.claudeAccounts.find((a) => a.id === resolved.key.id) - ?? (credFile ? this.claudeAccounts.find((a) => a.source === credFile) : this.claudeAccounts[0]); - if (account) { - let creds = refreshAccountCredentials(account); - if (!creds || creds.expiresAt <= Date.now() + 60_000) { - creds = await refreshAccountCredentialsAsync(account); - if (creds) account.credentials = creds; - } - if (creds) { - claudeCredentials = { accessToken: creds.accessToken }; - apiKey = creds.accessToken; - provider = "anthropic"; - } else { - console.warn(`dispatch: no valid Claude credentials for key "${resolved.key.id}"`); - } - } else { - console.warn(`dispatch: no Claude credentials found for key "${resolved.key.id}"`); - } - } else { - const envKey = resolveApiKey(resolved.key.id); - if (envKey) { - apiKey = envKey; - } else { - console.warn(`dispatch: env var not set for key "${resolved.key.id}", falling back to defaults`); - model = "deepseek-v4-flash"; - baseURL = "https://opencode.ai/zen/go/v1"; - apiKey = ""; - } - } - } else { - console.warn(`dispatch: could not resolve model for tag "${tag}", falling back to env vars`); - } - } - this.agent = new Agent({ model, apiKey, diff --git a/packages/api/src/routes/models.ts b/packages/api/src/routes/models.ts index ca58e38..86411f1 100644 --- a/packages/api/src/routes/models.ts +++ b/packages/api/src/routes/models.ts @@ -1,4 +1,4 @@ -import type { ModelRegistry, ModelResolver } from "@dispatch/core"; +import type { ModelRegistry } from "@dispatch/core"; import { ANTHROPIC_MODELS_FALLBACK, type ClaudeAccount, @@ -20,15 +20,12 @@ import { import { Hono } from "hono"; let getRegistry: () => ModelRegistry | null = () => null; -let getResolver: () => ModelResolver | null = () => null; let getAccounts: () => ClaudeAccount[] = () => []; export function setModelsGetter( registryGetter: () => ModelRegistry | null, - resolverGetter: () => ModelResolver | null, ): void { getRegistry = registryGetter; - getResolver = resolverGetter; } export function setAccountsGetter(getter: () => ClaudeAccount[]): void { @@ -45,11 +42,9 @@ export const modelsRoutes = new Hono(); modelsRoutes.get("/", (c) => { const registry = getRegistry(); if (!registry) { - return c.json({ models: [], tags: [], keys: [] }); + return c.json({ keys: [] }); } - const models = registry.getModels(); - const tags = registry.getAllTags(); const keyStates = registry.getKeys(); const keys = keyStates.map((ks) => ({ @@ -60,40 +55,7 @@ modelsRoutes.get("/", (c) => { exhaustedAt: ks.exhaustedAt ?? null, })); - return c.json({ models, tags, keys }); -}); - -modelsRoutes.get("/resolve", (c) => { - const registry = getRegistry(); - const resolver = getResolver(); - if (!registry || !resolver) { - return c.json({ resolved: null, reason: "no models configured" }); - } - - const tag = c.req.query("tag"); - if (!tag) { - return c.json({ error: "tag query parameter is required" }, 400); - } - - const matchingModels = registry.getModelsByTag(tag); - if (matchingModels.length === 0) { - return c.json({ resolved: null, reason: "no models match tag" }); - } - - const resolved = resolver.resolve(tag); - if (!resolved) { - return c.json({ resolved: null, reason: "all keys exhausted for matching providers" }); - } - - return c.json({ - resolved: { - model: resolved.model, - key: { - id: resolved.key.id, - provider: resolved.key.provider, - }, - }, - }); + return c.json({ keys }); }); // Fetch available models for a specific provider key. diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 83e2695..2cd8d55 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -1,9 +1,7 @@ import type { - AgentTemplate, ConfigError, DispatchConfig, KeyDefinition, - ModelDefinition, } from "../types/index.js"; function isRecord(value: unknown): value is Record<string, unknown> { @@ -58,69 +56,6 @@ function validatePermissions( return result; } -function validateAgentTemplate( - raw: unknown, - path: string, - errors: ConfigError[], -): AgentTemplate | null { - if (!isRecord(raw)) { - errors.push({ path, message: "must be an object" }); - return null; - } - - const requiredStrings = ["name", "description", "system_prompt", "model_tag"] as const; - for (const field of requiredStrings) { - if (typeof raw[field] !== "string") { - errors.push({ path: `${path}.${field}`, message: "must be a string" }); - return null; - } - } - - if (!Array.isArray(raw["tools"]) || !raw["tools"].every((t) => typeof t === "string")) { - errors.push({ path: `${path}.tools`, message: "must be an array of strings" }); - return null; - } - - const perms = validatePermissions(raw["permissions"] ?? {}, `${path}.permissions`, errors); - - return { - name: raw["name"] as string, - description: raw["description"] as string, - system_prompt: raw["system_prompt"] as string, - tools: raw["tools"] as string[], - model_tag: raw["model_tag"] as string, - permissions: perms, - }; -} - -function validateModel(raw: unknown, path: string, errors: ConfigError[]): ModelDefinition | null { - if (!isRecord(raw)) { - errors.push({ path, message: "must be an object" }); - return null; - } - if (typeof raw["id"] !== "string") { - errors.push({ path: `${path}.id`, message: "must be a string" }); - return null; - } - if (typeof raw["provider"] !== "string") { - errors.push({ path: `${path}.provider`, message: "must be a string" }); - return null; - } - if ( - !Array.isArray(raw["tags"]) || - raw["tags"].length === 0 || - !raw["tags"].every((t) => typeof t === "string") - ) { - errors.push({ path: `${path}.tags`, message: "must be a non-empty array of strings" }); - return null; - } - return { - id: raw["id"] as string, - provider: raw["provider"] as string, - tags: raw["tags"] as string[], - }; -} - function validateKey(raw: unknown, path: string, errors: ConfigError[]): KeyDefinition | null { if (!isRecord(raw)) { errors.push({ path, message: "must be an object" }); @@ -169,34 +104,6 @@ export function validateConfig(raw: unknown): { config: DispatchConfig; errors: // permissions (required, but can be empty) const permissions = validatePermissions(raw["permissions"] ?? {}, "permissions", errors); - // agents (optional) - let agents: Record<string, AgentTemplate> | undefined; - if (raw["agents"] !== undefined) { - if (!isRecord(raw["agents"])) { - errors.push({ path: "agents", message: "must be an object" }); - } else { - agents = {}; - for (const [key, value] of Object.entries(raw["agents"])) { - const agent = validateAgentTemplate(value, `agents.${key}`, errors); - if (agent) agents[key] = agent; - } - } - } - - // models (optional) - let models: ModelDefinition[] | undefined; - if (raw["models"] !== undefined) { - if (!Array.isArray(raw["models"])) { - errors.push({ path: "models", message: "must be an array" }); - } else { - models = []; - for (let i = 0; i < raw["models"].length; i++) { - const model = validateModel(raw["models"][i], `models[${i}]`, errors); - if (model) models.push(model); - } - } - } - // keys (optional) let keys: KeyDefinition[] | undefined; if (raw["keys"] !== undefined) { @@ -211,42 +118,9 @@ export function validateConfig(raw: unknown): { config: DispatchConfig; errors: } } - // fallback (optional) - let fallback: string[] | undefined; - if (raw["fallback"] !== undefined) { - if (!Array.isArray(raw["fallback"]) || !raw["fallback"].every((f) => typeof f === "string")) { - errors.push({ path: "fallback", message: "must be an array of strings" }); - } else { - fallback = raw["fallback"] as string[]; - // Validate that referenced key IDs exist - const keyIds = new Set((keys ?? []).map((k) => k.id)); - for (const id of fallback) { - if (!keyIds.has(id)) { - errors.push({ path: "fallback", message: `key id "${id}" not found in keys` }); - } - } - } - } - - // Warn if agent model_tags don't match any model - if (agents && models) { - const allTags = new Set(models.flatMap((m) => m.tags)); - for (const [name, agent] of Object.entries(agents)) { - if (!allTags.has(agent.model_tag)) { - errors.push({ - path: `agents.${name}.model_tag`, - message: `no model found with tag "${agent.model_tag}"`, - }); - } - } - } - const config: DispatchConfig = { permissions, - ...(agents !== undefined && { agents }), - ...(models !== undefined && { models }), ...(keys !== undefined && { keys }), - ...(fallback !== undefined && { fallback }), }; return { config, errors }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 90cafbe..b1e2e14 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -25,7 +25,7 @@ export { loadConfig, configToRuleset, validateConfig, createConfigWatcher } from export { parseSkillFile, loadSkills, resolveSkillsForAgent, getSkillByName, createSkillsWatcher } from "./skills/index.js"; // Models -export { ModelRegistry, ModelResolver } from "./models/index.js"; +export { ModelRegistry } from "./models/index.js"; // Credentials export * from "./credentials/index.js"; diff --git a/packages/core/src/models/index.ts b/packages/core/src/models/index.ts index e5efcc8..cf59749 100644 --- a/packages/core/src/models/index.ts +++ b/packages/core/src/models/index.ts @@ -1,2 +1 @@ export { ModelRegistry } from "./registry.js"; -export { ModelResolver } from "./resolver.js"; diff --git a/packages/core/src/models/registry.ts b/packages/core/src/models/registry.ts index f78fe0e..9ea0b32 100644 --- a/packages/core/src/models/registry.ts +++ b/packages/core/src/models/registry.ts @@ -1,25 +1,20 @@ -import type { KeyDefinition, KeyState, ModelDefinition } from "../types/index.js"; +import type { KeyDefinition, KeyState } from "../types/index.js"; export class ModelRegistry { - private models: ModelDefinition[]; private keyStates: Map<string, KeyState>; - private fallbackOrder: string[]; + private keyOrder: string[]; - constructor(models: ModelDefinition[], keys: KeyDefinition[], fallbackOrder: string[]) { - this.models = []; + constructor(keys: KeyDefinition[]) { this.keyStates = new Map(); - this.fallbackOrder = []; - this._initConfig(models, keys, fallbackOrder, new Map()); + this.keyOrder = []; + this._initConfig(keys, new Map()); } private _initConfig( - models: ModelDefinition[], keys: KeyDefinition[], - fallbackOrder: string[], existingStates: Map<string, KeyState>, ): void { - this.models = [...models]; - this.fallbackOrder = this._buildFallbackOrder(keys, fallbackOrder); + this.keyOrder = keys.map((k) => k.id); const newStates = new Map<string, KeyState>(); for (const key of keys) { @@ -34,51 +29,12 @@ export class ModelRegistry { this.keyStates = newStates; } - private _buildFallbackOrder(keys: KeyDefinition[], fallbackOrder: string[]): string[] { - const ordered: string[] = []; - const keyIds = new Set(keys.map((k) => k.id)); - - // Add keys from fallbackOrder first (if they exist) - for (const id of fallbackOrder) { - if (keyIds.has(id) && !ordered.includes(id)) { - ordered.push(id); - } - } - - // Append remaining keys not in fallbackOrder - for (const key of keys) { - if (!ordered.includes(key.id)) { - ordered.push(key.id); - } - } - - return ordered; - } - - getModels(): ModelDefinition[] { - return [...this.models]; - } - getKeys(): KeyState[] { - return this.fallbackOrder + return this.keyOrder .map((id) => this.keyStates.get(id)) .filter((state): state is KeyState => state !== undefined); } - getModelsByTag(tag: string): ModelDefinition[] { - return this.models.filter((m) => m.tags.includes(tag)); - } - - getAllTags(): string[] { - const tags = new Set<string>(); - for (const model of this.models) { - for (const tag of model.tags) { - tags.add(tag); - } - } - return [...tags]; - } - markKeyExhausted(keyId: string, error?: string): void { const state = this.keyStates.get(keyId); if (!state) return; @@ -118,13 +74,13 @@ export class ModelRegistry { return true; } - updateConfig(models: ModelDefinition[], keys: KeyDefinition[], fallbackOrder: string[]): void { - this._initConfig(models, keys, fallbackOrder, this.keyStates); + updateConfig(keys: KeyDefinition[]): void { + this._initConfig(keys, this.keyStates); } // Internal: get ordered key states for a specific provider getOrderedKeysForProvider(provider: string): KeyState[] { - return this.fallbackOrder + return this.keyOrder .map((id) => this.keyStates.get(id)) .filter( (state): state is KeyState => diff --git a/packages/core/src/models/resolver.ts b/packages/core/src/models/resolver.ts deleted file mode 100644 index 3ada713..0000000 --- a/packages/core/src/models/resolver.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { ResolvedModel } from "../types/index.js"; -import type { ModelRegistry } from "./registry.js"; - -export class ModelResolver { - private registry: ModelRegistry; - - constructor(registry: ModelRegistry) { - this.registry = registry; - } - - resolve(tag: string): ResolvedModel | null { - const models = this.registry.getModelsByTag(tag); - const keys = this.registry.getKeys(); - - for (const keyState of keys) { - if (keyState.status !== "active") continue; - const model = models.find((m) => m.provider === keyState.definition.provider); - if (model) { - return { model, key: keyState.definition }; - } - } - - return null; - } - - async waitForKey( - tag: string, - options?: { - pollIntervalMs?: number; - signal?: AbortSignal; - onWaiting?: () => void; - onResume?: () => void; - }, - ): Promise<ResolvedModel | null> { - const pollIntervalMs = options?.pollIntervalMs ?? 60000; - const signal = options?.signal; - - // Try immediately first - const immediate = this.resolve(tag); - if (immediate) return immediate; - - // Check if aborted before entering wait state - if (signal?.aborted) return null; - - options?.onWaiting?.(); - - return new Promise<ResolvedModel | null>((resolve) => { - let timer: ReturnType<typeof setTimeout> | null = null; - - const cleanup = () => { - if (timer !== null) { - clearTimeout(timer); - timer = null; - } - }; - - const onAbort = () => { - cleanup(); - resolve(null); - }; - - if (signal) { - signal.addEventListener("abort", onAbort, { once: true }); - } - - const poll = () => { - if (signal?.aborted) { - resolve(null); - return; - } - - const result = this.resolve(tag); - if (result) { - if (signal) { - signal.removeEventListener("abort", onAbort); - } - options?.onResume?.(); - resolve(result); - return; - } - - timer = setTimeout(poll, pollIntervalMs); - }; - - timer = setTimeout(poll, pollIntervalMs); - }); - } -} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 6977564..f5dafbb 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -77,28 +77,10 @@ export interface AgentConfig { // ─── Config Types (dispatch.toml) ──────────────────────────────── export interface DispatchConfig { - agents?: Record<string, AgentTemplate>; - models?: ModelDefinition[]; keys?: KeyDefinition[]; - fallback?: string[]; permissions: Record<string, string | Record<string, string>>; } -export interface AgentTemplate { - name: string; - description: string; - system_prompt: string; - tools: string[]; - permissions: Record<string, string | Record<string, string>>; - model_tag: string; -} - -export interface ModelDefinition { - id: string; - provider: string; - tags: string[]; -} - export interface KeyDefinition { id: string; provider: string; @@ -108,13 +90,6 @@ export interface KeyDefinition { credentials_file?: string; } -// ─── Model Resolution ──────────────────────────────────────────── - -export interface ResolvedModel { - model: ModelDefinition; - key: KeyDefinition; -} - export type KeyStatus = "active" | "exhausted"; export interface KeyState { diff --git a/packages/frontend/src/App.svelte b/packages/frontend/src/App.svelte index 51fbd00..4ea968c 100644 --- a/packages/frontend/src/App.svelte +++ b/packages/frontend/src/App.svelte @@ -9,14 +9,12 @@ import HotReloadIndicator from "./lib/components/HotReloadIndicator.svelte"; import { chatStore } from "./lib/chat.svelte.js"; import { wsClient } from "./lib/ws.svelte.js"; import { config } from "./lib/config.js"; -import type { KeyInfo, ModelInfo } from "./lib/types.js"; +import type { KeyInfo } from "./lib/types.js"; const STORAGE_KEY = "dispatch-theme"; -let modelsData = $state<{ models: ModelInfo[]; keys: KeyInfo[]; tags: string[] }>({ - models: [], +let modelsData = $state<{ keys: KeyInfo[] }>({ keys: [], - tags: [], }); let sidebarOpen = $state(true); @@ -27,9 +25,7 @@ async function fetchModels() { if (!res.ok) return; const data = await res.json(); modelsData = { - models: data.models ?? [], keys: data.keys ?? [], - tags: data.tags ? (Array.isArray(data.tags) ? data.tags : Object.keys(data.tags)) : [], }; } catch { // ignore fetch errors @@ -83,10 +79,8 @@ onMount(() => { class="w-80 flex-1 min-h-0 overflow-y-auto bg-base-100 border-l border-base-300 px-2 py-2 flex flex-col gap-2 [&>*]:shrink-0 transition-transform duration-300 ease-out" style="transform: translateX({sidebarOpen ? '0' : '100%'})" > - <SidebarPanel - models={modelsData.models} - keys={modelsData.keys} - tags={modelsData.tags} + <SidebarPanel + keys={modelsData.keys} tasks={chatStore.tasks} permissionLog={chatStore.permissionLog} apiBase={config.apiBase} diff --git a/packages/frontend/src/lib/components/ModelStatus.svelte b/packages/frontend/src/lib/components/ModelStatus.svelte index b2b6902..a627de8 100644 --- a/packages/frontend/src/lib/components/ModelStatus.svelte +++ b/packages/frontend/src/lib/components/ModelStatus.svelte @@ -7,12 +7,6 @@ exhaustedAt: number | null; } - interface ModelInfo { - id: string; - provider: string; - tags: string[]; - } - interface CredentialStatus { keyId: string; provider: string; @@ -23,15 +17,11 @@ } const { - models = [], keys = [], - tags = [], currentModel, apiBase = "", }: { - models?: ModelInfo[]; keys?: KeyInfo[]; - tags?: string[]; currentModel?: string; apiBase?: string; } = $props(); @@ -42,8 +32,6 @@ const allExhausted = $derived(totalKeys > 0 && activeKeys === 0); const someExhausted = $derived(totalKeys > 0 && activeKeys < totalKeys && activeKeys > 0); - const uniqueTags = $derived([...new Set(tags)]); - let credentialStatus = $state<Record<string, CredentialStatus>>({}); let importingKey = $state<string | null>(null); let importError = $state<string | null>(null); @@ -158,7 +146,7 @@ </script> <div class="flex flex-col gap-3"> - {#if models.length === 0 && keys.length === 0} + {#if keys.length === 0} <p class="text-xs text-base-content/50"> No models configured. Using environment defaults. </p> @@ -191,18 +179,6 @@ </div> {/if} - <!-- Tags --> - {#if uniqueTags.length > 0} - <div class="flex flex-col gap-1"> - <p class="text-xs text-base-content/50 uppercase tracking-wide">Tags</p> - <div class="flex flex-wrap gap-1"> - {#each uniqueTags as tag (tag)} - <span class="badge badge-outline badge-xs">{tag}</span> - {/each} - </div> - </div> - {/if} - <!-- Import error/success banners --> {#if importError} <div role="alert" class="text-xs text-error/80">{importError}</div> diff --git a/packages/frontend/src/lib/components/SidebarPanel.svelte b/packages/frontend/src/lib/components/SidebarPanel.svelte index 79eb73b..e5536de 100644 --- a/packages/frontend/src/lib/components/SidebarPanel.svelte +++ b/packages/frontend/src/lib/components/SidebarPanel.svelte @@ -7,12 +7,10 @@ import PermissionLog from "./PermissionLog.svelte"; import KeyUsage from "./KeyUsage.svelte"; import ClaudeReset from "./ClaudeReset.svelte"; - import type { TaskItem, LogEntry, KeyInfo, ModelInfo } from "../types.js"; + import type { TaskItem, LogEntry, KeyInfo } from "../types.js"; const { - models = [], keys = [], - tags = [], tasks = [], permissionLog = [], apiBase = "", @@ -23,9 +21,7 @@ onModelChange, onReasoningChange, }: { - models?: ModelInfo[]; keys?: KeyInfo[]; - tags?: string[]; tasks?: TaskItem[]; permissionLog?: LogEntry[]; apiBase?: string; @@ -109,7 +105,7 @@ {:else if panel.selected === "Claude Reset"} <ClaudeReset {apiBase} /> {:else if panel.selected === "Model Status"} - <ModelStatus {models} {keys} {tags} {apiBase} /> + <ModelStatus {keys} {apiBase} /> {:else if panel.selected === "Tasks"} <TaskListPanel {tasks} /> {:else if panel.selected === "Config"} diff --git a/packages/frontend/src/lib/types.ts b/packages/frontend/src/lib/types.ts index 559742d..28752a0 100644 --- a/packages/frontend/src/lib/types.ts +++ b/packages/frontend/src/lib/types.ts @@ -94,12 +94,6 @@ export interface KeyInfo { exhaustedAt: number | null; } -export interface ModelInfo { - id: string; - provider: string; - tags: string[]; -} - export interface LogEntry { id: string; permission: string; |
