codexmate 0.0.37 → 0.0.39

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 (38) hide show
  1. package/cli/analytics-export-args.js +68 -0
  2. package/cli/builtin-proxy.js +626 -207
  3. package/cli/openai-bridge.js +541 -210
  4. package/cli/session-usage.js +187 -1
  5. package/cli.js +84 -2
  6. package/package.json +1 -1
  7. package/web-ui/app.js +12 -3
  8. package/web-ui/modules/app.computed.main-tabs.mjs +37 -30
  9. package/web-ui/modules/app.methods.claude-config.mjs +111 -9
  10. package/web-ui/modules/app.methods.openclaw-editing.mjs +48 -0
  11. package/web-ui/modules/app.methods.openclaw-persist.mjs +13 -7
  12. package/web-ui/modules/app.methods.providers.mjs +36 -10
  13. package/web-ui/modules/app.methods.runtime.mjs +76 -1
  14. package/web-ui/modules/app.methods.startup-claude.mjs +1 -0
  15. package/web-ui/modules/config-mode.computed.mjs +3 -3
  16. package/web-ui/modules/i18n.dict.mjs +13 -0
  17. package/web-ui/modules/i18n.mjs +65 -16
  18. package/web-ui/modules/skills.methods.mjs +1 -1
  19. package/web-ui/partials/index/layout-header.html +16 -46
  20. package/web-ui/partials/index/modal-openclaw-config.html +135 -71
  21. package/web-ui/partials/index/modal-webhook.html +8 -8
  22. package/web-ui/partials/index/modals-basic.html +56 -16
  23. package/web-ui/partials/index/panel-config-claude.html +20 -20
  24. package/web-ui/partials/index/panel-config-codex.html +5 -5
  25. package/web-ui/partials/index/panel-config-openclaw.html +70 -64
  26. package/web-ui/partials/index/panel-dashboard.html +62 -77
  27. package/web-ui/partials/index/panel-settings.html +28 -7
  28. package/web-ui/partials/index/panel-trash.html +14 -14
  29. package/web-ui/res/web-ui-render.precompiled.js +846 -539
  30. package/web-ui/styles/controls-forms.css +6 -0
  31. package/web-ui/styles/dashboard.css +46 -14
  32. package/web-ui/styles/layout-shell.css +45 -0
  33. package/web-ui/styles/navigation-panels.css +3 -3
  34. package/web-ui/styles/openclaw-structured.css +383 -33
  35. package/web-ui/styles/responsive.css +68 -0
  36. package/web-ui/styles/sessions-usage.css +105 -9
  37. package/web-ui/styles/settings-panel.css +4 -0
  38. package/web-ui/partials/index/panel-config-codex.html.bak +0 -337
@@ -2,11 +2,41 @@ import { DICT } from './i18n.dict.mjs';
2
2
 
3
3
  const I18N_STORAGE_KEY = 'codexmateLang';
4
4
 
5
+ const LANGUAGE_META = Object.freeze([
6
+ Object.freeze({ code: 'zh', nativeName: '中文', englishName: 'Chinese', htmlLang: 'zh-CN', dir: 'ltr' }),
7
+ Object.freeze({ code: 'en', nativeName: 'English', englishName: 'English', htmlLang: 'en', dir: 'ltr' }),
8
+ Object.freeze({ code: 'ja', nativeName: '日本語', englishName: 'Japanese', htmlLang: 'ja', dir: 'ltr' }),
9
+ Object.freeze({ code: 'vi', nativeName: 'Tiếng Việt', englishName: 'Vietnamese', htmlLang: 'vi', dir: 'ltr' })
10
+ ]);
11
+
12
+ function getAvailableLanguages() {
13
+ return LANGUAGE_META.filter((item) => item && item.code && DICT[item.code]);
14
+ }
15
+
16
+ function getDefaultLanguageMeta() {
17
+ const available = getAvailableLanguages();
18
+ return available[0] || LANGUAGE_META[0];
19
+ }
20
+
21
+ function getLanguageMeta(code) {
22
+ const normalized = typeof code === 'string' ? code.trim().toLowerCase() : '';
23
+ return getAvailableLanguages().find((item) => item.code === normalized) || getDefaultLanguageMeta();
24
+ }
25
+
5
26
  function normalizeLang(value) {
6
27
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
7
- if (normalized === 'en') return 'en';
8
- if (normalized === 'ja') return 'ja';
9
- return 'zh';
28
+ const available = getAvailableLanguages();
29
+ const fallback = available[0] && available[0].code ? available[0].code : 'zh';
30
+ return available.some((item) => item.code === normalized) ? normalized : fallback;
31
+ }
32
+
33
+ function applyDocumentLanguage(next) {
34
+ try {
35
+ if (typeof document === 'undefined' || !document.documentElement) return;
36
+ const meta = getLanguageMeta(next);
37
+ document.documentElement.lang = meta.htmlLang || meta.code || 'zh-CN';
38
+ document.documentElement.dir = meta.dir || 'ltr';
39
+ } catch (_) {}
10
40
  }
11
41
 
12
42
  function interpolate(template, params) {
@@ -26,13 +56,38 @@ export function createI18nMethods() {
26
56
  : '';
27
57
  const next = normalizeLang(saved);
28
58
  this.lang = next;
29
- try {
30
- if (typeof document !== 'undefined' && document.documentElement) {
31
- if (next === 'en') document.documentElement.lang = 'en';
32
- else if (next === 'ja') document.documentElement.lang = 'ja';
33
- else document.documentElement.lang = 'zh-CN';
59
+ applyDocumentLanguage(next);
60
+ },
61
+ languageOptions() {
62
+ return getAvailableLanguages();
63
+ },
64
+ currentLanguageLabel() {
65
+ return getLanguageMeta(this.lang).nativeName || '中文';
66
+ },
67
+ openLanguageSettings() {
68
+ if (typeof this.switchMainTab === 'function') {
69
+ this.switchMainTab('settings');
70
+ } else {
71
+ this.mainTab = 'settings';
72
+ if (typeof this.saveNavState === 'function') {
73
+ this.saveNavState();
34
74
  }
35
- } catch (_) {}
75
+ }
76
+ this.settingsTab = 'general';
77
+ this.$nextTick(() => {
78
+ const target = typeof document !== 'undefined'
79
+ ? document.getElementById('settings-language')
80
+ : null;
81
+ if (target && typeof target.scrollIntoView === 'function') {
82
+ target.scrollIntoView({ block: 'center', behavior: 'smooth' });
83
+ }
84
+ const select = typeof document !== 'undefined'
85
+ ? document.getElementById('settings-language-select')
86
+ : null;
87
+ if (select && typeof select.focus === 'function') {
88
+ select.focus();
89
+ }
90
+ });
36
91
  },
37
92
  setLang(nextLang) {
38
93
  const next = normalizeLang(nextLang);
@@ -42,13 +97,7 @@ export function createI18nMethods() {
42
97
  localStorage.setItem(I18N_STORAGE_KEY, next);
43
98
  }
44
99
  } catch (_) {}
45
- try {
46
- if (typeof document !== 'undefined' && document.documentElement) {
47
- if (next === 'en') document.documentElement.lang = 'en';
48
- else if (next === 'ja') document.documentElement.lang = 'ja';
49
- else document.documentElement.lang = 'zh-CN';
50
- }
51
- } catch (_) {}
100
+ applyDocumentLanguage(next);
52
101
  },
53
102
  t(key, params = null) {
54
103
  const lang = normalizeLang(this.lang);
@@ -491,7 +491,7 @@ export function createSkillsMethods({ api }) {
491
491
  try {
492
492
  const res = await api('import-skills', {
493
493
  targetApp: this.skillsTargetApp,
494
- imports: [skill]
494
+ items: [skill]
495
495
  });
496
496
  if (res && res.error) {
497
497
  this.showMessage(res.error, 'error');
@@ -93,27 +93,12 @@
93
93
  @click="onMainTabClick('settings', $event)">{{ t('tab.settings') }}</button>
94
94
  </div>
95
95
 
96
- <div v-if="!sessionStandalone" class="lang-fab" role="group" :aria-label="t('lang.label')">
97
- <div class="lang-choice" role="group" :aria-label="t('lang.label')">
98
- <button
99
- type="button"
100
- class="lang-choice-btn"
101
- :aria-pressed="(lang || 'zh') === 'zh'"
102
- :class="{ active: (lang || 'zh') === 'zh' }"
103
- @click="setLang('zh')">ZH</button>
104
- <button
105
- type="button"
106
- class="lang-choice-btn"
107
- :aria-pressed="(lang || 'zh') === 'en'"
108
- :class="{ active: (lang || 'zh') === 'en' }"
109
- @click="setLang('en')">EN</button>
110
- <button
111
- type="button"
112
- class="lang-choice-btn"
113
- :aria-pressed="(lang || 'zh') === 'ja'"
114
- :class="{ active: (lang || 'zh') === 'ja' }"
115
- @click="setLang('ja')">日本語</button>
116
- </div>
96
+ <div v-if="!sessionStandalone" class="lang-fab">
97
+ <button
98
+ type="button"
99
+ class="language-settings-link"
100
+ :aria-label="t('settings.language.sideLabel', { language: currentLanguageLabel() })"
101
+ @click="openLanguageSettings">{{ t('settings.language.sideLabel', { language: currentLanguageLabel() }) }}</button>
117
102
  </div>
118
103
 
119
104
  <div :class="['app-shell', { standalone: sessionStandalone }]">
@@ -282,7 +267,7 @@
282
267
  <div class="side-item-title">{{ t('side.plugins.tools') }}</div>
283
268
  <div class="side-item-meta">
284
269
  <span>{{ t('side.plugins.tools.meta') }}</span>
285
- <span>{{ promptTemplatesList.length }} templates</span>
270
+ <span>{{ t('side.plugins.templatesCount', { count: promptTemplatesList.length }) }}</span>
286
271
  </div>
287
272
  </button>
288
273
  </div>
@@ -308,40 +293,25 @@
308
293
  :class="['side-item', { active: isMainTabNavActive('trash') }]"
309
294
  @pointerdown="onMainTabPointerDown('trash', $event)"
310
295
  @click="onMainTabClick('trash', $event)">
311
- <div class="side-item-title">回收站</div>
296
+ <div class="side-item-title">{{ t('settings.trash.title') }}</div>
312
297
  <div class="side-item-meta">
313
- <span>已删除会话</span>
298
+ <span>{{ t('settings.trash.meta') }}</span>
314
299
  <span v-if="sessionTrashCount > 0" class="side-item-badge">{{ sessionTrashCount }}</span>
315
300
  </div>
316
301
  </button>
317
302
  </div>
318
303
  <div id="side-tab-new" class="side-item side-item-ghost" tabindex="-1" aria-hidden="true">
319
- <div class="side-item-title">New Tab</div>
304
+ <div class="side-item-title">{{ t('side.newTab') }}</div>
320
305
  <div class="side-item-meta"><span>&nbsp;</span></div>
321
306
  </div>
322
307
  </div>
323
308
 
324
- <div class="side-rail-lang" role="group" :aria-label="t('lang.label')">
325
- <div class="lang-choice" role="group" :aria-label="t('lang.label')">
326
- <button
327
- type="button"
328
- class="lang-choice-btn"
329
- :aria-pressed="(lang || 'zh') === 'zh'"
330
- :class="{ active: (lang || 'zh') === 'zh' }"
331
- @click="setLang('zh')">ZH</button>
332
- <button
333
- type="button"
334
- class="lang-choice-btn"
335
- :aria-pressed="(lang || 'zh') === 'en'"
336
- :class="{ active: (lang || 'zh') === 'en' }"
337
- @click="setLang('en')">EN</button>
338
- <button
339
- type="button"
340
- class="lang-choice-btn"
341
- :aria-pressed="(lang || 'zh') === 'ja'"
342
- :class="{ active: (lang || 'zh') === 'ja' }"
343
- @click="setLang('ja')">日本語</button>
344
- </div>
309
+ <div class="side-rail-lang">
310
+ <button
311
+ type="button"
312
+ class="language-settings-link"
313
+ :aria-label="t('settings.language.sideLabel', { language: currentLanguageLabel() })"
314
+ @click="openLanguageSettings">{{ t('settings.language.sideLabel', { language: currentLanguageLabel() }) }}</button>
345
315
  </div>
346
316
  </aside>
347
317
  <main class="main-panel">
@@ -33,86 +33,150 @@
33
33
  <button class="btn-mini" @click="resetOpenclawQuick">{{ t('common.clear') }}</button>
34
34
  </div>
35
35
  </div>
36
- <div class="quick-steps">
37
- <div class="quick-step"><span class="step-badge">1</span><span>{{ t('modal.openclaw.quick.step1') }}</span></div>
38
- <div class="quick-step"><span class="step-badge">2</span><span>{{ t('modal.openclaw.quick.step2') }}</span></div>
39
- <div class="quick-step"><span class="step-badge">3</span><span>{{ t('modal.openclaw.quick.step3') }}</span></div>
40
- </div>
41
- <div class="quick-grid">
42
- <div class="quick-card">
43
- <div class="structured-card-title">Provider</div>
44
- <div class="form-group">
45
- <label class="form-label">{{ t('field.providerName') }}</label>
46
- <input v-model="openclawQuick.providerName" class="form-input" placeholder="例如: custom-myapi">
47
- <div class="form-hint">{{ t('modal.openclaw.quick.providerHint') }}</div>
48
- </div>
49
- <div class="form-group">
50
- <label class="form-label">{{ t('field.baseUrl') }}</label>
51
- <input v-model="openclawQuick.baseUrl" class="form-input" placeholder="https://api.example.com/v1" :readonly="openclawQuick.baseUrlReadOnly">
52
- <div v-if="openclawQuick.baseUrlDisplayKind === 'builtin-default'" class="form-hint">{{ t('modal.openclaw.quick.baseUrlHintDefault') }}</div>
53
- <div v-else-if="openclawQuick.baseUrlReadOnly" class="form-hint">{{ t('modal.openclaw.quick.baseUrlHintReadonly') }}</div>
54
- </div>
55
- <div class="form-group">
56
- <label class="form-label">API Key</label>
57
- <div class="list-row">
58
- <input v-model="openclawQuick.apiKey" class="form-input" :readonly="openclawQuick.apiKeyReadOnly" :type="(openclawQuick.apiKeyReadOnly || openclawQuick.showKey) ? 'text' : 'password'" placeholder="sk-...">
59
- <button v-if="!openclawQuick.apiKeyReadOnly" class="btn-mini" @click="toggleOpenclawQuickKey">
60
- {{ openclawQuick.showKey ? t('common.hide') : t('common.show') }}
36
+
37
+ <!-- Accordion Stepper -->
38
+ <div class="quick-accordion">
39
+ <!-- Step 1: Provider -->
40
+ <div :class="['accordion-panel', { active: openclawAccordionStep === 1, completed: openclawAccordionStep > 1 }]">
41
+ <button class="accordion-trigger" @click="toggleAccordionStep(1)" type="button">
42
+ <span class="accordion-step-badge">
43
+ <template v-if="openclawAccordionStep > 1">✓</template>
44
+ <template v-else>1</template>
45
+ </span>
46
+ <span class="accordion-title">Provider</span>
47
+ <span class="accordion-status" v-if="openclawQuick.providerName">{{ openclawQuick.providerName }}</span>
48
+ <svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
49
+ <path d="M6 9l6 6 6-6"/>
50
+ </svg>
51
+ </button>
52
+ <div class="accordion-content" v-show="openclawAccordionStep === 1">
53
+ <div class="form-group">
54
+ <label class="form-label">{{ t('field.providerName') }}</label>
55
+ <input v-model="openclawQuick.providerName" class="form-input" placeholder="例如: custom-myapi" @input="validateProviderName">
56
+ <div class="form-hint" :class="{ 'hint-error': !openclawValidation.providerName.valid }">
57
+ {{ openclawValidation.providerName.message || t('modal.openclaw.quick.providerHint') }}
58
+ </div>
59
+ </div>
60
+ <div class="form-group">
61
+ <label class="form-label">{{ t('field.baseUrl') }}</label>
62
+ <input v-model="openclawQuick.baseUrl" class="form-input" placeholder="https://api.example.com/v1" :readonly="openclawQuick.baseUrlReadOnly">
63
+ <div v-if="openclawQuick.baseUrlDisplayKind === 'builtin-default'" class="form-hint">{{ t('modal.openclaw.quick.baseUrlHintDefault') }}</div>
64
+ <div v-else-if="openclawQuick.baseUrlReadOnly" class="form-hint">{{ t('modal.openclaw.quick.baseUrlHintReadonly') }}</div>
65
+ </div>
66
+ <div class="form-group">
67
+ <label class="form-label">API Key</label>
68
+ <div class="list-row">
69
+ <input v-model="openclawQuick.apiKey" class="form-input" :readonly="openclawQuick.apiKeyReadOnly" :type="(openclawQuick.apiKeyReadOnly || openclawQuick.showKey) ? 'text' : 'password'" placeholder="sk-...">
70
+ <button v-if="!openclawQuick.apiKeyReadOnly" class="btn-mini" @click="toggleOpenclawQuickKey">
71
+ {{ openclawQuick.showKey ? t('common.hide') : t('common.show') }}
72
+ </button>
73
+ </div>
74
+ <div v-if="openclawQuick.apiKeyDisplayKind === 'auth-profile-value'" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintFromAuth') }}</div>
75
+ <div v-else-if="openclawQuick.apiKeyReadOnly" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintReadonly') }}</div>
76
+ <div v-else class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintKeep') }}</div>
77
+ </div>
78
+ <div class="form-group">
79
+ <label class="form-label">{{ t('field.apiType') }}</label>
80
+ <input v-model="openclawQuick.apiType" class="form-input" list="openclawApiTypeList" :placeholder="t('placeholder.apiTypeExample')">
81
+ <datalist id="openclawApiTypeList">
82
+ <option value="openai-responses"></option>
83
+ <option value="openai-chat"></option>
84
+ <option value="anthropic"></option>
85
+ <option value="custom"></option>
86
+ </datalist>
87
+ </div>
88
+ <div class="accordion-actions">
89
+ <button class="btn btn-confirm btn-sm" @click="nextAccordionStep" :disabled="!openclawQuick.providerName">
90
+ 下一步 →
61
91
  </button>
62
92
  </div>
63
- <div v-if="openclawQuick.apiKeyDisplayKind === 'auth-profile-value'" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintFromAuth') }}</div>
64
- <div v-else-if="openclawQuick.apiKeyReadOnly" class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintReadonly') }}</div>
65
- <div v-else class="form-hint">{{ t('modal.openclaw.quick.apiKeyHintKeep') }}</div>
66
- </div>
67
- <div class="form-group">
68
- <label class="form-label">{{ t('field.apiType') }}</label>
69
- <input v-model="openclawQuick.apiType" class="form-input" list="openclawApiTypeList" :placeholder="t('placeholder.apiTypeExample')">
70
- <datalist id="openclawApiTypeList">
71
- <option value="openai-responses"></option>
72
- <option value="openai-chat"></option>
73
- <option value="anthropic"></option>
74
- <option value="custom"></option>
75
- </datalist>
76
93
  </div>
77
94
  </div>
78
95
 
79
- <div class="quick-card">
80
- <div class="structured-card-title">{{ t('modal.openclaw.quick.modelTitle') }}</div>
81
- <div class="form-group">
82
- <label class="form-label">{{ t('field.modelId') }}</label>
83
- <input v-model="openclawQuick.modelId" class="form-input" :placeholder="t('placeholder.modelIdExample')">
84
- </div>
85
- <div class="form-group">
86
- <label class="form-label">{{ t('field.displayName') }}</label>
87
- <input v-model="openclawQuick.modelName" class="form-input" :placeholder="t('placeholder.modelNameOptional')">
88
- </div>
89
- <div class="form-group">
90
- <label class="form-label">{{ t('field.contextAndMaxOutput') }}</label>
91
- <div class="list-row">
92
- <input v-model="openclawQuick.contextWindow" class="form-input" :placeholder="t('field.contextWindow')">
93
- <input v-model="openclawQuick.maxTokens" class="form-input" :placeholder="t('field.maxOutput')">
96
+ <!-- Step 2: Model -->
97
+ <div :class="['accordion-panel', { active: openclawAccordionStep === 2, completed: openclawAccordionStep > 2 }]">
98
+ <button class="accordion-trigger" @click="toggleAccordionStep(2)" type="button">
99
+ <span class="accordion-step-badge">
100
+ <template v-if="openclawAccordionStep > 2">✓</template>
101
+ <template v-else>2</template>
102
+ </span>
103
+ <span class="accordion-title">{{ t('modal.openclaw.quick.modelTitle') }}</span>
104
+ <span class="accordion-status" v-if="openclawQuick.modelId">{{ openclawQuick.modelName || openclawQuick.modelId }}</span>
105
+ <svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
106
+ <path d="M6 9l6 6 6-6"/>
107
+ </svg>
108
+ </button>
109
+ <div class="accordion-content" v-show="openclawAccordionStep === 2">
110
+ <div class="form-group">
111
+ <label class="form-label">{{ t('field.modelId') }}</label>
112
+ <input v-model="openclawQuick.modelId" class="form-input" :placeholder="t('placeholder.modelIdExample')" @input="validateModelId">
113
+ <div class="form-hint" :class="{ 'hint-error': !openclawValidation.modelId.valid }">
114
+ {{ openclawValidation.modelId.message || '必填,例如: gpt-4' }}
115
+ </div>
116
+ </div>
117
+ <div class="form-group">
118
+ <label class="form-label">{{ t('field.displayName') }}</label>
119
+ <input v-model="openclawQuick.modelName" class="form-input" :placeholder="t('placeholder.modelNameOptional')">
120
+ <div class="form-hint">可选,用于显示</div>
121
+ </div>
122
+ <div class="form-group">
123
+ <label class="form-label">{{ t('field.contextAndMaxOutput') }}</label>
124
+ <div class="list-row">
125
+ <input v-model="openclawQuick.contextWindow" class="form-input" :placeholder="t('field.contextWindow')">
126
+ <input v-model="openclawQuick.maxTokens" class="form-input" :placeholder="t('field.maxOutput')">
127
+ </div>
128
+ <div class="form-hint">{{ t('hint.emptyNoChange') }}</div>
129
+ </div>
130
+ <div class="accordion-actions">
131
+ <button class="btn btn-cancel btn-sm" @click="prevAccordionStep">
132
+ ← 上一步
133
+ </button>
134
+ <button class="btn btn-confirm btn-sm" @click="nextAccordionStep" :disabled="!openclawQuick.modelId">
135
+ 下一步 →
136
+ </button>
94
137
  </div>
95
- <div class="form-hint">{{ t('hint.emptyNoChange') }}</div>
96
138
  </div>
97
139
  </div>
98
140
 
99
- <div class="quick-card">
100
- <div class="structured-card-title">{{ t('modal.openclaw.quick.optionsTitle') }}</div>
101
- <label class="quick-option">
102
- <input type="checkbox" v-model="openclawQuick.setPrimary">
103
- {{ t('modal.openclaw.quick.setPrimary') }}
104
- </label>
105
- <label class="quick-option">
106
- <input type="checkbox" v-model="openclawQuick.overrideProvider">
107
- {{ t('modal.openclaw.quick.overrideProvider') }}
108
- </label>
109
- <label class="quick-option">
110
- <input type="checkbox" v-model="openclawQuick.overrideModels">
111
- {{ t('modal.openclaw.quick.overrideModels') }}
112
- </label>
113
- <div class="form-hint">{{ t('modal.openclaw.quick.optionsHint') }}</div>
141
+ <!-- Step 3: Options -->
142
+ <div :class="['accordion-panel', { active: openclawAccordionStep === 3, completed: openclawAccordionStep > 3 }]">
143
+ <button class="accordion-trigger" @click="toggleAccordionStep(3)" type="button">
144
+ <span class="accordion-step-badge">
145
+ <template v-if="openclawAccordionStep > 3">✓</template>
146
+ <template v-else>3</template>
147
+ </span>
148
+ <span class="accordion-title">{{ t('modal.openclaw.quick.optionsTitle') }}</span>
149
+ <span class="accordion-status">高级选项</span>
150
+ <svg class="accordion-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
151
+ <path d="M6 9l6 6 6-6"/>
152
+ </svg>
153
+ </button>
154
+ <div class="accordion-content" v-show="openclawAccordionStep === 3">
155
+ <label class="quick-option">
156
+ <input type="checkbox" v-model="openclawQuick.setPrimary">
157
+ {{ t('modal.openclaw.quick.setPrimary') }}
158
+ </label>
159
+ <label class="quick-option">
160
+ <input type="checkbox" v-model="openclawQuick.overrideProvider">
161
+ {{ t('modal.openclaw.quick.overrideProvider') }}
162
+ </label>
163
+ <label class="quick-option">
164
+ <input type="checkbox" v-model="openclawQuick.overrideModels">
165
+ {{ t('modal.openclaw.quick.overrideModels') }}
166
+ </label>
167
+ <div class="form-hint">{{ t('modal.openclaw.quick.optionsHint') }}</div>
168
+ <div class="accordion-actions">
169
+ <button class="btn btn-cancel btn-sm" @click="prevAccordionStep">
170
+ ← 上一步
171
+ </button>
172
+ <button class="btn btn-confirm btn-sm" @click="finishAccordionStep">
173
+ 完成 ✓
174
+ </button>
175
+ </div>
176
+ </div>
114
177
  </div>
115
178
  </div>
179
+
116
180
  <div class="btn-group">
117
181
  <button class="btn btn-confirm" @click="applyOpenclawQuickToText">{{ t('modal.openclaw.quick.writeToEditor') }}</button>
118
182
  </div>
@@ -262,14 +326,14 @@
262
326
  :readonly="openclawSaving || openclawApplying"
263
327
  placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
264
328
  <div class="template-editor-warning">
265
- <span v-if="openclawEditing.lockName && openclawEditing.name === '默认配置'">默认配置始终映射当前 openclaw.json,请直接使用“保存并应用”。</span>
329
+ <span v-if="openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name)">默认配置始终映射当前 openclaw.json,请直接使用“保存并应用”。</span>
266
330
  <span v-else>保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。</span>
267
331
  </div>
268
332
  </div>
269
333
 
270
334
  <div class="btn-group">
271
335
  <button class="btn btn-cancel" @click="closeOpenclawConfigModal" :disabled="openclawSaving || openclawApplying">取消</button>
272
- <button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving || openclawApplying || (openclawEditing.lockName && openclawEditing.name === '默认配置')">
336
+ <button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving || openclawApplying || (openclawEditing.lockName && isDefaultOpenclawConfig(openclawEditing.name))">
273
337
  {{ openclawSaving ? '保存中...' : '保存' }}
274
338
  </button>
275
339
  <button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawSaving || openclawApplying">
@@ -1,18 +1,18 @@
1
- <!-- Webhook 配置模态框 -->
1
+ <!-- Webhook settings modal -->
2
2
  <div v-if="showWebhookModal" class="modal-overlay" @click.self="closeWebhookModal">
3
3
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="webhook-modal-title">
4
- <div class="modal-title" id="webhook-modal-title">Webhook 配置</div>
4
+ <div class="modal-title" id="webhook-modal-title">{{ t('settings.webhook.modalTitle') }}</div>
5
5
 
6
6
  <div class="form-group">
7
- <label class="form-label">启用状态</label>
7
+ <label class="form-label">{{ t('settings.webhook.enabledLabel') }}</label>
8
8
  <label class="settings-toggle">
9
9
  <input type="checkbox" v-model="webhookConfig.enabled">
10
- <span>启用 Webhook</span>
10
+ <span>{{ t('settings.webhook.enableToggle') }}</span>
11
11
  </label>
12
12
  </div>
13
13
 
14
14
  <div class="form-group">
15
- <label class="form-label">URL</label>
15
+ <label class="form-label">{{ t('settings.webhook.urlLabel') }}</label>
16
16
  <input
17
17
  v-model="webhookConfig.url"
18
18
  class="form-input"
@@ -23,7 +23,7 @@
23
23
  </div>
24
24
 
25
25
  <div class="form-group">
26
- <label class="form-label">事件</label>
26
+ <label class="form-label">{{ t('settings.webhook.eventsLabel') }}</label>
27
27
  <div class="webhook-events-checkbox-list">
28
28
  <label v-for="ev in webhookEventOptions" :key="ev" class="webhook-event-checkbox-item">
29
29
  <input type="checkbox" :checked="webhookConfig.events.includes(ev)" @change="toggleWebhookEvent(ev)">
@@ -33,9 +33,9 @@
33
33
  </div>
34
34
 
35
35
  <div class="btn-group">
36
- <button class="btn btn-cancel" @click="closeWebhookModal">取消</button>
36
+ <button class="btn btn-cancel" @click="closeWebhookModal">{{ t('common.cancel') }}</button>
37
37
  <button class="btn btn-confirm" @click="saveWebhookSettings" :disabled="webhookSaving">
38
- {{ webhookSaving ? '保存中...' : '保存' }}
38
+ {{ webhookSaving ? t('common.saving') : t('common.save') }}
39
39
  </button>
40
40
  </div>
41
41
  </div>