@braincrew-lab/langchain-canvas 0.2.0 → 0.3.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 +23 -11
- package/README.md +23 -11
- package/dist/{ChartRenderer-AAWVGKV5.js → ChartRenderer-ABRQ5YFN.js} +49 -31
- package/dist/{DocumentRenderer-NBRBPYU6.js → DocumentRenderer-YTEMC3Z4.js} +17 -12
- package/dist/{PdfRenderer-DPQT4E7O.js → PdfRenderer-GDA67MES.js} +11 -10
- package/dist/{SlidesRenderer-6ZGHXTQX.js → SlidesRenderer-JNPXNTNJ.js} +152 -65
- package/dist/TableRenderer-BJQE2HMO.js +636 -0
- package/dist/{chunk-UL5F66PN.js → chunk-FSFOURG5.js} +1 -1
- package/dist/chunk-QMOJEGRH.js +207 -0
- package/dist/{chunk-S54GJDSJ.js → chunk-ZLXAWRUP.js} +16 -0
- package/dist/index.d.ts +753 -5
- package/dist/index.js +1897 -195
- package/dist/styles.css +379 -4
- package/package.json +26 -2
- package/dist/TableRenderer-VKDD3CB6.js +0 -419
package/README.ko.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# @braincrew-lab/langchain-canvas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@braincrew-lab/langchain-canvas)
|
|
4
|
+
[](https://github.com/braincrew-lab/langchain-canvas/blob/main/LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
LangChain 챗 앱을 위한 캔버스. 에이전트가 아티팩트(웹 페이지·스프레드시트·슬라이드·차트·문서·PDF)를 스트리밍하면 대화 옆에 실시간으로 뜨고 그 자리에서 편집됩니다. 사용자가 직접 손보고 실제 파일로 내보낼 수 있습니다.
|
|
7
|
+
|
|
8
|
+
ChatGPT Canvas나 Claude Artifacts 같은 경험을, **내 React 앱에 넣고 내 에이전트에 연결하는 패키지**로 제공합니다. 스크린샷·아키텍처·와이어 프로토콜 전체는 [GitHub 레포](https://github.com/braincrew-lab/langchain-canvas)에 있습니다.
|
|
6
9
|
|
|
7
10
|
> [English README](./README.md)
|
|
8
11
|
|
|
@@ -74,26 +77,29 @@ HTML이 기본 substrate이고, 샌드박스 iframe에 렌더됩니다. 에이
|
|
|
74
77
|
|
|
75
78
|
정적 표가 아니라 스프레드시트 엔진(Fortune-sheet) 위에서 동작합니다.
|
|
76
79
|
|
|
77
|
-
- **실시간 수식** — `=SUM(C2:C4)`, `=
|
|
80
|
+
- **실시간 수식** — `=SUM(C2:C4)`, `=VLOOKUP(...)`, `=A2*B2` 입력 시 계산. 셀참조·범위·함수 자동완성 지원. 계산은 선택 설치 `fast-formula-parser`가 담당해 Excel 함수 세트(`IF`, `COUNTIF`, `SUMIF`, `INDEX`/`MATCH`, …)가 바로 동작
|
|
78
81
|
- **데이터로 온 수식** — 에이전트가 값으로 보낸 수식(`"=AVERAGE(B2:B4)"`)은 **로드 시 미리 계산**되어 결과가 바로 표시
|
|
79
|
-
-
|
|
82
|
+
- **Excel 스타일 리본** — 삽입 / 글꼴 / 맞춤 / 표시 형식 / 편집 / 보기로 그룹된 트레이, **수식 바**, **Σ 자동 합계** 메뉴
|
|
83
|
+
- **서식** — 폰트, 숫자/통화/%/날짜 서식, 굵게/기울임, 테두리, 셀 병합/해제, 정렬, **틀 고정**, 다중 시트, 그리고 에이전트가 만든 요란한 셀 색을 한 번에 걷어내는 **스타일 정리**
|
|
80
84
|
- 넓은 그리드에서 **상하좌우 부드러운 스크롤**
|
|
81
|
-
- **Export** — `.xlsx`(폰트/병합/서식 포함) 또는 `.csv`
|
|
85
|
+
- **Export** — `.xlsx`(폰트/병합/서식 포함) 또는 `.csv` — 둘 다 사용자의 그리드 편집이 반영됨
|
|
82
86
|
|
|
83
87
|
### 🖼️ 슬라이드 (`slides`) — 프리 캔버스 덱
|
|
84
88
|
|
|
85
89
|
모든 요소가 움직이는 PowerPoint 스타일 편집기.
|
|
86
90
|
|
|
87
|
-
- **자유 배치** — 텍스트/이미지 요소 드래그·리사이즈,
|
|
91
|
+
- **자유 배치** — 텍스트/이미지 요소 드래그·리사이즈, 드래그와 리사이즈 모두에서 스냅 가이드, 회전, Shift로 비율 고정 리사이즈
|
|
92
|
+
- **다중 선택 & 그룹** — Shift+클릭 또는 마퀴 선택 후 그룹/해제(⌘G / ⌘⇧G). 떠 있는 액션 바로 정렬·분배·복제·삭제
|
|
93
|
+
- **키보드 편집** — 방향키 미세 이동, ⌘D 복제, ⌘C/⌘V(슬라이드 간 가능), ⌘] / ⌘[ z-순서
|
|
88
94
|
- **인라인 편집** — 더블클릭 텍스트 편집, 굵게/크기/색/정렬 툴바
|
|
89
95
|
- **구조** — 슬라이드 추가/복제/삭제/순서, 썸네일 레일, 발표자 노트
|
|
90
|
-
-
|
|
91
|
-
- **Export** — `.pptx
|
|
96
|
+
- **8가지 테마**(Editorial · Gallery · Boardroom · Sage · Graphite · Observatory · Ultramarine · Bordeaux)·배경, 발표 모드(전체화면, 방향키 이동, 진행 표시)
|
|
97
|
+
- **Export** — `.pptx` 또는 **PDF**(전 슬라이드)
|
|
92
98
|
|
|
93
99
|
### 📝 문서 (`document`) — Markdown / Word
|
|
94
100
|
|
|
95
101
|
- **클릭 편집** — 페이지를 Markdown으로 편집(GFM 렌더)
|
|
96
|
-
- **Export** — `.docx`, `.md`, `.pdf`, `.html`
|
|
102
|
+
- **Export** — `.docx`, `.md`, `.hwpx`(한글), `.pdf`, `.html`
|
|
97
103
|
|
|
98
104
|
### 📈 차트 (`chart`)
|
|
99
105
|
|
|
@@ -102,10 +108,15 @@ HTML이 기본 substrate이고, 샌드박스 iframe에 렌더됩니다. 에이
|
|
|
102
108
|
- 시리즈(또는 파이 조각)별 **색상 변경**, 시리즈 이름, Y축 라벨, 스택 토글
|
|
103
109
|
- **Export** — `.pdf`(차트가 SVG라 선명) 또는 raw JSON
|
|
104
110
|
|
|
111
|
+
### 📄 PDF (`pdf`)
|
|
112
|
+
|
|
113
|
+
- 에이전트가 `{ type: "pdf", data: { src } }`(`data:`/`blob:`/https URL)를 emit 하면 **브라우저 내장 뷰어**로 렌더 — 의존성 0, 줌 컨트롤 포함
|
|
114
|
+
- `data:` 소스는 `application/pdf`로 고정되어, 조작된 아티팩트가 스크립트 실행 가능한 same-origin 프레임을 끼워 넣을 수 없음
|
|
115
|
+
|
|
105
116
|
### 📁 파일 — 라운드트립
|
|
106
117
|
|
|
107
|
-
- **Import** — 드래그앤드롭 또는 파일 선택: **CSV · Excel · Markdown · HTML · JSON** → 편집 가능한
|
|
108
|
-
- **Export** — 각 아티팩트를 네이티브
|
|
118
|
+
- **Import** — 드래그앤드롭 또는 파일 선택: **CSV · Excel(.xlsx) · Word(.docx) · PowerPoint(.pptx) · PDF · Markdown · HTML · JSON · 한글(.hwp / .hwpx)** → 편집 가능한 아티팩트로. `.xlsx`는 폰트/채우기/병합/서식/내장 이미지를, `.pptx`는 테마 색·마스터 스타일을 유지하고, 바이너리 `.hwp`는 직접 파싱해 서식 있는 HTML로 엽니다
|
|
119
|
+
- **Export** — 각 아티팩트를 네이티브 포맷(`.xlsx` / `.csv` / `.docx` / `.md` / `.hwpx` / `.pptx` / 차트 JSON)으로, 그리고 범용 **standalone `.html`** · **PDF**(브라우저 인쇄)
|
|
109
120
|
|
|
110
121
|
### 🧰 모든 아티팩트 공통
|
|
111
122
|
|
|
@@ -121,6 +132,7 @@ HTML이 기본 substrate이고, 샌드박스 iframe에 렌더됩니다. 에이
|
|
|
121
132
|
- **peerDependency:** React 18 또는 19 — 직접 제공. ESM 전용.
|
|
122
133
|
- **스타일:** `import "@braincrew-lab/langchain-canvas/styles.css"` 한 번.
|
|
123
134
|
- **인스턴스 격리:** `<CanvasProvider>`가 서브트리마다 독립 store 제공.
|
|
135
|
+
- **한국어 UI:** `<Canvas locale="ko" />` 하나로 UI 전체가 한국어/영어 전환.
|
|
124
136
|
- **커스텀 렌더러:** `registry`로 타입 렌더 방식 추가/오버라이드.
|
|
125
137
|
|
|
126
138
|
```tsx
|
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# @braincrew-lab/langchain-canvas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@braincrew-lab/langchain-canvas)
|
|
4
|
+
[](https://github.com/braincrew-lab/langchain-canvas/blob/main/LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
A canvas for your LangChain chat app. Your agent streams an artifact — a web page, a spreadsheet, a slide deck, a chart, a document, a PDF — and it shows up next to the conversation, live and editable. Users can tweak it by hand and export it to a real file.
|
|
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).
|
|
6
9
|
|
|
7
10
|
> [한국어 README](./README.ko.md)
|
|
8
11
|
|
|
@@ -74,26 +77,29 @@ HTML is the base substrate, rendered in a sandboxed iframe. Selecting and editin
|
|
|
74
77
|
|
|
75
78
|
Runs on a spreadsheet engine (Fortune-sheet), not a static table.
|
|
76
79
|
|
|
77
|
-
- **Live formulas** — type `=SUM(C2:C4)`, `=
|
|
80
|
+
- **Live formulas** — type `=SUM(C2:C4)`, `=VLOOKUP(...)`, `=A2*B2`; they calculate, with cell references, ranges, and function autocomplete. Evaluation is delegated to the optional `fast-formula-parser`, so the Excel function set (`IF`, `COUNTIF`, `SUMIF`, `INDEX`/`MATCH`, …) works out of the box.
|
|
78
81
|
- **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.
|
|
79
|
-
- **
|
|
82
|
+
- **Excel-style ribbon** — grouped Insert / Font / Alignment / Number / Editing / View trays, plus a **formula bar** and a **Σ AutoSum** menu.
|
|
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.
|
|
80
84
|
- **Smooth scrolling** in both directions over a large grid.
|
|
81
|
-
- **Export** to `.xlsx` (with fonts/merges/formats) or `.csv
|
|
85
|
+
- **Export** to `.xlsx` (with fonts/merges/formats) or `.csv` — both include the user's in-grid edits.
|
|
82
86
|
|
|
83
87
|
### 🖼️ Slides (`slides`) — a free-canvas deck
|
|
84
88
|
|
|
85
89
|
A PowerPoint-style editor where every element is movable.
|
|
86
90
|
|
|
87
|
-
- **Free positioning** — drag and resize text/image elements; snap to
|
|
91
|
+
- **Free positioning** — drag and resize text/image elements; snap guides on drag *and* resize; rotation; hold Shift to lock aspect while resizing.
|
|
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.
|
|
88
94
|
- **Inline editing** — double-click to edit text; format toolbar for bold/size/color/align.
|
|
89
95
|
- **Structure** — add/duplicate/delete/reorder slides, thumbnails rail, speaker notes.
|
|
90
|
-
- **
|
|
91
|
-
- **Export** to `.pptx
|
|
96
|
+
- **8 themes** (Editorial, Gallery, Boardroom, Sage, Graphite, Observatory, Ultramarine, Bordeaux) & backgrounds; present mode (full-screen, arrow-key navigation, progress bar).
|
|
97
|
+
- **Export** to `.pptx` or **PDF** (all slides).
|
|
92
98
|
|
|
93
99
|
### 📝 Documents (`document`) — Markdown / Word
|
|
94
100
|
|
|
95
101
|
- **Click-to-edit** the page as Markdown, rendered with GFM.
|
|
96
|
-
- **Export** to `.docx`, `.md`, `.pdf`, or `.html`.
|
|
102
|
+
- **Export** to `.docx`, `.md`, `.hwpx` (한글), `.pdf`, or `.html`.
|
|
97
103
|
|
|
98
104
|
### 📈 Charts (`chart`)
|
|
99
105
|
|
|
@@ -102,10 +108,15 @@ A PowerPoint-style editor where every element is movable.
|
|
|
102
108
|
- **Recolor** each series (or each pie slice), rename series, set the y-axis label, toggle stacking.
|
|
103
109
|
- **Export** to `.pdf` (the chart is SVG, so it prints crisply) or the raw JSON.
|
|
104
110
|
|
|
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
|
+
|
|
105
116
|
### 📁 Files — round-trip
|
|
106
117
|
|
|
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).
|
|
118
|
+
- **Import** by drag-and-drop or a file picker: **CSV · Excel (.xlsx) · Word (.docx) · PowerPoint (.pptx) · PDF · Markdown · HTML · JSON · 한글 (.hwp / .hwpx)**. They open as editable artifacts — `.xlsx` keeps fonts/fills/merges/formats/embedded images, `.pptx` keeps theme colors and master styles, and binary `.hwp` is parsed from scratch into formatted HTML.
|
|
119
|
+
- **Export** every artifact to its native format (`.xlsx` / `.csv` / `.docx` / `.md` / `.hwpx` / `.pptx` / chart JSON), plus a universal **standalone `.html`** and **PDF** (browser print).
|
|
109
120
|
|
|
110
121
|
### 🧰 Across every artifact
|
|
111
122
|
|
|
@@ -121,6 +132,7 @@ A PowerPoint-style editor where every element is movable.
|
|
|
121
132
|
- **Peer dependency:** React 18 or 19 — you bring your own. ESM only.
|
|
122
133
|
- **Styles:** `import "@braincrew-lab/langchain-canvas/styles.css"` once.
|
|
123
134
|
- **Isolated instances:** `<CanvasProvider>` gives each subtree its own store.
|
|
135
|
+
- **Korean UI:** `<Canvas locale="ko" />` localizes the entire chrome (English / Korean).
|
|
124
136
|
- **Bring your own renderer:** pass `registry` to add or override how a type renders.
|
|
125
137
|
|
|
126
138
|
```tsx
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { useArtifactPatch } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { useArtifactPatch } from './chunk-FSFOURG5.js';
|
|
2
|
+
import './chunk-ZLXAWRUP.js';
|
|
3
|
+
import { useT } from './chunk-QMOJEGRH.js';
|
|
3
4
|
import { useState, useRef, useMemo, useEffect } from 'react';
|
|
4
5
|
import * as echarts from 'echarts/core';
|
|
5
|
-
import { BarChart, LineChart, PieChart } from 'echarts/charts';
|
|
6
|
+
import { BarChart, LineChart, PieChart, ScatterChart } from 'echarts/charts';
|
|
6
7
|
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
|
|
7
8
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
8
9
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
9
10
|
|
|
10
|
-
echarts.use([BarChart, LineChart, PieChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);
|
|
11
|
+
echarts.use([BarChart, LineChart, PieChart, ScatterChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);
|
|
11
12
|
var PALETTE = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#0ea5e9", "#a855f7"];
|
|
12
13
|
var seriesColor = (series, index) => series.color ?? PALETTE[index % PALETTE.length];
|
|
13
14
|
var sliceColor = (options, index) => options?.colors?.[index] ?? PALETTE[index % PALETTE.length];
|
|
14
|
-
var CHART_TYPES = ["bar", "line", "area", "pie"];
|
|
15
|
+
var CHART_TYPES = ["bar", "line", "area", "scatter", "pie", "donut"];
|
|
15
16
|
function EChart({ option, height, onInst }) {
|
|
16
17
|
const boxRef = useRef(null);
|
|
17
18
|
const instRef = useRef(null);
|
|
@@ -35,6 +36,7 @@ function EChart({ option, height, onInst }) {
|
|
|
35
36
|
return /* @__PURE__ */ jsx("div", { ref: boxRef, className: "cv-chart__canvas", style: { width: "100%", height } });
|
|
36
37
|
}
|
|
37
38
|
function ChartRenderer({ artifact }) {
|
|
39
|
+
const t = useT();
|
|
38
40
|
const { chart, rows, xKey, series, options, echartsOption } = artifact.data;
|
|
39
41
|
const patch = useArtifactPatch(artifact.id);
|
|
40
42
|
const [editing, setEditing] = useState(false);
|
|
@@ -56,15 +58,15 @@ function ChartRenderer({ artifact }) {
|
|
|
56
58
|
return /* @__PURE__ */ jsxs("div", { className: "cv-chart", children: [
|
|
57
59
|
/* @__PURE__ */ jsxs("div", { className: "cv-chart__toolbar cv-chrome", children: [
|
|
58
60
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
|
|
59
|
-
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "
|
|
61
|
+
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: t("downloadPng"), children: t("downloadPng") })
|
|
60
62
|
] }),
|
|
61
63
|
/* @__PURE__ */ jsx(EChart, { option, height: 320, onInst: (i) => instRef.current = i })
|
|
62
64
|
] });
|
|
63
65
|
}
|
|
64
66
|
if (rows.length === 0) {
|
|
65
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-chart cv-chart--empty", children: "
|
|
67
|
+
return /* @__PURE__ */ jsx("div", { className: "cv-chart cv-chart--empty", children: t("waitingData") });
|
|
66
68
|
}
|
|
67
|
-
const isPie = chart === "pie";
|
|
69
|
+
const isPie = chart === "pie" || chart === "donut";
|
|
68
70
|
const setSeries = (i, partial) => patch({ series: series.map((s, j) => j === i ? { ...s, ...partial } : s) });
|
|
69
71
|
const setSliceColor = (i, color) => {
|
|
70
72
|
const colors = rows.map((_, j) => options?.colors?.[j] ?? PALETTE[j % PALETTE.length]);
|
|
@@ -80,38 +82,38 @@ function ChartRenderer({ artifact }) {
|
|
|
80
82
|
const removeRow = (i) => patch({ rows: rows.filter((_, j) => j !== i) });
|
|
81
83
|
return /* @__PURE__ */ jsxs("div", { className: "cv-chart", children: [
|
|
82
84
|
/* @__PURE__ */ jsxs("div", { className: "cv-chart__toolbar cv-chrome", children: [
|
|
83
|
-
/* @__PURE__ */ jsx("div", { className: "cv-chart__types", children: CHART_TYPES.map((
|
|
85
|
+
/* @__PURE__ */ jsx("div", { className: "cv-chart__types", children: CHART_TYPES.map((t2) => /* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${chart === t2 ? "is-primary" : ""}`, onClick: () => patch({ chart: t2 }), children: t2 }, t2)) }),
|
|
84
86
|
(chart === "bar" || chart === "area") && /* @__PURE__ */ jsxs("label", { className: "cv-chart__stack", children: [
|
|
85
87
|
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: !!options?.stacked, onChange: (e) => patch({ options: { ...options, stacked: e.target.checked } }) }),
|
|
86
|
-
"
|
|
88
|
+
t("stacked")
|
|
87
89
|
] }),
|
|
88
90
|
/* @__PURE__ */ jsx(
|
|
89
91
|
"input",
|
|
90
92
|
{
|
|
91
93
|
className: "cv-chart__title-input",
|
|
92
94
|
value: options?.title ?? "",
|
|
93
|
-
placeholder: "
|
|
95
|
+
placeholder: t("chartTitle"),
|
|
94
96
|
onChange: (e) => patch({ options: { ...options, title: e.target.value || void 0 } }),
|
|
95
|
-
title: "
|
|
97
|
+
title: t("chartTitle")
|
|
96
98
|
}
|
|
97
99
|
),
|
|
98
100
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
|
|
99
|
-
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "
|
|
100
|
-
/* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ? "
|
|
101
|
+
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: t("downloadPng"), children: t("downloadPng") }),
|
|
102
|
+
/* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ? t("doneEditing") : t("editData") })
|
|
101
103
|
] }),
|
|
102
104
|
editing && /* @__PURE__ */ jsxs("div", { className: "cv-chart__editor cv-chrome", children: [
|
|
103
105
|
/* @__PURE__ */ jsx("div", { className: "cv-chart__legend", children: isPie ? rows.map((r, i) => /* @__PURE__ */ jsxs("span", { className: "cv-chart__swatch", children: [
|
|
104
106
|
/* @__PURE__ */ jsx("input", { type: "color", value: sliceColor(options, i), onChange: (e) => setSliceColor(i, e.target.value), title: `Color: ${r[xKey]}` }),
|
|
105
107
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__swatch-label", children: String(r[xKey]) })
|
|
106
108
|
] }, i)) : series.map((s, i) => /* @__PURE__ */ jsxs("span", { className: "cv-chart__swatch", children: [
|
|
107
|
-
/* @__PURE__ */ jsx("input", { type: "color", value: seriesColor(s, i), onChange: (e) => setSeries(i, { color: e.target.value }), title: "
|
|
109
|
+
/* @__PURE__ */ jsx("input", { type: "color", value: seriesColor(s, i), onChange: (e) => setSeries(i, { color: e.target.value }), title: t("seriesColor") }),
|
|
108
110
|
/* @__PURE__ */ jsx(
|
|
109
111
|
"input",
|
|
110
112
|
{
|
|
111
113
|
className: "cv-chart__series-name",
|
|
112
114
|
value: s.label ?? s.key,
|
|
113
115
|
onChange: (e) => setSeries(i, { label: e.target.value }),
|
|
114
|
-
title: "
|
|
116
|
+
title: t("seriesName")
|
|
115
117
|
}
|
|
116
118
|
)
|
|
117
119
|
] }, s.key)) }),
|
|
@@ -149,6 +151,7 @@ function DataGrid({
|
|
|
149
151
|
onAddRow,
|
|
150
152
|
onRemoveRow
|
|
151
153
|
}) {
|
|
154
|
+
const t = useT();
|
|
152
155
|
return /* @__PURE__ */ jsxs("div", { className: "cv-chart__grid", children: [
|
|
153
156
|
/* @__PURE__ */ jsxs("table", { children: [
|
|
154
157
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
@@ -159,17 +162,17 @@ function DataGrid({
|
|
|
159
162
|
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
160
163
|
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("input", { value: String(row[xKey] ?? ""), onChange: (e) => onCell(i, xKey, e.target.value) }) }),
|
|
161
164
|
series.map((s) => /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(NumberCell, { value: row[s.key], onCommit: (n) => onCell(i, s.key, n) }) }, s.key)),
|
|
162
|
-
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("button", { className: "cv-chart__row-del", onClick: () => onRemoveRow(i), disabled: rows.length <= 1, title: "
|
|
165
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("button", { className: "cv-chart__row-del", onClick: () => onRemoveRow(i), disabled: rows.length <= 1, title: t("removeRow"), children: "\xD7" }) })
|
|
163
166
|
] }, i)) })
|
|
164
167
|
] }),
|
|
165
|
-
/* @__PURE__ */ jsx("button", { className: "cv-chart__addrow", onClick: onAddRow, children: "
|
|
168
|
+
/* @__PURE__ */ jsx("button", { className: "cv-chart__addrow", onClick: onAddRow, children: t("addRowBtn") })
|
|
166
169
|
] });
|
|
167
170
|
}
|
|
168
171
|
function toEChartsOption(chart, rows, xKey, series, options) {
|
|
169
172
|
const AXIS = "#9aa4b2";
|
|
170
173
|
const titleBlock = options?.title ? { title: { text: options.title, left: "center", top: 2, textStyle: { color: AXIS, fontSize: 15, fontWeight: 600 } } } : {};
|
|
171
174
|
const hasTitle = !!options?.title;
|
|
172
|
-
if (chart === "pie") {
|
|
175
|
+
if (chart === "pie" || chart === "donut") {
|
|
173
176
|
const valueKey = series[0]?.key ?? "value";
|
|
174
177
|
return {
|
|
175
178
|
...titleBlock,
|
|
@@ -178,7 +181,7 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
178
181
|
series: [
|
|
179
182
|
{
|
|
180
183
|
type: "pie",
|
|
181
|
-
radius: ["45%", "72%"],
|
|
184
|
+
radius: chart === "donut" ? ["45%", "70%"] : ["45%", "72%"],
|
|
182
185
|
padAngle: 2,
|
|
183
186
|
itemStyle: { borderRadius: 4 },
|
|
184
187
|
label: { color: AXIS },
|
|
@@ -191,13 +194,20 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
191
194
|
]
|
|
192
195
|
};
|
|
193
196
|
}
|
|
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]));
|
|
194
199
|
const stacked = (chart === "bar" || chart === "area") && !!options?.stacked;
|
|
195
200
|
return {
|
|
196
201
|
...titleBlock,
|
|
197
|
-
tooltip: { trigger: "axis" },
|
|
202
|
+
tooltip: { trigger: chart === "scatter" ? "item" : "axis" },
|
|
198
203
|
legend: { bottom: 0, textStyle: { color: AXIS } },
|
|
199
204
|
grid: { left: 8, right: 16, top: hasTitle ? 44 : 24, bottom: 40, containLabel: true },
|
|
200
|
-
xAxis: {
|
|
205
|
+
xAxis: scatterOnValueAxis ? {
|
|
206
|
+
type: "value",
|
|
207
|
+
axisLabel: { color: AXIS, fontSize: 12 },
|
|
208
|
+
axisLine: { lineStyle: { color: "#d4d7dd" } },
|
|
209
|
+
splitLine: { lineStyle: { color: "#e5e7eb", type: "dashed" } }
|
|
210
|
+
} : {
|
|
201
211
|
type: "category",
|
|
202
212
|
data: rows.map((r) => String(r[xKey] ?? "")),
|
|
203
213
|
axisLabel: { color: AXIS, fontSize: 12 },
|
|
@@ -210,15 +220,23 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
210
220
|
axisLabel: { color: AXIS, fontSize: 12 },
|
|
211
221
|
splitLine: { lineStyle: { color: "#e5e7eb", type: "dashed" } }
|
|
212
222
|
},
|
|
213
|
-
series: series.map(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
series: series.map(
|
|
224
|
+
(s, i) => chart === "scatter" ? {
|
|
225
|
+
name: s.label ?? s.key,
|
|
226
|
+
type: "scatter",
|
|
227
|
+
symbolSize: 10,
|
|
228
|
+
itemStyle: { color: seriesColor(s, i) },
|
|
229
|
+
data: scatterOnValueAxis ? rows.map((r) => [Number(r[xKey]), Number(r[s.key] ?? 0)]) : rows.map((r) => Number(r[s.key] ?? 0))
|
|
230
|
+
} : {
|
|
231
|
+
name: s.label ?? s.key,
|
|
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
|
+
)
|
|
222
240
|
};
|
|
223
241
|
}
|
|
224
242
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useArtifactPatch } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { useArtifactPatch } from './chunk-FSFOURG5.js';
|
|
2
|
+
import './chunk-ZLXAWRUP.js';
|
|
3
|
+
import { useT } from './chunk-QMOJEGRH.js';
|
|
3
4
|
import { useState, useRef } from 'react';
|
|
4
5
|
import ReactMarkdown from 'react-markdown';
|
|
5
6
|
import remarkGfm from 'remark-gfm';
|
|
@@ -10,6 +11,7 @@ function wordStats(text) {
|
|
|
10
11
|
return { words, minutes: Math.max(1, Math.round(words / 200)) };
|
|
11
12
|
}
|
|
12
13
|
function DocumentRenderer({ artifact }) {
|
|
14
|
+
const t = useT();
|
|
13
15
|
const { content } = artifact.data;
|
|
14
16
|
const patch = useArtifactPatch(artifact.id);
|
|
15
17
|
const [draft, setDraft] = useState(null);
|
|
@@ -46,15 +48,15 @@ function DocumentRenderer({ artifact }) {
|
|
|
46
48
|
const { words, minutes } = wordStats(editing ? draft ?? "" : content);
|
|
47
49
|
return /* @__PURE__ */ jsxs("div", { className: "cv-word", children: [
|
|
48
50
|
editing && /* @__PURE__ */ jsxs("div", { className: "cv-doc-bar cv-chrome", onMouseDown: (e) => e.preventDefault(), children: [
|
|
49
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
51
|
+
/* @__PURE__ */ jsx("button", { title: t("bold"), onMouseDown: (e) => {
|
|
50
52
|
e.preventDefault();
|
|
51
53
|
wrap("**");
|
|
52
54
|
}, children: /* @__PURE__ */ jsx("b", { children: "B" }) }),
|
|
53
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
55
|
+
/* @__PURE__ */ jsx("button", { title: t("italic"), onMouseDown: (e) => {
|
|
54
56
|
e.preventDefault();
|
|
55
57
|
wrap("*");
|
|
56
58
|
}, children: /* @__PURE__ */ jsx("i", { children: "I" }) }),
|
|
57
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
59
|
+
/* @__PURE__ */ jsx("button", { title: t("inlineCode"), onMouseDown: (e) => {
|
|
58
60
|
e.preventDefault();
|
|
59
61
|
wrap("`");
|
|
60
62
|
}, children: "<>" }),
|
|
@@ -67,33 +69,36 @@ function DocumentRenderer({ artifact }) {
|
|
|
67
69
|
e.preventDefault();
|
|
68
70
|
prefixLines("## ");
|
|
69
71
|
}, children: "H2" }),
|
|
70
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
72
|
+
/* @__PURE__ */ jsx("button", { title: t("bulletList"), onMouseDown: (e) => {
|
|
71
73
|
e.preventDefault();
|
|
72
74
|
prefixLines("- ");
|
|
73
75
|
}, children: "\u2022" }),
|
|
74
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
76
|
+
/* @__PURE__ */ jsx("button", { title: t("numberedList"), onMouseDown: (e) => {
|
|
75
77
|
e.preventDefault();
|
|
76
78
|
prefixLines("1. ");
|
|
77
79
|
}, children: "1." }),
|
|
78
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
80
|
+
/* @__PURE__ */ jsx("button", { title: t("quote"), onMouseDown: (e) => {
|
|
79
81
|
e.preventDefault();
|
|
80
82
|
prefixLines("> ");
|
|
81
83
|
}, children: "\u275D" }),
|
|
82
|
-
/* @__PURE__ */ jsx("button", { title: "
|
|
84
|
+
/* @__PURE__ */ jsx("button", { title: t("link"), onMouseDown: (e) => {
|
|
83
85
|
e.preventDefault();
|
|
84
86
|
link();
|
|
85
87
|
}, children: "\u{1F517}" }),
|
|
86
88
|
/* @__PURE__ */ jsx("span", { className: "cv-doc-bar__spacer" }),
|
|
87
89
|
/* @__PURE__ */ jsxs("span", { className: "cv-doc-bar__count", children: [
|
|
88
90
|
words,
|
|
89
|
-
"
|
|
91
|
+
" ",
|
|
92
|
+
t("words"),
|
|
93
|
+
" \xB7 ",
|
|
90
94
|
minutes,
|
|
91
|
-
"
|
|
95
|
+
" ",
|
|
96
|
+
t("minRead")
|
|
92
97
|
] }),
|
|
93
98
|
/* @__PURE__ */ jsx("button", { className: "cv-doc-bar__done", onMouseDown: (e) => {
|
|
94
99
|
e.preventDefault();
|
|
95
100
|
commit();
|
|
96
|
-
}, children: "
|
|
101
|
+
}, children: t("doneEditing") })
|
|
97
102
|
] }),
|
|
98
103
|
/* @__PURE__ */ jsx("div", { className: "cv-word__page", children: editing ? /* @__PURE__ */ jsx(
|
|
99
104
|
"textarea",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { useT } from './chunk-QMOJEGRH.js';
|
|
1
2
|
import { useState, useEffect, useMemo } from 'react';
|
|
2
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
4
|
|
|
4
|
-
// src/components/renderers/PdfRenderer.tsx
|
|
5
5
|
function dataUrlToBlob(url) {
|
|
6
6
|
const m = /^data:[^,]*?(;base64)?,(.*)$/s.exec(url);
|
|
7
7
|
if (!m) return null;
|
|
@@ -18,6 +18,7 @@ function dataUrlToBlob(url) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
function PdfRenderer({ artifact }) {
|
|
21
|
+
const t = useT();
|
|
21
22
|
const src = artifact.data.src;
|
|
22
23
|
const isData = typeof src === "string" && src.startsWith("data:");
|
|
23
24
|
const [blobUrl, setBlobUrl] = useState(null);
|
|
@@ -55,38 +56,38 @@ function PdfRenderer({ artifact }) {
|
|
|
55
56
|
a.click();
|
|
56
57
|
};
|
|
57
58
|
if (!src || broken) {
|
|
58
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-pdf cv-pdf--empty", children: "
|
|
59
|
+
return /* @__PURE__ */ jsx("div", { className: "cv-pdf cv-pdf--empty", children: t("waitingPdf") });
|
|
59
60
|
}
|
|
60
61
|
if (!viewUrl) {
|
|
61
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-pdf cv-pdf--empty", children: "
|
|
62
|
+
return /* @__PURE__ */ jsx("div", { className: "cv-pdf cv-pdf--empty", children: t("loading") });
|
|
62
63
|
}
|
|
63
64
|
return /* @__PURE__ */ jsxs("div", { className: "cv-pdf-panel", children: [
|
|
64
65
|
/* @__PURE__ */ jsxs("div", { className: "cv-pdf-tools", children: [
|
|
65
66
|
/* @__PURE__ */ jsx("span", { className: "cv-pdf-tools__name", title: filename, children: filename }),
|
|
66
67
|
/* @__PURE__ */ jsx("span", { className: "cv-pdf-tools__spacer" }),
|
|
67
68
|
/* @__PURE__ */ jsxs("span", { className: "cv-pdf-tools__zoom", children: [
|
|
68
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: zoomOut, title: "
|
|
69
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: zoomOut, title: t("zoomOut"), children: "\u2212" }),
|
|
69
70
|
/* @__PURE__ */ jsx(
|
|
70
71
|
"button",
|
|
71
72
|
{
|
|
72
73
|
type: "button",
|
|
73
74
|
className: "cv-pdf-tools__zoom-label",
|
|
74
75
|
onClick: () => setZoom("fit"),
|
|
75
|
-
title: "
|
|
76
|
-
children: zoom === "fit" ? "
|
|
76
|
+
title: t("fitWidth"),
|
|
77
|
+
children: zoom === "fit" ? t("fitWidth") : `${zoom}%`
|
|
77
78
|
}
|
|
78
79
|
),
|
|
79
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: zoomIn, title: "
|
|
80
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: zoomIn, title: t("zoomIn"), children: "\uFF0B" })
|
|
80
81
|
] }),
|
|
81
82
|
/* @__PURE__ */ jsx("span", { className: "cv-pdf-tools__sep" }),
|
|
82
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: download, title: "
|
|
83
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: download, title: t("download"), children: t("download") }),
|
|
83
84
|
/* @__PURE__ */ jsx(
|
|
84
85
|
"button",
|
|
85
86
|
{
|
|
86
87
|
type: "button",
|
|
87
88
|
onClick: () => window.open(viewUrl, "_blank", "noopener"),
|
|
88
|
-
title: "
|
|
89
|
-
children: "
|
|
89
|
+
title: t("openBtn"),
|
|
90
|
+
children: t("openBtn")
|
|
90
91
|
}
|
|
91
92
|
)
|
|
92
93
|
] }),
|