@ouraihub/aether-ui 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/LICENSE +789 -0
  2. package/fixtures/model-classify.json +42 -0
  3. package/fixtures/pricing-cost.json +56 -0
  4. package/fixtures/reasoning-infer.json +44 -0
  5. package/fixtures/sse/anthropic-messages/chinese.sse +24 -0
  6. package/fixtures/sse/anthropic-messages/multi-tool.sse +33 -0
  7. package/fixtures/sse/anthropic-messages/single-tool.sse +39 -0
  8. package/fixtures/sse/anthropic-messages/text.sse +27 -0
  9. package/fixtures/sse/anthropic-messages/truncated.sse +21 -0
  10. package/fixtures/sse/anthropic-messages/upstream-error.sse +12 -0
  11. package/fixtures/sse/expect.json +20 -0
  12. package/fixtures/sse/openai-chat/chinese.sse +14 -0
  13. package/fixtures/sse/openai-chat/multi-tool.sse +18 -0
  14. package/fixtures/sse/openai-chat/single-tool.sse +22 -0
  15. package/fixtures/sse/openai-chat/text.sse +14 -0
  16. package/fixtures/sse/openai-chat/truncated.sse +12 -0
  17. package/fixtures/sse/openai-chat/upstream-error.sse +6 -0
  18. package/fixtures/sse/openai-responses/chinese.sse +33 -0
  19. package/fixtures/sse/openai-responses/multi-tool.sse +36 -0
  20. package/fixtures/sse/openai-responses/single-tool.sse +27 -0
  21. package/fixtures/sse/openai-responses/text.sse +33 -0
  22. package/fixtures/sse/openai-responses/truncated.sse +30 -0
  23. package/fixtures/sse/openai-responses/upstream-error.sse +9 -0
  24. package/package.json +71 -3
  25. package/src/CopyInput.svelte +33 -0
  26. package/src/KitNotifier.svelte +33 -0
  27. package/src/chat/ApprovalCard.svelte +29 -0
  28. package/src/chat/ChatComposer.svelte +277 -0
  29. package/src/chat/ChatMessage.svelte +262 -0
  30. package/src/chat/ChatPanel.css +386 -0
  31. package/src/chat/ChatPanel.svelte +496 -0
  32. package/src/chat/ChatPanelApprovals.svelte +28 -0
  33. package/src/chat/ChatPanelComposer.svelte +317 -0
  34. package/src/chat/ChatPanelDialogs.svelte +242 -0
  35. package/src/chat/ChatPanelHeader.svelte +132 -0
  36. package/src/chat/ChatPanelMessages.svelte +166 -0
  37. package/src/chat/ChatPanelMode.svelte +190 -0
  38. package/src/chat/ChatPanelState.svelte.ts +1 -0
  39. package/src/chat/ChatPanelToolbar.svelte +425 -0
  40. package/src/chat/ChatSurface.svelte +168 -0
  41. package/src/chat/MessageItem.svelte +480 -0
  42. package/src/chat/ReasoningControl.svelte +286 -0
  43. package/src/chat/SkillPicker.svelte +133 -0
  44. package/src/chat/ToolCallItem.svelte +23 -0
  45. package/src/chat/UsageControl.svelte +216 -0
  46. package/src/chat/UsageDetails.svelte +76 -0
  47. package/src/chat/chat-attachments.svelte.ts +74 -0
  48. package/src/chat/chat-panel-history.svelte.ts +430 -0
  49. package/src/chat/chat-panel-props.ts +32 -0
  50. package/src/chat/index.ts +6 -0
  51. package/src/chat/run-session-history.ts +56 -0
  52. package/src/chat/run-session-types.ts +69 -0
  53. package/src/chat/run-session.svelte.ts +494 -0
  54. package/src/chat/test-support/chat-fixture.ts +178 -0
  55. package/src/chat/test-support/conversation-fixtures.ts +32 -0
  56. package/src/chat/test-support/run-session-fixture.ts +112 -0
  57. package/src/components/ui/alert/alert-action.svelte +20 -0
  58. package/src/components/ui/alert/alert-description.svelte +23 -0
  59. package/src/components/ui/alert/alert-title.svelte +23 -0
  60. package/src/components/ui/alert/alert.svelte +43 -0
  61. package/src/components/ui/alert/index.ts +17 -0
  62. package/src/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  63. package/src/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  64. package/src/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
  65. package/src/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  66. package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  67. package/src/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  68. package/src/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
  69. package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
  70. package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  71. package/src/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  72. package/src/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  73. package/src/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  74. package/src/components/ui/alert-dialog/index.ts +40 -0
  75. package/src/components/ui/button/button.svelte +81 -0
  76. package/src/components/ui/button/index.ts +17 -0
  77. package/src/components/ui/dialog/dialog-close.svelte +11 -0
  78. package/src/components/ui/dialog/dialog-content.svelte +48 -0
  79. package/src/components/ui/dialog/dialog-description.svelte +17 -0
  80. package/src/components/ui/dialog/dialog-footer.svelte +32 -0
  81. package/src/components/ui/dialog/dialog-header.svelte +20 -0
  82. package/src/components/ui/dialog/dialog-overlay.svelte +17 -0
  83. package/src/components/ui/dialog/dialog-portal.svelte +7 -0
  84. package/src/components/ui/dialog/dialog-title.svelte +17 -0
  85. package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
  86. package/src/components/ui/dialog/dialog.svelte +7 -0
  87. package/src/components/ui/dialog/index.ts +34 -0
  88. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  89. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  90. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  91. package/src/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  92. package/src/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  93. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  94. package/src/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  95. package/src/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  96. package/src/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +17 -0
  97. package/src/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  98. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  99. package/src/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  100. package/src/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  101. package/src/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  102. package/src/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  103. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  104. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  105. package/src/components/ui/dropdown-menu/index.ts +54 -0
  106. package/src/components/ui/empty/empty-content.svelte +23 -0
  107. package/src/components/ui/empty/empty-description.svelte +23 -0
  108. package/src/components/ui/empty/empty-header.svelte +20 -0
  109. package/src/components/ui/empty/empty-media.svelte +41 -0
  110. package/src/components/ui/empty/empty-title.svelte +20 -0
  111. package/src/components/ui/empty/empty.svelte +23 -0
  112. package/src/components/ui/empty/index.ts +22 -0
  113. package/src/components/ui/field/field-content.svelte +20 -0
  114. package/src/components/ui/field/field-description.svelte +25 -0
  115. package/src/components/ui/field/field-error.svelte +58 -0
  116. package/src/components/ui/field/field-group.svelte +23 -0
  117. package/src/components/ui/field/field-label.svelte +25 -0
  118. package/src/components/ui/field/field-legend.svelte +24 -0
  119. package/src/components/ui/field/field-separator.svelte +35 -0
  120. package/src/components/ui/field/field-set.svelte +20 -0
  121. package/src/components/ui/field/field-title.svelte +20 -0
  122. package/src/components/ui/field/field.svelte +47 -0
  123. package/src/components/ui/field/index.ts +33 -0
  124. package/src/components/ui/input/index.ts +7 -0
  125. package/src/components/ui/input/input.svelte +48 -0
  126. package/src/components/ui/label/index.ts +7 -0
  127. package/src/components/ui/label/label.svelte +20 -0
  128. package/src/components/ui/select/index.ts +40 -0
  129. package/src/components/ui/select/select-content.svelte +45 -0
  130. package/src/components/ui/select/select-group-heading.svelte +21 -0
  131. package/src/components/ui/select/select-group.svelte +17 -0
  132. package/src/components/ui/select/select-item.svelte +43 -0
  133. package/src/components/ui/select/select-label.svelte +20 -0
  134. package/src/components/ui/select/select-portal.svelte +7 -0
  135. package/src/components/ui/select/select-scroll-down-button.svelte +20 -0
  136. package/src/components/ui/select/select-scroll-up-button.svelte +20 -0
  137. package/src/components/ui/select/select-separator.svelte +18 -0
  138. package/src/components/ui/select/select-trigger.svelte +29 -0
  139. package/src/components/ui/select/select-value.svelte +7 -0
  140. package/src/components/ui/select/select.svelte +11 -0
  141. package/src/components/ui/separator/index.ts +7 -0
  142. package/src/components/ui/separator/separator.svelte +21 -0
  143. package/src/components/ui/sonner/index.ts +1 -0
  144. package/src/components/ui/sonner/sonner.svelte +40 -0
  145. package/src/components/ui/switch/index.ts +7 -0
  146. package/src/components/ui/switch/switch.svelte +34 -0
  147. package/src/components/ui/textarea/index.ts +7 -0
  148. package/src/components/ui/textarea/textarea.svelte +22 -0
  149. package/src/context.ts +34 -0
  150. package/src/dev/Demo.svelte +47 -0
  151. package/src/dev/main.ts +7 -0
  152. package/src/i18n/en.ts +645 -0
  153. package/src/i18n/index.ts +28 -0
  154. package/src/i18n/zh-CN.ts +610 -0
  155. package/src/index.ts +10 -0
  156. package/src/logs/LogPanel.svelte +103 -0
  157. package/src/logs/LogSettings.svelte +58 -0
  158. package/src/logs/index.ts +5 -0
  159. package/src/logs/logger.ts +96 -0
  160. package/src/markdown.ts +8 -0
  161. package/src/mcp/McpClientEditor.svelte +204 -0
  162. package/src/mcp/McpClientSettings.svelte +418 -0
  163. package/src/mcp/McpServerSettings.svelte +363 -0
  164. package/src/mcp/config.ts +26 -0
  165. package/src/mcp/index.ts +3 -0
  166. package/src/mcp/input.ts +74 -0
  167. package/src/notify.svelte.ts +43 -0
  168. package/src/ports/contract.ts +174 -0
  169. package/src/ports/fake-optional.ts +81 -0
  170. package/src/ports/fake.ts +488 -0
  171. package/src/ports/index.ts +7 -0
  172. package/src/ports/search.ts +56 -0
  173. package/src/ports/skill-sources.ts +45 -0
  174. package/src/ports/test-support/fake-fixture.ts +63 -0
  175. package/src/providers/EndpointKeysDialog.svelte +357 -0
  176. package/src/providers/EndpointModels.svelte +311 -0
  177. package/src/providers/EndpointPanel.svelte +266 -0
  178. package/src/providers/ModelPickerDialog.svelte +230 -0
  179. package/src/providers/ModelPricing.svelte +125 -0
  180. package/src/providers/ModelSelect.svelte +448 -0
  181. package/src/providers/ModelSettingsDialog.svelte +388 -0
  182. package/src/providers/ProviderDialogs.svelte +120 -0
  183. package/src/providers/ProviderList.svelte +159 -0
  184. package/src/providers/ProvidersSettings.svelte +491 -0
  185. package/src/providers/RequestConfigDialog.svelte +278 -0
  186. package/src/providers/SecretInput.svelte +284 -0
  187. package/src/providers/draft.ts +355 -0
  188. package/src/providers/edit-session.ts +170 -0
  189. package/src/providers/index.ts +5 -0
  190. package/src/providers/reasoning.ts +55 -0
  191. package/src/providers/revision.svelte.ts +6 -0
  192. package/src/providers/test-support/providers-fixture.ts +120 -0
  193. package/src/search/SearchAdvancedTools.svelte +182 -0
  194. package/src/search/SearchProviderCard.svelte +195 -0
  195. package/src/search/SearchSettings.svelte +500 -0
  196. package/src/search/index.ts +13 -0
  197. package/src/search/settings-flow.ts +67 -0
  198. package/src/session.ts +20 -0
  199. package/src/skills/SkillAdd.svelte +380 -0
  200. package/src/skills/SkillAuthoring.svelte +430 -0
  201. package/src/skills/SkillEditor.svelte +104 -0
  202. package/src/skills/SkillPreviewDialog.svelte +136 -0
  203. package/src/skills/SkillsSettings.svelte +147 -0
  204. package/src/skills/error.ts +32 -0
  205. package/src/skills/index.ts +4 -0
  206. package/src/skills/test-support/skills-fixture.ts +139 -0
  207. package/src/styles/kit.css +137 -0
  208. package/src/styles/tokens.css +22 -0
  209. package/src/tauri/index.ts +298 -0
  210. package/src/utils.ts +22 -0
  211. package/src/web/index.ts +441 -0
  212. package/README.md +0 -3
@@ -0,0 +1,317 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import XIcon from '@lucide/svelte/icons/x'
4
+ import { Button } from '#lib/components/ui/button/index.js'
5
+ import type { KitI18n } from '../i18n'
6
+ import type {
7
+ ContextUsage,
8
+ ImagePart,
9
+ ModelOption,
10
+ ModelRef,
11
+ PermissionMode,
12
+ ReasoningEffort,
13
+ SkillRef,
14
+ SkillsPort,
15
+ } from '../ports'
16
+ import { settingsTarget } from '../providers/revision.svelte'
17
+ import ChatComposer from './ChatComposer.svelte'
18
+ import ChatPanelMode from './ChatPanelMode.svelte'
19
+ import ChatPanelToolbar from './ChatPanelToolbar.svelte'
20
+ import ReasoningControl from './ReasoningControl.svelte'
21
+ import SkillPicker from './SkillPicker.svelte'
22
+ import UsageDetails from './UsageDetails.svelte'
23
+ import type { RunSession } from './run-session.svelte'
24
+
25
+ type Attached = ImagePart & { preview: string }
26
+ type Recovery = {
27
+ draft: string
28
+ images: Attached[]
29
+ droppedKey: string | null
30
+ contextKey: string | null
31
+ }
32
+
33
+ let {
34
+ i18n,
35
+ session,
36
+ purpose,
37
+ draft = $bindable(''),
38
+ canSend,
39
+ activeContext,
40
+ images,
41
+ vision,
42
+ recovery,
43
+ webSearch = $bindable(false),
44
+ generateImage = $bindable(false),
45
+ toolbar,
46
+ onToolbarChange,
47
+ paintingModel,
48
+ capability,
49
+ historyBusy,
50
+ onNewConversation,
51
+ attach,
52
+ onDraftInput,
53
+ onRemoveImage,
54
+ onRecoverDraft,
55
+ onDropContext,
56
+ hasSearch,
57
+ selected,
58
+ skillsBudget,
59
+ skillsPort,
60
+ onSkillsChange,
61
+ onSkillResolved,
62
+ mode,
63
+ allowedModes,
64
+ chooseMode,
65
+ reasoningEffort,
66
+ allowedEfforts,
67
+ reasoningOpen = $bindable(false),
68
+ chooseEffort,
69
+ currentContext,
70
+ picked,
71
+ onOpenSettings,
72
+ onSend,
73
+ onStop,
74
+ hasImages,
75
+ override,
76
+ }: {
77
+ i18n: KitI18n
78
+ session: RunSession
79
+ purpose: string
80
+ draft?: string
81
+ canSend: boolean
82
+ activeContext: { kind: string; id: string; label: string } | null
83
+ images: Attached[]
84
+ vision: boolean
85
+ recovery: Recovery | null
86
+ webSearch?: boolean
87
+ generateImage?: boolean
88
+ toolbar?: string[] | undefined
89
+ onToolbarChange?: ((ids: string[]) => void) | undefined
90
+ paintingModel: boolean
91
+ capability: ModelOption | null
92
+ historyBusy: boolean
93
+ onNewConversation: () => void
94
+ attach: (files: FileList | File[] | null | undefined) => Promise<void>
95
+ onDraftInput: () => void
96
+ onRemoveImage: (index: number) => void
97
+ onRecoverDraft: () => void
98
+ onDropContext: () => void
99
+ hasSearch: boolean
100
+ selected: SkillRef[]
101
+ skillsBudget: number
102
+ skillsPort?: SkillsPort | undefined
103
+ onSkillsChange: (refs: SkillRef[]) => void
104
+ onSkillResolved: () => void
105
+ mode: PermissionMode
106
+ allowedModes: PermissionMode[]
107
+ chooseMode: (mode: PermissionMode) => Promise<void>
108
+ reasoningEffort: ReasoningEffort
109
+ allowedEfforts: ReasoningEffort[]
110
+ reasoningOpen?: boolean
111
+ chooseEffort: (effort: ReasoningEffort) => void
112
+ currentContext: ContextUsage | null
113
+ picked: ModelRef | null
114
+ onOpenSettings?: (() => void) | undefined
115
+ onSend: () => void
116
+ onStop: () => void
117
+ hasImages: (files: FileList | null | undefined) => boolean
118
+ override?: Snippet | undefined
119
+ } = $props()
120
+ const t = $derived(i18n.t)
121
+ </script>
122
+
123
+ <ChatComposer
124
+ bind:value={draft}
125
+ label={t('chat.input')}
126
+ placeholder={t('chat.placeholder')}
127
+ submitLabel={t('chat.send')}
128
+ stopLabel={t('chat.stop')}
129
+ disabled={!canSend}
130
+ running={session.running}
131
+ expandLabel={t('chat.expandInput')}
132
+ collapseLabel={t('chat.collapseInput')}
133
+ onsubmit={onSend}
134
+ onstop={onStop}
135
+ oninput={onDraftInput}
136
+ onpaste={(event) => {
137
+ const files = event.clipboardData?.files
138
+ if (!hasImages(files)) return
139
+ event.preventDefault()
140
+ void attach(files)
141
+ }}
142
+ onfiles={(files) => void attach(files)}
143
+ {override}
144
+ >
145
+ {#snippet top()}
146
+ {#if activeContext}
147
+ <div class="context-chip">
148
+ <span title={activeContext.label}>{activeContext.label}</span><Button
149
+ type="button"
150
+ variant="ghost"
151
+ size="icon-sm"
152
+ aria-label={t('chat.context.remove', { label: activeContext.label })}
153
+ title={t('chat.context.remove', { label: activeContext.label })}
154
+ onclick={onDropContext}><XIcon /></Button
155
+ >
156
+ </div>
157
+ {/if}
158
+ {#if images.length > 0}
159
+ <ul class="image-tray">
160
+ {#each images as image, index (image.preview)}
161
+ <li>
162
+ <img src={image.preview} alt="" /><Button
163
+ type="button"
164
+ variant="secondary"
165
+ size="icon-xs"
166
+ aria-label={t('chat.removeImage', { index: index + 1 })}
167
+ title={t('chat.removeImage', { index: index + 1 })}
168
+ onclick={() => onRemoveImage(index)}><XIcon /></Button
169
+ >
170
+ </li>
171
+ {/each}
172
+ </ul>
173
+ {#if !vision}<p class="search-status" role="status">{t('chat.visionUnsupported')}</p>{/if}
174
+ {/if}
175
+ {#if recovery}
176
+ <div class="draft-recovery" role="status">
177
+ <span>{t('chat.draftRecoverable')}</span><Button
178
+ type="button"
179
+ variant="outline"
180
+ size="sm"
181
+ disabled={draft !== '' || images.length > 0}
182
+ onclick={onRecoverDraft}>{t('chat.restoreDraft')}</Button
183
+ >
184
+ </div>
185
+ {/if}
186
+ {#if hasSearch && purpose === 'chat' && session.search.status === 'searching'}<p
187
+ class="search-status"
188
+ role="status"
189
+ >
190
+ {t('chat.searching')}
191
+ </p>{/if}
192
+ {/snippet}
193
+ {#snippet tools()}
194
+ <ChatPanelToolbar
195
+ {i18n}
196
+ running={session.running}
197
+ {historyBusy}
198
+ {vision}
199
+ {paintingModel}
200
+ {capability}
201
+ {purpose}
202
+ {toolbar}
203
+ {onToolbarChange}
204
+ {hasSearch}
205
+ newConversation={onNewConversation}
206
+ {attach}
207
+ bind:webSearch
208
+ bind:generateImage
209
+ />
210
+ {#if skillsPort}<div class="skills-control">
211
+ <SkillPicker
212
+ port={skillsPort}
213
+ value={selected}
214
+ budget={skillsBudget}
215
+ changed={session.skillChanged}
216
+ onchange={onSkillsChange}
217
+ onresolved={onSkillResolved}
218
+ />
219
+ </div>{/if}
220
+ {/snippet}
221
+ {#snippet actions()}
222
+ <ChatPanelMode {i18n} {mode} {allowedModes} running={session.running} {chooseMode} />
223
+ {#if picked}
224
+ <ReasoningControl
225
+ value={reasoningEffort}
226
+ efforts={allowedEfforts}
227
+ disabled={session.running}
228
+ bind:open={reasoningOpen}
229
+ {i18n}
230
+ onchange={chooseEffort}
231
+ onOpenSettings={onOpenSettings
232
+ ? () => {
233
+ settingsTarget.value = { endpointId: picked.endpointId, modelId: picked.modelId }
234
+ onOpenSettings()
235
+ }
236
+ : undefined}
237
+ />
238
+ {/if}
239
+ <UsageDetails
240
+ context={currentContext}
241
+ modelName={capability?.name ?? capability?.modelId ?? ''}
242
+ usage={session.turns.at(-1)?.usage ?? null}
243
+ total={session.cumulativeUsage}
244
+ cost={session.turns.at(-1)?.cost ?? null}
245
+ totalCost={session.cumulativeCost}
246
+ />
247
+ {/snippet}
248
+ </ChatComposer>
249
+
250
+ <style>
251
+ .context-chip {
252
+ display: flex;
253
+ align-items: center;
254
+ align-self: flex-start;
255
+ gap: 0.25rem;
256
+ max-width: calc(100% - 2rem);
257
+ padding-left: 0.5rem;
258
+ background: var(--color-muted);
259
+ border-radius: var(--kit-radius, var(--radius));
260
+ }
261
+ .context-chip span {
262
+ min-width: 0;
263
+ overflow: hidden;
264
+ text-overflow: ellipsis;
265
+ white-space: nowrap;
266
+ }
267
+ .context-chip :global(button) {
268
+ flex-shrink: 0;
269
+ }
270
+ .image-tray {
271
+ display: flex;
272
+ flex-wrap: wrap;
273
+ gap: 0.375rem;
274
+ margin: 0;
275
+ padding: 0;
276
+ list-style: none;
277
+ }
278
+ .image-tray li {
279
+ position: relative;
280
+ }
281
+ .image-tray img {
282
+ width: 3.5rem;
283
+ height: 3.5rem;
284
+ object-fit: cover;
285
+ border: 1px solid var(--color-border);
286
+ border-radius: var(--kit-radius, var(--radius));
287
+ }
288
+ .image-tray :global(button) {
289
+ position: absolute;
290
+ top: -0.375rem;
291
+ right: -0.375rem;
292
+ }
293
+ .skills-control {
294
+ min-width: 0;
295
+ max-width: 100%;
296
+ }
297
+ .skills-control :global(div:first-child) {
298
+ flex-wrap: wrap;
299
+ min-width: 0;
300
+ }
301
+ .skills-control :global([data-slot='select-trigger']) {
302
+ width: auto;
303
+ max-width: 100%;
304
+ }
305
+ .search-status,
306
+ .draft-recovery {
307
+ margin: 0;
308
+ color: var(--color-muted-foreground);
309
+ font-size: var(--kit-font-size-sm, 0.8125rem);
310
+ }
311
+ .draft-recovery {
312
+ display: flex;
313
+ flex-wrap: wrap;
314
+ align-items: center;
315
+ gap: 0.5rem;
316
+ }
317
+ </style>
@@ -0,0 +1,242 @@
1
+ <script lang="ts">
2
+ import { Button } from '#lib/components/ui/button/index.js'
3
+ import { Input } from '#lib/components/ui/input/index.js'
4
+ import { Textarea } from '#lib/components/ui/textarea/index.js'
5
+ import * as Dialog from '#lib/components/ui/dialog/index.js'
6
+ import * as Field from '#lib/components/ui/field/index.js'
7
+ import ModelSelect from '../providers/ModelSelect.svelte'
8
+ import type { KitI18n } from '../i18n'
9
+ import type { ConversationClient, ConversationSummary, ModelOption, ModelRef } from '../ports'
10
+ let {
11
+ i18n,
12
+ history,
13
+ session,
14
+ conversations,
15
+ title,
16
+ historyLoading,
17
+ historyBusy,
18
+ historyError,
19
+ retryHistory,
20
+ open,
21
+ rename,
22
+ remove,
23
+ actionBusy,
24
+ replyDialog,
25
+ modelOptions,
26
+ modelError,
27
+ noModels,
28
+ validReplyModel,
29
+ submitReply,
30
+ reloadModels,
31
+ historyOpen = $bindable(false),
32
+ renaming = $bindable(false),
33
+ renameTitle = $bindable(''),
34
+ replyOpen = $bindable(false),
35
+ editDraft = $bindable(''),
36
+ replyModel = $bindable<ModelRef | null>(null),
37
+ targetLanguage = $bindable(''),
38
+ replyError,
39
+ }: {
40
+ i18n: KitI18n
41
+ history: ConversationClient | undefined
42
+ session: { running: boolean; conversationId: string | null }
43
+ conversations: ConversationSummary[]
44
+ title: string
45
+ historyLoading: boolean
46
+ historyBusy: boolean
47
+ historyError: string | null
48
+ retryHistory: () => Promise<void>
49
+ open: (id: string) => Promise<void>
50
+ rename: () => Promise<void>
51
+ remove: () => Promise<void>
52
+ actionBusy: boolean
53
+ replyDialog: 'edit' | 'translate' | null
54
+ modelOptions: ModelOption[] | null
55
+ modelError: string | null
56
+ noModels: boolean
57
+ validReplyModel: boolean
58
+ submitReply: () => Promise<void>
59
+ reloadModels: () => void
60
+ historyOpen?: boolean
61
+ renaming?: boolean
62
+ renameTitle?: string
63
+ replyOpen?: boolean
64
+ editDraft?: string
65
+ replyModel?: ModelRef | null
66
+ targetLanguage?: string
67
+ replyError: string | null
68
+ } = $props()
69
+ const t = $derived(i18n.t)
70
+ </script>
71
+
72
+ {#if history}
73
+ <Dialog.Root bind:open={historyOpen}>
74
+ <Dialog.Content showCloseButton={false}>
75
+ <Dialog.Header><Dialog.Title>{t('chat.history')}</Dialog.Title></Dialog.Header>
76
+ {#if historyLoading}<p role="status">{t('chat.historyLoading')}</p>{/if}
77
+ {#if historyError}<p role="alert">
78
+ {t('chat.historyFailed')}: {historyError}<Button
79
+ variant="outline"
80
+ size="sm"
81
+ disabled={historyBusy}
82
+ onclick={() => void retryHistory()}>{t('chat.retryHistory')}</Button
83
+ >
84
+ </p>{/if}
85
+ <div class="history-list">
86
+ {#each conversations as c (c.id)}<Button
87
+ variant="ghost"
88
+ title={c.title}
89
+ disabled={historyBusy || session.running}
90
+ onclick={() => void open(c.id)}><span>{c.title}</span></Button
91
+ >{/each}
92
+ </div>
93
+ {#if session.conversationId}
94
+ {#if renaming}
95
+ <form
96
+ onsubmit={(e) => {
97
+ e.preventDefault()
98
+ void rename()
99
+ }}
100
+ >
101
+ <Field.FieldGroup
102
+ ><Field.Field
103
+ ><Field.FieldLabel for="chat-conversation-title"
104
+ >{t('chat.conversationTitle')}</Field.FieldLabel
105
+ ><Input
106
+ id="chat-conversation-title"
107
+ bind:value={renameTitle}
108
+ disabled={historyBusy}
109
+ /></Field.Field
110
+ ><Button type="submit" disabled={historyBusy || renameTitle.trim() === ''}
111
+ >{t('common.confirm')}</Button
112
+ ></Field.FieldGroup
113
+ >
114
+ </form>
115
+ {:else}<Button
116
+ variant="outline"
117
+ disabled={historyBusy || session.running}
118
+ onclick={() => {
119
+ renameTitle = title
120
+ renaming = true
121
+ }}>{t('chat.rename')}</Button
122
+ >{/if}
123
+ <Button
124
+ variant="destructive"
125
+ disabled={historyBusy || session.running}
126
+ onclick={() => void remove()}>{t('chat.delete')}</Button
127
+ >
128
+ {/if}
129
+ <Dialog.Footer
130
+ ><Button variant="ghost" onclick={() => (historyOpen = false)}>{t('chat.close')}</Button
131
+ ></Dialog.Footer
132
+ >
133
+ </Dialog.Content>
134
+ </Dialog.Root>
135
+ {/if}
136
+ <Dialog.Root bind:open={replyOpen}>
137
+ <Dialog.Content class="reply-dialog" showCloseButton={false}>
138
+ <Dialog.Header>
139
+ <Dialog.Title>{t(replyDialog === 'edit' ? 'chat.editReply' : 'chat.translate')}</Dialog.Title>
140
+ <Dialog.Description
141
+ >{t(
142
+ replyDialog === 'edit' ? 'chat.editReplyHint' : 'chat.candidateHint',
143
+ )}</Dialog.Description
144
+ >
145
+ </Dialog.Header>
146
+ <form
147
+ onsubmit={(event) => {
148
+ event.preventDefault()
149
+ void submitReply()
150
+ }}
151
+ >
152
+ <Field.FieldGroup>
153
+ {#if replyDialog === 'edit'}
154
+ <Field.Field
155
+ ><Field.FieldLabel for="chat-reply-edit">{t('chat.replyText')}</Field.FieldLabel
156
+ ><Textarea
157
+ id="chat-reply-edit"
158
+ bind:value={editDraft}
159
+ rows={8}
160
+ maxlength={24_000}
161
+ disabled={actionBusy}
162
+ /></Field.Field
163
+ >
164
+ {:else}
165
+ <Field.Field
166
+ ><Field.FieldLabel>{t('chat.replyModel')}</Field.FieldLabel><ModelSelect
167
+ value={replyModel}
168
+ options={modelOptions}
169
+ disabled={actionBusy || modelError !== null}
170
+ onchange={(value) => (replyModel = value)}
171
+ /></Field.Field
172
+ >
173
+ {#if noModels}<p role="status">{t('chat.noModels')}</p>{/if}
174
+ {#if modelError}<p role="alert">
175
+ {modelError}<Button type="button" variant="outline" size="sm" onclick={reloadModels}
176
+ >{t('chat.retryModels')}</Button
177
+ >
178
+ </p>{/if}
179
+ <Field.Field
180
+ ><Field.FieldLabel for="chat-target-language"
181
+ >{t('chat.targetLanguage')}</Field.FieldLabel
182
+ ><Input
183
+ id="chat-target-language"
184
+ bind:value={targetLanguage}
185
+ maxlength={80}
186
+ disabled={actionBusy}
187
+ /></Field.Field
188
+ >
189
+ {/if}
190
+ {#if replyError}<p role="alert" class="text-destructive">{replyError}</p>{/if}
191
+ <Dialog.Footer>
192
+ <Button
193
+ type="button"
194
+ variant="ghost"
195
+ disabled={actionBusy}
196
+ onclick={() => (replyOpen = false)}>{t('common.cancel')}</Button
197
+ >
198
+ <Button
199
+ type="submit"
200
+ disabled={actionBusy ||
201
+ (replyDialog === 'edit'
202
+ ? !editDraft.trim() || editDraft.length > 24_000
203
+ : !validReplyModel || !targetLanguage.trim())}>{t('common.confirm')}</Button
204
+ >
205
+ </Dialog.Footer>
206
+ </Field.FieldGroup>
207
+ </form>
208
+ </Dialog.Content>
209
+ </Dialog.Root>
210
+
211
+ <style>
212
+ .history-list {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 0.25rem;
216
+ max-height: 40vh;
217
+ overflow-y: auto;
218
+ min-width: 0;
219
+ }
220
+ .history-list :global(button) {
221
+ justify-content: flex-start;
222
+ min-width: 0;
223
+ }
224
+ .history-list span {
225
+ min-width: 0;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ white-space: nowrap;
229
+ }
230
+ :global(.reply-dialog) {
231
+ max-height: calc(100dvh - 2rem);
232
+ overflow-y: auto;
233
+ }
234
+ :global(.reply-dialog form),
235
+ :global(.reply-dialog [data-slot='field-group']) {
236
+ min-width: 0;
237
+ }
238
+ :global(.reply-dialog textarea) {
239
+ max-height: 45dvh;
240
+ overflow-y: auto;
241
+ }
242
+ </style>
@@ -0,0 +1,132 @@
1
+ <script lang="ts">
2
+ import XIcon from '@lucide/svelte/icons/x'
3
+ import HistoryIcon from '@lucide/svelte/icons/history'
4
+ import { Button } from '#lib/components/ui/button/index.js'
5
+ import * as Alert from '#lib/components/ui/alert/index.js'
6
+ import type { KitI18n } from '../i18n'
7
+ import type { ConversationClient, ModelOption, ModelRef } from '../ports'
8
+ import ModelSelect from '../providers/ModelSelect.svelte'
9
+ import type { RunSession } from './run-session.svelte'
10
+
11
+ let {
12
+ i18n,
13
+ title,
14
+ session,
15
+ history,
16
+ historyBusy,
17
+ historyOpen,
18
+ historyError,
19
+ modelOptions,
20
+ modelError,
21
+ onHistory,
22
+ onClose,
23
+ onRetryHistory,
24
+ picked,
25
+ onPickModel,
26
+ onOpenSettings,
27
+ }: {
28
+ i18n: KitI18n
29
+ title: string
30
+ session: RunSession
31
+ history: ConversationClient | undefined
32
+ historyBusy: boolean
33
+ historyOpen: boolean
34
+ historyError: string | null
35
+ modelOptions: ModelOption[] | null
36
+ modelError: string | null
37
+ onHistory: () => void
38
+ onClose?: (() => void) | undefined
39
+ onRetryHistory: () => void
40
+ picked: ModelRef | null
41
+ onPickModel: (model: ModelRef) => void
42
+ onOpenSettings?: (() => void) | undefined
43
+ } = $props()
44
+ const t = $derived(i18n.t)
45
+ </script>
46
+
47
+ <header class="chat-header">
48
+ <div class="title-row">
49
+ <h2>{title}</h2>
50
+ {#if history}<Button
51
+ variant="ghost"
52
+ size="icon-sm"
53
+ disabled={session.running || historyBusy}
54
+ aria-label={t('chat.history')}
55
+ title={t('chat.history')}
56
+ onclick={onHistory}><HistoryIcon /></Button
57
+ >{/if}
58
+ {#if onClose}<Button
59
+ variant="ghost"
60
+ size="icon-sm"
61
+ aria-label={t('chat.close')}
62
+ title={t('chat.close')}
63
+ onclick={onClose}><XIcon /></Button
64
+ >{/if}
65
+ </div>
66
+ <div class="model-row">
67
+ <ModelSelect
68
+ value={picked}
69
+ options={modelOptions}
70
+ filter={{ modelType: 'chat' }}
71
+ disabled={session.running || modelError !== null}
72
+ onchange={onPickModel}
73
+ {...onOpenSettings ? { onOpenSettings } : {}}
74
+ />
75
+ </div>
76
+ </header>
77
+ {#if historyError && !historyOpen}
78
+ <Alert.Root variant="destructive">
79
+ <Alert.Title>{t('chat.historyFailed')}</Alert.Title>
80
+ <Alert.Description
81
+ >{historyError}<Button
82
+ variant="outline"
83
+ size="sm"
84
+ disabled={historyBusy || session.running}
85
+ onclick={onRetryHistory}>{t('chat.retryHistory')}</Button
86
+ ></Alert.Description
87
+ >
88
+ </Alert.Root>
89
+ {/if}
90
+ {#if session.actionError}<p role="alert" class="text-destructive text-kit-sm">
91
+ {t(`errors.${session.actionError}`)}
92
+ </p>{/if}
93
+ {#if session.historyRefreshError}<p role="alert" class="text-destructive text-kit-sm">
94
+ {t('chat.replyRefreshFailed')}<Button
95
+ variant="outline"
96
+ size="sm"
97
+ disabled={session.running || historyBusy}
98
+ onclick={() => session.conversationId && onRetryHistory()}>{t('chat.retryHistory')}</Button
99
+ >
100
+ </p>{/if}
101
+
102
+ <style>
103
+ .chat-header {
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 0.5rem;
107
+ flex: none;
108
+ min-width: 0;
109
+ }
110
+ .title-row {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.25rem;
114
+ min-width: 0;
115
+ }
116
+ .title-row h2 {
117
+ flex: 1;
118
+ min-width: 0;
119
+ margin: 0;
120
+ font: inherit;
121
+ font-weight: 600;
122
+ overflow: hidden;
123
+ text-overflow: ellipsis;
124
+ white-space: nowrap;
125
+ }
126
+ .title-row :global(button) {
127
+ flex-shrink: 0;
128
+ }
129
+ .model-row {
130
+ min-width: 0;
131
+ }
132
+ </style>