@cniot/mdd-editor 0.3.9 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { CnButton, CnCard, CnMessage as Message, Input } from '@cainiaofe/cn-ui';
2
+ import { CnButton, CnCard, CnMessage as Message, Dialog, Input } from '@cainiaofe/cn-ui';
3
3
  import {
4
4
  DEFAULT_BRIDGE_URL,
5
5
  getPageStatus,
@@ -17,7 +17,11 @@ import { EVENT_KEY } from '$src/common/const';
17
17
  const MIN_BRIDGE_VERSION = '0.1.4';
18
18
  const START_COMMAND = `npm i -g @cniot/mdd-ai-bridge
19
19
  mdd-ai-bridge`;
20
+ const DEFAULT_MAIN_PROJECT_PATH = '/Users/caoruiqi/Documents/code/owtb/tms-mach36';
21
+ const MAIN_PROJECT_PATH_STORAGE_KEY = 'mdd-local-ai-i18n-main-project-path';
20
22
  const trimEndSlash = (value = '') => value.replace(/\/+$/, '');
23
+ const hasChineseText = (value = '') => /[\u4e00-\u9fff]/.test(String(value));
24
+ const hasLetterText = (value = '') => /[a-zA-Z]/.test(String(value));
21
25
 
22
26
  const parseSchema = (value) => {
23
27
  if (!value) return null;
@@ -306,6 +310,184 @@ const collectLinkedPageReferences = ({ schemaJson, scriptInfo, rootCode, extraSo
306
310
  return mergeL4References([...schemaReferences, ...scriptReferences, ...extraReferences], rootCode);
307
311
  };
308
312
 
313
+ const I18N_SCHEMA_FIELDS = new Set([
314
+ 'label',
315
+ 'tooltip',
316
+ 'title',
317
+ 'placeholder',
318
+ 'children',
319
+ 'text',
320
+ 'submitText',
321
+ 'resetText',
322
+ 'cancelText',
323
+ 'secondConfirmTitle',
324
+ 'secondConfirmContent',
325
+ 'emptyText',
326
+ 'moreText',
327
+ ]);
328
+
329
+ const createI18nSchemaCandidates = (value, path = '$', candidates = []) => {
330
+ if (Array.isArray(value)) {
331
+ value.forEach((item, index) => createI18nSchemaCandidates(item, `${path}[${index}]`, candidates));
332
+ return candidates;
333
+ }
334
+ if (!value || typeof value !== 'object') return candidates;
335
+
336
+ Object.keys(value).forEach((key) => {
337
+ const nextPath = `${path}.${key}`;
338
+ const item = value[key];
339
+ if (typeof item === 'string' && I18N_SCHEMA_FIELDS.has(key) && (hasChineseText(item) || hasLetterText(item))) {
340
+ candidates.push({
341
+ path: nextPath,
342
+ field: key,
343
+ text: item,
344
+ });
345
+ }
346
+ createI18nSchemaCandidates(item, nextPath, candidates);
347
+ });
348
+ return candidates;
349
+ };
350
+
351
+ const createScriptI18nPreview = (script = '') => {
352
+ const seen = new Set();
353
+ const stringLikePattern = /(['"`])(?:(?!\1).)*[\u4e00-\u9fffA-Za-z](?:(?!\1).)*\1/;
354
+ return String(script)
355
+ .split('\n')
356
+ .map((line, index) => ({
357
+ lineNumber: index + 1,
358
+ line: line.trim(),
359
+ }))
360
+ .filter((item) => stringLikePattern.test(item.line))
361
+ .filter((item) => hasChineseText(item.line) || hasLetterText(item.line))
362
+ .filter((item) => {
363
+ const key = item.line;
364
+ if (seen.has(key)) return false;
365
+ seen.add(key);
366
+ return true;
367
+ })
368
+ .slice(0, 40);
369
+ };
370
+
371
+ const formatI18nCandidateList = (items = []) => {
372
+ if (!items.length) return '- 暂未在编辑器当前 schema 中预扫到 label/tooltip 等文案,请仍然完整检查 mdd.schema.json。';
373
+ return items
374
+ .slice(0, 80)
375
+ .map((item) => `- ${item.path} (${item.field}): ${item.text}`)
376
+ .join('\n');
377
+ };
378
+
379
+ const formatScriptPreviewList = (items = []) => {
380
+ if (!items.length) return '- 暂未在编辑器当前 script 中预扫到明显文案行,请仍然完整检查 mdd.script.jsx。';
381
+ return items.map((item) => `- L${item.lineNumber}: ${item.line}`).join('\n');
382
+ };
383
+
384
+ const formatLinkedI18nWorkspaceList = ({ linkedPages = {}, syncLinkedPages = false }) => {
385
+ if (!syncLinkedPages) {
386
+ return '本次未开启递归子页面,只处理当前 L4 页面。';
387
+ }
388
+ const pages = linkedPages?.pages || [];
389
+ const failed = linkedPages?.failed || [];
390
+ const syncedText = pages.length
391
+ ? pages
392
+ .map((page) => `- ${page.name || '未命名页面'} (${page.code}):${page.dir}`)
393
+ .join('\n')
394
+ : '- 未发现已同步的关联子页面。';
395
+ const failedText = failed.length
396
+ ? `\n\n递归扫描中有以下关联页面未能同步,请不要修改这些页面,最后报告给用户:\n${failed
397
+ .map((page) => `- ${page.name || '未命名页面'} (${page.code}):${page.reason || '未能自动同步'}`)
398
+ .join('\n')}`
399
+ : '';
400
+ return `本次已开启递归子页面,请同时处理当前页面和以下已同步的关联 L4 页面。每个页面目录都包含 mdd.schema.json 和 mdd.script.jsx:\n${syncedText}${failedText}`;
401
+ };
402
+
403
+ const buildL4I18nPrompt = ({
404
+ pageCode,
405
+ workspacePath,
406
+ mainProjectPath,
407
+ schemaJson,
408
+ scriptInfo,
409
+ linkedPages,
410
+ syncLinkedPages,
411
+ }) => {
412
+ const normalizedProjectPath = trimEndSlash(mainProjectPath || '');
413
+ const schemaCandidates = createI18nSchemaCandidates(schemaJson);
414
+ const scriptPreview = createScriptI18nPreview(scriptInfo);
415
+ const linkedWorkspaceText = formatLinkedI18nWorkspaceList({
416
+ linkedPages,
417
+ syncLinkedPages,
418
+ });
419
+
420
+ return `你现在要为一个 MDD L4 页面做多语言同步。请直接修改本地文件,不要只给建议。
421
+
422
+ 当前 L4 页面:
423
+ - 页面 Code:${pageCode}
424
+ - L4 工作区:${workspacePath}
425
+ - Schema 文件:${workspacePath}/mdd.schema.json
426
+ - Script 文件:${workspacePath}/mdd.script.jsx
427
+
428
+ 递归子页面:
429
+ ${linkedWorkspaceText}
430
+
431
+ 主工程:
432
+ - 工程根目录:${normalizedProjectPath}
433
+ - 英文词条:${normalizedProjectPath}/src/i18n/strings/en-US.json
434
+ - 越南语词条:${normalizedProjectPath}/src/i18n/strings/vi-VN.json
435
+
436
+ 目标:
437
+ 1. 扫描当前 L4 页面${syncLinkedPages ? '以及上面列出的所有已同步关联子 L4 页面' : ''}的 mdd.schema.json,处理所有面向用户展示的文案,不限中文;英文 UI 文案也必须处理。重点包括 label、tooltip,也包括 title、placeholder、children、text、submitText、resetText、cancelText、secondConfirmTitle、secondConfirmContent 等同类字段。
438
+ 2. 英文 label/tooltip 也属于需要同步的文案,例如 ResourceId、ResourceName、Status、id、edit template、enable、disable、edit、Add template 等,都要作为 i18n key 去检查 en-US.json 和 vi-VN.json。
439
+ 3. 扫描当前 L4 页面${syncLinkedPages ? '以及上面列出的所有已同步关联子 L4 页面' : ''}的 mdd.script.jsx,找出 JSX、弹窗、按钮、消息提示、表格列、选项、错误提示等用户可见硬编码文案。中文、英文、英文短语、数字/英文混合短语都算用户可见文案。
440
+ 4. 对 mdd.script.jsx 中未国际化的用户可见硬编码文案,引入并使用 $i18n 包裹。若文件还没有 import,请添加:
441
+ import $i18n from '@src/i18n';
442
+ 使用方式为 $i18n.get('文案 key')。不要处理注释、日志、接口字段名、对象 key、路径、埋点 code、非用户可见文本。
443
+ 5. mdd.schema.json 中的 label/tooltip 等 key 通常由 MDD 渲染引擎自动 i18n.tr 处理,原则上保留 schema 原始文案 key,不要为了国际化随意改 schema 结构;只需要为这些 key 补齐主工程翻译词条。
444
+ 6. 将所有需要翻译的文案 key 去重后,同步到主工程的 en-US.json 和 vi-VN.json:
445
+ - JSON 是扁平 key/value 格式,key 保持原文,不管原文是中文还是英文。
446
+ - 如果 key 已存在,必须跳过,不覆盖已有翻译。
447
+ - 如果 key 不存在,两个文件都要新增。
448
+ - 对英文 key:en-US.json 的 value 可以使用更规范的英文展示文案,例如 ResourceId 可写为 Resource ID、enable 可写为 Enable;vi-VN.json 的 value 必须翻译成越南语。
449
+ - 对中文 key:en-US.json 翻译成英文,vi-VN.json 翻译成越南语。
450
+ - 保持 JSON 合法、格式稳定,尽量维持原文件缩进和排序风格。
451
+ 7. i18n JSON 文件可能非常大,不要用 cat、sed 大段打印或把完整文件读进对话上下文。请优先用脚本精确查重和增量写入,例如:
452
+ node - <<'NODE'
453
+ const fs = require('fs');
454
+ const files = [
455
+ '${normalizedProjectPath}/src/i18n/strings/en-US.json',
456
+ '${normalizedProjectPath}/src/i18n/strings/vi-VN.json',
457
+ ];
458
+ const keys = [
459
+ // 在这里填入从 L4 页面收集并去重后的候选 key
460
+ ];
461
+ for (const file of files) {
462
+ const json = JSON.parse(fs.readFileSync(file, 'utf8'));
463
+ const missing = keys.filter((key) => !Object.prototype.hasOwnProperty.call(json, key));
464
+ console.log(file, 'missing:', missing);
465
+ }
466
+ NODE
467
+ 如果需要写入,也请用类似脚本读取 JSON 对象、只给 missing key 赋值、再 JSON.stringify(data, null, 2) 写回;写入前后只输出新增数量和新增 key 列表,不要输出完整 JSON。
468
+ 8. 翻译要求:
469
+ - 英文使用简洁准确的后台/物流系统 UI 文案,按钮短语首字母大写即可,不要机械直译。
470
+ - 越南语使用自然的越南语业务系统文案,保留变量占位符、数字、标点和专有名词。
471
+ - 带 {0}、{name}、\${value}、%s 等占位符的文案,翻译后必须完整保留占位符。
472
+ 9. 完成后请自检:
473
+ - 所有被处理页面的 mdd.schema.json 都是合法 JSON。
474
+ - en-US.json、vi-VN.json 是合法 JSON。
475
+ - 所有被处理页面的 mdd.script.jsx 都没有破坏 React/JSX 语法。
476
+ - 没有重复 key,没有覆盖已有 key。
477
+
478
+ 编辑器预扫到的当前页面 schema 候选文案如下,包含中文和英文;请以实际文件为准再完整补充,不要因为文案是英文就跳过:
479
+ ${formatI18nCandidateList(schemaCandidates)}
480
+
481
+ 编辑器预扫到的当前页面 script 候选行如下,请以实际文件为准识别真正的用户可见文案,不要因为文案是英文就跳过:
482
+ ${formatScriptPreviewList(scriptPreview)}
483
+
484
+ 请现在开始修改这些文件,并在最后输出:
485
+ - 每个 L4 页面分别包裹了哪些文案
486
+ - en-US.json 新增了多少条
487
+ - vi-VN.json 新增了多少条
488
+ - 是否有跳过的已存在 key`;
489
+ };
490
+
309
491
  const createLinkedPageExtraSources = (data = {}, prefix = 'payload') => {
310
492
  const sourceFields = [
311
493
  'schemaInfo',
@@ -452,6 +634,10 @@ const normalizeLinkedPagePayload = (reference, pageData) => {
452
634
  };
453
635
 
454
636
  const buildLinkedPagePayload = async (payload, config) => {
637
+ if (!config?.syncLinkedPages) {
638
+ return payload;
639
+ }
640
+
455
641
  const rootSchemaJson = parseSchema(payload.schemaInfo) || {};
456
642
  const resolver = getLinkedPageResolver(config);
457
643
  let rootPageData = null;
@@ -621,6 +807,14 @@ export default function LocalAIDrawer(props) {
621
807
  const [lastSummary, setLastSummary] = React.useState('');
622
808
  const [pullError, setPullError] = React.useState(null);
623
809
  const [lastDiffSummary, setLastDiffSummary] = React.useState(null);
810
+ const [syncLinkedPages, setSyncLinkedPages] = React.useState(false);
811
+ const [i18nDialogVisible, setI18nDialogVisible] = React.useState(false);
812
+ const [i18nMainProjectPath, setI18nMainProjectPath] = React.useState(() => {
813
+ if (typeof window === 'undefined') return DEFAULT_MAIN_PROJECT_PATH;
814
+ return window.localStorage?.getItem(MAIN_PROJECT_PATH_STORAGE_KEY) || DEFAULT_MAIN_PROJECT_PATH;
815
+ });
816
+ const [i18nPrompt, setI18nPrompt] = React.useState('');
817
+ const [i18nWorkspaceInfo, setI18nWorkspaceInfo] = React.useState(null);
624
818
 
625
819
  const schemaJson = schema?.getAllJSON?.() || {};
626
820
  const pageCode = getPageCode(schemaJson, pageMeta);
@@ -684,6 +878,29 @@ export default function LocalAIDrawer(props) {
684
878
  [expectedWorkspacePath, pageCode],
685
879
  );
686
880
 
881
+ const currentI18nPrompt = React.useMemo(
882
+ () =>
883
+ buildL4I18nPrompt({
884
+ pageCode,
885
+ workspacePath: i18nWorkspaceInfo?.dir || workspacePath || expectedWorkspacePath,
886
+ mainProjectPath: i18nMainProjectPath,
887
+ schemaJson,
888
+ scriptInfo: scriptInfo?.script || '',
889
+ linkedPages: i18nWorkspaceInfo?.linkedPages,
890
+ syncLinkedPages,
891
+ }),
892
+ [
893
+ expectedWorkspacePath,
894
+ i18nMainProjectPath,
895
+ i18nWorkspaceInfo,
896
+ pageCode,
897
+ schemaJson,
898
+ scriptInfo,
899
+ syncLinkedPages,
900
+ workspacePath,
901
+ ],
902
+ );
903
+
687
904
  const handleCopy = React.useCallback(async (text, successText) => {
688
905
  try {
689
906
  await copyText(text);
@@ -719,7 +936,10 @@ export default function LocalAIDrawer(props) {
719
936
  setPullError(null);
720
937
  setLastDiffSummary(null);
721
938
  try {
722
- const payload = await buildLinkedPagePayload(getPayload(), config);
939
+ const payload = await buildLinkedPagePayload(getPayload(), {
940
+ ...config,
941
+ syncLinkedPages,
942
+ });
723
943
  const res = await pushPage(baseURL, pageCode, payload);
724
944
  setWorkspacePath(res?.dir || '');
725
945
  setBridgeInfo((prev) => ({
@@ -728,7 +948,7 @@ export default function LocalAIDrawer(props) {
728
948
  }));
729
949
  setStatus('已同步到本地');
730
950
  setLastSummary(createLinkedPushSummary(res, pageCode));
731
- if (res?.linkedPages?.totalReferences > 0) {
951
+ if (syncLinkedPages && res?.linkedPages?.totalReferences > 0) {
732
952
  Message.success(
733
953
  `已发送到本地 AI 工作区,扫描到直接子 L4 页面 ${res.linkedPages.directCount || 0} 个,递归共 ${res.linkedPages.totalReferences} 个`,
734
954
  );
@@ -742,6 +962,80 @@ export default function LocalAIDrawer(props) {
742
962
  }
743
963
  };
744
964
 
965
+ const ensureCurrentWorkspace = async () => {
966
+ const statusRes = await getPageStatus(baseURL, pageCode);
967
+ if (statusRes?.exists && !syncLinkedPages) {
968
+ return {
969
+ ...statusRes,
970
+ created: false,
971
+ recursiveSynced: false,
972
+ };
973
+ }
974
+
975
+ const payload = await buildLinkedPagePayload(getPayload(), {
976
+ ...config,
977
+ syncLinkedPages,
978
+ });
979
+ const pushRes = await pushPage(baseURL, pageCode, payload);
980
+ return {
981
+ ...(pushRes || {}),
982
+ exists: true,
983
+ created: !statusRes?.exists,
984
+ recursiveSynced: syncLinkedPages,
985
+ dir: pushRes?.dir,
986
+ };
987
+ };
988
+
989
+ const handleI18nConfirm = async () => {
990
+ const mainProjectPath = trimEndSlash(i18nMainProjectPath || '');
991
+ if (!mainProjectPath || !mainProjectPath.startsWith('/')) {
992
+ Message.error('请输入主工程本地绝对路径');
993
+ return;
994
+ }
995
+
996
+ setLoadingAction('i18n');
997
+ try {
998
+ if (typeof window !== 'undefined') {
999
+ window.localStorage?.setItem(MAIN_PROJECT_PATH_STORAGE_KEY, mainProjectPath);
1000
+ }
1001
+ const workspaceInfo = await ensureCurrentWorkspace();
1002
+ setWorkspacePath(workspaceInfo?.dir || workspacePath);
1003
+ setI18nWorkspaceInfo(workspaceInfo);
1004
+ const prompt = buildL4I18nPrompt({
1005
+ pageCode,
1006
+ workspacePath: workspaceInfo?.dir || workspacePath || expectedWorkspacePath,
1007
+ mainProjectPath,
1008
+ schemaJson,
1009
+ scriptInfo: scriptInfo?.script || '',
1010
+ linkedPages: workspaceInfo?.linkedPages,
1011
+ syncLinkedPages,
1012
+ });
1013
+ setI18nPrompt(prompt);
1014
+ setLastSummary(
1015
+ workspaceInfo?.recursiveSynced
1016
+ ? `已按递归模式同步当前 L4 页面和关联子页面,请复制多语言提示词交给本地 AI 执行`
1017
+ : workspaceInfo?.created
1018
+ ? '已先把当前 L4 页面同步到本地工作区,请复制多语言提示词交给本地 AI 执行'
1019
+ : '当前 L4 页面本地工作区已存在,请复制多语言提示词交给本地 AI 执行',
1020
+ );
1021
+ Message.success(
1022
+ workspaceInfo?.recursiveSynced
1023
+ ? '已递归同步 L4 工作区并生成提示词'
1024
+ : workspaceInfo?.created
1025
+ ? '已同步当前 L4 工作区并生成提示词'
1026
+ : '已生成 L4 多语言提示词',
1027
+ );
1028
+ } catch (e) {
1029
+ Message.error(e.message || '生成 L4 多语言提示词失败');
1030
+ } finally {
1031
+ setLoadingAction('');
1032
+ }
1033
+ };
1034
+
1035
+ const handleCopyI18nPrompt = React.useCallback(() => {
1036
+ handleCopy(i18nPrompt || currentI18nPrompt, '已复制 L4 多语言提示词');
1037
+ }, [currentI18nPrompt, handleCopy, i18nPrompt]);
1038
+
745
1039
  const handlePull = async () => {
746
1040
  setLoadingAction('pull');
747
1041
  setPullError(null);
@@ -923,6 +1217,26 @@ export default function LocalAIDrawer(props) {
923
1217
  <CnButton loading={loadingAction === 'rollback'} onClick={handleRollback}>
924
1218
  回滚上次更改
925
1219
  </CnButton>
1220
+ <CnButton loading={loadingAction === 'i18n'} onClick={() => setI18nDialogVisible(true)}>
1221
+ 同步L4多语言
1222
+ </CnButton>
1223
+ </div>
1224
+ <div className="mdd-local-ai-sync-options">
1225
+ <button
1226
+ type="button"
1227
+ className={`mdd-local-ai-switch${syncLinkedPages ? ' is-checked' : ''}`}
1228
+ role="switch"
1229
+ aria-checked={syncLinkedPages}
1230
+ onClick={() => setSyncLinkedPages((value) => !value)}
1231
+ >
1232
+ <span className="mdd-local-ai-switch-track">
1233
+ <span className="mdd-local-ai-switch-handle" />
1234
+ </span>
1235
+ <span className="mdd-local-ai-switch-text">
1236
+ <strong>递归同步子页面</strong>
1237
+ <span>{syncLinkedPages ? '已开启,会扫描并同步关联 L4 页面' : '已关闭,只同步当前页面'}</span>
1238
+ </span>
1239
+ </button>
926
1240
  </div>
927
1241
  <div className="mdd-local-ai-action-notice">
928
1242
  “同步本地修改”只会同步当前页面的本地变更;如果关联子 L4 页面也在本地改过,请打开对应子页面的 L4 地址,分别执行“同步本地修改”并在页面上点击保存。
@@ -1046,6 +1360,114 @@ export default function LocalAIDrawer(props) {
1046
1360
  <div>本地工作区默认在 ~/.mdd-ai-workspace。</div>
1047
1361
  </div>
1048
1362
  </CnCard>
1363
+
1364
+ <Dialog
1365
+ v2
1366
+ centered
1367
+ visible={i18nDialogVisible}
1368
+ title="同步 L4 多语言"
1369
+ width="960px"
1370
+ footer={false}
1371
+ onClose={() => setI18nDialogVisible(false)}
1372
+ onCancel={() => setI18nDialogVisible(false)}
1373
+ >
1374
+ <div className="mdd-local-ai-i18n-dialog">
1375
+ <div className="mdd-local-ai-i18n-hero">
1376
+ <div>
1377
+ <div className="mdd-local-ai-i18n-kicker">Local AI i18n task</div>
1378
+ <div className="mdd-local-ai-i18n-title">生成本地 AI 多语言任务</div>
1379
+ <div className="mdd-local-ai-i18n-desc">
1380
+ {syncLinkedPages
1381
+ ? '已开启递归子页面,会先同步当前页面和关联 L4 页面,再生成多页面多语言提示词。'
1382
+ : '先确认当前 L4 工作区已同步,再让本地 AI 补齐主工程英文和越南语词条。'}
1383
+ </div>
1384
+ <div className="mdd-local-ai-i18n-langs">
1385
+ <span>English</span>
1386
+ <span>Tiếng Việt</span>
1387
+ {syncLinkedPages ? <span>递归 L4</span> : null}
1388
+ </div>
1389
+ </div>
1390
+ <span className="mdd-local-ai-i18n-code">{pageCode}</span>
1391
+ </div>
1392
+
1393
+ <div className="mdd-local-ai-i18n-card">
1394
+ <div className="mdd-local-ai-i18n-field">
1395
+ <div className="mdd-local-ai-i18n-field-head">
1396
+ <label>主工程 tms-mach36 本地绝对路径</label>
1397
+ <span>用于定位 src/i18n/strings</span>
1398
+ </div>
1399
+ <Input
1400
+ value={i18nMainProjectPath}
1401
+ onChange={setI18nMainProjectPath}
1402
+ placeholder={DEFAULT_MAIN_PROJECT_PATH}
1403
+ className="mdd-local-ai-i18n-input"
1404
+ />
1405
+ </div>
1406
+ </div>
1407
+
1408
+ <div className="mdd-local-ai-i18n-paths">
1409
+ <div>
1410
+ <span>L4 工作区</span>
1411
+ <strong>{i18nWorkspaceInfo?.dir || workspacePath || expectedWorkspacePath}</strong>
1412
+ </div>
1413
+ <div>
1414
+ <span>英文词条</span>
1415
+ <strong>{`${trimEndSlash(i18nMainProjectPath || DEFAULT_MAIN_PROJECT_PATH)}/src/i18n/strings/en-US.json`}</strong>
1416
+ </div>
1417
+ <div>
1418
+ <span>越南语词条</span>
1419
+ <strong>{`${trimEndSlash(i18nMainProjectPath || DEFAULT_MAIN_PROJECT_PATH)}/src/i18n/strings/vi-VN.json`}</strong>
1420
+ </div>
1421
+ </div>
1422
+
1423
+ {i18nWorkspaceInfo ? (
1424
+ <div className={`mdd-local-ai-i18n-state${i18nWorkspaceInfo.created ? ' is-created' : ''}`}>
1425
+ {i18nWorkspaceInfo.recursiveSynced
1426
+ ? `已按递归模式同步当前页面和 ${i18nWorkspaceInfo.linkedPages?.syncedCount || 0} 个关联 L4 页面${
1427
+ i18nWorkspaceInfo.linkedPages?.failedCount
1428
+ ? `,另有 ${i18nWorkspaceInfo.linkedPages.failedCount} 个关联页面需要处理`
1429
+ : ''
1430
+ }。`
1431
+ : i18nWorkspaceInfo.created
1432
+ ? '当前 L4 页面此前未同步,已先发送到本地 AI 工作区。'
1433
+ : '当前 L4 页面本地工作区已存在,请打开该目录让本地 AI 执行提示词。'}
1434
+ </div>
1435
+ ) : (
1436
+ <div className="mdd-local-ai-i18n-checklist">
1437
+ <div>检查当前 L4 工作区是否存在</div>
1438
+ <div>{syncLinkedPages ? '递归同步当前页面和关联 L4 页面' : '未同步时自动发送当前页面到本地 AI'}</div>
1439
+ <div>{syncLinkedPages ? '生成覆盖父子页面的提示词并去重同步词条' : '生成提示词并要求本地 AI 去重写入 en-US.json 和 vi-VN.json'}</div>
1440
+ </div>
1441
+ )}
1442
+
1443
+ {i18nPrompt ? (
1444
+ <div className="mdd-local-ai-i18n-prompt">
1445
+ <div className="mdd-local-ai-i18n-prompt-head">
1446
+ <span>给本地 AI 的提示词</span>
1447
+ <CnButton size="small" onClick={handleCopyI18nPrompt}>
1448
+ 复制提示词
1449
+ </CnButton>
1450
+ </div>
1451
+ <pre>{i18nPrompt}</pre>
1452
+ </div>
1453
+ ) : null}
1454
+
1455
+ <div className="mdd-local-ai-i18n-footer">
1456
+ <CnButton onClick={() => setI18nDialogVisible(false)}>取消</CnButton>
1457
+ <CnButton loading={loadingAction === 'open'} onClick={handleOpen}>
1458
+ 打开 L4 工作区
1459
+ </CnButton>
1460
+ <CnButton type="primary" loading={loadingAction === 'i18n'} onClick={handleI18nConfirm}>
1461
+ {i18nPrompt ? '重新生成提示词' : '确定并生成提示词'}
1462
+ </CnButton>
1463
+ {i18nPrompt ? (
1464
+ <CnButton type="primary" onClick={handleCopyI18nPrompt}>
1465
+ 复制提示词
1466
+ </CnButton>
1467
+ ) : null}
1468
+ </div>
1469
+ </div>
1470
+ </Dialog>
1049
1471
  </div>
1050
1472
  );
1051
1473
  }