@svadmin/ui 0.66.0 → 0.67.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.
@@ -1,24 +0,0 @@
1
- import type { ChatMessage, ChatAction } from '@svadmin/core';
2
- interface Props {
3
- /** Render the launcher inside a layout toolbar instead of as a floating action button. */
4
- docked?: boolean;
5
- /**
6
- * Panel suffix for chat history persistence. Both default and explicit
7
- * suffixes are scoped by backend API + typed tenant identity.
8
- * Set to '' to disable persistence.
9
- */
10
- persistKey?: string;
11
- /** Custom persistence callback — called when messages change */
12
- onPersist?: (messages: ChatMessage[]) => void;
13
- /** Custom restore callback — called on mount to load history */
14
- onRestore?: () => ChatMessage[];
15
- /** Callback when user clicks an action button in an assistant message */
16
- onAction?: (action: ChatAction, message: ChatMessage) => void;
17
- /** Stable target for tree-scoped ask-ai events. */
18
- scope?: string;
19
- /** Owning Layout scope used to arbitrate global keyboard shortcuts. */
20
- ownerScope?: string;
21
- }
22
- declare const ChatDialog: import("svelte").Component<Props, {}, "">;
23
- type ChatDialog = ReturnType<typeof ChatDialog>;
24
- export default ChatDialog;
@@ -1,204 +0,0 @@
1
- <script lang="ts">
2
- import { captureAdminContext, useParsed, type ChatProvider } from '@svadmin/core';
3
- import { Sparkles, X, Check, RefreshCcw } from '@lucide/svelte';
4
- import { slide } from 'svelte/transition';
5
- import { Button } from './ui/button/index.js';
6
- import MarkdownRenderer from './MarkdownRenderer.svelte';
7
- import TooltipButton from './TooltipButton.svelte';
8
-
9
- let { open = $bindable(false) } = $props<{ open?: boolean }>();
10
-
11
- const adminContext = captureAdminContext();
12
- const provider = $derived(adminContext.chatProvider);
13
- const parsed = useParsed();
14
- const ctx = $derived({
15
- currentResource: parsed.resource,
16
- selectedRecordId: parsed.id,
17
- currentView: parsed.action,
18
- });
19
-
20
- let isPredicting = $state(false);
21
- let insightText = $state('');
22
- let abortController: AbortController | null = null;
23
- let requestEpoch = 0;
24
-
25
- $effect(() => {
26
- const scopedProvider = provider;
27
- const scopedContext = {
28
- currentResource: ctx.currentResource,
29
- selectedRecordId: ctx.selectedRecordId,
30
- currentView: ctx.currentView,
31
- };
32
- const shouldFetch = open;
33
- void adminContext.tenantCacheKey?.__svadminTenant;
34
-
35
- clearCopilotScope();
36
- if (shouldFetch && scopedProvider) {
37
- void requestInsights(scopedProvider, scopedContext);
38
- }
39
-
40
- return cancelCopilotRequest;
41
- });
42
-
43
- async function getInsights() {
44
- const scopedProvider = provider;
45
- if (!scopedProvider) return;
46
- await requestInsights(scopedProvider, {
47
- currentResource: ctx.currentResource,
48
- selectedRecordId: ctx.selectedRecordId,
49
- currentView: ctx.currentView,
50
- });
51
- }
52
-
53
- function cancelCopilotRequest() {
54
- requestEpoch += 1;
55
- abortController?.abort();
56
- abortController = null;
57
- }
58
-
59
- function clearCopilotScope() {
60
- cancelCopilotRequest();
61
- isPredicting = false;
62
- insightText = '';
63
- }
64
-
65
- async function requestInsights(
66
- scopedProvider: ChatProvider,
67
- scopedContext: typeof ctx,
68
- ) {
69
- cancelCopilotRequest();
70
- const epoch = requestEpoch;
71
- const controller = new AbortController();
72
- abortController = controller;
73
- isPredicting = true;
74
- insightText = '';
75
-
76
- const prompt = `Analyze the current context and provide 3 quick insights or context-aware suggestions for the admin user.
77
- Resource: ${scopedContext.currentResource}
78
- View: ${scopedContext.currentView}
79
- Record ID: ${scopedContext.selectedRecordId || 'None'}
80
- Keep it concise, professional, and use bullet points.`;
81
-
82
- try {
83
- const result = scopedProvider.sendMessage(
84
- [{ id: 'insights', role: 'system', content: prompt, timestamp: Date.now() }],
85
- { signal: controller.signal }
86
- );
87
-
88
- if (Symbol.asyncIterator in result) {
89
- for await (const chunk of result as AsyncIterable<string>) {
90
- if (epoch !== requestEpoch) return;
91
- insightText += chunk;
92
- }
93
- } else {
94
- const response = await result;
95
- if (epoch === requestEpoch) insightText = response;
96
- }
97
- } catch (err: unknown) {
98
- if (epoch === requestEpoch && err instanceof Error && err.name !== 'AbortError') {
99
- insightText = 'Failed to generate insights for this context.';
100
- }
101
- } finally {
102
- if (epoch === requestEpoch) {
103
- isPredicting = false;
104
- if (abortController === controller) abortController = null;
105
- }
106
- }
107
- }
108
- </script>
109
-
110
- {#if open}
111
- <div
112
- class="fixed right-0 top-14 bottom-0 w-80 bg-card border-l shadow-2xl z-40 flex flex-col"
113
- transition:slide={{ axis: 'x', duration: 300 }}
114
- >
115
- <!-- Header -->
116
- <div class="h-14 flex items-center justify-between px-4 border-b bg-muted/20">
117
- <div class="flex items-center gap-2 font-medium">
118
- <Sparkles class="h-4 w-4 text-primary" />
119
- Copilot Sidebar
120
- </div>
121
- <TooltipButton tooltip="Close panel" variant="ghost" size="icon-sm" onclick={() => open = false}>
122
- <X class="h-4 w-4" />
123
- </TooltipButton>
124
- </div>
125
-
126
- <!-- Context Meta -->
127
- <div class="px-4 py-3 border-b bg-muted/10 text-xs text-muted-foreground flex flex-col gap-1">
128
- <div class="flex items-center gap-2">
129
- <span class="w-16">Resource:</span>
130
- <span class="font-mono text-foreground">{ctx.currentResource || 'Dashboard'}</span>
131
- </div>
132
- <div class="flex items-center gap-2">
133
- <span class="w-16">View:</span>
134
- <span class="font-mono text-foreground capitalize">{ctx.currentView || 'List'}</span>
135
- </div>
136
- {#if ctx.selectedRecordId}
137
- <div class="flex items-center gap-2">
138
- <span class="w-16">Record ID:</span>
139
- <span class="font-mono text-foreground">{ctx.selectedRecordId}</span>
140
- </div>
141
- {/if}
142
- </div>
143
-
144
- <!-- Content Area -->
145
- <div class="flex-1 overflow-y-auto p-4 space-y-6">
146
-
147
- <!-- Smart Insights Section -->
148
- <div>
149
- <h4 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3 flex items-center justify-between">
150
- Smart Insights
151
- {#if isPredicting}
152
- <span class="flex items-center gap-1 text-[10px] text-primary normal-case font-medium">
153
- <div class="h-1 w-1 bg-primary rounded-full animate-bounce delay-75"></div>
154
- <div class="h-1 w-1 bg-primary rounded-full animate-bounce delay-150"></div>
155
- <div class="h-1 w-1 bg-primary rounded-full animate-bounce delay-300"></div>
156
- </span>
157
- {:else}
158
- <Button variant="ghost" size="icon-sm" class="h-5 w-5 hover:bg-muted" onclick={getInsights}>
159
- <RefreshCcw class="h-3 w-3" />
160
- </Button>
161
- {/if}
162
- </h4>
163
-
164
- <div class="text-sm">
165
- {#if !insightText && !isPredicting}
166
- <div class="text-muted-foreground italic text-center py-4 bg-muted/30 rounded-lg border border-dashed">
167
- No insights loaded.
168
- </div>
169
- {:else}
170
- <div class="bg-primary/5 p-3 rounded-xl border border-primary/10 prose prose-sm dark:prose-invert prose-p:my-1 prose-ul:my-1 prose-li:my-0.5">
171
- <MarkdownRenderer content={insightText} streaming={isPredicting} />
172
- </div>
173
- {/if}
174
- </div>
175
- </div>
176
-
177
- <!-- Quick Actions (Example static definitions for the copilot) -->
178
- <div>
179
- <h4 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">
180
- Suggested Actions
181
- </h4>
182
- <div class="space-y-2">
183
- {#if ctx.currentView === 'list'}
184
- <button type="button" class="w-full flex items-center gap-2 text-left p-2 rounded-lg hover:bg-muted border border-transparent hover:border-border transition-colors text-sm cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
185
- <div class="bg-background rounded shadow-sm p-1"><Check class="h-3.5 w-3.5 text-muted-foreground" /></div>
186
- <span>Generate monthly report</span>
187
- </button>
188
- <button type="button" class="w-full flex items-center gap-2 text-left p-2 rounded-lg hover:bg-muted border border-transparent hover:border-border transition-colors text-sm cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
189
- <div class="bg-background rounded shadow-sm p-1"><Check class="h-3.5 w-3.5 text-muted-foreground" /></div>
190
- <span>Find anomalies in {ctx.currentResource}</span>
191
- </button>
192
- {:else if ctx.currentView === 'edit' || ctx.currentView === 'create'}
193
- <button type="button" class="w-full flex items-center gap-2 text-left p-2 rounded-lg hover:bg-muted border border-transparent hover:border-border transition-colors text-sm cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
194
- <div class="bg-background rounded shadow-sm p-1"><Check class="h-3.5 w-3.5 text-muted-foreground" /></div>
195
- <span>Auto-fill form from context</span>
196
- </button>
197
- {:else}
198
- <div class="text-muted-foreground text-sm py-2 text-center">No precise actions for this view</div>
199
- {/if}
200
- </div>
201
- </div>
202
- </div>
203
- </div>
204
- {/if}
@@ -1,6 +0,0 @@
1
- type $$ComponentProps = {
2
- open?: boolean;
3
- };
4
- declare const CopilotPanel: import("svelte").Component<$$ComponentProps, {}, "open">;
5
- type CopilotPanel = ReturnType<typeof CopilotPanel>;
6
- export default CopilotPanel;
@@ -1,139 +0,0 @@
1
- <script lang="ts">
2
- import { captureAdminContext, type ChatProvider } from '@svadmin/core';
3
- import { Sparkles, RefreshCcw, Loader2 } from '@lucide/svelte';
4
- import { Button } from './ui/button/index.js';
5
- import MarkdownRenderer from './MarkdownRenderer.svelte';
6
-
7
- interface Props {
8
- title?: string;
9
- context: string;
10
- autoFetch?: boolean;
11
- class?: string;
12
- }
13
-
14
- let { title = 'AI Insights', context, autoFetch = true, class: className = '' }: Props = $props();
15
-
16
- const adminContext = captureAdminContext();
17
- const provider = $derived(adminContext.chatProvider);
18
-
19
- let isPredicting = $state(false);
20
- let insightText = $state('');
21
- let abortController: AbortController | null = null;
22
- let requestEpoch = 0;
23
-
24
- $effect(() => {
25
- const scopedProvider = provider;
26
- const scopedContext = context;
27
- const shouldFetch = autoFetch;
28
- void adminContext.tenantCacheKey?.__svadminTenant;
29
-
30
- clearInsightScope();
31
- if (shouldFetch && scopedContext && scopedProvider) {
32
- void requestInsights(scopedProvider, scopedContext);
33
- }
34
-
35
- return cancelInsightRequest;
36
- });
37
-
38
- export async function getInsights() {
39
- const scopedProvider = provider;
40
- if (!scopedProvider || !context) return;
41
- await requestInsights(scopedProvider, context);
42
- }
43
-
44
- function cancelInsightRequest() {
45
- requestEpoch += 1;
46
- abortController?.abort();
47
- abortController = null;
48
- }
49
-
50
- function clearInsightScope() {
51
- cancelInsightRequest();
52
- isPredicting = false;
53
- insightText = '';
54
- }
55
-
56
- async function requestInsights(scopedProvider: ChatProvider, scopedContext: string) {
57
- cancelInsightRequest();
58
- const epoch = requestEpoch;
59
- const controller = new AbortController();
60
- abortController = controller;
61
- isPredicting = true;
62
- insightText = '';
63
-
64
- const prompt = `Based on the following data/context, generate 3 concise, highly professional insights.
65
- Use bullet points. Do not include introductory text like "Here are the insights".
66
-
67
- Data Context:
68
- ${scopedContext}`;
69
-
70
- try {
71
- const result = scopedProvider.sendMessage(
72
- [{ id: 'insights', role: 'user', content: prompt, timestamp: Date.now() }],
73
- { signal: controller.signal }
74
- );
75
-
76
- if (Symbol.asyncIterator in result) {
77
- for await (const chunk of result as AsyncIterable<string>) {
78
- if (epoch !== requestEpoch) return;
79
- insightText += chunk;
80
- }
81
- } else {
82
- const response = await result;
83
- if (epoch === requestEpoch) insightText = response;
84
- }
85
- } catch (err: unknown) {
86
- if (epoch === requestEpoch && err instanceof Error && err.name !== 'AbortError') {
87
- insightText = 'Failed to generate insights.';
88
- }
89
- } finally {
90
- if (epoch === requestEpoch) {
91
- isPredicting = false;
92
- if (abortController === controller) abortController = null;
93
- }
94
- }
95
- }
96
- </script>
97
-
98
- <div
99
- class="rounded-xl border bg-card text-card-foreground shadow-sm flex flex-col {className}"
100
- role="region"
101
- aria-label={title}
102
- >
103
- <div class="flex items-center justify-between p-4 border-b">
104
- <div class="flex items-center gap-2 font-medium">
105
- <Sparkles class="h-4 w-4 text-warning" aria-hidden="true" />
106
- {title}
107
- </div>
108
- <Button
109
- variant="ghost"
110
- size="icon-sm"
111
- class="h-6 w-6"
112
- onclick={getInsights}
113
- disabled={isPredicting || !provider}
114
- aria-label="Refresh AI Insights"
115
- >
116
- <RefreshCcw class="h-3 w-3 {isPredicting ? 'animate-spin' : ''}" aria-hidden="true" />
117
- </Button>
118
- </div>
119
-
120
- <div class="p-4 flex-1" role="status" aria-live="polite" aria-busy={isPredicting}>
121
- {#if !provider}
122
- <div class="text-sm text-muted-foreground italic flex items-center justify-center h-full">
123
- ChatProvider not configured.
124
- </div>
125
- {:else if isPredicting && !insightText}
126
- <div class="flex items-center justify-center gap-2 text-sm text-muted-foreground h-full py-4">
127
- <Loader2 class="h-4 w-4 animate-spin" aria-hidden="true" /> Analyzing data...
128
- </div>
129
- {:else if !insightText}
130
- <div class="text-sm text-muted-foreground italic flex items-center justify-center h-full">
131
- No insights available. Click refresh to analyze.
132
- </div>
133
- {:else}
134
- <div class="text-sm prose prose-sm dark:prose-invert prose-p:my-1 prose-ul:my-1 prose-li:my-0.5">
135
- <MarkdownRenderer content={insightText} streaming={isPredicting} />
136
- </div>
137
- {/if}
138
- </div>
139
- </div>
@@ -1,11 +0,0 @@
1
- interface Props {
2
- title?: string;
3
- context: string;
4
- autoFetch?: boolean;
5
- class?: string;
6
- }
7
- declare const InsightCard: import("svelte").Component<Props, {
8
- getInsights: () => Promise<void>;
9
- }, "">;
10
- type InsightCard = ReturnType<typeof InsightCard>;
11
- export default InsightCard;
@@ -1,245 +0,0 @@
1
- <script lang="ts">
2
- /* eslint-disable svelte/no-at-html-tags */
3
- import { onMount } from "svelte";
4
-
5
- interface Props {
6
- /** The raw markdown string to render */
7
- content: string;
8
- /** Whether to enable streaming/typing effect styling */
9
- streaming?: boolean;
10
- /** Custom class for the container */
11
- class?: string;
12
- }
13
-
14
- let { content, streaming = false, class: className = "" }: Props = $props();
15
-
16
- type MarkedConstructor = typeof import("marked").Marked;
17
- type MarkedHighlight = typeof import("marked-highlight").markedHighlight;
18
- type HighlightApi = typeof import("highlight.js").default;
19
- type Sanitizer = typeof import("isomorphic-dompurify").default;
20
-
21
- // Lazily loaded optional peer dependencies (marked, marked-highlight,
22
- // highlight.js, isomorphic-dompurify). They are declared as optional peer
23
- // deps; statically importing them would crash consumers that have not
24
- // installed them, even when MarkdownRenderer is never rendered. Load them
25
- // dynamically so the module graph resolves without them and the component
26
- // degrades to escaped-text rendering when they are absent.
27
- let MarkedCtor = $state<MarkedConstructor | null>(null);
28
- let markedHighlightFn = $state<MarkedHighlight | null>(null);
29
- let hljs = $state<HighlightApi | null>(null);
30
- let DOMPurify = $state<Sanitizer | null>(null);
31
-
32
- onMount(() => {
33
- let cancelled = false;
34
- (async () => {
35
- try {
36
- const [markedPkg, markedHighlightPkg, hljsPkg, DOMPurifyPkg] = await Promise.all([
37
- import("marked"),
38
- import("marked-highlight"),
39
- import("highlight.js"),
40
- import("isomorphic-dompurify"),
41
- ]);
42
- if (cancelled) return;
43
- MarkedCtor = markedPkg.Marked;
44
- markedHighlightFn = markedHighlightPkg.markedHighlight;
45
- hljs = hljsPkg.default;
46
- DOMPurify = DOMPurifyPkg.default;
47
- // Only import the theme css when highlight.js is available
48
- if (hljs && Object.keys(hljs).length > 0) {
49
- import("highlight.js/styles/github-dark.css").catch(() => {});
50
- }
51
- } catch {
52
- // Optional deps not installed; fall back to escaped-text rendering.
53
- if (cancelled) return;
54
- }
55
- })();
56
- return () => { cancelled = true; };
57
- });
58
-
59
- const hasMarkdownDeps = $derived(
60
- typeof MarkedCtor === "function" &&
61
- typeof DOMPurify?.sanitize === "function" &&
62
- typeof markedHighlightFn === "function",
63
- );
64
-
65
- // Configure marked with syntax highlighting if available
66
- const markedObj = $derived.by(() => {
67
- const Constructor = MarkedCtor;
68
- const highlightExtension = markedHighlightFn;
69
- if (!Constructor || !highlightExtension) return null;
70
-
71
- return new Constructor(
72
- highlightExtension({
73
- langPrefix: "hljs language-",
74
- highlight(code: string, lang: string) {
75
- const language = hljs?.getLanguage && hljs.getLanguage(lang) ? lang : "plaintext";
76
- return hljs?.highlight ? hljs.highlight(code, { language }).value : code;
77
- },
78
- }),
79
- );
80
- });
81
-
82
- // Render HTML safely
83
- const html = $derived.by(() => {
84
- const purifier = DOMPurify;
85
- const parser = markedObj;
86
- if (hasMarkdownDeps && purifier && parser) {
87
- return purifier.sanitize(parser.parse(content || ""));
88
- }
89
- return `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</div>`;
90
- });
91
-
92
- // Handle copy code blocks
93
- let copiedBlock = $state<string | null>(null);
94
-
95
- function handleCopy(event: MouseEvent) {
96
- const target = event.target as HTMLElement;
97
- const button = target.closest(".copy-btn");
98
- if (!button) return;
99
-
100
- const pre = button.closest(".code-block-wrapper")?.querySelector("pre");
101
- if (!pre) return;
102
-
103
- const code = pre.textContent || "";
104
- navigator.clipboard.writeText(code);
105
-
106
- const id = Math.random().toString(36);
107
- copiedBlock = id;
108
- button.setAttribute("data-copied-id", id);
109
- // Swap to a semantic check icon; color comes from the .copy-btn CSS token.
110
- button.querySelector("svg")?.replaceWith(createCheckIcon());
111
-
112
- setTimeout(() => {
113
- if (copiedBlock === id) copiedBlock = null;
114
- }, 2000);
115
- }
116
-
117
- function createCheckIcon(): SVGSVGElement {
118
- const svgNamespace = "http://www.w3.org/2000/svg";
119
- const svg = document.createElementNS(svgNamespace, "svg");
120
- svg.setAttribute("width", "14");
121
- svg.setAttribute("height", "14");
122
- svg.setAttribute("viewBox", "0 0 24 24");
123
- svg.setAttribute("fill", "none");
124
- svg.setAttribute("stroke", "currentColor");
125
- svg.setAttribute("stroke-width", "2");
126
- svg.setAttribute("stroke-linecap", "round");
127
- svg.setAttribute("stroke-linejoin", "round");
128
- svg.setAttribute("aria-hidden", "true");
129
- svg.classList.add("lucide", "lucide-check");
130
-
131
- const polyline = document.createElementNS(svgNamespace, "polyline");
132
- polyline.setAttribute("points", "20 6 9 17 4 12");
133
- svg.appendChild(polyline);
134
-
135
- return svg;
136
- }
137
-
138
- function createCopyIcon(): SVGSVGElement {
139
- const svgNamespace = "http://www.w3.org/2000/svg";
140
- const svg = document.createElementNS(svgNamespace, "svg");
141
- svg.setAttribute("width", "14");
142
- svg.setAttribute("height", "14");
143
- svg.setAttribute("viewBox", "0 0 24 24");
144
- svg.setAttribute("fill", "none");
145
- svg.setAttribute("stroke", "currentColor");
146
- svg.setAttribute("stroke-width", "2");
147
- svg.setAttribute("stroke-linecap", "round");
148
- svg.setAttribute("stroke-linejoin", "round");
149
- svg.setAttribute("aria-hidden", "true");
150
- svg.classList.add("lucide", "lucide-copy");
151
-
152
- const rect = document.createElementNS(svgNamespace, "rect");
153
- rect.setAttribute("width", "14");
154
- rect.setAttribute("height", "14");
155
- rect.setAttribute("x", "8");
156
- rect.setAttribute("y", "8");
157
- rect.setAttribute("rx", "2");
158
- rect.setAttribute("ry", "2");
159
- svg.appendChild(rect);
160
-
161
- const path = document.createElementNS(svgNamespace, "path");
162
- path.setAttribute("d", "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2");
163
- svg.appendChild(path);
164
-
165
- return svg;
166
- }
167
-
168
- // Inject copy buttons into the HTML after rendering
169
- // We do this via action instead of raw string manipulation for safety
170
- function enhanceCodeBlocks(node: HTMLElement, _: string) {
171
- let destroyed = false;
172
- let enhancementScheduled = false;
173
-
174
- function enhance() {
175
- if (destroyed) return;
176
-
177
- // Find all pre > code blocks that don't have wrappers yet
178
- const pres = node.querySelectorAll("pre:not(.enhanced)");
179
- pres.forEach((pre) => {
180
- pre.classList.add("enhanced");
181
- const wrapper = document.createElement("div");
182
- wrapper.className =
183
- "code-block-wrapper group relative my-4 rounded-md bg-foreground/95";
184
-
185
- const header = document.createElement("div");
186
- header.className =
187
- "flex items-center justify-between px-4 py-2 text-xs text-background/70 border-b border-background/15";
188
-
189
- const lang =
190
- Array.from(pre.querySelector("code")?.classList || [])
191
- .find((className) => className.startsWith("language-"))
192
- ?.replace("language-", "") || "Code";
193
- const languageLabel = document.createElement("span");
194
- languageLabel.textContent = lang;
195
-
196
- const copyButton = document.createElement("button");
197
- copyButton.type = "button";
198
- copyButton.className =
199
- "copy-btn hover:text-background transition-colors flex items-center gap-1";
200
- copyButton.setAttribute("aria-label", "Copy code");
201
- copyButton.appendChild(createCopyIcon());
202
-
203
- header.append(languageLabel, copyButton);
204
- pre.replaceWith(wrapper);
205
- wrapper.appendChild(header);
206
-
207
- const scrollContainer = document.createElement("div");
208
- scrollContainer.className = "overflow-x-auto p-4";
209
- scrollContainer.appendChild(pre);
210
- wrapper.appendChild(scrollContainer);
211
- });
212
- }
213
-
214
- function scheduleEnhancement() {
215
- if (enhancementScheduled || destroyed) return;
216
- enhancementScheduled = true;
217
- queueMicrotask(() => {
218
- enhancementScheduled = false;
219
- enhance();
220
- });
221
- }
222
-
223
- node.addEventListener("click", handleCopy);
224
- enhance();
225
- scheduleEnhancement();
226
-
227
- return {
228
- update(_newHtml: string) {
229
- scheduleEnhancement();
230
- },
231
- destroy() {
232
- destroyed = true;
233
- node.removeEventListener("click", handleCopy);
234
- },
235
- };
236
- }
237
- </script>
238
-
239
- <div
240
- class="prose prose-sm dark:prose-invert max-w-none wrap-break-word {className}"
241
- class:streaming={streaming}
242
- use:enhanceCodeBlocks={html}
243
- >
244
- {@html html}
245
- </div>
@@ -1,11 +0,0 @@
1
- interface Props {
2
- /** The raw markdown string to render */
3
- content: string;
4
- /** Whether to enable streaming/typing effect styling */
5
- streaming?: boolean;
6
- /** Custom class for the container */
7
- class?: string;
8
- }
9
- declare const MarkdownRenderer: import("svelte").Component<Props, {}, "">;
10
- type MarkdownRenderer = ReturnType<typeof MarkdownRenderer>;
11
- export default MarkdownRenderer;