@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
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import { Button } from '#lib/components/ui/button/index.js'
3
+ import * as Empty from '#lib/components/ui/empty/index.js'
4
+ import * as Alert from '#lib/components/ui/alert/index.js'
5
+ import type { ConversationClient, ImageRef, ModelOption, ModelRef } from '../ports'
6
+ import type { ChatTurn, RunSession } from './run-session.svelte'
7
+ import MessageItem from './MessageItem.svelte'
8
+ import type { KitI18n } from '../i18n'
9
+
10
+ let {
11
+ i18n,
12
+ session,
13
+ groups,
14
+ purpose,
15
+ history,
16
+ restoreBlocked,
17
+ actionBusy,
18
+ modelOptions,
19
+ modelError,
20
+ capability,
21
+ noModels,
22
+ onRetryModels,
23
+ onOpenSettings,
24
+ onInsert,
25
+ onSaveNote,
26
+ onExportMarkdown,
27
+ sourceLabel,
28
+ sourceModel,
29
+ canEdit,
30
+ onRetry,
31
+ onEdit,
32
+ onBranch,
33
+ onCopyConversation,
34
+ onSwitchModel,
35
+ onTranslate,
36
+ onDelete,
37
+ onUseContext,
38
+ }: {
39
+ i18n: KitI18n
40
+ session: RunSession
41
+ groups: [string, ChatTurn[]][]
42
+ purpose: string
43
+ history: ConversationClient | undefined
44
+ restoreBlocked: boolean
45
+ actionBusy: boolean
46
+ modelOptions: ModelOption[] | null
47
+ modelError: string | null
48
+ noModels: boolean
49
+ capability: ModelOption | null
50
+ onRetryModels: () => void
51
+ onOpenSettings?: (() => void) | undefined
52
+ onInsert?: ((markdown: string) => void) | undefined
53
+ onSaveNote?: ((markdown: string, images: ImageRef[]) => Promise<void> | void) | undefined
54
+ onExportMarkdown?: ((markdown: string) => Promise<void> | void) | undefined
55
+ sourceLabel: (turn: ChatTurn) => string
56
+ sourceModel: (turn: ChatTurn) => ModelRef | null
57
+ canEdit: (turn: ChatTurn) => boolean
58
+ onRetry?: (turn: ChatTurn) => void
59
+ onEdit?: (turn: ChatTurn) => void
60
+ onBranch?: (turn: ChatTurn) => void
61
+ onCopyConversation?: (turn: ChatTurn) => void
62
+ onSwitchModel?: (turn: ChatTurn, model: ModelRef) => void
63
+ onTranslate?: (turn: ChatTurn) => void
64
+ onDelete?: (turn: ChatTurn) => void
65
+ onUseContext?: (turn: ChatTurn) => void
66
+ } = $props()
67
+ const t = $derived(i18n.t)
68
+ </script>
69
+
70
+ {#if session.turns.length > 0}
71
+ <ol class="turns">
72
+ {#each groups as [questionId, replies] (questionId)}
73
+ {@const candidates = replies.filter((reply) => reply.kind === 'reply').length > 1}
74
+ {#each replies as turn, i (turn.id)}
75
+ <MessageItem
76
+ {turn}
77
+ {onInsert}
78
+ {onSaveNote}
79
+ {onExportMarkdown}
80
+ latest={turn.id === session.turns.at(-1)?.id}
81
+ modelLabel={turn.kind === 'translation'
82
+ ? `${t('chat.translationReply')} · ${sourceLabel(turn)}`
83
+ : sourceLabel(turn)}
84
+ showInput={i === 0 && turn.kind !== 'translation'}
85
+ chosen={candidates && session.selectedTurnId === turn.id}
86
+ busy={actionBusy}
87
+ editable={canEdit(turn)}
88
+ onRetry={purpose === 'chat' && (turn.persisted || capability !== null)
89
+ ? () => onRetry?.(turn)
90
+ : undefined}
91
+ onEdit={purpose === 'chat' && history && session.conversationId && turn.persisted
92
+ ? () => onEdit?.(turn)
93
+ : undefined}
94
+ onBranch={purpose === 'chat' && history && session.conversationId && turn.persisted
95
+ ? () => onBranch?.(turn)
96
+ : undefined}
97
+ onCopyConversation={purpose === 'chat' &&
98
+ history &&
99
+ session.conversationId &&
100
+ turn.persisted
101
+ ? () => onCopyConversation?.(turn)
102
+ : undefined}
103
+ onSwitchModel={purpose === 'chat' && history && session.conversationId && turn.persisted
104
+ ? (picked: ModelRef) => onSwitchModel?.(turn, picked)
105
+ : undefined}
106
+ modelOptions={turn.images.length > 0
107
+ ? (modelOptions?.filter((option) => option.canVision) ?? null)
108
+ : modelOptions}
109
+ sourceModel={sourceModel(turn)}
110
+ onTranslate={purpose === 'chat' && history && session.conversationId && turn.persisted
111
+ ? () => onTranslate?.(turn)
112
+ : undefined}
113
+ onDelete={purpose === 'chat' && history && session.conversationId && turn.persisted
114
+ ? () => onDelete?.(turn)
115
+ : undefined}
116
+ onUseContext={candidates &&
117
+ purpose === 'chat' &&
118
+ history &&
119
+ session.conversationId &&
120
+ turn.persisted
121
+ ? () => onUseContext?.(turn)
122
+ : undefined}
123
+ />
124
+ {/each}
125
+ {/each}
126
+ </ol>
127
+ {/if}
128
+ {#if modelError}
129
+ <Alert.Root variant="destructive">
130
+ <Alert.Title>{t('chat.modelsFailed')}</Alert.Title>
131
+ <Alert.Description
132
+ >{modelError}<Button variant="outline" size="sm" onclick={onRetryModels}
133
+ >{t('chat.retryModels')}</Button
134
+ ></Alert.Description
135
+ >
136
+ </Alert.Root>
137
+ {:else if modelOptions === null}
138
+ <p class="empty-message" role="status">{t('chat.modelsLoading')}</p>
139
+ {:else if noModels}
140
+ <Empty.Root
141
+ ><Empty.Description>{t('chat.noModels')}</Empty.Description>{#if onOpenSettings}<Button
142
+ variant="outline"
143
+ size="sm"
144
+ onclick={onOpenSettings}>{t('chat.openSettings')}</Button
145
+ >{/if}</Empty.Root
146
+ >
147
+ {:else if session.turns.length === 0 && !restoreBlocked}
148
+ <p class="empty-message">{t('chat.empty')}</p>
149
+ {/if}
150
+
151
+ <style>
152
+ .turns {
153
+ display: flex;
154
+ flex-direction: column;
155
+ gap: 1rem;
156
+ padding: 0;
157
+ margin: 0;
158
+ list-style: none;
159
+ }
160
+ .empty-message {
161
+ color: var(--color-muted-foreground);
162
+ text-align: center;
163
+ padding: 1.5rem 0.5rem;
164
+ margin: 0;
165
+ }
166
+ </style>
@@ -0,0 +1,190 @@
1
+ <script lang="ts">
2
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'
3
+ import CheckIcon from '@lucide/svelte/icons/check'
4
+ import ShieldIcon from '@lucide/svelte/icons/shield'
5
+ import HandIcon from '@lucide/svelte/icons/hand'
6
+ import RouteIcon from '@lucide/svelte/icons/route'
7
+ import FilePenIcon from '@lucide/svelte/icons/file-pen'
8
+ import ShieldCheckIcon from '@lucide/svelte/icons/shield-check'
9
+ import ShieldAlertIcon from '@lucide/svelte/icons/shield-alert'
10
+ import InfoIcon from '@lucide/svelte/icons/info'
11
+ import * as DropdownMenu from '#lib/components/ui/dropdown-menu/index.js'
12
+ import { Button } from '#lib/components/ui/button/index.js'
13
+ import type { KitI18n } from '../i18n'
14
+ import type { PermissionMode } from '../ports'
15
+ let {
16
+ i18n,
17
+ mode,
18
+ allowedModes,
19
+ running,
20
+ chooseMode,
21
+ }: {
22
+ i18n: KitI18n
23
+ mode: PermissionMode
24
+ allowedModes: PermissionMode[]
25
+ running: boolean
26
+ chooseMode: (mode: PermissionMode) => Promise<void>
27
+ } = $props()
28
+ const t = $derived(i18n.t)
29
+ </script>
30
+
31
+ {#if mode === 'plan'}<span class="plan-badge" role="status">{t('chat.mode.planBadge')}</span>{/if}
32
+ {#if allowedModes.length > 1 || allowedModes.includes('full_access')}
33
+ <DropdownMenu.Root>
34
+ <DropdownMenu.Trigger disabled={running}>
35
+ {#snippet child({ props })}<Button
36
+ {...props}
37
+ variant="ghost"
38
+ size="sm"
39
+ class={mode === 'full_access' ? 'mode-danger' : undefined}
40
+ aria-label={`${t('chat.mode.label')}: ${t(`chat.mode.${mode}`)}`}
41
+ ><ShieldIcon data-icon="inline-start" /><span data-composer-mode-label>
42
+ {t(`chat.mode.${mode}`)}</span
43
+ ><ChevronDownIcon data-composer-mode-label data-icon="inline-end" /></Button
44
+ >{/snippet}
45
+ </DropdownMenu.Trigger>
46
+ <DropdownMenu.Content side="top" align="end" class="mode-menu">
47
+ <DropdownMenu.Group aria-label={t('chat.mode.label')}>
48
+ {#each allowedModes as option (option)}
49
+ <DropdownMenu.Item
50
+ class={option === 'full_access' ? 'mode-option mode-danger' : 'mode-option'}
51
+ aria-current={option === mode ? 'true' : undefined}
52
+ onSelect={() => void chooseMode(option)}
53
+ >{#if option === 'default'}<HandIcon />
54
+ {:else if option === 'plan'}<RouteIcon />
55
+ {:else if option === 'accept_edits'}<FilePenIcon />
56
+ {:else if option === 'auto'}<ShieldCheckIcon />
57
+ {:else}<ShieldAlertIcon />{/if}
58
+ <span class="mode-name">{t(`chat.mode.${option}`)}</span>
59
+ <span class="mode-description">{t(`chat.mode.${option}Hint`)}</span>
60
+ <span class="mode-notice">
61
+ {#if option === 'auto' || option === 'full_access'}
62
+ <span
63
+ title={t(option === 'auto' ? 'chat.mode.ruleNotice' : 'chat.mode.fullAccessBody')}
64
+ >
65
+ <InfoIcon />
66
+ </span>
67
+ {/if}
68
+ </span>
69
+ <span class="mode-check"
70
+ >{#if option === mode}<CheckIcon />{/if}</span
71
+ ></DropdownMenu.Item
72
+ >
73
+ {/each}
74
+ </DropdownMenu.Group>
75
+ <div class="mode-footer">
76
+ <span>{t('chat.mode.label')}</span>
77
+ <span class="mode-keys"
78
+ ><kbd>↑↓</kbd>{t('chat.mode.navigate')}<kbd>↵</kbd>{t('chat.mode.confirm')}<kbd>Esc</kbd
79
+ >{t('chat.mode.close')}</span
80
+ >
81
+ </div>
82
+ </DropdownMenu.Content>
83
+ </DropdownMenu.Root>
84
+ {/if}
85
+
86
+ <style>
87
+ .plan-badge {
88
+ padding: 0.125rem 0.5rem;
89
+ border: 1px solid var(--color-border);
90
+ border-radius: 999px;
91
+ color: var(--color-muted-foreground);
92
+ font-size: var(--kit-font-size-sm, 0.8125rem);
93
+ white-space: nowrap;
94
+ }
95
+ :global(.mode-menu) {
96
+ width: min(35rem, calc(100vw - 1rem));
97
+ padding: 0.375rem;
98
+ border-radius: 0.75rem;
99
+ }
100
+ :global(.mode-option) {
101
+ display: grid;
102
+ grid-template-columns: 1rem 7.5rem minmax(0, 1fr) 1rem 0.875rem;
103
+ align-items: center;
104
+ column-gap: 0.5rem;
105
+ min-height: 2.125rem;
106
+ padding: 0.375rem 0.5rem;
107
+ border-radius: 0.5rem;
108
+ font-size: var(--kit-font-size-sm, 0.8125rem);
109
+ }
110
+ :global(.mode-option[aria-current='true']) {
111
+ background: var(--color-muted);
112
+ }
113
+ :global(.mode-option > svg) {
114
+ color: var(--color-muted-foreground);
115
+ width: 0.875rem;
116
+ height: 0.875rem;
117
+ }
118
+ .mode-name {
119
+ white-space: nowrap;
120
+ }
121
+ .mode-description {
122
+ color: var(--color-muted-foreground);
123
+ font-size: var(--kit-font-size-xs, 0.75rem);
124
+ text-align: right;
125
+ line-height: 1.5;
126
+ }
127
+ .mode-notice,
128
+ .mode-check {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ color: var(--color-muted-foreground);
133
+ }
134
+ .mode-notice {
135
+ color: var(--color-warning, var(--color-muted-foreground));
136
+ }
137
+ .mode-notice :global(svg),
138
+ .mode-check :global(svg) {
139
+ display: block;
140
+ width: 0.8125rem;
141
+ height: 0.8125rem;
142
+ }
143
+ :global(.mode-danger),
144
+ :global(.mode-danger) :is(.mode-name, .mode-description, .mode-notice),
145
+ :global(.mode-option.mode-danger > svg) {
146
+ color: var(--color-destructive);
147
+ }
148
+ .mode-footer {
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: center;
152
+ gap: 0.75rem;
153
+ padding: 0.625rem 0.375rem 0.125rem;
154
+ color: var(--color-muted-foreground);
155
+ font-size: var(--kit-font-size-xs, 0.75rem);
156
+ }
157
+ .mode-keys {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 0.25rem;
161
+ }
162
+ .mode-keys kbd {
163
+ padding: 0 0.25rem;
164
+ border-radius: 0.25rem;
165
+ background: var(--color-muted);
166
+ font: inherit;
167
+ }
168
+ .mode-keys kbd:not(:first-child) {
169
+ margin-left: 0.375rem;
170
+ }
171
+ @media (max-width: 480px) {
172
+ :global(.mode-option) {
173
+ grid-template-columns: 1rem minmax(0, 1fr) 1rem 0.875rem;
174
+ row-gap: 0.125rem;
175
+ }
176
+ .mode-description {
177
+ grid-column: 2;
178
+ grid-row: 2;
179
+ text-align: left;
180
+ }
181
+ .mode-notice {
182
+ grid-column: 3;
183
+ grid-row: 1;
184
+ }
185
+ .mode-check {
186
+ grid-column: 4;
187
+ grid-row: 1;
188
+ }
189
+ }
190
+ </style>
@@ -0,0 +1 @@
1
+ // Chat panel state implementation.