@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
@@ -1,29 +1,20 @@
1
1
  <script lang="ts">
2
2
  // Provider settings (docs/33): list on the left, detail on the right, every field saves itself.
3
3
  import { Button } from '#lib/components/ui/button/index.js'
4
- import * as Dialog from '#lib/components/ui/dialog/index.js'
5
- import * as DropdownMenu from '#lib/components/ui/dropdown-menu/index.js'
6
4
  import * as Empty from '#lib/components/ui/empty/index.js'
7
5
  import * as Field from '#lib/components/ui/field/index.js'
8
- import { Input } from '#lib/components/ui/input/index.js'
9
6
  import * as Select from '#lib/components/ui/select/index.js'
10
7
  import { Switch } from '#lib/components/ui/switch/index.js'
11
- import { Textarea } from '#lib/components/ui/textarea/index.js'
12
- import EllipsisIcon from '@lucide/svelte/icons/ellipsis-vertical'
13
- import SquarePenIcon from '@lucide/svelte/icons/square-pen'
14
- import Trash2Icon from '@lucide/svelte/icons/trash-2'
15
- import UserPenIcon from '@lucide/svelte/icons/user-pen'
16
- import PlusIcon from '@lucide/svelte/icons/plus'
17
- import SearchIcon from '@lucide/svelte/icons/search'
18
8
  import Settings2Icon from '@lucide/svelte/icons/settings-2'
19
- import SlidersHorizontalIcon from '@lucide/svelte/icons/sliders-horizontal'
9
+ import PlusIcon from '@lucide/svelte/icons/plus'
20
10
  import { getKitContext } from '../context'
21
11
  import { isKitClientError, type ProbeResult, type Provider, type ProviderPreset } from '../ports'
22
- import { cn } from '../utils'
23
12
  import { newEndpoint, savable, toDraft, validateModel, type ProviderDraft } from './draft'
24
13
  import { createProviderEditSession, type ProviderEditSession } from './edit-session'
25
14
  import EndpointPanel, { type CheckOutcome, type CheckState } from './EndpointPanel.svelte'
26
15
  import ModelPickerDialog from './ModelPickerDialog.svelte'
16
+ import ProviderDialogs from './ProviderDialogs.svelte'
17
+ import ProviderList from './ProviderList.svelte'
27
18
  import { providersRevision, settingsTarget } from './revision.svelte'
28
19
 
29
20
  let {
@@ -39,7 +30,6 @@
39
30
  const { ports, i18n, notify } = getKitContext()
40
31
  const { t } = i18n
41
32
  const port = ports.providers
42
- const FILTERS = ['all', 'enabled', 'disabled'] as const
43
33
 
44
34
  let providers = $state<Provider[]>([])
45
35
  let draft = $state<ProviderDraft | null>(null)
@@ -53,26 +43,15 @@
53
43
  /** Last rejected model setting, named in the banner (docs/41 §4.3). */
54
44
  let invalid = $state<{ model: string | null; message: string } | null>(null)
55
45
  let enabledSaving = $state(false)
56
- let query = $state('')
57
- let filter = $state<(typeof FILTERS)[number]>('all')
58
46
  let addOpen = $state(false)
59
47
  let newName = $state('')
60
48
  let infoOpen = $state(false)
61
49
  let editName = $state('')
62
50
  let notesOpen = $state(false)
63
51
  let editNotes = $state('')
64
- /** Provider whose row menu is open (hover ⋯ or right-click). */
65
- let menuFor = $state<string | null>(null)
66
52
  let editSession: ProviderEditSession | null = null
67
53
  let probeGeneration = 0
68
54
 
69
- const visible = $derived(
70
- providers.filter(
71
- (p) =>
72
- p.name.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()) &&
73
- (filter === 'all' || (filter === 'enabled') === p.enabled),
74
- ),
75
- )
76
55
  const endpoint = $derived(draft?.endpoints[active] ?? null)
77
56
 
78
57
  function errorText(error: unknown): string {
@@ -87,25 +66,26 @@
87
66
  pricing: true,
88
67
  }
89
68
  /** A rejected model setting: the backend names field + reason; the model is `entity` or found by the local rules. */
90
- function modelError(error: unknown, sent: ProviderDraft): { model: string | null; message: string } | null {
69
+ function modelError(
70
+ error: unknown,
71
+ sent: ProviderDraft,
72
+ ): { model: string | null; message: string } | null {
91
73
  if (!isKitClientError(error) || error.code !== 'bad_request') return null
92
74
  const { field, reason, entity } = error.details
93
75
  if (!field || !reason || !Object.hasOwn(MODEL_FIELDS, field)) return null
94
- const models = sent.endpoints.flatMap((e) => e.models.map((model) => ({ model, protocol: e.protocol })))
76
+ const models = sent.endpoints.flatMap((e) =>
77
+ e.models.map((model) => ({ model, protocol: e.protocol })),
78
+ )
95
79
  const found =
96
80
  models.find(({ model }) => model.id === entity) ??
97
81
  models.find(({ model, protocol }) =>
98
- validateModel(model, protocol).some((item) => item.field === field && item.reason === reason),
82
+ validateModel(model, protocol).some(
83
+ (item) => item.field === field && item.reason === reason,
84
+ ),
99
85
  )
100
86
  const model = found ? (found.model.name ?? found.model.id) : null
101
87
  return { model, message: t(`providers.error.${field}.${reason}`) }
102
88
  }
103
- function status(p: Provider): string | null {
104
- if (!p.enabled) return null
105
- return p.endpoints.length === 0 || p.endpoints.some((e) => !e.keys.some((k) => k.enabled && k.readable))
106
- ? 'bg-warning'
107
- : 'bg-success'
108
- }
109
89
  function notifyChanged() {
110
90
  providersRevision.value++
111
91
  onchanged?.()
@@ -151,7 +131,13 @@
151
131
  }
152
132
  async function select(p: Provider) {
153
133
  if (p.id === draft?.id) return
154
- if (failed && !(await notify.confirm({ title: t('providers.discard_title'), body: t('providers.discard_body') })))
134
+ if (
135
+ failed &&
136
+ !(await notify.confirm({
137
+ title: t('providers.discard_title'),
138
+ body: t('providers.discard_body'),
139
+ }))
140
+ )
155
141
  return
156
142
  load(toDraft(p))
157
143
  }
@@ -166,11 +152,14 @@
166
152
  name: preset.name,
167
153
  presetId: preset.id,
168
154
  enabled: false,
169
- endpoints: [{ slug: 'ep-1', baseUrl: preset.baseUrl, protocol: preset.protocol, models: [] }],
155
+ endpoints: [
156
+ { slug: 'ep-1', baseUrl: preset.baseUrl, protocol: preset.protocol, models: [] },
157
+ ],
170
158
  })
171
159
  }
172
160
  const target = settingsTarget.value
173
- const owner = target && providers.find((p) => p.endpoints.some((e) => e.id === target.endpointId))
161
+ const owner =
162
+ target && providers.find((p) => p.endpoints.some((e) => e.id === target.endpointId))
174
163
  if (!owner) settingsTarget.value = null
175
164
  load(owner ? toDraft(owner) : providers[0] ? toDraft(providers[0]) : null)
176
165
  // EndpointPanel opens the target model's dialog when it mounts (and clears the target).
@@ -184,7 +173,8 @@
184
173
  }
185
174
  }
186
175
  const keyUrl = $derived(
187
- presets.find((preset) => preset.id === providers.find((p) => p.id === draft?.id)?.presetId)?.apiKeyUrl,
176
+ presets.find((preset) => preset.id === providers.find((p) => p.id === draft?.id)?.presetId)
177
+ ?.apiKeyUrl,
188
178
  )
189
179
  $effect(() => {
190
180
  if (port) void refresh()
@@ -246,7 +236,6 @@
246
236
  }
247
237
  /** Row menu actions act on that row, so select it first (honours the unsaved-changes prompt). */
248
238
  async function rowAction(p: Provider, action: 'edit' | 'notes' | 'delete') {
249
- menuFor = null
250
239
  await select(p)
251
240
  if (draft?.id !== p.id) return
252
241
  if (action === 'delete') return remove()
@@ -359,101 +348,20 @@
359
348
  : { slug, value: { state: 'error', message: errorText(error) } }
360
349
  }
361
350
  }
362
- const probed = $derived(probe ? (draft?.endpoints.find((e) => e.slug === probe?.slug) ?? null) : null)
351
+ const probed = $derived(
352
+ probe ? (draft?.endpoints.find((e) => e.slug === probe?.slug) ?? null) : null,
353
+ )
363
354
  </script>
364
355
 
365
356
  {#if port}
366
357
  <div class="flex min-h-0 min-w-0 flex-col gap-4 sm:flex-row">
367
- <nav class="flex w-full shrink-0 flex-col gap-1 sm:w-60" aria-label={t('providers.title')}>
368
- <div class="mb-1 flex items-center gap-1.5">
369
- <div class="relative min-w-0 flex-1">
370
- <SearchIcon
371
- class="text-muted-foreground pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2"
372
- />
373
- <Input
374
- class="pl-8"
375
- placeholder={t('providers.search')}
376
- aria-label={t('providers.search')}
377
- bind:value={query}
378
- autocomplete="off"
379
- />
380
- </div>
381
- <Select.Root type="single" value={filter} onValueChange={(value) => (filter = value as typeof filter)}>
382
- <Select.Trigger
383
- class={cn('shrink-0 px-2 [&>svg:last-child]:hidden', filter !== 'all' && 'bg-accent')}
384
- aria-label={t('providers.filter')}
385
- title={t(`providers.filter_${filter}`)}
386
- >
387
- <SlidersHorizontalIcon />
388
- </Select.Trigger>
389
- <Select.Content>
390
- <Select.Group>
391
- {#each FILTERS as f (f)}<Select.Item value={f} label={t(`providers.filter_${f}`)} />{/each}
392
- </Select.Group>
393
- </Select.Content>
394
- </Select.Root>
395
- </div>
396
- <ul class="flex min-w-0 flex-col gap-0.5">
397
- {#each visible as p (p.id)}
398
- {@const dot = status(p)}
399
- <li
400
- class="group/row relative"
401
- oncontextmenu={(event) => {
402
- event.preventDefault()
403
- menuFor = p.id
404
- }}
405
- >
406
- <button
407
- type="button"
408
- class={cn(
409
- 'hover:bg-muted flex h-9 w-full min-w-0 items-center gap-2.5 rounded-kit px-2 pr-8 text-left text-kit-base',
410
- draft?.id === p.id && 'bg-muted font-medium',
411
- )}
412
- aria-current={draft?.id === p.id ? 'true' : undefined}
413
- onclick={() => void select(p)}
414
- >
415
- <span
416
- aria-hidden="true"
417
- class="bg-primary/10 text-primary flex size-6 shrink-0 items-center justify-center rounded-md text-kit-xs font-semibold"
418
- >{p.name.slice(0, 1).toUpperCase()}</span
419
- >
420
- <span class={cn('min-w-0 flex-1 truncate', !p.enabled && 'text-muted-foreground')}>{p.name}</span>
421
- {#if dot}<span class={cn('size-1.5 shrink-0 rounded-full', dot)}></span><span class="sr-only"
422
- >{t(dot === 'bg-success' ? 'providers.filter_enabled' : 'providers.needs_setup')}</span
423
- >{/if}
424
- </button>
425
- <DropdownMenu.Root open={menuFor === p.id} onOpenChange={(open) => (menuFor = open ? p.id : null)}>
426
- <DropdownMenu.Trigger>
427
- {#snippet child({ props })}<button
428
- {...props}
429
- type="button"
430
- class={cn(
431
- 'text-muted-foreground hover:text-foreground hover:bg-accent absolute top-1/2 right-1 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md opacity-0 group-hover/row:opacity-100 focus-visible:opacity-100',
432
- menuFor === p.id && 'opacity-100',
433
- )}
434
- aria-label={t('providers.row_menu', { name: p.name })}
435
- title={t('providers.row_menu', { name: p.name })}><EllipsisIcon class="size-4" /></button
436
- >{/snippet}
437
- </DropdownMenu.Trigger>
438
- <DropdownMenu.Content align="start" class="min-w-36">
439
- <DropdownMenu.Item onSelect={() => void rowAction(p, 'edit')}
440
- ><SquarePenIcon />{t('providers.edit')}</DropdownMenu.Item
441
- >
442
- <DropdownMenu.Item onSelect={() => void rowAction(p, 'notes')}
443
- ><UserPenIcon />{t('providers.notes')}</DropdownMenu.Item
444
- >
445
- {#if !p.presetId}<DropdownMenu.Item variant="destructive" onSelect={() => void rowAction(p, 'delete')}
446
- ><Trash2Icon />{t('providers.remove')}</DropdownMenu.Item
447
- >{/if}
448
- </DropdownMenu.Content>
449
- </DropdownMenu.Root>
450
- </li>
451
- {:else}
452
- <li class="text-muted-foreground px-2 py-4 text-center text-kit-sm">{t('providers.no_matches')}</li>
453
- {/each}
454
- </ul>
455
- <Button variant="outline" class="mt-2" onclick={() => (addOpen = true)}><PlusIcon />{t('providers.add')}</Button>
456
- </nav>
358
+ <ProviderList
359
+ {providers}
360
+ selectedId={draft?.id ?? null}
361
+ onselect={(provider) => void select(provider)}
362
+ onaction={(provider, action) => void rowAction(provider, action)}
363
+ onadd={() => (addOpen = true)}
364
+ />
457
365
 
458
366
  <section class="flex min-w-0 flex-1 flex-col gap-5">
459
367
  {#if draft}
@@ -483,7 +391,10 @@
483
391
  <p role="alert" class="text-destructive flex items-center gap-2 text-kit-sm">
484
392
  {invalid
485
393
  ? invalid.model
486
- ? t('providers.save_model_invalid', { model: invalid.model, error: invalid.message })
394
+ ? t('providers.save_model_invalid', {
395
+ model: invalid.model,
396
+ error: invalid.message,
397
+ })
487
398
  : invalid.message
488
399
  : t('providers.save_failed')}
489
400
  <Button variant="link" size="sm" class="h-auto p-0" onclick={() => void commit()}
@@ -493,15 +404,24 @@
493
404
  {/if}
494
405
  {#if draft.endpoints.length > 1}
495
406
  <Field.Field orientation="horizontal" class="gap-2">
496
- <Field.Label for="kit-provider-endpoint" class="shrink-0">{t('providers.endpoints')}</Field.Label>
497
- <Select.Root type="single" value={String(active)} onValueChange={(value) => (active = Number(value))}>
407
+ <Field.Label for="kit-provider-endpoint" class="shrink-0"
408
+ >{t('providers.endpoints')}</Field.Label
409
+ >
410
+ <Select.Root
411
+ type="single"
412
+ value={String(active)}
413
+ onValueChange={(value) => (active = Number(value))}
414
+ >
498
415
  <Select.Trigger id="kit-provider-endpoint" class="min-w-0 flex-1">
499
416
  <span class="truncate">{endpoint?.baseUrl || t('providers.endpoint_new')}</span>
500
417
  </Select.Trigger>
501
418
  <Select.Content>
502
419
  <Select.Group>
503
420
  {#each draft.endpoints as e, index (e.slug)}
504
- <Select.Item value={String(index)} label={e.baseUrl || t('providers.endpoint_new')} />
421
+ <Select.Item
422
+ value={String(index)}
423
+ label={e.baseUrl || t('providers.endpoint_new')}
424
+ />
505
425
  {/each}
506
426
  </Select.Group>
507
427
  </Select.Content>
@@ -554,78 +474,18 @@
554
474
  void commit()
555
475
  }}
556
476
  />
557
-
558
- <Dialog.Root bind:open={addOpen}>
559
- <Dialog.Content class="sm:max-w-md">
560
- <Dialog.Header><Dialog.Title>{t('providers.add')}</Dialog.Title></Dialog.Header>
561
- <form
562
- class="contents"
563
- onsubmit={(event) => {
564
- event.preventDefault()
565
- void create()
566
- }}
567
- >
568
- <Field.Field>
569
- <Field.Label for="kit-provider-new">{t('providers.name')}</Field.Label>
570
- <Input id="kit-provider-new" maxlength={80} bind:value={newName} />
571
- </Field.Field>
572
- <Dialog.Footer>
573
- <Button type="button" variant="outline" onclick={() => (addOpen = false)}>{t('common.cancel')}</Button>
574
- <Button type="submit" disabled={!newName.trim()}>{t('common.confirm')}</Button>
575
- </Dialog.Footer>
576
- </form>
577
- </Dialog.Content>
578
- </Dialog.Root>
579
-
580
- <Dialog.Root bind:open={infoOpen}>
581
- <Dialog.Content class="sm:max-w-md">
582
- <Dialog.Header><Dialog.Title>{t('providers.settings')}</Dialog.Title></Dialog.Header>
583
- <form
584
- class="contents"
585
- onsubmit={(event) => {
586
- event.preventDefault()
587
- rename()
588
- }}
589
- >
590
- <Field.Field>
591
- <Field.Label for="kit-provider-name">{t('providers.name')}</Field.Label>
592
- <Input id="kit-provider-name" maxlength={80} bind:value={editName} />
593
- </Field.Field>
594
- <Dialog.Footer class="sm:justify-between">
595
- {#if !providers.find((p) => p.id === draft?.id)?.presetId}<Button
596
- type="button"
597
- variant="ghost"
598
- class="text-destructive"
599
- onclick={() => void remove()}>{t('providers.delete')}</Button
600
- >{:else}<span></span>{/if}
601
- <Button type="submit" disabled={!editName.trim()}>{t('providers.save')}</Button>
602
- </Dialog.Footer>
603
- </form>
604
- </Dialog.Content>
605
- </Dialog.Root>
606
-
607
- <Dialog.Root bind:open={notesOpen}>
608
- <Dialog.Content class="sm:max-w-lg">
609
- <Dialog.Header><Dialog.Title>{t('providers.notes')}</Dialog.Title></Dialog.Header>
610
- <form
611
- class="contents"
612
- onsubmit={(event) => {
613
- event.preventDefault()
614
- saveNotes()
615
- }}
616
- >
617
- <Textarea
618
- class="min-h-64 font-mono text-kit-sm"
619
- aria-label={t('providers.notes')}
620
- placeholder={t('providers.notes_placeholder')}
621
- maxlength={10000}
622
- bind:value={editNotes}
623
- />
624
- <Dialog.Footer>
625
- <Button type="button" variant="outline" onclick={() => (notesOpen = false)}>{t('common.cancel')}</Button>
626
- <Button type="submit">{t('providers.save')}</Button>
627
- </Dialog.Footer>
628
- </form>
629
- </Dialog.Content>
630
- </Dialog.Root>
477
+ <ProviderDialogs
478
+ bind:addOpen
479
+ bind:infoOpen
480
+ bind:notesOpen
481
+ bind:newName
482
+ bind:editName
483
+ bind:editNotes
484
+ {draft}
485
+ {providers}
486
+ {create}
487
+ {rename}
488
+ {saveNotes}
489
+ {remove}
490
+ />
631
491
  {/if}
@@ -84,7 +84,8 @@
84
84
  }
85
85
  }
86
86
  /** Same rule as the backend `image_base`: optional, http(s), ≤2048 bytes. */
87
- const badImage = (url: string) => !!url && (!validUrl(url) || new TextEncoder().encode(url).length > 2048)
87
+ const badImage = (url: string) =>
88
+ !!url && (!validUrl(url) || new TextEncoder().encode(url).length > 2048)
88
89
  function save() {
89
90
  error = null
90
91
  const url = baseUrl.trim()
@@ -125,13 +126,20 @@
125
126
  <Field.Group>
126
127
  <Field.Field>
127
128
  <Field.Label for="kit-endpoint-protocol">{t('providers.protocol')}</Field.Label>
128
- <Select.Root type="single" value={protocol} onValueChange={(value) => (protocol = value as Protocol)}>
129
+ <Select.Root
130
+ type="single"
131
+ value={protocol}
132
+ onValueChange={(value) => (protocol = value as Protocol)}
133
+ >
129
134
  <Select.Trigger id="kit-endpoint-protocol" class="w-full"
130
135
  >{t(`providers.protocol.${protocol}`)}</Select.Trigger
131
136
  >
132
137
  <Select.Content>
133
138
  <Select.Group>
134
- {#each PROTOCOLS as p (p)}<Select.Item value={p} label={t(`providers.protocol.${p}`)} />{/each}
139
+ {#each PROTOCOLS as p (p)}<Select.Item
140
+ value={p}
141
+ label={t(`providers.protocol.${p}`)}
142
+ />{/each}
135
143
  </Select.Group>
136
144
  </Select.Content>
137
145
  </Select.Root>
@@ -139,7 +147,9 @@
139
147
  <Field.Field>
140
148
  <div class="flex items-center gap-2">
141
149
  <Field.Label for="kit-request-url">{t('providers.base_url')}</Field.Label>
142
- <span class="bg-primary/10 text-primary rounded-kit px-1.5 text-kit-xs">{t('providers.default')}</span>
150
+ <span class="bg-primary/10 text-primary rounded-kit px-1.5 text-kit-xs"
151
+ >{t('providers.default')}</span
152
+ >
143
153
  </div>
144
154
  <CopyInput
145
155
  id="kit-request-url"
@@ -149,7 +159,9 @@
149
159
  />
150
160
  {#if validUrl(baseUrl)}
151
161
  <Field.Description class="truncate font-mono text-kit-xs"
152
- >{t('providers.base_url_hint', { url: `POST ${chatUrl(baseUrl, protocol)}` })}</Field.Description
162
+ >{t('providers.base_url_hint', {
163
+ url: `POST ${chatUrl(baseUrl, protocol)}`,
164
+ })}</Field.Description
153
165
  >
154
166
  {/if}
155
167
  </Field.Field>
@@ -171,26 +183,36 @@
171
183
  placeholder="https://example.com"
172
184
  bind:value={imageBaseUrl}
173
185
  />
174
- <Field.Description class="text-kit-xs">{t('providers.image_base_url_hint')}</Field.Description>
186
+ <Field.Description class="text-kit-xs"
187
+ >{t('providers.image_base_url_hint')}</Field.Description
188
+ >
175
189
  </Field.Field>
176
190
  <Field.Field>
177
- <Field.Label for="kit-request-image-edit-url">{t('providers.image_edit_base_url')}</Field.Label>
191
+ <Field.Label for="kit-request-image-edit-url"
192
+ >{t('providers.image_edit_base_url')}</Field.Label
193
+ >
178
194
  <CopyInput
179
195
  id="kit-request-image-edit-url"
180
196
  class="font-mono"
181
197
  placeholder="https://example.com"
182
198
  bind:value={imageEditBaseUrl}
183
199
  />
184
- <Field.Description class="text-kit-xs">{t('providers.image_edit_base_url_hint')}</Field.Description>
200
+ <Field.Description class="text-kit-xs"
201
+ >{t('providers.image_edit_base_url_hint')}</Field.Description
202
+ >
185
203
  </Field.Field>
186
204
  <Field.Field>
187
205
  <div class="flex items-center justify-between gap-2">
188
- <Field.Label for={json === null ? undefined : 'kit-request-headers'}>{t('providers.headers')}</Field.Label>
206
+ <Field.Label for={json === null ? undefined : 'kit-request-headers'}
207
+ >{t('providers.headers')}</Field.Label
208
+ >
189
209
  <Button
190
210
  variant="ghost"
191
211
  size="icon"
192
212
  title={json === null ? t('providers.headers_to_json') : t('providers.headers_to_list')}
193
- aria-label={json === null ? t('providers.headers_to_json') : t('providers.headers_to_list')}
213
+ aria-label={json === null
214
+ ? t('providers.headers_to_json')
215
+ : t('providers.headers_to_list')}
194
216
  onclick={toggle}
195
217
  >{#if json === null}<BracesIcon />{:else}<ListIcon />{/if}</Button
196
218
  >
@@ -222,10 +244,16 @@
222
244
  variant="ghost"
223
245
  class="self-start"
224
246
  disabled={rows.length >= 20}
225
- onclick={() => rows.push({ name: '', value: '' })}><PlusIcon />{t('providers.headers_add')}</Button
247
+ onclick={() => rows.push({ name: '', value: '' })}
248
+ ><PlusIcon />{t('providers.headers_add')}</Button
226
249
  >
227
250
  {:else}
228
- <Textarea id="kit-request-headers" class="min-h-32 font-mono text-xs" spellcheck={false} bind:value={json} />
251
+ <Textarea
252
+ id="kit-request-headers"
253
+ class="min-h-32 font-mono text-xs"
254
+ spellcheck={false}
255
+ bind:value={json}
256
+ />
229
257
  {/if}
230
258
  </Field.Field>
231
259
  </Field.Group>
@@ -83,7 +83,10 @@
83
83
  if (copying) await copy()
84
84
  } catch (error) {
85
85
  if (session.isCurrent(token))
86
- notify.toast('error', error instanceof Error ? error.message : t('providers.key_reveal_failed'))
86
+ notify.toast(
87
+ 'error',
88
+ error instanceof Error ? error.message : t('providers.key_reveal_failed'),
89
+ )
87
90
  } finally {
88
91
  if (session.isCurrent(token)) {
89
92
  busy = false
@@ -143,7 +146,10 @@
143
146
  if (!endpointId || !keyId) return
144
147
  const token = session.capture()
145
148
  if (
146
- !(await notify.confirm({ title: t('providers.key_delete'), body: t('providers.key_delete_confirm') })) ||
149
+ !(await notify.confirm({
150
+ title: t('providers.key_delete'),
151
+ body: t('providers.key_delete_confirm'),
152
+ })) ||
147
153
  !session.isCurrent(token)
148
154
  )
149
155
  return
@@ -189,7 +195,9 @@
189
195
  class="flex h-full min-w-0 flex-1 items-center gap-1.5 pl-2.5 text-left outline-none"
190
196
  onclick={() => manage(null)}
191
197
  >
192
- {#if primary.label}<span class="shrink-0 font-sans text-muted-foreground">{primary.label}</span>{/if}
198
+ {#if primary.label}<span class="shrink-0 font-sans text-muted-foreground"
199
+ >{primary.label}</span
200
+ >{/if}
193
201
  <span class="min-w-0 truncate">{plain ?? maskedText(primary, t)}</span>
194
202
  {#if keys.length > 1}<span class="shrink-0 font-sans text-kit-xs text-muted-foreground"
195
203
  >+{keys.length - 1}</span