@braincrew-lab/langchain-canvas 0.2.0 → 0.4.9
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.
- package/dist/{ChartRenderer-AAWVGKV5.js → ChartRenderer-JGRJ23OB.js} +10 -20
- package/dist/{DocumentRenderer-NBRBPYU6.js → DocumentRenderer-ZQDQMX7X.js} +8 -4
- package/dist/FileRenderer-3ZHNORZJ.js +39 -0
- package/dist/SlidesRenderer-56R3TNWN.js +496 -0
- package/dist/{TableRenderer-VKDD3CB6.js → TableRenderer-3ESF577F.js} +49 -193
- package/dist/chunk-7T5DRR3F.js +109 -0
- package/dist/{chunk-S54GJDSJ.js → chunk-EHW446VF.js} +62 -47
- package/dist/chunk-FTNRRJ3K.js +13 -0
- package/dist/chunk-IFNRLN4Y.js +137 -0
- package/dist/{chunk-UL5F66PN.js → chunk-K2UZAYW2.js} +1 -1
- package/dist/chunk-SGOPRUQ4.js +182 -0
- package/dist/formula-27TCEZI5.js +2 -0
- package/dist/formula-cli.d.ts +2 -0
- package/dist/formula-cli.js +37 -0
- package/dist/index.d.ts +201 -289
- package/dist/index.js +393 -860
- package/dist/langgraph/index.d.ts +68 -0
- package/dist/langgraph/index.js +111 -0
- package/dist/styles.css +125 -100
- package/dist/types-BfGP9R2I.d.ts +324 -0
- package/package.json +40 -5
- package/dist/PdfRenderer-DPQT4E7O.js +0 -97
- package/dist/SlidesRenderer-6ZGHXTQX.js +0 -877
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact data shapes — mirror of `langchain_canvas/protocol/artifacts.py`.
|
|
3
|
+
*
|
|
4
|
+
* An `Artifact` is transport-agnostic: `{ id, type, title, version, status,
|
|
5
|
+
* data }`. `type` is a registry key resolved to a React component; `data` is the
|
|
6
|
+
* type-specific payload that component reads. Keep this file in lockstep with
|
|
7
|
+
* the Python module — a field here must exist there, and vice versa.
|
|
8
|
+
*/
|
|
9
|
+
type ArtifactStatus = "streaming" | "complete" | "error";
|
|
10
|
+
interface Artifact<TData = unknown> {
|
|
11
|
+
/** Stable identity — the reconciliation key. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Registry key: "document" | "chart" | ... */
|
|
14
|
+
type: string;
|
|
15
|
+
/** Shown in the canvas header / tab. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** 1-based; bumped on every `canvas.replace`. */
|
|
18
|
+
version: number;
|
|
19
|
+
status: ArtifactStatus;
|
|
20
|
+
data: TData;
|
|
21
|
+
meta?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The base substrate: raw HTML, rendered in a sandboxed iframe. Everything a
|
|
25
|
+
* canvas can show is ultimately HTML; `document` / `chart` / `table` are
|
|
26
|
+
* structured conveniences the SDK renders for you, while `html` lets an agent
|
|
27
|
+
* emit an arbitrary self-contained page (the Claude-Artifacts / Genspark model).
|
|
28
|
+
*/
|
|
29
|
+
interface HtmlData {
|
|
30
|
+
html: string;
|
|
31
|
+
}
|
|
32
|
+
interface DocumentData {
|
|
33
|
+
format: "markdown";
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
interface ChartSeries {
|
|
37
|
+
/** Column in `ChartData.rows` to plot. */
|
|
38
|
+
key: string;
|
|
39
|
+
label?: string;
|
|
40
|
+
color?: string;
|
|
41
|
+
}
|
|
42
|
+
interface ChartOptions {
|
|
43
|
+
stacked?: boolean;
|
|
44
|
+
yLabel?: string;
|
|
45
|
+
/** Chart title shown above the plot. */
|
|
46
|
+
title?: string;
|
|
47
|
+
/** Per-slice colors for pie charts, index-aligned to `rows`. */
|
|
48
|
+
colors?: string[];
|
|
49
|
+
}
|
|
50
|
+
interface ChartData {
|
|
51
|
+
chart: "line" | "bar" | "area" | "pie";
|
|
52
|
+
/** Tidy/long-form rows, consumed directly by the charting library. */
|
|
53
|
+
rows: Array<Record<string, string | number>>;
|
|
54
|
+
/** Category / x-axis field. */
|
|
55
|
+
xKey: string;
|
|
56
|
+
series: ChartSeries[];
|
|
57
|
+
options?: ChartOptions;
|
|
58
|
+
/**
|
|
59
|
+
* Optional raw ECharts `option`. When present it's rendered verbatim — an
|
|
60
|
+
* escape hatch for agents/apps that already produce a full ECharts config
|
|
61
|
+
* (the tidy `rows`/`series` model is ignored, and inline editing is disabled).
|
|
62
|
+
*/
|
|
63
|
+
echartsOption?: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
interface TableColumn {
|
|
66
|
+
key: string;
|
|
67
|
+
label?: string;
|
|
68
|
+
align?: "left" | "right" | "center";
|
|
69
|
+
}
|
|
70
|
+
interface TableData {
|
|
71
|
+
columns: TableColumn[];
|
|
72
|
+
rows: Array<Record<string, string | number>>;
|
|
73
|
+
/**
|
|
74
|
+
* Opaque spreadsheet state (Fortune-sheet sheets) once the user has edited the
|
|
75
|
+
* grid — carries merges, per-cell fonts/formats, and formulas that the simple
|
|
76
|
+
* columns/rows shape can't hold. Present after the first interactive edit;
|
|
77
|
+
* exporters prefer it over columns/rows.
|
|
78
|
+
*/
|
|
79
|
+
sheet?: Array<Record<string, unknown>>;
|
|
80
|
+
}
|
|
81
|
+
/** A freely-positioned element on a "blank" slide (percent geometry, 0–100). */
|
|
82
|
+
interface SlideElement {
|
|
83
|
+
id: string;
|
|
84
|
+
type: "text" | "image" | "shape";
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
w: number;
|
|
88
|
+
h: number;
|
|
89
|
+
text?: string;
|
|
90
|
+
src?: string;
|
|
91
|
+
fontSize?: number;
|
|
92
|
+
bold?: boolean;
|
|
93
|
+
color?: string;
|
|
94
|
+
align?: "left" | "center" | "right";
|
|
95
|
+
/** Shape kind for `type: "shape"`. */
|
|
96
|
+
shape?: "rect" | "ellipse" | "line";
|
|
97
|
+
/** Fill (rect/ellipse) or stroke (line) color for a shape. */
|
|
98
|
+
fill?: string;
|
|
99
|
+
}
|
|
100
|
+
interface Slide {
|
|
101
|
+
/** title · content (bullets) · section · image · two-column · blank (free canvas). */
|
|
102
|
+
layout?: "title" | "content" | "section" | "image" | "two-column" | "blank";
|
|
103
|
+
/** Freely-positioned elements for the "blank" layout. */
|
|
104
|
+
elements?: SlideElement[];
|
|
105
|
+
title?: string;
|
|
106
|
+
subtitle?: string;
|
|
107
|
+
bullets?: string[];
|
|
108
|
+
/** Right-hand bullets for the "two-column" layout. */
|
|
109
|
+
bullets2?: string[];
|
|
110
|
+
/** Image (data: URL or https URL) for the "image" layout. */
|
|
111
|
+
image?: string;
|
|
112
|
+
/** Slide background color (hex). */
|
|
113
|
+
background?: string;
|
|
114
|
+
/** Slide text color (hex). */
|
|
115
|
+
textColor?: string;
|
|
116
|
+
/** Speaker notes (not shown on the slide; exported to the .pptx notes pane). */
|
|
117
|
+
notes?: string;
|
|
118
|
+
/** Content padding as a percent of the slide width (a safe margin around the
|
|
119
|
+
* free canvas). Applied in the editor, present view, thumbnails, and export. */
|
|
120
|
+
padding?: number;
|
|
121
|
+
}
|
|
122
|
+
/** The deck's page size in inches — the coordinate space percent geometry
|
|
123
|
+
* refers to. Absent means the classic 16:9 canvas (10 x 5.625). When a
|
|
124
|
+
* template skin is attached, tools fill this with the skin's real page so
|
|
125
|
+
* the editor, the preview, and the exported file agree on one aspect
|
|
126
|
+
* ratio. */
|
|
127
|
+
interface SlidePage {
|
|
128
|
+
widthIn: number;
|
|
129
|
+
heightIn: number;
|
|
130
|
+
}
|
|
131
|
+
interface SlidesData {
|
|
132
|
+
slides: Slide[];
|
|
133
|
+
page?: SlidePage;
|
|
134
|
+
/** Optional pptx skin: a canvas reference ("sources/brand.pptx") whose
|
|
135
|
+
* master and layouts the pptx export builds on. Export-time only — the
|
|
136
|
+
* canvas preview does not render the skin; a missing or unreadable skin
|
|
137
|
+
* degrades to the blank-layout export. */
|
|
138
|
+
template?: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* A stored canvas file shown as itself — a window onto the store, not a copy.
|
|
142
|
+
* `path` is the canvas-relative reference (`sources/photo.png`); the renderer
|
|
143
|
+
* resolves it against the host's asset endpoint for display and download.
|
|
144
|
+
* `cover` / `excerpt` / `detail` are *derived* previews (never stored).
|
|
145
|
+
*/
|
|
146
|
+
interface FileData {
|
|
147
|
+
path: string;
|
|
148
|
+
name: string;
|
|
149
|
+
mediaType?: string;
|
|
150
|
+
size?: number;
|
|
151
|
+
/** data: URI thumbnail of page one (page-renderable sources). */
|
|
152
|
+
cover?: string;
|
|
153
|
+
/** Short text sample, via the source converter. */
|
|
154
|
+
excerpt?: string;
|
|
155
|
+
/** One-line content summary ("3 pages", "5 slides"). */
|
|
156
|
+
detail?: string;
|
|
157
|
+
}
|
|
158
|
+
type HtmlArtifact = Artifact<HtmlData> & {
|
|
159
|
+
type: "html";
|
|
160
|
+
};
|
|
161
|
+
type DocumentArtifact = Artifact<DocumentData> & {
|
|
162
|
+
type: "document";
|
|
163
|
+
};
|
|
164
|
+
type ChartArtifact = Artifact<ChartData> & {
|
|
165
|
+
type: "chart";
|
|
166
|
+
};
|
|
167
|
+
type TableArtifact = Artifact<TableData> & {
|
|
168
|
+
type: "table";
|
|
169
|
+
};
|
|
170
|
+
type SlidesArtifact = Artifact<SlidesData> & {
|
|
171
|
+
type: "slides";
|
|
172
|
+
};
|
|
173
|
+
type FileArtifact = Artifact<FileData> & {
|
|
174
|
+
type: "file";
|
|
175
|
+
};
|
|
176
|
+
type KnownArtifact = HtmlArtifact | DocumentArtifact | ChartArtifact | TableArtifact | SlidesArtifact | FileArtifact;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Canvas Wire Protocol v1 — event envelopes. Mirror of
|
|
180
|
+
* `langchain_canvas/protocol/events.py`. Every SSE frame is one `StreamEvent`,
|
|
181
|
+
* discriminated by `type`. See `docs/02-protocol.md` for the specification.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
interface MessageDelta {
|
|
185
|
+
type: "message.delta";
|
|
186
|
+
messageId: string;
|
|
187
|
+
text: string;
|
|
188
|
+
}
|
|
189
|
+
interface MessageEnd {
|
|
190
|
+
type: "message.end";
|
|
191
|
+
messageId: string;
|
|
192
|
+
}
|
|
193
|
+
interface ToolStart {
|
|
194
|
+
type: "tool.start";
|
|
195
|
+
toolCallId: string;
|
|
196
|
+
name: string;
|
|
197
|
+
}
|
|
198
|
+
interface ToolEnd {
|
|
199
|
+
type: "tool.end";
|
|
200
|
+
toolCallId: string;
|
|
201
|
+
ok: boolean;
|
|
202
|
+
}
|
|
203
|
+
interface CanvasCreate {
|
|
204
|
+
type: "canvas.create";
|
|
205
|
+
artifact: Artifact;
|
|
206
|
+
}
|
|
207
|
+
/** Append `text` to the string at `data.<path>` (e.g. a document body). */
|
|
208
|
+
interface CanvasAppend {
|
|
209
|
+
type: "canvas.append";
|
|
210
|
+
id: string;
|
|
211
|
+
path: string;
|
|
212
|
+
text: string;
|
|
213
|
+
}
|
|
214
|
+
/** JSON-merge-patch (RFC 7386) `patch` into the artifact's `data`. */
|
|
215
|
+
interface CanvasPatch {
|
|
216
|
+
type: "canvas.patch";
|
|
217
|
+
id: string;
|
|
218
|
+
patch: Record<string, unknown>;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Replace a single element (by its `data-cid` tree path) inside an `html`
|
|
222
|
+
* artifact with new outer HTML — an O(1) surgical edit that avoids resending the
|
|
223
|
+
* whole page. The reconciler resolves the `cid` path against the source HTML.
|
|
224
|
+
*/
|
|
225
|
+
interface CanvasNodePatch {
|
|
226
|
+
type: "canvas.node_patch";
|
|
227
|
+
id: string;
|
|
228
|
+
cid: string;
|
|
229
|
+
html: string;
|
|
230
|
+
}
|
|
231
|
+
/** Replace wholesale — the reconciler snapshots a new version. */
|
|
232
|
+
interface CanvasReplace {
|
|
233
|
+
type: "canvas.replace";
|
|
234
|
+
id: string;
|
|
235
|
+
artifact: Artifact;
|
|
236
|
+
}
|
|
237
|
+
interface CanvasStatus {
|
|
238
|
+
type: "canvas.status";
|
|
239
|
+
id: string;
|
|
240
|
+
status: ArtifactStatus;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Promote the artifact's current state to a described version snapshot.
|
|
244
|
+
* `revision` is the opaque store revision when a CanvasStore backs the canvas.
|
|
245
|
+
*/
|
|
246
|
+
interface CanvasCommit {
|
|
247
|
+
type: "canvas.commit";
|
|
248
|
+
id: string;
|
|
249
|
+
description: string;
|
|
250
|
+
revision?: string;
|
|
251
|
+
}
|
|
252
|
+
interface ErrorEvent {
|
|
253
|
+
type: "error";
|
|
254
|
+
message: string;
|
|
255
|
+
}
|
|
256
|
+
interface DoneEvent {
|
|
257
|
+
type: "done";
|
|
258
|
+
}
|
|
259
|
+
type ChatEvent = MessageDelta | MessageEnd | ToolStart | ToolEnd;
|
|
260
|
+
type CanvasEvent = CanvasCreate | CanvasAppend | CanvasPatch | CanvasNodePatch | CanvasReplace | CanvasStatus | CanvasCommit;
|
|
261
|
+
type StreamEvent = ChatEvent | CanvasEvent | ErrorEvent | DoneEvent;
|
|
262
|
+
/** Narrow a `StreamEvent` to the canvas family. */
|
|
263
|
+
declare function isCanvasEvent(event: StreamEvent): event is CanvasEvent;
|
|
264
|
+
/** Narrow a `StreamEvent` to the chat family. */
|
|
265
|
+
declare function isChatEvent(event: StreamEvent): event is ChatEvent;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Element selection — a client→server concern (it rides the chat request, not
|
|
269
|
+
* the SSE wire). When the user clicks an element inside an `html` artifact, the
|
|
270
|
+
* inspector reports which element was chosen; an edit instruction then carries
|
|
271
|
+
* this context so the agent can make a targeted change.
|
|
272
|
+
*/
|
|
273
|
+
interface ElementSelection {
|
|
274
|
+
/** The `html` artifact the element belongs to. */
|
|
275
|
+
artifactId: string;
|
|
276
|
+
/** Deterministic path id assigned by the inspector (e.g. "e-0-2"). */
|
|
277
|
+
cid: string;
|
|
278
|
+
/** Human/agent-readable selector, e.g. "button.cta". */
|
|
279
|
+
selector: string;
|
|
280
|
+
/** Lowercased tag name. */
|
|
281
|
+
tag: string;
|
|
282
|
+
/** Short text preview of the element. */
|
|
283
|
+
text?: string;
|
|
284
|
+
/** The element's current outer HTML (truncated) — edit context for the agent. */
|
|
285
|
+
outerHtml?: string;
|
|
286
|
+
/** Snapshot of the element's key computed styles (for the style panel). */
|
|
287
|
+
styles?: Record<string, string>;
|
|
288
|
+
/** True when the element is a group wrapper (offers "Ungroup"). */
|
|
289
|
+
isGroup?: boolean;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* `CanvasTransport` — the socket between the canvas UI and an agent backend.
|
|
294
|
+
*
|
|
295
|
+
* The whole contract is one promise: *given a user message, return the stream
|
|
296
|
+
* of `StreamEvent`s the canvas should apply.* Everything downstream of the
|
|
297
|
+
* socket (event batching, reconcile, rendering, error display) is
|
|
298
|
+
* transport-agnostic, so swapping how the app talks to its backend never
|
|
299
|
+
* touches a component.
|
|
300
|
+
*
|
|
301
|
+
* First-party implementations: `sseTransport` (the reference Canvas Wire
|
|
302
|
+
* Protocol over SSE — the default), `mockTransport` (scripted offline
|
|
303
|
+
* streams), and `langgraphTransport` (a LangGraph server, from the
|
|
304
|
+
* `/langgraph` entry point). An app with its own backend implements this
|
|
305
|
+
* interface instead of forking the hook.
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
/** One user turn, as handed to the transport by `useCanvasStream`. */
|
|
309
|
+
interface TransportRequest {
|
|
310
|
+
/** Conversation thread id (server-side memory / canvas scope). */
|
|
311
|
+
threadId: string;
|
|
312
|
+
/** The user's message. */
|
|
313
|
+
message: string;
|
|
314
|
+
/** Element context for a targeted edit (set when editing selected elements). */
|
|
315
|
+
selections?: ElementSelection[];
|
|
316
|
+
/** Aborted when the user hits stop or the component unmounts. */
|
|
317
|
+
signal?: AbortSignal;
|
|
318
|
+
}
|
|
319
|
+
interface CanvasTransport {
|
|
320
|
+
/** Open the stream for one user turn and yield events until it ends. */
|
|
321
|
+
stream(request: TransportRequest): AsyncIterable<StreamEvent>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { type Artifact as A, type TableColumn as B, type CanvasTransport as C, type DocumentData as D, type ElementSelection as E, type FileData as F, type ToolEnd as G, type HtmlData as H, type ToolStart as I, type TransportRequest as J, type KnownArtifact as K, isCanvasEvent as L, type MessageDelta as M, isChatEvent as N, type StreamEvent as S, type TableData as T, type CanvasEvent as a, type SlidesData as b, type ChartData as c, type ArtifactStatus as d, type CanvasAppend as e, type CanvasCommit as f, type CanvasCreate as g, type CanvasNodePatch as h, type CanvasPatch as i, type CanvasReplace as j, type CanvasStatus as k, type ChartArtifact as l, type ChartOptions as m, type ChartSeries as n, type ChatEvent as o, type DocumentArtifact as p, type DoneEvent as q, type ErrorEvent as r, type FileArtifact as s, type HtmlArtifact as t, type MessageEnd as u, type Slide as v, type SlideElement as w, type SlidePage as x, type SlidesArtifact as y, type TableArtifact as z };
|
package/package.json
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braincrew-lab/langchain-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": "Brain Crew (https://github.com/braincrew-lab)",
|
|
7
|
+
"homepage": "https://github.com/braincrew-lab/langchain-canvas#readme",
|
|
6
8
|
"type": "module",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"langchain",
|
|
11
|
+
"langgraph",
|
|
12
|
+
"ai",
|
|
13
|
+
"agent",
|
|
14
|
+
"canvas",
|
|
15
|
+
"artifacts",
|
|
16
|
+
"generative-ui",
|
|
17
|
+
"streaming",
|
|
18
|
+
"llm",
|
|
19
|
+
"react",
|
|
20
|
+
"document",
|
|
21
|
+
"chart",
|
|
22
|
+
"spreadsheet",
|
|
23
|
+
"slides"
|
|
24
|
+
],
|
|
7
25
|
"repository": {
|
|
8
26
|
"type": "git",
|
|
9
27
|
"url": "git+https://github.com/braincrew-lab/langchain-canvas.git",
|
|
10
28
|
"directory": "packages/canvas-react"
|
|
11
29
|
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/braincrew-lab/langchain-canvas/issues"
|
|
32
|
+
},
|
|
12
33
|
"sideEffects": [
|
|
13
34
|
"*.css",
|
|
14
35
|
"./dist/index.js"
|
|
@@ -25,14 +46,19 @@
|
|
|
25
46
|
"types": "./dist/index.d.ts",
|
|
26
47
|
"import": "./dist/index.js"
|
|
27
48
|
},
|
|
28
|
-
"./styles.css": "./dist/styles.css"
|
|
49
|
+
"./styles.css": "./dist/styles.css",
|
|
50
|
+
"./langgraph": {
|
|
51
|
+
"types": "./dist/langgraph/index.d.ts",
|
|
52
|
+
"import": "./dist/langgraph/index.js"
|
|
53
|
+
}
|
|
29
54
|
},
|
|
30
55
|
"publishConfig": {
|
|
31
56
|
"access": "public"
|
|
32
57
|
},
|
|
33
58
|
"peerDependencies": {
|
|
34
59
|
"react": "^18 || ^19",
|
|
35
|
-
"react-dom": "^18 || ^19"
|
|
60
|
+
"react-dom": "^18 || ^19",
|
|
61
|
+
"@langchain/langgraph-sdk": ">=0.0.70"
|
|
36
62
|
},
|
|
37
63
|
"dependencies": {
|
|
38
64
|
"@fortune-sheet/react": "^1.0.4",
|
|
@@ -52,12 +78,21 @@
|
|
|
52
78
|
"jsdom": "^25",
|
|
53
79
|
"tsup": "^8.2.4",
|
|
54
80
|
"typescript": "^5.5.4",
|
|
55
|
-
"vitest": "^2"
|
|
81
|
+
"vitest": "^2",
|
|
82
|
+
"@langchain/langgraph-sdk": "^1.9.28"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"@langchain/langgraph-sdk": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
56
88
|
},
|
|
57
89
|
"scripts": {
|
|
58
90
|
"build": "tsup",
|
|
59
91
|
"dev": "tsup --watch",
|
|
60
92
|
"typecheck": "tsc --noEmit",
|
|
61
|
-
"test": "vitest run"
|
|
93
|
+
"test": "vitest run",
|
|
94
|
+
"release:patch": "npm version patch -m \"release: %s\"",
|
|
95
|
+
"release:minor": "npm version minor -m \"release: %s\"",
|
|
96
|
+
"release:major": "npm version major -m \"release: %s\""
|
|
62
97
|
}
|
|
63
98
|
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
|
|
4
|
-
// src/components/renderers/PdfRenderer.tsx
|
|
5
|
-
function dataUrlToBlob(url) {
|
|
6
|
-
const m = /^data:[^,]*?(;base64)?,(.*)$/s.exec(url);
|
|
7
|
-
if (!m) return null;
|
|
8
|
-
try {
|
|
9
|
-
if (m[1]) {
|
|
10
|
-
const bin = atob(m[2]);
|
|
11
|
-
const bytes = new Uint8Array(bin.length);
|
|
12
|
-
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
13
|
-
return new Blob([bytes], { type: "application/pdf" });
|
|
14
|
-
}
|
|
15
|
-
return new Blob([decodeURIComponent(m[2])], { type: "application/pdf" });
|
|
16
|
-
} catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function PdfRenderer({ artifact }) {
|
|
21
|
-
const src = artifact.data.src;
|
|
22
|
-
const isData = typeof src === "string" && src.startsWith("data:");
|
|
23
|
-
const [blobUrl, setBlobUrl] = useState(null);
|
|
24
|
-
const [broken, setBroken] = useState(false);
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
setBroken(false);
|
|
27
|
-
if (!isData) {
|
|
28
|
-
setBlobUrl(null);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const blob = dataUrlToBlob(src);
|
|
32
|
-
if (!blob) {
|
|
33
|
-
setBroken(true);
|
|
34
|
-
setBlobUrl(null);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const url = URL.createObjectURL(blob);
|
|
38
|
-
setBlobUrl(url);
|
|
39
|
-
return () => URL.revokeObjectURL(url);
|
|
40
|
-
}, [src, isData]);
|
|
41
|
-
const viewUrl = isData ? blobUrl : src;
|
|
42
|
-
const filename = useMemo(
|
|
43
|
-
() => artifact.data.filename || `${artifact.title || "document"}.pdf`,
|
|
44
|
-
[artifact.data.filename, artifact.title]
|
|
45
|
-
);
|
|
46
|
-
const [zoom, setZoom] = useState("fit");
|
|
47
|
-
const zoomOut = () => setZoom((z) => Math.max(50, (z === "fit" ? 100 : z) - 25));
|
|
48
|
-
const zoomIn = () => setZoom((z) => Math.min(300, (z === "fit" ? 100 : z) + 25));
|
|
49
|
-
const frameSrc = viewUrl ? `${viewUrl}#toolbar=0&navpanes=0&zoom=${zoom === "fit" ? "page-width" : zoom}` : null;
|
|
50
|
-
const download = () => {
|
|
51
|
-
if (!viewUrl) return;
|
|
52
|
-
const a = document.createElement("a");
|
|
53
|
-
a.href = viewUrl;
|
|
54
|
-
a.download = filename;
|
|
55
|
-
a.click();
|
|
56
|
-
};
|
|
57
|
-
if (!src || broken) {
|
|
58
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-pdf cv-pdf--empty", children: "Waiting for PDF\u2026" });
|
|
59
|
-
}
|
|
60
|
-
if (!viewUrl) {
|
|
61
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-pdf cv-pdf--empty", children: "Loading PDF\u2026" });
|
|
62
|
-
}
|
|
63
|
-
return /* @__PURE__ */ jsxs("div", { className: "cv-pdf-panel", children: [
|
|
64
|
-
/* @__PURE__ */ jsxs("div", { className: "cv-pdf-tools", children: [
|
|
65
|
-
/* @__PURE__ */ jsx("span", { className: "cv-pdf-tools__name", title: filename, children: filename }),
|
|
66
|
-
/* @__PURE__ */ jsx("span", { className: "cv-pdf-tools__spacer" }),
|
|
67
|
-
/* @__PURE__ */ jsxs("span", { className: "cv-pdf-tools__zoom", children: [
|
|
68
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: zoomOut, title: "Zoom out", children: "\u2212" }),
|
|
69
|
-
/* @__PURE__ */ jsx(
|
|
70
|
-
"button",
|
|
71
|
-
{
|
|
72
|
-
type: "button",
|
|
73
|
-
className: "cv-pdf-tools__zoom-label",
|
|
74
|
-
onClick: () => setZoom("fit"),
|
|
75
|
-
title: "Fit to width",
|
|
76
|
-
children: zoom === "fit" ? "Fit" : `${zoom}%`
|
|
77
|
-
}
|
|
78
|
-
),
|
|
79
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: zoomIn, title: "Zoom in", children: "\uFF0B" })
|
|
80
|
-
] }),
|
|
81
|
-
/* @__PURE__ */ jsx("span", { className: "cv-pdf-tools__sep" }),
|
|
82
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: download, title: "Download PDF", children: "\u2913 Download" }),
|
|
83
|
-
/* @__PURE__ */ jsx(
|
|
84
|
-
"button",
|
|
85
|
-
{
|
|
86
|
-
type: "button",
|
|
87
|
-
onClick: () => window.open(viewUrl, "_blank", "noopener"),
|
|
88
|
-
title: "Open in a new tab",
|
|
89
|
-
children: "\u2197 Open"
|
|
90
|
-
}
|
|
91
|
-
)
|
|
92
|
-
] }),
|
|
93
|
-
/* @__PURE__ */ jsx("div", { className: "cv-pdf", children: /* @__PURE__ */ jsx("iframe", { className: "cv-pdf__frame", src: frameSrc, title: filename }, frameSrc) })
|
|
94
|
-
] });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export { PdfRenderer };
|