blob: cc9866e10416e8621e3638a24963873f1b1aef0d (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
<script lang="ts">
import type { InvokeMessage } from "@dispatch/ui-contract";
import { ChatView, Composer, ModelSelector } from "../features/chat";
import { TabBar } from "../features/tabs";
import { SurfaceView } from "../features/surface-host";
import type { AppStore } from "./store.svelte";
let { store }: { store: AppStore } = $props();
function handleSelect(surfaceId: string) {
store.select(surfaceId);
}
function handleInvoke(msg: InvokeMessage) {
store.invoke(msg.surfaceId, msg.actionId, msg.payload);
}
function handleSend(text: string) {
store.send(text);
}
function handleSelectModel(model: string) {
store.selectModel(model);
}
</script>
<main class="flex h-screen flex-col">
<div class="flex items-center justify-between border-b border-base-300 px-4 py-2">
<h1 class="text-lg font-bold">Dispatch</h1>
</div>
{#if store.lastError}
<div role="alert" class="alert alert-error mx-4 mt-2">
<strong>Error:</strong>
{store.lastError.message}
</div>
{/if}
{#if store.activeChat.error}
<div role="alert" class="alert alert-warning mx-4 mt-2">
<strong>Chat error:</strong>
{store.activeChat.error}
</div>
{/if}
<TabBar
tabs={store.tabs}
activeConversationId={store.activeConversationId}
onSelect={(id) => store.selectTab(id)}
onClose={(id) => store.closeTab(id)}
onNewDraft={() => store.newDraft()}
/>
<div class="flex flex-1 flex-col overflow-hidden">
<div class="flex items-center gap-2 px-4 py-2">
<ModelSelector
models={store.models}
selected={store.activeModel}
onSelect={handleSelectModel}
/>
</div>
<div class="flex-1 overflow-y-auto">
<ChatView chunks={store.activeChat.chunks} />
</div>
<Composer onSend={handleSend} />
</div>
{#if store.catalog.length > 0}
<section class="border-t border-base-300 p-4">
<h2 class="mb-2 text-sm font-semibold">Surfaces</h2>
<div class="flex flex-wrap gap-2">
{#each store.catalog as entry (entry.id)}
<button
class="btn btn-sm"
class:btn-active={entry.id === store.selectedId}
aria-current={entry.id === store.selectedId ? "true" : undefined}
onclick={() => handleSelect(entry.id)}
>
{entry.title}
<span class="text-xs opacity-60">({entry.region})</span>
</button>
{/each}
</div>
</section>
{/if}
{#if store.selectedSpec}
<section class="border-t border-base-300 p-4">
<SurfaceView spec={store.selectedSpec} onInvoke={handleInvoke} />
</section>
{/if}
</main>
|