@ouraihub/aether-ui 0.0.0-stage → 0.1.0

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 (179) 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 +23 -0
  28. package/src/chat/ChatPanel.svelte +2097 -0
  29. package/src/chat/MessageItem.svelte +583 -0
  30. package/src/chat/SkillPicker.svelte +126 -0
  31. package/src/chat/ToolCallItem.svelte +23 -0
  32. package/src/chat/UsageDetails.svelte +138 -0
  33. package/src/chat/index.ts +2 -0
  34. package/src/chat/run-session.svelte.ts +556 -0
  35. package/src/components/ui/alert/alert-action.svelte +20 -0
  36. package/src/components/ui/alert/alert-description.svelte +23 -0
  37. package/src/components/ui/alert/alert-title.svelte +23 -0
  38. package/src/components/ui/alert/alert.svelte +43 -0
  39. package/src/components/ui/alert/index.ts +17 -0
  40. package/src/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  41. package/src/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  42. package/src/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
  43. package/src/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  44. package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  45. package/src/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  46. package/src/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
  47. package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
  48. package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  49. package/src/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  50. package/src/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  51. package/src/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  52. package/src/components/ui/alert-dialog/index.ts +40 -0
  53. package/src/components/ui/button/button.svelte +81 -0
  54. package/src/components/ui/button/index.ts +17 -0
  55. package/src/components/ui/dialog/dialog-close.svelte +11 -0
  56. package/src/components/ui/dialog/dialog-content.svelte +48 -0
  57. package/src/components/ui/dialog/dialog-description.svelte +17 -0
  58. package/src/components/ui/dialog/dialog-footer.svelte +32 -0
  59. package/src/components/ui/dialog/dialog-header.svelte +20 -0
  60. package/src/components/ui/dialog/dialog-overlay.svelte +17 -0
  61. package/src/components/ui/dialog/dialog-portal.svelte +7 -0
  62. package/src/components/ui/dialog/dialog-title.svelte +17 -0
  63. package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
  64. package/src/components/ui/dialog/dialog.svelte +7 -0
  65. package/src/components/ui/dialog/index.ts +34 -0
  66. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  67. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  68. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  69. package/src/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  70. package/src/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  71. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  72. package/src/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  73. package/src/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  74. package/src/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +17 -0
  75. package/src/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  76. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  77. package/src/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  78. package/src/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  79. package/src/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  80. package/src/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  81. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  82. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  83. package/src/components/ui/dropdown-menu/index.ts +54 -0
  84. package/src/components/ui/empty/empty-content.svelte +23 -0
  85. package/src/components/ui/empty/empty-description.svelte +23 -0
  86. package/src/components/ui/empty/empty-header.svelte +20 -0
  87. package/src/components/ui/empty/empty-media.svelte +41 -0
  88. package/src/components/ui/empty/empty-title.svelte +20 -0
  89. package/src/components/ui/empty/empty.svelte +23 -0
  90. package/src/components/ui/empty/index.ts +22 -0
  91. package/src/components/ui/field/field-content.svelte +20 -0
  92. package/src/components/ui/field/field-description.svelte +25 -0
  93. package/src/components/ui/field/field-error.svelte +58 -0
  94. package/src/components/ui/field/field-group.svelte +23 -0
  95. package/src/components/ui/field/field-label.svelte +25 -0
  96. package/src/components/ui/field/field-legend.svelte +24 -0
  97. package/src/components/ui/field/field-separator.svelte +35 -0
  98. package/src/components/ui/field/field-set.svelte +20 -0
  99. package/src/components/ui/field/field-title.svelte +20 -0
  100. package/src/components/ui/field/field.svelte +47 -0
  101. package/src/components/ui/field/index.ts +33 -0
  102. package/src/components/ui/input/index.ts +7 -0
  103. package/src/components/ui/input/input.svelte +48 -0
  104. package/src/components/ui/label/index.ts +7 -0
  105. package/src/components/ui/label/label.svelte +20 -0
  106. package/src/components/ui/select/index.ts +40 -0
  107. package/src/components/ui/select/select-content.svelte +45 -0
  108. package/src/components/ui/select/select-group-heading.svelte +21 -0
  109. package/src/components/ui/select/select-group.svelte +17 -0
  110. package/src/components/ui/select/select-item.svelte +43 -0
  111. package/src/components/ui/select/select-label.svelte +20 -0
  112. package/src/components/ui/select/select-portal.svelte +7 -0
  113. package/src/components/ui/select/select-scroll-down-button.svelte +20 -0
  114. package/src/components/ui/select/select-scroll-up-button.svelte +20 -0
  115. package/src/components/ui/select/select-separator.svelte +18 -0
  116. package/src/components/ui/select/select-trigger.svelte +29 -0
  117. package/src/components/ui/select/select-value.svelte +7 -0
  118. package/src/components/ui/select/select.svelte +11 -0
  119. package/src/components/ui/separator/index.ts +7 -0
  120. package/src/components/ui/separator/separator.svelte +21 -0
  121. package/src/components/ui/sonner/index.ts +1 -0
  122. package/src/components/ui/sonner/sonner.svelte +40 -0
  123. package/src/components/ui/switch/index.ts +7 -0
  124. package/src/components/ui/switch/switch.svelte +34 -0
  125. package/src/components/ui/textarea/index.ts +7 -0
  126. package/src/components/ui/textarea/textarea.svelte +22 -0
  127. package/src/context.ts +34 -0
  128. package/src/dev/Demo.svelte +45 -0
  129. package/src/dev/main.ts +7 -0
  130. package/src/i18n/en.ts +617 -0
  131. package/src/i18n/index.ts +23 -0
  132. package/src/i18n/zh-CN.ts +599 -0
  133. package/src/index.ts +10 -0
  134. package/src/logs/LogPanel.svelte +97 -0
  135. package/src/logs/LogSettings.svelte +56 -0
  136. package/src/logs/index.ts +5 -0
  137. package/src/logs/logger.ts +96 -0
  138. package/src/markdown.ts +8 -0
  139. package/src/mcp/McpClientSettings.svelte +491 -0
  140. package/src/mcp/McpServerSettings.svelte +327 -0
  141. package/src/mcp/config.ts +26 -0
  142. package/src/mcp/index.ts +3 -0
  143. package/src/mcp/input.ts +73 -0
  144. package/src/notify.svelte.ts +43 -0
  145. package/src/ports/contract.ts +165 -0
  146. package/src/ports/fake.ts +484 -0
  147. package/src/ports/index.ts +7 -0
  148. package/src/ports/search.ts +47 -0
  149. package/src/ports/skill-sources.ts +45 -0
  150. package/src/providers/EndpointKeysDialog.svelte +343 -0
  151. package/src/providers/EndpointPanel.svelte +491 -0
  152. package/src/providers/ModelPickerDialog.svelte +187 -0
  153. package/src/providers/ModelSelect.svelte +434 -0
  154. package/src/providers/ModelSettingsDialog.svelte +453 -0
  155. package/src/providers/ProvidersSettings.svelte +631 -0
  156. package/src/providers/RequestConfigDialog.svelte +250 -0
  157. package/src/providers/SecretInput.svelte +276 -0
  158. package/src/providers/draft.ts +329 -0
  159. package/src/providers/edit-session.ts +146 -0
  160. package/src/providers/index.ts +5 -0
  161. package/src/providers/reasoning.ts +41 -0
  162. package/src/providers/revision.svelte.ts +4 -0
  163. package/src/search/SearchSettings.svelte +703 -0
  164. package/src/search/index.ts +13 -0
  165. package/src/search/settings-flow.ts +62 -0
  166. package/src/session.ts +20 -0
  167. package/src/skills/SkillAdd.svelte +347 -0
  168. package/src/skills/SkillAuthoring.svelte +381 -0
  169. package/src/skills/SkillEditor.svelte +94 -0
  170. package/src/skills/SkillPreviewDialog.svelte +122 -0
  171. package/src/skills/SkillsSettings.svelte +140 -0
  172. package/src/skills/error.ts +32 -0
  173. package/src/skills/index.ts +4 -0
  174. package/src/styles/kit.css +137 -0
  175. package/src/styles/tokens.css +22 -0
  176. package/src/tauri/index.ts +282 -0
  177. package/src/utils.ts +22 -0
  178. package/src/web/index.ts +376 -0
  179. package/README.md +0 -3
@@ -0,0 +1,97 @@
1
+ <script lang="ts">
2
+ // Log viewer (docs/16 §4.5): tail(300) every 2 s while the page is visible; problems-only filter; open folder.
3
+ import { Alert, AlertDescription } from '#lib/components/ui/alert/index.js'
4
+ import { Button } from '#lib/components/ui/button/index.js'
5
+ import * as Empty from '#lib/components/ui/empty/index.js'
6
+ import { Switch } from '#lib/components/ui/switch/index.js'
7
+ import { getKitContext } from '../context'
8
+ import type { LogEntry } from '../ports'
9
+ import { cn } from '../utils'
10
+
11
+ const POLL_MS = 2000
12
+ const LIMIT = 300
13
+
14
+ const { ports, i18n, notify } = getKitContext()
15
+ const t = i18n.t
16
+ const logs = ports.logs
17
+
18
+ let entries = $state<LogEntry[]>([])
19
+ let failed = $state(false)
20
+ let problemsOnly = $state(false)
21
+ const shown = $derived(problemsOnly ? entries.filter((e) => e.level === 'ERROR' || e.level === 'WARN') : entries)
22
+
23
+ async function refresh() {
24
+ if (!logs) return
25
+ try {
26
+ entries = await logs.tail(LIMIT)
27
+ failed = false
28
+ } catch {
29
+ failed = true
30
+ }
31
+ }
32
+
33
+ $effect(() => {
34
+ if (!logs) return
35
+ let timer: number | undefined
36
+ const sync = () => {
37
+ window.clearInterval(timer)
38
+ timer = undefined
39
+ if (document.visibilityState !== 'visible') return
40
+ void refresh()
41
+ timer = window.setInterval(() => void refresh(), POLL_MS)
42
+ }
43
+ sync()
44
+ document.addEventListener('visibilitychange', sync)
45
+ return () => {
46
+ window.clearInterval(timer)
47
+ document.removeEventListener('visibilitychange', sync)
48
+ }
49
+ })
50
+
51
+ async function openDir() {
52
+ try {
53
+ await ports.desktop?.openLogsDirectory()
54
+ } catch {
55
+ notify.toast('error', t('errors.internal'))
56
+ }
57
+ }
58
+ </script>
59
+
60
+ {#if logs}
61
+ <section class="flex min-h-0 flex-1 flex-col gap-3" aria-label={t('logs.title')}>
62
+ <header class="flex items-center gap-3">
63
+ <h2 class="text-base font-medium">{t('logs.title')}</h2>
64
+ <label class="ml-auto flex items-center gap-2 text-sm">
65
+ <Switch bind:checked={problemsOnly} />
66
+ {t('logs.problemsOnly')}
67
+ </label>
68
+ {#if ports.desktop}
69
+ <Button variant="outline" size="sm" onclick={() => void openDir()}>{t('logs.openDir')}</Button>
70
+ {/if}
71
+ </header>
72
+ {#if failed}
73
+ <Alert variant="destructive"><AlertDescription>{t('logs.loadFailed')}</AlertDescription></Alert>
74
+ {/if}
75
+ {#if shown.length === 0}
76
+ <Empty.Root>
77
+ <Empty.Header><Empty.Title>{t('logs.empty')}</Empty.Title></Empty.Header>
78
+ </Empty.Root>
79
+ {:else}
80
+ <ol class="bg-muted min-h-0 flex-1 overflow-auto rounded-md p-2 font-mono text-xs">
81
+ {#each shown as entry (entry.seq)}
82
+ <li
83
+ class={cn(
84
+ 'break-all whitespace-pre-wrap',
85
+ entry.level === 'ERROR' && 'text-destructive',
86
+ (entry.level === 'DEBUG' || entry.level === 'TRACE') && 'text-muted-foreground',
87
+ )}
88
+ >
89
+ {entry.time}
90
+ {entry.level}
91
+ {entry.target}: {entry.message}
92
+ </li>
93
+ {/each}
94
+ </ol>
95
+ {/if}
96
+ </section>
97
+ {/if}
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ // Log level (docs/16 §4.5): applies immediately; warns when the log file cannot be written.
3
+ import { Alert, AlertDescription } from '#lib/components/ui/alert/index.js'
4
+ import * as Field from '#lib/components/ui/field/index.js'
5
+ import * as Select from '#lib/components/ui/select/index.js'
6
+ import { getKitContext } from '../context'
7
+ import { isKitClientError } from '../ports'
8
+ import type { LogLevel, LogStatus } from '../ports'
9
+
10
+ const LEVELS: readonly LogLevel[] = ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE']
11
+
12
+ const { ports, i18n, notify } = getKitContext()
13
+ const t = i18n.t
14
+ const logs = ports.logs
15
+
16
+ let status = $state<LogStatus | null>(null)
17
+
18
+ async function run(call: () => Promise<LogStatus>) {
19
+ try {
20
+ status = await call()
21
+ } catch (error) {
22
+ notify.toast('error', t(`errors.${isKitClientError(error) ? error.code : 'internal'}`))
23
+ }
24
+ }
25
+
26
+ $effect(() => {
27
+ if (logs) void run(() => logs.getLevel())
28
+ })
29
+ </script>
30
+
31
+ {#if logs}
32
+ <Field.Group>
33
+ <Field.Field>
34
+ <Field.Label for="kit-log-level">{t('logs.level')}</Field.Label>
35
+ <Select.Root
36
+ type="single"
37
+ value={status?.level ?? ''}
38
+ disabled={!status}
39
+ onValueChange={(value) => void run(() => logs.setLevel(value as LogLevel))}
40
+ >
41
+ <Select.Trigger id="kit-log-level" class="w-40">{status?.level ?? '—'}</Select.Trigger>
42
+ <Select.Content>
43
+ <Select.Group>
44
+ {#each LEVELS as level (level)}
45
+ <Select.Item value={level} label={level} />
46
+ {/each}
47
+ </Select.Group>
48
+ </Select.Content>
49
+ </Select.Root>
50
+ <Field.Description>{t('logs.levelHint')}</Field.Description>
51
+ </Field.Field>
52
+ {#if status && !status.available}
53
+ <Alert variant="destructive"><AlertDescription>{t('logs.unavailable')}</AlertDescription></Alert>
54
+ {/if}
55
+ </Field.Group>
56
+ {/if}
@@ -0,0 +1,5 @@
1
+ // LogPanel、LogSettings、createLogger。设计:docs/16 §4.5。
2
+ export { createLogger } from './logger'
3
+ export type { BatchingLogger } from './logger'
4
+ export { default as LogPanel } from './LogPanel.svelte'
5
+ export { default as LogSettings } from './LogSettings.svelte'
@@ -0,0 +1,96 @@
1
+ // createLogger (docs/16 §4.5): same whitelist as the backend (docs/10 §10), batched into LogsPort.ingest.
2
+ // ponytail: secret detection stays in the backend (it rejects and counts); mirror mask.rs here
3
+ // if a secret-looking value must never leave the page.
4
+ import type { KitLogger, LogFields } from '../context'
5
+ import type { IngestEntry, IngestLevel, LogsPort } from '../ports'
6
+
7
+ const MAX_QUEUE = 200
8
+ const BATCH = 20
9
+ const MAX_BATCH = 50
10
+ const DELAY_MS = 250
11
+ const MAX_FIELDS = 16
12
+ const MAX_STRING = 200
13
+ const EVENT = /^[a-z][a-z0-9_]*(\.[a-z0-9_]+){1,5}$/
14
+ const KEY = /^[a-z][a-z0-9_]*(\.[a-z0-9_]+){0,5}$/
15
+
16
+ export interface BatchingLogger extends KitLogger {
17
+ /** Sends everything queued now (also done on `pagehide`). */
18
+ flush(): Promise<void>
19
+ }
20
+
21
+ function isValid(event: string, fields: LogFields | undefined): boolean {
22
+ if (!EVENT.test(event)) return false
23
+ const entries = Object.entries(fields ?? {})
24
+ return (
25
+ entries.length <= MAX_FIELDS &&
26
+ entries.every(([key, value]) => {
27
+ if (!KEY.test(key)) return false
28
+ if (typeof value === 'string') return [...value].length <= MAX_STRING
29
+ return typeof value === 'boolean' || Number.isFinite(value)
30
+ })
31
+ )
32
+ }
33
+
34
+ export function createLogger(port: LogsPort): BatchingLogger {
35
+ const queue: IngestEntry[] = []
36
+ let dropped = 0
37
+ let timer: number | undefined
38
+ let inFlight: Promise<void> | null = null
39
+
40
+ // One request at a time; entries queue meanwhile, bounded by MAX_QUEUE.
41
+ async function drain(): Promise<void> {
42
+ while (queue.length > 0 || dropped > 0) {
43
+ const entries = queue.splice(0, MAX_BATCH)
44
+ const droppedCount = dropped
45
+ dropped = 0
46
+ try {
47
+ await port.ingest({ entries, droppedCount })
48
+ } catch {
49
+ dropped += droppedCount + entries.length
50
+ return
51
+ }
52
+ }
53
+ }
54
+
55
+ function schedule(): void {
56
+ timer ??= window.setTimeout(() => void flush(), DELAY_MS)
57
+ }
58
+
59
+ function flush(): Promise<void> {
60
+ window.clearTimeout(timer)
61
+ timer = undefined
62
+ inFlight ??= drain().finally(() => {
63
+ inFlight = null
64
+ if (queue.length > 0) schedule()
65
+ })
66
+ return inFlight
67
+ }
68
+
69
+ function push(level: IngestLevel, event: string, fields?: LogFields): void {
70
+ if (!isValid(event, fields)) {
71
+ dropped++
72
+ return
73
+ }
74
+ if (queue.length >= MAX_QUEUE) {
75
+ // Full: drop the oldest debug/info first; a new debug/info entry is dropped itself.
76
+ dropped++
77
+ const low = queue.findIndex((entry) => entry.level === 'debug' || entry.level === 'info')
78
+ if (low >= 0) queue.splice(low, 1)
79
+ else if (level === 'debug' || level === 'info') return
80
+ else queue.shift()
81
+ }
82
+ queue.push(fields ? { level, event, fields } : { level, event })
83
+ if (queue.length >= BATCH) void flush()
84
+ else schedule()
85
+ }
86
+
87
+ window.addEventListener('pagehide', () => void flush())
88
+
89
+ return {
90
+ debug: (event, fields) => push('debug', event, fields),
91
+ info: (event, fields) => push('info', event, fields),
92
+ warn: (event, fields) => push('warn', event, fields),
93
+ error: (event, fields) => push('error', event, fields),
94
+ flush,
95
+ }
96
+ }
@@ -0,0 +1,8 @@
1
+ // Markdown → sanitized HTML. Render it with `{@attach (node) => { node.innerHTML = html }}`, never `{@html}` (docs/04 §5).
2
+ import DOMPurify from 'dompurify'
3
+ import { marked } from 'marked'
4
+
5
+ /** Model and skill text is untrusted: DOMPurify drops scripts, event handlers and `javascript:` URLs. */
6
+ export function renderMarkdown(source: string): string {
7
+ return DOMPurify.sanitize(marked.parse(source, { async: false, gfm: true }))
8
+ }