@arronqzy/react-blueprint 1.0.3 → 1.0.5

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.
Files changed (32) hide show
  1. package/index.ts +2 -1
  2. package/package.json +7 -6
  3. package/src/BlueprintContextMenu.tsx +5 -3
  4. package/src/BlueprintNodeConfigSidebar.tsx +56 -57
  5. package/src/components/BlueprintExecutionLogPanel.tsx +30 -28
  6. package/src/components/BlueprintLibrarySelect.tsx +11 -4
  7. package/src/components/BlueprintMetaDialog.tsx +20 -10
  8. package/src/components/BlueprintNodeSwitchTaskDialog.tsx +14 -8
  9. package/src/components/BlueprintPanelToolbar.tsx +50 -42
  10. package/src/components/ClockNodeConfigPanel.tsx +19 -12
  11. package/src/components/FetchNodeConfigPanel.tsx +36 -33
  12. package/src/components/FetchUrlAutocomplete.tsx +3 -1
  13. package/src/components/JsonNodeConfigPanel.tsx +15 -7
  14. package/src/components/LogicNodeConfigPanel.tsx +13 -7
  15. package/src/components/ViewElementMultiSelect.tsx +25 -18
  16. package/src/dev-seed.ts +2 -2
  17. package/src/fetch-config-task-store.ts +8 -2
  18. package/src/graph/document.ts +51 -14
  19. package/src/graph/node-summary.ts +32 -18
  20. package/src/hooks/useBlueprintPageLifecycle.ts +29 -1
  21. package/src/library/blueprint-io.ts +12 -3
  22. package/src/library/swagger-docs.ts +15 -5
  23. package/src/main.tsx +12 -5
  24. package/src/nodes/AndFlowNode.tsx +7 -5
  25. package/src/nodes/BlueprintFlowNode.tsx +4 -2
  26. package/src/nodes/BlueprintNodeCard.tsx +4 -2
  27. package/src/nodes/BlueprintNodeShell.tsx +4 -2
  28. package/src/nodes/ClockFlowNode.tsx +5 -3
  29. package/src/nodes/FetchFlowNode.tsx +4 -2
  30. package/src/nodes/JsonFlowNode.tsx +4 -2
  31. package/src/nodes/LifecycleFlowNode.tsx +5 -3
  32. package/src/nodes/LogicFlowNode.tsx +4 -2
package/index.ts CHANGED
@@ -7,7 +7,8 @@ export type {
7
7
  BlueprintLibraryOption,
8
8
  } from "./src/BlueprintNodeConfigSidebar";
9
9
  export * from "./src/graph";
10
- export { resolveBlueprintConfigSource, resolveBlueprintNodeTypeLabel } from "./src/graph/document";
10
+ export { patchNodeConfigSource } from "./src/graph/document";
11
+ export { resolveBlueprintConfigSource, resolveBlueprintNodeTypeLabel, getBlueprintNodeTypeLabel, getLifecyclePhaseLabel, BLUEPRINT_CONFIG_TYPE_LABEL_KEYS, BLUEPRINT_LIFECYCLE_PHASE_KEYS } from "./src/graph/document";
11
12
  export type { BlueprintConfigSource } from "./src/graph/document";
12
13
  export { documentToRunnableGraph } from "./src/runtime/document-to-runnable-graph";
13
14
  export { BlueprintExecutionLogPanel } from "./src/components/BlueprintExecutionLogPanel";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/react-blueprint",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "",
5
5
  "main": "index.ts",
6
6
  "exports": {
@@ -12,9 +12,10 @@
12
12
  "dayjs": "^1.11.13",
13
13
  "react-dom": "^19",
14
14
  "@xyflow/react": "^12.9.3",
15
- "@arronqzy/blueprint-dsl": "1.0.3",
16
- "@arronqzy/react-rx-store": "1.1.2",
17
- "@arronqzy/ui": "1.0.2"
15
+ "@arronqzy/blueprint-dsl": "1.0.4",
16
+ "@arronqzy/i18n": "0.1.2",
17
+ "@arronqzy/react-rx-store": "1.1.3",
18
+ "@arronqzy/ui": "1.0.4"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/react": "^19.0.1",
@@ -27,9 +28,9 @@
27
28
  "eslint": "^8.57.0",
28
29
  "typescript": "^5.5.4",
29
30
  "vite": "^5.1.4",
30
- "@arronqzy/tailwind": "1.0.2",
31
+ "@arronqzy/typescript-config": "1.0.0",
31
32
  "@arronqzy/eslint-config": "0.0.0",
32
- "@arronqzy/typescript-config": "1.0.0"
33
+ "@arronqzy/tailwind": "1.0.2"
33
34
  },
34
35
  "keywords": [],
35
36
  "author": "",
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef } from "react";
2
+ import { useI18n } from "@arronqzy/i18n/react";
2
3
 
3
4
  export type BlueprintContextMenuState =
4
5
  | {
@@ -35,6 +36,7 @@ export function BlueprintContextMenu({
35
36
  onDeleteNode,
36
37
  onDeleteEdge,
37
38
  }: BlueprintContextMenuProps) {
39
+ const { t } = useI18n();
38
40
  const menuRef = useRef<HTMLDivElement>(null);
39
41
 
40
42
  useEffect(() => {
@@ -74,7 +76,7 @@ export function BlueprintContextMenu({
74
76
  onClose();
75
77
  }}
76
78
  >
77
- 新增节点
79
+ {t("blueprint.toolbar.addNode")}
78
80
  </button>
79
81
  ) : null}
80
82
 
@@ -87,7 +89,7 @@ export function BlueprintContextMenu({
87
89
  onClose();
88
90
  }}
89
91
  >
90
- 删除节点
92
+ {t("blueprint.toolbar.deleteNode")}
91
93
  </button>
92
94
  ) : null}
93
95
 
@@ -100,7 +102,7 @@ export function BlueprintContextMenu({
100
102
  onClose();
101
103
  }}
102
104
  >
103
- 删除连线
105
+ {t("blueprint.toolbar.deleteEdge")}
104
106
  </button>
105
107
  ) : null}
106
108
  </div>
@@ -9,10 +9,10 @@ import {
9
9
  } from "@arronqzy/ui";
10
10
  import {
11
11
  LIFECYCLE_NODE_TYPE,
12
- PAGE_LIFECYCLE_LABELS,
13
12
  PAGE_LIFECYCLE_PHASES,
14
13
  type PageLifecyclePhase,
15
14
  } from "@arronqzy/blueprint-dsl";
15
+ import { useI18n } from "@arronqzy/i18n/react";
16
16
 
17
17
  import { FetchNodeConfigPanel } from "./components/FetchNodeConfigPanel";
18
18
  import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel";
@@ -20,12 +20,14 @@ import { JsonNodeConfigPanel } from "./components/JsonNodeConfigPanel";
20
20
  import { LogicNodeConfigPanel } from "./components/LogicNodeConfigPanel";
21
21
  import { ViewElementMultiSelect } from "./components/ViewElementMultiSelect";
22
22
  import {
23
+ getLifecyclePhaseLabel,
23
24
  patchNodeConfigSource,
24
25
  pruneViewElementIds,
25
26
  resolveBlueprintConfigSource,
26
27
  resolveViewElementIds,
27
28
  type BlueprintConfigSource,
28
29
  type BlueprintGraphNode,
30
+ type BlueprintNodeRole,
29
31
  } from "./graph/document";
30
32
 
31
33
  export type BlueprintViewElementOption = {
@@ -67,6 +69,16 @@ export type BlueprintNodeConfigSidebarProps = {
67
69
  ) => void;
68
70
  };
69
71
 
72
+ const ROLE_LABEL_KEYS: Record<BlueprintNodeRole, string> = {
73
+ blueprint: "blueprint.config.roleBlueprint",
74
+ lifecycle: "blueprint.config.roleLifecycle",
75
+ and: "blueprint.config.roleAnd",
76
+ fetch: "blueprint.config.roleFetch",
77
+ json: "blueprint.config.roleJson",
78
+ logic: "blueprint.config.roleLogic",
79
+ clock: "blueprint.config.roleClock",
80
+ };
81
+
70
82
  export function BlueprintNodeConfigSidebar({
71
83
  node,
72
84
  viewElementOptions = [],
@@ -75,6 +87,7 @@ export function BlueprintNodeConfigSidebar({
75
87
  onUpdateAllowFalseSignalPropagation,
76
88
  onUpdateNode,
77
89
  }: BlueprintNodeConfigSidebarProps) {
90
+ const { t } = useI18n();
78
91
  const configSource = resolveBlueprintConfigSource(node);
79
92
  const linkedViewElementIds = resolveViewElementIds(node);
80
93
  const existingViewElementIdSet = useMemo(
@@ -108,28 +121,19 @@ export function BlueprintNodeConfigSidebar({
108
121
  viewElementOptions.length,
109
122
  ]);
110
123
 
124
+ const roleLabel = t(ROLE_LABEL_KEYS[node.role] ?? "blueprint.config.roleLogic");
125
+
111
126
  return (
112
127
  <div className="flex h-full min-h-0 flex-col overflow-hidden bg-background text-foreground">
113
128
  <div className="shrink-0 border-b border-border px-3 py-2">
114
- <div className="text-xs font-semibold">蓝图节点配置</div>
129
+ <div className="text-xs font-semibold">{t("blueprint.config.title")}</div>
115
130
  <div className="mt-0.5 text-[11px] text-muted-foreground">
116
- {node.role === "blueprint"
117
- ? "蓝图节点"
118
- : node.role === "lifecycle"
119
- ? "生命周期节点"
120
- : node.role === "and"
121
- ? "并运算节点"
122
- : node.role === "fetch"
123
- ? "数据源节点"
124
- : node.role === "json"
125
- ? "JSON 节点"
126
- : "逻辑节点"}{" "}
127
- · {node.id}
131
+ {roleLabel} · {node.id}
128
132
  </div>
129
133
  </div>
130
134
  <div className="min-h-0 flex-1 space-y-3 overflow-auto p-3 text-xs">
131
135
  <label className="block space-y-1">
132
- <span className="text-muted-foreground">节点名称</span>
136
+ <span className="text-muted-foreground">{t("blueprint.config.nodeName")}</span>
133
137
  <Input
134
138
  value={node.label}
135
139
  onChange={(e: ChangeEvent<HTMLInputElement>) =>
@@ -140,7 +144,7 @@ export function BlueprintNodeConfigSidebar({
140
144
  </label>
141
145
 
142
146
  <label className="block space-y-1">
143
- <span className="text-muted-foreground">配置类型</span>
147
+ <span className="text-muted-foreground">{t("blueprint.config.configType")}</span>
144
148
  <Select
145
149
  value={configSource}
146
150
  onValueChange={(value: string) => {
@@ -163,25 +167,25 @@ export function BlueprintNodeConfigSidebar({
163
167
  <SelectValue />
164
168
  </SelectTrigger>
165
169
  <SelectContent>
166
- <SelectItem value="blueprint">蓝图配置</SelectItem>
167
- <SelectItem value="logic">逻辑配置</SelectItem>
168
- <SelectItem value="and">并运算</SelectItem>
169
- <SelectItem value="lifecycle">生命周期配置</SelectItem>
170
- <SelectItem value="fetch">数据源获取</SelectItem>
171
- <SelectItem value="json">JSON 节点</SelectItem>
172
- <SelectItem value="clock">时钟</SelectItem>
173
- <SelectItem value="view">视图节点配置</SelectItem>
170
+ <SelectItem value="blueprint">{t("blueprint.config.configBlueprint")}</SelectItem>
171
+ <SelectItem value="logic">{t("blueprint.config.configLogic")}</SelectItem>
172
+ <SelectItem value="and">{t("blueprint.config.configAnd")}</SelectItem>
173
+ <SelectItem value="lifecycle">{t("blueprint.config.configLifecycle")}</SelectItem>
174
+ <SelectItem value="fetch">{t("blueprint.config.configFetch")}</SelectItem>
175
+ <SelectItem value="json">{t("blueprint.config.configJson")}</SelectItem>
176
+ <SelectItem value="clock">{t("blueprint.config.configClock")}</SelectItem>
177
+ <SelectItem value="view">{t("blueprint.config.configView")}</SelectItem>
174
178
  </SelectContent>
175
179
  </Select>
176
180
  </label>
177
181
 
178
182
  {configSource === "view" ? (
179
183
  <div className="block space-y-1">
180
- <span className="text-muted-foreground">关联视图节点</span>
184
+ <span className="text-muted-foreground">{t("blueprint.config.linkedViewNodes")}</span>
181
185
  <ViewElementMultiSelect
182
186
  options={viewElementOptions}
183
187
  value={linkedViewElementIds}
184
- placeholder="选择视图节点"
188
+ placeholder={t("blueprint.config.selectViewNode")}
185
189
  onChange={(next) =>
186
190
  onUpdateNode(node.id, {
187
191
  viewElementIds: next,
@@ -192,15 +196,16 @@ export function BlueprintNodeConfigSidebar({
192
196
  />
193
197
  {linkedViewElementIds.length === 0 ? (
194
198
  <p className="text-[11px] text-muted-foreground">
195
- 可多选视图画布节点;关联后仍在此配置蓝图节点,视图属性请在视图面板中编辑。
199
+ {t("blueprint.config.viewMultiHint")}
196
200
  </p>
197
201
  ) : (
198
202
  <p className="text-[11px] text-muted-foreground">
199
- 已关联 {linkedViewElementIds.length} 个视图节点:
200
- {linkedViewElementIds
201
- .map((id) => viewElementLabelById.get(id) ?? id)
202
- .join("、")}
203
- 。
203
+ {t("blueprint.config.linkedViewCount", {
204
+ count: linkedViewElementIds.length,
205
+ names: linkedViewElementIds
206
+ .map((id) => viewElementLabelById.get(id) ?? id)
207
+ .join("、"),
208
+ })}
204
209
  </p>
205
210
  )}
206
211
  </div>
@@ -208,15 +213,12 @@ export function BlueprintNodeConfigSidebar({
208
213
 
209
214
  {configSource === "blueprint" ? (
210
215
  <div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
211
- <div className="font-medium text-foreground">蓝图属性</div>
216
+ <div className="font-medium text-foreground">{t("blueprint.config.blueprintAttrs")}</div>
212
217
  <p className="text-[11px] text-muted-foreground">
213
- 选中蓝图库中的蓝图后,当输入端收到<strong>真信号</strong>
214
- 时才会执行该蓝图;执行完成后从输出端发出
215
- <strong>真信号</strong>(含嵌套蓝图输出值与当前节点信息),执行失败则发出
216
- <strong>假信号</strong>。
218
+ {t("blueprint.config.blueprintAttrsHint")}
217
219
  </p>
218
220
  <label className="block space-y-1">
219
- <span className="text-muted-foreground">引用蓝图库</span>
221
+ <span className="text-muted-foreground">{t("blueprint.config.refLibrary")}</span>
220
222
  <Select
221
223
  value={node.libraryBlueprintId ?? "__none__"}
222
224
  onValueChange={(value: string) => {
@@ -227,10 +229,10 @@ export function BlueprintNodeConfigSidebar({
227
229
  }}
228
230
  >
229
231
  <SelectTrigger className="h-8">
230
- <SelectValue placeholder="选择蓝图库中的蓝图" />
232
+ <SelectValue placeholder={t("blueprint.config.selectLibraryBlueprint")} />
231
233
  </SelectTrigger>
232
234
  <SelectContent className="z-[10100]">
233
- <SelectItem value="__none__">未关联</SelectItem>
235
+ <SelectItem value="__none__">{t("blueprint.config.unlinked")}</SelectItem>
234
236
  {blueprintLibraryOptions.map((opt) => (
235
237
  <SelectItem key={opt.id} value={opt.id}>
236
238
  {opt.label}
@@ -241,7 +243,7 @@ export function BlueprintNodeConfigSidebar({
241
243
  </label>
242
244
  {!node.libraryBlueprintId ? (
243
245
  <p className="text-[11px] text-muted-foreground">
244
- 请先从蓝图库选择要引用的蓝图。
246
+ {t("blueprint.config.selectLibraryFirst")}
245
247
  </p>
246
248
  ) : null}
247
249
  <label className="flex items-start gap-2 pt-1">
@@ -254,7 +256,7 @@ export function BlueprintNodeConfigSidebar({
254
256
  className="mt-0.5 h-3.5 w-3.5 shrink-0 rounded border border-input"
255
257
  />
256
258
  <span className="text-[11px] leading-relaxed text-muted-foreground">
257
- 允许假信号传递:开启后,节点输出假信号时不会阻塞任务链,错误信息会继续向下游传递。
259
+ {t("blueprint.config.allowFalsePropagateBlueprint")}
258
260
  </span>
259
261
  </label>
260
262
  </div>
@@ -262,15 +264,15 @@ export function BlueprintNodeConfigSidebar({
262
264
 
263
265
  {configSource === "lifecycle" ? (
264
266
  <div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
265
- <div className="font-medium text-foreground">生命周期钩子</div>
267
+ <div className="font-medium text-foreground">{t("blueprint.config.lifecycleHook")}</div>
266
268
  <p className="text-[11px] text-muted-foreground">
267
- 生命周期节点<strong>没有输入口</strong>,仅右侧输出口。
269
+ {t("blueprint.config.lifecycleNoInput")}
268
270
  {node.lifecyclePhase === "blueprintActivated"
269
- ? "当本蓝图被其他蓝图的蓝图配置节点引用且收到真信号时,自动向下游发出真信号,输出值为父级传入的输入数据。"
270
- : "当页面进入对应生命周期时,自动向下游发出真/假信号。"}
271
+ ? t("blueprint.config.lifecycleBlueprintActivatedHint")
272
+ : t("blueprint.config.lifecyclePageHint")}
271
273
  </p>
272
274
  <label className="block space-y-1">
273
- <span className="text-muted-foreground">监听阶段</span>
275
+ <span className="text-muted-foreground">{t("blueprint.config.listenPhase")}</span>
274
276
  <Select
275
277
  value={node.lifecyclePhase ?? "mounted"}
276
278
  onValueChange={(value: string) =>
@@ -288,7 +290,7 @@ export function BlueprintNodeConfigSidebar({
288
290
  <SelectContent>
289
291
  {PAGE_LIFECYCLE_PHASES.map((phase) => (
290
292
  <SelectItem key={phase} value={phase}>
291
- {PAGE_LIFECYCLE_LABELS[phase]}
293
+ {getLifecyclePhaseLabel(t, phase)}
292
294
  </SelectItem>
293
295
  ))}
294
296
  </SelectContent>
@@ -296,7 +298,7 @@ export function BlueprintNodeConfigSidebar({
296
298
  </label>
297
299
  {node.parentId ? (
298
300
  <p className="text-[11px] text-muted-foreground">
299
- 所属蓝图节点:{node.parentId}
301
+ {t("blueprint.config.parentBlueprintNode", { id: node.parentId })}
300
302
  </p>
301
303
  ) : null}
302
304
  </div>
@@ -316,12 +318,9 @@ export function BlueprintNodeConfigSidebar({
316
318
 
317
319
  {configSource === "and" ? (
318
320
  <div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
319
- <div className="font-medium text-foreground">并运算</div>
321
+ <div className="font-medium text-foreground">{t("blueprint.config.andTitle")}</div>
320
322
  <p className="text-[11px] text-muted-foreground">
321
- 左侧两个输入口 <strong>inA</strong>、<strong>inB</strong>。
322
- 每个输入口可连 n 条线,同端口任一为真则该端口视为真(或)。
323
- 仅当 <strong>inA 与 inB 均为真信号</strong> 时,从输出口发出真信号;
324
- 否则发出假信号。
323
+ {t("blueprint.config.andHint")}
325
324
  </p>
326
325
  </div>
327
326
  ) : null}
@@ -331,7 +330,7 @@ export function BlueprintNodeConfigSidebar({
331
330
  <LogicNodeConfigPanel node={node} onUpdateNode={onUpdateNode} />
332
331
  {node.parentId ? (
333
332
  <p className="text-[11px] text-muted-foreground">
334
- 所属蓝图节点:{node.parentId}
333
+ {t("blueprint.config.parentBlueprintNode", { id: node.parentId })}
335
334
  </p>
336
335
  ) : null}
337
336
  </>
@@ -339,7 +338,7 @@ export function BlueprintNodeConfigSidebar({
339
338
 
340
339
  {configSource !== "blueprint" && configSource !== "and" ? (
341
340
  <div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
342
- <div className="font-medium text-foreground">任务链执行</div>
341
+ <div className="font-medium text-foreground">{t("blueprint.config.taskChain")}</div>
343
342
  <label className="flex items-start gap-2">
344
343
  <input
345
344
  type="checkbox"
@@ -350,7 +349,7 @@ export function BlueprintNodeConfigSidebar({
350
349
  className="mt-0.5 h-3.5 w-3.5 shrink-0 rounded border border-input"
351
350
  />
352
351
  <span className="text-[11px] leading-relaxed text-muted-foreground">
353
- 允许假信号传递:默认假信号会阻塞任务链;开启后继续向下游传递假信号与错误信息。
352
+ {t("blueprint.config.allowFalsePropagateDefault")}
354
353
  </span>
355
354
  </label>
356
355
  </div>
@@ -17,10 +17,11 @@ import {
17
17
  cn,
18
18
  } from "@arronqzy/ui";
19
19
  import {
20
- PAGE_LIFECYCLE_LABELS,
21
- type ExecutionTraceEntry,
20
+ type ExecutionTraceEntry,
22
21
  type PageLifecyclePhase,
23
22
  } from "@arronqzy/blueprint-dsl";
23
+ import { useI18n } from "@arronqzy/i18n/react";
24
+ import { getLifecyclePhaseLabel } from "../graph/document";
24
25
 
25
26
  import type { ExecutionLogSettings } from "../library/execution-log-settings";
26
27
 
@@ -151,6 +152,7 @@ export function BlueprintExecutionLogPanel({
151
152
  hasSavedRuns = false,
152
153
  lifecyclePhase,
153
154
  }: BlueprintExecutionLogPanelProps) {
155
+ const { t } = useI18n();
154
156
  const scrollRef = useRef<HTMLDivElement>(null);
155
157
  const bottomRef = useRef<HTMLDivElement>(null);
156
158
  const prevEntryCountRef = useRef(entries.length);
@@ -160,8 +162,8 @@ export function BlueprintExecutionLogPanel({
160
162
 
161
163
  const phaseLabel = useMemo(() => {
162
164
  if (!lifecyclePhase) return null;
163
- return PAGE_LIFECYCLE_LABELS[lifecyclePhase as PageLifecyclePhase] ?? lifecyclePhase;
164
- }, [lifecyclePhase]);
165
+ return getLifecyclePhaseLabel(t, lifecyclePhase as PageLifecyclePhase);
166
+ }, [lifecyclePhase, t]);
165
167
 
166
168
  const latestEntryPreview = useMemo(() => {
167
169
  const latest = entries[entries.length - 1];
@@ -211,9 +213,9 @@ export function BlueprintExecutionLogPanel({
211
213
  <div className="shrink-0 border-b border-border px-3 py-2">
212
214
  <div className="flex items-start justify-between gap-2">
213
215
  <div className="min-w-0">
214
- <div className="text-xs font-semibold">蓝图任务输出日志</div>
216
+ <div className="text-xs font-semibold">{t("blueprint.log.title")}</div>
215
217
  <div className="mt-0.5 text-[11px] text-muted-foreground">
216
- {phaseLabel ? `模拟场景:${phaseLabel}` : "选择生命周期节点并开始调试"}
218
+ {phaseLabel ? t("blueprint.log.simulateSceneWithPhase", { phaseLabel }) : t("blueprint.log.selectLifecycleToDebug")}
217
219
  </div>
218
220
  </div>
219
221
  <div className="flex shrink-0 items-center gap-1">
@@ -223,8 +225,8 @@ export function BlueprintExecutionLogPanel({
223
225
  variant="outline"
224
226
  size="icon"
225
227
  className="h-7 w-7 shrink-0"
226
- content="立即清空 IndexedDB 中所有已保存的蓝图执行日志"
227
- aria-label="清空 IndexedDB 日志"
228
+ content={t("blueprint.log.clearIdbTooltip")}
229
+ aria-label={t("blueprint.log.clearIdbAria")}
228
230
  disabled={!hasSavedRuns}
229
231
  onClick={() => void onClearAllSaved()}
230
232
  >
@@ -236,8 +238,8 @@ export function BlueprintExecutionLogPanel({
236
238
  variant="outline"
237
239
  size="icon"
238
240
  className="h-7 w-7 shrink-0"
239
- content="清空当前日志,并重置画布执行高亮"
240
- aria-label="清空日志"
241
+ content={t("blueprint.log.clearCurrentTooltip")}
242
+ aria-label={t("blueprint.log.clearCurrentAria")}
241
243
  disabled={entries.length === 0}
242
244
  onClick={onClear}
243
245
  >
@@ -255,7 +257,7 @@ export function BlueprintExecutionLogPanel({
255
257
  >
256
258
  {entries.length === 0 ? (
257
259
  <p className="text-[11px] text-muted-foreground">
258
- 暂无执行记录。使用工具栏「走完全流程」或「下一步」开始模拟。
260
+ {t("blueprint.log.emptyHint")}
259
261
  </p>
260
262
  ) : (
261
263
  <div className="space-y-0">
@@ -279,7 +281,7 @@ export function BlueprintExecutionLogPanel({
279
281
  <div className="mt-2 grid gap-2">
280
282
  <div>
281
283
  <div className="mb-1 text-[10px] font-medium text-muted-foreground">
282
- 输入
284
+ {t("blueprint.log.input")}
283
285
  </div>
284
286
  <pre className="max-h-32 overflow-auto rounded border border-border/60 bg-background p-2 font-mono text-[10px] leading-relaxed text-foreground">
285
287
  {formatJson(entry.inputs)}
@@ -287,7 +289,7 @@ export function BlueprintExecutionLogPanel({
287
289
  </div>
288
290
  <div>
289
291
  <div className="mb-1 text-[10px] font-medium text-muted-foreground">
290
- 输出
292
+ {t("blueprint.log.output")}
291
293
  </div>
292
294
  <pre
293
295
  className={cn(
@@ -322,7 +324,7 @@ export function BlueprintExecutionLogPanel({
322
324
  onClick={() => scrollToBottom("smooth")}
323
325
  >
324
326
  <ArrowDownIcon className="h-3.5 w-3.5 shrink-0" />
325
- <span className="truncate">有新日志</span>
327
+ <span className="truncate">{t("blueprint.log.hasNewLogs")}</span>
326
328
  {latestEntryPreview ? (
327
329
  <span className="truncate opacity-90">· {latestEntryPreview}</span>
328
330
  ) : null}
@@ -338,26 +340,26 @@ export function BlueprintExecutionLogPanel({
338
340
  variant="outline"
339
341
  size="sm"
340
342
  className="w-full"
341
- content="将当前调试日志保存到 IndexedDB"
343
+ content={t("blueprint.log.saveLogTooltip")}
342
344
  disabled={entries.length === 0}
343
345
  onClick={onSave}
344
346
  >
345
- 保存日志
347
+ {t("blueprint.log.saveLog")}
346
348
  </LogTooltipButton>
347
349
  <LogTooltipButton
348
350
  type="button"
349
351
  variant="outline"
350
352
  size="sm"
351
353
  className="w-full"
352
- content="将当前调试日志导出为 JSON 文件"
354
+ content={t("blueprint.log.exportJsonTooltip")}
353
355
  disabled={entries.length === 0}
354
356
  onClick={onExport}
355
357
  >
356
- 导出 JSON
358
+ {t("blueprint.log.exportJson")}
357
359
  </LogTooltipButton>
358
360
  </div>
359
361
 
360
- <LogTooltip content="调试运行完成后,自动将日志写入 IndexedDB">
362
+ <LogTooltip content={t("blueprint.log.autoSaveTooltip")}>
361
363
  <label className="flex cursor-default items-center gap-2">
362
364
  <input
363
365
  type="checkbox"
@@ -366,15 +368,15 @@ export function BlueprintExecutionLogPanel({
366
368
  className="h-3.5 w-3.5 rounded border border-input"
367
369
  />
368
370
  <span className="text-[11px] text-muted-foreground">
369
- 运行完成后自动保存到 IndexedDB
371
+ {t("blueprint.log.autoSaveAfterRun")}
370
372
  </span>
371
373
  </label>
372
374
  </LogTooltip>
373
375
 
374
376
  <div className="grid grid-cols-2 gap-2">
375
- <LogTooltip content="IndexedDB 中最多保留的日志条数,超出后自动删除最旧记录">
377
+ <LogTooltip content={t("blueprint.log.maxSavedCountTooltip")}>
376
378
  <div className="space-y-1">
377
- <Label className="text-[11px] text-muted-foreground">最多保存条数</Label>
379
+ <Label className="text-[11px] text-muted-foreground">{t("blueprint.log.maxSavedCount")}</Label>
378
380
  <Input
379
381
  type="number"
380
382
  min={1}
@@ -386,13 +388,13 @@ export function BlueprintExecutionLogPanel({
386
388
  })
387
389
  }
388
390
  className="h-8"
389
- aria-label="IndexedDB 最多保存日志条数"
391
+ aria-label={t("blueprint.log.maxSavedCountAria")}
390
392
  />
391
393
  </div>
392
394
  </LogTooltip>
393
- <LogTooltip content="超过保留天数的已保存日志将在清理时被删除">
395
+ <LogTooltip content={t("blueprint.log.retentionDaysTooltip")}>
394
396
  <div className="space-y-1">
395
- <Label className="text-[11px] text-muted-foreground">保留天数</Label>
397
+ <Label className="text-[11px] text-muted-foreground">{t("blueprint.log.retentionDays")}</Label>
396
398
  <Input
397
399
  type="number"
398
400
  min={1}
@@ -404,7 +406,7 @@ export function BlueprintExecutionLogPanel({
404
406
  })
405
407
  }
406
408
  className="h-8"
407
- aria-label="日志保留天数"
409
+ aria-label={t("blueprint.log.retentionDaysAria")}
408
410
  />
409
411
  </div>
410
412
  </LogTooltip>
@@ -415,10 +417,10 @@ export function BlueprintExecutionLogPanel({
415
417
  variant="secondary"
416
418
  size="sm"
417
419
  className="w-full"
418
- content="按保留天数与条数上限,清理 IndexedDB 中的日志记录"
420
+ content={t("blueprint.log.cleanupTooltip")}
419
421
  onClick={onApplyRetention}
420
422
  >
421
- 清理过期与超额
423
+ {t("blueprint.log.cleanupExpired")}
422
424
  </LogTooltipButton>
423
425
  </div>
424
426
  </div>
@@ -7,6 +7,7 @@ import {
7
7
  SelectTrigger,
8
8
  SelectValue,
9
9
  } from "@arronqzy/ui";
10
+ import { useI18n } from "@arronqzy/i18n/react";
10
11
 
11
12
  import type { BlueprintLibraryListItem } from "../library/types";
12
13
 
@@ -26,11 +27,13 @@ function groupItems(items: BlueprintLibraryListItem[]) {
26
27
  export function BlueprintLibrarySelect({
27
28
  items,
28
29
  value,
29
- placeholder = "蓝图库",
30
+ placeholder,
30
31
  onSelect,
31
32
  }: BlueprintLibrarySelectProps) {
33
+ const { t } = useI18n();
32
34
  const { saved, imported } = groupItems(items);
33
35
  const hasItems = items.length > 0;
36
+ const resolvedPlaceholder = placeholder ?? t("blueprint.toolbar.library");
34
37
 
35
38
  return (
36
39
  <Select
@@ -39,12 +42,16 @@ export function BlueprintLibrarySelect({
39
42
  disabled={!hasItems}
40
43
  >
41
44
  <SelectTrigger className="h-7 w-[168px] text-xs">
42
- <SelectValue placeholder={hasItems ? placeholder : "蓝图库为空"} />
45
+ <SelectValue
46
+ placeholder={
47
+ hasItems ? resolvedPlaceholder : t("blueprint.toolbar.libraryEmpty")
48
+ }
49
+ />
43
50
  </SelectTrigger>
44
51
  <SelectContent className="z-[10100]">
45
52
  {saved.length > 0 ? (
46
53
  <SelectGroup>
47
- <SelectLabel>已保存</SelectLabel>
54
+ <SelectLabel>{t("blueprint.toolbar.saved")}</SelectLabel>
48
55
  {saved.map((item) => (
49
56
  <SelectItem key={item.id} value={item.id} className="text-xs">
50
57
  {item.name}
@@ -54,7 +61,7 @@ export function BlueprintLibrarySelect({
54
61
  ) : null}
55
62
  {imported.length > 0 ? (
56
63
  <SelectGroup>
57
- <SelectLabel>已导入</SelectLabel>
64
+ <SelectLabel>{t("blueprint.toolbar.imported")}</SelectLabel>
58
65
  {imported.map((item) => (
59
66
  <SelectItem key={item.id} value={item.id} className="text-xs">
60
67
  {item.name}