@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouraihub/aether-ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "LGPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "svelte": "^5.57.1"
31
31
  },
32
32
  "dependencies": {
33
- "@ouraihub/aether-contracts": "0.1.0",
33
+ "@ouraihub/aether-contracts": "0.1.2",
34
34
  "@internationalized/date": "3.12.4",
35
35
  "@lucide/svelte": "0.561.0",
36
36
  "@tauri-apps/api": "2.11.1",
@@ -49,7 +49,7 @@
49
49
  "@tailwindcss/vite": "4.3.3",
50
50
  "@testing-library/svelte": "5.4.2",
51
51
  "jsdom": "30.1.2",
52
- "svelte": "5.57.1",
52
+ "svelte": "5.57.2",
53
53
  "svelte-check": "4.7.6",
54
54
  "tailwindcss": "4.3.3",
55
55
  "typescript": "5.9.3",
@@ -12,12 +12,18 @@
12
12
  <Alert.Root aria-label={t('chat.approval.title', { name: tool.name })}>
13
13
  <Alert.Title>{t('chat.approval.title', { name: tool.name })}</Alert.Title>
14
14
  <Alert.Description class="flex flex-col gap-2">
15
- <span>{tool.server ? t('chat.approval.mcp', { server: tool.server }) : t('chat.approval.host')}</span>
15
+ <span
16
+ >{tool.server
17
+ ? t('chat.approval.mcp', { server: tool.server })
18
+ : t('chat.approval.host')}</span
19
+ >
16
20
  <pre
17
21
  class="bg-muted max-h-40 w-full overflow-auto rounded-kit-sm p-2 text-kit-xs whitespace-pre-wrap">{tool.pending}</pre>
18
22
  <span class="flex gap-2">
19
23
  <Button size="sm" onclick={() => onAnswer(true)}>{t('chat.approval.allow')}</Button>
20
- <Button size="sm" variant="outline" onclick={() => onAnswer(false)}>{t('chat.approval.deny')}</Button>
24
+ <Button size="sm" variant="outline" onclick={() => onAnswer(false)}
25
+ >{t('chat.approval.deny')}</Button
26
+ >
21
27
  </span>
22
28
  </Alert.Description>
23
29
  </Alert.Root>
@@ -0,0 +1,319 @@
1
+ <script lang="ts">
2
+ import { tick, untrack, type Snippet } from 'svelte'
3
+ import MaximizeIcon from '@lucide/svelte/icons/maximize-2'
4
+ import MinimizeIcon from '@lucide/svelte/icons/minimize-2'
5
+ import ArrowUpIcon from '@lucide/svelte/icons/arrow-up'
6
+ import SquareIcon from '@lucide/svelte/icons/square'
7
+ import { Button } from '#lib/components/ui/button/index.js'
8
+
9
+ let {
10
+ value = $bindable(''),
11
+ label,
12
+ placeholder,
13
+ submitLabel,
14
+ stopLabel = 'Stop',
15
+ disabled = false,
16
+ running = false,
17
+ stopping = false,
18
+ maxlength,
19
+ onsubmit,
20
+ onstop,
21
+ oninput,
22
+ onpaste,
23
+ onfiles,
24
+ top,
25
+ tools,
26
+ actions,
27
+ override,
28
+ expandLabel = 'Expand input',
29
+ collapseLabel = 'Collapse input',
30
+ }: {
31
+ value?: string | undefined
32
+ label: string
33
+ placeholder?: string | undefined
34
+ submitLabel: string
35
+ stopLabel?: string | undefined
36
+ disabled?: boolean | undefined
37
+ running?: boolean | undefined
38
+ stopping?: boolean | undefined
39
+ maxlength?: number | undefined
40
+ onsubmit: () => void
41
+ onstop?: (() => void) | undefined
42
+ oninput?: (() => void) | undefined
43
+ onpaste?: ((event: ClipboardEvent) => void) | undefined
44
+ onfiles?: ((files: File[]) => void) | undefined
45
+ top?: Snippet | undefined
46
+ tools?: Snippet | undefined
47
+ actions?: Snippet | undefined
48
+ override?: Snippet | undefined
49
+ expandLabel?: string | undefined
50
+ collapseLabel?: string | undefined
51
+ } = $props()
52
+ let expanded = $state(false)
53
+ let textarea = $state<HTMLTextAreaElement | null>(null)
54
+ let normal = $state<HTMLFormElement | null>(null)
55
+ let replacement = $state<HTMLDivElement | null>(null)
56
+ let restoreFocus: HTMLElement | null = null
57
+ let overridden = false
58
+ function submit() {
59
+ if (!disabled && !running && !override) onsubmit()
60
+ }
61
+ function resize() {
62
+ if (!textarea) return
63
+ textarea.style.height = 'auto'
64
+ textarea.style.height = `${textarea.scrollHeight}px`
65
+ }
66
+ $effect(() => {
67
+ void value
68
+ void expanded
69
+ void override
70
+ void tick().then(resize)
71
+ })
72
+ $effect.pre(() => {
73
+ const next = !!override
74
+ untrack(() => {
75
+ if (next && !overridden) {
76
+ const active = document.activeElement
77
+ restoreFocus = active instanceof HTMLElement && normal?.contains(active) ? active : null
78
+ } else if (!next && overridden) {
79
+ const active = document.activeElement
80
+ const shouldRestore =
81
+ active === document.body || !!(active && replacement?.contains(active))
82
+ const target = restoreFocus
83
+ void tick().then(() => {
84
+ if (
85
+ shouldRestore &&
86
+ target?.isConnected &&
87
+ (document.activeElement === document.body || document.activeElement === active)
88
+ )
89
+ target.focus({ preventScroll: true })
90
+ })
91
+ restoreFocus = null
92
+ }
93
+ overridden = next
94
+ })
95
+ })
96
+ </script>
97
+
98
+ <div class="composer-shell">
99
+ <form
100
+ class="composer"
101
+ class:expanded
102
+ bind:this={normal}
103
+ hidden={!!override}
104
+ inert={!!override}
105
+ onsubmit={(event) => {
106
+ event.preventDefault()
107
+ submit()
108
+ }}
109
+ ondragover={(event) => {
110
+ if (onfiles && event.dataTransfer?.types.includes('Files')) event.preventDefault()
111
+ }}
112
+ ondrop={(event) => {
113
+ if (!onfiles || !event.dataTransfer?.files.length) return
114
+ event.preventDefault()
115
+ onfiles([...event.dataTransfer.files])
116
+ }}
117
+ >
118
+ <div class="expand-control">
119
+ <Button
120
+ type="button"
121
+ variant="ghost"
122
+ size="icon-sm"
123
+ aria-label={expanded ? collapseLabel : expandLabel}
124
+ title={expanded ? collapseLabel : expandLabel}
125
+ aria-expanded={expanded}
126
+ onmousedown={(event) => event.preventDefault()}
127
+ onclick={() => {
128
+ expanded = !expanded
129
+ }}
130
+ >
131
+ {#if expanded}<MinimizeIcon />{:else}<MaximizeIcon />{/if}
132
+ </Button>
133
+ </div>
134
+ {#if top}<div class="composer-top">{@render top()}</div>{/if}
135
+ <textarea
136
+ bind:this={textarea}
137
+ bind:value
138
+ rows="2"
139
+ aria-label={label}
140
+ {placeholder}
141
+ {maxlength}
142
+ oninput={() => {
143
+ resize()
144
+ oninput?.()
145
+ }}
146
+ {onpaste}
147
+ onkeydown={(event) => {
148
+ if (event.key === 'Escape' && expanded && !event.isComposing && !event.defaultPrevented) {
149
+ event.preventDefault()
150
+ event.stopPropagation()
151
+ expanded = false
152
+ return
153
+ }
154
+ if (
155
+ event.key === 'Enter' &&
156
+ (event.ctrlKey || event.metaKey) &&
157
+ !event.isComposing &&
158
+ event.keyCode !== 229
159
+ ) {
160
+ event.preventDefault()
161
+ if (!event.repeat) submit()
162
+ }
163
+ }}></textarea>
164
+ <div class="composer-tools">
165
+ <div class="tool-group">
166
+ {#if tools}{@render tools()}{/if}
167
+ </div>
168
+ <div class="tool-group send-tools">
169
+ {#if actions}{@render actions()}{/if}
170
+ {#if running && onstop}<Button
171
+ type="button"
172
+ variant="destructive"
173
+ size="icon-sm"
174
+ disabled={stopping}
175
+ aria-label={stopLabel}
176
+ title={stopLabel}
177
+ onclick={onstop}><SquareIcon /></Button
178
+ >
179
+ {:else}<Button
180
+ type="submit"
181
+ size="icon-sm"
182
+ disabled={disabled || running}
183
+ aria-label={submitLabel}
184
+ title={submitLabel}><ArrowUpIcon /></Button
185
+ >{/if}
186
+ </div>
187
+ </div>
188
+ </form>
189
+ {#if override}<div class="composer-override" bind:this={replacement}>
190
+ {@render override()}
191
+ </div>{/if}
192
+ </div>
193
+
194
+ <style>
195
+ /* Hallmark · component: integrated composer · theme: host semantic tokens */
196
+ .composer-shell {
197
+ min-width: 0;
198
+ container-type: inline-size;
199
+ container-name: composer;
200
+ }
201
+ .composer {
202
+ box-sizing: border-box;
203
+ position: relative;
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 0.5rem;
207
+ min-width: 0;
208
+ max-height: 68cqh;
209
+ overflow-y: auto;
210
+ border: 1px solid var(--color-border);
211
+ border-radius: calc(var(--kit-radius, var(--radius)) * 1.5);
212
+ padding: 0.5rem;
213
+ background: var(--color-background);
214
+ }
215
+ .composer[hidden] {
216
+ display: none;
217
+ }
218
+ .expand-control {
219
+ position: absolute;
220
+ top: 0.25rem;
221
+ right: 0.25rem;
222
+ }
223
+ @media (pointer: coarse) {
224
+ .expand-control :global(button),
225
+ .composer-tools :global(button) {
226
+ min-width: 44px;
227
+ min-height: 44px;
228
+ }
229
+ }
230
+ .composer-top {
231
+ padding-right: 2.75rem;
232
+ min-width: 0;
233
+ }
234
+ textarea {
235
+ box-sizing: border-box;
236
+ display: block;
237
+ flex-shrink: 0;
238
+ width: 100%;
239
+ min-width: 0;
240
+ min-height: 3.5rem;
241
+ max-height: 35cqh;
242
+ resize: none;
243
+ padding: 0.5rem 2.75rem 0.5rem 0.5rem;
244
+ border: 0;
245
+ border-radius: var(--kit-radius-sm, var(--radius));
246
+ background: transparent;
247
+ color: var(--color-foreground);
248
+ font: inherit;
249
+ line-height: 1.5;
250
+ overflow-y: auto;
251
+ }
252
+ textarea::placeholder {
253
+ color: var(--color-muted-foreground);
254
+ }
255
+ textarea:focus-visible {
256
+ outline: 2px solid var(--color-ring);
257
+ outline-offset: -2px;
258
+ }
259
+ .expanded textarea {
260
+ min-height: 55cqh;
261
+ max-height: 55cqh;
262
+ }
263
+ .composer-tools,
264
+ .tool-group {
265
+ display: flex;
266
+ align-items: center;
267
+ flex-wrap: nowrap;
268
+ gap: 0.25rem;
269
+ min-width: 0;
270
+ }
271
+ .composer-tools {
272
+ justify-content: space-between;
273
+ gap: 0.5rem;
274
+ overflow: visible;
275
+ }
276
+ /* Tools then the model pill on the left; only the pill shrinks. Usage and send stay whole. */
277
+ .tool-group:first-child {
278
+ flex: 1 1 auto;
279
+ }
280
+ .tool-group > :global(:not([data-composer-model])) {
281
+ flex-shrink: 0;
282
+ }
283
+ .send-tools {
284
+ flex: 0 0 auto;
285
+ justify-content: flex-end;
286
+ margin-left: auto;
287
+ }
288
+ @container composer (max-width: 560px) {
289
+ .composer-tools :global([data-composer-mode-label]),
290
+ .composer-tools :global([data-composer-model-detail]),
291
+ .composer-tools :global([data-composer-capacity]) {
292
+ display: none;
293
+ }
294
+ .composer-tools :global([data-composer-model]) {
295
+ flex: 0 1 auto;
296
+ min-width: 4.5rem;
297
+ max-width: 9rem;
298
+ padding-inline: 2px;
299
+ }
300
+ .composer-tools {
301
+ gap: 2px;
302
+ }
303
+ }
304
+ @container composer (max-width: 400px) {
305
+ .composer-tools :global([data-composer-model]) {
306
+ max-width: 6rem;
307
+ }
308
+ .composer-tools :global([data-slot='select-trigger']) {
309
+ max-width: 5.5rem;
310
+ overflow: hidden;
311
+ }
312
+ .composer-tools :global([data-composer-model] [data-icon='inline-end']) {
313
+ display: none;
314
+ }
315
+ }
316
+ .composer-override {
317
+ min-width: 0;
318
+ }
319
+ </style>
@@ -0,0 +1,262 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import { renderMarkdown } from '../markdown'
4
+ import { Button } from '#lib/components/ui/button/index.js'
5
+
6
+ let {
7
+ role,
8
+ text,
9
+ label,
10
+ status,
11
+ reasoning,
12
+ reasoningLabel = 'Reasoning',
13
+ process,
14
+ attachments,
15
+ children,
16
+ actions,
17
+ latest = false,
18
+ selected = false,
19
+ showMoreLabel = 'Show more',
20
+ showLessLabel = 'Show less',
21
+ }: {
22
+ role: 'user' | 'assistant'
23
+ text: string
24
+ label?: string | undefined
25
+ status?: string | undefined
26
+ reasoning?: string | undefined
27
+ reasoningLabel?: string | undefined
28
+ process?: Snippet | undefined
29
+ attachments?: Snippet | undefined
30
+ children?: Snippet | undefined
31
+ actions?: Snippet | undefined
32
+ latest?: boolean | undefined
33
+ selected?: boolean | undefined
34
+ showMoreLabel?: string | undefined
35
+ showLessLabel?: string | undefined
36
+ } = $props()
37
+ const html = $derived(role === 'assistant' ? renderMarkdown(text) : '')
38
+ const preview = $derived.by(() => {
39
+ if (role !== 'user') return null
40
+ let lines = 0
41
+ let end = 0
42
+ for (const line of text.split('\n')) {
43
+ if (line.trim() && ++lines > 5) return text.slice(0, end).trimEnd()
44
+ end += line.length + 1
45
+ }
46
+ return null
47
+ })
48
+ let expanded = $state(false)
49
+ </script>
50
+
51
+ <div
52
+ class="chat-message"
53
+ data-status={status}
54
+ class:user={role === 'user'}
55
+ class:assistant={role === 'assistant'}
56
+ class:latest
57
+ class:selected
58
+ >
59
+ {#if label}<div class="reply-source">{label}</div>{/if}
60
+ {#if attachments}{@render attachments()}{/if}
61
+ {#if reasoning}<details class="thinking">
62
+ <summary>{reasoningLabel}</summary>
63
+ <p>{reasoning}</p>
64
+ </details>{/if}
65
+ {#if process}{@render process()}{/if}
66
+ {#if role === 'user'}
67
+ <div class="user-bubble">
68
+ <p class="user-message">{preview !== null && !expanded ? preview : text}</p>
69
+ {#if preview !== null}<Button
70
+ type="button"
71
+ variant="ghost"
72
+ size="sm"
73
+ aria-expanded={expanded}
74
+ onclick={() => {
75
+ expanded = !expanded
76
+ }}>{expanded ? showLessLabel : showMoreLabel}</Button
77
+ >{/if}
78
+ </div>
79
+ {:else}
80
+ <div
81
+ class="markdown"
82
+ {@attach (node) => {
83
+ node.innerHTML = html
84
+ }}
85
+ ></div>
86
+ {/if}
87
+ {#if children}{@render children()}{/if}
88
+ {#if actions}<div class="message-actions">{@render actions()}</div>{/if}
89
+ </div>
90
+
91
+ <style>
92
+ /* Hallmark · component: open assistant message · theme: host semantic tokens */
93
+ .chat-message {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.75rem;
97
+ min-width: 0;
98
+ max-width: 100%;
99
+ overflow-wrap: anywhere;
100
+ color: var(--color-foreground);
101
+ }
102
+ .assistant {
103
+ padding-block: 0.75rem;
104
+ }
105
+ .selected {
106
+ outline: 1px solid var(--color-border);
107
+ outline-offset: 0.5rem;
108
+ border-radius: var(--kit-radius, var(--radius));
109
+ }
110
+ .user {
111
+ align-items: flex-end;
112
+ }
113
+ .user-bubble {
114
+ max-width: min(90%, 42rem);
115
+ border-radius: var(--kit-radius, var(--radius));
116
+ padding: 0.625rem 0.875rem;
117
+ background: var(--color-muted);
118
+ }
119
+ .user-message {
120
+ margin: 0;
121
+ font-size: var(--kit-text-base);
122
+ white-space: pre-wrap;
123
+ }
124
+ .reply-source,
125
+ .thinking {
126
+ color: var(--color-muted-foreground);
127
+ font-size: var(--kit-text-sm);
128
+ }
129
+ .thinking summary {
130
+ cursor: pointer;
131
+ }
132
+ .thinking summary:focus-visible {
133
+ outline: 2px solid var(--color-ring);
134
+ outline-offset: 2px;
135
+ }
136
+ .thinking p {
137
+ white-space: pre-wrap;
138
+ margin-block: 0.5rem;
139
+ }
140
+ .message-actions {
141
+ display: flex;
142
+ align-items: center;
143
+ flex-wrap: wrap;
144
+ gap: 0.25rem;
145
+ min-height: 2rem;
146
+ }
147
+ .message-actions :global(button) {
148
+ min-width: 2rem;
149
+ min-height: 2rem;
150
+ }
151
+ @media (hover: hover) {
152
+ .assistant:not(.latest) .message-actions {
153
+ opacity: 0;
154
+ }
155
+ .assistant:hover .message-actions,
156
+ .assistant:focus-within .message-actions {
157
+ opacity: 1;
158
+ }
159
+ }
160
+ .markdown {
161
+ font-size: var(--kit-text-base);
162
+ line-height: 1.6;
163
+ }
164
+ .markdown :global(p),
165
+ .markdown :global(ul),
166
+ .markdown :global(ol),
167
+ .markdown :global(blockquote),
168
+ .markdown :global(pre),
169
+ .markdown :global(table) {
170
+ margin: 0.75em 0;
171
+ }
172
+ .markdown :global(h1),
173
+ .markdown :global(h2),
174
+ .markdown :global(h3),
175
+ .markdown :global(h4),
176
+ .markdown :global(h5),
177
+ .markdown :global(h6) {
178
+ margin: 1em 0 0.5em;
179
+ font-weight: 600;
180
+ line-height: 1.35;
181
+ }
182
+ .markdown :global(h1) {
183
+ font-size: 1.4em;
184
+ }
185
+ .markdown :global(h2) {
186
+ font-size: 1.25em;
187
+ }
188
+ .markdown :global(h3) {
189
+ font-size: 1.1em;
190
+ }
191
+ .markdown :global(ul),
192
+ .markdown :global(ol) {
193
+ padding-inline-start: 1.5em;
194
+ }
195
+ .markdown :global(ul) {
196
+ list-style: disc;
197
+ }
198
+ .markdown :global(ol) {
199
+ list-style: decimal;
200
+ }
201
+ .markdown :global(li) {
202
+ margin: 0.25em 0;
203
+ }
204
+ .markdown :global(li::marker) {
205
+ color: var(--color-muted-foreground);
206
+ }
207
+ .markdown :global(blockquote) {
208
+ border-inline-start: 3px solid var(--color-border);
209
+ padding-inline-start: 0.75em;
210
+ color: var(--color-muted-foreground);
211
+ }
212
+ .markdown :global(a) {
213
+ color: var(--color-primary);
214
+ text-decoration: underline;
215
+ overflow-wrap: anywhere;
216
+ }
217
+ .markdown :global(code) {
218
+ border-radius: var(--kit-radius-sm);
219
+ background: var(--color-background);
220
+ padding: 0.1em 0.3em;
221
+ font-size: 0.95em;
222
+ }
223
+ .markdown :global(pre) {
224
+ max-width: 100%;
225
+ overflow-x: auto;
226
+ padding: 0.75em;
227
+ border: 1px solid var(--color-border);
228
+ border-radius: var(--kit-radius-sm);
229
+ background: var(--color-background);
230
+ white-space: pre;
231
+ overflow-wrap: normal;
232
+ }
233
+ .markdown :global(pre code) {
234
+ padding: 0;
235
+ background: none;
236
+ }
237
+ .markdown :global(table) {
238
+ display: block;
239
+ max-width: 100%;
240
+ overflow-x: auto;
241
+ border-collapse: collapse;
242
+ }
243
+ .markdown :global(th),
244
+ .markdown :global(td) {
245
+ padding: 0.4em 0.6em;
246
+ border: 1px solid var(--color-border);
247
+ }
248
+ .markdown :global(th) {
249
+ font-weight: 600;
250
+ }
251
+ .markdown :global(img),
252
+ .markdown :global(video) {
253
+ max-width: 100%;
254
+ height: auto;
255
+ }
256
+ .markdown :global(:first-child) {
257
+ margin-top: 0;
258
+ }
259
+ .markdown :global(:last-child) {
260
+ margin-bottom: 0;
261
+ }
262
+ </style>