svedocs 0.1.0-beta.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 (192) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +38 -0
  3. package/dist/ai/openai-compatible.d.ts +13 -0
  4. package/dist/ai/openai-compatible.d.ts.map +1 -0
  5. package/dist/ai/openai-compatible.js +92 -0
  6. package/dist/ai/providers.d.ts +19 -0
  7. package/dist/ai/providers.d.ts.map +1 -0
  8. package/dist/ai/providers.js +185 -0
  9. package/dist/ai/rate-limit.d.ts +18 -0
  10. package/dist/ai/rate-limit.d.ts.map +1 -0
  11. package/dist/ai/rate-limit.js +52 -0
  12. package/dist/ai/response.d.ts +4 -0
  13. package/dist/ai/response.d.ts.map +1 -0
  14. package/dist/ai/response.js +151 -0
  15. package/dist/ai/runtime.d.ts +30 -0
  16. package/dist/ai/runtime.d.ts.map +1 -0
  17. package/dist/ai/runtime.js +105 -0
  18. package/dist/ai/types.d.ts +93 -0
  19. package/dist/ai/types.d.ts.map +1 -0
  20. package/dist/ai/types.js +1 -0
  21. package/dist/ai.d.ts +7 -0
  22. package/dist/ai.d.ts.map +1 -0
  23. package/dist/ai.js +6 -0
  24. package/dist/ambient.d.ts +5 -0
  25. package/dist/cloudflare.d.ts +27 -0
  26. package/dist/cloudflare.d.ts.map +1 -0
  27. package/dist/cloudflare.js +146 -0
  28. package/dist/config.d.ts +311 -0
  29. package/dist/config.d.ts.map +1 -0
  30. package/dist/config.js +243 -0
  31. package/dist/core/checks.d.ts +4 -0
  32. package/dist/core/checks.d.ts.map +1 -0
  33. package/dist/core/checks.js +252 -0
  34. package/dist/core/config.d.ts +5 -0
  35. package/dist/core/config.d.ts.map +1 -0
  36. package/dist/core/config.js +182 -0
  37. package/dist/core/content.d.ts +7 -0
  38. package/dist/core/content.d.ts.map +1 -0
  39. package/dist/core/content.js +211 -0
  40. package/dist/core/links.d.ts +8 -0
  41. package/dist/core/links.d.ts.map +1 -0
  42. package/dist/core/links.js +94 -0
  43. package/dist/core/navigation.d.ts +5 -0
  44. package/dist/core/navigation.d.ts.map +1 -0
  45. package/dist/core/navigation.js +144 -0
  46. package/dist/core/search.d.ts +5 -0
  47. package/dist/core/search.d.ts.map +1 -0
  48. package/dist/core/search.js +41 -0
  49. package/dist/core/types.d.ts +269 -0
  50. package/dist/core/types.d.ts.map +1 -0
  51. package/dist/core/types.js +4 -0
  52. package/dist/core/utils.d.ts +12 -0
  53. package/dist/core/utils.d.ts.map +1 -0
  54. package/dist/core/utils.js +52 -0
  55. package/dist/core.d.ts +8 -0
  56. package/dist/core.d.ts.map +1 -0
  57. package/dist/core.js +7 -0
  58. package/dist/index.d.ts +6 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +3 -0
  61. package/dist/mdx/ast.d.ts +32 -0
  62. package/dist/mdx/ast.d.ts.map +1 -0
  63. package/dist/mdx/ast.js +148 -0
  64. package/dist/mdx/code.d.ts +14 -0
  65. package/dist/mdx/code.d.ts.map +1 -0
  66. package/dist/mdx/code.js +315 -0
  67. package/dist/mdx/compile.d.ts +26 -0
  68. package/dist/mdx/compile.d.ts.map +1 -0
  69. package/dist/mdx/compile.js +63 -0
  70. package/dist/mdx/diff.d.ts +5 -0
  71. package/dist/mdx/diff.d.ts.map +1 -0
  72. package/dist/mdx/diff.js +160 -0
  73. package/dist/mdx/utils.d.ts +10 -0
  74. package/dist/mdx/utils.d.ts.map +1 -0
  75. package/dist/mdx/utils.js +64 -0
  76. package/dist/og/image.d.ts +21 -0
  77. package/dist/og/image.d.ts.map +1 -0
  78. package/dist/og/image.js +216 -0
  79. package/dist/og/metadata.d.ts +7 -0
  80. package/dist/og/metadata.d.ts.map +1 -0
  81. package/dist/og/metadata.js +160 -0
  82. package/dist/og/types.d.ts +59 -0
  83. package/dist/og/types.d.ts.map +1 -0
  84. package/dist/og/types.js +1 -0
  85. package/dist/og.d.ts +4 -0
  86. package/dist/og.d.ts.map +1 -0
  87. package/dist/og.js +3 -0
  88. package/dist/search/algolia.d.ts +10 -0
  89. package/dist/search/algolia.d.ts.map +1 -0
  90. package/dist/search/algolia.js +82 -0
  91. package/dist/search/cloudflare-sync.d.ts +5 -0
  92. package/dist/search/cloudflare-sync.d.ts.map +1 -0
  93. package/dist/search/cloudflare-sync.js +169 -0
  94. package/dist/search/cloudflare.d.ts +9 -0
  95. package/dist/search/cloudflare.d.ts.map +1 -0
  96. package/dist/search/cloudflare.js +115 -0
  97. package/dist/search/local.d.ts +9 -0
  98. package/dist/search/local.d.ts.map +1 -0
  99. package/dist/search/local.js +122 -0
  100. package/dist/search/runtime.d.ts +41 -0
  101. package/dist/search/runtime.d.ts.map +1 -0
  102. package/dist/search/runtime.js +151 -0
  103. package/dist/search/types.d.ts +172 -0
  104. package/dist/search/types.d.ts.map +1 -0
  105. package/dist/search/types.js +1 -0
  106. package/dist/search/typesense.d.ts +11 -0
  107. package/dist/search/typesense.d.ts.map +1 -0
  108. package/dist/search/typesense.js +91 -0
  109. package/dist/search/utils.d.ts +10 -0
  110. package/dist/search/utils.d.ts.map +1 -0
  111. package/dist/search/utils.js +55 -0
  112. package/dist/search.d.ts +8 -0
  113. package/dist/search.d.ts.map +1 -0
  114. package/dist/search.js +7 -0
  115. package/dist/svelte.d.ts +24 -0
  116. package/dist/svelte.d.ts.map +1 -0
  117. package/dist/svelte.js +51 -0
  118. package/dist/testing.d.ts +3 -0
  119. package/dist/testing.d.ts.map +1 -0
  120. package/dist/testing.js +23 -0
  121. package/dist/theme/AskAiPanel.svelte +433 -0
  122. package/dist/theme/AskAiPanel.svelte.d.ts +27 -0
  123. package/dist/theme/AskAiPanel.svelte.d.ts.map +1 -0
  124. package/dist/theme/Button.svelte +34 -0
  125. package/dist/theme/Button.svelte.d.ts +41 -0
  126. package/dist/theme/Button.svelte.d.ts.map +1 -0
  127. package/dist/theme/Checkbox.svelte +63 -0
  128. package/dist/theme/Checkbox.svelte.d.ts +45 -0
  129. package/dist/theme/Checkbox.svelte.d.ts.map +1 -0
  130. package/dist/theme/CodeBlock.svelte +6 -0
  131. package/dist/theme/CodeBlock.svelte.d.ts +22 -0
  132. package/dist/theme/CodeBlock.svelte.d.ts.map +1 -0
  133. package/dist/theme/DocPage.svelte +121 -0
  134. package/dist/theme/DocPage.svelte.d.ts +33 -0
  135. package/dist/theme/DocPage.svelte.d.ts.map +1 -0
  136. package/dist/theme/DocsApp.svelte +78 -0
  137. package/dist/theme/DocsApp.svelte.d.ts +63 -0
  138. package/dist/theme/DocsApp.svelte.d.ts.map +1 -0
  139. package/dist/theme/DocsLayout.svelte +167 -0
  140. package/dist/theme/DocsLayout.svelte.d.ts +40 -0
  141. package/dist/theme/DocsLayout.svelte.d.ts.map +1 -0
  142. package/dist/theme/FloatingToolbar.svelte +73 -0
  143. package/dist/theme/FloatingToolbar.svelte.d.ts +22 -0
  144. package/dist/theme/FloatingToolbar.svelte.d.ts.map +1 -0
  145. package/dist/theme/FormField.svelte +41 -0
  146. package/dist/theme/FormField.svelte.d.ts +35 -0
  147. package/dist/theme/FormField.svelte.d.ts.map +1 -0
  148. package/dist/theme/HomePage.svelte +228 -0
  149. package/dist/theme/HomePage.svelte.d.ts +58 -0
  150. package/dist/theme/HomePage.svelte.d.ts.map +1 -0
  151. package/dist/theme/Input.svelte +36 -0
  152. package/dist/theme/Input.svelte.d.ts +34 -0
  153. package/dist/theme/Input.svelte.d.ts.map +1 -0
  154. package/dist/theme/PageLayout.svelte +36 -0
  155. package/dist/theme/PageLayout.svelte.d.ts +31 -0
  156. package/dist/theme/PageLayout.svelte.d.ts.map +1 -0
  157. package/dist/theme/RootLayout.svelte +330 -0
  158. package/dist/theme/RootLayout.svelte.d.ts +40 -0
  159. package/dist/theme/RootLayout.svelte.d.ts.map +1 -0
  160. package/dist/theme/ScopeSwitcher.svelte +103 -0
  161. package/dist/theme/ScopeSwitcher.svelte.d.ts +24 -0
  162. package/dist/theme/ScopeSwitcher.svelte.d.ts.map +1 -0
  163. package/dist/theme/SearchDialog.svelte +253 -0
  164. package/dist/theme/SearchDialog.svelte.d.ts +28 -0
  165. package/dist/theme/SearchDialog.svelte.d.ts.map +1 -0
  166. package/dist/theme/Select.svelte +37 -0
  167. package/dist/theme/Select.svelte.d.ts +42 -0
  168. package/dist/theme/Select.svelte.d.ts.map +1 -0
  169. package/dist/theme/SidebarTree.svelte +136 -0
  170. package/dist/theme/SidebarTree.svelte.d.ts +24 -0
  171. package/dist/theme/SidebarTree.svelte.d.ts.map +1 -0
  172. package/dist/theme/Textarea.svelte +33 -0
  173. package/dist/theme/Textarea.svelte.d.ts +33 -0
  174. package/dist/theme/Textarea.svelte.d.ts.map +1 -0
  175. package/dist/theme/ThemeToggle.svelte +39 -0
  176. package/dist/theme/ThemeToggle.svelte.d.ts +21 -0
  177. package/dist/theme/ThemeToggle.svelte.d.ts.map +1 -0
  178. package/dist/theme/forms.d.ts +4 -0
  179. package/dist/theme/forms.d.ts.map +1 -0
  180. package/dist/theme/forms.js +3 -0
  181. package/dist/theme/index.d.ts +21 -0
  182. package/dist/theme/index.d.ts.map +1 -0
  183. package/dist/theme/index.js +19 -0
  184. package/dist/theme/portal.d.ts +5 -0
  185. package/dist/theme/portal.d.ts.map +1 -0
  186. package/dist/theme/portal.js +19 -0
  187. package/dist/theme/styles.css +3697 -0
  188. package/dist/virtual.d.ts +59 -0
  189. package/dist/vite.d.ts +10 -0
  190. package/dist/vite.d.ts.map +1 -0
  191. package/dist/vite.js +304 -0
  192. package/package.json +118 -0
@@ -0,0 +1,433 @@
1
+ <script lang="ts">
2
+ import { onMount, tick } from 'svelte';
3
+ import type { SvedocsResolvedConfig, SvedocsSearchRecord } from '../core/types.js';
4
+ import { searchRecords } from '../search/local.js';
5
+ import type { SearchScope } from '../search/types.js';
6
+ import { portal } from './portal.js';
7
+
8
+ type Citation = { title: string; url: string; section?: string };
9
+ type ChatRole = 'user' | 'assistant';
10
+ type ChatMessage = {
11
+ id: number;
12
+ role: ChatRole;
13
+ content: string;
14
+ citations?: Citation[];
15
+ error?: string;
16
+ welcome?: boolean;
17
+ };
18
+
19
+ export let config: SvedocsResolvedConfig;
20
+ export let records: SvedocsSearchRecord[] = [];
21
+ export let loadRecords: (() => Promise<SvedocsSearchRecord[]>) | undefined = undefined;
22
+ export let scope: SearchScope = {};
23
+ export let buildMode: SvedocsResolvedConfig['build']['mode'] = 'edge';
24
+
25
+ let open = false;
26
+ let input = '';
27
+ let messages: ChatMessage[] = [];
28
+ let loading = false;
29
+ let panel: HTMLDivElement | undefined;
30
+ let textarea: HTMLTextAreaElement | undefined;
31
+ let scrollEl: HTMLDivElement | undefined;
32
+ let previousFocus: HTMLElement | undefined;
33
+ let messageId = 0;
34
+ let loadedRecords: SvedocsSearchRecord[] = records;
35
+ let recordsRequest: Promise<SvedocsSearchRecord[]> | undefined;
36
+
37
+ $: if (records.length > 0 && loadedRecords !== records) {
38
+ loadedRecords = records;
39
+ }
40
+ $: enabled = config.ai.enabled;
41
+ $: label = config.ai.label ?? 'Ask AI';
42
+ $: placeholder = config.ai.placeholder ?? 'Ask about the docs';
43
+ $: suggestions = config.ai.suggestions ?? [];
44
+ $: welcomeMessage = config.ai.welcomeMessage;
45
+ $: showSuggestions = suggestions.length > 0
46
+ && !loading
47
+ && messages.filter((m) => !m.welcome).length === 0;
48
+
49
+ function nextId() {
50
+ return ++messageId;
51
+ }
52
+
53
+ function show() {
54
+ if (!enabled) return;
55
+ if (open) return;
56
+ previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : undefined;
57
+ open = true;
58
+ if (messages.length === 0 && welcomeMessage) {
59
+ messages = [{ id: nextId(), role: 'assistant', content: welcomeMessage, welcome: true }];
60
+ }
61
+ tick().then(() => {
62
+ textarea?.focus();
63
+ scrollToBottom();
64
+ });
65
+ }
66
+
67
+ function hide() {
68
+ open = false;
69
+ tick().then(() => previousFocus?.focus());
70
+ }
71
+
72
+ function reset() {
73
+ messages = welcomeMessage
74
+ ? [{ id: nextId(), role: 'assistant', content: welcomeMessage, welcome: true }]
75
+ : [];
76
+ input = '';
77
+ }
78
+
79
+ function scrollToBottom() {
80
+ if (!scrollEl) return;
81
+ scrollEl.scrollTop = scrollEl.scrollHeight;
82
+ }
83
+
84
+ async function send(text: string) {
85
+ const value = text.trim();
86
+ if (!value || loading) return;
87
+ input = '';
88
+ const userMsg: ChatMessage = { id: nextId(), role: 'user', content: value };
89
+ const assistantMsg: ChatMessage = { id: nextId(), role: 'assistant', content: '', citations: [] };
90
+ messages = [...messages, userMsg, assistantMsg];
91
+ loading = true;
92
+ await tick();
93
+ scrollToBottom();
94
+
95
+ const transcript = messages
96
+ .filter((m) => !m.welcome && !(m.role === 'assistant' && m.id === assistantMsg.id))
97
+ .map((m) => ({ role: m.role, content: m.content }));
98
+
99
+ if (buildMode !== 'edge') {
100
+ const sourceRecords = await ensureRecords();
101
+ updateAssistant(assistantMsg.id, createLocalAskDraft(value, sourceRecords));
102
+ loading = false;
103
+ await tick();
104
+ scrollToBottom();
105
+ return;
106
+ }
107
+
108
+ try {
109
+ const response = await fetch('/api/ask', {
110
+ method: 'POST',
111
+ headers: {
112
+ 'content-type': 'application/json',
113
+ accept: 'text/event-stream'
114
+ },
115
+ body: JSON.stringify({
116
+ question: value,
117
+ messages: transcript,
118
+ ...scope
119
+ })
120
+ });
121
+
122
+ if (response.headers.get('content-type')?.includes('text/event-stream')) {
123
+ await readAskStream(response, assistantMsg.id);
124
+ if (!response.ok && !findMessage(assistantMsg.id)?.error) {
125
+ throw new Error(`Ask AI returned ${response.status}.`);
126
+ }
127
+ return;
128
+ }
129
+ if (!response.ok) {
130
+ const failure = await response.json().catch(() => ({})) as { error?: string };
131
+ throw new Error(failure.error ?? `Ask AI returned ${response.status}.`);
132
+ }
133
+ const result = await response.json() as {
134
+ answer?: string;
135
+ citations?: Citation[];
136
+ };
137
+ updateAssistant(assistantMsg.id, {
138
+ content: result.answer ?? '',
139
+ citations: result.citations ?? []
140
+ });
141
+ } catch (requestError) {
142
+ const sourceRecords = await ensureRecords();
143
+ const fallbackCitations = rankRecords(sourceRecords, value.toLowerCase(), scope).slice(0, 3).map((result) => ({
144
+ title: result.title,
145
+ url: result.url,
146
+ ...(result.section ? { section: result.section } : {})
147
+ }));
148
+ const fallback = fallbackCitations.length > 0
149
+ ? `I found ${fallbackCitations.length} relevant source${fallbackCitations.length === 1 ? '' : 's'}. Connect the ${config.ai.provider} provider to replace this local draft with a hosted Ask AI response.`
150
+ : `Ask AI is ready. Connect ${config.ai.provider} and index your docs to answer this question with citations.`;
151
+ updateAssistant(assistantMsg.id, {
152
+ content: fallback,
153
+ citations: fallbackCitations,
154
+ error: requestError instanceof Error ? requestError.message : 'Ask AI failed.'
155
+ });
156
+ } finally {
157
+ loading = false;
158
+ await tick();
159
+ scrollToBottom();
160
+ }
161
+ }
162
+
163
+ async function ensureRecords(): Promise<SvedocsSearchRecord[]> {
164
+ if (loadedRecords.length > 0 || !loadRecords) return loadedRecords;
165
+ if (!recordsRequest) {
166
+ recordsRequest = loadRecords()
167
+ .then((nextRecords) => {
168
+ loadedRecords = nextRecords;
169
+ return nextRecords;
170
+ })
171
+ .catch((error) => {
172
+ recordsRequest = undefined;
173
+ throw error;
174
+ });
175
+ }
176
+ try {
177
+ return await recordsRequest;
178
+ } catch {
179
+ return loadedRecords;
180
+ }
181
+ }
182
+
183
+ function createLocalAskDraft(question: string, records: SvedocsSearchRecord[]): Partial<ChatMessage> {
184
+ const citations = rankRecords(records, question.toLowerCase(), scope).slice(0, 3).map((result) => ({
185
+ title: result.title,
186
+ url: result.url,
187
+ ...(result.section ? { section: result.section } : {})
188
+ }));
189
+ return {
190
+ content: citations.length > 0
191
+ ? `I found ${citations.length} relevant source${citations.length === 1 ? '' : 's'} in this documentation.`
192
+ : 'I could not find a matching local source for that question.',
193
+ citations
194
+ };
195
+ }
196
+
197
+ function findMessage(id: number): ChatMessage | undefined {
198
+ return messages.find((m) => m.id === id);
199
+ }
200
+
201
+ function updateAssistant(id: number, patch: Partial<ChatMessage>) {
202
+ messages = messages.map((m) => (m.id === id ? { ...m, ...patch } : m));
203
+ }
204
+
205
+ function appendDelta(id: number, delta: string) {
206
+ if (!delta) return;
207
+ messages = messages.map((m) => (m.id === id ? { ...m, content: m.content + delta } : m));
208
+ scrollToBottom();
209
+ }
210
+
211
+ function setCitations(id: number, citations: Citation[]) {
212
+ messages = messages.map((m) => (m.id === id ? { ...m, citations } : m));
213
+ }
214
+
215
+ function setError(id: number, error: string) {
216
+ messages = messages.map((m) => (m.id === id ? { ...m, error } : m));
217
+ }
218
+
219
+ function rankRecords(records: SvedocsSearchRecord[], query: string, scope: SearchScope) {
220
+ return searchRecords(records, { query, limit: 5, ...scope });
221
+ }
222
+
223
+ async function readAskStream(response: Response, assistantId: number) {
224
+ const reader = response.body?.getReader();
225
+ if (!reader) return;
226
+ const decoder = new TextDecoder();
227
+ let buffer = '';
228
+ while (true) {
229
+ const { done, value } = await reader.read();
230
+ if (done) break;
231
+ buffer += decoder.decode(value, { stream: true });
232
+ let boundary = buffer.indexOf('\n\n');
233
+ while (boundary >= 0) {
234
+ readAskEvent(buffer.slice(0, boundary), assistantId);
235
+ buffer = buffer.slice(boundary + 2);
236
+ boundary = buffer.indexOf('\n\n');
237
+ }
238
+ }
239
+ if (buffer.trim()) readAskEvent(buffer, assistantId);
240
+ }
241
+
242
+ function readAskEvent(block: string, assistantId: number) {
243
+ const event = /^event:\s*(.+)$/m.exec(block)?.[1] ?? 'message';
244
+ const data = block.split('\n').filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trim()).join('\n');
245
+ if (!data || data === '[DONE]') return;
246
+ const payload = JSON.parse(data) as {
247
+ answer?: string;
248
+ delta?: string;
249
+ content?: string;
250
+ citations?: Citation[];
251
+ chunks?: Array<{ title?: string; url?: string; metadata?: Record<string, unknown>; item?: { title?: string; metadata?: Record<string, unknown> } }>;
252
+ choices?: Array<{ delta?: { content?: string }; message?: { content?: string }; text?: string }>;
253
+ error?: string;
254
+ };
255
+ if (event === 'answer') updateAssistant(assistantId, { content: payload.answer ?? '' });
256
+ if (event === 'delta') appendDelta(assistantId, payload.delta ?? payload.content ?? '');
257
+ if (event === 'message') {
258
+ appendDelta(
259
+ assistantId,
260
+ payload.choices?.[0]?.delta?.content
261
+ ?? payload.choices?.[0]?.message?.content
262
+ ?? payload.choices?.[0]?.text
263
+ ?? payload.content
264
+ ?? ''
265
+ );
266
+ }
267
+ if (event === 'chunks') setCitations(assistantId, normalizeChunkCitations(payload.chunks ?? []));
268
+ if (event === 'citations') setCitations(assistantId, payload.citations ?? []);
269
+ if (event === 'error') setError(assistantId, payload.error ?? 'Ask AI failed.');
270
+ }
271
+
272
+ function normalizeChunkCitations(chunks: Array<{ title?: string; url?: string; metadata?: Record<string, unknown>; item?: { title?: string; metadata?: Record<string, unknown> } }>): Citation[] {
273
+ return chunks.slice(0, 5).map((chunk, index) => {
274
+ const metadata = normalizeSvedocsMetadata({ ...(chunk.item?.metadata ?? {}), ...(chunk.metadata ?? {}) });
275
+ const section = stringValue(metadata.section);
276
+ return {
277
+ title: stringValue(chunk.title) ?? stringValue(chunk.item?.title) ?? stringValue(metadata.title) ?? `Source ${index + 1}`,
278
+ url: stringValue(chunk.url) ?? stringValue(metadata.url) ?? stringValue(metadata.source_url) ?? '#',
279
+ ...(section ? { section } : {})
280
+ };
281
+ });
282
+ }
283
+
284
+ function normalizeSvedocsMetadata(metadata: Record<string, unknown>) {
285
+ const embedded = stringValue(metadata.svedocs);
286
+ if (!embedded) return metadata;
287
+ try {
288
+ const parsed = JSON.parse(embedded) as Record<string, unknown>;
289
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return metadata;
290
+ return { ...metadata, ...parsed };
291
+ } catch {
292
+ return metadata;
293
+ }
294
+ }
295
+
296
+ function stringValue(value: unknown): string | undefined {
297
+ return typeof value === 'string' && value.trim() ? value : undefined;
298
+ }
299
+
300
+ function handleKeydown(event: KeyboardEvent) {
301
+ if (event.key === 'Escape') {
302
+ event.preventDefault();
303
+ hide();
304
+ }
305
+ if (event.key === 'Tab') trapFocus(event, panel);
306
+ }
307
+
308
+ function handleComposerKeydown(event: KeyboardEvent) {
309
+ if (event.key === 'Enter' && !event.shiftKey) {
310
+ event.preventDefault();
311
+ send(input);
312
+ }
313
+ }
314
+
315
+ function trapFocus(event: KeyboardEvent, root: HTMLElement | undefined) {
316
+ if (!root) return;
317
+ const focusable = Array.from(
318
+ root.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])')
319
+ ).filter((element) => !element.hasAttribute('disabled') && element.offsetParent !== null);
320
+ const first = focusable[0];
321
+ const last = focusable.at(-1);
322
+ if (!first || !last) return;
323
+ if (event.shiftKey && document.activeElement === first) {
324
+ event.preventDefault();
325
+ last.focus();
326
+ } else if (!event.shiftKey && document.activeElement === last) {
327
+ event.preventDefault();
328
+ first.focus();
329
+ }
330
+ }
331
+
332
+ onMount(() => {
333
+ window.addEventListener('svedocs:open-ai', show);
334
+ return () => window.removeEventListener('svedocs:open-ai', show);
335
+ });
336
+ </script>
337
+
338
+ {#if open}
339
+ <div class="sd-chat-portal" use:portal>
340
+ <div
341
+ bind:this={panel}
342
+ class="sd-chat-panel"
343
+ role="dialog"
344
+ aria-modal="true"
345
+ aria-label={label}
346
+ tabindex="-1"
347
+ on:keydown={handleKeydown}
348
+ >
349
+ <header class="sd-chat-header">
350
+ <div class="sd-chat-title">
351
+ <span class="sd-chat-mark" aria-hidden="true"></span>
352
+ <strong>{label}</strong>
353
+ </div>
354
+ <div class="sd-chat-actions">
355
+ {#if messages.filter((m) => !m.welcome).length > 0}
356
+ <button type="button" class="sd-chat-icon-button" aria-label="New chat" title="New chat" on:click={reset}>
357
+ <svg aria-hidden="true" viewBox="0 0 24 24">
358
+ <path d="M4 12h8m4 0h4M12 4v8m0 4v4" />
359
+ </svg>
360
+ </button>
361
+ {/if}
362
+ <button type="button" class="sd-chat-icon-button" aria-label="Close" title="Close" on:click={hide}>
363
+ <svg aria-hidden="true" viewBox="0 0 24 24">
364
+ <path d="M6 6l12 12M18 6L6 18" />
365
+ </svg>
366
+ </button>
367
+ </div>
368
+ </header>
369
+
370
+ <div bind:this={scrollEl} class="sd-chat-messages" role="log" aria-live="polite">
371
+ {#if messages.length === 0}
372
+ <div class="sd-chat-empty">
373
+ <p>Ask anything about these docs.</p>
374
+ </div>
375
+ {/if}
376
+ {#each messages as message (message.id)}
377
+ <div class="sd-chat-bubble" data-role={message.role}>
378
+ <div class="sd-chat-bubble-body">
379
+ {#if message.role === 'assistant' && !message.content && loading && message.id === messages[messages.length - 1]?.id}
380
+ <span class="sd-chat-typing" aria-label="Thinking">
381
+ <span></span><span></span><span></span>
382
+ </span>
383
+ {:else}
384
+ {message.content}
385
+ {/if}
386
+ </div>
387
+ {#if message.citations && message.citations.length > 0}
388
+ <div class="sd-chat-citations">
389
+ {#each message.citations as citation}
390
+ <a href={citation.url} on:click={hide}>
391
+ {citation.section ?? citation.title}
392
+ </a>
393
+ {/each}
394
+ </div>
395
+ {/if}
396
+ {#if message.error}
397
+ <p class="sd-chat-error">{message.error}</p>
398
+ {/if}
399
+ </div>
400
+ {/each}
401
+ </div>
402
+
403
+ {#if showSuggestions}
404
+ <div class="sd-chat-suggestions">
405
+ {#each suggestions as suggestion}
406
+ <button type="button" on:click={() => send(suggestion)}>{suggestion}</button>
407
+ {/each}
408
+ </div>
409
+ {/if}
410
+
411
+ <form class="sd-chat-composer" on:submit|preventDefault={() => send(input)}>
412
+ <textarea
413
+ bind:this={textarea}
414
+ bind:value={input}
415
+ rows="1"
416
+ placeholder={placeholder}
417
+ on:keydown={handleComposerKeydown}
418
+ ></textarea>
419
+ <button
420
+ class="sd-chat-send"
421
+ type="submit"
422
+ aria-label="Send"
423
+ title="Send"
424
+ disabled={!input.trim() || loading}
425
+ >
426
+ <svg aria-hidden="true" viewBox="0 0 24 24">
427
+ <path d="M4 12l16-8-6 16-2-7-8-1Z" />
428
+ </svg>
429
+ </button>
430
+ </form>
431
+ </div>
432
+ </div>
433
+ {/if}
@@ -0,0 +1,27 @@
1
+ import type { SvedocsResolvedConfig, SvedocsSearchRecord } from '../core/types.js';
2
+ import type { SearchScope } from '../search/types.js';
3
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
4
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
5
+ $$bindings?: Bindings;
6
+ } & Exports;
7
+ (internal: unknown, props: Props & {
8
+ $$events?: Events;
9
+ $$slots?: Slots;
10
+ }): Exports & {
11
+ $set?: any;
12
+ $on?: any;
13
+ };
14
+ z_$$bindings?: Bindings;
15
+ }
16
+ declare const AskAiPanel: $$__sveltets_2_IsomorphicComponent<{
17
+ config: SvedocsResolvedConfig;
18
+ records?: SvedocsSearchRecord[];
19
+ loadRecords?: (() => Promise<SvedocsSearchRecord[]>) | undefined;
20
+ scope?: SearchScope;
21
+ buildMode?: SvedocsResolvedConfig["build"]["mode"];
22
+ }, {
23
+ [evt: string]: CustomEvent<any>;
24
+ }, {}, {}, string>;
25
+ type AskAiPanel = InstanceType<typeof AskAiPanel>;
26
+ export default AskAiPanel;
27
+ //# sourceMappingURL=AskAiPanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AskAiPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/theme/AskAiPanel.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAmatD,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,UAAU;YAViH,qBAAqB;cAAY,mBAAmB,EAAE;kBAAgB,CAAC,MAAM,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC,GAAG,SAAS;YAAU,WAAW;gBAAc,qBAAqB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;;;kBAUnO,CAAC;AAC9E,KAAK,UAAU,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;AACpD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ import { classNames, type SvedocsButtonVariant, type SvedocsControlDensity } from './forms.js';
4
+
5
+ interface $$Props extends Omit<HTMLButtonAttributes, 'class' | 'type'> {
6
+ class?: string;
7
+ density?: SvedocsControlDensity;
8
+ type?: HTMLButtonAttributes['type'];
9
+ variant?: SvedocsButtonVariant;
10
+ }
11
+
12
+ export let type: HTMLButtonAttributes['type'] = 'button';
13
+ export let variant: SvedocsButtonVariant = 'default';
14
+ export let density: SvedocsControlDensity = 'md';
15
+
16
+ let className = '';
17
+
18
+ export { className as class };
19
+ </script>
20
+
21
+ <button
22
+ {...$$restProps}
23
+ class={classNames('sd-control-button', className)}
24
+ data-variant={variant}
25
+ data-density={density}
26
+ {type}
27
+ on:blur
28
+ on:click
29
+ on:focus
30
+ on:keydown
31
+ on:keyup
32
+ >
33
+ <slot />
34
+ </button>
@@ -0,0 +1,41 @@
1
+ import type { HTMLButtonAttributes } from 'svelte/elements';
2
+ import { type SvedocsButtonVariant, type SvedocsControlDensity } from './forms.js';
3
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
4
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
5
+ $$bindings?: Bindings;
6
+ } & Exports;
7
+ (internal: unknown, props: Props & {
8
+ $$events?: Events;
9
+ $$slots?: Slots;
10
+ }): Exports & {
11
+ $set?: any;
12
+ $on?: any;
13
+ };
14
+ z_$$bindings?: Bindings;
15
+ }
16
+ type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
17
+ default: any;
18
+ } ? Props extends Record<string, never> ? any : {
19
+ children?: any;
20
+ } : {});
21
+ declare const Button: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<Omit<HTMLButtonAttributes, "type" | "class"> & {
22
+ class?: string;
23
+ density?: SvedocsControlDensity;
24
+ type?: HTMLButtonAttributes["type"];
25
+ variant?: SvedocsButtonVariant;
26
+ }, {
27
+ default: {};
28
+ }>, {
29
+ blur: FocusEvent;
30
+ click: PointerEvent;
31
+ focus: FocusEvent;
32
+ keydown: KeyboardEvent;
33
+ keyup: KeyboardEvent;
34
+ } & {
35
+ [evt: string]: CustomEvent<any>;
36
+ }, {
37
+ default: {};
38
+ }, {}, string>;
39
+ type Button = InstanceType<typeof Button>;
40
+ export default Button;
41
+ //# sourceMappingURL=Button.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../src/theme/Button.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAc,KAAK,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA4B/F,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AACD,KAAK,gCAAgC,CAAC,KAAK,EAAE,KAAK,IAAI,KAAK,GACvD,CAAC,KAAK,SAAS;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,GACzB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACnC,GAAG,GACH;IAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAE,GAClB,EAAE,CAAC,CAAC;AAId,QAAA,MAAM,MAAM;YAnCA,MAAM;cACJ,qBAAqB;WACxB,oBAAoB,CAAC,MAAM,CAAC;cACzB,oBAAoB;;;;;;;;;;;;;cAgC+D,CAAC;AAChF,KAAK,MAAM,GAAG,YAAY,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,eAAe,MAAM,CAAC"}
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import { classNames, type SvedocsControlDensity } from './forms.js';
4
+
5
+ interface $$Props extends Omit<HTMLInputAttributes, 'checked' | 'class' | 'type'> {
6
+ checked?: boolean;
7
+ class?: string;
8
+ density?: SvedocsControlDensity;
9
+ description?: string;
10
+ indeterminate?: boolean;
11
+ invalid?: boolean;
12
+ label?: string;
13
+ }
14
+
15
+ export let checked = false;
16
+ export let indeterminate = false;
17
+ export let invalid = false;
18
+ export let label = '';
19
+ export let description = '';
20
+ export let density: SvedocsControlDensity = 'md';
21
+
22
+ let className = '';
23
+ let input: HTMLInputElement | undefined;
24
+
25
+ export { className as class };
26
+
27
+ $: if (input) input.indeterminate = indeterminate;
28
+ </script>
29
+
30
+ <label class={classNames('sd-checkbox', className)} data-density={density} data-invalid={invalid ? 'true' : undefined}>
31
+ <input
32
+ {...$$restProps}
33
+ bind:this={input}
34
+ type="checkbox"
35
+ aria-invalid={invalid ? 'true' : undefined}
36
+ bind:checked
37
+ on:blur
38
+ on:change
39
+ on:focus
40
+ on:input
41
+ on:keydown
42
+ on:keyup
43
+ />
44
+ <span class="sd-checkbox-box" aria-hidden="true">
45
+ <svg class="sd-checkbox-check" viewBox="0 0 16 16">
46
+ <path d="M3.2 8.2 6.4 11 12.8 4.5" />
47
+ </svg>
48
+ <svg class="sd-checkbox-minus" viewBox="0 0 16 16">
49
+ <path d="M3.5 8h9" />
50
+ </svg>
51
+ </span>
52
+ {#if label || description || $$slots.default}
53
+ <span class="sd-checkbox-copy">
54
+ {#if label}
55
+ <span class="sd-checkbox-label">{label}</span>
56
+ {/if}
57
+ <slot />
58
+ {#if description}
59
+ <span class="sd-checkbox-description">{description}</span>
60
+ {/if}
61
+ </span>
62
+ {/if}
63
+ </label>
@@ -0,0 +1,45 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import { type SvedocsControlDensity } from './forms.js';
3
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
4
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
5
+ $$bindings?: Bindings;
6
+ } & Exports;
7
+ (internal: unknown, props: Props & {
8
+ $$events?: Events;
9
+ $$slots?: Slots;
10
+ }): Exports & {
11
+ $set?: any;
12
+ $on?: any;
13
+ };
14
+ z_$$bindings?: Bindings;
15
+ }
16
+ type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
17
+ default: any;
18
+ } ? Props extends Record<string, never> ? any : {
19
+ children?: any;
20
+ } : {});
21
+ declare const Checkbox: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<Omit<HTMLInputAttributes, "type" | "class" | "checked"> & {
22
+ checked?: boolean;
23
+ class?: string;
24
+ density?: SvedocsControlDensity;
25
+ description?: string;
26
+ indeterminate?: boolean;
27
+ invalid?: boolean;
28
+ label?: string;
29
+ }, {
30
+ default: {};
31
+ }>, {
32
+ blur: FocusEvent;
33
+ change: Event;
34
+ focus: FocusEvent;
35
+ input: InputEvent;
36
+ keydown: KeyboardEvent;
37
+ keyup: KeyboardEvent;
38
+ } & {
39
+ [evt: string]: CustomEvent<any>;
40
+ }, {
41
+ default: {};
42
+ }, {}, string>;
43
+ type Checkbox = InstanceType<typeof Checkbox>;
44
+ export default Checkbox;
45
+ //# sourceMappingURL=Checkbox.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Checkbox.svelte.d.ts","sourceRoot":"","sources":["../../src/theme/Checkbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAc,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAwDpE,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AACD,KAAK,gCAAgC,CAAC,KAAK,EAAE,KAAK,IAAI,KAAK,GACvD,CAAC,KAAK,SAAS;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,GACzB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACnC,GAAG,GACH;IAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAE,GAClB,EAAE,CAAC,CAAC;AAId,QAAA,MAAM,QAAQ;cA/DA,OAAO;YACT,MAAM;cACJ,qBAAqB;kBACjB,MAAM;oBACJ,OAAO;cACb,OAAO;YACT,MAAM;;;;;;;;;;;;;;cAyDiF,CAAC;AAClF,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,6 @@
1
+ <script lang="ts">
2
+ export let code = '';
3
+ export let language = 'text';
4
+ </script>
5
+
6
+ <pre class="sd-code" data-language={language}><code>{code}</code></pre>