@braincrew-lab/langchain-canvas 0.1.11 → 0.1.12

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.
@@ -67,6 +67,16 @@ function ChartRenderer({ artifact }) {
67
67
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: !!options?.stacked, onChange: (e) => patch({ options: { ...options, stacked: e.target.checked } }) }),
68
68
  "Stacked"
69
69
  ] }),
70
+ /* @__PURE__ */ jsx(
71
+ "input",
72
+ {
73
+ className: "cv-chart__title-input",
74
+ value: options?.title ?? "",
75
+ placeholder: "Chart title\u2026",
76
+ onChange: (e) => patch({ options: { ...options, title: e.target.value || void 0 } }),
77
+ title: "Chart title"
78
+ }
79
+ ),
70
80
  /* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
71
81
  /* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ? "Done" : "Edit data" })
72
82
  ] }),
@@ -128,9 +138,12 @@ function DataGrid({
128
138
  }
129
139
  function toEChartsOption(chart, rows, xKey, series, options) {
130
140
  const AXIS = "#9aa4b2";
141
+ const titleBlock = options?.title ? { title: { text: options.title, left: "center", top: 2, textStyle: { color: AXIS, fontSize: 15, fontWeight: 600 } } } : {};
142
+ const hasTitle = !!options?.title;
131
143
  if (chart === "pie") {
132
144
  const valueKey = series[0]?.key ?? "value";
133
145
  return {
146
+ ...titleBlock,
134
147
  tooltip: { trigger: "item" },
135
148
  legend: { bottom: 0, textStyle: { color: AXIS } },
136
149
  series: [
@@ -151,9 +164,10 @@ function toEChartsOption(chart, rows, xKey, series, options) {
151
164
  }
152
165
  const stacked = (chart === "bar" || chart === "area") && !!options?.stacked;
153
166
  return {
167
+ ...titleBlock,
154
168
  tooltip: { trigger: "axis" },
155
169
  legend: { bottom: 0, textStyle: { color: AXIS } },
156
- grid: { left: 8, right: 16, top: 24, bottom: 40, containLabel: true },
170
+ grid: { left: 8, right: 16, top: hasTitle ? 44 : 24, bottom: 40, containLabel: true },
157
171
  xAxis: {
158
172
  type: "category",
159
173
  data: rows.map((r) => String(r[xKey] ?? "")),
@@ -0,0 +1,123 @@
1
+ import { useArtifactPatch } from './chunk-TERWLUW3.js';
2
+ import './chunk-KKLWKR5G.js';
3
+ import { useState, useRef } from 'react';
4
+ import ReactMarkdown from 'react-markdown';
5
+ import remarkGfm from 'remark-gfm';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ function wordStats(text) {
9
+ const words = text.trim() ? text.trim().split(/\s+/).length : 0;
10
+ return { words, minutes: Math.max(1, Math.round(words / 200)) };
11
+ }
12
+ function DocumentRenderer({ artifact }) {
13
+ const { content } = artifact.data;
14
+ const patch = useArtifactPatch(artifact.id);
15
+ const [draft, setDraft] = useState(null);
16
+ const taRef = useRef(null);
17
+ const editing = draft !== null;
18
+ const commit = () => {
19
+ if (draft !== null && draft !== content) patch({ content: draft });
20
+ setDraft(null);
21
+ };
22
+ const apply = (fn) => {
23
+ const ta = taRef.current;
24
+ if (!ta || draft === null) return;
25
+ const { text, a, b } = fn(draft, ta.selectionStart, ta.selectionEnd);
26
+ setDraft(text);
27
+ requestAnimationFrame(() => {
28
+ ta.focus();
29
+ ta.setSelectionRange(a, b);
30
+ });
31
+ };
32
+ const wrap = (mark) => apply((full, s, e) => ({ text: full.slice(0, s) + mark + full.slice(s, e) + mark + full.slice(e), a: s + mark.length, b: e + mark.length }));
33
+ const link = () => apply((full, s, e) => {
34
+ const label = full.slice(s, e) || "link";
35
+ const ins = `[${label}](https://)`;
36
+ const urlAt = s + label.length + 3;
37
+ return { text: full.slice(0, s) + ins + full.slice(e), a: urlAt, b: urlAt + 8 };
38
+ });
39
+ const prefixLines = (prefix) => apply((full, s, e) => {
40
+ const start = full.lastIndexOf("\n", s - 1) + 1;
41
+ const nl = full.indexOf("\n", e);
42
+ const end = nl === -1 ? full.length : nl;
43
+ const block = full.slice(start, end).split("\n").map((l) => prefix + l).join("\n");
44
+ return { text: full.slice(0, start) + block + full.slice(end), a: start, b: start + block.length };
45
+ });
46
+ const { words, minutes } = wordStats(editing ? draft ?? "" : content);
47
+ return /* @__PURE__ */ jsxs("div", { className: "cv-word", children: [
48
+ editing && /* @__PURE__ */ jsxs("div", { className: "cv-doc-bar cv-chrome", onMouseDown: (e) => e.preventDefault(), children: [
49
+ /* @__PURE__ */ jsx("button", { title: "Bold", onMouseDown: (e) => {
50
+ e.preventDefault();
51
+ wrap("**");
52
+ }, children: /* @__PURE__ */ jsx("b", { children: "B" }) }),
53
+ /* @__PURE__ */ jsx("button", { title: "Italic", onMouseDown: (e) => {
54
+ e.preventDefault();
55
+ wrap("*");
56
+ }, children: /* @__PURE__ */ jsx("i", { children: "I" }) }),
57
+ /* @__PURE__ */ jsx("button", { title: "Inline code", onMouseDown: (e) => {
58
+ e.preventDefault();
59
+ wrap("`");
60
+ }, children: "<>" }),
61
+ /* @__PURE__ */ jsx("span", { className: "cv-doc-bar__sep" }),
62
+ /* @__PURE__ */ jsx("button", { title: "Heading 1", onMouseDown: (e) => {
63
+ e.preventDefault();
64
+ prefixLines("# ");
65
+ }, children: "H1" }),
66
+ /* @__PURE__ */ jsx("button", { title: "Heading 2", onMouseDown: (e) => {
67
+ e.preventDefault();
68
+ prefixLines("## ");
69
+ }, children: "H2" }),
70
+ /* @__PURE__ */ jsx("button", { title: "Bullet list", onMouseDown: (e) => {
71
+ e.preventDefault();
72
+ prefixLines("- ");
73
+ }, children: "\u2022" }),
74
+ /* @__PURE__ */ jsx("button", { title: "Numbered list", onMouseDown: (e) => {
75
+ e.preventDefault();
76
+ prefixLines("1. ");
77
+ }, children: "1." }),
78
+ /* @__PURE__ */ jsx("button", { title: "Quote", onMouseDown: (e) => {
79
+ e.preventDefault();
80
+ prefixLines("> ");
81
+ }, children: "\u275D" }),
82
+ /* @__PURE__ */ jsx("button", { title: "Link", onMouseDown: (e) => {
83
+ e.preventDefault();
84
+ link();
85
+ }, children: "\u{1F517}" }),
86
+ /* @__PURE__ */ jsx("span", { className: "cv-doc-bar__spacer" }),
87
+ /* @__PURE__ */ jsxs("span", { className: "cv-doc-bar__count", children: [
88
+ words,
89
+ " words \xB7 ",
90
+ minutes,
91
+ " min read"
92
+ ] }),
93
+ /* @__PURE__ */ jsx("button", { className: "cv-doc-bar__done", onMouseDown: (e) => {
94
+ e.preventDefault();
95
+ commit();
96
+ }, children: "Done" })
97
+ ] }),
98
+ /* @__PURE__ */ jsx("div", { className: "cv-word__page", children: editing ? /* @__PURE__ */ jsx(
99
+ "textarea",
100
+ {
101
+ ref: taRef,
102
+ className: "cv-doc-editor",
103
+ value: draft,
104
+ autoFocus: true,
105
+ onBlur: commit,
106
+ onChange: (e) => setDraft(e.target.value)
107
+ }
108
+ ) : /* @__PURE__ */ jsxs(
109
+ "article",
110
+ {
111
+ className: "cv-doc",
112
+ title: "Click to edit",
113
+ onClick: () => artifact.status !== "streaming" && setDraft(content),
114
+ children: [
115
+ /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: content }),
116
+ artifact.status === "streaming" && /* @__PURE__ */ jsx("span", { className: "cv-caret", "aria-hidden": true })
117
+ ]
118
+ }
119
+ ) })
120
+ ] });
121
+ }
122
+
123
+ export { DocumentRenderer };
package/dist/index.d.ts CHANGED
@@ -46,6 +46,8 @@ interface ChartSeries {
46
46
  interface ChartOptions {
47
47
  stacked?: boolean;
48
48
  yLabel?: string;
49
+ /** Chart title shown above the plot. */
50
+ title?: string;
49
51
  /** Per-slice colors for pie charts, index-aligned to `rows`. */
50
52
  colors?: string[];
51
53
  }
package/dist/index.js CHANGED
@@ -2111,8 +2111,8 @@ function HtmlRenderer({ artifact: artifact2 }) {
2111
2111
  }
2112
2112
 
2113
2113
  // src/components/renderers/index.ts
2114
- var ChartRenderer = lazy(() => import('./ChartRenderer-4MWKP2J4.js').then((m) => ({ default: m.ChartRenderer })));
2115
- var DocumentRenderer = lazy(() => import('./DocumentRenderer-LJGP6N7E.js').then((m) => ({ default: m.DocumentRenderer })));
2114
+ var ChartRenderer = lazy(() => import('./ChartRenderer-CWV2OX3S.js').then((m) => ({ default: m.ChartRenderer })));
2115
+ var DocumentRenderer = lazy(() => import('./DocumentRenderer-KJ6FTGKH.js').then((m) => ({ default: m.DocumentRenderer })));
2116
2116
  var TableRenderer = lazy(() => import('./TableRenderer-ZF7MVA7I.js').then((m) => ({ default: m.TableRenderer })));
2117
2117
  var SlidesRenderer = lazy(() => import('./SlidesRenderer-7ICJQHYT.js').then((m) => ({ default: m.SlidesRenderer })));
2118
2118
  var builtinRenderers = {
package/dist/styles.css CHANGED
@@ -295,6 +295,21 @@
295
295
 
296
296
  /* --- Word viewer (document page) ---------------------------------------------- */
297
297
 
298
+ .cv-doc-bar {
299
+ position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 4px;
300
+ max-width: 816px; margin: 0 auto 12px; padding: 6px 8px;
301
+ border: 1px solid var(--cv-border); border-radius: 10px; background: var(--cv-surface);
302
+ flex-wrap: wrap;
303
+ }
304
+ .cv-doc-bar button {
305
+ border: 0; background: transparent; color: var(--cv-text); font: inherit; font-size: 13px;
306
+ min-width: 28px; height: 28px; padding: 0 8px; border-radius: 6px; cursor: pointer;
307
+ }
308
+ .cv-doc-bar button:hover { background: var(--cv-surface-2, rgba(120,120,140,0.12)); }
309
+ .cv-doc-bar__sep { width: 1px; height: 18px; background: var(--cv-border); margin: 0 4px; }
310
+ .cv-doc-bar__spacer { flex: 1; }
311
+ .cv-doc-bar__count { font-size: 12px; color: var(--cv-muted); white-space: nowrap; }
312
+ .cv-doc-bar__done { background: var(--cv-accent) !important; color: #fff; font-weight: 600; }
298
313
  .cv-word { background: #edeef1; padding: 28px 16px; min-height: 100%; }
299
314
  .cv-word .cv-edit-toolbar { max-width: 816px; margin: 0 auto 12px; }
300
315
  .cv-word__page {
@@ -690,6 +705,12 @@
690
705
  .cv-chart__types .cv-edit-btn { text-transform: capitalize; }
691
706
  .cv-chart__stack { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--cv-muted); cursor: pointer; }
692
707
  .cv-chart__spacer { flex: 1; }
708
+ .cv-chart__title-input {
709
+ font: inherit; font-size: 13px; padding: 5px 10px; border-radius: 7px;
710
+ border: 1px solid var(--cv-border); background: var(--cv-bg); color: var(--cv-text);
711
+ min-width: 120px; max-width: 220px;
712
+ }
713
+ .cv-chart__title-input:focus { outline: none; border-color: var(--cv-accent); }
693
714
 
694
715
  /* --- chart editor ------------------------------------------------------------- */
695
716
  .cv-chart__editor {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braincrew-lab/langchain-canvas",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,40 +0,0 @@
1
- import { useArtifactPatch } from './chunk-TERWLUW3.js';
2
- import './chunk-KKLWKR5G.js';
3
- import { useState } from 'react';
4
- import ReactMarkdown from 'react-markdown';
5
- import remarkGfm from 'remark-gfm';
6
- import { jsx, jsxs } from 'react/jsx-runtime';
7
-
8
- function DocumentRenderer({ artifact }) {
9
- const { content } = artifact.data;
10
- const patch = useArtifactPatch(artifact.id);
11
- const [draft, setDraft] = useState(null);
12
- const editing = draft !== null;
13
- const commit = () => {
14
- if (draft !== null && draft !== content) patch({ content: draft });
15
- setDraft(null);
16
- };
17
- return /* @__PURE__ */ jsx("div", { className: "cv-word", children: /* @__PURE__ */ jsx("div", { className: "cv-word__page", children: editing ? /* @__PURE__ */ jsx(
18
- "textarea",
19
- {
20
- className: "cv-doc-editor",
21
- value: draft,
22
- autoFocus: true,
23
- onBlur: commit,
24
- onChange: (e) => setDraft(e.target.value)
25
- }
26
- ) : /* @__PURE__ */ jsxs(
27
- "article",
28
- {
29
- className: "cv-doc",
30
- title: "Click to edit",
31
- onClick: () => artifact.status !== "streaming" && setDraft(content),
32
- children: [
33
- /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: content }),
34
- artifact.status === "streaming" && /* @__PURE__ */ jsx("span", { className: "cv-caret", "aria-hidden": true })
35
- ]
36
- }
37
- ) }) });
38
- }
39
-
40
- export { DocumentRenderer };