codexmate 0.1.27 → 0.1.29

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 (48) hide show
  1. package/README.md +53 -44
  2. package/README.vi.md +45 -36
  3. package/README.zh.md +51 -42
  4. package/cli/config-bootstrap.js +2 -9
  5. package/cli/session-usage.js +2 -2
  6. package/cli.js +407 -40
  7. package/package.json +1 -1
  8. package/web-ui/app.js +7 -0
  9. package/web-ui/index.html +12 -0
  10. package/web-ui/logic.sessions.mjs +6 -4
  11. package/web-ui/modules/app.computed.session.mjs +52 -33
  12. package/web-ui/modules/app.methods.agents.mjs +36 -0
  13. package/web-ui/modules/app.methods.codex-config.mjs +3 -1
  14. package/web-ui/modules/app.methods.index.mjs +2 -0
  15. package/web-ui/modules/app.methods.providers.mjs +12 -4
  16. package/web-ui/modules/app.methods.session-actions.mjs +10 -8
  17. package/web-ui/modules/app.methods.session-browser.mjs +3 -1
  18. package/web-ui/modules/app.methods.session-trash.mjs +16 -16
  19. package/web-ui/modules/app.methods.theme.mjs +39 -0
  20. package/web-ui/modules/app.methods.web-ui-preferences.mjs +9 -0
  21. package/web-ui/modules/i18n/locales/en.mjs +80 -2
  22. package/web-ui/modules/i18n/locales/ja.mjs +80 -2
  23. package/web-ui/modules/i18n/locales/vi.mjs +80 -2
  24. package/web-ui/modules/i18n/locales/zh-tw.mjs +80 -2
  25. package/web-ui/modules/i18n/locales/zh.mjs +80 -2
  26. package/web-ui/modules/i18n.mjs +7 -0
  27. package/web-ui/partials/index/modal-config-template-agents.html +1 -1
  28. package/web-ui/partials/index/modal-health-check.html +1 -1
  29. package/web-ui/partials/index/modal-openclaw-config.html +50 -50
  30. package/web-ui/partials/index/panel-dashboard.html +1 -1
  31. package/web-ui/partials/index/panel-prompts.html +2 -2
  32. package/web-ui/partials/index/panel-sessions.html +47 -30
  33. package/web-ui/partials/index/panel-settings.html +30 -0
  34. package/web-ui/res/web-ui-render.precompiled.js +244 -157
  35. package/web-ui/session-helpers.mjs +3 -0
  36. package/web-ui/styles/base-theme.css +3 -2
  37. package/web-ui/styles/bridge-pool.css +1 -1
  38. package/web-ui/styles/controls-forms.css +1 -1
  39. package/web-ui/styles/layout-shell.css +0 -1
  40. package/web-ui/styles/sessions-preview.css +77 -0
  41. package/web-ui/styles/sessions-toolbar-trash.css +1 -1
  42. package/web-ui/styles/sessions-usage.css +1 -1
  43. package/web-ui/styles/settings-panel.css +2 -2
  44. package/web-ui/styles/theme-dark-tokens.css +83 -0
  45. package/web-ui/styles/theme-dark.css +1355 -0
  46. package/web-ui/styles/theme-ocean-tokens.css +88 -0
  47. package/web-ui/styles/theme-ocean.css +920 -0
  48. package/web-ui/styles.css +2 -0
@@ -524,6 +524,11 @@ const zh = Object.freeze({
524
524
  'toast.provider.updateFail': '更新失败',
525
525
  'toast.provider.resetSuccess': '配置已重装{backup}',
526
526
  'toast.provider.resetFail': '重装失败',
527
+
528
+ 'toast.provider.resetBackup': '(已备份: {file})',
529
+ 'toast.provider.modelRequired': '请输入模型',
530
+ 'toast.provider.addModelFail': '新增模型失败',
531
+ 'toast.provider.deleteModelFail': '删除模型失败',
527
532
  'toast.provider.fieldsRequired': '名称、URL、API Key 和模型名称必填',
528
533
  'toast.provider.urlRequired': 'URL 必填',
529
534
  'toast.claude.modelRequired': '请输入模型',
@@ -690,11 +695,12 @@ const zh = Object.freeze({
690
695
  'sessions.source.workbuddy': 'WorkBuddy AI',
691
696
  'sessions.source.pi': 'Pi',
692
697
  'sessions.source.opencode': 'OpenCode',
698
+ 'sessions.source.zed': 'Zed',
693
699
  'sessions.loadingList': '会话加载中...',
694
700
  'sessions.empty': '暂无可用会话记录',
695
701
  'sessions.unknownTime': '未知时间',
696
702
 
697
- 'sessions.query.placeholder.enabled': '关键词检索(支持 Codex/Claude/Gemini/CodeBuddy,例:claude code)',
703
+ 'sessions.query.placeholder.enabled': "关键词检索(例:claude code)",
698
704
  'sessions.query.placeholder.disabled': '当前来源暂不支持关键词检索',
699
705
  'sessions.pin': '置顶',
700
706
  'sessions.unpin': '取消置顶',
@@ -705,7 +711,7 @@ const zh = Object.freeze({
705
711
  'sessions.preview.loading': '加载中...',
706
712
  'sessions.batch.select': '批量删除',
707
713
  'sessions.batch.exit': '退出选择',
708
- 'sessions.batch.selectAll': '全选',
714
+ 'sessions.batch.selectAllVisibleN': '全选可见 {count} 项',
709
715
  'sessions.batch.unselectAll': '取消全选',
710
716
  'sessions.batch.deleteSelected': '删除选中',
711
717
  'sessions.batch.deleteSelectedN': '删除选中({count})',
@@ -720,6 +726,17 @@ const zh = Object.freeze({
720
726
  'sessions.preview.moveToTrash': '移入回收站',
721
727
  'sessions.preview.deleting': '删除中...',
722
728
  'sessions.preview.moving': '移入中...',
729
+ 'sessions.export.range': "导出范围",
730
+ 'sessions.export.default': "最近 1000 条",
731
+ 'sessions.export.all': "全部可提取消息",
732
+ 'sessions.export.scopeHint': "全部仅取消消息数量上限,不是完整状态备份。",
733
+ 'sessions.export.truncated': "本次导出已截断;需要更早内容可选择全部后重新导出。",
734
+ 'sessions.search.coverage': "已扫描 {scanned} / {candidates} 条候选会话",
735
+ 'sessions.search.partial': "本次候选尚未扫描完,请缩小来源或项目范围。",
736
+ 'sessions.search.scopeHint': "仅反映本次候选范围,不代表全部历史记录。",
737
+ 'sessions.empty.filtered': "当前筛选没有匹配记录。",
738
+ 'sessions.empty.source': "当前来源未发现可展示记录,请确认本机已有会话并刷新。",
739
+ 'sessions.empty.clearFilters': "清除筛选",
723
740
  'sessions.preview.export': '导出记录',
724
741
  'sessions.preview.exporting': '导出中...',
725
742
  'sessions.preview.convert': '生成派生会话',
@@ -1132,6 +1149,12 @@ const zh = Object.freeze({
1132
1149
  'settings.language.meta': '选择 Web UI 的显示语言',
1133
1150
  'settings.language.label': '界面语言',
1134
1151
  'settings.language.hint': '语言偏好会保存在当前浏览器。',
1152
+ 'settings.theme.title': '外观主题',
1153
+ 'settings.theme.meta': '选择 Web UI 的配色主题',
1154
+ 'settings.theme.light': '浅色',
1155
+ 'settings.theme.dark': '深色',
1156
+ 'settings.theme.ocean': '海洋蓝',
1157
+ 'settings.theme.hint': '主题偏好会自动保存,刷新后保持不变。',
1135
1158
  'settings.sharePrefix.title': '分享命令前缀',
1136
1159
  'settings.sharePrefix.meta': '影响 Web UI 里“复制分享命令”的前缀',
1137
1160
  'settings.sharePrefix.label': '前缀',
@@ -1206,6 +1229,20 @@ const zh = Object.freeze({
1206
1229
  'settings.trash.retentionLabel': '保留天数',
1207
1230
  'settings.trash.retentionUnit': '天',
1208
1231
  'settings.trash.retentionHint': '范围 1-365 天,默认 30 天。每次加载回收站时自动清理过期记录。',
1232
+
1233
+ 'settings.trash.loadCountFail': '加载回收站数量失败',
1234
+ 'settings.trash.loadFail': '加载回收站失败',
1235
+ 'settings.trash.restored': '会话已恢复',
1236
+ 'settings.trash.restoreFail': '恢复失败',
1237
+ 'settings.trash.purged': '已彻底删除',
1238
+ 'settings.trash.purgeFail': '彻底删除失败',
1239
+ 'settings.trash.cleared': '回收站已清空',
1240
+ 'settings.trash.clearFail': '清空回收站失败',
1241
+ 'settings.trash.purgeConfirmTitle': '彻底删除回收站记录',
1242
+ 'settings.trash.purgeConfirmMessage': '该会话将从回收站永久删除,且无法恢复。',
1243
+ 'settings.trash.clearConfirmTitle': '清空回收站',
1244
+ 'settings.trash.clearConfirmMessage': '该操作会永久删除回收站中的全部会话,且无法恢复。',
1245
+ 'settings.trash.clearConfirmButton': '全部清空',
1209
1246
  'settings.trash.count': '共 {count} 项',
1210
1247
  'settings.trash.retentionShort': '保留 {days} 天',
1211
1248
  'settings.trash.disabled': '回收站已关闭',
@@ -1246,6 +1283,9 @@ const zh = Object.freeze({
1246
1283
  'settings.reset.button': '重装配置',
1247
1284
  'settings.reset.loading': '重装中...',
1248
1285
 
1286
+ 'settings.reset.confirmTitle': '确认重置配置',
1287
+ 'settings.reset.confirmMessage': '将先备份 config.toml,再用默认配置覆盖当前配置。确定继续吗?',
1288
+
1249
1289
  // Market (Skills)
1250
1290
  'market.title': 'Skills 概览',
1251
1291
  'market.subtitle': '切换目标并查看本地 Skills。',
@@ -1356,6 +1396,44 @@ const zh = Object.freeze({
1356
1396
  'modal.openclaw.quick.subtitle': '按 3 步完成:填 Provider 和模型,写入编辑器,保存并应用。',
1357
1397
  'modal.openclaw.quick.step2': '点击写入编辑器',
1358
1398
  'modal.openclaw.structured.writeHint': '写入编辑器会重排 JSON,注释可能丢失。',
1399
+
1400
+ 'modal.openclaw.next': '下一步 →',
1401
+ 'modal.openclaw.prev': '← 上一步',
1402
+ 'modal.openclaw.finish': '完成 ✓',
1403
+ 'modal.openclaw.quick.advancedOptions': '高级选项',
1404
+ 'modal.openclaw.quick.providerNamePlaceholder': '例如: custom-myapi',
1405
+ 'modal.openclaw.quick.modelIdHint': '必填,例如: gpt-4',
1406
+ 'modal.openclaw.quick.modelNameHint': '可选,用于显示',
1407
+ 'modal.openclaw.structured.title': '结构化配置(高级)',
1408
+ 'modal.openclaw.structured.primaryModel': '主模型',
1409
+ 'modal.openclaw.structured.fallbackModels': '备选模型',
1410
+ 'modal.openclaw.structured.providerModelPlaceholder': '例如: provider/model',
1411
+ 'modal.openclaw.structured.addFallback': '添加备选',
1412
+ 'modal.openclaw.structured.workspacePlaceholder': '例如: ~/.openclaw/workspace',
1413
+ 'modal.openclaw.structured.timeoutPlaceholder': '例如: 600',
1414
+ 'modal.openclaw.structured.contextTokensPlaceholder': '例如: 4096',
1415
+ 'modal.openclaw.structured.maxConcurrentPlaceholder': '例如: 2',
1416
+ 'modal.openclaw.structured.envVars': '环境变量',
1417
+ 'modal.openclaw.structured.addEnvVar': '添加变量',
1418
+ 'modal.openclaw.structured.addAllow': '添加 allow',
1419
+ 'modal.openclaw.structured.addDeny': '添加 deny',
1420
+ 'modal.openclaw.structured.allowPlaceholder': '例如: fs.read*',
1421
+ 'modal.openclaw.structured.denyPlaceholder': '例如: net.*',
1422
+ 'modal.openclaw.structured.providersTitle': 'Providers(只读)',
1423
+ 'modal.openclaw.structured.noProviders': '未发现 providers 配置(可能使用环境变量或外部配置)。',
1424
+ 'modal.openclaw.structured.sourceLabel': '来源',
1425
+ 'modal.openclaw.structured.active': '使用中',
1426
+ 'modal.openclaw.structured.noFields': '未配置字段',
1427
+ 'modal.openclaw.structured.missingProviders': '使用中的 provider 未在配置中显示:{list}。',
1428
+ 'modal.openclaw.structured.agentsTitle': 'Agents(只读)',
1429
+ 'modal.openclaw.structured.noAgents': '未发现 agents.list 配置。',
1430
+ 'modal.openclaw.structured.themeLabel': '主题',
1431
+ 'modal.openclaw.structured.emojiLabel': '表情',
1432
+ 'modal.openclaw.structured.avatarLabel': '头像',
1433
+ 'modal.openclaw.editorLabel': 'OpenClaw 配置(JSON5)',
1434
+ 'modal.openclaw.editorPlaceholder': '在这里编辑 OpenClaw 配置(JSON5)',
1435
+ 'modal.openclaw.defaultConfigHint': '默认配置始终映射当前 openclaw.json,请直接使用“保存并应用”。',
1436
+ 'modal.openclaw.saveOnlyHint': '保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。',
1359
1437
  'openclaw.action.applyAria': '应用 OpenClaw 配置:{name}',
1360
1438
  'openclaw.action.editAria': '编辑 OpenClaw 配置:{name}',
1361
1439
  'openclaw.action.deleteAria': '删除 OpenClaw 配置:{name}'
@@ -57,6 +57,13 @@ export function createI18nMethods() {
57
57
  languageOptions() {
58
58
  return getAvailableLanguages();
59
59
  },
60
+ paren(text) {
61
+ const value = String(text);
62
+ const lang = normalizeLang(this.lang);
63
+ return (lang === 'zh' || lang === 'zh-tw' || lang === 'ja')
64
+ ? `(${value})`
65
+ : ` (${value})`;
66
+ },
60
67
  currentLanguageLabel() {
61
68
  return getLanguageMeta(this.lang).nativeName || '中文';
62
69
  },
@@ -123,7 +123,7 @@
123
123
  <div class="form-hint">
124
124
  {{ agentsPath || t('common.notLoaded') }}
125
125
  <span v-if="agentsPath">
126
- ({{ agentsExists ? t('common.exists') : t('common.notExistsWillCreateOnSave') }})
126
+ {{ paren(agentsExists ? t('common.exists') : t('common.notExistsWillCreateOnSave')) }}
127
127
  </span>
128
128
  </div>
129
129
  </div>
@@ -7,7 +7,7 @@
7
7
  <template v-else>
8
8
  <div class="form-hint">
9
9
  {{ healthCheckResult.ok ? t('config.health.ok') : t('config.health.fail') }}
10
- <span v-if="healthCheckResult.issues">({{ t('config.health.issues', { count: healthCheckResult.issues.length }) }})</span>
10
+ <span v-if="healthCheckResult.issues">{{ paren(t('config.health.issues', { count: healthCheckResult.issues.length })) }}</span>
11
11
  </div>
12
12
 
13
13
  <div v-if="healthCheckResult.remote && healthCheckResult.remote.type === 'remote-health-check'" class="form-hint">
@@ -12,7 +12,7 @@
12
12
  <div class="form-hint">
13
13
  {{ openclawConfigPath || t('common.notLoaded') }}
14
14
  <span v-if="openclawConfigPath">
15
- ({{ openclawConfigExists ? t('common.exists') : t('common.notExistsWillCreateOnApply') }})
15
+ {{ paren(openclawConfigExists ? t('common.exists') : t('common.notExistsWillCreateOnApply')) }}
16
16
  </span>
17
17
  </div>
18
18
  <div class="btn-group" style="justify-content:flex-start;">
@@ -52,7 +52,7 @@
52
52
  <div class="accordion-content" v-show="openclawAccordionStep === 1">
53
53
  <div class="form-group">
54
54
  <label class="form-label">{{ t('field.providerName') }}</label>
55
- <input v-model="openclawQuick.providerName" class="form-input" placeholder="例如: custom-myapi" @input="validateProviderName">
55
+ <input v-model="openclawQuick.providerName" class="form-input" :placeholder="t('modal.openclaw.quick.providerNamePlaceholder')" @input="validateProviderName">
56
56
  <div class="form-hint" :class="{ 'hint-error': !openclawValidation.providerName.valid }">
57
57
  {{ openclawValidation.providerName.message || t('modal.openclaw.quick.providerHint') }}
58
58
  </div>
@@ -87,7 +87,7 @@
87
87
  </div>
88
88
  <div class="accordion-actions">
89
89
  <button class="btn btn-confirm btn-sm" @click="nextAccordionStep" :disabled="!openclawQuick.providerName">
90
- 下一步 →
90
+ {{ t('modal.openclaw.next') }}
91
91
  </button>
92
92
  </div>
93
93
  </div>
@@ -111,13 +111,13 @@
111
111
  <label class="form-label">{{ t('field.modelId') }}</label>
112
112
  <input v-model="openclawQuick.modelId" class="form-input" :placeholder="t('placeholder.modelIdExample')" @input="validateModelId">
113
113
  <div class="form-hint" :class="{ 'hint-error': !openclawValidation.modelId.valid }">
114
- {{ openclawValidation.modelId.message || '必填,例如: gpt-4' }}
114
+ {{ openclawValidation.modelId.message || t('modal.openclaw.quick.modelIdHint') }}
115
115
  </div>
116
116
  </div>
117
117
  <div class="form-group">
118
118
  <label class="form-label">{{ t('field.displayName') }}</label>
119
119
  <input v-model="openclawQuick.modelName" class="form-input" :placeholder="t('placeholder.modelNameOptional')">
120
- <div class="form-hint">可选,用于显示</div>
120
+ <div class="form-hint">{{ t('modal.openclaw.quick.modelNameHint') }}</div>
121
121
  </div>
122
122
  <div class="form-group">
123
123
  <label class="form-label">{{ t('field.contextAndMaxOutput') }}</label>
@@ -129,10 +129,10 @@
129
129
  </div>
130
130
  <div class="accordion-actions">
131
131
  <button class="btn btn-cancel btn-sm" @click="prevAccordionStep">
132
- ← 上一步
132
+ {{ t('modal.openclaw.prev') }}
133
133
  </button>
134
134
  <button class="btn btn-confirm btn-sm" @click="nextAccordionStep" :disabled="!openclawQuick.modelId">
135
- 下一步 →
135
+ {{ t('modal.openclaw.next') }}
136
136
  </button>
137
137
  </div>
138
138
  </div>
@@ -146,7 +146,7 @@
146
146
  <template v-else>3</template>
147
147
  </span>
148
148
  <span class="accordion-title">{{ t('modal.openclaw.quick.optionsTitle') }}</span>
149
- <span class="accordion-status">高级选项</span>
149
+ <span class="accordion-status">{{ t('modal.openclaw.quick.advancedOptions') }}</span>
150
150
  <svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
151
151
  <path d="M6 9l6 6 6-6"/>
152
152
  </svg>
@@ -167,10 +167,10 @@
167
167
  <div class="form-hint">{{ t('modal.openclaw.quick.optionsHint') }}</div>
168
168
  <div class="accordion-actions">
169
169
  <button class="btn btn-cancel btn-sm" @click="prevAccordionStep">
170
- ← 上一步
170
+ {{ t('modal.openclaw.prev') }}
171
171
  </button>
172
172
  <button class="btn btn-confirm btn-sm" @click="finishAccordionStep">
173
- 完成 ✓
173
+ {{ t('modal.openclaw.finish') }}
174
174
  </button>
175
175
  </div>
176
176
  </div>
@@ -184,55 +184,55 @@
184
184
 
185
185
  <div class="structured-section">
186
186
  <div class="structured-header">
187
- <span class="structured-title">结构化配置(高级)</span>
187
+ <span class="structured-title">{{ t('modal.openclaw.structured.title') }}</span>
188
188
  <span class="form-hint">{{ t('modal.openclaw.structured.writeHint') }}</span>
189
189
  </div>
190
190
  <div class="structured-grid">
191
191
  <div class="structured-card">
192
192
  <div class="structured-card-title">Agents Defaults</div>
193
193
  <div class="form-group">
194
- <label class="form-label">主模型</label>
195
- <input v-model="openclawStructured.agentPrimary" class="form-input" placeholder="例如: provider/model">
194
+ <label class="form-label">{{ t('modal.openclaw.structured.primaryModel') }}</label>
195
+ <input v-model="openclawStructured.agentPrimary" class="form-input" :placeholder="t('modal.openclaw.structured.providerModelPlaceholder')">
196
196
  </div>
197
197
  <div class="form-group">
198
- <label class="form-label">备选模型</label>
198
+ <label class="form-label">{{ t('modal.openclaw.structured.fallbackModels') }}</label>
199
199
  <div class="list-row" v-for="(item, index) in openclawStructured.agentFallbacks" :key="'fallback-' + index">
200
- <input v-model="openclawStructured.agentFallbacks[index]" class="form-input" placeholder="例如: provider/model">
201
- <button class="btn-mini delete" @click="removeOpenclawFallback(index)">删除</button>
200
+ <input v-model="openclawStructured.agentFallbacks[index]" class="form-input" :placeholder="t('modal.openclaw.structured.providerModelPlaceholder')">
201
+ <button class="btn-mini delete" @click="removeOpenclawFallback(index)">{{ t('common.delete') }}</button>
202
202
  </div>
203
- <button class="btn-mini" @click="addOpenclawFallback">添加备选</button>
203
+ <button class="btn-mini" @click="addOpenclawFallback">{{ t('modal.openclaw.structured.addFallback') }}</button>
204
204
  </div>
205
205
  <div class="form-group">
206
206
  <label class="form-label">Workspace</label>
207
- <input v-model="openclawStructured.workspace" class="form-input" placeholder="例如: ~/.openclaw/workspace">
207
+ <input v-model="openclawStructured.workspace" class="form-input" :placeholder="t('modal.openclaw.structured.workspacePlaceholder')">
208
208
  </div>
209
209
  <div class="form-group">
210
210
  <label class="form-label">Timeout(s)</label>
211
- <input v-model="openclawStructured.timeout" class="form-input" placeholder="例如: 600">
211
+ <input v-model="openclawStructured.timeout" class="form-input" :placeholder="t('modal.openclaw.structured.timeoutPlaceholder')">
212
212
  </div>
213
213
  <div class="form-group">
214
214
  <label class="form-label">Context Tokens</label>
215
- <input v-model="openclawStructured.contextTokens" class="form-input" placeholder="例如: 4096">
215
+ <input v-model="openclawStructured.contextTokens" class="form-input" :placeholder="t('modal.openclaw.structured.contextTokensPlaceholder')">
216
216
  </div>
217
217
  <div class="form-group">
218
218
  <label class="form-label">Max Concurrent</label>
219
- <input v-model="openclawStructured.maxConcurrent" class="form-input" placeholder="例如: 2">
219
+ <input v-model="openclawStructured.maxConcurrent" class="form-input" :placeholder="t('modal.openclaw.structured.maxConcurrentPlaceholder')">
220
220
  </div>
221
221
  </div>
222
222
 
223
223
  <div class="structured-card">
224
224
  <div class="structured-card-title">Env</div>
225
225
  <div class="form-group">
226
- <label class="form-label">环境变量</label>
226
+ <label class="form-label">{{ t('modal.openclaw.structured.envVars') }}</label>
227
227
  <div class="list-row" v-for="(item, index) in openclawStructured.envItems" :key="'env-' + index">
228
228
  <input v-model="item.key" class="form-input" placeholder="KEY">
229
229
  <input v-model="item.value" class="form-input" :type="item.show ? 'text' : 'password'" placeholder="VALUE">
230
230
  <button class="btn-mini" @click="toggleOpenclawEnvItem(index)">
231
- {{ item.show ? '隐藏' : '显示' }}
231
+ {{ item.show ? t('common.hide') : t('common.show') }}
232
232
  </button>
233
- <button class="btn-mini delete" @click="removeOpenclawEnvItem(index)">删除</button>
233
+ <button class="btn-mini delete" @click="removeOpenclawEnvItem(index)">{{ t('common.delete') }}</button>
234
234
  </div>
235
- <button class="btn-mini" @click="addOpenclawEnvItem">添加变量</button>
235
+ <button class="btn-mini" @click="addOpenclawEnvItem">{{ t('modal.openclaw.structured.addEnvVar') }}</button>
236
236
  </div>
237
237
  </div>
238
238
 
@@ -250,34 +250,34 @@
250
250
  <div class="form-group">
251
251
  <label class="form-label">Allow</label>
252
252
  <div class="list-row" v-for="(item, index) in openclawStructured.toolsAllow" :key="'allow-' + index">
253
- <input v-model="openclawStructured.toolsAllow[index]" class="form-input" placeholder="例如: fs.read*">
254
- <button class="btn-mini delete" @click="removeOpenclawToolsAllow(index)">删除</button>
253
+ <input v-model="openclawStructured.toolsAllow[index]" class="form-input" :placeholder="t('modal.openclaw.structured.allowPlaceholder')">
254
+ <button class="btn-mini delete" @click="removeOpenclawToolsAllow(index)">{{ t('common.delete') }}</button>
255
255
  </div>
256
- <button class="btn-mini" @click="addOpenclawToolsAllow">添加 allow</button>
256
+ <button class="btn-mini" @click="addOpenclawToolsAllow">{{ t('modal.openclaw.structured.addAllow') }}</button>
257
257
  </div>
258
258
  <div class="form-group">
259
259
  <label class="form-label">Deny</label>
260
260
  <div class="list-row" v-for="(item, index) in openclawStructured.toolsDeny" :key="'deny-' + index">
261
- <input v-model="openclawStructured.toolsDeny[index]" class="form-input" placeholder="例如: net.*">
262
- <button class="btn-mini delete" @click="removeOpenclawToolsDeny(index)">删除</button>
261
+ <input v-model="openclawStructured.toolsDeny[index]" class="form-input" :placeholder="t('modal.openclaw.structured.denyPlaceholder')">
262
+ <button class="btn-mini delete" @click="removeOpenclawToolsDeny(index)">{{ t('common.delete') }}</button>
263
263
  </div>
264
- <button class="btn-mini" @click="addOpenclawToolsDeny">添加 deny</button>
264
+ <button class="btn-mini" @click="addOpenclawToolsDeny">{{ t('modal.openclaw.structured.addDeny') }}</button>
265
265
  </div>
266
266
  </div>
267
267
 
268
268
  <div class="structured-card">
269
- <div class="structured-card-title">Providers(只读)</div>
269
+ <div class="structured-card-title">{{ t('modal.openclaw.structured.providersTitle') }}</div>
270
270
  <div v-if="openclawProviders.length === 0" class="form-hint">
271
- 未发现 providers 配置(可能使用环境变量或外部配置)。
271
+ {{ t('modal.openclaw.structured.noProviders') }}
272
272
  </div>
273
273
  <div v-else class="provider-list">
274
274
  <div class="provider-item" v-for="(provider, index) in openclawProviders" :key="provider.key + '-' + provider.source + '-' + index">
275
275
  <div class="provider-header">
276
276
  <span class="provider-name">{{ provider.key }}</span>
277
- <span class="provider-source">来源: {{ provider.source }}</span>
278
- <span v-if="provider.isActive" class="pill configured">使用中</span>
277
+ <span class="provider-source">{{ t('modal.openclaw.structured.sourceLabel') }}: {{ provider.source }}</span>
278
+ <span v-if="provider.isActive" class="pill configured">{{ t('modal.openclaw.structured.active') }}</span>
279
279
  </div>
280
- <div v-if="provider.fields.length === 0" class="form-hint">未配置字段</div>
280
+ <div v-if="provider.fields.length === 0" class="form-hint">{{ t('modal.openclaw.structured.noFields') }}</div>
281
281
  <div v-else class="provider-fields">
282
282
  <div class="provider-field" v-for="field in provider.fields" :key="provider.key + '-' + field.key">
283
283
  <span class="provider-field-key">{{ field.key }}</span>
@@ -287,14 +287,14 @@
287
287
  </div>
288
288
  </div>
289
289
  <div v-if="openclawMissingProviders.length" class="form-hint">
290
- 使用中的 provider 未在配置中显示:{{ openclawMissingProviders.join(', ') }}。
290
+ {{ t('modal.openclaw.structured.missingProviders', { list: openclawMissingProviders.join(', ') }) }}
291
291
  </div>
292
292
  </div>
293
293
 
294
294
  <div class="structured-card">
295
- <div class="structured-card-title">Agents(只读)</div>
295
+ <div class="structured-card-title">{{ t('modal.openclaw.structured.agentsTitle') }}</div>
296
296
  <div v-if="openclawAgentsList.length === 0" class="form-hint">
297
- 未发现 agents.list 配置。
297
+ {{ t('modal.openclaw.structured.noAgents') }}
298
298
  </div>
299
299
  <div v-else class="agent-list">
300
300
  <div class="agent-item" v-for="(agent, index) in openclawAgentsList" :key="agent.key + '-' + index">
@@ -303,41 +303,41 @@
303
303
  <span class="agent-id">ID: {{ agent.id }}</span>
304
304
  </div>
305
305
  <div class="agent-meta" v-if="agent.theme || agent.emoji || agent.avatar">
306
- <span v-if="agent.theme">主题: {{ agent.theme }}</span>
307
- <span v-if="agent.emoji">表情: {{ agent.emoji }}</span>
308
- <span v-if="agent.avatar">头像: {{ agent.avatar }}</span>
306
+ <span v-if="agent.theme">{{ t('modal.openclaw.structured.themeLabel') }}: {{ agent.theme }}</span>
307
+ <span v-if="agent.emoji">{{ t('modal.openclaw.structured.emojiLabel') }}: {{ agent.emoji }}</span>
308
+ <span v-if="agent.avatar">{{ t('modal.openclaw.structured.avatarLabel') }}: {{ agent.avatar }}</span>
309
309
  </div>
310
310
  </div>
311
311
  </div>
312
312
  </div>
313
313
  </div>
314
314
  <div class="btn-group">
315
- <button class="btn btn-confirm secondary" @click="syncOpenclawStructuredFromText">从文本刷新</button>
315
+ <button class="btn btn-confirm secondary" @click="syncOpenclawStructuredFromText">{{ t('common.refreshFromText') }}</button>
316
316
  <button class="btn btn-confirm" @click="applyOpenclawStructuredToText">{{ t('common.writeToEditor') }}</button>
317
317
  </div>
318
318
  </div>
319
319
 
320
320
  <div class="form-group">
321
- <label class="form-label">OpenClaw 配置(JSON5)</label>
321
+ <label class="form-label">{{ t('modal.openclaw.editorLabel') }}</label>
322
322
  <textarea
323
323
  v-model="openclawEditing.content"
324
324
  class="form-input template-editor"
325
325
  spellcheck="false"
326
326
  :readonly="openclawSaving || openclawApplying"
327
- placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
327
+ :placeholder="t('modal.openclaw.editorPlaceholder')"></textarea>
328
328
  <div class="template-editor-warning">
329
- <span v-if="openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name)">默认配置始终映射当前 openclaw.json,请直接使用“保存并应用”。</span>
330
- <span v-else>保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。</span>
329
+ <span v-if="openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name)">{{ t('modal.openclaw.defaultConfigHint') }}</span>
330
+ <span v-else>{{ t('modal.openclaw.saveOnlyHint') }}</span>
331
331
  </div>
332
332
  </div>
333
333
 
334
334
  <div class="btn-group">
335
- <button class="btn btn-cancel" @click="closeOpenclawConfigModal" :disabled="openclawSaving || openclawApplying">取消</button>
335
+ <button class="btn btn-cancel" @click="closeOpenclawConfigModal" :disabled="openclawSaving || openclawApplying">{{ t('common.cancel') }}</button>
336
336
  <button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving || openclawApplying || (openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name))">
337
- {{ openclawSaving ? '保存中...' : '保存' }}
337
+ {{ openclawSaving ? t('common.saving') : t('common.save') }}
338
338
  </button>
339
339
  <button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawSaving || openclawApplying">
340
- {{ openclawApplying ? '应用中...' : '保存并应用' }}
340
+ {{ openclawApplying ? t('common.applying') : t('common.saveApply') }}
341
341
  </button>
342
342
  </div>
343
343
  </div>
@@ -35,7 +35,7 @@
35
35
  <div class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
36
36
  <div class="doctor-health-title">
37
37
  {{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
38
- <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
38
+ <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">{{ paren(t('dashboard.health.issues', { count: healthCheckResult.issues.length })) }}</span>
39
39
  </div>
40
40
  </div>
41
41
  <div v-if="healthCheckResult.report" class="doctor-action-list">
@@ -40,7 +40,7 @@
40
40
  <div class="form-hint">
41
41
  {{ agentsPath || t('common.notLoaded') }}
42
42
  <span v-if="agentsPath">
43
- ({{ agentsExists ? t('common.exists') : t('common.notExistsWillCreateOnSave') }})
43
+ {{ paren(agentsExists ? t('common.exists') : t('common.notExistsWillCreateOnSave')) }}
44
44
  </span>
45
45
  <span v-if="agentsPath" class="prompts-token-hint">· {{ t('prompts.tokenCount.hint', { count: agentsTokenCount }) }}</span>
46
46
  </div>
@@ -279,7 +279,7 @@
279
279
  <div class="form-hint">
280
280
  {{ sysPromptPath || t('common.notLoaded') }}
281
281
  <span v-if="sysPromptPath">
282
- ({{ sysPromptExists ? t('common.exists') : t('common.notExistsWillCreateOnSave') }})
282
+ {{ paren(sysPromptExists ? t('common.exists') : t('common.notExistsWillCreateOnSave')) }}
283
283
  </span>
284
284
  <span v-if="sysPromptPath" class="prompts-token-hint">· {{ t('prompts.tokenCount.hint', { count: sysPromptTokenCount }) }}</span>
285
285
  </div>