@braincrew-lab/langchain-canvas 0.3.0 → 0.5.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/README.ko.md +11 -23
- package/README.md +11 -23
- package/dist/{ChartRenderer-ABRQ5YFN.js → ChartRenderer-JGRJ23OB.js} +39 -67
- package/dist/{DocumentRenderer-YTEMC3Z4.js → DocumentRenderer-ZQDQMX7X.js} +18 -19
- package/dist/FileRenderer-3ZHNORZJ.js +39 -0
- package/dist/SlidesRenderer-4TZNTSEL.js +514 -0
- package/dist/TableRenderer-3ESF577F.js +275 -0
- package/dist/chunk-7T5DRR3F.js +109 -0
- package/dist/{chunk-ZLXAWRUP.js → chunk-EHW446VF.js} +49 -50
- package/dist/chunk-FTNRRJ3K.js +13 -0
- package/dist/chunk-IFNRLN4Y.js +137 -0
- package/dist/{chunk-FSFOURG5.js → chunk-K2UZAYW2.js} +1 -1
- package/dist/{chunk-6L3AL6W4.js → chunk-SE6AP3A7.js} +21 -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 +166 -1002
- package/dist/index.js +361 -2526
- package/dist/langgraph/index.d.ts +68 -0
- package/dist/langgraph/index.js +111 -0
- package/dist/styles.css +59 -409
- package/dist/types-BfGP9R2I.d.ts +324 -0
- package/package.json +15 -4
- package/dist/PdfRenderer-GDA67MES.js +0 -98
- package/dist/SlidesRenderer-JNPXNTNJ.js +0 -964
- package/dist/TableRenderer-BJQE2HMO.js +0 -636
- package/dist/chunk-QMOJEGRH.js +0 -207
package/README.ko.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# @braincrew-lab/langchain-canvas
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/braincrew-lab/langchain-canvas/blob/main/LICENSE)
|
|
3
|
+
LangChain 챗 앱을 위한 캔버스. 에이전트가 아티팩트(웹 페이지·스프레드시트·슬라이드·차트·문서)를 스트리밍하면 대화 옆에 실시간으로 뜨고 그 자리에서 편집됩니다. 사용자가 직접 손보고 실제 파일로 내보낼 수 있습니다.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ChatGPT Canvas나 Claude Artifacts 같은 경험을, **내 React 앱에 넣고 내 에이전트에 연결하는 패키지**로 제공합니다. 스크린샷·아키텍처·와이어 프로토콜 전체는 [GitHub 레포](https://github.com/braincrew-lab/langchain-canvas)에 있습니다.
|
|
5
|
+
ChatGPT Canvas나 Claude Artifacts 같은 경험을, **내 React 앱에 넣고 내 에이전트에 연결하는 패키지**로 제공합니다.
|
|
9
6
|
|
|
10
7
|
> [English README](./README.md)
|
|
11
8
|
|
|
@@ -77,29 +74,26 @@ HTML이 기본 substrate이고, 샌드박스 iframe에 렌더됩니다. 에이
|
|
|
77
74
|
|
|
78
75
|
정적 표가 아니라 스프레드시트 엔진(Fortune-sheet) 위에서 동작합니다.
|
|
79
76
|
|
|
80
|
-
- **실시간 수식** — `=SUM(C2:C4)`, `=
|
|
77
|
+
- **실시간 수식** — `=SUM(C2:C4)`, `=AVERAGE(...)`, `=A2*B2` 입력 시 계산. 셀참조·범위·함수 자동완성 지원
|
|
81
78
|
- **데이터로 온 수식** — 에이전트가 값으로 보낸 수식(`"=AVERAGE(B2:B4)"`)은 **로드 시 미리 계산**되어 결과가 바로 표시
|
|
82
|
-
-
|
|
83
|
-
- **서식** — 폰트, 숫자/통화/%/날짜 서식, 굵게/기울임, 테두리, 셀 병합/해제, 정렬, **틀 고정**, 다중 시트, 그리고 에이전트가 만든 요란한 셀 색을 한 번에 걷어내는 **스타일 정리**
|
|
79
|
+
- **전체 툴바** — 폰트, 숫자/통화/% 서식, 굵게/기울임, 테두리, 셀 병합, 정렬, 다중 시트 — 데스크톱 수준
|
|
84
80
|
- 넓은 그리드에서 **상하좌우 부드러운 스크롤**
|
|
85
|
-
- **Export** — `.xlsx`(폰트/병합/서식 포함) 또는 `.csv`
|
|
81
|
+
- **Export** — `.xlsx`(폰트/병합/서식 포함) 또는 `.csv`
|
|
86
82
|
|
|
87
83
|
### 🖼️ 슬라이드 (`slides`) — 프리 캔버스 덱
|
|
88
84
|
|
|
89
85
|
모든 요소가 움직이는 PowerPoint 스타일 편집기.
|
|
90
86
|
|
|
91
|
-
- **자유 배치** — 텍스트/이미지 요소 드래그·리사이즈,
|
|
92
|
-
- **다중 선택 & 그룹** — Shift+클릭 또는 마퀴 선택 후 그룹/해제(⌘G / ⌘⇧G). 떠 있는 액션 바로 정렬·분배·복제·삭제
|
|
93
|
-
- **키보드 편집** — 방향키 미세 이동, ⌘D 복제, ⌘C/⌘V(슬라이드 간 가능), ⌘] / ⌘[ z-순서
|
|
87
|
+
- **자유 배치** — 텍스트/이미지 요소 드래그·리사이즈, 가이드 스냅
|
|
94
88
|
- **인라인 편집** — 더블클릭 텍스트 편집, 굵게/크기/색/정렬 툴바
|
|
95
89
|
- **구조** — 슬라이드 추가/복제/삭제/순서, 썸네일 레일, 발표자 노트
|
|
96
|
-
-
|
|
97
|
-
- **Export** — `.pptx
|
|
90
|
+
- **테마·배경**, 발표 모드(전체화면, 방향키 이동)
|
|
91
|
+
- **Export** — `.pptx`, **Figma 복사**(에디터블 프레임으로 바로 붙여넣기), **PDF**(전 슬라이드)
|
|
98
92
|
|
|
99
93
|
### 📝 문서 (`document`) — Markdown / Word
|
|
100
94
|
|
|
101
95
|
- **클릭 편집** — 페이지를 Markdown으로 편집(GFM 렌더)
|
|
102
|
-
- **Export** — `.docx`, `.md`, `.
|
|
96
|
+
- **Export** — `.docx`, `.md`, `.pdf`, `.html`
|
|
103
97
|
|
|
104
98
|
### 📈 차트 (`chart`)
|
|
105
99
|
|
|
@@ -108,15 +102,10 @@ HTML이 기본 substrate이고, 샌드박스 iframe에 렌더됩니다. 에이
|
|
|
108
102
|
- 시리즈(또는 파이 조각)별 **색상 변경**, 시리즈 이름, Y축 라벨, 스택 토글
|
|
109
103
|
- **Export** — `.pdf`(차트가 SVG라 선명) 또는 raw JSON
|
|
110
104
|
|
|
111
|
-
### 📄 PDF (`pdf`)
|
|
112
|
-
|
|
113
|
-
- 에이전트가 `{ type: "pdf", data: { src } }`(`data:`/`blob:`/https URL)를 emit 하면 **브라우저 내장 뷰어**로 렌더 — 의존성 0, 줌 컨트롤 포함
|
|
114
|
-
- `data:` 소스는 `application/pdf`로 고정되어, 조작된 아티팩트가 스크립트 실행 가능한 same-origin 프레임을 끼워 넣을 수 없음
|
|
115
|
-
|
|
116
105
|
### 📁 파일 — 라운드트립
|
|
117
106
|
|
|
118
|
-
- **Import** — 드래그앤드롭 또는 파일 선택: **CSV · Excel
|
|
119
|
-
- **Export** — 각 아티팩트를 네이티브
|
|
107
|
+
- **Import** — 드래그앤드롭 또는 파일 선택: **CSV · Excel · Markdown · HTML · JSON** → 편집 가능한 아티팩트로
|
|
108
|
+
- **Export** — 각 아티팩트를 네이티브 포맷으로, 그리고 범용 **standalone `.html`** · **PDF**(브라우저 인쇄)
|
|
120
109
|
|
|
121
110
|
### 🧰 모든 아티팩트 공통
|
|
122
111
|
|
|
@@ -132,7 +121,6 @@ HTML이 기본 substrate이고, 샌드박스 iframe에 렌더됩니다. 에이
|
|
|
132
121
|
- **peerDependency:** React 18 또는 19 — 직접 제공. ESM 전용.
|
|
133
122
|
- **스타일:** `import "@braincrew-lab/langchain-canvas/styles.css"` 한 번.
|
|
134
123
|
- **인스턴스 격리:** `<CanvasProvider>`가 서브트리마다 독립 store 제공.
|
|
135
|
-
- **한국어 UI:** `<Canvas locale="ko" />` 하나로 UI 전체가 한국어/영어 전환.
|
|
136
124
|
- **커스텀 렌더러:** `registry`로 타입 렌더 방식 추가/오버라이드.
|
|
137
125
|
|
|
138
126
|
```tsx
|
package/README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# @braincrew-lab/langchain-canvas
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/braincrew-lab/langchain-canvas/blob/main/LICENSE)
|
|
3
|
+
A canvas for your LangChain chat app. Your agent streams an artifact — a web page, a spreadsheet, a slide deck, a chart, a document — and it shows up next to the conversation, live and editable. Users can tweak it by hand and export it to a real file.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Think ChatGPT Canvas or Claude Artifacts, but a package you drop into your own React app and point at your own agent. Screenshots, architecture, and the full wire protocol live in the [GitHub repo](https://github.com/braincrew-lab/langchain-canvas).
|
|
5
|
+
Think ChatGPT Canvas or Claude Artifacts, but a package you drop into your own React app and point at your own agent.
|
|
9
6
|
|
|
10
7
|
> [한국어 README](./README.ko.md)
|
|
11
8
|
|
|
@@ -77,29 +74,26 @@ HTML is the base substrate, rendered in a sandboxed iframe. Selecting and editin
|
|
|
77
74
|
|
|
78
75
|
Runs on a spreadsheet engine (Fortune-sheet), not a static table.
|
|
79
76
|
|
|
80
|
-
- **Live formulas** — type `=SUM(C2:C4)`, `=
|
|
77
|
+
- **Live formulas** — type `=SUM(C2:C4)`, `=AVERAGE(...)`, `=A2*B2`; they calculate, with cell references, ranges, and function autocomplete.
|
|
81
78
|
- **Formulas from data** — a formula the agent sends as a value (e.g. `"=AVERAGE(B2:B4)"`) is **pre-computed on load** so it shows its result immediately.
|
|
82
|
-
- **
|
|
83
|
-
- **Formatting** — fonts, number/currency/percent/date formats, bold/italic, borders, cell merge/unmerge, alignment, **freeze panes**, multiple sheets, and a one-click **clean styling** that strips garish agent-generated fills.
|
|
79
|
+
- **Full toolbar** — fonts, number/currency/percent formats, bold/italic, borders, cell merging, alignment, multiple sheets — like a desktop spreadsheet.
|
|
84
80
|
- **Smooth scrolling** in both directions over a large grid.
|
|
85
|
-
- **Export** to `.xlsx` (with fonts/merges/formats) or `.csv
|
|
81
|
+
- **Export** to `.xlsx` (with fonts/merges/formats) or `.csv`.
|
|
86
82
|
|
|
87
83
|
### 🖼️ Slides (`slides`) — a free-canvas deck
|
|
88
84
|
|
|
89
85
|
A PowerPoint-style editor where every element is movable.
|
|
90
86
|
|
|
91
|
-
- **Free positioning** — drag and resize text/image elements; snap
|
|
92
|
-
- **Multi-select & groups** — Shift+click or marquee-select, then group/ungroup (⌘G / ⌘⇧G); a floating bar offers align, distribute, duplicate, delete.
|
|
93
|
-
- **Keyboard editing** — arrow-key nudge, ⌘D duplicate, ⌘C/⌘V across slides, ⌘] / ⌘[ z-order.
|
|
87
|
+
- **Free positioning** — drag and resize text/image elements; snap to guides.
|
|
94
88
|
- **Inline editing** — double-click to edit text; format toolbar for bold/size/color/align.
|
|
95
89
|
- **Structure** — add/duplicate/delete/reorder slides, thumbnails rail, speaker notes.
|
|
96
|
-
- **
|
|
97
|
-
- **Export** to `.pptx
|
|
90
|
+
- **Themes & backgrounds**, present mode (full-screen, arrow-key navigation).
|
|
91
|
+
- **Export** to `.pptx`, or copy to **Figma** (paste straight in as editable frames), or **PDF** (all slides).
|
|
98
92
|
|
|
99
93
|
### 📝 Documents (`document`) — Markdown / Word
|
|
100
94
|
|
|
101
95
|
- **Click-to-edit** the page as Markdown, rendered with GFM.
|
|
102
|
-
- **Export** to `.docx`, `.md`, `.
|
|
96
|
+
- **Export** to `.docx`, `.md`, `.pdf`, or `.html`.
|
|
103
97
|
|
|
104
98
|
### 📈 Charts (`chart`)
|
|
105
99
|
|
|
@@ -108,15 +102,10 @@ A PowerPoint-style editor where every element is movable.
|
|
|
108
102
|
- **Recolor** each series (or each pie slice), rename series, set the y-axis label, toggle stacking.
|
|
109
103
|
- **Export** to `.pdf` (the chart is SVG, so it prints crisply) or the raw JSON.
|
|
110
104
|
|
|
111
|
-
### 📄 PDFs (`pdf`)
|
|
112
|
-
|
|
113
|
-
- The agent emits `{ type: "pdf", data: { src } }` (a `data:`/`blob:`/https URL) and the file renders in the **browser's built-in viewer** — zero dependencies, with a zoom control.
|
|
114
|
-
- `data:` sources are pinned to `application/pdf`, so a crafted artifact can't smuggle a scriptable same-origin frame.
|
|
115
|
-
|
|
116
105
|
### 📁 Files — round-trip
|
|
117
106
|
|
|
118
|
-
- **Import** by drag-and-drop or a file picker: **CSV · Excel
|
|
119
|
-
- **Export** every artifact to its native format
|
|
107
|
+
- **Import** by drag-and-drop or a file picker: **CSV · Excel · Markdown · HTML · JSON**. They open as editable artifacts.
|
|
108
|
+
- **Export** every artifact to its native format, plus a universal **standalone `.html`** and **PDF** (browser print).
|
|
120
109
|
|
|
121
110
|
### 🧰 Across every artifact
|
|
122
111
|
|
|
@@ -132,7 +121,6 @@ A PowerPoint-style editor where every element is movable.
|
|
|
132
121
|
- **Peer dependency:** React 18 or 19 — you bring your own. ESM only.
|
|
133
122
|
- **Styles:** `import "@braincrew-lab/langchain-canvas/styles.css"` once.
|
|
134
123
|
- **Isolated instances:** `<CanvasProvider>` gives each subtree its own store.
|
|
135
|
-
- **Korean UI:** `<Canvas locale="ko" />` localizes the entire chrome (English / Korean).
|
|
136
124
|
- **Bring your own renderer:** pass `registry` to add or override how a type renders.
|
|
137
125
|
|
|
138
126
|
```tsx
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { useArtifactPatch } from './chunk-
|
|
2
|
-
import './chunk-
|
|
3
|
-
import { useT } from './chunk-QMOJEGRH.js';
|
|
1
|
+
import { useArtifactPatch } from './chunk-K2UZAYW2.js';
|
|
2
|
+
import './chunk-EHW446VF.js';
|
|
4
3
|
import { useState, useRef, useMemo, useEffect } from 'react';
|
|
5
4
|
import * as echarts from 'echarts/core';
|
|
6
|
-
import { BarChart, LineChart, PieChart
|
|
5
|
+
import { BarChart, LineChart, PieChart } from 'echarts/charts';
|
|
7
6
|
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
|
|
8
7
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
9
8
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
10
9
|
|
|
11
|
-
echarts.use([BarChart, LineChart, PieChart,
|
|
10
|
+
echarts.use([BarChart, LineChart, PieChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);
|
|
12
11
|
var PALETTE = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#0ea5e9", "#a855f7"];
|
|
13
12
|
var seriesColor = (series, index) => series.color ?? PALETTE[index % PALETTE.length];
|
|
14
13
|
var sliceColor = (options, index) => options?.colors?.[index] ?? PALETTE[index % PALETTE.length];
|
|
15
|
-
var CHART_TYPES = ["bar", "line", "area", "
|
|
14
|
+
var CHART_TYPES = ["bar", "line", "area", "pie"];
|
|
16
15
|
function EChart({ option, height, onInst }) {
|
|
17
16
|
const boxRef = useRef(null);
|
|
18
17
|
const instRef = useRef(null);
|
|
@@ -36,7 +35,6 @@ function EChart({ option, height, onInst }) {
|
|
|
36
35
|
return /* @__PURE__ */ jsx("div", { ref: boxRef, className: "cv-chart__canvas", style: { width: "100%", height } });
|
|
37
36
|
}
|
|
38
37
|
function ChartRenderer({ artifact }) {
|
|
39
|
-
const t = useT();
|
|
40
38
|
const { chart, rows, xKey, series, options, echartsOption } = artifact.data;
|
|
41
39
|
const patch = useArtifactPatch(artifact.id);
|
|
42
40
|
const [editing, setEditing] = useState(false);
|
|
@@ -58,15 +56,15 @@ function ChartRenderer({ artifact }) {
|
|
|
58
56
|
return /* @__PURE__ */ jsxs("div", { className: "cv-chart", children: [
|
|
59
57
|
/* @__PURE__ */ jsxs("div", { className: "cv-chart__toolbar cv-chrome", children: [
|
|
60
58
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
|
|
61
|
-
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title:
|
|
59
|
+
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "Download as PNG", children: "\u2913 PNG" })
|
|
62
60
|
] }),
|
|
63
61
|
/* @__PURE__ */ jsx(EChart, { option, height: 320, onInst: (i) => instRef.current = i })
|
|
64
62
|
] });
|
|
65
63
|
}
|
|
66
64
|
if (rows.length === 0) {
|
|
67
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-chart cv-chart--empty", children:
|
|
65
|
+
return /* @__PURE__ */ jsx("div", { className: "cv-chart cv-chart--empty", children: "Waiting for data\u2026" });
|
|
68
66
|
}
|
|
69
|
-
const isPie = chart === "pie"
|
|
67
|
+
const isPie = chart === "pie";
|
|
70
68
|
const setSeries = (i, partial) => patch({ series: series.map((s, j) => j === i ? { ...s, ...partial } : s) });
|
|
71
69
|
const setSliceColor = (i, color) => {
|
|
72
70
|
const colors = rows.map((_, j) => options?.colors?.[j] ?? PALETTE[j % PALETTE.length]);
|
|
@@ -82,38 +80,38 @@ function ChartRenderer({ artifact }) {
|
|
|
82
80
|
const removeRow = (i) => patch({ rows: rows.filter((_, j) => j !== i) });
|
|
83
81
|
return /* @__PURE__ */ jsxs("div", { className: "cv-chart", children: [
|
|
84
82
|
/* @__PURE__ */ jsxs("div", { className: "cv-chart__toolbar cv-chrome", children: [
|
|
85
|
-
/* @__PURE__ */ jsx("div", { className: "cv-chart__types", children: CHART_TYPES.map((
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "cv-chart__types", children: CHART_TYPES.map((t) => /* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${chart === t ? "is-primary" : ""}`, onClick: () => patch({ chart: t }), children: t }, t)) }),
|
|
86
84
|
(chart === "bar" || chart === "area") && /* @__PURE__ */ jsxs("label", { className: "cv-chart__stack", children: [
|
|
87
85
|
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: !!options?.stacked, onChange: (e) => patch({ options: { ...options, stacked: e.target.checked } }) }),
|
|
88
|
-
|
|
86
|
+
"Stacked"
|
|
89
87
|
] }),
|
|
90
88
|
/* @__PURE__ */ jsx(
|
|
91
89
|
"input",
|
|
92
90
|
{
|
|
93
91
|
className: "cv-chart__title-input",
|
|
94
92
|
value: options?.title ?? "",
|
|
95
|
-
placeholder:
|
|
93
|
+
placeholder: "Chart title\u2026",
|
|
96
94
|
onChange: (e) => patch({ options: { ...options, title: e.target.value || void 0 } }),
|
|
97
|
-
title:
|
|
95
|
+
title: "Chart title"
|
|
98
96
|
}
|
|
99
97
|
),
|
|
100
98
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
|
|
101
|
-
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title:
|
|
102
|
-
/* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ?
|
|
99
|
+
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "Download as PNG", children: "\u2913 PNG" }),
|
|
100
|
+
/* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ? "Done" : "Edit data" })
|
|
103
101
|
] }),
|
|
104
102
|
editing && /* @__PURE__ */ jsxs("div", { className: "cv-chart__editor cv-chrome", children: [
|
|
105
103
|
/* @__PURE__ */ jsx("div", { className: "cv-chart__legend", children: isPie ? rows.map((r, i) => /* @__PURE__ */ jsxs("span", { className: "cv-chart__swatch", children: [
|
|
106
104
|
/* @__PURE__ */ jsx("input", { type: "color", value: sliceColor(options, i), onChange: (e) => setSliceColor(i, e.target.value), title: `Color: ${r[xKey]}` }),
|
|
107
105
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__swatch-label", children: String(r[xKey]) })
|
|
108
106
|
] }, i)) : series.map((s, i) => /* @__PURE__ */ jsxs("span", { className: "cv-chart__swatch", children: [
|
|
109
|
-
/* @__PURE__ */ jsx("input", { type: "color", value: seriesColor(s, i), onChange: (e) => setSeries(i, { color: e.target.value }), title:
|
|
107
|
+
/* @__PURE__ */ jsx("input", { type: "color", value: seriesColor(s, i), onChange: (e) => setSeries(i, { color: e.target.value }), title: "Series color" }),
|
|
110
108
|
/* @__PURE__ */ jsx(
|
|
111
109
|
"input",
|
|
112
110
|
{
|
|
113
111
|
className: "cv-chart__series-name",
|
|
114
112
|
value: s.label ?? s.key,
|
|
115
113
|
onChange: (e) => setSeries(i, { label: e.target.value }),
|
|
116
|
-
title:
|
|
114
|
+
title: "Series name"
|
|
117
115
|
}
|
|
118
116
|
)
|
|
119
117
|
] }, s.key)) }),
|
|
@@ -126,23 +124,6 @@ function ChartRenderer({ artifact }) {
|
|
|
126
124
|
/* @__PURE__ */ jsx(EChart, { option, height: editing ? 260 : 340, onInst: (i) => instRef.current = i })
|
|
127
125
|
] });
|
|
128
126
|
}
|
|
129
|
-
function NumberCell({ value, onCommit }) {
|
|
130
|
-
const [draft, setDraft] = useState(null);
|
|
131
|
-
const shown = draft ?? String(Number(value ?? 0));
|
|
132
|
-
return /* @__PURE__ */ jsx(
|
|
133
|
-
"input",
|
|
134
|
-
{
|
|
135
|
-
type: "number",
|
|
136
|
-
value: shown,
|
|
137
|
-
onChange: (e) => {
|
|
138
|
-
setDraft(e.target.value);
|
|
139
|
-
const n = Number(e.target.value);
|
|
140
|
-
if (e.target.value !== "" && Number.isFinite(n)) onCommit(n);
|
|
141
|
-
},
|
|
142
|
-
onBlur: () => setDraft(null)
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
127
|
function DataGrid({
|
|
147
128
|
rows,
|
|
148
129
|
xKey,
|
|
@@ -151,7 +132,6 @@ function DataGrid({
|
|
|
151
132
|
onAddRow,
|
|
152
133
|
onRemoveRow
|
|
153
134
|
}) {
|
|
154
|
-
const t = useT();
|
|
155
135
|
return /* @__PURE__ */ jsxs("div", { className: "cv-chart__grid", children: [
|
|
156
136
|
/* @__PURE__ */ jsxs("table", { children: [
|
|
157
137
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
@@ -161,18 +141,25 @@ function DataGrid({
|
|
|
161
141
|
] }) }),
|
|
162
142
|
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
163
143
|
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("input", { value: String(row[xKey] ?? ""), onChange: (e) => onCell(i, xKey, e.target.value) }) }),
|
|
164
|
-
series.map((s) => /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
|
|
165
|
-
|
|
144
|
+
series.map((s) => /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
|
|
145
|
+
"input",
|
|
146
|
+
{
|
|
147
|
+
type: "number",
|
|
148
|
+
value: Number(row[s.key] ?? 0),
|
|
149
|
+
onChange: (e) => onCell(i, s.key, e.target.value === "" ? 0 : Number(e.target.value))
|
|
150
|
+
}
|
|
151
|
+
) }, s.key)),
|
|
152
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("button", { className: "cv-chart__row-del", onClick: () => onRemoveRow(i), disabled: rows.length <= 1, title: "Remove row", children: "\xD7" }) })
|
|
166
153
|
] }, i)) })
|
|
167
154
|
] }),
|
|
168
|
-
/* @__PURE__ */ jsx("button", { className: "cv-chart__addrow", onClick: onAddRow, children:
|
|
155
|
+
/* @__PURE__ */ jsx("button", { className: "cv-chart__addrow", onClick: onAddRow, children: "+ Add row" })
|
|
169
156
|
] });
|
|
170
157
|
}
|
|
171
158
|
function toEChartsOption(chart, rows, xKey, series, options) {
|
|
172
159
|
const AXIS = "#9aa4b2";
|
|
173
160
|
const titleBlock = options?.title ? { title: { text: options.title, left: "center", top: 2, textStyle: { color: AXIS, fontSize: 15, fontWeight: 600 } } } : {};
|
|
174
161
|
const hasTitle = !!options?.title;
|
|
175
|
-
if (chart === "pie"
|
|
162
|
+
if (chart === "pie") {
|
|
176
163
|
const valueKey = series[0]?.key ?? "value";
|
|
177
164
|
return {
|
|
178
165
|
...titleBlock,
|
|
@@ -181,7 +168,7 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
181
168
|
series: [
|
|
182
169
|
{
|
|
183
170
|
type: "pie",
|
|
184
|
-
radius:
|
|
171
|
+
radius: ["45%", "72%"],
|
|
185
172
|
padAngle: 2,
|
|
186
173
|
itemStyle: { borderRadius: 4 },
|
|
187
174
|
label: { color: AXIS },
|
|
@@ -194,20 +181,13 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
194
181
|
]
|
|
195
182
|
};
|
|
196
183
|
}
|
|
197
|
-
const isNumeric = (v) => v !== void 0 && v !== "" && Number.isFinite(Number(v));
|
|
198
|
-
const scatterOnValueAxis = chart === "scatter" && rows.length > 0 && rows.every((r) => isNumeric(r[xKey]));
|
|
199
184
|
const stacked = (chart === "bar" || chart === "area") && !!options?.stacked;
|
|
200
185
|
return {
|
|
201
186
|
...titleBlock,
|
|
202
|
-
tooltip: { trigger:
|
|
187
|
+
tooltip: { trigger: "axis" },
|
|
203
188
|
legend: { bottom: 0, textStyle: { color: AXIS } },
|
|
204
189
|
grid: { left: 8, right: 16, top: hasTitle ? 44 : 24, bottom: 40, containLabel: true },
|
|
205
|
-
xAxis:
|
|
206
|
-
type: "value",
|
|
207
|
-
axisLabel: { color: AXIS, fontSize: 12 },
|
|
208
|
-
axisLine: { lineStyle: { color: "#d4d7dd" } },
|
|
209
|
-
splitLine: { lineStyle: { color: "#e5e7eb", type: "dashed" } }
|
|
210
|
-
} : {
|
|
190
|
+
xAxis: {
|
|
211
191
|
type: "category",
|
|
212
192
|
data: rows.map((r) => String(r[xKey] ?? "")),
|
|
213
193
|
axisLabel: { color: AXIS, fontSize: 12 },
|
|
@@ -220,23 +200,15 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
220
200
|
axisLabel: { color: AXIS, fontSize: 12 },
|
|
221
201
|
splitLine: { lineStyle: { color: "#e5e7eb", type: "dashed" } }
|
|
222
202
|
},
|
|
223
|
-
series: series.map(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
type: chart === "area" ? "line" : chart,
|
|
233
|
-
...chart === "area" ? { areaStyle: { opacity: 0.2 }, smooth: false } : {},
|
|
234
|
-
...chart !== "bar" ? { showSymbol: false, lineStyle: { width: 2 } } : {},
|
|
235
|
-
stack: stacked ? "total" : void 0,
|
|
236
|
-
itemStyle: { color: seriesColor(s, i) },
|
|
237
|
-
data: rows.map((r) => Number(r[s.key] ?? 0))
|
|
238
|
-
}
|
|
239
|
-
)
|
|
203
|
+
series: series.map((s, i) => ({
|
|
204
|
+
name: s.label ?? s.key,
|
|
205
|
+
type: chart === "area" ? "line" : chart,
|
|
206
|
+
...chart === "area" ? { areaStyle: { opacity: 0.2 }, smooth: false } : {},
|
|
207
|
+
...chart !== "bar" ? { showSymbol: false, lineStyle: { width: 2 } } : {},
|
|
208
|
+
stack: stacked ? "total" : void 0,
|
|
209
|
+
itemStyle: { color: seriesColor(s, i) },
|
|
210
|
+
data: rows.map((r) => Number(r[s.key] ?? 0))
|
|
211
|
+
}))
|
|
240
212
|
};
|
|
241
213
|
}
|
|
242
214
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import './chunk-
|
|
3
|
-
import {
|
|
1
|
+
import { useAssetUrl } from './chunk-FTNRRJ3K.js';
|
|
2
|
+
import './chunk-7T5DRR3F.js';
|
|
3
|
+
import { useArtifactPatch } from './chunk-K2UZAYW2.js';
|
|
4
|
+
import './chunk-EHW446VF.js';
|
|
4
5
|
import { useState, useRef } from 'react';
|
|
5
|
-
import ReactMarkdown from 'react-markdown';
|
|
6
|
+
import ReactMarkdown, { defaultUrlTransform } from 'react-markdown';
|
|
6
7
|
import remarkGfm from 'remark-gfm';
|
|
7
8
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
8
9
|
|
|
@@ -11,12 +12,13 @@ function wordStats(text) {
|
|
|
11
12
|
return { words, minutes: Math.max(1, Math.round(words / 200)) };
|
|
12
13
|
}
|
|
13
14
|
function DocumentRenderer({ artifact }) {
|
|
14
|
-
const t = useT();
|
|
15
15
|
const { content } = artifact.data;
|
|
16
16
|
const patch = useArtifactPatch(artifact.id);
|
|
17
17
|
const [draft, setDraft] = useState(null);
|
|
18
18
|
const taRef = useRef(null);
|
|
19
19
|
const editing = draft !== null;
|
|
20
|
+
const assetUrl = useAssetUrl();
|
|
21
|
+
const urlTransform = (url) => defaultUrlTransform(assetUrl(url) ?? url);
|
|
20
22
|
const commit = () => {
|
|
21
23
|
if (draft !== null && draft !== content) patch({ content: draft });
|
|
22
24
|
setDraft(null);
|
|
@@ -48,15 +50,15 @@ function DocumentRenderer({ artifact }) {
|
|
|
48
50
|
const { words, minutes } = wordStats(editing ? draft ?? "" : content);
|
|
49
51
|
return /* @__PURE__ */ jsxs("div", { className: "cv-word", children: [
|
|
50
52
|
editing && /* @__PURE__ */ jsxs("div", { className: "cv-doc-bar cv-chrome", onMouseDown: (e) => e.preventDefault(), children: [
|
|
51
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
53
|
+
/* @__PURE__ */ jsx("button", { title: "Bold", onMouseDown: (e) => {
|
|
52
54
|
e.preventDefault();
|
|
53
55
|
wrap("**");
|
|
54
56
|
}, children: /* @__PURE__ */ jsx("b", { children: "B" }) }),
|
|
55
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
57
|
+
/* @__PURE__ */ jsx("button", { title: "Italic", onMouseDown: (e) => {
|
|
56
58
|
e.preventDefault();
|
|
57
59
|
wrap("*");
|
|
58
60
|
}, children: /* @__PURE__ */ jsx("i", { children: "I" }) }),
|
|
59
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
61
|
+
/* @__PURE__ */ jsx("button", { title: "Inline code", onMouseDown: (e) => {
|
|
60
62
|
e.preventDefault();
|
|
61
63
|
wrap("`");
|
|
62
64
|
}, children: "<>" }),
|
|
@@ -69,36 +71,33 @@ function DocumentRenderer({ artifact }) {
|
|
|
69
71
|
e.preventDefault();
|
|
70
72
|
prefixLines("## ");
|
|
71
73
|
}, children: "H2" }),
|
|
72
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
74
|
+
/* @__PURE__ */ jsx("button", { title: "Bullet list", onMouseDown: (e) => {
|
|
73
75
|
e.preventDefault();
|
|
74
76
|
prefixLines("- ");
|
|
75
77
|
}, children: "\u2022" }),
|
|
76
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
78
|
+
/* @__PURE__ */ jsx("button", { title: "Numbered list", onMouseDown: (e) => {
|
|
77
79
|
e.preventDefault();
|
|
78
80
|
prefixLines("1. ");
|
|
79
81
|
}, children: "1." }),
|
|
80
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
82
|
+
/* @__PURE__ */ jsx("button", { title: "Quote", onMouseDown: (e) => {
|
|
81
83
|
e.preventDefault();
|
|
82
84
|
prefixLines("> ");
|
|
83
85
|
}, children: "\u275D" }),
|
|
84
|
-
/* @__PURE__ */ jsx("button", { title:
|
|
86
|
+
/* @__PURE__ */ jsx("button", { title: "Link", onMouseDown: (e) => {
|
|
85
87
|
e.preventDefault();
|
|
86
88
|
link();
|
|
87
89
|
}, children: "\u{1F517}" }),
|
|
88
90
|
/* @__PURE__ */ jsx("span", { className: "cv-doc-bar__spacer" }),
|
|
89
91
|
/* @__PURE__ */ jsxs("span", { className: "cv-doc-bar__count", children: [
|
|
90
92
|
words,
|
|
91
|
-
" ",
|
|
92
|
-
t("words"),
|
|
93
|
-
" \xB7 ",
|
|
93
|
+
" words \xB7 ",
|
|
94
94
|
minutes,
|
|
95
|
-
" "
|
|
96
|
-
t("minRead")
|
|
95
|
+
" min read"
|
|
97
96
|
] }),
|
|
98
97
|
/* @__PURE__ */ jsx("button", { className: "cv-doc-bar__done", onMouseDown: (e) => {
|
|
99
98
|
e.preventDefault();
|
|
100
99
|
commit();
|
|
101
|
-
}, children:
|
|
100
|
+
}, children: "Done" })
|
|
102
101
|
] }),
|
|
103
102
|
/* @__PURE__ */ jsx("div", { className: "cv-word__page", children: editing ? /* @__PURE__ */ jsx(
|
|
104
103
|
"textarea",
|
|
@@ -117,7 +116,7 @@ function DocumentRenderer({ artifact }) {
|
|
|
117
116
|
title: "Click to edit",
|
|
118
117
|
onClick: () => artifact.status !== "streaming" && setDraft(content),
|
|
119
118
|
children: [
|
|
120
|
-
/* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: content }),
|
|
119
|
+
/* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], urlTransform, children: content }),
|
|
121
120
|
artifact.status === "streaming" && /* @__PURE__ */ jsx("span", { className: "cv-caret", "aria-hidden": true })
|
|
122
121
|
]
|
|
123
122
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
|
|
2
|
+
import { useCanvasStore } from './chunk-EHW446VF.js';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function formatSize(size) {
|
|
6
|
+
if (typeof size !== "number") return null;
|
|
7
|
+
if (size < 1024) return `${size} B`;
|
|
8
|
+
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
|
9
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
10
|
+
}
|
|
11
|
+
function iconFor(mediaType, name) {
|
|
12
|
+
if (mediaType?.startsWith("image/")) return "\u{1F5BC}\uFE0F";
|
|
13
|
+
if (mediaType === "application/pdf") return "\u{1F4D5}";
|
|
14
|
+
const lower = name.toLowerCase();
|
|
15
|
+
if (lower.endsWith(".docx") || lower.endsWith(".doc")) return "\u{1F4C4}";
|
|
16
|
+
if (lower.endsWith(".xlsx") || lower.endsWith(".csv")) return "\u{1F522}";
|
|
17
|
+
if (lower.endsWith(".pptx") || lower.endsWith(".ppt")) return "\u{1F4FD}\uFE0F";
|
|
18
|
+
return "\u{1F4CE}";
|
|
19
|
+
}
|
|
20
|
+
function FileRenderer({ artifact }) {
|
|
21
|
+
const { path, name, mediaType, size, cover, excerpt, detail } = artifact.data;
|
|
22
|
+
const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
|
|
23
|
+
const href = assetBaseUrl && isAssetReference(path) ? resolveAssetUrl(path, assetBaseUrl) : null;
|
|
24
|
+
const isImage = Boolean(mediaType?.startsWith("image/"));
|
|
25
|
+
const facts = [mediaType, formatSize(size), detail].filter(Boolean).join(" \xB7 ");
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "cv-file", children: [
|
|
27
|
+
isImage && href ? /* @__PURE__ */ jsx("img", { className: "cv-file__image", src: href, alt: name }) : cover ? /* @__PURE__ */ jsx("img", { className: "cv-file__cover", src: cover, alt: `${name} \u2014 first page` }) : excerpt ? /* @__PURE__ */ jsx("pre", { className: "cv-file__excerpt", children: excerpt }) : null,
|
|
28
|
+
/* @__PURE__ */ jsxs("div", { className: "cv-file__card", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("span", { className: "cv-file__icon", "aria-hidden": true, children: iconFor(mediaType, name) }),
|
|
30
|
+
/* @__PURE__ */ jsxs("span", { className: "cv-file__meta", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("b", { children: name }),
|
|
32
|
+
facts && /* @__PURE__ */ jsx("span", { className: "cv-file__facts", children: facts })
|
|
33
|
+
] }),
|
|
34
|
+
href && /* @__PURE__ */ jsx("a", { className: "cv-file__download", href, download: name, children: "Download" })
|
|
35
|
+
] })
|
|
36
|
+
] });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { FileRenderer };
|