@arronqzy/react-blueprint 1.0.17 → 1.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/react-blueprint",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "",
5
5
  "files": [
6
6
  "index.ts",
@@ -18,10 +18,10 @@
18
18
  "dayjs": "^1.11.13",
19
19
  "react-dom": "^19",
20
20
  "@xyflow/react": "^12.9.3",
21
- "@arronqzy/blueprint-dsl": "1.0.14",
21
+ "@arronqzy/blueprint-dsl": "1.0.15",
22
22
  "@arronqzy/react-rx-store": "1.1.8",
23
- "@arronqzy/i18n": "0.1.12",
24
- "@arronqzy/ui": "1.0.9"
23
+ "@arronqzy/ui": "1.0.9",
24
+ "@arronqzy/i18n": "0.1.15"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/react": "^19.0.1",
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from "react";
2
- import { useI18n } from "@arronqzy/i18n/react";
2
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
3
3
 
4
4
  export type BlueprintContextMenuState =
5
5
  | {
@@ -16,7 +16,7 @@ import {
16
16
  type PageLifecyclePhase,
17
17
  type ViewEventType,
18
18
  } from "@arronqzy/blueprint-dsl";
19
- import { useI18n } from "@arronqzy/i18n/react";
19
+ import { EnsureI18nProvider, useI18nOptional as useI18n } from "@arronqzy/i18n/react";
20
20
 
21
21
  import { FetchNodeConfigPanel } from "./components/FetchNodeConfigPanel";
22
22
  import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel";
@@ -96,7 +96,15 @@ const ROLE_LABEL_KEYS: Record<BlueprintNodeRole, string> = {
96
96
  event: "blueprint.config.roleEvent",
97
97
  };
98
98
 
99
- export function BlueprintNodeConfigSidebar({
99
+ export function BlueprintNodeConfigSidebar(props: BlueprintNodeConfigSidebarProps) {
100
+ return (
101
+ <EnsureI18nProvider>
102
+ <BlueprintNodeConfigSidebarInner {...props} />
103
+ </EnsureI18nProvider>
104
+ );
105
+ }
106
+
107
+ function BlueprintNodeConfigSidebarInner({
100
108
  node,
101
109
  graphNodes = [],
102
110
  graphEdges = [],
@@ -400,7 +408,13 @@ export function BlueprintNodeConfigSidebar({
400
408
  ) : null}
401
409
 
402
410
  {configSource === "json" ? (
403
- <JsonNodeConfigPanel node={node} onUpdateNode={onUpdateNode} />
411
+ <JsonNodeConfigPanel
412
+ node={node}
413
+ graphNodes={graphNodes}
414
+ graphEdges={graphEdges}
415
+ traceEntries={traceEntries}
416
+ onUpdateNode={onUpdateNode}
417
+ />
404
418
  ) : null}
405
419
 
406
420
  {configSource === "storage" ? (
@@ -20,7 +20,7 @@ import {
20
20
  type ExecutionTraceEntry,
21
21
  type PageLifecyclePhase,
22
22
  } from "@arronqzy/blueprint-dsl";
23
- import { useI18n } from "@arronqzy/i18n/react";
23
+ import { EnsureI18nProvider, useI18nOptional as useI18n } from "@arronqzy/i18n/react";
24
24
  import { getLifecyclePhaseLabel } from "../graph/document";
25
25
 
26
26
  import type { ExecutionLogSettings } from "../library/execution-log-settings";
@@ -140,7 +140,15 @@ export type BlueprintExecutionLogPanelProps = {
140
140
  lifecyclePhase?: string;
141
141
  };
142
142
 
143
- export function BlueprintExecutionLogPanel({
143
+ export function BlueprintExecutionLogPanel(props: BlueprintExecutionLogPanelProps) {
144
+ return (
145
+ <EnsureI18nProvider>
146
+ <BlueprintExecutionLogPanelInner {...props} />
147
+ </EnsureI18nProvider>
148
+ );
149
+ }
150
+
151
+ function BlueprintExecutionLogPanelInner({
144
152
  entries,
145
153
  settings,
146
154
  onUpdateSettings,
@@ -7,7 +7,7 @@ import {
7
7
  SelectTrigger,
8
8
  SelectValue,
9
9
  } from "@arronqzy/ui";
10
- import { useI18n } from "@arronqzy/i18n/react";
10
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
11
11
 
12
12
  import type { BlueprintLibraryListItem } from "../library/types";
13
13
 
@@ -11,7 +11,7 @@ import {
11
11
  Label,
12
12
  Textarea,
13
13
  } from "@arronqzy/ui";
14
- import { useI18n } from "@arronqzy/i18n/react";
14
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
15
15
 
16
16
  import type { BlueprintMetaDraft } from "../library/types";
17
17
 
@@ -7,7 +7,7 @@ import {
7
7
  DialogHeader,
8
8
  DialogTitle,
9
9
  } from "@arronqzy/ui";
10
- import { useI18n } from "@arronqzy/i18n/react";
10
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
11
11
 
12
12
  export type BlueprintNodeSwitchTaskDialogProps = {
13
13
  open: boolean;
@@ -18,7 +18,7 @@ import {
18
18
  import type { BlueprintLibraryListItem } from "../library/types";
19
19
  import type { LifecycleNodeOption } from "../hooks/useBlueprintDebugSession";
20
20
  import { type PageLifecyclePhase } from "@arronqzy/blueprint-dsl";
21
- import { useI18n } from "@arronqzy/i18n/react";
21
+ import { EnsureI18nProvider, useI18nOptional as useI18n } from "@arronqzy/i18n/react";
22
22
  import { getLifecyclePhaseLabel } from "../graph/document";
23
23
 
24
24
  export type BlueprintRenameDialogProps = {
@@ -393,7 +393,15 @@ function LibraryRow({
393
393
  );
394
394
  }
395
395
 
396
- export function BlueprintPanelToolbar({
396
+ export function BlueprintPanelToolbar(props: BlueprintPanelToolbarProps) {
397
+ return (
398
+ <EnsureI18nProvider>
399
+ <BlueprintPanelToolbarInner {...props} />
400
+ </EnsureI18nProvider>
401
+ );
402
+ }
403
+
404
+ function BlueprintPanelToolbarInner({
397
405
  items,
398
406
  activeId,
399
407
  currentBlueprintLabel,
@@ -1,7 +1,7 @@
1
1
  import { useCallback, type ChangeEvent } from "react";
2
2
  import { Input } from "@arronqzy/ui";
3
3
  import type { ClockNodeConfig } from "@arronqzy/blueprint-dsl";
4
- import { useI18n } from "@arronqzy/i18n/react";
4
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
5
5
 
6
6
  import type { BlueprintGraphNode } from "../graph/document";
7
7
  import { resolveNodeClockConfig } from "../graph/document";
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import { useI18n } from "@arronqzy/i18n/react";
2
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
3
3
  import {
4
4
  Tooltip,
5
5
  TooltipContent,
@@ -45,7 +45,7 @@ import {
45
45
  useFetchDebugTask,
46
46
  useSwaggerLoadTask,
47
47
  } from "../fetch-config-task-store";
48
- import { useI18n } from "@arronqzy/i18n/react";
48
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
49
49
 
50
50
  import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./ConfigHintIcon";
51
51
  import { FetchUrlAutocomplete } from "./FetchUrlAutocomplete";
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { cn } from "@arronqzy/ui";
3
- import { useI18n } from "@arronqzy/i18n/react";
3
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
4
4
  import {
5
5
  buildEndpointSuggestions,
6
6
  filterEndpointSuggestions,
@@ -1,14 +1,26 @@
1
1
  import { useCallback, useMemo, useState, type ChangeEvent } from "react";
2
- import { validateJsonString } from "@arronqzy/blueprint-dsl";
3
- import type { JsonNodeConfig } from "@arronqzy/blueprint-dsl";
4
- import { useI18n } from "@arronqzy/i18n/react";
2
+ import type {
3
+ ExecutionTraceEntry,
4
+ JsonNodeConfig,
5
+ } from "@arronqzy/blueprint-dsl";
6
+ import {
7
+ latestTraceOutputsByNode,
8
+ resolveFetchIncomingScope,
9
+ resolveFetchScopeAutocompleteRoot,
10
+ validateJsonStringAllowingScope,
11
+ } from "@arronqzy/blueprint-dsl";
12
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
5
13
 
6
- import type { BlueprintGraphNode } from "../graph/document";
14
+ import type { BlueprintGraphEdge, BlueprintGraphNode } from "../graph/document";
7
15
  import { resolveNodeJsonConfig } from "../graph/document";
8
- import { ConfigSectionTitle } from "./ConfigHintIcon";
16
+ import { ConfigHintIcon, ConfigSectionTitle } from "./ConfigHintIcon";
17
+ import { ScopeTemplateAutocompleteHost } from "./ScopeTemplateAutocompleteHost";
9
18
 
10
19
  export type JsonNodeConfigPanelProps = {
11
20
  node: BlueprintGraphNode;
21
+ graphNodes?: BlueprintGraphNode[];
22
+ graphEdges?: BlueprintGraphEdge[];
23
+ traceEntries?: ExecutionTraceEntry[];
12
24
  onUpdateNode: (
13
25
  nodeId: string,
14
26
  patch: Partial<Pick<BlueprintGraphNode, "jsonConfig" | "configSource">>
@@ -27,25 +39,44 @@ function patchJsonConfig(
27
39
 
28
40
  export function JsonNodeConfigPanel({
29
41
  node,
42
+ graphNodes = [],
43
+ graphEdges = [],
44
+ traceEntries = [],
30
45
  onUpdateNode,
31
46
  }: JsonNodeConfigPanelProps) {
32
47
  const { t } = useI18n();
48
+ const [formEl, setFormEl] = useState<HTMLDivElement | null>(null);
33
49
  const jsonConfig = resolveNodeJsonConfig(node);
34
50
  const [draftError, setDraftError] = useState<string | null>(null);
35
51
 
52
+ const incomingScope = useMemo(() => {
53
+ const outputs = latestTraceOutputsByNode(traceEntries);
54
+ return resolveFetchIncomingScope({
55
+ fetchNodeId: node.id,
56
+ nodes: graphNodes,
57
+ edges: graphEdges,
58
+ getOutput: (sourceId, port) => outputs[sourceId]?.[port],
59
+ });
60
+ }, [graphEdges, graphNodes, node.id, traceEntries]);
61
+ const autocompleteScope = resolveFetchScopeAutocompleteRoot(incomingScope);
62
+
36
63
  const storedValidation = useMemo(
37
- () => validateJsonString(jsonConfig.jsonString),
64
+ () => validateJsonStringAllowingScope(jsonConfig.jsonString),
38
65
  [jsonConfig.jsonString]
39
66
  );
40
67
 
41
68
  const parseError =
42
69
  draftError ??
43
70
  (storedValidation.ok ? null : storedValidation.error);
71
+ const deferredScope =
72
+ storedValidation.ok && "deferred" in storedValidation
73
+ ? storedValidation.deferred === true
74
+ : false;
44
75
 
45
76
  const handleChange = useCallback(
46
77
  (e: ChangeEvent<HTMLTextAreaElement>) => {
47
78
  const jsonString = e.target.value;
48
- const result = validateJsonString(jsonString);
79
+ const result = validateJsonStringAllowingScope(jsonString);
49
80
  setDraftError(result.ok ? null : result.error);
50
81
  onUpdateNode(node.id, patchJsonConfig(node, { jsonString }));
51
82
  },
@@ -53,15 +84,25 @@ export function JsonNodeConfigPanel({
53
84
  );
54
85
 
55
86
  return (
56
- <div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
87
+ <div
88
+ ref={setFormEl}
89
+ className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5"
90
+ >
91
+ <ScopeTemplateAutocompleteHost
92
+ scope={autocompleteScope}
93
+ container={formEl}
94
+ />
57
95
  <ConfigSectionTitle
58
96
  title={t("blueprint.config.jsonTitle")}
59
97
  hint={t("blueprint.config.jsonHint")}
60
98
  />
61
99
 
62
100
  <label className="block space-y-1">
63
- <span className="text-muted-foreground">
101
+ <span className="inline-flex items-center gap-1 text-muted-foreground">
64
102
  {t("blueprint.config.jsonContent")}
103
+ <ConfigHintIcon label={t("blueprint.config.jsonContent")}>
104
+ {t("blueprint.config.jsonScopeHint")}
105
+ </ConfigHintIcon>
65
106
  </span>
66
107
  <textarea
67
108
  value={jsonConfig.jsonString}
@@ -69,12 +110,16 @@ export function JsonNodeConfigPanel({
69
110
  rows={12}
70
111
  spellCheck={false}
71
112
  className="w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
72
- placeholder={'{\n "key": "value"\n}'}
113
+ placeholder={'{\n "key": "{scope?.value}"\n}'}
73
114
  />
74
115
  {parseError ? (
75
116
  <p className="text-[11px] text-destructive">
76
117
  {t("blueprint.config.jsonFormatError", { error: parseError })}
77
118
  </p>
119
+ ) : deferredScope ? (
120
+ <p className="text-[11px] text-muted-foreground">
121
+ {t("blueprint.config.jsonScopeDeferred")}
122
+ </p>
78
123
  ) : (
79
124
  <p className="text-[11px] text-muted-foreground">
80
125
  {t("blueprint.config.jsonFormatOk")}
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useMemo, useState, type ChangeEvent } from "react";
2
2
  import { validateLogicSourceCode } from "@arronqzy/blueprint-dsl";
3
3
  import type { LogicNodeConfig } from "@arronqzy/blueprint-dsl";
4
- import { useI18n } from "@arronqzy/i18n/react";
4
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
5
5
 
6
6
  import type { BlueprintGraphNode } from "../graph/document";
7
7
  import { resolveNodeLogicConfig } from "../graph/document";
@@ -0,0 +1,142 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { cn } from "@arronqzy/ui";
3
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
4
+ import {
5
+ filterStorageKeySuggestions,
6
+ listBrowserStorageKeys,
7
+ type StorageKind,
8
+ } from "@arronqzy/blueprint-dsl";
9
+
10
+ export type StorageKeyAutocompleteProps = {
11
+ value: string;
12
+ /** One or more storage targets whose keys should be suggested. */
13
+ storages: StorageKind | readonly StorageKind[];
14
+ placeholder?: string;
15
+ onChange: (value: string) => void;
16
+ };
17
+
18
+ export function StorageKeyAutocomplete({
19
+ value,
20
+ storages,
21
+ placeholder,
22
+ onChange,
23
+ }: StorageKeyAutocompleteProps) {
24
+ const { t } = useI18n();
25
+ const [open, setOpen] = useState(false);
26
+ const [activeIndex, setActiveIndex] = useState(0);
27
+ const [keys, setKeys] = useState<string[]>([]);
28
+ const containerRef = useRef<HTMLDivElement>(null);
29
+
30
+ const storageSignature = useMemo(() => {
31
+ const list = Array.isArray(storages) ? [...storages] : [storages];
32
+ return list.join(",");
33
+ }, [storages]);
34
+
35
+ const refreshKeys = useCallback(() => {
36
+ const list = Array.isArray(storages) ? storages : [storages];
37
+ setKeys(listBrowserStorageKeys(list));
38
+ }, [storages]);
39
+
40
+ useEffect(() => {
41
+ refreshKeys();
42
+ }, [refreshKeys, storageSignature]);
43
+
44
+ const filtered = useMemo(
45
+ () => filterStorageKeySuggestions(keys, value),
46
+ [keys, value]
47
+ );
48
+
49
+ useEffect(() => {
50
+ setActiveIndex(0);
51
+ }, [value, filtered.length]);
52
+
53
+ useEffect(() => {
54
+ if (!open) return;
55
+ const onPointerDown = (event: MouseEvent) => {
56
+ if (!containerRef.current?.contains(event.target as globalThis.Node)) {
57
+ setOpen(false);
58
+ }
59
+ };
60
+ window.addEventListener("pointerdown", onPointerDown);
61
+ return () => window.removeEventListener("pointerdown", onPointerDown);
62
+ }, [open]);
63
+
64
+ const showSuggestions = open && keys.length > 0;
65
+
66
+ const selectSuggestion = (index: number) => {
67
+ const item = filtered[index];
68
+ if (!item) return;
69
+ onChange(item);
70
+ setOpen(false);
71
+ };
72
+
73
+ const openDropdown = () => {
74
+ refreshKeys();
75
+ setOpen(true);
76
+ };
77
+
78
+ return (
79
+ <div ref={containerRef} className="relative">
80
+ <input
81
+ value={value}
82
+ placeholder={placeholder}
83
+ spellCheck={false}
84
+ onChange={(e) => {
85
+ onChange(e.target.value);
86
+ setOpen(true);
87
+ }}
88
+ onFocus={openDropdown}
89
+ onClick={openDropdown}
90
+ onKeyDown={(e) => {
91
+ if (!showSuggestions || filtered.length === 0) {
92
+ if (e.key === "Escape") setOpen(false);
93
+ return;
94
+ }
95
+ if (e.key === "ArrowDown") {
96
+ e.preventDefault();
97
+ setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));
98
+ } else if (e.key === "ArrowUp") {
99
+ e.preventDefault();
100
+ setActiveIndex((prev) => Math.max(prev - 1, 0));
101
+ } else if (e.key === "Enter") {
102
+ e.preventDefault();
103
+ selectSuggestion(activeIndex);
104
+ } else if (e.key === "Escape") {
105
+ setOpen(false);
106
+ }
107
+ }}
108
+ className={cn(
109
+ "flex h-8 w-full rounded-md border border-input bg-background px-2 py-1 font-mono text-[11px] text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
110
+ )}
111
+ />
112
+ {showSuggestions ? (
113
+ <div className="absolute z-[10150] mt-1 max-h-52 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md">
114
+ {filtered.length === 0 ? (
115
+ <div className="px-2 py-1.5 text-[11px] text-muted-foreground">
116
+ {t("blueprint.config.noMatchingStorageKey")}
117
+ </div>
118
+ ) : (
119
+ filtered.map((key, index) => (
120
+ <button
121
+ key={key}
122
+ type="button"
123
+ className={cn(
124
+ "flex w-full items-center px-2 py-1.5 text-left font-mono text-[11px] hover:bg-accent",
125
+ index === activeIndex && "bg-accent"
126
+ )}
127
+ onMouseDown={(e) => e.preventDefault()}
128
+ onClick={() => selectSuggestion(index)}
129
+ >
130
+ {key}
131
+ </button>
132
+ ))
133
+ )}
134
+ </div>
135
+ ) : open && keys.length === 0 ? (
136
+ <div className="absolute z-[10150] mt-1 w-full rounded-md border border-border bg-popover px-2 py-1.5 text-[11px] text-muted-foreground shadow-md">
137
+ {t("blueprint.config.noStorageKeys")}
138
+ </div>
139
+ ) : null}
140
+ </div>
141
+ );
142
+ }
@@ -20,12 +20,13 @@ import {
20
20
  resolveFetchScopeAutocompleteRoot,
21
21
  uniqueStorageKinds,
22
22
  } from "@arronqzy/blueprint-dsl";
23
- import { useI18n } from "@arronqzy/i18n/react";
23
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
24
24
 
25
25
  import type { BlueprintGraphEdge, BlueprintGraphNode } from "../graph/document";
26
26
  import { resolveNodeStorageConfig } from "../graph/document";
27
27
  import { ConfigFieldLabel, ConfigSectionTitle } from "./ConfigHintIcon";
28
28
  import { ScopeTemplateAutocompleteHost } from "./ScopeTemplateAutocompleteHost";
29
+ import { StorageKeyAutocomplete } from "./StorageKeyAutocomplete";
29
30
 
30
31
  export type StorageNodeConfigPanelProps = {
31
32
  node: BlueprintGraphNode;
@@ -90,10 +91,10 @@ export function StorageNodeConfigPanel({
90
91
  );
91
92
 
92
93
  const handleReadKeyChange = useCallback(
93
- (e: ChangeEvent<HTMLInputElement>) => {
94
+ (key: string) => {
94
95
  onUpdateNode(
95
96
  node.id,
96
- patchStorageConfig(node, { read: { key: e.target.value } })
97
+ patchStorageConfig(node, { read: { key } })
97
98
  );
98
99
  },
99
100
  [node, onUpdateNode]
@@ -115,10 +116,10 @@ export function StorageNodeConfigPanel({
115
116
  );
116
117
 
117
118
  const handleSetKeyChange = useCallback(
118
- (e: ChangeEvent<HTMLInputElement>) => {
119
+ (key: string) => {
119
120
  onUpdateNode(
120
121
  node.id,
121
- patchStorageConfig(node, { set: { key: e.target.value } })
122
+ patchStorageConfig(node, { set: { key } })
122
123
  );
123
124
  },
124
125
  [node, onUpdateNode]
@@ -134,6 +135,14 @@ export function StorageNodeConfigPanel({
134
135
  [node, onUpdateNode]
135
136
  );
136
137
 
138
+ const setKeySuggestionStorages = useMemo(
139
+ () =>
140
+ storageConfig.set.storages.length > 0
141
+ ? storageConfig.set.storages
142
+ : (["session", "local"] as const),
143
+ [storageConfig.set.storages]
144
+ );
145
+
137
146
  return (
138
147
  <div
139
148
  ref={setFormEl}
@@ -174,12 +183,11 @@ export function StorageNodeConfigPanel({
174
183
  </label>
175
184
  <label className="block space-y-1">
176
185
  <ConfigFieldLabel label={t("blueprint.config.storageKey")} />
177
- <Input
186
+ <StorageKeyAutocomplete
178
187
  value={storageConfig.read.key}
179
- onChange={handleReadKeyChange}
180
- spellCheck={false}
181
- className="h-8 font-mono text-[11px]"
188
+ storages={storageConfig.read.storage}
182
189
  placeholder="key"
190
+ onChange={handleReadKeyChange}
183
191
  />
184
192
  </label>
185
193
  </div>
@@ -213,12 +221,11 @@ export function StorageNodeConfigPanel({
213
221
  </div>
214
222
  <label className="block space-y-1">
215
223
  <ConfigFieldLabel label={t("blueprint.config.storageKey")} />
216
- <Input
224
+ <StorageKeyAutocomplete
217
225
  value={storageConfig.set.key}
218
- onChange={handleSetKeyChange}
219
- spellCheck={false}
220
- className="h-8 font-mono text-[11px]"
226
+ storages={setKeySuggestionStorages}
221
227
  placeholder="key"
228
+ onChange={handleSetKeyChange}
222
229
  />
223
230
  </label>
224
231
  <label className="block space-y-1">
@@ -12,7 +12,7 @@ import {
12
12
  PopoverContent,
13
13
  PopoverTrigger,
14
14
  } from "@arronqzy/ui";
15
- import { useI18n } from "@arronqzy/i18n/react";
15
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
16
16
 
17
17
  export type ViewElementMultiSelectOption = {
18
18
  id: string;
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef } from "react";
2
- import { useI18n } from "@arronqzy/i18n/react";
2
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
3
3
  import { toast } from "@arronqzy/ui";
4
4
  import {
5
5
  addEdge,
package/src/index.tsx CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  type Node,
15
15
  type Edge,
16
16
  } from "@xyflow/react";
17
+ import { EnsureI18nProvider } from "@arronqzy/i18n/react";
17
18
  import "@xyflow/react/dist/style.css";
18
19
  import "./blueprint.css";
19
20
 
@@ -241,33 +242,35 @@ export const BluePrintReactRoot: FC<BluePrintReactRootProps> = ({
241
242
  const containerRef = useRef<HTMLDivElement>(null);
242
243
 
243
244
  return (
244
- <div
245
- ref={containerRef}
246
- data-workspace-region="blueprint"
247
- className="bp-canvas h-full w-full bg-background text-foreground"
248
- style={{
249
- width: "100%",
250
- height: "100%",
251
- minHeight: 0,
252
- ...style,
253
- }}
254
- >
255
- <BlueprintCanvasProvider
256
- onSelectNode={onSelectNode}
257
- onAbortClock={onAbortClock}
245
+ <EnsureI18nProvider>
246
+ <div
247
+ ref={containerRef}
248
+ data-workspace-region="blueprint"
249
+ className="bp-canvas h-full w-full bg-background text-foreground"
250
+ style={{
251
+ width: "100%",
252
+ height: "100%",
253
+ minHeight: 0,
254
+ ...style,
255
+ }}
258
256
  >
259
- <ReactFlowProvider>
260
- <BlueprintCanvas
261
- containerRef={containerRef}
262
- graph={graph}
263
- onGraphChange={onGraphChange}
264
- selectedNodeId={selectedNodeId}
265
- executionOverlay={executionOverlay}
266
- onSelectNode={onSelectNode}
267
- libraryNameById={libraryNameById}
268
- />
269
- </ReactFlowProvider>
270
- </BlueprintCanvasProvider>
271
- </div>
257
+ <BlueprintCanvasProvider
258
+ onSelectNode={onSelectNode}
259
+ onAbortClock={onAbortClock}
260
+ >
261
+ <ReactFlowProvider>
262
+ <BlueprintCanvas
263
+ containerRef={containerRef}
264
+ graph={graph}
265
+ onGraphChange={onGraphChange}
266
+ selectedNodeId={selectedNodeId}
267
+ executionOverlay={executionOverlay}
268
+ onSelectNode={onSelectNode}
269
+ libraryNameById={libraryNameById}
270
+ />
271
+ </ReactFlowProvider>
272
+ </BlueprintCanvasProvider>
273
+ </div>
274
+ </EnsureI18nProvider>
272
275
  );
273
276
  };
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import { Handle, Position, type NodeProps } from "@xyflow/react";
3
3
  import { cn } from "@arronqzy/ui";
4
4
 
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
 
4
4
  import { resolveBlueprintNodeTypeLabel } from "../graph/document";
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import { cn } from "@arronqzy/ui";
3
3
 
4
4
  export type BlueprintNodeCardProps = {
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import { Handle, Position } from "@xyflow/react";
3
3
  import { cn } from "@arronqzy/ui";
4
4
 
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
  import { cn } from "@arronqzy/ui";
4
4
 
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
 
4
4
  import { resolveBlueprintNodeTypeLabel } from "../graph/document";
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
 
4
4
  import { resolveBlueprintNodeTypeLabel } from "../graph/document";
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
 
4
4
  import { resolveBlueprintNodeTypeLabel } from "../graph/document";
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import { Handle, Position, type NodeProps } from "@xyflow/react";
3
3
  import { cn } from "@arronqzy/ui";
4
4
 
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
 
4
4
  import { resolveBlueprintNodeTypeLabel } from "../graph/document";
@@ -1,4 +1,4 @@
1
- import { useI18n } from "@arronqzy/i18n/react";
1
+ import { useI18nOptional as useI18n } from "@arronqzy/i18n/react";
2
2
  import type { NodeProps } from "@xyflow/react";
3
3
 
4
4
  import { resolveBlueprintNodeTypeLabel } from "../graph/document";