@ouraihub/aether-ui 0.1.0 → 0.1.2

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 (90) hide show
  1. package/package.json +3 -3
  2. package/src/chat/ApprovalCard.svelte +8 -2
  3. package/src/chat/ChatComposer.svelte +319 -0
  4. package/src/chat/ChatMessage.svelte +262 -0
  5. package/src/chat/ChatPanel.css +386 -0
  6. package/src/chat/ChatPanel.svelte +276 -1880
  7. package/src/chat/ChatPanelApprovals.svelte +28 -0
  8. package/src/chat/ChatPanelComposer.svelte +336 -0
  9. package/src/chat/ChatPanelDialogs.svelte +242 -0
  10. package/src/chat/ChatPanelHeader.svelte +108 -0
  11. package/src/chat/ChatPanelMessages.svelte +166 -0
  12. package/src/chat/ChatPanelMode.svelte +190 -0
  13. package/src/chat/ChatPanelState.svelte.ts +1 -0
  14. package/src/chat/ChatPanelToolbar.svelte +436 -0
  15. package/src/chat/ChatSurface.svelte +168 -0
  16. package/src/chat/ContextUsageStatus.svelte +97 -0
  17. package/src/chat/MessageItem.svelte +111 -214
  18. package/src/chat/ModelEffortPicker.svelte +326 -0
  19. package/src/chat/PickerEffort.svelte +71 -0
  20. package/src/chat/ReasoningControl.svelte +184 -0
  21. package/src/chat/ReasoningSlider.svelte +174 -0
  22. package/src/chat/SkillPicker.svelte +14 -6
  23. package/src/chat/UsageControl.svelte +223 -0
  24. package/src/chat/UsageDetails.svelte +44 -90
  25. package/src/chat/chat-attachments.svelte.ts +78 -0
  26. package/src/chat/chat-panel-history.svelte.ts +430 -0
  27. package/src/chat/chat-panel-models.svelte.ts +97 -0
  28. package/src/chat/chat-panel-props.ts +38 -0
  29. package/src/chat/context-status.ts +101 -0
  30. package/src/chat/icons/LICENSE-REMIX +186 -0
  31. package/src/chat/icons/LICENSE-SIMPLE-ICONS +30 -0
  32. package/src/chat/icons/PathIcon.svelte +8 -0
  33. package/src/chat/icons/effort-icons.ts +65 -0
  34. package/src/chat/icons/status-icons.ts +105 -0
  35. package/src/chat/index.ts +25 -1
  36. package/src/chat/model-effort.ts +177 -0
  37. package/src/chat/run-session-history.ts +56 -0
  38. package/src/chat/run-session-types.ts +69 -0
  39. package/src/chat/run-session.svelte.ts +73 -135
  40. package/src/chat/test-support/chat-fixture.ts +194 -0
  41. package/src/chat/test-support/conversation-fixtures.ts +32 -0
  42. package/src/chat/test-support/run-session-fixture.ts +112 -0
  43. package/src/dev/Demo.svelte +3 -1
  44. package/src/i18n/en-chat.d.ts +1 -0
  45. package/src/i18n/en-chat.js +201 -0
  46. package/src/i18n/en.ts +58 -180
  47. package/src/i18n/index.ts +7 -2
  48. package/src/i18n/zh-CN-chat.d.ts +1 -0
  49. package/src/i18n/zh-CN-chat.js +190 -0
  50. package/src/i18n/zh-CN.ts +8 -153
  51. package/src/logs/LogPanel.svelte +9 -3
  52. package/src/logs/LogSettings.svelte +3 -1
  53. package/src/mcp/McpClientEditor.svelte +204 -0
  54. package/src/mcp/McpClientSettings.svelte +57 -130
  55. package/src/mcp/McpServerSettings.svelte +47 -11
  56. package/src/mcp/input.ts +2 -1
  57. package/src/ports/contract.ts +11 -2
  58. package/src/ports/fake-optional.ts +81 -0
  59. package/src/ports/fake.ts +90 -86
  60. package/src/ports/search.ts +11 -2
  61. package/src/ports/test-support/fake-fixture.ts +63 -0
  62. package/src/providers/EndpointKeysDialog.svelte +27 -13
  63. package/src/providers/EndpointModels.svelte +311 -0
  64. package/src/providers/EndpointPanel.svelte +33 -258
  65. package/src/providers/ModelPickerDialog.svelte +59 -16
  66. package/src/providers/ModelPricing.svelte +125 -0
  67. package/src/providers/ModelSelect.svelte +22 -8
  68. package/src/providers/ModelSettingsDialog.svelte +61 -126
  69. package/src/providers/ProviderDialogs.svelte +120 -0
  70. package/src/providers/ProviderList.svelte +159 -0
  71. package/src/providers/ProvidersSettings.svelte +67 -207
  72. package/src/providers/RequestConfigDialog.svelte +40 -12
  73. package/src/providers/SecretInput.svelte +11 -3
  74. package/src/providers/draft.ts +39 -13
  75. package/src/providers/edit-session.ts +31 -7
  76. package/src/providers/reasoning.ts +18 -4
  77. package/src/providers/revision.svelte.ts +3 -1
  78. package/src/providers/test-support/providers-fixture.ts +120 -0
  79. package/src/search/SearchAdvancedTools.svelte +182 -0
  80. package/src/search/SearchProviderCard.svelte +195 -0
  81. package/src/search/SearchSettings.svelte +62 -265
  82. package/src/search/settings-flow.ts +8 -3
  83. package/src/skills/SkillAdd.svelte +51 -18
  84. package/src/skills/SkillAuthoring.svelte +69 -20
  85. package/src/skills/SkillEditor.svelte +13 -3
  86. package/src/skills/SkillPreviewDialog.svelte +19 -5
  87. package/src/skills/SkillsSettings.svelte +11 -4
  88. package/src/skills/test-support/skills-fixture.ts +139 -0
  89. package/src/tauri/index.ts +23 -7
  90. package/src/web/index.ts +90 -25
@@ -3,9 +3,8 @@
3
3
  import { Button } from '#lib/components/ui/button/index.js'
4
4
  import * as DropdownMenu from '#lib/components/ui/dropdown-menu/index.js'
5
5
  import { getKitContext } from '../context'
6
- import { renderMarkdown } from '../markdown'
7
- import ApprovalCard from './ApprovalCard.svelte'
8
- import type { ChatTurn, ToolItem } from './run-session.svelte'
6
+ import ChatMessage from './ChatMessage.svelte'
7
+ import type { ChatTurn } from './run-session.svelte'
9
8
  import type { ImageRef, ModelOption, ModelRef } from '../ports'
10
9
  import ModelSelect from '../providers/ModelSelect.svelte'
11
10
  import ExternalLinkIcon from '@lucide/svelte/icons/external-link'
@@ -29,7 +28,7 @@
29
28
 
30
29
  let {
31
30
  turn,
32
- onApprove,
31
+ latest = false,
33
32
  onInsert,
34
33
  onRetry,
35
34
  modelLabel,
@@ -50,7 +49,7 @@
50
49
  onExportMarkdown,
51
50
  }: {
52
51
  turn: ChatTurn
53
- onApprove: (tool: ToolItem, allow: boolean) => void
52
+ latest?: boolean
54
53
  onInsert?: ((markdown: string) => void) | undefined
55
54
  onRetry?: (() => void) | undefined
56
55
  modelLabel?: string
@@ -73,7 +72,6 @@
73
72
  } = $props()
74
73
  const { i18n, notify } = getKitContext()
75
74
  const { t } = i18n
76
- const html = $derived(renderMarkdown(turn.text))
77
75
  const savable = $derived(turn.text !== '' || turn.generated.length > 0)
78
76
  function safeUrl(value: string): string | null {
79
77
  try {
@@ -157,62 +155,75 @@
157
155
  </script>
158
156
 
159
157
  <li class="message-turn flex min-w-0 flex-col gap-2">
160
- {#if showInput && turn.images.length > 0}
161
- <ul class="user-images">
162
- {#each turn.images as image, index (index)}
163
- <li>
164
- {#if image.url}<a
165
- href={image.url}
166
- target="_blank"
167
- rel="noopener"
168
- onclick={(event) => openImage(event, image.url)}
169
- aria-label={t('chat.openImage', { index: index + 1 })}
170
- title={t('chat.openImage', { index: index + 1 })}><img src={image.url} alt="" /></a
171
- >{:else}<span class="image-expired">{t('chat.imageExpired')}</span>{/if}
172
- </li>
173
- {/each}
174
- </ul>
158
+ {#if showInput}
159
+ <ChatMessage
160
+ role="user"
161
+ text={turn.input}
162
+ showMoreLabel={t('chat.showMore')}
163
+ showLessLabel={t('chat.showLess')}
164
+ >
165
+ {#snippet attachments()}
166
+ {#if turn.images.length > 0}
167
+ <ul class="user-images">
168
+ {#each turn.images as image, index (index)}
169
+ <li>
170
+ {#if image.url}<a
171
+ href={image.url}
172
+ target="_blank"
173
+ rel="noopener"
174
+ onclick={(event) => openImage(event, image.url)}
175
+ aria-label={t('chat.openImage', { index: index + 1 })}
176
+ title={t('chat.openImage', { index: index + 1 })}
177
+ ><img src={image.url} alt="" /></a
178
+ >{:else}<span class="image-expired">{t('chat.imageExpired')}</span>{/if}
179
+ </li>
180
+ {/each}
181
+ </ul>
182
+ {/if}
183
+ {/snippet}
184
+ </ChatMessage>
175
185
  {/if}
176
- {#if showInput}<p class="user-message">{turn.input}</p>{/if}
177
- <div class="assistant-message" class:chosen>
178
- {#if modelLabel}<div class="reply-source">{modelLabel}</div>{/if}
179
- {#if turn.reasoningText}
180
- <details class="thinking text-muted-foreground text-kit-sm">
181
- <summary class="cursor-pointer">{t('chat.reasoning')}</summary>
182
- <p class="whitespace-pre-wrap">{turn.reasoningText}</p>
183
- </details>
184
- {/if}
185
- {#each turn.tools as tool (tool.callId)}
186
- {#if tool.pending === null}
187
- <ToolCallItem {tool} />
188
- {:else}
189
- <ApprovalCard {tool} onAnswer={(allow) => onApprove(tool, allow)} />
186
+ <ChatMessage
187
+ role="assistant"
188
+ text={turn.text}
189
+ label={modelLabel}
190
+ status={turn.status}
191
+ reasoning={turn.reasoningText}
192
+ reasoningLabel={t('chat.reasoning')}
193
+ selected={chosen}
194
+ {latest}
195
+ >
196
+ {#snippet process()}
197
+ {#if turn.tools.some((tool) => tool.pending === null)}
198
+ <details class="tool-process" open={turn.status === 'running'}>
199
+ <summary>{t('chat.toolProcess')}</summary>
200
+ {#each turn.tools.filter((tool) => tool.pending === null) as tool (tool.callId)}<ToolCallItem
201
+ {tool}
202
+ />{/each}
203
+ </details>
190
204
  {/if}
191
- {/each}
192
- {#if turn.generated.length > 0}
193
- <ul class="generated-images" aria-label={t('chat.generatedImages')}>
194
- {#each turn.generated as image, index (index)}
195
- <li>
196
- {#if image.url}<a
197
- href={image.url}
198
- target="_blank"
199
- rel="noopener"
200
- onclick={(event) => openImage(event, image.url)}
201
- aria-label={t('chat.openGeneratedImage', { index: index + 1 })}
202
- title={t('chat.openGeneratedImage', { index: index + 1 })}><img src={image.url} alt="" /></a
203
- >{:else}<span class="image-expired">{t('chat.imageExpired')}</span>{/if}
204
- </li>
205
- {/each}
206
- </ul>
207
- {/if}
208
- <div
209
- class="markdown"
210
- {@attach (node) => {
211
- node.innerHTML = html
212
- }}
213
- ></div>
214
- {#if savable || (onRetry && turn.status !== 'running') || onUseContext || onDelete}
215
- <div class="message-actions">
205
+ {/snippet}
206
+ {#snippet attachments()}
207
+ {#if turn.generated.length > 0}
208
+ <ul class="generated-images" aria-label={t('chat.generatedImages')}>
209
+ {#each turn.generated as image, index (index)}
210
+ <li>
211
+ {#if image.url}<a
212
+ href={image.url}
213
+ target="_blank"
214
+ rel="noopener"
215
+ onclick={(event) => openImage(event, image.url)}
216
+ aria-label={t('chat.openGeneratedImage', { index: index + 1 })}
217
+ title={t('chat.openGeneratedImage', { index: index + 1 })}
218
+ ><img src={image.url} alt="" /></a
219
+ >{:else}<span class="image-expired">{t('chat.imageExpired')}</span>{/if}
220
+ </li>
221
+ {/each}
222
+ </ul>
223
+ {/if}
224
+ {/snippet}
225
+ {#snippet actions()}
226
+ {#if savable || (onRetry && turn.status !== 'running') || onUseContext || onDelete}
216
227
  {#if turn.text !== ''}
217
228
  <Button
218
229
  variant="ghost"
@@ -226,8 +237,13 @@
226
237
  {/if}
227
238
  {#if onRetry && turn.status !== 'running'}
228
239
  {@const label = t(turn.status === 'failed' ? 'chat.retry' : 'chat.regenerate')}
229
- <Button variant="ghost" size="icon-xs" title={label} aria-label={label} disabled={busy} onclick={onRetry}
230
- ><RotateCcwIcon /></Button
240
+ <Button
241
+ variant="ghost"
242
+ size="icon-xs"
243
+ title={label}
244
+ aria-label={label}
245
+ disabled={busy}
246
+ onclick={onRetry}><RotateCcwIcon /></Button
231
247
  >
232
248
  {/if}
233
249
  {#if onSwitchModel}<ModelSelect
@@ -259,7 +275,8 @@
259
275
  aria-label={t('chat.useContext')}
260
276
  aria-pressed={chosen}
261
277
  disabled={busy || turn.status !== 'completed'}
262
- onclick={onUseContext}><ThumbsUpIcon class={chosen ? 'fill-primary text-primary' : ''} /></Button
278
+ onclick={onUseContext}
279
+ ><ThumbsUpIcon class={chosen ? 'fill-primary text-primary' : ''} /></Button
263
280
  >{/if}
264
281
  {#if onSaveNote && savable}<Button
265
282
  variant="ghost"
@@ -303,20 +320,26 @@
303
320
  <DropdownMenu.Content class="w-auto">
304
321
  {#if onEdit || onBranch || onCopyConversation}
305
322
  <DropdownMenu.Group>
306
- {#if onEdit}<DropdownMenu.Item disabled={busy || !editable} onSelect={() => onEdit?.()}
323
+ {#if onEdit}<DropdownMenu.Item
324
+ disabled={busy || !editable}
325
+ onSelect={() => onEdit?.()}
307
326
  ><FilePenLineIcon />{t('chat.editReply')}</DropdownMenu.Item
308
327
  >{/if}
309
328
  {#if onBranch}<DropdownMenu.Item disabled={busy} onSelect={() => onBranch?.()}
310
329
  ><SplitIcon />{t('chat.branch')}</DropdownMenu.Item
311
330
  >{/if}
312
- {#if onCopyConversation}<DropdownMenu.Item disabled={busy} onSelect={() => onCopyConversation?.()}
331
+ {#if onCopyConversation}<DropdownMenu.Item
332
+ disabled={busy}
333
+ onSelect={() => onCopyConversation?.()}
313
334
  ><CopyPlusIcon />{t('chat.copyConversation')}</DropdownMenu.Item
314
335
  >{/if}
315
336
  </DropdownMenu.Group>
316
337
  <DropdownMenu.Separator />
317
338
  {/if}
318
339
  <DropdownMenu.Sub>
319
- <DropdownMenu.SubTrigger disabled={!savable}><SaveIcon />{t('chat.save')}</DropdownMenu.SubTrigger>
340
+ <DropdownMenu.SubTrigger disabled={!savable}
341
+ ><SaveIcon />{t('chat.save')}</DropdownMenu.SubTrigger
342
+ >
320
343
  <DropdownMenu.SubContent>
321
344
  <DropdownMenu.Item disabled={!turn.text} onSelect={() => void download()}
322
345
  >{t('chat.saveFile')}</DropdownMenu.Item
@@ -329,7 +352,8 @@
329
352
  </DropdownMenu.Sub>
330
353
  <DropdownMenu.Separator />
331
354
  <DropdownMenu.Sub>
332
- <DropdownMenu.SubTrigger disabled={!turn.text}><UploadIcon />{t('chat.export')}</DropdownMenu.SubTrigger
355
+ <DropdownMenu.SubTrigger disabled={!turn.text}
356
+ ><UploadIcon />{t('chat.export')}</DropdownMenu.SubTrigger
333
357
  >
334
358
  <DropdownMenu.SubContent>
335
359
  <DropdownMenu.Item disabled={!turn.text} onSelect={() => void download()}
@@ -340,9 +364,11 @@
340
364
  </DropdownMenu.Content>
341
365
  </DropdownMenu.Root>
342
366
  {/if}
343
- <span class="text-muted-foreground text-kit-sm" role="status">{copied ? t('chat.copied') : ''}</span>
344
- </div>
345
- {/if}
367
+ <span class="text-muted-foreground text-kit-sm" role="status"
368
+ >{copied ? t('chat.copied') : ''}</span
369
+ >
370
+ {/if}
371
+ {/snippet}
346
372
  {#if turn.search.status === 'complete'}
347
373
  <section class="flex flex-col gap-1 text-kit-sm" aria-label={t('chat.sources')}>
348
374
  <p class="text-muted-foreground">{t('chat.sources')}</p>
@@ -353,7 +379,9 @@
353
379
  href={url}
354
380
  target="_blank"
355
381
  rel="noopener noreferrer"
356
- ><ExternalLinkIcon data-icon="inline-start" /><span class="truncate">{hit.title || url}</span></a
382
+ ><ExternalLinkIcon data-icon="inline-start" /><span class="truncate"
383
+ >{hit.title || url}</span
384
+ ></a
357
385
  >{/if}
358
386
  {/each}
359
387
  </section>
@@ -363,21 +391,19 @@
363
391
  {:else if turn.status === 'failed'}
364
392
  <div class="flex items-center gap-2">
365
393
  <p class="text-destructive text-kit-sm" role="alert">
366
- {turn.error === 'interrupted' ? t('chat.interrupted') : t(`errors.${turn.error ?? 'internal'}`)}
394
+ {turn.error === 'interrupted'
395
+ ? t('chat.interrupted')
396
+ : t(`errors.${turn.error ?? 'internal'}`)}
367
397
  </p>
368
398
  </div>
369
399
  {:else if turn.exhausted}
370
400
  <p class="text-muted-foreground text-kit-sm">{t('chat.exhausted')}</p>
371
401
  {/if}
372
- </div>
402
+ </ChatMessage>
373
403
  </li>
374
404
 
375
405
  <style>
376
- .message-turn,
377
- .user-message,
378
- .assistant-message,
379
- .markdown {
380
- box-sizing: border-box;
406
+ .message-turn {
381
407
  min-width: 0;
382
408
  max-width: 100%;
383
409
  overflow-wrap: anywhere;
@@ -440,144 +466,15 @@
440
466
  font-size: var(--kit-font-size-sm, 0.8125rem);
441
467
  text-align: center;
442
468
  }
443
- .user-message {
444
- align-self: flex-end;
445
- margin: 0;
446
- padding: 0.625rem 0.75rem;
447
- border-radius: var(--kit-radius);
448
- background: var(--color-primary);
449
- color: var(--color-primary-foreground);
450
- font-size: var(--kit-text-base);
451
- white-space: pre-wrap;
452
- }
453
- .assistant-message {
454
- display: flex;
455
- flex-direction: column;
456
- gap: 0.75rem;
457
- padding: 0.75rem;
458
- border-radius: var(--kit-radius);
459
- background: var(--color-muted);
460
- color: var(--color-foreground);
461
- }
462
- .assistant-message.chosen {
463
- outline: 1px solid var(--color-primary);
464
- }
465
- .reply-source {
466
- font-size: var(--kit-text-sm);
469
+ .tool-process {
467
470
  color: var(--color-muted-foreground);
468
- overflow-wrap: anywhere;
469
- }
470
- .thinking {
471
- min-width: 0;
472
- }
473
- .message-actions {
474
- display: flex;
475
- align-items: center;
476
- flex-wrap: wrap;
477
- gap: 0.25rem;
478
- border-top: 1px solid var(--color-border);
479
- padding-top: 0.5rem;
480
- }
481
- .markdown {
482
- font-size: var(--kit-text-base);
483
- line-height: 1.6;
484
- }
485
- .markdown :global(p),
486
- .markdown :global(ul),
487
- .markdown :global(ol),
488
- .markdown :global(blockquote),
489
- .markdown :global(pre),
490
- .markdown :global(table) {
491
- margin: 0.75em 0;
492
- }
493
- .markdown :global(h1),
494
- .markdown :global(h2),
495
- .markdown :global(h3),
496
- .markdown :global(h4),
497
- .markdown :global(h5),
498
- .markdown :global(h6) {
499
- margin: 1em 0 0.5em;
500
- font-weight: 600;
501
- line-height: 1.35;
502
- }
503
- .markdown :global(h1) {
504
- font-size: 1.4em;
505
- }
506
- .markdown :global(h2) {
507
- font-size: 1.25em;
508
- }
509
- .markdown :global(h3) {
510
- font-size: 1.1em;
511
- }
512
- .markdown :global(ul),
513
- .markdown :global(ol) {
514
- padding-inline-start: 1.5em;
515
- }
516
- .markdown :global(ul) {
517
- list-style: disc;
518
- }
519
- .markdown :global(ol) {
520
- list-style: decimal;
521
- }
522
- .markdown :global(li) {
523
- margin: 0.25em 0;
524
- }
525
- .markdown :global(li::marker) {
526
- color: var(--color-muted-foreground);
527
- }
528
- .markdown :global(blockquote) {
529
- border-inline-start: 3px solid var(--color-border);
530
- padding-inline-start: 0.75em;
531
- color: var(--color-muted-foreground);
532
- }
533
- .markdown :global(a) {
534
- color: var(--color-primary);
535
- text-decoration: underline;
536
- overflow-wrap: anywhere;
537
- }
538
- .markdown :global(code) {
539
- border-radius: var(--kit-radius-sm);
540
- background: var(--color-background);
541
- padding: 0.1em 0.3em;
542
- font-size: 0.95em;
543
- }
544
- .markdown :global(pre) {
545
- max-width: 100%;
546
- overflow-x: auto;
547
- padding: 0.75em;
548
- border: 1px solid var(--color-border);
549
- border-radius: var(--kit-radius-sm);
550
- background: var(--color-background);
551
- white-space: pre;
552
- overflow-wrap: normal;
553
- }
554
- .markdown :global(pre code) {
555
- padding: 0;
556
- background: none;
557
- }
558
- .markdown :global(table) {
559
- display: block;
560
- max-width: 100%;
561
- overflow-x: auto;
562
- border-collapse: collapse;
563
- }
564
- .markdown :global(th),
565
- .markdown :global(td) {
566
- padding: 0.4em 0.6em;
567
- border: 1px solid var(--color-border);
568
- }
569
- .markdown :global(th) {
570
- font-weight: 600;
571
- }
572
- .markdown :global(img),
573
- .markdown :global(video) {
574
- max-width: 100%;
575
- height: auto;
471
+ font-size: var(--kit-text-sm);
576
472
  }
577
- .markdown :global(:first-child) {
578
- margin-top: 0;
473
+ .tool-process summary {
474
+ cursor: pointer;
579
475
  }
580
- .markdown :global(:last-child) {
581
- margin-bottom: 0;
476
+ .tool-process summary:focus-visible {
477
+ outline: 2px solid var(--color-ring);
478
+ outline-offset: 2px;
582
479
  }
583
480
  </style>