@ai-matrx/rich-content 0.2.8 → 0.2.10
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/CHANGELOG.md +13 -0
- package/dist/code-block/CodeBlock.js +2 -7
- package/dist/display/blocks/common/BlockHeaderWrapper.js +2 -9
- package/dist/display/blocks/diagram/InteractiveDiagramBlock.js +29 -72
- package/dist/display/blocks/images/ImageBlock.js +9 -27
- package/dist/display/blocks/json/json-tabular-utils.d.ts +7 -5
- package/dist/display/blocks/json/json-tabular-utils.js +26 -24
- package/dist/display/blocks/links/LinkComponent.js +3 -7
- package/dist/display/blocks/svg/SvgBlock.js +2 -7
- package/dist/display/blocks/table/StreamingTableRenderer.js +7 -32
- package/dist/display/chat-markdown/BasicMarkdownContent.js +8 -6
- package/dist/display/chat-markdown/ConfigurableMarkdownContent.js +8 -6
- package/dist/display/tables/MarkdownTable.js +7 -32
- package/dist/display/tables/TableWithSeparatedControls.js +7 -32
- package/dist/display/tables/table-csv.d.ts +7 -0
- package/dist/display/tables/table-csv.js +19 -0
- package/dist/host/app-bindings.d.ts +2 -0
- package/dist/host/app-bindings.js +2 -0
- package/dist/mermaid/export.d.ts +1 -0
- package/dist/mermaid/export.js +5 -8
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.10
|
|
4
|
+
|
|
5
|
+
- Markdown copy goes through the host's `app.CopyButtons` (the copy menu; matrx-frontend ALC-19).
|
|
6
|
+
- Table CSV/TSV and workbook export through Alchemy (`toDelimitedText`, `buildFile`); `display/tables/table-csv`.
|
|
7
|
+
|
|
8
|
+
### Consumer action
|
|
9
|
+
|
|
10
|
+
- Register `app.CopyButtons` (matrx-frontend: `components/agent-copy/CopyButtons`).
|
|
11
|
+
|
|
12
|
+
## 0.2.9
|
|
13
|
+
|
|
14
|
+
AP-2 (2026-10-07) — the kit and Alchemy doors are the only doors in this package (aidream guard `apps/shared/scripts/check-shared-doors.mjs`). New dependency `@ai-matrx/alchemy`; `html2canvas` dependency removed. Every table/code/SVG/JSON/mermaid/diagram download saves through `@ai-matrx/kit/download` (`downloadFile` / `downloadUrl`) — the tables' "Download Again" toast action now really downloads again (it clicked a revoked URL before). Link/URL/mermaid copies go through `@ai-matrx/kit/clipboard` `copyText` (copied state from its boolean; `copyMermaidSource`/`copyMermaidSvg` throw when refused, as before); ImageBlock's image copy uses `useClipboard().copyImage`. The diagram PNG export captures through `@ai-matrx/alchemy/operate/capture` `captureElement` with `safeColors` (the local getComputedStyle patch is gone). No consumer action.
|
|
15
|
+
|
|
3
16
|
## 0.2.8
|
|
4
17
|
### Unreleased
|
|
5
18
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
3
4
|
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
5
|
import { useRef, useState, useEffect } from "react";
|
|
5
6
|
import { KindDataGate } from "../host/app-bindings.js";
|
|
@@ -184,15 +185,9 @@ const CodeBlockBody = ({
|
|
|
184
185
|
};
|
|
185
186
|
const handleDownload = (e) => {
|
|
186
187
|
e.stopPropagation();
|
|
187
|
-
const blob = new Blob([code], { type: "text/plain" });
|
|
188
|
-
const url = window.URL.createObjectURL(blob);
|
|
189
|
-
const a = document.createElement("a");
|
|
190
|
-
a.href = url;
|
|
191
188
|
const titleFile = fence.title && /^[\w./-]+\.\w+$/.test(fence.title) ? fence.title.split("/").pop() : void 0;
|
|
192
189
|
const ext = codeLanguageToExtension(rawLanguage || "txt");
|
|
193
|
-
|
|
194
|
-
a.click();
|
|
195
|
-
window.URL.revokeObjectURL(url);
|
|
190
|
+
downloadFile(titleFile ?? `code${ext}`, code, "text/plain");
|
|
196
191
|
};
|
|
197
192
|
const toggleLineNumbers = (e) => {
|
|
198
193
|
e.stopPropagation();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
3
4
|
import { useState, useRef, useEffect } from "react";
|
|
4
5
|
import {
|
|
5
6
|
Maximize2,
|
|
@@ -93,15 +94,7 @@ const BlockHeaderWrapper = ({
|
|
|
93
94
|
};
|
|
94
95
|
const handleExport = () => {
|
|
95
96
|
if (exportData === void 0) return;
|
|
96
|
-
|
|
97
|
-
type: "application/json"
|
|
98
|
-
});
|
|
99
|
-
const url = URL.createObjectURL(blob);
|
|
100
|
-
const a = document.createElement("a");
|
|
101
|
-
a.href = url;
|
|
102
|
-
a.download = `${exportFilename}.json`;
|
|
103
|
-
a.click();
|
|
104
|
-
URL.revokeObjectURL(url);
|
|
97
|
+
downloadFile(`${exportFilename}.json`, JSON.stringify(exportData, null, 2), "application/json");
|
|
105
98
|
};
|
|
106
99
|
const handleImportClick = () => fileInputRef.current?.click();
|
|
107
100
|
const handleFileChange = (e) => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { captureElement } from "@ai-matrx/alchemy/operate/capture";
|
|
4
|
+
import { downloadFile, downloadUrl } from "@ai-matrx/kit/download";
|
|
3
5
|
import { Button, Chip } from "@ai-matrx/design-system/controls";
|
|
4
6
|
import {
|
|
5
7
|
useState,
|
|
@@ -121,7 +123,6 @@ import {
|
|
|
121
123
|
DropdownMenuSeparator,
|
|
122
124
|
DropdownMenuTrigger
|
|
123
125
|
} from "@ai-matrx/design-system";
|
|
124
|
-
const UNSAFE_COLOR_RE = /\b(oklch|oklab|lab|lch|color)\s*\(/i;
|
|
125
126
|
function backgroundVariantFromDiagram(diagram) {
|
|
126
127
|
if (diagram.renderHints?.background === "lines")
|
|
127
128
|
return BackgroundVariant.Lines;
|
|
@@ -129,42 +130,6 @@ function backgroundVariantFromDiagram(diagram) {
|
|
|
129
130
|
return BackgroundVariant.Cross;
|
|
130
131
|
return BackgroundVariant.Dots;
|
|
131
132
|
}
|
|
132
|
-
function safenColor(value, prop, isDark) {
|
|
133
|
-
if (!UNSAFE_COLOR_RE.test(value)) return value;
|
|
134
|
-
const lp = prop.toLowerCase();
|
|
135
|
-
if (lp.includes("background")) return isDark ? "#1f2937" : "#ffffff";
|
|
136
|
-
if (lp === "color") return isDark ? "#f3f4f6" : "#111827";
|
|
137
|
-
if (lp.includes("border") || lp.includes("outline"))
|
|
138
|
-
return isDark ? "#4b5563" : "#d1d5db";
|
|
139
|
-
if (lp === "fill" || lp === "stroke") return isDark ? "#9ca3af" : "#374151";
|
|
140
|
-
return isDark ? "#6b7280" : "#6b7280";
|
|
141
|
-
}
|
|
142
|
-
function patchComputedStyleForCapture(isDark) {
|
|
143
|
-
const original = window.getComputedStyle.bind(window);
|
|
144
|
-
window.getComputedStyle = function(elt, pseudo) {
|
|
145
|
-
const computed = original(elt, pseudo);
|
|
146
|
-
return new Proxy(computed, {
|
|
147
|
-
get(target, prop) {
|
|
148
|
-
const value = target[prop];
|
|
149
|
-
if (typeof prop === "string" && typeof value === "string" && value && UNSAFE_COLOR_RE.test(value)) {
|
|
150
|
-
return safenColor(value, prop, isDark);
|
|
151
|
-
}
|
|
152
|
-
if (prop === "getPropertyValue") {
|
|
153
|
-
return (p) => {
|
|
154
|
-
const raw = target.getPropertyValue(p);
|
|
155
|
-
return raw && UNSAFE_COLOR_RE.test(raw) ? safenColor(raw, p, isDark) : raw;
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
if (typeof value === "function")
|
|
159
|
-
return value.bind(target);
|
|
160
|
-
return value;
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
};
|
|
164
|
-
return () => {
|
|
165
|
-
window.getComputedStyle = original;
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
133
|
const PedigreeNode = ({
|
|
169
134
|
data,
|
|
170
135
|
selected
|
|
@@ -1081,22 +1046,20 @@ const DiagramFlow = ({
|
|
|
1081
1046
|
}
|
|
1082
1047
|
exportContainer.appendChild(cloned);
|
|
1083
1048
|
document.body.appendChild(exportContainer);
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
ignoreElements: (el) => el.classList.contains("react-flow__controls") || el.classList.contains("react-flow__minimap") || el.classList.contains("react-flow__panel")
|
|
1094
|
-
}).finally(restore);
|
|
1049
|
+
captureElement(exportContainer, {
|
|
1050
|
+
backgroundColor: bgColor,
|
|
1051
|
+
scale: 2,
|
|
1052
|
+
useCORS: true,
|
|
1053
|
+
allowTaint: true,
|
|
1054
|
+
width: imageWidth,
|
|
1055
|
+
height: imageHeight,
|
|
1056
|
+
safeColors: { dark: isDark },
|
|
1057
|
+
ignoreElements: (el) => el.classList.contains("react-flow__controls") || el.classList.contains("react-flow__minimap") || el.classList.contains("react-flow__panel")
|
|
1095
1058
|
}).then((canvas) => {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1059
|
+
downloadUrl(
|
|
1060
|
+
canvas.toDataURL("image/png"),
|
|
1061
|
+
`${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_diagram${isDark ? "_dark" : "_light"}.png`
|
|
1062
|
+
);
|
|
1100
1063
|
}).catch((err) => console.warn("Diagram export failed:", err)).finally(() => document.body.removeChild(exportContainer));
|
|
1101
1064
|
}, [getNodes, diagram.title, onExportImage]);
|
|
1102
1065
|
const measuredWidth = useStore((s) => s.width);
|
|
@@ -2133,15 +2096,11 @@ const InteractiveDiagramBlock = ({
|
|
|
2133
2096
|
triggerPrint
|
|
2134
2097
|
} = usePrintOptions(diagramPrinter, diagram);
|
|
2135
2098
|
const exportDiagramJSON = useCallback(() => {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
a.href = url;
|
|
2142
|
-
a.download = `${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}.json`;
|
|
2143
|
-
a.click();
|
|
2144
|
-
URL.revokeObjectURL(url);
|
|
2099
|
+
downloadFile(
|
|
2100
|
+
`${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}.json`,
|
|
2101
|
+
JSON.stringify({ diagram }, null, 2),
|
|
2102
|
+
"application/json"
|
|
2103
|
+
);
|
|
2145
2104
|
}, [diagram]);
|
|
2146
2105
|
const toggleEditing = () => {
|
|
2147
2106
|
const next = !editing;
|
|
@@ -2154,18 +2113,16 @@ const InteractiveDiagramBlock = ({
|
|
|
2154
2113
|
);
|
|
2155
2114
|
if (!viewport) return;
|
|
2156
2115
|
const isDark = document.documentElement.classList.contains("dark") || window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
useCORS: true
|
|
2163
|
-
}).finally(restore);
|
|
2116
|
+
captureElement(viewport, {
|
|
2117
|
+
backgroundColor: isDark ? "#111827" : "#ffffff",
|
|
2118
|
+
scale: 2,
|
|
2119
|
+
useCORS: true,
|
|
2120
|
+
safeColors: { dark: isDark }
|
|
2164
2121
|
}).then((canvas) => {
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2122
|
+
downloadUrl(
|
|
2123
|
+
canvas.toDataURL("image/png"),
|
|
2124
|
+
`${diagram.title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_diagram.png`
|
|
2125
|
+
);
|
|
2169
2126
|
}).catch((err) => console.warn("Image export failed:", err));
|
|
2170
2127
|
}, [diagram.title]);
|
|
2171
2128
|
const renderLegend = () => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { copyText, useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
3
5
|
import { useState, useRef, useEffect } from "react";
|
|
4
6
|
import {
|
|
5
7
|
DownloadIcon,
|
|
@@ -46,6 +48,7 @@ const ImageBlockImpl = ({ src: srcProp, alt = "Image" }) => {
|
|
|
46
48
|
const editableFileId = ourFile?.fileId ?? null;
|
|
47
49
|
const [feedback, setFeedback] = useState("none");
|
|
48
50
|
const [showCopySuccess, setShowCopySuccess] = useState(false);
|
|
51
|
+
const { copyImage } = useClipboard();
|
|
49
52
|
const [showShareModal, setShowShareModal] = useState(false);
|
|
50
53
|
const [showExpandedView, setShowExpandedView] = useState(false);
|
|
51
54
|
const [zoomLevel, setZoomLevel] = useState(1);
|
|
@@ -55,45 +58,24 @@ const ImageBlockImpl = ({ src: srcProp, alt = "Image" }) => {
|
|
|
55
58
|
try {
|
|
56
59
|
const response = await fetch(src);
|
|
57
60
|
const blob = await response.blob();
|
|
58
|
-
|
|
59
|
-
const link = document.createElement("a");
|
|
60
|
-
link.href = url;
|
|
61
|
-
link.download = src.split("/").pop() || "image";
|
|
62
|
-
document.body.appendChild(link);
|
|
63
|
-
link.click();
|
|
64
|
-
document.body.removeChild(link);
|
|
65
|
-
window.URL.revokeObjectURL(url);
|
|
61
|
+
downloadFile(src.split("/").pop() || "image", blob, blob.type);
|
|
66
62
|
} catch (err) {
|
|
67
63
|
console.error("Failed to download image:", err);
|
|
68
64
|
}
|
|
69
65
|
};
|
|
70
66
|
const handleCopyUrl = (e) => {
|
|
71
67
|
e?.preventDefault();
|
|
72
|
-
|
|
68
|
+
void copyText(src).then((landed) => {
|
|
69
|
+
if (!landed) return;
|
|
73
70
|
setShowCopySuccess(true);
|
|
74
71
|
setTimeout(() => setShowCopySuccess(false), 2e3);
|
|
75
|
-
}).catch((err) => {
|
|
76
|
-
console.error("Failed to copy URL:", err);
|
|
77
72
|
});
|
|
78
73
|
};
|
|
79
74
|
const handleCopyImage = async (e) => {
|
|
80
75
|
e?.preventDefault();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (navigator.clipboard && navigator.clipboard.write) {
|
|
85
|
-
await navigator.clipboard.write([
|
|
86
|
-
new ClipboardItem({
|
|
87
|
-
[blob.type]: blob
|
|
88
|
-
})
|
|
89
|
-
]);
|
|
90
|
-
setShowCopySuccess(true);
|
|
91
|
-
setTimeout(() => setShowCopySuccess(false), 2e3);
|
|
92
|
-
} else {
|
|
93
|
-
console.error("Clipboard API not supported");
|
|
94
|
-
}
|
|
95
|
-
} catch (err) {
|
|
96
|
-
console.error("Failed to copy image to clipboard:", err);
|
|
76
|
+
if (await copyImage(src)) {
|
|
77
|
+
setShowCopySuccess(true);
|
|
78
|
+
setTimeout(() => setShowCopySuccess(false), 2e3);
|
|
97
79
|
}
|
|
98
80
|
};
|
|
99
81
|
const handleShare = async (e) => {
|
|
@@ -49,16 +49,18 @@ export declare function detectTabular(value: unknown): TabularShape;
|
|
|
49
49
|
* `null` and `undefined` become empty strings (Excel-friendly).
|
|
50
50
|
*/
|
|
51
51
|
export declare function cellToCsv(value: unknown): string;
|
|
52
|
+
/**
|
|
53
|
+
* Rows -> CSV/TSV text through Alchemy's one writer (quoting, line breaks, `'` before a leading
|
|
54
|
+
* = + - @ text; real numbers stay numbers, so -5 is never altered).
|
|
55
|
+
*/
|
|
52
56
|
export declare function rowsToCsv(rows: Record<string, unknown>[], columns: string[], delimiter?: string): string;
|
|
53
57
|
export declare function rowsToNdjson(rows: unknown[]): string;
|
|
54
58
|
/** Browser file-download for a text payload. */
|
|
55
59
|
export declare function downloadText(filename: string, content: string, mime?: string): void;
|
|
56
60
|
/**
|
|
57
|
-
* XLSX export
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* Cell values are pre-stringified for objects/arrays so spreadsheets
|
|
61
|
-
* receive plain text instead of "[object Object]".
|
|
61
|
+
* XLSX export through Alchemy's workbook format (the one spreadsheet engine).
|
|
62
|
+
* Header row = `columns`; object/array cells are pre-stringified so
|
|
63
|
+
* spreadsheets receive plain text instead of "[object Object]".
|
|
62
64
|
*/
|
|
63
65
|
export declare function rowsToXlsx(rows: Record<string, unknown>[], columns: string[], filename: string): Promise<void>;
|
|
64
66
|
/** Default filename helper (no extension). */
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildFile } from "@ai-matrx/alchemy/operate";
|
|
2
|
+
import { toDelimitedText } from "@ai-matrx/alchemy/operate/read";
|
|
3
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
2
4
|
function parseJsonSafe(text) {
|
|
3
5
|
if (!text || !text.trim()) {
|
|
4
6
|
return { ok: false, error: "Empty input" };
|
|
@@ -72,38 +74,38 @@ function cellToCsv(value) {
|
|
|
72
74
|
}
|
|
73
75
|
return String(value);
|
|
74
76
|
}
|
|
75
|
-
function escapeCsv(value, delimiter) {
|
|
76
|
-
const needsQuote = value.includes(delimiter) || value.includes('"') || value.includes("\n") || value.includes("\r");
|
|
77
|
-
if (!needsQuote) return value;
|
|
78
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
79
|
-
}
|
|
80
77
|
function rowsToCsv(rows, columns, delimiter = ",") {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
(row) => columns.map((c) =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
return toDelimitedText(
|
|
79
|
+
columns,
|
|
80
|
+
rows.map((row) => columns.map((c) => cellToCsvValue(row[c]))),
|
|
81
|
+
{ format: delimiter === " " ? "tsv" : "csv", spreadsheetSafe: true }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function cellToCsvValue(value) {
|
|
85
|
+
if (value === null || value === void 0) return "";
|
|
86
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
87
|
+
return cellToCsv(value);
|
|
87
88
|
}
|
|
88
89
|
function rowsToNdjson(rows) {
|
|
89
90
|
return rows.map((r) => JSON.stringify(r)).join("\n");
|
|
90
91
|
}
|
|
91
92
|
function downloadText(filename, content, mime = "text/plain") {
|
|
92
93
|
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
|
|
93
|
-
|
|
94
|
-
const a = document.createElement("a");
|
|
95
|
-
a.href = url;
|
|
96
|
-
a.download = filename;
|
|
97
|
-
document.body.appendChild(a);
|
|
98
|
-
a.click();
|
|
99
|
-
document.body.removeChild(a);
|
|
100
|
-
window.URL.revokeObjectURL(url);
|
|
94
|
+
downloadFile(filename, blob, blob.type);
|
|
101
95
|
}
|
|
102
96
|
async function rowsToXlsx(rows, columns, filename) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
const cell = (v) => v === null || v === void 0 ? null : typeof v === "object" ? JSON.stringify(v) : typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? v : String(v);
|
|
98
|
+
const built = await buildFile(
|
|
99
|
+
{
|
|
100
|
+
kind: "registered",
|
|
101
|
+
format: "workbook",
|
|
102
|
+
value: { sheets: [{ name: "Data", rows: [columns, ...rows.map((row) => columns.map((c) => cell(row[c])))] }] }
|
|
103
|
+
},
|
|
104
|
+
"xlsx-workbook",
|
|
105
|
+
{ filename }
|
|
106
|
+
);
|
|
107
|
+
const blob = built.blob();
|
|
108
|
+
downloadFile(built.filename, blob, blob.type);
|
|
107
109
|
}
|
|
108
110
|
function defaultJsonFilename() {
|
|
109
111
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { copyText } from "@ai-matrx/kit/clipboard";
|
|
3
4
|
import React, { useState, useRef, useEffect, useCallback, useId, memo } from "react";
|
|
4
5
|
import { createPortal } from "react-dom";
|
|
5
6
|
import { cn } from "@ai-matrx/design-system";
|
|
@@ -164,15 +165,10 @@ const ValidLinkComponentCore = ({
|
|
|
164
165
|
clearOpenTimer();
|
|
165
166
|
}, [clearOpenTimer]);
|
|
166
167
|
const handleCopyLink = useCallback(() => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
navigator.clipboard.writeText(finalHref).then(() => {
|
|
168
|
+
void copyText(finalHref).then((landed) => {
|
|
169
|
+
if (!landed) return;
|
|
172
170
|
setCopied(true);
|
|
173
171
|
setTimeout(() => setCopied(false), 2e3);
|
|
174
|
-
}).catch((err) => {
|
|
175
|
-
console.error("Failed to copy link:", err);
|
|
176
172
|
});
|
|
177
173
|
}, [finalHref]);
|
|
178
174
|
const handleOpenInNewTab = useCallback(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
3
4
|
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
5
|
import { useEffect, useMemo, useState } from "react";
|
|
5
6
|
import { createPortal } from "react-dom";
|
|
@@ -49,13 +50,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
49
50
|
};
|
|
50
51
|
const handleDownload = () => {
|
|
51
52
|
try {
|
|
52
|
-
|
|
53
|
-
const url = URL.createObjectURL(blob);
|
|
54
|
-
const a = document.createElement("a");
|
|
55
|
-
a.href = url;
|
|
56
|
-
a.download = `${(title ?? "illustration").replace(/[^\w-]+/g, "-").toLowerCase()}.svg`;
|
|
57
|
-
a.click();
|
|
58
|
-
URL.revokeObjectURL(url);
|
|
53
|
+
downloadFile(`${(title ?? "illustration").replace(/[^\w-]+/g, "-").toLowerCase()}.svg`, svg, "image/svg+xml");
|
|
59
54
|
} catch {
|
|
60
55
|
toast.error("Couldn't download the SVG");
|
|
61
56
|
}
|
|
@@ -76,6 +76,8 @@ import {
|
|
|
76
76
|
parseMarkdownTable,
|
|
77
77
|
cleanTableHeaderKey
|
|
78
78
|
} from "./parseMarkdownTable.js";
|
|
79
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
80
|
+
import { tableToCsv } from "../../tables/table-csv.js";
|
|
79
81
|
const NO_HIDDEN_COLUMNS = /* @__PURE__ */ new Set();
|
|
80
82
|
const ExportDropdownMenu = ({
|
|
81
83
|
tableData,
|
|
@@ -291,26 +293,13 @@ const StreamingTableRendererCore = ({
|
|
|
291
293
|
};
|
|
292
294
|
const downloadCSV = () => {
|
|
293
295
|
try {
|
|
294
|
-
const csvContent =
|
|
295
|
-
headers.map((h) => h.replace(/"/g, '""')).join(","),
|
|
296
|
-
...rows.map(
|
|
297
|
-
(row) => row.map((cell) => {
|
|
298
|
-
const escaped = cell.replace(/"/g, '""');
|
|
299
|
-
return cell.includes(",") ? `"${escaped}"` : escaped;
|
|
300
|
-
}).join(",")
|
|
301
|
-
)
|
|
302
|
-
].join("\n");
|
|
296
|
+
const csvContent = tableToCsv(headers, rows);
|
|
303
297
|
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
304
|
-
|
|
305
|
-
const link = document.createElement("a");
|
|
306
|
-
link.href = url;
|
|
307
|
-
link.download = "table_data.csv";
|
|
308
|
-
link.click();
|
|
309
|
-
URL.revokeObjectURL(url);
|
|
298
|
+
downloadFile("table_data.csv", blob, blob.type);
|
|
310
299
|
toast.success("Table exported to CSV", {
|
|
311
300
|
action: {
|
|
312
301
|
label: "Download Again",
|
|
313
|
-
onClick: () =>
|
|
302
|
+
onClick: () => downloadFile("table_data.csv", blob, blob.type),
|
|
314
303
|
className: "font-medium"
|
|
315
304
|
}
|
|
316
305
|
});
|
|
@@ -327,25 +316,11 @@ const StreamingTableRendererCore = ({
|
|
|
327
316
|
const blob = new Blob([markdownContent], {
|
|
328
317
|
type: "text/markdown;charset=utf-8"
|
|
329
318
|
});
|
|
330
|
-
|
|
331
|
-
const link = document.createElement("a");
|
|
332
|
-
link.href = url;
|
|
333
|
-
link.download = fileName;
|
|
334
|
-
document.body.appendChild(link);
|
|
335
|
-
link.click();
|
|
336
|
-
document.body.removeChild(link);
|
|
337
|
-
URL.revokeObjectURL(url);
|
|
319
|
+
downloadFile(fileName, blob, blob.type);
|
|
338
320
|
toast.success("Markdown file downloaded", {
|
|
339
321
|
action: {
|
|
340
322
|
label: "Download Again",
|
|
341
|
-
onClick: () =>
|
|
342
|
-
const newLink = document.createElement("a");
|
|
343
|
-
newLink.href = url;
|
|
344
|
-
newLink.download = fileName;
|
|
345
|
-
document.body.appendChild(newLink);
|
|
346
|
-
newLink.click();
|
|
347
|
-
document.body.removeChild(newLink);
|
|
348
|
-
},
|
|
323
|
+
onClick: () => downloadFile(fileName, blob, blob.type),
|
|
349
324
|
className: "font-medium"
|
|
350
325
|
}
|
|
351
326
|
});
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
reportDelimiterViolations
|
|
8
8
|
} from "@ai-matrx/kit/delimiter-guard";
|
|
9
9
|
import { captureError } from "../../host/diagnostics.js";
|
|
10
|
-
import {
|
|
10
|
+
import { CopyButtons } from "../../host/app-bindings.js";
|
|
11
11
|
import {
|
|
12
12
|
TableRenderPathDiagnostic
|
|
13
13
|
} from "../blocks/table/TableRenderPathDiagnostic.js";
|
|
@@ -102,12 +102,14 @@ const BasicMarkdownContentBody = ({
|
|
|
102
102
|
/* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: PROSE_FRAME_CSS } }),
|
|
103
103
|
/* @__PURE__ */ jsx(MarkdownCore, { preset: "chat", components, children: processedContent }),
|
|
104
104
|
!isStreamActive && isHovering && /* @__PURE__ */ jsx("div", { className: "absolute top-0 right-0 p-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200", children: showCopyButton && /* @__PURE__ */ jsx(
|
|
105
|
-
|
|
105
|
+
CopyButtons,
|
|
106
106
|
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
label: "Content",
|
|
108
|
+
human: content,
|
|
109
|
+
contentFlavor: "markdown",
|
|
110
|
+
stripThinking: true,
|
|
111
|
+
size: "sm",
|
|
112
|
+
className: "absolute top-1 right-1 z-10 mt-1 mr-1"
|
|
111
113
|
}
|
|
112
114
|
) })
|
|
113
115
|
]
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "@ai-matrx/kit/delimiter-guard";
|
|
13
13
|
import { captureError } from "../../host/diagnostics.js";
|
|
14
14
|
import { LinkComponent } from "../blocks/links/LinkComponent.js";
|
|
15
|
-
import {
|
|
15
|
+
import { CopyButtons } from "../../host/app-bindings.js";
|
|
16
16
|
import {
|
|
17
17
|
useMediaBlob,
|
|
18
18
|
useMediaLoadRecovery
|
|
@@ -614,12 +614,14 @@ const ConfigurableMarkdownContentBody = ({
|
|
|
614
614
|
/* @__PURE__ */ jsx(MarkdownCore, { preset: "rich", components, children: processedContent }),
|
|
615
615
|
!isStreamActive && isHovering && /* @__PURE__ */ jsxs("div", { className: "absolute top-0 right-0 p-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200", children: [
|
|
616
616
|
showCopyButton && /* @__PURE__ */ jsx(
|
|
617
|
-
|
|
617
|
+
CopyButtons,
|
|
618
618
|
{
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
619
|
+
label: "Content",
|
|
620
|
+
human: content,
|
|
621
|
+
contentFlavor: "markdown",
|
|
622
|
+
stripThinking: true,
|
|
623
|
+
size: "sm",
|
|
624
|
+
className: "absolute top-1 right-1 z-10 mt-1 mr-1"
|
|
623
625
|
}
|
|
624
626
|
),
|
|
625
627
|
onEditRequest && /* @__PURE__ */ jsx(
|
|
@@ -60,6 +60,8 @@ import {
|
|
|
60
60
|
clearColumn,
|
|
61
61
|
duplicateColumn
|
|
62
62
|
} from "./editing/tableMutations.js";
|
|
63
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
64
|
+
import { tableToCsv } from "./table-csv.js";
|
|
63
65
|
const ExportDropdownMenu = ({
|
|
64
66
|
tableData,
|
|
65
67
|
content,
|
|
@@ -290,26 +292,13 @@ const MarkdownTable = ({
|
|
|
290
292
|
};
|
|
291
293
|
const downloadCSV = () => {
|
|
292
294
|
try {
|
|
293
|
-
const csvContent =
|
|
294
|
-
internalTableData.headers.map((h) => h.replace(/"/g, '""')).join(","),
|
|
295
|
-
...internalTableData.rows.map(
|
|
296
|
-
(row) => row.map((cell) => {
|
|
297
|
-
const escaped = cell.replace(/"/g, '""');
|
|
298
|
-
return cell.includes(",") ? `"${escaped}"` : escaped;
|
|
299
|
-
}).join(",")
|
|
300
|
-
)
|
|
301
|
-
].join("\n");
|
|
295
|
+
const csvContent = tableToCsv(internalTableData.headers, internalTableData.rows);
|
|
302
296
|
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
303
|
-
|
|
304
|
-
const link = document.createElement("a");
|
|
305
|
-
link.href = url;
|
|
306
|
-
link.download = "table_data.csv";
|
|
307
|
-
link.click();
|
|
308
|
-
URL.revokeObjectURL(url);
|
|
297
|
+
downloadFile("table_data.csv", blob, blob.type);
|
|
309
298
|
toast.success("Table exported to CSV", {
|
|
310
299
|
action: {
|
|
311
300
|
label: "Download Again",
|
|
312
|
-
onClick: () =>
|
|
301
|
+
onClick: () => downloadFile("table_data.csv", blob, blob.type),
|
|
313
302
|
className: "font-medium"
|
|
314
303
|
}
|
|
315
304
|
});
|
|
@@ -324,25 +313,11 @@ const MarkdownTable = ({
|
|
|
324
313
|
const blob = new Blob([markdownContent], {
|
|
325
314
|
type: "text/markdown;charset=utf-8"
|
|
326
315
|
});
|
|
327
|
-
|
|
328
|
-
const link = document.createElement("a");
|
|
329
|
-
link.href = url;
|
|
330
|
-
link.download = fileName;
|
|
331
|
-
document.body.appendChild(link);
|
|
332
|
-
link.click();
|
|
333
|
-
document.body.removeChild(link);
|
|
334
|
-
URL.revokeObjectURL(url);
|
|
316
|
+
downloadFile(fileName, blob, blob.type);
|
|
335
317
|
toast.success("Markdown file downloaded", {
|
|
336
318
|
action: {
|
|
337
319
|
label: "Download Again",
|
|
338
|
-
onClick: () =>
|
|
339
|
-
const newLink = document.createElement("a");
|
|
340
|
-
newLink.href = url;
|
|
341
|
-
newLink.download = fileName;
|
|
342
|
-
document.body.appendChild(newLink);
|
|
343
|
-
newLink.click();
|
|
344
|
-
document.body.removeChild(newLink);
|
|
345
|
-
},
|
|
320
|
+
onClick: () => downloadFile(fileName, blob, blob.type),
|
|
346
321
|
className: "font-medium"
|
|
347
322
|
}
|
|
348
323
|
});
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
import { useToastManager } from "../../utils/lifted/useToastManager.js";
|
|
31
31
|
import { THEMES } from "../themes.js";
|
|
32
32
|
import { useOpenSaveToTable } from "../../host/tables.js";
|
|
33
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
34
|
+
import { tableToCsv } from "./table-csv.js";
|
|
33
35
|
function useDebounce(value, delay) {
|
|
34
36
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
35
37
|
useEffect(() => {
|
|
@@ -107,26 +109,13 @@ const TableControls = ({
|
|
|
107
109
|
}, [content, copyTableToClipboard, toast]);
|
|
108
110
|
const downloadCSV = useCallback(() => {
|
|
109
111
|
try {
|
|
110
|
-
const csvContent =
|
|
111
|
-
tableData.headers.map((h) => h.replace(/"/g, '""')).join(","),
|
|
112
|
-
...tableData.rows.map(
|
|
113
|
-
(row) => row.map((cell) => {
|
|
114
|
-
const escaped = cell.replace(/"/g, '""');
|
|
115
|
-
return cell.includes(",") ? `"${escaped}"` : escaped;
|
|
116
|
-
}).join(",")
|
|
117
|
-
)
|
|
118
|
-
].join("\n");
|
|
112
|
+
const csvContent = tableToCsv(tableData.headers, tableData.rows);
|
|
119
113
|
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
120
|
-
|
|
121
|
-
const link = document.createElement("a");
|
|
122
|
-
link.href = url;
|
|
123
|
-
link.download = "table_data.csv";
|
|
124
|
-
link.click();
|
|
125
|
-
URL.revokeObjectURL(url);
|
|
114
|
+
downloadFile("table_data.csv", blob, blob.type);
|
|
126
115
|
toast.success("Table exported to CSV", {
|
|
127
116
|
action: {
|
|
128
117
|
label: "Download Again",
|
|
129
|
-
onClick: () =>
|
|
118
|
+
onClick: () => downloadFile("table_data.csv", blob, blob.type),
|
|
130
119
|
className: "font-medium"
|
|
131
120
|
}
|
|
132
121
|
});
|
|
@@ -141,25 +130,11 @@ const TableControls = ({
|
|
|
141
130
|
const blob = new Blob([markdownContent], {
|
|
142
131
|
type: "text/markdown;charset=utf-8"
|
|
143
132
|
});
|
|
144
|
-
|
|
145
|
-
const link = document.createElement("a");
|
|
146
|
-
link.href = url;
|
|
147
|
-
link.download = fileName;
|
|
148
|
-
document.body.appendChild(link);
|
|
149
|
-
link.click();
|
|
150
|
-
document.body.removeChild(link);
|
|
151
|
-
URL.revokeObjectURL(url);
|
|
133
|
+
downloadFile(fileName, blob, blob.type);
|
|
152
134
|
toast.success("Markdown file downloaded", {
|
|
153
135
|
action: {
|
|
154
136
|
label: "Download Again",
|
|
155
|
-
onClick: () =>
|
|
156
|
-
const newLink = document.createElement("a");
|
|
157
|
-
newLink.href = url;
|
|
158
|
-
newLink.download = fileName;
|
|
159
|
-
document.body.appendChild(newLink);
|
|
160
|
-
newLink.click();
|
|
161
|
-
document.body.removeChild(newLink);
|
|
162
|
-
},
|
|
137
|
+
onClick: () => downloadFile(fileName, blob, blob.type),
|
|
163
138
|
className: "font-medium"
|
|
164
139
|
}
|
|
165
140
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CSV text of a markdown/streaming table, written by Alchemy's one writer. Quotes, commas and
|
|
3
|
+
* line breaks are quoted; text that opens with = + - @ is prefixed `'` so a spreadsheet never runs
|
|
4
|
+
* it (alchemy `spreadsheetSafe`, on). A cell that is a plain number is written as a number, so a
|
|
5
|
+
* negative amount stays `-5`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function tableToCsv(headers: readonly string[], rows: readonly (readonly string[])[]): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { toDelimitedText } from "@ai-matrx/alchemy/operate/read";
|
|
2
|
+
const PLAIN_NUMBER = /^-?\d+(\.\d+)?$/;
|
|
3
|
+
function cellValue(cell) {
|
|
4
|
+
if (PLAIN_NUMBER.test(cell)) {
|
|
5
|
+
const n = Number(cell);
|
|
6
|
+
if (String(n) === cell) return n;
|
|
7
|
+
}
|
|
8
|
+
return cell;
|
|
9
|
+
}
|
|
10
|
+
function tableToCsv(headers, rows) {
|
|
11
|
+
return toDelimitedText(
|
|
12
|
+
headers,
|
|
13
|
+
rows.map((row) => row.map(cellValue)),
|
|
14
|
+
{ spreadsheetSafe: true }
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
tableToCsv
|
|
19
|
+
};
|
|
@@ -47,6 +47,7 @@ export interface RichContentAppBindings {
|
|
|
47
47
|
MarkdownStream: HostComponent;
|
|
48
48
|
CitationMarkerInline: HostComponent;
|
|
49
49
|
InlineCopyButton: HostComponent;
|
|
50
|
+
CopyButtons: HostComponent;
|
|
50
51
|
AdvancedMenu: HostComponent;
|
|
51
52
|
useAdvancedMenu: HostFn;
|
|
52
53
|
EntityRef: HostComponent;
|
|
@@ -129,6 +130,7 @@ export declare const kindHasRecordChrome: HostFn;
|
|
|
129
130
|
export declare const KindFixItBar: HostComponent;
|
|
130
131
|
export declare const MarkdownStream: HostComponent;
|
|
131
132
|
export declare const CitationMarkerInline: HostComponent;
|
|
133
|
+
export declare const CopyButtons: HostComponent;
|
|
132
134
|
export declare const InlineCopyButton: HostComponent;
|
|
133
135
|
export declare const AdvancedMenu: HostComponent;
|
|
134
136
|
export declare const useAdvancedMenu: HostFn;
|
|
@@ -70,6 +70,7 @@ const kindHasRecordChrome = fn("kindHasRecordChrome", () => false, "kind records
|
|
|
70
70
|
const KindFixItBar = component("KindFixItBar", null, "no fix-it bar on broken kinds");
|
|
71
71
|
const MarkdownStream = component("MarkdownStream", null, "the full renderer is not registered");
|
|
72
72
|
const CitationMarkerInline = component("CitationMarkerInline", TextOnly, "citation markers show as text");
|
|
73
|
+
const CopyButtons = component("CopyButtons", null, "no copy menu on rendered content");
|
|
73
74
|
const InlineCopyButton = component("InlineCopyButton", null, "no inline copy button");
|
|
74
75
|
const AdvancedMenu = component("AdvancedMenu", null, "no block menus");
|
|
75
76
|
const useAdvancedMenu = fn("useAdvancedMenu", () => ({ isOpen: false, open: noop, close: noop, toggle: noop, menuProps: {} }), "no block menus");
|
|
@@ -105,6 +106,7 @@ export {
|
|
|
105
106
|
ArtifactRender,
|
|
106
107
|
CitationMarkerInline,
|
|
107
108
|
CodeBlockWithContextAttach,
|
|
109
|
+
CopyButtons,
|
|
108
110
|
EntityRef,
|
|
109
111
|
IconInputWithValidation,
|
|
110
112
|
InlineCopyButton,
|
package/dist/mermaid/export.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NormalizedFile } from "@ai-matrx/media/files/engine/handler/types";
|
|
2
|
+
/** Throws when the browser refused the copy — callers say so. */
|
|
2
3
|
export declare function copyMermaidSource(source: string): Promise<void>;
|
|
3
4
|
/** Copies the SVG markup as text — the universally pasteable form. */
|
|
4
5
|
export declare function copyMermaidSvg(svg: string): Promise<void>;
|
package/dist/mermaid/export.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { copyText } from "@ai-matrx/kit/clipboard";
|
|
3
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
2
4
|
import { fileHandler } from "@ai-matrx/media/files/engine/handler/handler";
|
|
3
5
|
function svgBlob(svg) {
|
|
4
6
|
return new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
|
|
5
7
|
}
|
|
6
8
|
function triggerDownload(blob, fileName) {
|
|
7
|
-
|
|
8
|
-
const a = document.createElement("a");
|
|
9
|
-
a.href = url;
|
|
10
|
-
a.download = fileName;
|
|
11
|
-
a.click();
|
|
12
|
-
URL.revokeObjectURL(url);
|
|
9
|
+
downloadFile(fileName, blob, blob.type);
|
|
13
10
|
}
|
|
14
11
|
function safeFileName(title, extension) {
|
|
15
12
|
const base = (title || "diagram").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
16
13
|
return `${base || "diagram"}.${extension}`;
|
|
17
14
|
}
|
|
18
15
|
async function copyMermaidSource(source) {
|
|
19
|
-
await
|
|
16
|
+
if (!await copyText(source)) throw new Error("The browser refused the clipboard.");
|
|
20
17
|
}
|
|
21
18
|
async function copyMermaidSvg(svg) {
|
|
22
|
-
await
|
|
19
|
+
if (!await copyText(svg)) throw new Error("The browser refused the clipboard.");
|
|
23
20
|
}
|
|
24
21
|
function downloadMermaidSvg(svg, title) {
|
|
25
22
|
triggerDownload(svgBlob(svg), safeFileName(title, "svg"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/rich-content",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "The AI Matrx formatted-text engine: the one markdown renderer, streaming markdown, the block splitter and renderer, the generic blocks, <RichContent> levels, the rich-document action registry and menus, copy, and the selection toolbar — as portable React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,7 +85,6 @@
|
|
|
85
85
|
"hast-util-sanitize": "^5.0.2",
|
|
86
86
|
"hast-util-to-string": "^3.0.1",
|
|
87
87
|
"hast-util-to-text": "^4.0.2",
|
|
88
|
-
"html2canvas": "^1.4.1",
|
|
89
88
|
"katex": "^0.18.9",
|
|
90
89
|
"lucide-react": "^1.48.0",
|
|
91
90
|
"marked": "^18.0.14",
|
|
@@ -116,19 +115,20 @@
|
|
|
116
115
|
"unist-util-visit": "^5.1.0",
|
|
117
116
|
"unist-util-visit-parents": "^6.0.2",
|
|
118
117
|
"yaml": "^2.9.1",
|
|
119
|
-
"@ai-matrx/
|
|
120
|
-
"@ai-matrx/data": "latest",
|
|
118
|
+
"@ai-matrx/alchemy": "latest",
|
|
121
119
|
"@ai-matrx/content-ir": "latest",
|
|
122
120
|
"@ai-matrx/content-ir-react": "latest",
|
|
123
|
-
"@ai-matrx/
|
|
121
|
+
"@ai-matrx/data": "latest",
|
|
124
122
|
"@ai-matrx/agents": "latest",
|
|
125
|
-
"@ai-matrx/
|
|
123
|
+
"@ai-matrx/design-system": "latest",
|
|
126
124
|
"@ai-matrx/icons": "latest",
|
|
125
|
+
"@ai-matrx/canvas": "latest",
|
|
127
126
|
"@ai-matrx/kit": "latest",
|
|
128
|
-
"@ai-matrx/media": "latest",
|
|
129
127
|
"@ai-matrx/print": "latest",
|
|
130
128
|
"@ai-matrx/records": "latest",
|
|
131
|
-
"@ai-matrx/
|
|
129
|
+
"@ai-matrx/media": "latest",
|
|
130
|
+
"@ai-matrx/tap-target": "latest",
|
|
131
|
+
"@ai-matrx/diff": "latest"
|
|
132
132
|
},
|
|
133
133
|
"peerDependencies": {
|
|
134
134
|
"react": ">=19.0.0",
|