@poodle64/librarian 2026.10.3 → 2026.10.5

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 (54) hide show
  1. package/README.md +12 -4
  2. package/dist/chat.svelte.d.ts +40 -0
  3. package/dist/chat.svelte.js +57 -3
  4. package/dist/client.d.ts +3 -0
  5. package/dist/client.js +4 -1
  6. package/dist/components/activity-group/activity-group.svelte +72 -77
  7. package/dist/components/activity-group/activity-group.svelte.d.ts +6 -1
  8. package/dist/components/agent-transcript/agent-transcript.svelte +225 -9
  9. package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +19 -0
  10. package/dist/components/artefact-pane/artefact-pane.svelte +387 -43
  11. package/dist/components/artefact-pane/artefact-pane.svelte.d.ts +10 -1
  12. package/dist/components/conversation/conversation.svelte +74 -4
  13. package/dist/components/conversation/conversation.svelte.d.ts +15 -0
  14. package/dist/components/markdown/markdown.d.ts +30 -2
  15. package/dist/components/markdown/markdown.js +142 -5
  16. package/dist/components/markdown/markdown.svelte +130 -16
  17. package/dist/components/markdown/markdown.svelte.d.ts +12 -0
  18. package/dist/components/source-fold/index.d.ts +2 -0
  19. package/dist/components/source-fold/index.js +2 -0
  20. package/dist/components/source-fold/source-fold.svelte +225 -0
  21. package/dist/components/source-fold/source-fold.svelte.d.ts +17 -0
  22. package/dist/components/source-list/source-list.svelte +5 -3
  23. package/dist/components/source-list/source-list.svelte.d.ts +2 -0
  24. package/dist/components/thinking-row/thinking-row.svelte +38 -16
  25. package/dist/components/thinking-row/thinking-row.svelte.d.ts +3 -0
  26. package/dist/components/tool-row/tool-row.svelte +161 -49
  27. package/dist/components/tool-row/tool-row.svelte.d.ts +2 -3
  28. package/dist/components/visual-block/entity-card.svelte +94 -0
  29. package/dist/components/visual-block/entity-card.svelte.d.ts +7 -0
  30. package/dist/components/visual-block/timeline.svelte +99 -0
  31. package/dist/components/visual-block/timeline.svelte.d.ts +7 -0
  32. package/dist/components/visual-block/vega-chart.svelte +110 -0
  33. package/dist/components/visual-block/vega-chart.svelte.d.ts +7 -0
  34. package/dist/components/visual-block/visual-block.svelte +53 -0
  35. package/dist/components/visual-block/visual-block.svelte.d.ts +13 -0
  36. package/dist/components/written-card/index.d.ts +2 -0
  37. package/dist/components/written-card/index.js +2 -0
  38. package/dist/components/written-card/written-card.svelte +178 -0
  39. package/dist/components/written-card/written-card.svelte.d.ts +15 -0
  40. package/dist/copy.d.ts +73 -0
  41. package/dist/copy.js +56 -0
  42. package/dist/diff.d.ts +8 -0
  43. package/dist/diff.js +37 -0
  44. package/dist/steps.d.ts +28 -0
  45. package/dist/steps.js +168 -0
  46. package/dist/transcript.svelte.d.ts +19 -34
  47. package/dist/transcript.svelte.js +64 -56
  48. package/dist/visuals.d.ts +38 -0
  49. package/dist/visuals.js +82 -0
  50. package/dist/web-sources.d.ts +34 -0
  51. package/dist/web-sources.js +90 -0
  52. package/dist/written.d.ts +71 -0
  53. package/dist/written.js +83 -0
  54. package/package.json +14 -1
@@ -2,8 +2,10 @@ import type { Snippet } from 'svelte';
2
2
  import type { DescribeTool, Turn } from '../../transcript.svelte';
3
3
  import type { Verdict } from '../../chat.svelte';
4
4
  import type { Citation, LoadDocument } from '../../citations';
5
+ import { type LoadWritten, type WrittenUrl } from '../../written';
5
6
  import { type ChatCopy } from '../../copy';
6
7
  import { type AgentTranscriptProps } from '../agent-transcript/agent-transcript.svelte';
8
+ import type { Visual } from '../../visuals';
7
9
  interface Props {
8
10
  turns: Turn[];
9
11
  running: boolean;
@@ -56,6 +58,19 @@ interface Props {
56
58
  showing?: string;
57
59
  /** The persona's own words for its tools, and what they read. */
58
60
  describeTool?: DescribeTool;
61
+ /** Replaces the question at `position` (its place among `turns`) with
62
+ * `text` and asks it again from there (`Chat.edit`). Offered on every
63
+ * plain question while no answer is being written; without it no turn
64
+ * can be edited. */
65
+ onedit?: (position: number, text: string) => Promise<boolean>;
66
+ /** Reads one version of a document the persona wrote, to show it in the
67
+ * column. Without it its cards link but do not open. */
68
+ loadWritten?: LoadWritten;
69
+ /** Where a version downloads. Without it no download links render. */
70
+ writtenUrl?: WrittenUrl;
71
+ /** Renderers by a fence's language (`defaultVisuals`, and the app's own):
72
+ * a block of one is drawn by its component rather than as code. */
73
+ visuals?: Record<string, Visual>;
59
74
  /** Prices a turn's `outcome.costUsd` in the host's own currency and
60
75
  * words ("about A$0.34"), for the footer's cost line beside a turn's
61
76
  * duration. The package never converts currency itself; omitted, the
@@ -17,8 +17,13 @@
17
17
  */
18
18
  /** Close anything the stream has opened but not yet finished. */
19
19
  export declare function balance(markdown: string): string;
20
- export declare function render(markdown: string, { streaming }?: {
21
- streaming?: boolean | undefined;
20
+ /**
21
+ * `markdown` as sanitised HTML. With `previous`, the version before it, each
22
+ * block it changed is marked on the sanitised DOM, before its one serialisation.
23
+ */
24
+ export declare function render(markdown: string, { streaming, previous }?: {
25
+ streaming?: boolean;
26
+ previous?: string;
22
27
  }): string;
23
28
  /**
24
29
  * Mark every inline `code` span that names a known collection.
@@ -42,6 +47,29 @@ export declare function markCollections(root: HTMLElement, collections: Set<stri
42
47
  * button ROLE and a tab stop, and the component delegates the events.
43
48
  */
44
49
  export declare function markCitations(root: HTMLElement, valid: Set<number>): void;
50
+ /** A turn's prose and the fenced blocks of a registered language, in order. */
51
+ export type Part = {
52
+ kind: 'md';
53
+ text: string;
54
+ } | {
55
+ kind: 'visual';
56
+ lang: string;
57
+ source: string;
58
+ closed: boolean;
59
+ };
60
+ /**
61
+ * Cut `markdown` at each fenced block whose language `has` a renderer for.
62
+ * A fence of another language (or none) is ordinary code and stays in the
63
+ * prose, and so does anything inside one: an example of a visual's fence in a
64
+ * code block is text. A block still open at the end is `closed: false`.
65
+ */
66
+ export declare function splitVisuals(markdown: string, has: (lang: string) => boolean): Part[];
67
+ /**
68
+ * Turn each link to a numbered web page into its citation chip: the link's
69
+ * text stays, unlinked, and the chip after it carries the page's number and
70
+ * opens the page. Against the rendered DOM, for the reason `markCitations` is.
71
+ */
72
+ export declare function markWebLinks(root: HTMLElement, numbers: Map<string, number>): void;
45
73
  /**
46
74
  * Replace every `<pre><code>` in a rendered fragment with a highlighted one.
47
75
  * Runs against the DOM node rather than the HTML string so it can be applied
@@ -17,6 +17,8 @@
17
17
  */
18
18
  import DOMPurify from 'isomorphic-dompurify';
19
19
  import { marked } from 'marked';
20
+ import { markChanges } from '../../diff';
21
+ import { webKey } from '../../web-sources';
20
22
  marked.setOptions({ gfm: true, breaks: false });
21
23
  /** Close anything the stream has opened but not yet finished. */
22
24
  export function balance(markdown) {
@@ -35,14 +37,60 @@ export function balance(markdown) {
35
37
  }
36
38
  return text;
37
39
  }
38
- export function render(markdown, { streaming = false } = {}) {
39
- const source = streaming ? balance(markdown) : markdown;
40
- const html = marked.parse(source, { async: false });
41
- return DOMPurify.sanitize(html, {
40
+ const FETCHERS = 'img, picture, source, video, audio, track, iframe, object, embed, link, style, image, use';
41
+ const URL_ATTRS = ['src', 'href', 'xlink:href', 'poster', 'data', 'srcset'];
42
+ /** Only what cannot leave the origin: a data URL, a same-origin path, a fragment. */
43
+ function stays(url) {
44
+ const u = url.trim();
45
+ return /^data:/i.test(u) || (u.startsWith('/') && !u.startsWith('//')) || u.startsWith('#');
46
+ }
47
+ /**
48
+ * A rendered answer must not fetch from anywhere: a remote image or url() in
49
+ * agent output is a beacon that carries the conversation out in its query.
50
+ * A remote image degrades to its alt text (or its address as a link).
51
+ */
52
+ function dropFetchers(root) {
53
+ for (const el of root.querySelectorAll(FETCHERS)) {
54
+ const urls = URL_ATTRS.map((a) => el.getAttribute(a)).filter((v) => v !== null);
55
+ if (urls.every(stays))
56
+ continue;
57
+ if (el.tagName === 'IMG') {
58
+ const alt = el.getAttribute('alt')?.trim();
59
+ const src = el.getAttribute('src')?.trim() ?? '';
60
+ if (alt)
61
+ el.replaceWith(root.ownerDocument.createTextNode(alt));
62
+ else if (/^https?:\/\//i.test(src)) {
63
+ const a = root.ownerDocument.createElement('a');
64
+ a.setAttribute('href', src);
65
+ a.textContent = src;
66
+ el.replaceWith(a);
67
+ }
68
+ else
69
+ el.remove();
70
+ }
71
+ else
72
+ el.remove();
73
+ }
74
+ }
75
+ /**
76
+ * `markdown` as sanitised HTML. With `previous`, the version before it, each
77
+ * block it changed is marked on the sanitised DOM, before its one serialisation.
78
+ */
79
+ export function render(markdown, { streaming = false, previous } = {}) {
80
+ const body = sanitised(streaming ? balance(markdown) : markdown);
81
+ if (previous !== undefined && !streaming)
82
+ markChanges(body, sanitised(previous));
83
+ return body.innerHTML;
84
+ }
85
+ function sanitised(source) {
86
+ const body = DOMPurify.sanitize(marked.parse(source, { async: false }), {
42
87
  ADD_ATTR: ['target', 'rel', 'data-cite', 'tabindex', 'role'],
43
88
  FORBID_TAGS: ['style', 'form', 'input', 'button'],
44
- FORBID_ATTR: ['style', 'onerror', 'onload']
89
+ FORBID_ATTR: ['style', 'srcset', 'onerror', 'onload'],
90
+ RETURN_DOM: true
45
91
  });
92
+ dropFetchers(body);
93
+ return body;
46
94
  }
47
95
  let highlighterPromise = null;
48
96
  /** Loaded once, lazily — shiki's engine and grammars are not small. */
@@ -137,6 +185,95 @@ export function markCitations(root, valid) {
137
185
  text.replaceWith(fragment);
138
186
  }
139
187
  }
188
+ const OPEN = /^ {0,3}(`{3,}|~{3,})[ \t]*([^\s`]*)[^`]*$/;
189
+ const CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
190
+ /**
191
+ * Cut `markdown` at each fenced block whose language `has` a renderer for.
192
+ * A fence of another language (or none) is ordinary code and stays in the
193
+ * prose, and so does anything inside one: an example of a visual's fence in a
194
+ * code block is text. A block still open at the end is `closed: false`.
195
+ */
196
+ export function splitVisuals(markdown, has) {
197
+ const parts = [];
198
+ let prose = [];
199
+ let code = null;
200
+ const flush = () => {
201
+ if (prose.join('').trim())
202
+ parts.push({ kind: 'md', text: prose.join('\n') });
203
+ prose = [];
204
+ };
205
+ for (const line of markdown.split('\n')) {
206
+ if (code) {
207
+ const close = CLOSE.exec(line);
208
+ if (close && close[1][0] === code.fence[0] && close[1].length >= code.fence.length) {
209
+ if (code.visual) {
210
+ parts.push({
211
+ kind: 'visual',
212
+ lang: code.visual.lang,
213
+ source: code.visual.lines.join('\n'),
214
+ closed: true
215
+ });
216
+ }
217
+ else
218
+ prose.push(line);
219
+ code = null;
220
+ }
221
+ else if (code.visual)
222
+ code.visual.lines.push(line);
223
+ else
224
+ prose.push(line);
225
+ continue;
226
+ }
227
+ const open = OPEN.exec(line);
228
+ if (open) {
229
+ const lang = open[2];
230
+ if (lang && has(lang)) {
231
+ flush();
232
+ code = { fence: open[1], visual: { lang, lines: [] } };
233
+ }
234
+ else {
235
+ code = { fence: open[1] };
236
+ prose.push(line);
237
+ }
238
+ continue;
239
+ }
240
+ prose.push(line);
241
+ }
242
+ if (code?.visual)
243
+ parts.push({
244
+ kind: 'visual',
245
+ lang: code.visual.lang,
246
+ source: code.visual.lines.join('\n'),
247
+ closed: false
248
+ });
249
+ else
250
+ flush();
251
+ return parts;
252
+ }
253
+ /**
254
+ * Turn each link to a numbered web page into its citation chip: the link's
255
+ * text stays, unlinked, and the chip after it carries the page's number and
256
+ * opens the page. Against the rendered DOM, for the reason `markCitations` is.
257
+ */
258
+ export function markWebLinks(root, numbers) {
259
+ if (numbers.size === 0)
260
+ return;
261
+ for (const link of root.querySelectorAll('a[href]')) {
262
+ const href = link.getAttribute('href') ?? '';
263
+ const n = numbers.get(webKey(href));
264
+ if (n === undefined)
265
+ continue;
266
+ const chip = document.createElement('sup');
267
+ chip.className = 'ds-cite';
268
+ chip.dataset.cite = String(n);
269
+ chip.dataset.web = href;
270
+ chip.setAttribute('role', 'button');
271
+ chip.setAttribute('tabindex', '0');
272
+ chip.setAttribute('aria-label', `Web page ${n}`);
273
+ chip.textContent = String(n);
274
+ link.replaceWith(...link.childNodes, chip);
275
+ }
276
+ }
140
277
  /**
141
278
  * Replace every `<pre><code>` in a rendered fragment with a highlighted one.
142
279
  * Runs against the DOM node rather than the HTML string so it can be applied
@@ -7,7 +7,17 @@
7
7
  consuming app.
8
8
  -->
9
9
  <script lang="ts">
10
- import { highlight, markCitations, markCollections, render } from './markdown';
10
+ import { resolveCopy, type ChatCopy } from '../../copy';
11
+ import type { Visual } from '../../visuals';
12
+ import VisualBlock from '../visual-block/visual-block.svelte';
13
+ import {
14
+ highlight,
15
+ markCitations,
16
+ markCollections,
17
+ markWebLinks,
18
+ render,
19
+ splitVisuals
20
+ } from './markdown';
11
21
 
12
22
  interface Props {
13
23
  content: string;
@@ -16,10 +26,20 @@
16
26
  collectionNames?: Set<string>;
17
27
  /** `[n]` markers to turn into chips; omit and the markers stay as text. */
18
28
  citationNumbers?: Set<number>;
29
+ /** Web pages by `webKey`, numbered after the library's: a link to one
30
+ * renders as its chip. */
31
+ webNumbers?: Map<string, number>;
19
32
  oncite?: (n: number) => void;
20
33
  /** A step down, for prose that is reference material beside an answer
21
34
  * rather than the answer itself — the source pane's sections. */
22
35
  dense?: boolean;
36
+ /** Renderers by a fence's language: a block of one is drawn by its
37
+ * component in place of the code. Absent, every fence is code. */
38
+ visuals?: Record<string, Visual>;
39
+ /** The same document's earlier version, as Markdown: what differs here
40
+ * is marked with a bar. */
41
+ previous?: string;
42
+ copy?: Partial<ChatCopy>;
23
43
  }
24
44
 
25
45
  let {
@@ -27,43 +47,68 @@
27
47
  streaming = false,
28
48
  collectionNames = new Set(),
29
49
  citationNumbers = new Set(),
50
+ webNumbers = new Map(),
30
51
  oncite,
31
- dense = false
52
+ dense = false,
53
+ visuals,
54
+ previous,
55
+ copy
32
56
  }: Props = $props();
33
57
  let host = $state<HTMLElement | null>(null);
34
58
 
35
- const html = $derived(render(content, { streaming }));
59
+ const words = $derived(resolveCopy(copy));
60
+
61
+ // A visual is its own component between chunks of prose, so a token that
62
+ // grows the last chunk leaves the charts above it standing.
63
+ const parts = $derived(
64
+ visuals
65
+ ? splitVisuals(content, (lang) => Object.hasOwn(visuals, lang))
66
+ : [{ kind: 'md' as const, text: content }]
67
+ );
68
+ const chunked = $derived(parts.some((part) => part.kind === 'visual'));
69
+ const lastPart = $derived(parts.length - 1);
70
+
71
+ const prose = (text: string, last: boolean): string => render(text, { streaming: streaming && last, previous });
36
72
 
37
73
  // Highlight only once the text has settled. Re-running per token would
38
74
  // re-parse grammars for a block that changes again milliseconds later.
39
75
  $effect(() => {
40
- if (!host || !html) return;
76
+ void parts;
77
+ if (!host) return;
41
78
  // Chips are cheap and wanted DURING streaming; highlighting is not.
42
79
  markCollections(host, collectionNames);
43
80
  markCitations(host, citationNumbers);
81
+ markWebLinks(host, webNumbers);
44
82
  if (streaming) return;
45
83
  void highlight(host);
46
84
  });
47
85
 
48
86
  // Delegated, because the chips are created by the sanitiser's output rather
49
87
  // than by this template — there is no element here to put a handler on.
50
- function citeFrom(target: EventTarget | null): number | null {
51
- const chip = (target as HTMLElement | null)?.closest?.('[data-cite]');
52
- const n = Number((chip as HTMLElement | undefined)?.dataset.cite);
53
- return Number.isFinite(n) && n > 0 ? n : null;
88
+ function chipOf(target: EventTarget | null): HTMLElement | null {
89
+ return ((target as HTMLElement | null)?.closest?.('[data-cite]') as HTMLElement | null) ?? null;
90
+ }
91
+
92
+ function press(chip: HTMLElement) {
93
+ if (chip.dataset.web) {
94
+ window.open(chip.dataset.web, '_blank', 'noopener,noreferrer');
95
+ return;
96
+ }
97
+ const n = Number(chip.dataset.cite);
98
+ if (Number.isFinite(n) && n > 0) oncite?.(n);
54
99
  }
55
100
 
56
101
  function click(event: MouseEvent) {
57
- const n = citeFrom(event.target);
58
- if (n !== null) oncite?.(n);
102
+ const chip = chipOf(event.target);
103
+ if (chip) press(chip);
59
104
  }
60
105
 
61
106
  function keydown(event: KeyboardEvent) {
62
107
  if (event.key !== 'Enter' && event.key !== ' ') return;
63
- const n = citeFrom(event.target);
64
- if (n === null) return;
108
+ const chip = chipOf(event.target);
109
+ if (!chip) return;
65
110
  event.preventDefault();
66
- oncite?.(n);
111
+ press(chip);
67
112
  }
68
113
  </script>
69
114
 
@@ -77,9 +122,30 @@
77
122
  class="agent-prose"
78
123
  class:is-dense={dense}
79
124
  class:is-streaming={streaming}
125
+ class:is-chunked={chunked}
80
126
  >
81
- <!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitised in render() -->
82
- {@html html}
127
+ {#if !chunked}
128
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitised in render() -->
129
+ {@html prose(content, true)}
130
+ {:else}
131
+ {#each parts as part, i (i)}
132
+ {#if part.kind === 'md'}
133
+ <div class="ds-lib-chunk">
134
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitised in render() -->
135
+ {@html prose(part.text, i === lastPart)}
136
+ </div>
137
+ {:else if visuals}
138
+ <VisualBlock
139
+ lang={part.lang}
140
+ visual={visuals[part.lang]}
141
+ source={part.source}
142
+ closed={part.closed}
143
+ {streaming}
144
+ {words}
145
+ />
146
+ {/if}
147
+ {/each}
148
+ {/if}
83
149
  </div>
84
150
 
85
151
  <style>
@@ -111,6 +177,31 @@
111
177
  animation: ds-lib-caret 1.1s steps(1, end) infinite;
112
178
  }
113
179
 
180
+ /* Chunked, the caret rides the last prose chunk's last block, not the chunk. */
181
+ .agent-prose.is-chunked.is-streaming :global(> .ds-lib-chunk:last-child::after) {
182
+ content: none;
183
+ }
184
+
185
+ .agent-prose.is-chunked.is-streaming :global(> .ds-lib-chunk:last-child > *:last-child::after) {
186
+ content: '';
187
+ display: inline-block;
188
+ width: 0.45em;
189
+ height: 1.05em;
190
+ margin-inline-start: 0.12em;
191
+ border-radius: 1px;
192
+ background: var(--ds-color-primary);
193
+ vertical-align: -0.15em;
194
+ animation: ds-lib-caret 1.1s steps(1, end) infinite;
195
+ }
196
+
197
+ .agent-prose :global(.ds-lib-chunk > *:first-child) {
198
+ margin-top: 0;
199
+ }
200
+
201
+ .agent-prose :global(.ds-lib-chunk > *:last-child) {
202
+ margin-bottom: 0;
203
+ }
204
+
114
205
  @keyframes ds-lib-caret {
115
206
  50% {
116
207
  opacity: 0;
@@ -118,7 +209,8 @@
118
209
  }
119
210
 
120
211
  @media (prefers-reduced-motion: reduce) {
121
- .agent-prose.is-streaming :global(> *:last-child::after) {
212
+ .agent-prose.is-streaming :global(> *:last-child::after),
213
+ .agent-prose.is-chunked.is-streaming :global(> .ds-lib-chunk:last-child > *:last-child::after) {
122
214
  animation: none;
123
215
  }
124
216
  }
@@ -354,6 +446,28 @@
354
446
  outline-offset: 2px;
355
447
  }
356
448
 
449
+ /* What this version changed: a bar in the margin beside the block. */
450
+ .agent-prose :global([data-changed]) {
451
+ position: relative;
452
+ }
453
+
454
+ .agent-prose :global([data-changed]::before) {
455
+ content: '';
456
+ position: absolute;
457
+ inset-block: 0;
458
+ inset-inline-start: -0.75rem;
459
+ width: 2px;
460
+ background: var(--ds-color-foreground);
461
+ }
462
+
463
+ .agent-prose :global(tr[data-changed]::before) {
464
+ content: none;
465
+ }
466
+
467
+ .agent-prose :global(tr[data-changed] > td:first-child) {
468
+ box-shadow: inset 2px 0 0 var(--ds-color-foreground);
469
+ }
470
+
357
471
  .agent-prose :global(hr) {
358
472
  border: 0;
359
473
  border-top: 1px solid var(--ds-color-border);
@@ -1,3 +1,5 @@
1
+ import { type ChatCopy } from '../../copy';
2
+ import type { Visual } from '../../visuals';
1
3
  interface Props {
2
4
  content: string;
3
5
  streaming?: boolean;
@@ -5,10 +7,20 @@ interface Props {
5
7
  collectionNames?: Set<string>;
6
8
  /** `[n]` markers to turn into chips; omit and the markers stay as text. */
7
9
  citationNumbers?: Set<number>;
10
+ /** Web pages by `webKey`, numbered after the library's: a link to one
11
+ * renders as its chip. */
12
+ webNumbers?: Map<string, number>;
8
13
  oncite?: (n: number) => void;
9
14
  /** A step down, for prose that is reference material beside an answer
10
15
  * rather than the answer itself — the source pane's sections. */
11
16
  dense?: boolean;
17
+ /** Renderers by a fence's language: a block of one is drawn by its
18
+ * component in place of the code. Absent, every fence is code. */
19
+ visuals?: Record<string, Visual>;
20
+ /** The same document's earlier version, as Markdown: what differs here
21
+ * is marked with a bar. */
22
+ previous?: string;
23
+ copy?: Partial<ChatCopy>;
12
24
  }
13
25
  declare const Markdown: import("svelte").Component<Props, {}, "">;
14
26
  type Markdown = ReturnType<typeof Markdown>;
@@ -0,0 +1,2 @@
1
+ export { default } from './source-fold.svelte';
2
+ export { default as SourceFold } from './source-fold.svelte';
@@ -0,0 +1,2 @@
1
+ export { default } from './source-fold.svelte';
2
+ export { default as SourceFold } from './source-fold.svelte';