diff options
| author | Adam Malczewski <[email protected]> | 2026-09-05 00:34:45 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-09-05 00:34:45 +0900 |
| commit | a104c094b588560f955ff52dc7f949cf3c355f94 (patch) | |
| tree | 61983d5568f47fd23739705aaec815be1336b595 /src/app/views/ModelPane.svelte | |
| parent | 60a726883be85c174e88ae832ef95f3c0f084ec9 (diff) | |
| download | dispatch-web-a104c094b588560f955ff52dc7f949cf3c355f94.tar.gz dispatch-web-a104c094b588560f955ff52dc7f949cf3c355f94.zip | |
feat(layout): plan 01 — tile engine + view registry
Diffstat (limited to 'src/app/views/ModelPane.svelte')
| -rw-r--r-- | src/app/views/ModelPane.svelte | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/src/app/views/ModelPane.svelte b/src/app/views/ModelPane.svelte new file mode 100644 index 0000000..0344e47 --- /dev/null +++ b/src/app/views/ModelPane.svelte @@ -0,0 +1,131 @@ +<script lang="ts"> + /** + * The `model` view — the model selector + the per-conversation + * reasoning-effort / cwd / computer controls, with the store adapters that + * used to live in App.svelte. The controls re-mount per workspace + * conversation (the {#key} moved inside the wrapper) so inputs can't bleed + * across tabs — editable for a draft too. + */ + import { + ModelSelector, + ReasoningEffortSelector, + type ThinkingSelection, + type ThinkingSelectionSaveResult, + } from "../../features/chat"; + import { CwdField, type CwdSaveResult } from "../../features/cwd-lsp"; + import { + ComputerField, + type ComputerSaveResult, + type ComputerStatusResult, + type LoadComputerStatus, + type SaveComputer, + type TestComputer, + type TestComputerResult, + } from "../../features/computer"; + import type { NamedAgentSelectionSnapshot } from "../../features/named-agents"; + import type { AppViewPorts } from "../view-ports"; + + let { ports }: { ports: AppViewPorts } = $props(); + const store = $derived(ports.store); + + function handleSelectModel(model: string): void { + store.selectModel(model); + } + + // A Named Agent selected in the model selector: resolve it to its raw model + // AND record the snapshot on the active conversation (plan §6/§7). The store + // persists the raw model to `/conversations/:id/model` + retains the snapshot + // on the tab. + function handleSelectNamedAgent(snapshot: NamedAgentSelectionSnapshot): void { + store.selectModel(snapshot.model, snapshot); + } + + // Adapt the store's reasoning-effort + thinking results to the chat + // feature's combined selector port. The selector sends ONE selection ("off" + // or a level); the adapter fans it out to the right per-axis PUT(s). "off" is + // a SEPARATE signal from the effort level: it persists `thinking: false` + // (the umans route maps that to `reasoning_effort: "none"`), leaving the + // effort level untouched so an off→on toggle restores it. A level ensures + // thinking is ON (the level is meaningless while thinking is off) then sets + // the effort level. + async function saveThinkingSelection( + selection: ThinkingSelection, + ): Promise<ThinkingSelectionSaveResult | null> { + if (selection === "off") { + const result = await store.setThinking(false); + if (result === null) return null; + return result.ok + ? { ok: true, selection: "off" } + : { ok: false, error: result.error }; + } + // A level: enable thinking first if it is currently off, then set the level. + if (store.thinking === false) { + const on = await store.setThinking(true); + if (on !== null && !on.ok) { + return { ok: false, error: on.error }; + } + } + const result = await store.setReasoningEffort(selection); + if (result === null) return null; + return result.ok + ? { ok: true, selection: result.reasoningEffort } + : { ok: false, error: result.error }; + } + + async function saveCwd(cwd: string): Promise<CwdSaveResult | null> { + const result = await store.setCwd(cwd); + if (result === null) return null; + return result.ok ? { ok: true, cwd: result.cwd } : { ok: false, error: result.error }; + } + + async function saveComputer(computerId: string | null): Promise<ComputerSaveResult | null> { + const result = await store.setComputer(computerId); + if (result === null) return null; + return result.ok ? { ok: true, computerId: result.computerId } : { ok: false, error: result.error }; + } + + const loadComputerStatus: LoadComputerStatus = async ( + alias: string, + ): Promise<ComputerStatusResult | null> => { + const result = await store.computerStatus(alias); + if (result === null) return null; + return result.ok ? { ok: true, status: result.response } : { ok: false, error: result.error }; + }; + + const testComputer: TestComputer = async ( + alias: string, + ): Promise<TestComputerResult | null> => { + const result = await store.testComputer(alias); + if (result === null) return null; + return result.ok ? { ok: true, response: result.response } : { ok: false, error: result.error }; + }; +</script> + +<div class="flex flex-col gap-3"> + <ModelSelector + models={store.models} + selected={store.activeModel} + onSelect={handleSelectModel} + modelInfo={store.modelInfo} + namedAgents={store.namedAgents} + namedAgentSnapshot={store.namedAgentSnapshot} + onSelectNamedAgent={handleSelectNamedAgent} + zeroProviders={store.enabledProviderCount === 0} + /> + {#key store.currentConversationId} + <ReasoningEffortSelector + persistedEffort={store.reasoningEffort} + persistedThinking={store.thinking} + save={saveThinkingSelection} + /> + <CwdField cwd={store.cwd} canEdit={true} save={saveCwd} /> + <ComputerField + computerId={store.computerId} + canEdit={true} + computers={store.computers} + save={saveComputer} + loadStatus={loadComputerStatus} + test={testComputer} + /> + {/key} +</div> |
