@braincrew-lab/langchain-canvas 0.1.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.
- package/LICENSE +21 -0
- package/README.ko.md +170 -0
- package/README.md +170 -0
- package/dist/ChartRenderer-CKCJ2LQX.js +182 -0
- package/dist/DocumentRenderer-7HP3YQUK.js +40 -0
- package/dist/SlidesRenderer-KFSPD63I.js +399 -0
- package/dist/TableRenderer-TRYSMQA7.js +250 -0
- package/dist/chunk-6L3AL6W4.js +24 -0
- package/dist/chunk-HHJWIO2C.js +12 -0
- package/dist/chunk-PSIT32I5.js +242 -0
- package/dist/chunk-YZZSJJMQ.js +14 -0
- package/dist/index.d.ts +669 -0
- package/dist/index.js +2570 -0
- package/dist/styles.css +1066 -0
- package/package.json +63 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
|
+
import { StoreApi } from 'zustand/vanilla';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Artifact data shapes — mirror of `langchain_canvas/protocol/artifacts.py`.
|
|
7
|
+
*
|
|
8
|
+
* An `Artifact` is transport-agnostic: `{ id, type, title, version, status,
|
|
9
|
+
* data }`. `type` is a registry key resolved to a React component; `data` is the
|
|
10
|
+
* type-specific payload that component reads. Keep this file in lockstep with
|
|
11
|
+
* the Python module — a field here must exist there, and vice versa.
|
|
12
|
+
*/
|
|
13
|
+
type ArtifactStatus = "streaming" | "complete" | "error";
|
|
14
|
+
interface Artifact<TData = unknown> {
|
|
15
|
+
/** Stable identity — the reconciliation key. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Registry key: "document" | "chart" | ... */
|
|
18
|
+
type: string;
|
|
19
|
+
/** Shown in the canvas header / tab. */
|
|
20
|
+
title: string;
|
|
21
|
+
/** 1-based; bumped on every `canvas.replace`. */
|
|
22
|
+
version: number;
|
|
23
|
+
status: ArtifactStatus;
|
|
24
|
+
data: TData;
|
|
25
|
+
meta?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The base substrate: raw HTML, rendered in a sandboxed iframe. Everything a
|
|
29
|
+
* canvas can show is ultimately HTML; `document` / `chart` / `table` are
|
|
30
|
+
* structured conveniences the SDK renders for you, while `html` lets an agent
|
|
31
|
+
* emit an arbitrary self-contained page (the Claude-Artifacts / Genspark model).
|
|
32
|
+
*/
|
|
33
|
+
interface HtmlData {
|
|
34
|
+
html: string;
|
|
35
|
+
}
|
|
36
|
+
interface DocumentData {
|
|
37
|
+
format: "markdown";
|
|
38
|
+
content: string;
|
|
39
|
+
}
|
|
40
|
+
interface ChartSeries {
|
|
41
|
+
/** Column in `ChartData.rows` to plot. */
|
|
42
|
+
key: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
color?: string;
|
|
45
|
+
}
|
|
46
|
+
interface ChartOptions {
|
|
47
|
+
stacked?: boolean;
|
|
48
|
+
yLabel?: string;
|
|
49
|
+
/** Per-slice colors for pie charts, index-aligned to `rows`. */
|
|
50
|
+
colors?: string[];
|
|
51
|
+
}
|
|
52
|
+
interface ChartData {
|
|
53
|
+
chart: "line" | "bar" | "area" | "pie";
|
|
54
|
+
/** Tidy/long-form rows, consumed directly by the charting library. */
|
|
55
|
+
rows: Array<Record<string, string | number>>;
|
|
56
|
+
/** Category / x-axis field. */
|
|
57
|
+
xKey: string;
|
|
58
|
+
series: ChartSeries[];
|
|
59
|
+
options?: ChartOptions;
|
|
60
|
+
/**
|
|
61
|
+
* Optional raw ECharts `option`. When present it's rendered verbatim — an
|
|
62
|
+
* escape hatch for agents/apps that already produce a full ECharts config
|
|
63
|
+
* (the tidy `rows`/`series` model is ignored, and inline editing is disabled).
|
|
64
|
+
*/
|
|
65
|
+
echartsOption?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
interface TableColumn {
|
|
68
|
+
key: string;
|
|
69
|
+
label?: string;
|
|
70
|
+
align?: "left" | "right" | "center";
|
|
71
|
+
}
|
|
72
|
+
interface TableData {
|
|
73
|
+
columns: TableColumn[];
|
|
74
|
+
rows: Array<Record<string, string | number>>;
|
|
75
|
+
/**
|
|
76
|
+
* Opaque spreadsheet state (Fortune-sheet sheets) once the user has edited the
|
|
77
|
+
* grid — carries merges, per-cell fonts/formats, and formulas that the simple
|
|
78
|
+
* columns/rows shape can't hold. Present after the first interactive edit;
|
|
79
|
+
* exporters prefer it over columns/rows.
|
|
80
|
+
*/
|
|
81
|
+
sheet?: Array<Record<string, unknown>>;
|
|
82
|
+
}
|
|
83
|
+
/** A freely-positioned element on a "blank" slide (percent geometry, 0–100). */
|
|
84
|
+
interface SlideElement {
|
|
85
|
+
id: string;
|
|
86
|
+
type: "text" | "image";
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
w: number;
|
|
90
|
+
h: number;
|
|
91
|
+
text?: string;
|
|
92
|
+
src?: string;
|
|
93
|
+
fontSize?: number;
|
|
94
|
+
bold?: boolean;
|
|
95
|
+
color?: string;
|
|
96
|
+
align?: "left" | "center" | "right";
|
|
97
|
+
}
|
|
98
|
+
interface Slide {
|
|
99
|
+
/** title · content (bullets) · section · image · two-column · blank (free canvas). */
|
|
100
|
+
layout?: "title" | "content" | "section" | "image" | "two-column" | "blank";
|
|
101
|
+
/** Freely-positioned elements for the "blank" layout. */
|
|
102
|
+
elements?: SlideElement[];
|
|
103
|
+
title?: string;
|
|
104
|
+
subtitle?: string;
|
|
105
|
+
bullets?: string[];
|
|
106
|
+
/** Right-hand bullets for the "two-column" layout. */
|
|
107
|
+
bullets2?: string[];
|
|
108
|
+
/** Image (data: URL or https URL) for the "image" layout. */
|
|
109
|
+
image?: string;
|
|
110
|
+
/** Slide background color (hex). */
|
|
111
|
+
background?: string;
|
|
112
|
+
/** Slide text color (hex). */
|
|
113
|
+
textColor?: string;
|
|
114
|
+
/** Speaker notes (not shown on the slide; exported to the .pptx notes pane). */
|
|
115
|
+
notes?: string;
|
|
116
|
+
}
|
|
117
|
+
interface SlidesData {
|
|
118
|
+
slides: Slide[];
|
|
119
|
+
}
|
|
120
|
+
type HtmlArtifact = Artifact<HtmlData> & {
|
|
121
|
+
type: "html";
|
|
122
|
+
};
|
|
123
|
+
type DocumentArtifact = Artifact<DocumentData> & {
|
|
124
|
+
type: "document";
|
|
125
|
+
};
|
|
126
|
+
type ChartArtifact = Artifact<ChartData> & {
|
|
127
|
+
type: "chart";
|
|
128
|
+
};
|
|
129
|
+
type TableArtifact = Artifact<TableData> & {
|
|
130
|
+
type: "table";
|
|
131
|
+
};
|
|
132
|
+
type SlidesArtifact = Artifact<SlidesData> & {
|
|
133
|
+
type: "slides";
|
|
134
|
+
};
|
|
135
|
+
type KnownArtifact = HtmlArtifact | DocumentArtifact | ChartArtifact | TableArtifact | SlidesArtifact;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Canvas Wire Protocol v1 — event envelopes. Mirror of
|
|
139
|
+
* `langchain_canvas/protocol/events.py`. Every SSE frame is one `StreamEvent`,
|
|
140
|
+
* discriminated by `type`. See `docs/02-protocol.md` for the specification.
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
interface MessageDelta {
|
|
144
|
+
type: "message.delta";
|
|
145
|
+
messageId: string;
|
|
146
|
+
text: string;
|
|
147
|
+
}
|
|
148
|
+
interface MessageEnd {
|
|
149
|
+
type: "message.end";
|
|
150
|
+
messageId: string;
|
|
151
|
+
}
|
|
152
|
+
interface ToolStart {
|
|
153
|
+
type: "tool.start";
|
|
154
|
+
toolCallId: string;
|
|
155
|
+
name: string;
|
|
156
|
+
}
|
|
157
|
+
interface ToolEnd {
|
|
158
|
+
type: "tool.end";
|
|
159
|
+
toolCallId: string;
|
|
160
|
+
ok: boolean;
|
|
161
|
+
}
|
|
162
|
+
interface CanvasCreate {
|
|
163
|
+
type: "canvas.create";
|
|
164
|
+
artifact: Artifact;
|
|
165
|
+
}
|
|
166
|
+
/** Append `text` to the string at `data.<path>` (e.g. a document body). */
|
|
167
|
+
interface CanvasAppend {
|
|
168
|
+
type: "canvas.append";
|
|
169
|
+
id: string;
|
|
170
|
+
path: string;
|
|
171
|
+
text: string;
|
|
172
|
+
}
|
|
173
|
+
/** JSON-merge-patch (RFC 7386) `patch` into the artifact's `data`. */
|
|
174
|
+
interface CanvasPatch {
|
|
175
|
+
type: "canvas.patch";
|
|
176
|
+
id: string;
|
|
177
|
+
patch: Record<string, unknown>;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Replace a single element (by its `data-cid` tree path) inside an `html`
|
|
181
|
+
* artifact with new outer HTML — an O(1) surgical edit that avoids resending the
|
|
182
|
+
* whole page. The reconciler resolves the `cid` path against the source HTML.
|
|
183
|
+
*/
|
|
184
|
+
interface CanvasNodePatch {
|
|
185
|
+
type: "canvas.node_patch";
|
|
186
|
+
id: string;
|
|
187
|
+
cid: string;
|
|
188
|
+
html: string;
|
|
189
|
+
}
|
|
190
|
+
/** Replace wholesale — the reconciler snapshots a new version. */
|
|
191
|
+
interface CanvasReplace {
|
|
192
|
+
type: "canvas.replace";
|
|
193
|
+
id: string;
|
|
194
|
+
artifact: Artifact;
|
|
195
|
+
}
|
|
196
|
+
interface CanvasStatus {
|
|
197
|
+
type: "canvas.status";
|
|
198
|
+
id: string;
|
|
199
|
+
status: ArtifactStatus;
|
|
200
|
+
}
|
|
201
|
+
interface CanvasClose {
|
|
202
|
+
type: "canvas.close";
|
|
203
|
+
id: string;
|
|
204
|
+
}
|
|
205
|
+
interface ErrorEvent {
|
|
206
|
+
type: "error";
|
|
207
|
+
message: string;
|
|
208
|
+
}
|
|
209
|
+
interface DoneEvent {
|
|
210
|
+
type: "done";
|
|
211
|
+
}
|
|
212
|
+
type ChatEvent = MessageDelta | MessageEnd | ToolStart | ToolEnd;
|
|
213
|
+
type CanvasEvent = CanvasCreate | CanvasAppend | CanvasPatch | CanvasNodePatch | CanvasReplace | CanvasStatus | CanvasClose;
|
|
214
|
+
type StreamEvent = ChatEvent | CanvasEvent | ErrorEvent | DoneEvent;
|
|
215
|
+
/** Narrow a `StreamEvent` to the canvas family. */
|
|
216
|
+
declare function isCanvasEvent(event: StreamEvent): event is CanvasEvent;
|
|
217
|
+
/** Narrow a `StreamEvent` to the chat family. */
|
|
218
|
+
declare function isChatEvent(event: StreamEvent): event is ChatEvent;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Element selection — a client→server concern (it rides the chat request, not
|
|
222
|
+
* the SSE wire). When the user clicks an element inside an `html` artifact, the
|
|
223
|
+
* inspector reports which element was chosen; an edit instruction then carries
|
|
224
|
+
* this context so the agent can make a targeted change.
|
|
225
|
+
*/
|
|
226
|
+
interface ElementSelection {
|
|
227
|
+
/** The `html` artifact the element belongs to. */
|
|
228
|
+
artifactId: string;
|
|
229
|
+
/** Deterministic path id assigned by the inspector (e.g. "e-0-2"). */
|
|
230
|
+
cid: string;
|
|
231
|
+
/** Human/agent-readable selector, e.g. "button.cta". */
|
|
232
|
+
selector: string;
|
|
233
|
+
/** Lowercased tag name. */
|
|
234
|
+
tag: string;
|
|
235
|
+
/** Short text preview of the element. */
|
|
236
|
+
text?: string;
|
|
237
|
+
/** The element's current outer HTML (truncated) — edit context for the agent. */
|
|
238
|
+
outerHtml?: string;
|
|
239
|
+
/** Snapshot of the element's key computed styles (for the style panel). */
|
|
240
|
+
styles?: Record<string, string>;
|
|
241
|
+
/** True when the element is a group wrapper (offers "Ungroup"). */
|
|
242
|
+
isGroup?: boolean;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The reconciler — the single place artifact state is mutated.
|
|
247
|
+
*
|
|
248
|
+
* `reduceCanvas(state, event)` is a pure function: given the current canvas
|
|
249
|
+
* state and one canvas event, it returns the next state. The store, hooks, and
|
|
250
|
+
* components never branch on event `type` — they read the reconciled artifacts.
|
|
251
|
+
* That keeps streaming (`append`), partial updates (`patch`), and versioning
|
|
252
|
+
* (`replace`) auditable in one reducer.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
interface CanvasState {
|
|
256
|
+
/** Current (latest-version) artifact per id. */
|
|
257
|
+
artifacts: Record<string, Artifact>;
|
|
258
|
+
/** Version snapshots per id, oldest first. Grows on `canvas.replace`. */
|
|
259
|
+
history: Record<string, Artifact[]>;
|
|
260
|
+
/** Creation order — drives tab ordering in the panel. */
|
|
261
|
+
order: string[];
|
|
262
|
+
/** The artifact the panel currently focuses. */
|
|
263
|
+
activeId: string | null;
|
|
264
|
+
}
|
|
265
|
+
declare function emptyCanvasState(): CanvasState;
|
|
266
|
+
declare function reduceCanvas(state: CanvasState, event: CanvasEvent): CanvasState;
|
|
267
|
+
/**
|
|
268
|
+
* RFC 7386 JSON Merge Patch: `null` deletes a key, plain objects merge
|
|
269
|
+
* recursively, everything else (arrays, scalars) replaces.
|
|
270
|
+
*/
|
|
271
|
+
declare function mergePatch(target: unknown, patch: unknown): unknown;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* SSE client — POST a chat message and yield parsed `StreamEvent`s.
|
|
275
|
+
*
|
|
276
|
+
* We use `fetch` + `ReadableStream` rather than the browser `EventSource`
|
|
277
|
+
* because the chat endpoint is a POST (EventSource is GET-only) and we want an
|
|
278
|
+
* `AbortSignal` for cancellation. Frames are separated by a blank line; each
|
|
279
|
+
* `data:` payload is one JSON `StreamEvent`.
|
|
280
|
+
*/
|
|
281
|
+
|
|
282
|
+
interface ChatRequest {
|
|
283
|
+
threadId: string;
|
|
284
|
+
message: string;
|
|
285
|
+
/** Element context for a targeted edit (set when editing selected elements). */
|
|
286
|
+
selections?: ElementSelection[];
|
|
287
|
+
}
|
|
288
|
+
interface StreamOptions {
|
|
289
|
+
signal?: AbortSignal;
|
|
290
|
+
headers?: Record<string, string>;
|
|
291
|
+
}
|
|
292
|
+
/** Open the chat stream and yield events until the server sends `done`. */
|
|
293
|
+
declare function streamChat(endpoint: string, request: ChatRequest, options?: StreamOptions): AsyncGenerator<StreamEvent>;
|
|
294
|
+
/** Turn a byte stream of SSE frames into a stream of typed events. */
|
|
295
|
+
declare function parseSSE(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<StreamEvent>;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The iframe inspector — makes an `html` artifact directly editable.
|
|
299
|
+
*
|
|
300
|
+
* `withInspector(html)` injects a small, self-contained script + style into the
|
|
301
|
+
* page rendered inside the sandboxed iframe. That script:
|
|
302
|
+
*
|
|
303
|
+
* 1. stamps every element with a deterministic `data-cid` (a tree path like
|
|
304
|
+
* `e-0-2`), so a selection survives re-renders and the agent can target it;
|
|
305
|
+
* 2. outlines the element under the cursor on hover;
|
|
306
|
+
* 3. on click, marks it selected and `postMessage`s a selection payload —
|
|
307
|
+
* including a snapshot of its key computed styles — to the parent window;
|
|
308
|
+
* 4. on double-click, makes a text element `contenteditable`; on blur it posts
|
|
309
|
+
* the edited element back as `node_edit` (the host commits it as a
|
|
310
|
+
* `canvas.node_patch`);
|
|
311
|
+
* 5. responds to parent commands: `set_style` (apply a style live), `commit`
|
|
312
|
+
* (post the element's current HTML back as `node_edit`), and `clear`.
|
|
313
|
+
*
|
|
314
|
+
* The script runs inside a `sandbox="allow-scripts"` iframe with a null origin,
|
|
315
|
+
* so it cannot reach the parent DOM, cookies, or storage — only `postMessage`.
|
|
316
|
+
*/
|
|
317
|
+
declare const INSPECTOR_MARK = "langchain-canvas";
|
|
318
|
+
/** Inject the inspector into an HTML string, before `</body>` when present. The
|
|
319
|
+
* injected nodes are tagged `data-lcx` so a full-document save can strip them.
|
|
320
|
+
* Also ensures a responsive viewport meta so device-width media queries behave
|
|
321
|
+
* the same in the preview, in export, and on a real device. */
|
|
322
|
+
declare function withInspector(html: string): string;
|
|
323
|
+
/** Computed-style properties surfaced to the style panel (camelCase). */
|
|
324
|
+
declare const STYLE_PROPS: readonly ["color", "backgroundColor", "fontSize", "fontWeight", "textAlign", "lineHeight", "letterSpacing", "padding", "borderRadius", "width"];
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Schema-driven playback — render the canvas from a scripted list of wire
|
|
328
|
+
* events, with no backend, no LLM, and no API key.
|
|
329
|
+
*
|
|
330
|
+
* The canvas is defined entirely by the wire protocol (`StreamEvent`s). That
|
|
331
|
+
* means you can develop and demo the UI purely against the schema: hand it a
|
|
332
|
+
* fixture and watch it render exactly as a real LangGraph agent would drive it.
|
|
333
|
+
* `mockStream` turns an event array into a timed async stream, shaped identically
|
|
334
|
+
* to `streamChat`, so anything that consumes one consumes the other.
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
interface MockStreamOptions {
|
|
338
|
+
/** Delay between events, ms. Simulates streaming cadence. Default 120. */
|
|
339
|
+
delayMs?: number;
|
|
340
|
+
signal?: AbortSignal;
|
|
341
|
+
}
|
|
342
|
+
/** Yield a fixed list of events over time — a drop-in for `streamChat`. */
|
|
343
|
+
declare function mockStream(events: StreamEvent[], options?: MockStreamOptions): AsyncGenerator<StreamEvent>;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The canvas store — chat transcript + reconciled canvas state in one place.
|
|
347
|
+
*
|
|
348
|
+
* This is a *factory*, not a singleton: `createCanvasStore()` returns an
|
|
349
|
+
* isolated store so an app can host several independent canvas/chat instances.
|
|
350
|
+
* `<CanvasProvider>` (see `context.tsx`) wires one up; provider-less apps share a
|
|
351
|
+
* lazily-created default store, keeping the simple API working.
|
|
352
|
+
*
|
|
353
|
+
* Every wire event flows through `applyEvent` → the pure `reduceCanvas` reducer
|
|
354
|
+
* for canvas events, folded into `messages` for chat events, so streaming,
|
|
355
|
+
* patching, and versioning stay in one auditable place.
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
interface ChatMessage {
|
|
359
|
+
id: string;
|
|
360
|
+
role: "user" | "assistant";
|
|
361
|
+
text: string;
|
|
362
|
+
/** Artifact ids this assistant message produced — drives inline artifact cards. */
|
|
363
|
+
artifactIds?: string[];
|
|
364
|
+
}
|
|
365
|
+
/** A command the editing UI forwards to the active html artifact's iframe. */
|
|
366
|
+
interface IframeCommand {
|
|
367
|
+
artifactId: string;
|
|
368
|
+
/** style · structure (duplicate/delete/move/insert/insert_html) · group/ungroup · set_src · clear. */
|
|
369
|
+
type: "set_style" | "commit" | "clear" | "set_src" | "duplicate" | "delete" | "move_up" | "move_down" | "insert" | "insert_html" | "group" | "ungroup";
|
|
370
|
+
/** Target element (omitted for document-level inserts with no selection). */
|
|
371
|
+
cid?: string;
|
|
372
|
+
/** Members to wrap for `group`. */
|
|
373
|
+
cids?: string[];
|
|
374
|
+
prop?: string;
|
|
375
|
+
value?: string;
|
|
376
|
+
/** Tag/block to insert for `insert` (e.g. "h2", "p", "button", "img", "hr", "section"). */
|
|
377
|
+
block?: string;
|
|
378
|
+
/** HTML fragment to insert for `insert_html` (a built-in section template). */
|
|
379
|
+
html?: string;
|
|
380
|
+
/** Monotonic counter so re-issuing an identical command still fires. */
|
|
381
|
+
seq: number;
|
|
382
|
+
}
|
|
383
|
+
interface CanvasStore {
|
|
384
|
+
canvas: CanvasState;
|
|
385
|
+
messages: ChatMessage[];
|
|
386
|
+
isStreaming: boolean;
|
|
387
|
+
error: string | null;
|
|
388
|
+
/** Elements the user selected inside an `html` artifact (click = 1, marquee = N). */
|
|
389
|
+
selections: ElementSelection[];
|
|
390
|
+
/** Last command forwarded to the html iframe (style panel → renderer bus). */
|
|
391
|
+
iframeCommand: IframeCommand | null;
|
|
392
|
+
/** Snapshots for undo/redo — only user edits are recorded (not agent streaming). */
|
|
393
|
+
undoStack: CanvasState[];
|
|
394
|
+
redoStack: CanvasState[];
|
|
395
|
+
applyEvent: (event: StreamEvent) => void;
|
|
396
|
+
/** Apply a batch of events in a single store write (one re-render per frame). */
|
|
397
|
+
applyEvents: (events: StreamEvent[]) => void;
|
|
398
|
+
/** Apply a *user*-initiated event, recording a snapshot so it can be undone. */
|
|
399
|
+
applyUserEvent: (event: StreamEvent) => void;
|
|
400
|
+
undo: () => void;
|
|
401
|
+
redo: () => void;
|
|
402
|
+
addUserMessage: (text: string) => void;
|
|
403
|
+
setStreaming: (value: boolean) => void;
|
|
404
|
+
setActiveArtifact: (id: string) => void;
|
|
405
|
+
setSelections: (selections: ElementSelection[]) => void;
|
|
406
|
+
sendIframeCommand: (command: Omit<IframeCommand, "seq">) => void;
|
|
407
|
+
reset: () => void;
|
|
408
|
+
}
|
|
409
|
+
/** Create an isolated canvas store. */
|
|
410
|
+
declare function createCanvasStore(): StoreApi<CanvasStore>;
|
|
411
|
+
|
|
412
|
+
interface CanvasProviderProps {
|
|
413
|
+
children: ReactNode;
|
|
414
|
+
/** Bring your own store (e.g. shared across trees); one is created otherwise. */
|
|
415
|
+
store?: StoreApi<CanvasStore>;
|
|
416
|
+
}
|
|
417
|
+
declare function CanvasProvider({ children, store }: CanvasProviderProps): react.JSX.Element;
|
|
418
|
+
/** The raw store API for the nearest provider (or the default store). */
|
|
419
|
+
declare function useCanvasStoreApi(): StoreApi<CanvasStore>;
|
|
420
|
+
/** Subscribe to a slice of the canvas store. */
|
|
421
|
+
declare function useCanvasStore<T>(selector: (state: CanvasStore) => T): T;
|
|
422
|
+
|
|
423
|
+
interface UseCanvasStreamOptions {
|
|
424
|
+
/** Chat SSE endpoint. Defaults to `/api/chat`. */
|
|
425
|
+
endpoint?: string;
|
|
426
|
+
/** Conversation thread id (for server-side memory). Defaults to a fresh uuid. */
|
|
427
|
+
threadId?: string;
|
|
428
|
+
/**
|
|
429
|
+
* Offline mock: given the user's message, return a scripted `StreamEvent[]`
|
|
430
|
+
* to play instead of hitting the network — an OpenAPI-style "try it" with no
|
|
431
|
+
* real LLM call. Return `null` to fall through to the live endpoint.
|
|
432
|
+
*/
|
|
433
|
+
mock?: (message: string) => StreamEvent[] | null;
|
|
434
|
+
}
|
|
435
|
+
declare function useCanvasStream(options?: UseCanvasStreamOptions): {
|
|
436
|
+
sendMessage: (text: string, withSelections?: ElementSelection[]) => Promise<void>;
|
|
437
|
+
stop: () => void | undefined;
|
|
438
|
+
reset: () => void;
|
|
439
|
+
setActiveArtifact: (id: string) => void;
|
|
440
|
+
selections: ElementSelection[];
|
|
441
|
+
editSelection: (instruction: string) => void;
|
|
442
|
+
clearSelection: () => void;
|
|
443
|
+
messages: ChatMessage[];
|
|
444
|
+
canvas: CanvasState;
|
|
445
|
+
isStreaming: boolean;
|
|
446
|
+
error: string | null;
|
|
447
|
+
threadId: string;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
declare function useCanvasReplay(): {
|
|
451
|
+
play: (events: StreamEvent[], options?: MockStreamOptions) => Promise<void>;
|
|
452
|
+
stop: () => void | undefined;
|
|
453
|
+
reset: () => void;
|
|
454
|
+
canvas: CanvasState;
|
|
455
|
+
isPlaying: boolean;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* `useArtifactPatch` — let a renderer commit an inline edit back to the store.
|
|
460
|
+
*
|
|
461
|
+
* Editing an artifact on the canvas is just a client-side `canvas.patch`: it
|
|
462
|
+
* flows through the exact same reconciler an agent's edits do, so the edited
|
|
463
|
+
* value is the one that renders, versions, and exports. Renderers stay in sync
|
|
464
|
+
* with the single source of truth instead of holding a private copy.
|
|
465
|
+
*/
|
|
466
|
+
declare function useArtifactPatch(id: string): (patch: Record<string, unknown>) => void;
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Schema fixtures — scripted wire-event sequences that render the canvas with no
|
|
470
|
+
* backend. Feed one to `useCanvasReplay().play(scenario.events)`.
|
|
471
|
+
*
|
|
472
|
+
* Each scenario is nothing but `StreamEvent`s: exactly what a LangGraph agent
|
|
473
|
+
* would emit over the wire. They double as living documentation of the protocol
|
|
474
|
+
* and as a zero-dependency way to develop renderers.
|
|
475
|
+
*/
|
|
476
|
+
|
|
477
|
+
interface Scenario {
|
|
478
|
+
id: string;
|
|
479
|
+
title: string;
|
|
480
|
+
description: string;
|
|
481
|
+
events: StreamEvent[];
|
|
482
|
+
}
|
|
483
|
+
declare const scenarios: Scenario[];
|
|
484
|
+
|
|
485
|
+
interface RendererProps<TData = unknown> {
|
|
486
|
+
artifact: Artifact<TData>;
|
|
487
|
+
}
|
|
488
|
+
type ArtifactRenderer = ComponentType<RendererProps<any>>;
|
|
489
|
+
type ArtifactRegistry = Record<string, ArtifactRenderer>;
|
|
490
|
+
interface CanvasRegistryProviderProps {
|
|
491
|
+
registry: ArtifactRegistry;
|
|
492
|
+
children: ReactNode;
|
|
493
|
+
}
|
|
494
|
+
declare function CanvasRegistryProvider({ registry, children }: CanvasRegistryProviderProps): react.JSX.Element;
|
|
495
|
+
/** Resolve the renderer for an artifact type, or `undefined` if unregistered. */
|
|
496
|
+
declare function useRenderer(type: string): ArtifactRenderer | undefined;
|
|
497
|
+
/** Merge registries — later entries win. Handy for extending the built-ins. */
|
|
498
|
+
declare function mergeRegistries(...registries: ArtifactRegistry[]): ArtifactRegistry;
|
|
499
|
+
|
|
500
|
+
interface CanvasProps {
|
|
501
|
+
/** Renderer map. Defaults to the built-in html/document/chart/table renderers. */
|
|
502
|
+
registry?: ArtifactRegistry;
|
|
503
|
+
/** Rendered when no artifact has been opened yet. */
|
|
504
|
+
emptyState?: ReactNode;
|
|
505
|
+
/**
|
|
506
|
+
* Handle a targeted edit of the selected element (from `useCanvasStream`'s
|
|
507
|
+
* `editSelection`). When provided, clicking an element in an `html` artifact
|
|
508
|
+
* reveals a quick-edit bar.
|
|
509
|
+
*/
|
|
510
|
+
onEditElement?: (instruction: string) => void;
|
|
511
|
+
}
|
|
512
|
+
declare function Canvas({ registry, emptyState, onEditElement }: CanvasProps): react.JSX.Element;
|
|
513
|
+
|
|
514
|
+
interface ExportMenuProps {
|
|
515
|
+
artifact: Artifact;
|
|
516
|
+
getRenderedHtml: () => string | null;
|
|
517
|
+
}
|
|
518
|
+
declare function ExportMenu({ artifact, getRenderedHtml }: ExportMenuProps): react.JSX.Element;
|
|
519
|
+
|
|
520
|
+
interface SelectionBarProps {
|
|
521
|
+
selections: ElementSelection[];
|
|
522
|
+
onEdit: (instruction: string) => void;
|
|
523
|
+
onClear: () => void;
|
|
524
|
+
}
|
|
525
|
+
declare function SelectionBar({ selections, onEdit, onClear }: SelectionBarProps): react.JSX.Element;
|
|
526
|
+
|
|
527
|
+
declare function StylePanel({ selection }: {
|
|
528
|
+
selection: ElementSelection;
|
|
529
|
+
}): react.JSX.Element;
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* `<ArtifactCard>` — an inline reference to an artifact, for the chat transcript.
|
|
533
|
+
*
|
|
534
|
+
* Assistant messages carry the ids of the artifacts they produced
|
|
535
|
+
* (`message.artifactIds`); render a card per id under the bubble so the artifact
|
|
536
|
+
* shows up *in the conversation* (like ChatGPT), and clicking it focuses that
|
|
537
|
+
* artifact in the `<Canvas />` panel.
|
|
538
|
+
*/
|
|
539
|
+
declare function ArtifactCard({ artifactId }: {
|
|
540
|
+
artifactId: string;
|
|
541
|
+
}): react.JSX.Element | null;
|
|
542
|
+
|
|
543
|
+
declare function SlidesRenderer$1({ artifact }: RendererProps<SlidesData>): react.JSX.Element;
|
|
544
|
+
|
|
545
|
+
declare function TableRenderer$1({ artifact }: RendererProps<TableData>): react.JSX.Element;
|
|
546
|
+
|
|
547
|
+
declare function DocumentRenderer$1({ artifact }: RendererProps<DocumentData>): react.JSX.Element;
|
|
548
|
+
|
|
549
|
+
declare function ChartRenderer$1({ artifact }: RendererProps<ChartData>): react.JSX.Element;
|
|
550
|
+
|
|
551
|
+
declare function HtmlRenderer({ artifact }: RendererProps<HtmlData>): react.JSX.Element;
|
|
552
|
+
|
|
553
|
+
declare const ChartRenderer: react.LazyExoticComponent<typeof ChartRenderer$1>;
|
|
554
|
+
declare const DocumentRenderer: react.LazyExoticComponent<typeof DocumentRenderer$1>;
|
|
555
|
+
declare const TableRenderer: react.LazyExoticComponent<typeof TableRenderer$1>;
|
|
556
|
+
declare const SlidesRenderer: react.LazyExoticComponent<typeof SlidesRenderer$1>;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* The batteries-included renderers. `html` is the base substrate (sandboxed
|
|
560
|
+
* iframe); the rest are structured conveniences, lazily loaded. Pass to
|
|
561
|
+
* `<Canvas registry />` or merge with your own. They render under `<Canvas>`'s
|
|
562
|
+
* Suspense boundary, so the on-demand chunks resolve transparently.
|
|
563
|
+
*/
|
|
564
|
+
declare const builtinRenderers: ArtifactRegistry;
|
|
565
|
+
|
|
566
|
+
/** Browser download helpers — trigger a file save from an in-memory string. */
|
|
567
|
+
/** Save `content` as a file named `filename` with the given MIME type. */
|
|
568
|
+
declare function downloadBlob(filename: string, mime: string, content: BlobPart): void;
|
|
569
|
+
/** Turn a title into a safe file stem: "Q1 Report!" -> "q1-report". */
|
|
570
|
+
declare function slugify(text: string): string;
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Artifact → file exporters.
|
|
574
|
+
*
|
|
575
|
+
* Two kinds of export:
|
|
576
|
+
*
|
|
577
|
+
* 1. **HTML** (`toStandaloneHtml`) — wrap the *rendered* DOM of any artifact into
|
|
578
|
+
* a self-contained `.html` document with inlined styles.
|
|
579
|
+
* 2. **Data exporters** (`dataExporters`) — deterministic, per-type conversions
|
|
580
|
+
* straight from `artifact.data`: markdown `.md`, table `.csv`/`.xlsx`,
|
|
581
|
+
* document `.docx`, slides `.pptx`, raw `.json`.
|
|
582
|
+
*
|
|
583
|
+
* Office formats (xlsx / docx / pptx) are produced with `exceljs` / `docx` /
|
|
584
|
+
* `pptxgenjs`, loaded via **dynamic import** so they never touch the main
|
|
585
|
+
* bundle — only the code path a user actually clicks pulls them in.
|
|
586
|
+
*/
|
|
587
|
+
|
|
588
|
+
interface FileExport {
|
|
589
|
+
/** Menu label, e.g. "Excel". */
|
|
590
|
+
label: string;
|
|
591
|
+
/** File extension without the dot, e.g. "xlsx". */
|
|
592
|
+
extension: string;
|
|
593
|
+
mime: string;
|
|
594
|
+
/** Build the file contents (text or binary; may be async for Office formats). */
|
|
595
|
+
build: (artifact: Artifact) => BlobPart | Promise<BlobPart>;
|
|
596
|
+
}
|
|
597
|
+
/** Per-type data exporters, keyed by `artifact.type`. */
|
|
598
|
+
declare const dataExporters: Record<string, FileExport[]>;
|
|
599
|
+
/** Wrap already-rendered inner HTML into a standalone, styled `.html` document. */
|
|
600
|
+
declare function toStandaloneHtml(title: string, renderedHtml: string): string;
|
|
601
|
+
/**
|
|
602
|
+
* A print-ready HTML document with one landscape page per slide — fed to the
|
|
603
|
+
* browser's print pipeline to produce a multi-page PDF. Elements keep their
|
|
604
|
+
* percentage geometry, so pages match the on-canvas layout exactly.
|
|
605
|
+
*/
|
|
606
|
+
declare function slidesToPrintHtml(data: SlidesData, title: string): string;
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* PDF export via the browser's own print pipeline — zero dependencies, and the
|
|
610
|
+
* output is pixel-faithful to what the canvas renders (the browser is the best
|
|
611
|
+
* HTML→PDF engine there is).
|
|
612
|
+
*
|
|
613
|
+
* We write the standalone HTML into an offscreen iframe and trigger `print()`
|
|
614
|
+
* on it; the user picks "Save as PDF" in the print dialog. Using an iframe (not
|
|
615
|
+
* a popup) avoids popup blockers and never navigates the host page away.
|
|
616
|
+
*/
|
|
617
|
+
/**
|
|
618
|
+
* Print an HTML document to PDF through an offscreen iframe.
|
|
619
|
+
*
|
|
620
|
+
* Security: the export HTML may be untrusted (LLM-generated or imported), so the
|
|
621
|
+
* frame is **sandboxed without `allow-scripts`** — scripts, `onerror`, `onload`,
|
|
622
|
+
* etc. never execute, so nothing can run in the host origin. `allow-same-origin`
|
|
623
|
+
* (safe here precisely because scripts are disabled) lets us call `print()` on
|
|
624
|
+
* the frame; `allow-modals` permits the print dialog. `srcdoc` is used instead of
|
|
625
|
+
* `document.write` so the content is parsed inertly.
|
|
626
|
+
*/
|
|
627
|
+
declare function printToPdf(html: string): void;
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* File → artifact importers — the inverse of `export/exporters.ts`.
|
|
631
|
+
*
|
|
632
|
+
* `langchain-canvas` isn't only a viewer for agent output: you can open a real
|
|
633
|
+
* file and edit it on the canvas, then export it back out (round-trip). Each
|
|
634
|
+
* importer maps a file to a `canvas.create` + `canvas.status: complete` event
|
|
635
|
+
* pair, so an opened file flows through the exact same reconciler path an
|
|
636
|
+
* agent's stream would — no special-casing downstream.
|
|
637
|
+
*
|
|
638
|
+
* Dependency policy mirrors the exporters: text formats (csv / md / html / json)
|
|
639
|
+
* are parsed inline with zero dependencies; `.xlsx` reuses `exceljs`, loaded via
|
|
640
|
+
* dynamic import so it never touches the main bundle.
|
|
641
|
+
*/
|
|
642
|
+
|
|
643
|
+
/** Extensions we can turn into an artifact, for `accept="…"` and drop filtering. */
|
|
644
|
+
declare const IMPORTABLE_EXTENSIONS: readonly [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx"];
|
|
645
|
+
/** True when the file has an extension we know how to import. */
|
|
646
|
+
declare const canImport: (file: File) => boolean;
|
|
647
|
+
/**
|
|
648
|
+
* Parse a file into canvas events. Rejects if the extension is unsupported so
|
|
649
|
+
* callers can surface a clear message. Text parsing is synchronous; `.xlsx`
|
|
650
|
+
* awaits its dynamic import.
|
|
651
|
+
*/
|
|
652
|
+
declare function importFile(file: File): Promise<StreamEvent[]>;
|
|
653
|
+
/** RFC-4180-ish CSV parser: handles quoted fields, embedded commas, and "" escapes. */
|
|
654
|
+
declare function parseCsv(text: string): TableData;
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* `useCanvasImport` — open local files onto the canvas.
|
|
658
|
+
*
|
|
659
|
+
* Turns a `File` (from a file picker or a drag-and-drop) into canvas events and
|
|
660
|
+
* applies them through the store, so the imported document/sheet/page becomes a
|
|
661
|
+
* first-class artifact you can edit and re-export. Returns the id of the last
|
|
662
|
+
* artifact created so callers can focus it.
|
|
663
|
+
*/
|
|
664
|
+
declare function useCanvasImport(): {
|
|
665
|
+
importFiles: (files: Iterable<File>) => Promise<string | null>;
|
|
666
|
+
canImport: (file: File) => boolean;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
export { type Artifact, ArtifactCard, type ArtifactRegistry, type ArtifactRenderer, type ArtifactStatus, Canvas, type CanvasAppend, type CanvasClose, type CanvasCreate, type CanvasEvent, type CanvasNodePatch, type CanvasPatch, type CanvasProps, CanvasProvider, type CanvasProviderProps, CanvasRegistryProvider, type CanvasReplace, type CanvasState, type CanvasStatus, type CanvasStore, type ChartArtifact, type ChartData, type ChartOptions, ChartRenderer, type ChartSeries, type ChatEvent, type ChatMessage, type ChatRequest, type DocumentArtifact, type DocumentData, DocumentRenderer, type DoneEvent, type ElementSelection, type ErrorEvent, ExportMenu, type FileExport, type HtmlArtifact, type HtmlData, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, type IframeCommand, type KnownArtifact, type MessageDelta, type MessageEnd, type MockStreamOptions, type RendererProps, STYLE_PROPS, type Scenario, SelectionBar, type Slide, type SlideElement, type SlidesArtifact, type SlidesData, SlidesRenderer, type StreamEvent, type StreamOptions, StylePanel, type TableArtifact, type TableColumn, type TableData, TableRenderer, type ToolEnd, type ToolStart, type UseCanvasStreamOptions, builtinRenderers, canImport, createCanvasStore, dataExporters, downloadBlob, emptyCanvasState, importFile, isCanvasEvent, isChatEvent, mergePatch, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, reduceCanvas, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useArtifactPatch, useCanvasImport, useCanvasReplay, useCanvasStore, useCanvasStoreApi, useCanvasStream, useRenderer, withInspector };
|