codexmate 0.0.38 → 0.0.40

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 (43) hide show
  1. package/cli/builtin-proxy.js +626 -207
  2. package/cli/config-bootstrap.js +6 -1
  3. package/cli/openai-bridge.js +541 -210
  4. package/cli.js +189 -4
  5. package/package.json +1 -1
  6. package/plugins/prompt-templates/computed.mjs +61 -3
  7. package/plugins/prompt-templates/manifest.mjs +3 -0
  8. package/web-ui/app.js +14 -3
  9. package/web-ui/modules/app.computed.main-tabs.mjs +39 -30
  10. package/web-ui/modules/app.methods.claude-config.mjs +111 -9
  11. package/web-ui/modules/app.methods.index.mjs +2 -0
  12. package/web-ui/modules/app.methods.openclaw-editing.mjs +48 -0
  13. package/web-ui/modules/app.methods.openclaw-persist.mjs +13 -7
  14. package/web-ui/modules/app.methods.providers.mjs +36 -10
  15. package/web-ui/modules/app.methods.runtime.mjs +76 -1
  16. package/web-ui/modules/app.methods.startup-claude.mjs +7 -0
  17. package/web-ui/modules/app.methods.tool-config-permissions.mjs +87 -0
  18. package/web-ui/modules/config-mode.computed.mjs +3 -3
  19. package/web-ui/modules/i18n/locales/en.mjs +1140 -0
  20. package/web-ui/modules/i18n/locales/ja.mjs +1130 -0
  21. package/web-ui/modules/i18n/locales/vi.mjs +239 -0
  22. package/web-ui/modules/i18n/locales/zh.mjs +1143 -0
  23. package/web-ui/modules/i18n.dict.mjs +9 -3195
  24. package/web-ui/modules/i18n.mjs +65 -16
  25. package/web-ui/partials/index/layout-header.html +16 -46
  26. package/web-ui/partials/index/modal-openclaw-config.html +135 -71
  27. package/web-ui/partials/index/modal-webhook.html +8 -8
  28. package/web-ui/partials/index/modals-basic.html +56 -16
  29. package/web-ui/partials/index/panel-config-claude.html +51 -21
  30. package/web-ui/partials/index/panel-config-codex.html +34 -5
  31. package/web-ui/partials/index/panel-config-openclaw.html +70 -64
  32. package/web-ui/partials/index/panel-dashboard.html +62 -77
  33. package/web-ui/partials/index/panel-settings.html +28 -7
  34. package/web-ui/partials/index/panel-trash.html +14 -14
  35. package/web-ui/res/web-ui-render.precompiled.js +1783 -1386
  36. package/web-ui/styles/controls-forms.css +99 -0
  37. package/web-ui/styles/dashboard.css +46 -14
  38. package/web-ui/styles/layout-shell.css +45 -0
  39. package/web-ui/styles/navigation-panels.css +3 -3
  40. package/web-ui/styles/openclaw-structured.css +383 -33
  41. package/web-ui/styles/responsive.css +68 -0
  42. package/web-ui/styles/sessions-usage.css +105 -9
  43. package/web-ui/styles/settings-panel.css +4 -0
@@ -16,6 +16,68 @@
16
16
  </button>
17
17
  </div>
18
18
  </div>
19
+ <div class="doctor-status-row">
20
+ <div class="doctor-status-chip" :class="inspectorHealthTone">
21
+ <span>{{ t('dashboard.status.health') }}</span>
22
+ <strong>{{ inspectorHealthStatus }}</strong>
23
+ </div>
24
+ <div class="doctor-status-chip">
25
+ <span>{{ t('dashboard.status.busy') }}</span>
26
+ <strong>{{ inspectorBusyStatus }}</strong>
27
+ </div>
28
+ <div class="doctor-status-chip">
29
+ <span>{{ t('dashboard.status.models') }}</span>
30
+ <strong>{{ inspectorModelLoadStatus }}</strong>
31
+ </div>
32
+ </div>
33
+ <div v-if="healthCheckResult" class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
34
+ <div class="doctor-health-title">
35
+ {{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
36
+ <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
37
+ </div>
38
+ </div>
39
+ <div v-if="healthCheckResult && healthCheckResult.report" class="doctor-action-list">
40
+ <template v-if="healthCheckResult.report.issues && healthCheckResult.report.issues.length">
41
+ <div v-for="issue in healthCheckResult.report.issues" :key="issue.id" class="doctor-action-card">
42
+ <div class="doctor-action-head">
43
+ <div class="doctor-action-title">{{ issue.problem || (issue.problemKey ? t(issue.problemKey, issue.problemParams) : '') }}</div>
44
+ <div :class="['doctor-action-severity', issue.severity]">{{ issue.severityLabel || issue.severity }}</div>
45
+ </div>
46
+ <div class="doctor-action-impact">{{ issue.impact || (issue.impactKey ? t(issue.impactKey, issue.impactParams) : '') }}</div>
47
+ <div
48
+ v-if="issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target)"
49
+ class="doctor-action-buttons">
50
+ <template v-for="(action, index) in issue.actions" :key="issue.id + '-action-' + index">
51
+ <button
52
+ v-if="action.type === 'navigate' && action.target"
53
+ type="button"
54
+ class="btn-tool btn-tool-compact"
55
+ @click="action.target ? switchMainTab(action.target) : null">
56
+ {{ action.label || (action.labelKey ? t(action.labelKey, action.labelParams) : t('dashboard.doctor.open')) }}
57
+ </button>
58
+ </template>
59
+ </div>
60
+ </div>
61
+ </template>
62
+ <div class="doctor-action-footer">
63
+ <button
64
+ type="button"
65
+ class="btn-tool btn-tool-compact"
66
+ @click="healthCheckResult && healthCheckResult.report
67
+ ? downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
68
+ : null">
69
+ {{ t('dashboard.doctor.export.json') }}
70
+ </button>
71
+ <button
72
+ type="button"
73
+ class="btn-tool btn-tool-compact"
74
+ @click="healthCheckResult && healthCheckResult.report
75
+ ? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
76
+ : null">
77
+ {{ t('dashboard.doctor.export.md') }}
78
+ </button>
79
+ </div>
80
+ </div>
19
81
  <div class="doctor-grid">
20
82
  <button type="button" class="doctor-card" @click="switchMainTab('config')" :disabled="loading || !!initError">
21
83
  <div class="doctor-card-title">{{ t('dashboard.card.config') }}</div>
@@ -105,82 +167,5 @@
105
167
  </div>
106
168
  </button>
107
169
  </div>
108
- <div class="doctor-status-row">
109
- <div class="doctor-status-chip" :class="inspectorHealthTone">
110
- <span>{{ t('dashboard.status.health') }}</span>
111
- <strong>{{ inspectorHealthStatus }}</strong>
112
- </div>
113
- <div class="doctor-status-chip">
114
- <span>{{ t('dashboard.status.busy') }}</span>
115
- <strong>{{ inspectorBusyStatus }}</strong>
116
- </div>
117
- <div class="doctor-status-chip">
118
- <span>{{ t('dashboard.status.models') }}</span>
119
- <strong>{{ inspectorModelLoadStatus }}</strong>
120
- </div>
121
- </div>
122
- <div v-if="healthCheckResult" class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
123
- <div class="doctor-health-title">
124
- {{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
125
- <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
126
- </div>
127
- </div>
128
- <div v-if="healthCheckResult && healthCheckResult.report && healthCheckResult.report.issues && healthCheckResult.report.issues.length" class="doctor-action-list">
129
- <div v-for="issue in healthCheckResult.report.issues" :key="issue.id" class="doctor-action-card">
130
- <div class="doctor-action-head">
131
- <div class="doctor-action-title">{{ issue.problem || (issue.problemKey ? t(issue.problemKey, issue.problemParams) : '') }}</div>
132
- <div :class="['doctor-action-severity', issue.severity]">{{ issue.severityLabel || issue.severity }}</div>
133
- </div>
134
- <div class="doctor-action-impact">{{ issue.impact || (issue.impactKey ? t(issue.impactKey, issue.impactParams) : '') }}</div>
135
- <div v-if="issue.actions && issue.actions.length" class="doctor-action-buttons">
136
- <template v-for="(action, index) in issue.actions" :key="issue.id + '-action-' + index">
137
- <button
138
- v-if="action.type === 'navigate'"
139
- type="button"
140
- class="btn-tool btn-tool-compact"
141
- @click="switchMainTab(action.target)">
142
- {{ action.label || (action.labelKey ? t(action.labelKey, action.labelParams) : t('dashboard.doctor.open')) }}
143
- </button>
144
- <button
145
- v-else-if="action.type === 'run-check'"
146
- type="button"
147
- class="btn-tool btn-tool-compact"
148
- @click="runHealthCheck({ doctor: true, forceRefresh: true })"
149
- :disabled="healthCheckLoading">
150
- {{ t('dashboard.doctor.runChecks') }}
151
- </button>
152
- <button
153
- v-else-if="action.type === 'export'"
154
- type="button"
155
- class="btn-tool btn-tool-compact"
156
- @click="healthCheckResult && healthCheckResult.report
157
- ? (action.format === 'md'
158
- ? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
159
- : downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8'))
160
- : null">
161
- {{ action.format === 'md' ? t('dashboard.doctor.export.md') : t('dashboard.doctor.export.json') }}
162
- </button>
163
- </template>
164
- </div>
165
- </div>
166
- <div class="doctor-action-footer">
167
- <button
168
- type="button"
169
- class="btn-tool btn-tool-compact"
170
- @click="healthCheckResult && healthCheckResult.report
171
- ? downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
172
- : null">
173
- {{ t('dashboard.doctor.export.json') }}
174
- </button>
175
- <button
176
- type="button"
177
- class="btn-tool btn-tool-compact"
178
- @click="healthCheckResult && healthCheckResult.report
179
- ? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
180
- : null">
181
- {{ t('dashboard.doctor.export.md') }}
182
- </button>
183
- </div>
184
- </div>
185
170
  </div>
186
171
  </div>
@@ -40,6 +40,27 @@
40
40
  role="tabpanel"
41
41
  aria-labelledby="settings-tab-general">
42
42
  <div class="settings-grid">
43
+ <section id="settings-language" class="settings-card" :aria-label="t('settings.language.title')">
44
+ <div class="settings-card-main">
45
+ <div class="settings-card-content">
46
+ <div class="settings-card-title">{{ t('settings.language.title') }}</div>
47
+ <p class="settings-card-desc">{{ t('settings.language.meta') }}</p>
48
+ <label class="selector-label" for="settings-language-select">{{ t('settings.language.label') }}</label>
49
+ <select
50
+ id="settings-language-select"
51
+ class="model-select settings-language-select"
52
+ :value="lang"
53
+ @change="setLang($event.target.value)">
54
+ <option
55
+ v-for="option in languageOptions()"
56
+ :key="option.code"
57
+ :value="option.code">{{ option.nativeName }} · {{ option.englishName }}</option>
58
+ </select>
59
+ <p class="settings-card-hint">{{ t('settings.language.hint') }}</p>
60
+ </div>
61
+ </div>
62
+ </section>
63
+
43
64
  <section class="settings-card" :aria-label="t('settings.sharePrefix.title')">
44
65
  <div class="settings-card-main">
45
66
  <div class="settings-card-content">
@@ -76,21 +97,21 @@
76
97
  </div>
77
98
  </section>
78
99
 
79
- <section class="settings-card" :aria-label="'Webhook'">
100
+ <section class="settings-card" :aria-label="t('settings.webhook.title')">
80
101
  <div class="settings-card-main">
81
102
  <div class="settings-card-content">
82
- <div class="settings-card-title">Webhook</div>
83
- <p class="settings-card-desc">配置变更时外发通知</p>
103
+ <div class="settings-card-title">{{ t('settings.webhook.title') }}</div>
104
+ <p class="settings-card-desc">{{ t('settings.webhook.meta') }}</p>
84
105
  <div class="webhook-status">
85
106
  <span class="webhook-status-dot" :class="{ active: webhookConfig.enabled }"></span>
86
- <span class="webhook-status-label">{{ webhookConfig.enabled ? '已启用' : '已禁用' }}</span>
107
+ <span class="webhook-status-label">{{ webhookConfig.enabled ? t('settings.webhook.enabled') : t('settings.webhook.disabled') }}</span>
87
108
  <code v-if="webhookConfig.url" class="webhook-url">{{ webhookConfig.url }}</code>
88
109
  </div>
89
110
  </div>
90
111
  </div>
91
112
  <button class="settings-card-action" @click="openWebhookModal" :class="{ 'settings-card-action--active': webhookConfig.enabled }">
92
- <span v-if="webhookConfig.enabled">{{ webhookConfig.url ? '编辑' : '配置' }}</span>
93
- <span v-else>启用</span>
113
+ <span v-if="webhookConfig.enabled">{{ webhookConfig.url ? t('settings.webhook.edit') : t('settings.webhook.configure') }}</span>
114
+ <span v-else>{{ t('settings.webhook.enable') }}</span>
94
115
  </button>
95
116
  </section>
96
117
  </div>
@@ -136,7 +157,7 @@
136
157
  <div class="settings-retention">
137
158
  <label for="settings-trash-retention-days">{{ t('settings.trash.retentionLabel') }}</label>
138
159
  <input id="settings-trash-retention-days" type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
139
- <span>天</span>
160
+ <span>{{ t('settings.trash.retentionUnit') }}</span>
140
161
  </div>
141
162
  <p class="settings-card-hint">{{ t('settings.trash.retentionHint') }}</p>
142
163
  </div>
@@ -1,4 +1,4 @@
1
- <!-- 回收站面板 -->
1
+ <!-- Trash panel -->
2
2
  <div
3
3
  v-show="mainTab === 'trash'"
4
4
  class="mode-content"
@@ -6,7 +6,7 @@
6
6
  role="tabpanel"
7
7
  aria-labelledby="tab-trash">
8
8
  <div v-if="!loading" class="trash-panel-shell">
9
- <!-- 空态 -->
9
+ <!-- Empty state -->
10
10
  <div v-if="getSessionTrashViewState() === 'empty'" class="trash-empty-state">
11
11
  <svg class="trash-empty-svg" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="1.2">
12
12
  <path d="M20 22l4 32h16l4-32"/>
@@ -15,38 +15,38 @@
15
15
  <path d="M28 30v16M36 30v16" stroke-width="1.6" stroke-linecap="round"/>
16
16
  </svg>
17
17
  <div class="trash-empty-title">{{ t('settings.trash.empty') }}</div>
18
- <div class="trash-empty-hint">删除的会话保留 {{ sessionTrashRetentionDays }} 天后自动清理</div>
18
+ <div class="trash-empty-hint">{{ t('settings.trash.emptyHint', { days: sessionTrashRetentionDays }) }}</div>
19
19
  </div>
20
20
 
21
- <!-- 加载态 -->
21
+ <!-- Loading state -->
22
22
  <div v-else-if="getSessionTrashViewState() === 'loading'" class="trash-empty-state">
23
23
  <div class="trash-spinner"></div>
24
24
  <div class="trash-empty-title">{{ t('settings.trash.loading') }}</div>
25
25
  </div>
26
26
 
27
- <!-- 错误态 -->
27
+ <!-- Error state -->
28
28
  <div v-else-if="getSessionTrashViewState() === 'retry'" class="trash-empty-state">
29
29
  <svg class="trash-empty-svg" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="1.2">
30
30
  <circle cx="32" cy="32" r="22"/>
31
31
  <path d="M32 20v16M32 44v2" stroke-width="2" stroke-linecap="round"/>
32
32
  </svg>
33
33
  <div class="trash-empty-title">{{ t('settings.trash.retry') }}</div>
34
- <button class="btn-tool" @click="loadSessionTrash({ forceRefresh: true })">重试</button>
34
+ <button class="btn-tool" @click="loadSessionTrash({ forceRefresh: true })">{{ t('common.retry') }}</button>
35
35
  </div>
36
36
 
37
- <!-- 列表态 -->
37
+ <!-- List state -->
38
38
  <template v-else>
39
39
  <div class="trash-toolbar">
40
40
  <div class="trash-toolbar-left">
41
- <span class="trash-toolbar-count">{{ sessionTrashCount }} 个已删除会话</span>
42
- <span class="trash-toolbar-retention">{{ sessionTrashRetentionDays }} 天后自动清理</span>
41
+ <span class="trash-toolbar-count">{{ t('settings.trash.count', { count: sessionTrashCount }) }}</span>
42
+ <span class="trash-toolbar-retention">{{ t('settings.trash.retentionShort', { days: sessionTrashRetentionDays }) }}</span>
43
43
  </div>
44
44
  <div class="trash-toolbar-right">
45
- <button class="btn-mini" @click="loadSessionTrash({ forceRefresh: true })" :disabled="sessionTrashLoading" :title="t('sessions.refresh')">
45
+ <button class="btn-mini" @click="loadSessionTrash({ forceRefresh: true })" :disabled="sessionTrashLoading" :aria-label="t('sessions.refresh')" :title="t('sessions.refresh')">
46
46
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="btn-icon-sm"><path d="M21 2v6h-6M3 12a9 9 0 0115-6.7L21 8M3 22v-6h6M21 12a9 9 0 01-15 6.7L3 16"/></svg>
47
47
  </button>
48
48
  <button class="btn-mini delete" @click="clearSessionTrash" :disabled="sessionTrashClearing || sessionTrashLoading || !(Number(sessionTrashCount) > 0)">
49
- {{ sessionTrashClearing ? '清空中…' : '清空' }}
49
+ {{ sessionTrashClearing ? t('settings.trash.clearing') : t('settings.trash.clearShort') }}
50
50
  </button>
51
51
  </div>
52
52
  </div>
@@ -63,10 +63,10 @@
63
63
  </div>
64
64
  </div>
65
65
  <div class="trash-item-actions">
66
- <button class="trash-action-btn restore" @click="restoreSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)" :title="sessionTrashRestoring[getSessionTrashActionKey(item)] ? '恢复中…' : '恢复'">
66
+ <button class="trash-action-btn restore" @click="restoreSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)" :aria-label="sessionTrashRestoring[getSessionTrashActionKey(item)] ? t('settings.trash.restoring') : t('settings.trash.restore')" :title="sessionTrashRestoring[getSessionTrashActionKey(item)] ? t('settings.trash.restoring') : t('settings.trash.restore')">
67
67
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 119 9"/><path d="M3 4v6h6"/></svg>
68
68
  </button>
69
- <button class="trash-action-btn delete" @click="purgeSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)" :title="sessionTrashPurging[getSessionTrashActionKey(item)] ? '删除中…' : '彻底删除'">
69
+ <button class="trash-action-btn delete" @click="purgeSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)" :aria-label="sessionTrashPurging[getSessionTrashActionKey(item)] ? t('settings.trash.purging') : t('settings.trash.purge')" :title="sessionTrashPurging[getSessionTrashActionKey(item)] ? t('settings.trash.purging') : t('settings.trash.purge')">
70
70
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6M10 11v6M14 11v6"/></svg>
71
71
  </button>
72
72
  </div>
@@ -74,7 +74,7 @@
74
74
  </div>
75
75
  <div v-if="sessionTrashHasMoreItems" class="trash-list-footer">
76
76
  <button class="btn-tool btn-tool-compact" @click="loadMoreSessionTrashItems" :disabled="sessionTrashLoading || sessionTrashClearing">
77
- 加载更多({{ sessionTrashHiddenCount }} 条)
77
+ {{ t('settings.trash.loadMoreItems', { count: sessionTrashHiddenCount }) }}
78
78
  </button>
79
79
  </div>
80
80
  </div>