@ai-matrx/rich-content 0.2.165 → 0.2.167
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 +14 -0
- package/dist/code-block/CodeBlock.js +6 -2
- package/dist/code-block/CodeBlockHeader.d.ts +2 -0
- package/dist/code-block/CodeBlockHeader.js +7 -3
- package/dist/display/block-host.d.ts +40 -0
- package/dist/display/block-host.js +40 -0
- package/dist/display/blocks/ChatCollapsibleWrapper.js +8 -3
- package/dist/display/blocks/chart/ChartBlock.js +44 -29
- package/dist/display/blocks/common/BlockHeaderWrapper.js +5 -2
- package/dist/display/blocks/common/ContentBlockWrapper.js +6 -3
- package/dist/display/blocks/csv/CsvBlock.js +6 -2
- package/dist/display/blocks/diagram/InteractiveDiagramBlock.js +5 -2
- package/dist/display/blocks/diff/DiffBlock.js +10 -5
- package/dist/display/blocks/json/JsonBlock.js +10 -4
- package/dist/display/blocks/markdown-preview/MarkdownPreviewBlock.js +6 -2
- package/dist/display/blocks/mermaid/MermaidBlock.js +119 -113
- package/dist/display/blocks/svg/SvgBlock.js +11 -6
- package/dist/display/blocks/toml/TomlBlock.js +8 -2
- package/dist/display/blocks/tree/TreeBlock.js +8 -2
- package/dist/display/blocks/xml/XmlBlock.js +6 -2
- package/dist/display/blocks/yaml/YamlBlock.js +8 -2
- package/dist/rich-document/variants/shared/AlchemyDocumentMenu.js +1 -36
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.167
|
|
4
|
+
|
|
5
|
+
UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
|
|
6
|
+
Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/rich-content/src`.
|
|
7
|
+
|
|
8
|
+
## 0.2.166
|
|
9
|
+
|
|
10
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
11
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
12
|
+
Diff: `git diff 6a0868df3e c3135c57b9 -- apps/shared/rich-content/src`
|
|
13
|
+
|
|
14
|
+
Changed shipped source files (1):
|
|
15
|
+
- `apps/shared/rich-content/src/rich-document/variants/shared/AlchemyDocumentMenu.tsx`
|
|
16
|
+
|
|
3
17
|
## 0.2.165
|
|
4
18
|
### Unreleased
|
|
5
19
|
|
|
@@ -22,6 +22,7 @@ import { ShikiCodeView } from "./highlight/ShikiCodeView.js";
|
|
|
22
22
|
import { parseFenceMeta } from "@ai-matrx/content-ir/source";
|
|
23
23
|
import { agentForPromptKey } from "../utils/lifted/code-editor-agents.js";
|
|
24
24
|
import { useOpenSmartCodeEditorWindow } from "../host/app-bindings.js";
|
|
25
|
+
import { useBlockHostSlot } from "../display/block-host.js";
|
|
25
26
|
import {
|
|
26
27
|
normalizeViewLanguage,
|
|
27
28
|
mapLanguageForMonaco,
|
|
@@ -74,6 +75,7 @@ const CodeBlockBody = ({
|
|
|
74
75
|
setEditedCode(initialCode);
|
|
75
76
|
}
|
|
76
77
|
const [isCopied, setIsCopied] = useState(false);
|
|
78
|
+
const host = useBlockHostSlot();
|
|
77
79
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
78
80
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
79
81
|
const [isEditing, setIsEditing] = useState(false);
|
|
@@ -241,7 +243,8 @@ const CodeBlockBody = ({
|
|
|
241
243
|
{
|
|
242
244
|
ref: containerRef,
|
|
243
245
|
className: cn(
|
|
244
|
-
"w-full my-4 rounded-t-xl rounded-b-lg border
|
|
246
|
+
"w-full my-4 rounded-t-xl rounded-b-lg border transition-colors",
|
|
247
|
+
host.selected ? "border-primary" : "border-neutral-200 dark:border-neutral-700",
|
|
245
248
|
// `overflow-clip` (NOT `overflow-hidden`) so this box still clips to
|
|
246
249
|
// its rounded corners without becoming a scroll container — an
|
|
247
250
|
// overflow-hidden ancestor would trap StickyButtons' `position:
|
|
@@ -287,7 +290,8 @@ const CodeBlockBody = ({
|
|
|
287
290
|
allowEdit,
|
|
288
291
|
customBuiltinKeys,
|
|
289
292
|
headerLeftSlot,
|
|
290
|
-
extraMenuItems
|
|
293
|
+
extraMenuItems,
|
|
294
|
+
headerRightSlot: host.actions
|
|
291
295
|
}
|
|
292
296
|
),
|
|
293
297
|
showStickyButtons && /* @__PURE__ */ jsx(
|
|
@@ -54,6 +54,8 @@ interface CodeBlockHeaderProps {
|
|
|
54
54
|
* (e.g. "Data" from JsonBlock) appear as their own section.
|
|
55
55
|
*/
|
|
56
56
|
extraMenuItems?: CodeBlockMenuItem[];
|
|
57
|
+
/** Rendered after the kebab: a host's own tools for this block (the editor's "</>", Remove). */
|
|
58
|
+
headerRightSlot?: React.ReactNode;
|
|
57
59
|
}
|
|
58
60
|
export declare const CodeBlockHeader: React.FC<CodeBlockHeaderProps>;
|
|
59
61
|
export declare const EditButton: ({ isEditing, toggleEdit, }: {
|
|
@@ -67,7 +67,8 @@ const CodeBlockHeader = ({
|
|
|
67
67
|
allowEdit = true,
|
|
68
68
|
customBuiltinKeys = [],
|
|
69
69
|
headerLeftSlot,
|
|
70
|
-
extraMenuItems
|
|
70
|
+
extraMenuItems,
|
|
71
|
+
headerRightSlot
|
|
71
72
|
}) => {
|
|
72
73
|
const canCollapse = linesCount > 5;
|
|
73
74
|
return /* @__PURE__ */ jsxs(
|
|
@@ -143,7 +144,8 @@ const CodeBlockHeader = ({
|
|
|
143
144
|
isCompleteHTML,
|
|
144
145
|
handleViewHTML,
|
|
145
146
|
isCreatingPage,
|
|
146
|
-
extraMenuItems
|
|
147
|
+
extraMenuItems,
|
|
148
|
+
headerRightSlot
|
|
147
149
|
}
|
|
148
150
|
)
|
|
149
151
|
]
|
|
@@ -183,7 +185,8 @@ const CodeBlockButtons = ({
|
|
|
183
185
|
isCompleteHTML = false,
|
|
184
186
|
handleViewHTML,
|
|
185
187
|
isCreatingPage = false,
|
|
186
|
-
extraMenuItems
|
|
188
|
+
extraMenuItems,
|
|
189
|
+
headerRightSlot
|
|
187
190
|
}) => {
|
|
188
191
|
const menu = useAdvancedMenu();
|
|
189
192
|
const kebabRef = useRef(null);
|
|
@@ -395,6 +398,7 @@ const CodeBlockButtons = ({
|
|
|
395
398
|
}
|
|
396
399
|
}
|
|
397
400
|
) }),
|
|
401
|
+
headerRightSlot,
|
|
398
402
|
/* @__PURE__ */ jsx(
|
|
399
403
|
AdvancedMenu,
|
|
400
404
|
{
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE BLOCK HOST SLOT — one frame per block (Arman, 2026-10-14: "create a nice and clean wrapper
|
|
3
|
+
* that replaces whatever the components already have or you don't have any type of visible
|
|
4
|
+
* wrapper or anything that takes up space").
|
|
5
|
+
*
|
|
6
|
+
* A host that draws a rendered block with tools of its own (the rich editor's "</>", Remove,
|
|
7
|
+
* drag) never wraps the block in a second box. It provides `BlockHostProvider`; a block whose
|
|
8
|
+
* card has a header row calls `useBlockHostSlot()` and draws its `actions` among its own header
|
|
9
|
+
* buttons, so the host's tools join that row, and shows `selected` as its own border colour.
|
|
10
|
+
* A block that claims no slot leaves the host to draw a small overlay inside the block's corner
|
|
11
|
+
* (the provider's `onClaimedChange` reports false).
|
|
12
|
+
*
|
|
13
|
+
* Only the outermost block that claims the slot gets it: a block nested inside another block's
|
|
14
|
+
* render (markdown inside a card) draws nothing, so the tools appear once. With no host (Read, chat),
|
|
15
|
+
* every export here renders nothing and changes nothing.
|
|
16
|
+
*/
|
|
17
|
+
import { type ReactNode } from "react";
|
|
18
|
+
export interface BlockHostProviderProps {
|
|
19
|
+
/** The host's own tools for this block, drawn in the block's header row. */
|
|
20
|
+
renderActions: () => ReactNode;
|
|
21
|
+
/** The block is selected in its host; the block shows it on its own border. */
|
|
22
|
+
selected?: boolean;
|
|
23
|
+
/** Told whether a header in the block took the tools (false → the host draws its overlay). */
|
|
24
|
+
onClaimedChange?: (claimed: boolean) => void;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare function BlockHostProvider({ renderActions, selected, onClaimedChange, children }: BlockHostProviderProps): import("react").JSX.Element;
|
|
28
|
+
export interface BlockHostSlot {
|
|
29
|
+
/** The host's tools for the block's header row; null without a host or for a nested block. */
|
|
30
|
+
actions: ReactNode;
|
|
31
|
+
/** The host has this block selected: show it as the block's own border colour. */
|
|
32
|
+
selected: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Claims the host slot for a block's header. Call it in the component that draws the card;
|
|
36
|
+
* `enabled: false` while that component draws no header of its own (a child block does).
|
|
37
|
+
*/
|
|
38
|
+
export declare function useBlockHostSlot(enabled?: boolean): BlockHostSlot;
|
|
39
|
+
/** The host's tools as an element, for a header that needs nothing else from the slot. */
|
|
40
|
+
export declare function BlockHostActions(): import("react").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
const BlockHostContext = createContext(null);
|
|
5
|
+
let nextClaimId = 1;
|
|
6
|
+
function BlockHostProvider({ renderActions, selected = false, onClaimedChange, children }) {
|
|
7
|
+
const [claimants, setClaimants] = useState([]);
|
|
8
|
+
const owner = claimants.length ? Math.min(...claimants) : null;
|
|
9
|
+
const report = useRef(onClaimedChange);
|
|
10
|
+
report.current = onClaimedChange;
|
|
11
|
+
const claim = useMemo(
|
|
12
|
+
() => (id) => {
|
|
13
|
+
setClaimants((current) => current.includes(id) ? current : [...current, id]);
|
|
14
|
+
return () => setClaimants((current) => current.filter((other) => other !== id));
|
|
15
|
+
},
|
|
16
|
+
[]
|
|
17
|
+
);
|
|
18
|
+
useLayoutEffect(() => {
|
|
19
|
+
report.current?.(owner !== null);
|
|
20
|
+
}, [owner]);
|
|
21
|
+
const value = useMemo(() => ({ renderActions, selected, owner, claim }), [renderActions, selected, owner, claim]);
|
|
22
|
+
return /* @__PURE__ */ jsx(BlockHostContext.Provider, { value, children });
|
|
23
|
+
}
|
|
24
|
+
function useBlockHostSlot(enabled = true) {
|
|
25
|
+
const host = useContext(BlockHostContext);
|
|
26
|
+
const id = useRef(0);
|
|
27
|
+
if (id.current === 0) id.current = nextClaimId++;
|
|
28
|
+
const claim = enabled ? host?.claim : void 0;
|
|
29
|
+
useLayoutEffect(() => claim ? claim(id.current) : void 0, [claim]);
|
|
30
|
+
if (!host || host.owner !== id.current) return { actions: null, selected: false };
|
|
31
|
+
return { actions: host.renderActions(), selected: host.selected };
|
|
32
|
+
}
|
|
33
|
+
function BlockHostActions() {
|
|
34
|
+
return /* @__PURE__ */ jsx(Fragment, { children: useBlockHostSlot().actions });
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
BlockHostActions,
|
|
38
|
+
BlockHostProvider,
|
|
39
|
+
useBlockHostSlot
|
|
40
|
+
};
|
|
@@ -4,6 +4,7 @@ import { useState } from "react";
|
|
|
4
4
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@ai-matrx/design-system";
|
|
5
5
|
import { ChevronDown } from "lucide-react";
|
|
6
6
|
import { ControlRow } from "@ai-matrx/design-system/controls";
|
|
7
|
+
import { useBlockHostSlot } from "../block-host.js";
|
|
7
8
|
const ChatCollapsibleWrapper = ({
|
|
8
9
|
icon,
|
|
9
10
|
title,
|
|
@@ -15,6 +16,7 @@ const ChatCollapsibleWrapper = ({
|
|
|
15
16
|
maxWidth = "max-w-5xl"
|
|
16
17
|
}) => {
|
|
17
18
|
const [isOpen, setIsOpen] = useState(initialOpen);
|
|
19
|
+
const host = useBlockHostSlot();
|
|
18
20
|
const handleOpenChange = (open) => {
|
|
19
21
|
setIsOpen(open);
|
|
20
22
|
if (onStateChange) {
|
|
@@ -26,7 +28,7 @@ const ChatCollapsibleWrapper = ({
|
|
|
26
28
|
{
|
|
27
29
|
open: isOpen,
|
|
28
30
|
onOpenChange: handleOpenChange,
|
|
29
|
-
className: `border-2 border-zinc-200 dark:border-zinc-700 rounded-3xl shadow-sm my-1 w-full ${maxWidth} mx-auto hover:bg-accent/50 hover:shadow-sm ${className}`,
|
|
31
|
+
className: `border-2 ${host.selected ? "border-primary" : "border-zinc-200 dark:border-zinc-700"} transition-colors rounded-3xl shadow-sm my-1 w-full ${maxWidth} mx-auto hover:bg-accent/50 hover:shadow-sm ${className}`,
|
|
30
32
|
children: [
|
|
31
33
|
/* @__PURE__ */ jsxs("div", { className: "relative flex w-full items-center justify-between rounded-t-lg hover:bg-accent/50 hover:shadow-sm", children: [
|
|
32
34
|
/* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "flex-1 flex items-center gap-2 py-1 pl-4 pr-1 font-medium hover:bg-accent/50 hover:shadow-sm", children: [
|
|
@@ -39,8 +41,11 @@ const ChatCollapsibleWrapper = ({
|
|
|
39
41
|
}
|
|
40
42
|
)
|
|
41
43
|
] }),
|
|
42
|
-
controls && // One ControlRow: every action carries its own 3px half-gap, the row adds none.
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
+
(controls || host.actions) && // One ControlRow: every action carries its own 3px half-gap, the row adds none.
|
|
45
|
+
/* @__PURE__ */ jsxs(ControlRow, { nowrap: true, className: "shrink-0 pr-3", onClick: (e) => e.stopPropagation(), children: [
|
|
46
|
+
controls,
|
|
47
|
+
host.actions
|
|
48
|
+
] })
|
|
44
49
|
] }),
|
|
45
50
|
/* @__PURE__ */ jsx(CollapsibleContent, { className: "bg-transparent dark:bg-transparent border-none", children: /* @__PURE__ */ jsxs("div", { className: "relative p-0 border-none", children: [
|
|
46
51
|
/* @__PURE__ */ jsx("div", { className: "absolute top-0 left-8 right-8 h-px bg-zinc-200 dark:bg-zinc-700" }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
4
|
import { useEffect, useMemo, useState } from "react";
|
|
5
5
|
import { clientLazy } from "../../../utils/client-lazy.js";
|
|
@@ -11,6 +11,7 @@ import { cn } from "@ai-matrx/design-system";
|
|
|
11
11
|
import { parseChartSpec } from "./chart-spec.js";
|
|
12
12
|
import { useCanvasPresentation } from "@ai-matrx/canvas/react";
|
|
13
13
|
import { chartPaneLayout } from "../canvas-adaptive.js";
|
|
14
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
14
15
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
15
16
|
const CHART_TYPE_LABEL = {
|
|
16
17
|
bar: "Bar chart",
|
|
@@ -35,40 +36,54 @@ const ChartBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
35
36
|
const [showSource, setShowSource] = useState(false);
|
|
36
37
|
const title = spec?.title ?? (spec ? CHART_TYPE_LABEL[spec.type] ?? "Chart" : "Chart");
|
|
37
38
|
const presentation = useCanvasPresentation();
|
|
39
|
+
const host = useBlockHostSlot();
|
|
38
40
|
const paneLayout = chartPaneLayout(presentation, spec?.type ?? "");
|
|
39
41
|
const handleCopy = async () => {
|
|
40
42
|
if (!await copyText(source, void 0, "Couldn't copy the chart source")) return;
|
|
41
43
|
setCopied(true);
|
|
42
44
|
setTimeout(() => setCopied(false), 1500);
|
|
43
45
|
};
|
|
44
|
-
return /* @__PURE__ */ jsxs(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
46
|
+
return /* @__PURE__ */ jsxs(
|
|
47
|
+
"div",
|
|
48
|
+
{
|
|
49
|
+
className: cn(
|
|
50
|
+
"my-3 overflow-hidden rounded-lg border bg-card transition-colors",
|
|
51
|
+
host.selected ? "border-primary" : "border-border",
|
|
52
|
+
className
|
|
53
|
+
),
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
|
|
56
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
57
|
+
/* @__PURE__ */ jsx(BarChart3, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: title }),
|
|
59
|
+
spec && /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-xs text-muted-foreground sm:inline", children: CHART_TYPE_LABEL[spec.type] ?? "Chart" }),
|
|
60
|
+
isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "building\u2026" })
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
63
|
+
!isStreamActive && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
64
|
+
/* @__PURE__ */ jsx(IconBtn, { label: copied ? "Copied" : "Copy chart source", onClick: handleCopy, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
|
|
65
|
+
spec && presentation === null && /* @__PURE__ */ jsx(IconBtn, { label: "View fullscreen", onClick: () => setFullscreen(true), children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
|
|
66
|
+
] }),
|
|
67
|
+
host.actions
|
|
68
|
+
] })
|
|
69
|
+
] }),
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "p-3", children: isStreamActive ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", "aria-busy": "true", children: [
|
|
71
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/3" }),
|
|
72
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-48 w-full" })
|
|
73
|
+
] }) : error ? /* @__PURE__ */ jsx(ChartError, { error, source, show: showSource, onToggle: () => setShowSource((v) => !v) }) : spec ? /* @__PURE__ */ jsx(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
className: paneLayout ? "w-full" : "h-[340px] w-full",
|
|
77
|
+
style: paneLayout ? { height: paneLayout.height } : void 0,
|
|
78
|
+
"data-chart-legend": paneLayout?.legend ?? "default",
|
|
79
|
+
"data-chart-bars": paneLayout?.horizontalBars ? "horizontal" : "vertical",
|
|
80
|
+
children: /* @__PURE__ */ jsx(ChartCanvas, { spec, paneLayout })
|
|
81
|
+
}
|
|
82
|
+
) : null }),
|
|
83
|
+
fullscreen && spec && /* @__PURE__ */ jsx(ChartFullscreen, { spec, title, onClose: () => setFullscreen(false) })
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
);
|
|
72
87
|
};
|
|
73
88
|
function IconBtn({ label, onClick, children }) {
|
|
74
89
|
return /* @__PURE__ */ jsx(Button, { variant: "quiet", "aria-label": label, title: label, onClick, children });
|
|
@@ -20,6 +20,7 @@ import IconButton from "../../../utils/lifted/IconButton.js";
|
|
|
20
20
|
import { AdvancedMenu } from "../../../host/app-bindings.js";
|
|
21
21
|
import { useIsMobile } from "@ai-matrx/kit/media-query";
|
|
22
22
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
23
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
23
24
|
const BlockHeaderWrapper = ({
|
|
24
25
|
icon: Icon,
|
|
25
26
|
iconClassName = "bg-blue-500 dark:bg-blue-600",
|
|
@@ -42,6 +43,7 @@ const BlockHeaderWrapper = ({
|
|
|
42
43
|
canvasButtonClassName = "bg-brand-secondary text-brand-secondary-foreground shadow-sm hover:bg-brand-secondary/80 hover:shadow-md transform hover:scale-105 transition-all"
|
|
43
44
|
}) => {
|
|
44
45
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
46
|
+
const host = useBlockHostSlot();
|
|
45
47
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
46
48
|
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
|
|
47
49
|
const menuAnchorRef = useRef(null);
|
|
@@ -170,7 +172,7 @@ const BlockHeaderWrapper = ({
|
|
|
170
172
|
/* @__PURE__ */ jsxs(
|
|
171
173
|
"div",
|
|
172
174
|
{
|
|
173
|
-
className: `${headerGradient} rounded-xl p-2 shadow-md border ${headerBorder}`,
|
|
175
|
+
className: `${headerGradient} rounded-xl p-2 shadow-md border transition-colors ${host.selected ? "border-primary" : headerBorder}`,
|
|
174
176
|
children: [
|
|
175
177
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [
|
|
176
178
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 flex-1", children: [
|
|
@@ -282,7 +284,8 @@ const BlockHeaderWrapper = ({
|
|
|
282
284
|
categorizeItems: false
|
|
283
285
|
}
|
|
284
286
|
)
|
|
285
|
-
] })
|
|
287
|
+
] }),
|
|
288
|
+
host.actions
|
|
286
289
|
] })
|
|
287
290
|
] }),
|
|
288
291
|
controls && /* @__PURE__ */ jsx("div", { children: controls })
|
|
@@ -21,6 +21,7 @@ import IconButton from "../../../utils/lifted/IconButton.js";
|
|
|
21
21
|
import { toast } from "../../../host/toast.js";
|
|
22
22
|
import { reportCanvasOpenDrop } from "../../../host/app-bindings.js";
|
|
23
23
|
import { ErrorAlchemyMenu } from "../../../host/ErrorActions.js";
|
|
24
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
24
25
|
const ContentBlockWrapper = ({
|
|
25
26
|
children,
|
|
26
27
|
title,
|
|
@@ -46,6 +47,7 @@ const ContentBlockWrapper = ({
|
|
|
46
47
|
closeOnEscape = true
|
|
47
48
|
}) => {
|
|
48
49
|
const [isFullScreen, setIsFullScreen] = useState(defaultFullscreen);
|
|
50
|
+
const host = useBlockHostSlot();
|
|
49
51
|
const { open: openCanvas } = useCanvas();
|
|
50
52
|
const { openArtifact } = useOpenArtifactInCanvas();
|
|
51
53
|
const { isCanvasAvailable } = useCanvasOpenGuard();
|
|
@@ -161,7 +163,7 @@ const ContentBlockWrapper = ({
|
|
|
161
163
|
}
|
|
162
164
|
) }),
|
|
163
165
|
/* @__PURE__ */ jsxs("div", { className: isFullScreen ? "flex-1 overflow-y-auto p-3" : "p-3", children: [
|
|
164
|
-
(title || subtitle || headerContent || actionButtons.length > 0) && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
166
|
+
(title || subtitle || headerContent || actionButtons.length > 0 || host.actions) && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
165
167
|
(title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
|
|
166
168
|
title && /* @__PURE__ */ jsx(
|
|
167
169
|
"h1",
|
|
@@ -173,7 +175,7 @@ const ContentBlockWrapper = ({
|
|
|
173
175
|
subtitle && /* @__PURE__ */ jsx("div", { className: "text-center mt-2", children: /* @__PURE__ */ jsx("span", { className: "text-sm bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 px-3 py-1 rounded-full", children: subtitle }) })
|
|
174
176
|
] }),
|
|
175
177
|
headerContent,
|
|
176
|
-
actionButtons.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 flex-wrap", children: [
|
|
178
|
+
(actionButtons.length > 0 || host.actions) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 flex-wrap", children: [
|
|
177
179
|
onSave && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mr-2", children: [
|
|
178
180
|
isSaving && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 text-blue-600 dark:text-blue-400 text-xs", title: "Saving...", children: /* @__PURE__ */ jsx(Cloud, { className: "h-4 w-4 animate-pulse" }) }),
|
|
179
181
|
!isSaving && lastSaved && /* @__PURE__ */ jsx(
|
|
@@ -199,7 +201,8 @@ const ContentBlockWrapper = ({
|
|
|
199
201
|
className: action.className
|
|
200
202
|
},
|
|
201
203
|
index
|
|
202
|
-
))
|
|
204
|
+
)),
|
|
205
|
+
host.actions
|
|
203
206
|
] })
|
|
204
207
|
] }),
|
|
205
208
|
/* @__PURE__ */ jsx("div", { className: contentClassName, children })
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { toast } from "../../../host/toast.js";
|
|
19
19
|
import { useOpenSaveToTable } from "../../../host/tables.js";
|
|
20
20
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
21
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
21
22
|
function parseCsv(content, delimiter) {
|
|
22
23
|
return parseDelimited(content, { delimiter, skipEmptyLines: "greedy" }).data.map(
|
|
23
24
|
(row) => row.map((cell) => cell.trim())
|
|
@@ -36,6 +37,7 @@ const CsvBlock = ({
|
|
|
36
37
|
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
37
38
|
});
|
|
38
39
|
const [copied, setCopied] = useState(false);
|
|
40
|
+
const host = useBlockHostSlot();
|
|
39
41
|
const [sortCol, setSortCol] = useState(null);
|
|
40
42
|
const [sortDir, setSortDir] = useState(null);
|
|
41
43
|
const [editCell, setEditCell] = useState(
|
|
@@ -116,7 +118,8 @@ const CsvBlock = ({
|
|
|
116
118
|
"div",
|
|
117
119
|
{
|
|
118
120
|
className: cn(
|
|
119
|
-
"my-3 rounded-lg border
|
|
121
|
+
"my-3 rounded-lg border bg-card overflow-hidden transition-colors",
|
|
122
|
+
host.selected ? "border-primary" : "border-border",
|
|
120
123
|
className
|
|
121
124
|
),
|
|
122
125
|
children: [
|
|
@@ -132,7 +135,8 @@ const CsvBlock = ({
|
|
|
132
135
|
] }),
|
|
133
136
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
134
137
|
openSaveToTable ? /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Table2, {}), onClick: handleSaveAsTable, title: "Save to a table", children: /* @__PURE__ */ jsx("span", { children: "Save to a table" }) }) : null,
|
|
135
|
-
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
|
|
138
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
|
|
139
|
+
host.actions
|
|
136
140
|
] })
|
|
137
141
|
] }),
|
|
138
142
|
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm border-collapse", children: [
|
|
@@ -123,6 +123,7 @@ import {
|
|
|
123
123
|
DropdownMenuSeparator,
|
|
124
124
|
DropdownMenuTrigger
|
|
125
125
|
} from "@ai-matrx/design-system";
|
|
126
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
126
127
|
function backgroundVariantFromDiagram(diagram) {
|
|
127
128
|
if (diagram.renderHints?.background === "lines")
|
|
128
129
|
return BackgroundVariant.Lines;
|
|
@@ -2056,6 +2057,7 @@ const InteractiveDiagramBlock = ({
|
|
|
2056
2057
|
}) => {
|
|
2057
2058
|
const isWorkspace = presentation === "workspace";
|
|
2058
2059
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
2060
|
+
const host = useBlockHostSlot(!isWorkspace);
|
|
2059
2061
|
const [editing, setEditing] = useState(defaultEditing);
|
|
2060
2062
|
const canEdit = Boolean(onDiagramChange);
|
|
2061
2063
|
const [cardShowMiniMap, setCardShowMiniMap] = useState(false);
|
|
@@ -2179,7 +2181,7 @@ const InteractiveDiagramBlock = ({
|
|
|
2179
2181
|
// own <Controls/> (Zoom in/out, Fit view) and the hand-rolled
|
|
2180
2182
|
// Arrange-map/Restore-layout/background/minimap/export buttons
|
|
2181
2183
|
// below all render at 26-34px, well under the iOS floor.
|
|
2182
|
-
isWorkspace ? "relative h-full min-h-0 w-full overflow-hidden bg-background matrx-touch-targets" : `w-full rounded-xl border border-border matrx-touch-targets ${isFullScreen ? "fixed inset-0 z-50 flex items-center justify-center p-2" : "py-2"}`
|
|
2184
|
+
isWorkspace ? "relative h-full min-h-0 w-full overflow-hidden bg-background matrx-touch-targets" : `w-full rounded-xl border transition-colors ${host.selected ? "border-primary" : "border-border"} matrx-touch-targets ${isFullScreen ? "fixed inset-0 z-50 flex items-center justify-center p-2" : "py-2"}`
|
|
2183
2185
|
),
|
|
2184
2186
|
children: /* @__PURE__ */ jsxs(
|
|
2185
2187
|
"div",
|
|
@@ -2299,7 +2301,8 @@ const InteractiveDiagramBlock = ({
|
|
|
2299
2301
|
onClick: exportDiagramJSON,
|
|
2300
2302
|
variant: "outline"
|
|
2301
2303
|
}
|
|
2302
|
-
)
|
|
2304
|
+
),
|
|
2305
|
+
host.actions
|
|
2303
2306
|
] })
|
|
2304
2307
|
] }) }),
|
|
2305
2308
|
isWorkspace && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute bottom-3 left-1/2 z-20 -translate-x-1/2", children: /* @__PURE__ */ jsxs("div", { className: "pointer-events-auto flex items-center gap-1 rounded-xl border border-border/70 bg-card/90 p-1 shadow-lg backdrop-blur-xl", children: [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
4
|
import { useMemo, useState } from "react";
|
|
5
5
|
import { clientLazy } from "../../../utils/client-lazy.js";
|
|
@@ -13,6 +13,7 @@ import { ErrorAlchemyMenu } from "../../../host/ErrorActions.js";
|
|
|
13
13
|
import { useCanvasPresentation } from "@ai-matrx/canvas/react";
|
|
14
14
|
import { diffIsSplit } from "../canvas-adaptive.js";
|
|
15
15
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
16
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
16
17
|
const DiffCanvas = clientLazy(() => import("./DiffCanvas.js"), {
|
|
17
18
|
loading: () => /* @__PURE__ */ jsx(Skeleton, { className: "h-40 w-full" })
|
|
18
19
|
});
|
|
@@ -75,6 +76,7 @@ const DiffSpecBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
75
76
|
const spec = parsed && !("error" in parsed) ? parsed : null;
|
|
76
77
|
const error = parsed && "error" in parsed ? parsed.error : null;
|
|
77
78
|
const [copied, setCopied] = useState(false);
|
|
79
|
+
const host = useBlockHostSlot();
|
|
78
80
|
const [split, setSplit] = useState(null);
|
|
79
81
|
const presentation = useCanvasPresentation();
|
|
80
82
|
const effectiveSplit = diffIsSplit({
|
|
@@ -88,16 +90,19 @@ const DiffSpecBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
88
90
|
setCopied(true);
|
|
89
91
|
setTimeout(() => setCopied(false), 1500);
|
|
90
92
|
};
|
|
91
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border
|
|
93
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border bg-card transition-colors", host.selected ? "border-primary" : "border-border", className), children: [
|
|
92
94
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
|
|
93
95
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
94
96
|
/* @__PURE__ */ jsx(GitCompareArrows, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
|
|
95
97
|
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: spec?.title ?? "Changes" }),
|
|
96
98
|
isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "\u2026" })
|
|
97
99
|
] }),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
101
|
+
!isStreamActive && spec && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
102
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: effectiveSplit ? /* @__PURE__ */ jsx(Rows3, {}) : /* @__PURE__ */ jsx(Columns2, {}), "aria-label": effectiveSplit ? "Unified view" : "Split view", title: effectiveSplit ? "Unified view" : "Split view", onClick: () => setSplit(!effectiveSplit) }),
|
|
103
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), "aria-label": copied ? "Copied" : "Copy source", title: copied ? "Copied" : "Copy source", onClick: handleCopy })
|
|
104
|
+
] }),
|
|
105
|
+
host.actions
|
|
101
106
|
] })
|
|
102
107
|
] }),
|
|
103
108
|
/* @__PURE__ */ jsx("div", { className: "max-h-[60dvh] overflow-auto", children: isStreamActive ? /* @__PURE__ */ jsx("div", { className: "p-3", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-40 w-full" }) }) : error ? /* @__PURE__ */ jsxs("div", { className: "m-3 flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm", children: [
|
|
@@ -58,6 +58,7 @@ const JsonTableView = lazy(
|
|
|
58
58
|
() => import("./JsonTableView.js")
|
|
59
59
|
);
|
|
60
60
|
import { JsonTreeViewer, RawJsonExplorer } from "../../../host/app-bindings.js";
|
|
61
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
61
62
|
function PaneFallback({ label = "Loading\u2026" }) {
|
|
62
63
|
return /* @__PURE__ */ jsx("div", { className: "p-4 text-xs text-muted-foreground italic", children: label });
|
|
63
64
|
}
|
|
@@ -85,6 +86,7 @@ const JsonBlock = ({
|
|
|
85
86
|
const openConvertToShape = useOpenConvertToShapeWindow();
|
|
86
87
|
const routedAbove = useContext(JsonKindRouteContext);
|
|
87
88
|
const [mode, setMode] = useState("code");
|
|
89
|
+
const host = useBlockHostSlot(mode !== "code");
|
|
88
90
|
const openSaveToTable = useOpenSaveToTable();
|
|
89
91
|
const [formatOverride, setFormatOverride] = useState(null);
|
|
90
92
|
const effectiveContent = formatOverride ?? content;
|
|
@@ -369,7 +371,8 @@ const JsonBlock = ({
|
|
|
369
371
|
"div",
|
|
370
372
|
{
|
|
371
373
|
className: cn(
|
|
372
|
-
"w-full my-4 rounded-xl overflow-hidden border
|
|
374
|
+
"w-full my-4 rounded-xl overflow-hidden border bg-background transition-colors",
|
|
375
|
+
host.selected ? "border-primary" : "border-neutral-200 dark:border-neutral-700",
|
|
373
376
|
className
|
|
374
377
|
),
|
|
375
378
|
children: [
|
|
@@ -379,7 +382,8 @@ const JsonBlock = ({
|
|
|
379
382
|
toggle: viewToggle,
|
|
380
383
|
menuItems: fullMenuItems,
|
|
381
384
|
caption: mode === "table" ? tabularCaption || "" : "",
|
|
382
|
-
content: effectiveContent
|
|
385
|
+
content: effectiveContent,
|
|
386
|
+
hostActions: host.actions
|
|
383
387
|
}
|
|
384
388
|
),
|
|
385
389
|
/* @__PURE__ */ jsxs("div", { className: "bg-card", children: [
|
|
@@ -479,7 +483,8 @@ const JsonViewHeader = ({
|
|
|
479
483
|
toggle,
|
|
480
484
|
menuItems,
|
|
481
485
|
caption,
|
|
482
|
-
content
|
|
486
|
+
content,
|
|
487
|
+
hostActions
|
|
483
488
|
}) => {
|
|
484
489
|
const { copyText } = useClipboard({
|
|
485
490
|
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
@@ -546,7 +551,8 @@ const JsonViewHeader = ({
|
|
|
546
551
|
width: "260px",
|
|
547
552
|
maxWidth: "300px"
|
|
548
553
|
}
|
|
549
|
-
)
|
|
554
|
+
),
|
|
555
|
+
hostActions
|
|
550
556
|
] })
|
|
551
557
|
] });
|
|
552
558
|
};
|
|
@@ -8,6 +8,7 @@ import { Copy, Check, Eye, Code2, FileText } from "lucide-react";
|
|
|
8
8
|
import { NestedRichContent } from "../../../levels/standard/NestedRichContent.js";
|
|
9
9
|
import MatrxMiniLoader from "../../../utils/lifted/MatrxMiniLoader.js";
|
|
10
10
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
11
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
11
12
|
const CodeBlock = lazy(
|
|
12
13
|
() => import("../../../code-block/CodeBlock.js")
|
|
13
14
|
);
|
|
@@ -23,6 +24,7 @@ const MarkdownPreviewBlock = ({
|
|
|
23
24
|
});
|
|
24
25
|
const [mode, setMode] = useState("preview");
|
|
25
26
|
const [copied, setCopied] = useState(false);
|
|
27
|
+
const host = useBlockHostSlot();
|
|
26
28
|
const handleCopy = async () => {
|
|
27
29
|
if (!await copyText(content)) return;
|
|
28
30
|
setCopied(true);
|
|
@@ -32,7 +34,8 @@ const MarkdownPreviewBlock = ({
|
|
|
32
34
|
"div",
|
|
33
35
|
{
|
|
34
36
|
className: cn(
|
|
35
|
-
"my-3 rounded-lg border
|
|
37
|
+
"my-3 rounded-lg border bg-card overflow-hidden transition-colors",
|
|
38
|
+
host.selected ? "border-primary" : "border-border",
|
|
36
39
|
className
|
|
37
40
|
),
|
|
38
41
|
children: [
|
|
@@ -46,7 +49,8 @@ const MarkdownPreviewBlock = ({
|
|
|
46
49
|
/* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: mode === "preview", icon: /* @__PURE__ */ jsx(Eye, {}), onClick: () => setMode("preview"), children: "Preview" }),
|
|
47
50
|
/* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: mode === "source", icon: /* @__PURE__ */ jsx(Code2, {}), onClick: () => setMode("source"), children: "Source" })
|
|
48
51
|
] }),
|
|
49
|
-
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" })
|
|
52
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
|
|
53
|
+
host.actions
|
|
50
54
|
] })
|
|
51
55
|
] }),
|
|
52
56
|
mode === "preview" ? /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: renderedPreview && !isStreamActive ? renderedPreview : /* @__PURE__ */ jsx(NestedRichContent, { source: content, isStreaming: isStreamActive }) }) : /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(MatrxMiniLoader, {}), children: /* @__PURE__ */ jsx(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
4
4
|
import { Suspense, lazy, useEffect, useState } from "react";
|
|
5
5
|
import {
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
resolveMermaidTheme,
|
|
55
55
|
DEFAULT_MERMAID_PREFERENCES
|
|
56
56
|
} from "../../../mermaid/types.js";
|
|
57
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
57
58
|
const CodeBlock = lazy(
|
|
58
59
|
() => import("../../../code-block/CodeBlock.js")
|
|
59
60
|
);
|
|
@@ -100,6 +101,7 @@ const MermaidBlock = ({
|
|
|
100
101
|
};
|
|
101
102
|
const [showSource, setShowSource] = useState(false);
|
|
102
103
|
const [copied, setCopied] = useState(false);
|
|
104
|
+
const host = useBlockHostSlot();
|
|
103
105
|
const [fullscreen, setFullscreen] = useState(false);
|
|
104
106
|
const [svgEl, setSvgEl] = useState(null);
|
|
105
107
|
const [frameMaxHeight, setFrameMaxHeight] = useState(520);
|
|
@@ -221,7 +223,8 @@ const MermaidBlock = ({
|
|
|
221
223
|
"div",
|
|
222
224
|
{
|
|
223
225
|
className: cn(
|
|
224
|
-
"my-3 overflow-hidden rounded-lg border
|
|
226
|
+
"my-3 overflow-hidden rounded-lg border bg-card transition-colors",
|
|
227
|
+
host.selected ? "border-primary" : "border-border",
|
|
225
228
|
className
|
|
226
229
|
),
|
|
227
230
|
children: [
|
|
@@ -236,122 +239,125 @@ const MermaidBlock = ({
|
|
|
236
239
|
] }) : null,
|
|
237
240
|
isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "drawing\u2026" })
|
|
238
241
|
] }),
|
|
239
|
-
|
|
240
|
-
/* @__PURE__ */ jsxs(
|
|
241
|
-
/* @__PURE__ */
|
|
242
|
-
|
|
243
|
-
/* @__PURE__ */
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
theme
|
|
254
|
-
)),
|
|
255
|
-
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
256
|
-
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Style" }),
|
|
257
|
-
LOOK_CHOICES.map((look) => /* @__PURE__ */ jsxs(
|
|
258
|
-
DropdownMenuItem,
|
|
259
|
-
{
|
|
260
|
-
onClick: () => setOption("look", look),
|
|
261
|
-
children: [
|
|
262
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1", children: look === "handDrawn" ? "Hand-drawn" : "Classic" }),
|
|
263
|
-
effective.look === look && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
|
|
264
|
-
]
|
|
265
|
-
},
|
|
266
|
-
look
|
|
267
|
-
)),
|
|
268
|
-
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
269
|
-
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Layout" }),
|
|
270
|
-
LAYOUT_CHOICES.map((layout) => /* @__PURE__ */ jsxs(
|
|
271
|
-
DropdownMenuItem,
|
|
272
|
-
{
|
|
273
|
-
onClick: () => setOption("layout", layout),
|
|
274
|
-
children: [
|
|
275
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 uppercase", children: layout }),
|
|
276
|
-
effective.layout === layout && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
|
|
277
|
-
]
|
|
278
|
-
},
|
|
279
|
-
layout
|
|
280
|
-
)),
|
|
281
|
-
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
282
|
-
/* @__PURE__ */ jsx(
|
|
283
|
-
DropdownMenuItem,
|
|
284
|
-
{
|
|
285
|
-
onClick: () => {
|
|
286
|
-
setMermaidPreferences(effective);
|
|
287
|
-
toast.success("Saved as your default diagram style");
|
|
242
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
243
|
+
!isStreamActive && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
244
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
245
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { text: "Diagram style", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Palette, {}), "aria-label": "Diagram style" }) }) }),
|
|
246
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [
|
|
247
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Theme" }),
|
|
248
|
+
THEME_CHOICES.map((theme) => /* @__PURE__ */ jsxs(
|
|
249
|
+
DropdownMenuItem,
|
|
250
|
+
{
|
|
251
|
+
onClick: () => setOption("theme", theme),
|
|
252
|
+
children: [
|
|
253
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 capitalize", children: theme }),
|
|
254
|
+
effective.theme === theme && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
|
|
255
|
+
]
|
|
288
256
|
},
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const svg = currentSvg();
|
|
302
|
-
if (svg) downloadMermaidSvg(svg, title);
|
|
303
|
-
else toast.error("Nothing rendered yet");
|
|
257
|
+
theme
|
|
258
|
+
)),
|
|
259
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
260
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Style" }),
|
|
261
|
+
LOOK_CHOICES.map((look) => /* @__PURE__ */ jsxs(
|
|
262
|
+
DropdownMenuItem,
|
|
263
|
+
{
|
|
264
|
+
onClick: () => setOption("look", look),
|
|
265
|
+
children: [
|
|
266
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", children: look === "handDrawn" ? "Hand-drawn" : "Classic" }),
|
|
267
|
+
effective.look === look && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
|
|
268
|
+
]
|
|
304
269
|
},
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
await downloadMermaidPng(svg, title);
|
|
319
|
-
} catch (err) {
|
|
320
|
-
console.error("[MermaidBlock] PNG export failed", err);
|
|
321
|
-
toast.error("PNG export failed");
|
|
322
|
-
}
|
|
270
|
+
look
|
|
271
|
+
)),
|
|
272
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
273
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: "Layout" }),
|
|
274
|
+
LAYOUT_CHOICES.map((layout) => /* @__PURE__ */ jsxs(
|
|
275
|
+
DropdownMenuItem,
|
|
276
|
+
{
|
|
277
|
+
onClick: () => setOption("layout", layout),
|
|
278
|
+
children: [
|
|
279
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 uppercase", children: layout }),
|
|
280
|
+
effective.layout === layout && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
|
|
281
|
+
]
|
|
323
282
|
},
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
283
|
+
layout
|
|
284
|
+
)),
|
|
285
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
286
|
+
/* @__PURE__ */ jsx(
|
|
287
|
+
DropdownMenuItem,
|
|
288
|
+
{
|
|
289
|
+
onClick: () => {
|
|
290
|
+
setMermaidPreferences(effective);
|
|
291
|
+
toast.success("Saved as your default diagram style");
|
|
292
|
+
},
|
|
293
|
+
children: "Save as my default"
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
] })
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
299
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { text: "Export diagram", children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Download, {}), "aria-label": "Export diagram" }) }) }),
|
|
300
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-48", children: [
|
|
301
|
+
/* @__PURE__ */ jsx(
|
|
302
|
+
DropdownMenuItem,
|
|
303
|
+
{
|
|
304
|
+
onClick: () => {
|
|
305
|
+
const svg = currentSvg();
|
|
306
|
+
if (svg) downloadMermaidSvg(svg, title);
|
|
307
|
+
else toast.error("Nothing rendered yet");
|
|
308
|
+
},
|
|
309
|
+
children: "Download SVG"
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
/* @__PURE__ */ jsx(
|
|
313
|
+
DropdownMenuItem,
|
|
314
|
+
{
|
|
315
|
+
onClick: async () => {
|
|
316
|
+
const svg = currentSvg();
|
|
317
|
+
if (!svg) {
|
|
318
|
+
toast.error("Nothing rendered yet");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
await downloadMermaidPng(svg, title);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
console.error("[MermaidBlock] PNG export failed", err);
|
|
325
|
+
toast.error("PNG export failed");
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
children: "Download PNG"
|
|
329
|
+
}
|
|
330
|
+
),
|
|
331
|
+
/* @__PURE__ */ jsx(
|
|
332
|
+
DropdownMenuItem,
|
|
333
|
+
{
|
|
334
|
+
onClick: () => downloadMermaidSource(source, title),
|
|
335
|
+
children: "Download source (.mmd)"
|
|
336
|
+
}
|
|
337
|
+
),
|
|
338
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
339
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: handleSaveToWorkspace, children: [
|
|
340
|
+
/* @__PURE__ */ jsx(FolderUp, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
341
|
+
"Save to my files"
|
|
342
|
+
] })
|
|
338
343
|
] })
|
|
339
|
-
] })
|
|
344
|
+
] }),
|
|
345
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { text: copied ? "Copied" : "Copy diagram source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), "aria-label": "Copy diagram source", onClick: handleCopy }) }),
|
|
346
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { text: showSource ? "Hide source" : "Show source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: showSource, icon: /* @__PURE__ */ jsx(Code2, {}), "aria-label": showSource ? "Hide source" : "Show source", onClick: () => setShowSource((v) => !v) }) }),
|
|
347
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { text: "View fullscreen", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Expand, {}), "aria-label": "View fullscreen", onClick: () => setFullscreen(true) }) }),
|
|
348
|
+
isCanvasAvailable && /* @__PURE__ */ jsx(SimpleTooltip, { text: "Open in canvas", children: /* @__PURE__ */ jsx(
|
|
349
|
+
Button,
|
|
350
|
+
{
|
|
351
|
+
type: "button",
|
|
352
|
+
"aria-label": "Open in canvas",
|
|
353
|
+
onClick: () => handleOpenCanvas(),
|
|
354
|
+
variant: "quiet",
|
|
355
|
+
icon: /* @__PURE__ */ jsx(Maximize2, {}),
|
|
356
|
+
children: "Canvas"
|
|
357
|
+
}
|
|
358
|
+
) })
|
|
340
359
|
] }),
|
|
341
|
-
|
|
342
|
-
/* @__PURE__ */ jsx(SimpleTooltip, { text: showSource ? "Hide source" : "Show source", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: showSource, icon: /* @__PURE__ */ jsx(Code2, {}), "aria-label": showSource ? "Hide source" : "Show source", onClick: () => setShowSource((v) => !v) }) }),
|
|
343
|
-
/* @__PURE__ */ jsx(SimpleTooltip, { text: "View fullscreen", children: /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Expand, {}), "aria-label": "View fullscreen", onClick: () => setFullscreen(true) }) }),
|
|
344
|
-
isCanvasAvailable && /* @__PURE__ */ jsx(SimpleTooltip, { text: "Open in canvas", children: /* @__PURE__ */ jsx(
|
|
345
|
-
Button,
|
|
346
|
-
{
|
|
347
|
-
type: "button",
|
|
348
|
-
"aria-label": "Open in canvas",
|
|
349
|
-
onClick: () => handleOpenCanvas(),
|
|
350
|
-
variant: "quiet",
|
|
351
|
-
icon: /* @__PURE__ */ jsx(Maximize2, {}),
|
|
352
|
-
children: "Canvas"
|
|
353
|
-
}
|
|
354
|
-
) })
|
|
360
|
+
host.actions
|
|
355
361
|
] })
|
|
356
362
|
] }),
|
|
357
363
|
/* @__PURE__ */ jsx(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { downloadFile } from "@ai-matrx/kit/download";
|
|
4
4
|
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
5
5
|
import { useEffect, useMemo, useState } from "react";
|
|
@@ -10,6 +10,7 @@ import { Skeleton } from "@ai-matrx/design-system";
|
|
|
10
10
|
import SandboxedHtml from "../common/SandboxedHtml.js";
|
|
11
11
|
import { cn } from "@ai-matrx/design-system";
|
|
12
12
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
13
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
13
14
|
function readSvgMeta(svg) {
|
|
14
15
|
let aspect = 16 / 9;
|
|
15
16
|
const viewBox = /viewBox\s*=\s*["']\s*[\d.+-]+\s+[\d.+-]+\s+([\d.]+)\s+([\d.]+)\s*["']/i.exec(svg);
|
|
@@ -38,6 +39,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
38
39
|
const { aspect, title } = useMemo(() => readSvgMeta(svg), [svg]);
|
|
39
40
|
const srcDoc = useMemo(() => toSrcDoc(svg), [svg]);
|
|
40
41
|
const [copied, setCopied] = useState(false);
|
|
42
|
+
const host = useBlockHostSlot();
|
|
41
43
|
const [fullscreen, setFullscreen] = useState(false);
|
|
42
44
|
const handleCopy = async () => {
|
|
43
45
|
if (!await copyText(svg, void 0, "Couldn't copy the SVG")) return;
|
|
@@ -52,7 +54,7 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
52
54
|
}
|
|
53
55
|
};
|
|
54
56
|
const hasSvg = /<svg[\s>]/i.test(svg);
|
|
55
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border
|
|
57
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border bg-card transition-colors", host.selected ? "border-primary" : "border-border", className), children: [
|
|
56
58
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
|
|
57
59
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
58
60
|
/* @__PURE__ */ jsx(ImageIcon, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
|
|
@@ -60,10 +62,13 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
60
62
|
/* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-xs text-muted-foreground sm:inline", children: "SVG" }),
|
|
61
63
|
isStreamActive && /* @__PURE__ */ jsx("span", { className: "shrink-0 animate-pulse text-xs text-muted-foreground", children: "drawing\u2026" })
|
|
62
64
|
] }),
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
66
|
+
!isStreamActive && hasSvg && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(IconBtn, { label: copied ? "Copied" : "Copy SVG source", onClick: handleCopy, children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
|
|
68
|
+
/* @__PURE__ */ jsx(IconBtn, { label: "Download SVG", onClick: handleDownload, children: /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" }) }),
|
|
69
|
+
/* @__PURE__ */ jsx(IconBtn, { label: "View fullscreen", onClick: () => setFullscreen(true), children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
|
|
70
|
+
] }),
|
|
71
|
+
host.actions
|
|
67
72
|
] })
|
|
68
73
|
] }),
|
|
69
74
|
/* @__PURE__ */ jsx("div", { className: "p-3", children: isStreamActive || !hasSvg ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", "aria-busy": "true", children: [
|
|
@@ -6,6 +6,7 @@ import { useState } from "react";
|
|
|
6
6
|
import { cn } from "@ai-matrx/design-system";
|
|
7
7
|
import { Copy, Check, ChevronRight, ChevronDown } from "lucide-react";
|
|
8
8
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
9
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
9
10
|
function parseTomlLines(content) {
|
|
10
11
|
return content.split("\n").map((raw) => {
|
|
11
12
|
const trimmed = raw.trim();
|
|
@@ -70,6 +71,7 @@ const TomlBlock = ({ content, className }) => {
|
|
|
70
71
|
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
71
72
|
});
|
|
72
73
|
const [copied, setCopied] = useState(false);
|
|
74
|
+
const host = useBlockHostSlot();
|
|
73
75
|
const [collapsedSections, setCollapsedSections] = useState(
|
|
74
76
|
/* @__PURE__ */ new Set()
|
|
75
77
|
);
|
|
@@ -105,13 +107,17 @@ const TomlBlock = ({ content, className }) => {
|
|
|
105
107
|
"div",
|
|
106
108
|
{
|
|
107
109
|
className: cn(
|
|
108
|
-
"my-3 rounded-lg border
|
|
110
|
+
"my-3 rounded-lg border bg-card overflow-hidden transition-colors",
|
|
111
|
+
host.selected ? "border-primary" : "border-border",
|
|
109
112
|
className
|
|
110
113
|
),
|
|
111
114
|
children: [
|
|
112
115
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50 bg-muted/30", children: [
|
|
113
116
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-semibold text-violet-600 dark:text-violet-400", children: "TOML" }),
|
|
114
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
118
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
|
|
119
|
+
host.actions
|
|
120
|
+
] })
|
|
115
121
|
] }),
|
|
116
122
|
/* @__PURE__ */ jsx("div", { className: "px-3 py-2 font-mono text-sm leading-relaxed overflow-x-auto", children: lines.map((line, idx) => {
|
|
117
123
|
if (hiddenLines.has(idx)) return null;
|
|
@@ -8,6 +8,7 @@ import { Copy, Check } from "lucide-react";
|
|
|
8
8
|
import { useCanvasPresentation } from "@ai-matrx/canvas/react";
|
|
9
9
|
import { treeWrapsLines } from "../canvas-adaptive.js";
|
|
10
10
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
11
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
11
12
|
const CONNECTORS = [
|
|
12
13
|
"\u251C\u2500",
|
|
13
14
|
"\u2514\u2500",
|
|
@@ -91,6 +92,7 @@ const TreeBlock = ({ content, className }) => {
|
|
|
91
92
|
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
92
93
|
});
|
|
93
94
|
const [copied, setCopied] = useState(false);
|
|
95
|
+
const host = useBlockHostSlot();
|
|
94
96
|
const lines = parseTreeLines(content);
|
|
95
97
|
const wrap = treeWrapsLines(useCanvasPresentation());
|
|
96
98
|
const handleCopy = async () => {
|
|
@@ -102,13 +104,17 @@ const TreeBlock = ({ content, className }) => {
|
|
|
102
104
|
"div",
|
|
103
105
|
{
|
|
104
106
|
className: cn(
|
|
105
|
-
"my-3 rounded-lg border
|
|
107
|
+
"my-3 rounded-lg border bg-card overflow-hidden transition-colors",
|
|
108
|
+
host.selected ? "border-primary" : "border-border",
|
|
106
109
|
className
|
|
107
110
|
),
|
|
108
111
|
children: [
|
|
109
112
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50 bg-muted/30", children: [
|
|
110
113
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-semibold text-teal-600 dark:text-teal-400", children: "Tree" }),
|
|
111
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
115
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
|
|
116
|
+
host.actions
|
|
117
|
+
] })
|
|
112
118
|
] }),
|
|
113
119
|
/* @__PURE__ */ jsx(
|
|
114
120
|
"div",
|
|
@@ -10,6 +10,7 @@ import { cn } from "@ai-matrx/design-system";
|
|
|
10
10
|
import { tokenizeXml } from "./xml-tokenize.js";
|
|
11
11
|
import { useMarkdownStreaming } from "../../../markdown-core/streaming-context.js";
|
|
12
12
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
13
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
13
14
|
function renderAttributes(attrs) {
|
|
14
15
|
return attrs.map((attr, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
15
16
|
" ",
|
|
@@ -33,6 +34,7 @@ const XmlBlock = ({
|
|
|
33
34
|
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
34
35
|
});
|
|
35
36
|
const [copied, setCopied] = useState(false);
|
|
37
|
+
const host = useBlockHostSlot();
|
|
36
38
|
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
37
39
|
const [cardCollapsed, setCardCollapsed] = useState(false);
|
|
38
40
|
const tokens = tokenizeXml(content);
|
|
@@ -94,7 +96,8 @@ const XmlBlock = ({
|
|
|
94
96
|
"div",
|
|
95
97
|
{
|
|
96
98
|
className: cn(
|
|
97
|
-
"my-3 rounded-lg border
|
|
99
|
+
"my-3 rounded-lg border bg-card overflow-hidden transition-colors",
|
|
100
|
+
host.selected ? "border-primary" : "border-border",
|
|
98
101
|
className
|
|
99
102
|
),
|
|
100
103
|
children: [
|
|
@@ -110,7 +113,8 @@ const XmlBlock = ({
|
|
|
110
113
|
}
|
|
111
114
|
) : null
|
|
112
115
|
] }),
|
|
113
|
-
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, "aria-label": "Copy XML", onClick: handleCopy, className: "size-11 lg:size-auto" })
|
|
116
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, "aria-label": "Copy XML", onClick: handleCopy, className: "size-11 lg:size-auto" }),
|
|
117
|
+
host.actions
|
|
114
118
|
] }),
|
|
115
119
|
!cardCollapsed && /* @__PURE__ */ jsx(
|
|
116
120
|
"div",
|
|
@@ -6,6 +6,7 @@ import { useState } from "react";
|
|
|
6
6
|
import { cn } from "@ai-matrx/design-system";
|
|
7
7
|
import { ChevronRight, ChevronDown, Copy, Check } from "lucide-react";
|
|
8
8
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
9
|
+
import { useBlockHostSlot } from "../../block-host.js";
|
|
9
10
|
function parseYamlLines(content) {
|
|
10
11
|
return content.split("\n").map((raw) => {
|
|
11
12
|
const indent = raw.search(/\S|$/);
|
|
@@ -78,6 +79,7 @@ const YamlBlock = ({ content, className }) => {
|
|
|
78
79
|
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
79
80
|
});
|
|
80
81
|
const [copied, setCopied] = useState(false);
|
|
82
|
+
const host = useBlockHostSlot();
|
|
81
83
|
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
82
84
|
const lines = parseYamlLines(content);
|
|
83
85
|
const handleCopy = async () => {
|
|
@@ -121,13 +123,17 @@ const YamlBlock = ({ content, className }) => {
|
|
|
121
123
|
"div",
|
|
122
124
|
{
|
|
123
125
|
className: cn(
|
|
124
|
-
"my-3 rounded-lg border
|
|
126
|
+
"my-3 rounded-lg border bg-card overflow-hidden transition-colors",
|
|
127
|
+
host.selected ? "border-primary" : "border-border",
|
|
125
128
|
className
|
|
126
129
|
),
|
|
127
130
|
children: [
|
|
128
131
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50 bg-muted/30", children: [
|
|
129
132
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono font-semibold text-amber-600 dark:text-amber-400", children: "YAML" }),
|
|
130
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
134
|
+
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {}), glyphTone: copied ? "success" : void 0, onClick: handleCopy, "aria-label": "Copy" }),
|
|
135
|
+
host.actions
|
|
136
|
+
] })
|
|
131
137
|
] }),
|
|
132
138
|
/* @__PURE__ */ jsx("div", { className: "px-3 py-2 font-mono text-sm leading-relaxed overflow-x-auto", children: visibleLines.map((idx) => {
|
|
133
139
|
const line = lines[idx];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useAlchemyDisclosure } from "../../../host/app-bindings.js";
|
|
4
|
-
import * as React from "react";
|
|
5
4
|
import { ContentTransferMenu } from "@ai-matrx/alchemy/react/workspace";
|
|
6
5
|
import { unwrapKindEnvelopes } from "@ai-matrx/content-ir/source";
|
|
7
6
|
import { directSource } from "@ai-matrx/alchemy/operate";
|
|
@@ -15,7 +14,6 @@ function AlchemyDocumentMenu({
|
|
|
15
14
|
richCopy
|
|
16
15
|
}) {
|
|
17
16
|
useAlchemyDisclosure();
|
|
18
|
-
const [hasThinking, setHasThinking] = React.useState(false);
|
|
19
17
|
const source = {
|
|
20
18
|
id: sourceId,
|
|
21
19
|
label: "Document",
|
|
@@ -29,7 +27,6 @@ function AlchemyDocumentMenu({
|
|
|
29
27
|
"The document changed. Reopen Alchemy Menu for the current document."
|
|
30
28
|
);
|
|
31
29
|
const unthought = removeThinkingContent(ctx.content);
|
|
32
|
-
setHasThinking(unthought !== ctx.content);
|
|
33
30
|
const text = unwrapKindEnvelopes(unthought);
|
|
34
31
|
return directSource(
|
|
35
32
|
{ kind: "markdown", text },
|
|
@@ -42,28 +39,6 @@ function AlchemyDocumentMenu({
|
|
|
42
39
|
);
|
|
43
40
|
}
|
|
44
41
|
};
|
|
45
|
-
const includingThinkingSource = {
|
|
46
|
-
id: `${sourceId}:including-thinking`,
|
|
47
|
-
label: "Document",
|
|
48
|
-
capture: async ({ signal }) => {
|
|
49
|
-
signal.throwIfAborted();
|
|
50
|
-
const ctx = getCtx();
|
|
51
|
-
const capturedSourceId = ctx.instanceKey("alchemy");
|
|
52
|
-
if (capturedSourceId !== sourceId)
|
|
53
|
-
throw new Error(
|
|
54
|
-
"The document changed. Reopen Alchemy Menu for the current document."
|
|
55
|
-
);
|
|
56
|
-
return directSource(
|
|
57
|
-
{ kind: "markdown", text: ctx.content },
|
|
58
|
-
{
|
|
59
|
-
id: `${capturedSourceId}:including-thinking`,
|
|
60
|
-
sourceId: capturedSourceId,
|
|
61
|
-
revision: ctx.content,
|
|
62
|
-
label: "Document"
|
|
63
|
-
}
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
42
|
return /* @__PURE__ */ jsx(
|
|
68
43
|
ContentTransferMenu,
|
|
69
44
|
{
|
|
@@ -80,17 +55,7 @@ function AlchemyDocumentMenu({
|
|
|
80
55
|
// The split button carries the three ways to copy (COPY_LABELS); the palette keeps the rest.
|
|
81
56
|
richCopy: (flavor) => richCopy(flavor === "default" ? "markdown" : flavor),
|
|
82
57
|
richCopyFlavors: []
|
|
83
|
-
} : {}
|
|
84
|
-
variants: hasThinking ? [
|
|
85
|
-
{
|
|
86
|
-
id: "include-thinking",
|
|
87
|
-
label: "Raw with thinking",
|
|
88
|
-
copyLabel: "Raw with thinking",
|
|
89
|
-
section: "copy",
|
|
90
|
-
hint: "Includes the original reasoning content",
|
|
91
|
-
source: includingThinkingSource
|
|
92
|
-
}
|
|
93
|
-
] : []
|
|
58
|
+
} : {}
|
|
94
59
|
},
|
|
95
60
|
sourceId
|
|
96
61
|
);
|
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.167",
|
|
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",
|
|
@@ -131,20 +131,20 @@
|
|
|
131
131
|
"unist-util-visit-parents": "^6.0.2",
|
|
132
132
|
"yaml": "^2.9.1",
|
|
133
133
|
"@ai-matrx/agents": "latest",
|
|
134
|
+
"@ai-matrx/alchemy": "latest",
|
|
135
|
+
"@ai-matrx/api": "latest",
|
|
134
136
|
"@ai-matrx/content-ir": "latest",
|
|
135
137
|
"@ai-matrx/content-ir-react": "latest",
|
|
138
|
+
"@ai-matrx/canvas": "latest",
|
|
136
139
|
"@ai-matrx/data": "latest",
|
|
137
|
-
"@ai-matrx/alchemy": "latest",
|
|
138
140
|
"@ai-matrx/diff": "latest",
|
|
139
|
-
"@ai-matrx/
|
|
141
|
+
"@ai-matrx/kit": "latest",
|
|
142
|
+
"@ai-matrx/media": "latest",
|
|
140
143
|
"@ai-matrx/print": "latest",
|
|
141
|
-
"@ai-matrx/api": "latest",
|
|
142
144
|
"@ai-matrx/records": "latest",
|
|
143
145
|
"@ai-matrx/records-ui": "latest",
|
|
144
146
|
"@ai-matrx/icons": "latest",
|
|
145
|
-
"@ai-matrx/
|
|
146
|
-
"@ai-matrx/media": "latest",
|
|
147
|
-
"@ai-matrx/kit": "latest"
|
|
147
|
+
"@ai-matrx/design-system": "latest"
|
|
148
148
|
},
|
|
149
149
|
"peerDependencies": {
|
|
150
150
|
"react": ">=19.0.0",
|
|
@@ -205,5 +205,5 @@
|
|
|
205
205
|
"verify:modules-tarball": "node ./scripts/verify-modules-tarball.mjs",
|
|
206
206
|
"verify:tarball": "node ./scripts/verify-tarball.mjs"
|
|
207
207
|
},
|
|
208
|
-
"gitHead": "
|
|
208
|
+
"gitHead": "ebc4f61cd17a04b88022d1a42f79b586e89c30a1"
|
|
209
209
|
}
|