codexmate 0.0.23 → 0.0.24

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 (55) hide show
  1. package/README.md +4 -3
  2. package/README.zh.md +4 -2
  3. package/cli/auth-profiles.js +23 -7
  4. package/cli/doctor-core.js +903 -0
  5. package/cli/import-skills-url.js +334 -0
  6. package/cli.js +304 -208
  7. package/lib/cli-models-utils.js +0 -40
  8. package/lib/cli-network-utils.js +28 -2
  9. package/package.json +5 -2
  10. package/plugins/README.md +20 -0
  11. package/plugins/README.zh-CN.md +20 -0
  12. package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
  13. package/plugins/prompt-templates/computed.mjs +253 -0
  14. package/plugins/prompt-templates/index.mjs +8 -0
  15. package/plugins/prompt-templates/manifest.mjs +15 -0
  16. package/plugins/prompt-templates/methods.mjs +619 -0
  17. package/plugins/prompt-templates/overview.mjs +90 -0
  18. package/plugins/prompt-templates/ownership.mjs +19 -0
  19. package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
  20. package/plugins/prompt-templates/storage.mjs +64 -0
  21. package/plugins/registry.mjs +16 -0
  22. package/res/logo-pack.webp +0 -0
  23. package/web-ui/app.js +15 -32
  24. package/web-ui/index.html +4 -3
  25. package/web-ui/modules/app.computed.dashboard.mjs +22 -22
  26. package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
  27. package/web-ui/modules/app.methods.agents.mjs +91 -3
  28. package/web-ui/modules/app.methods.codex-config.mjs +153 -164
  29. package/web-ui/modules/app.methods.navigation.mjs +34 -1
  30. package/web-ui/modules/app.methods.runtime.mjs +24 -2
  31. package/web-ui/modules/app.methods.session-browser.mjs +9 -2
  32. package/web-ui/modules/config-mode.computed.mjs +1 -3
  33. package/web-ui/modules/i18n.dict.mjs +2039 -0
  34. package/web-ui/modules/i18n.mjs +2 -1769
  35. package/web-ui/partials/index/layout-header.html +36 -32
  36. package/web-ui/partials/index/modal-config-template-agents.html +3 -4
  37. package/web-ui/partials/index/modal-health-check.html +33 -60
  38. package/web-ui/partials/index/panel-config-claude.html +35 -15
  39. package/web-ui/partials/index/panel-config-codex.html +47 -19
  40. package/web-ui/partials/index/panel-config-openclaw.html +8 -3
  41. package/web-ui/partials/index/panel-dashboard.html +186 -0
  42. package/web-ui/partials/index/panel-docs.html +1 -1
  43. package/web-ui/partials/index/panel-market.html +3 -0
  44. package/web-ui/partials/index/panel-orchestration.html +3 -0
  45. package/web-ui/partials/index/panel-plugins.html +16 -10
  46. package/web-ui/partials/index/panel-sessions.html +4 -1
  47. package/web-ui/partials/index/panel-settings.html +1 -1
  48. package/web-ui/partials/index/panel-usage.html +2 -1
  49. package/web-ui/styles/controls-forms.css +9 -2
  50. package/web-ui/styles/dashboard.css +274 -0
  51. package/web-ui/styles/layout-shell.css +2 -2
  52. package/web-ui/styles/sessions-list.css +3 -3
  53. package/web-ui/styles/sessions-usage.css +9 -0
  54. package/web-ui/styles.css +1 -0
  55. package/res/logo.png +0 -0
@@ -0,0 +1,186 @@
1
+ <div
2
+ v-show="mainTab === 'dashboard'"
3
+ class="mode-content"
4
+ id="panel-dashboard"
5
+ role="tabpanel"
6
+ :aria-labelledby="'tab-dashboard'">
7
+ <div class="selector-section doctor-hero">
8
+ <div class="selector-header">
9
+ <span class="selector-title">{{ t('dashboard.doctor.title') }}</span>
10
+ <div class="selector-actions">
11
+ <button
12
+ class="btn-tool btn-tool-compact"
13
+ @click="runHealthCheck"
14
+ :disabled="loading || !!initError || healthCheckLoading">
15
+ {{ healthCheckLoading ? t('dashboard.doctor.checking') : t('dashboard.doctor.runChecks') }}
16
+ </button>
17
+ </div>
18
+ </div>
19
+ <div class="doctor-grid">
20
+ <button type="button" class="doctor-card" @click="switchMainTab('config')" :disabled="loading || !!initError">
21
+ <div class="doctor-card-title">{{ t('dashboard.card.config') }}</div>
22
+ <div class="doctor-card-meta">
23
+ <span>{{ inspectorConfigModeLabel }}</span>
24
+ <span>·</span>
25
+ <span>{{ inspectorCurrentConfigLabel }}</span>
26
+ </div>
27
+ <div class="doctor-card-kv">
28
+ <span>{{ t('dashboard.kv.model') }}</span>
29
+ <span>{{ inspectorCurrentModelLabel }}</span>
30
+ </div>
31
+ </button>
32
+ <button type="button" class="doctor-card" @click="switchMainTab('sessions')" :disabled="loading || !!initError">
33
+ <div class="doctor-card-title">{{ t('dashboard.card.sessions') }}</div>
34
+ <div class="doctor-card-meta">
35
+ <span>{{ sessionsLoading ? t('dashboard.state.loading') : (sessionsLoadedOnce ? t('dashboard.state.ready') : t('dashboard.state.idle')) }}</span>
36
+ <span>·</span>
37
+ <span>{{ t('dashboard.sessions.count', { count: sessionsList.length }) }}</span>
38
+ </div>
39
+ <div class="doctor-card-kv" v-if="activeSessionDetailError">
40
+ <span>{{ t('dashboard.kv.issue') }}</span>
41
+ <span class="doctor-kv-error">{{ activeSessionDetailError }}</span>
42
+ </div>
43
+ <div class="doctor-card-kv" v-else>
44
+ <span>{{ t('dashboard.kv.active') }}</span>
45
+ <span>{{ activeSession ? (activeSession.title || activeSession.sessionId) : t('dashboard.none') }}</span>
46
+ </div>
47
+ </button>
48
+ <button type="button" class="doctor-card" @click="switchMainTab('usage')" :disabled="loading || !!initError">
49
+ <div class="doctor-card-title">{{ t('dashboard.card.usage') }}</div>
50
+ <div class="doctor-card-meta">
51
+ <span>{{ sessionsUsageLoading ? t('dashboard.state.loading') : (sessionsUsageLoadedOnce ? t('dashboard.state.ready') : t('dashboard.state.idle')) }}</span>
52
+ <span>·</span>
53
+ <span>{{ t('dashboard.usage.range', { value: (sessionsUsageTimeRange === 'all' ? t('usage.range.all') : (sessionsUsageTimeRange === '30d' ? t('usage.range.30d.short') : t('usage.range.7d.short'))) }) }}</span>
54
+ </div>
55
+ <div class="doctor-card-kv" v-if="sessionsUsageError">
56
+ <span>{{ t('dashboard.kv.issue') }}</span>
57
+ <span class="doctor-kv-error">{{ sessionsUsageError }}</span>
58
+ </div>
59
+ <div class="doctor-card-kv" v-else>
60
+ <span>{{ t('dashboard.kv.sessions') }}</span>
61
+ <span>{{ sessionUsageSummaryCards[0] ? sessionUsageSummaryCards[0].value : 0 }}</span>
62
+ </div>
63
+ <div class="doctor-card-kv"
64
+ v-if="sessionsUsageLoadedOnce
65
+ && sessionsUsageList.filter(session => !(session && typeof session.model === 'string' && session.model.trim())).length">
66
+ <span>{{ t('dashboard.kv.missingModel') }}</span>
67
+ <span>{{ sessionsUsageList.filter(session => !(session && typeof session.model === 'string' && session.model.trim())).length }}</span>
68
+ </div>
69
+ </button>
70
+ <button
71
+ v-if="taskOrchestrationTabEnabled"
72
+ type="button"
73
+ class="doctor-card"
74
+ @click="switchMainTab('orchestration')"
75
+ :disabled="loading || !!initError">
76
+ <div class="doctor-card-title">{{ t('dashboard.card.tasks') }}</div>
77
+ <div class="doctor-card-meta">
78
+ <span>{{ taskOrchestration && taskOrchestration.loading ? t('dashboard.state.loading') : t('dashboard.state.ready') }}</span>
79
+ <span>·</span>
80
+ <span>{{ t('dashboard.tasks.queue', { running: taskOrchestrationQueueStats.running, queued: taskOrchestrationQueueStats.queued }) }}</span>
81
+ </div>
82
+ <div class="doctor-card-kv" v-if="taskOrchestration && taskOrchestration.planIssues && taskOrchestration.planIssues.length">
83
+ <span>{{ t('dashboard.kv.blockers') }}</span>
84
+ <span>{{ taskOrchestration.planIssues.length }}</span>
85
+ </div>
86
+ <div class="doctor-card-kv" v-else>
87
+ <span>{{ t('dashboard.kv.runs') }}</span>
88
+ <span>{{ taskOrchestration && taskOrchestration.runs ? taskOrchestration.runs.length : 0 }}</span>
89
+ </div>
90
+ </button>
91
+ <button type="button" class="doctor-card" @click="switchMainTab('market')" :disabled="loading || !!initError">
92
+ <div class="doctor-card-title">{{ t('dashboard.card.skills') }}</div>
93
+ <div class="doctor-card-meta">
94
+ <span>{{ skillsMarketBusy ? t('dashboard.state.loading') : t('dashboard.state.ready') }}</span>
95
+ <span>·</span>
96
+ <span>{{ t('dashboard.skills.count', { installed: skillsList.length, importable: skillsImportList.length }) }}</span>
97
+ </div>
98
+ <div class="doctor-card-kv">
99
+ <span>{{ t('dashboard.kv.target') }}</span>
100
+ <span>{{ skillsTargetLabel }}</span>
101
+ </div>
102
+ <div class="doctor-card-kv" v-if="skillsRootPath">
103
+ <span>{{ t('dashboard.kv.root') }}</span>
104
+ <span>{{ skillsRootPath }}</span>
105
+ </div>
106
+ </button>
107
+ </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
+ </div>
186
+ </div>
@@ -95,7 +95,7 @@
95
95
  </div>
96
96
  </div>
97
97
  <div class="docs-command-row">
98
- <div class="docs-command-box" role="group" :aria-label="`${target.name} command`">
98
+ <div class="docs-command-box" role="group" :aria-label="t('docs.command.aria', { name: target.name })">
99
99
  <code class="install-command">{{ target.command }}</code>
100
100
  <button
101
101
  type="button"
@@ -17,6 +17,9 @@
17
17
  <button type="button" class="btn-tool btn-tool-compact" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
18
18
  {{ t('market.openManager') }}
19
19
  </button>
20
+ <button type="button" class="btn-tool btn-tool-compact" @click="switchMainTab('dashboard')" :disabled="loading || !!initError || skillsMarketBusy">
21
+ {{ t('dashboard.doctor.title') }}
22
+ </button>
20
23
  </div>
21
24
  </div>
22
25
 
@@ -19,6 +19,9 @@
19
19
  <button type="button" class="btn-tool btn-tool-compact" @click="resetTaskOrchestrationDraft()" :disabled="taskOrchestration.running || taskOrchestration.queueAdding || taskOrchestration.planning">
20
20
  {{ t('orchestration.draft.reset') }}
21
21
  </button>
22
+ <button type="button" class="btn-tool btn-tool-compact" @click="switchMainTab('dashboard')" :disabled="loading || !!initError">
23
+ {{ t('dashboard.doctor.title') }}
24
+ </button>
22
25
  </div>
23
26
  </div>
24
27
  <div v-if="taskOrchestrationQueueStats.running || taskOrchestrationQueueStats.queued || taskOrchestration.runs.length" class="task-hero-meta-strip" :aria-label="t('orchestration.summary.aria')">
@@ -45,7 +45,7 @@
45
45
  <div v-else-if="pluginsActiveId === 'prompt-templates'" class="plugins-panel">
46
46
  <div class="plugins-panel-head">
47
47
  <div class="plugins-panel-title">{{ t('plugins.promptTemplates.title') }}</div>
48
- <div class="plugins-panel-subtitle">{{ t('plugins.promptTemplates.subtitle') }}</div>
48
+ <div v-if="pluginsActiveAttribution" class="plugins-panel-note">{{ pluginsActiveAttribution }}</div>
49
49
  </div>
50
50
 
51
51
  <div class="prompt-templates-modebar" role="tablist" :aria-label="t('plugins.promptTemplates.mode.aria')">
@@ -68,6 +68,9 @@
68
68
  <div class="prompt-compose-selected">
69
69
  <div class="prompt-compose-selected-title">{{ (promptComposerActiveTemplate && promptComposerActiveTemplate.name) ? promptComposerActiveTemplate.name : t('plugins.promptTemplates.compose.chooseTemplate') }}</div>
70
70
  <div class="prompt-compose-selected-meta">{{ (promptComposerActiveTemplate && promptComposerActiveTemplate.description) ? promptComposerActiveTemplate.description : t('plugins.promptTemplates.compose.chooseTemplateHint') }}</div>
71
+ <div v-if="promptComposerActiveTemplate && promptComposerActiveTemplate.isBuiltin && (promptComposerActiveTemplate.createdBy || (promptComposerActiveTemplate.maintainers && promptComposerActiveTemplate.maintainers.length))" class="plugins-panel-note">
72
+ {{ t('plugins.meta.attribution', { createdBy: promptComposerActiveTemplate.createdBy || '', maintainers: (promptComposerActiveTemplate.maintainers || []).join(', ') }) }}
73
+ </div>
71
74
  </div>
72
75
 
73
76
  <div class="prompt-compose-form">
@@ -78,7 +81,7 @@
78
81
  @change="selectPromptComposerTemplate($event.target.value)"
79
82
  :disabled="pluginsLoading || !promptTemplatesList.length">
80
83
  <option v-for="tpl in promptTemplatesList" :key="'compose-tpl-' + tpl.id" :value="tpl.id">
81
- {{ tpl.name }}{{ tpl.isBuiltin ? '(内置)' : '' }}
84
+ {{ tpl.name }}{{ tpl.isBuiltin ? t('plugins.promptTemplates.compose.builtinSuffix') : '' }}
82
85
  </option>
83
86
  </select>
84
87
 
@@ -98,10 +101,10 @@
98
101
  <div>
99
102
  <div class="prompt-vars-title">{{ t('plugins.promptTemplates.vars.title') }}</div>
100
103
  <div v-if="!promptComposerActiveTemplate.isBuiltin" class="plugins-panel-note">{{ t('plugins.promptTemplates.compose.varsHint') }}</div>
101
- <div v-if="promptComposerMissingVars.length" class="plugins-panel-note">未填 {{ promptComposerMissingVars.length }} 项</div>
104
+ <div v-if="promptComposerMissingVars.length" class="plugins-panel-note">{{ t('plugins.promptTemplates.compose.missingCount', { count: promptComposerMissingVars.length }) }}</div>
102
105
  </div>
103
106
  <div class="prompt-editor-actions">
104
- <button v-if="promptComposerMissingVars.length" type="button" class="btn-mini" @click="focusPromptComposerFirstMissingVar" :disabled="pluginsLoading">跳到未填</button>
107
+ <button v-if="promptComposerMissingVars.length" type="button" class="btn-mini" @click="focusPromptComposerFirstMissingVar" :disabled="pluginsLoading">{{ t('plugins.promptTemplates.compose.jumpToMissing') }}</button>
105
108
  <button type="button" class="btn-mini" @click="resetPromptComposerVarValues" :disabled="pluginsLoading">{{ t('plugins.promptTemplates.vars.reset') }}</button>
106
109
  </div>
107
110
  </div>
@@ -126,7 +129,7 @@
126
129
  </div>
127
130
  <button type="button" class="btn-mini" @click="copyPromptComposerRendered" :disabled="pluginsLoading || !promptComposerRendered">{{ t('plugins.promptTemplates.compose.copy') }}</button>
128
131
  </div>
129
- <textarea class="form-input prompt-preview-textarea" :value="promptComposerRendered" rows="10" readonly spellcheck="false" aria-label="Rendered prompt (composer)"></textarea>
132
+ <textarea class="form-input prompt-preview-textarea" :value="promptComposerRendered" rows="10" readonly spellcheck="false" :aria-label="t('plugins.promptTemplates.compose.outputAria')"></textarea>
130
133
  </div>
131
134
  </div>
132
135
  </div>
@@ -138,7 +141,7 @@
138
141
  class="form-input"
139
142
  type="text"
140
143
  v-model.trim="promptTemplatesKeyword"
141
- aria-label="Search templates"
144
+ :aria-label="t('plugins.promptTemplates.manage.searchAria')"
142
145
  :placeholder="t('plugins.promptTemplates.manage.searchPlaceholder')">
143
146
  <button type="button" class="btn-mini" @click="createPromptTemplate" :disabled="pluginsLoading">{{ t('plugins.promptTemplates.manage.create') }}</button>
144
147
  <button type="button" class="btn-mini" @click="exportPromptTemplates" :disabled="pluginsLoading || !promptTemplatesList.length">{{ t('plugins.promptTemplates.manage.export') }}</button>
@@ -173,7 +176,7 @@
173
176
  <template v-else>
174
177
  <div class="prompt-editor-head">
175
178
  <div class="prompt-editor-title-row">
176
- <input class="form-input prompt-editor-name" type="text" v-model.trim="promptTemplateDraft.name" :disabled="promptTemplateDraft.isBuiltin" :placeholder="t('plugins.promptTemplates.editor.namePlaceholder')" aria-label="Template name">
179
+ <input class="form-input prompt-editor-name" type="text" v-model.trim="promptTemplateDraft.name" :disabled="promptTemplateDraft.isBuiltin" :placeholder="t('plugins.promptTemplates.editor.namePlaceholder')" :aria-label="t('plugins.promptTemplates.editor.nameAria')">
177
180
  <div v-if="!promptTemplateDraft.isBuiltin" class="prompt-editor-actions">
178
181
  <button type="button" class="btn-mini" @click="duplicatePromptTemplate" :disabled="pluginsLoading">{{ t('plugins.promptTemplates.editor.duplicate') }}</button>
179
182
  <button type="button" class="btn-mini delete" @click="deletePromptTemplate" :disabled="pluginsLoading">{{ t('plugins.promptTemplates.editor.delete') }}</button>
@@ -183,7 +186,10 @@
183
186
  <div v-if="promptTemplateDraft.isBuiltin" class="plugins-panel-note">
184
187
  {{ t('plugins.promptTemplates.editor.builtinReadOnly') }}
185
188
  </div>
186
- <input class="form-input" type="text" v-model.trim="promptTemplateDraft.description" :disabled="promptTemplateDraft.isBuiltin" :placeholder="t('plugins.promptTemplates.editor.descPlaceholder')" aria-label="Template description">
189
+ <div v-if="promptTemplateDraft.isBuiltin && (promptTemplateDraft.createdBy || (promptTemplateDraft.maintainers && promptTemplateDraft.maintainers.length))" class="plugins-panel-note">
190
+ {{ t('plugins.meta.attribution', { createdBy: promptTemplateDraft.createdBy || '', maintainers: (promptTemplateDraft.maintainers || []).join(', ') }) }}
191
+ </div>
192
+ <input class="form-input" type="text" v-model.trim="promptTemplateDraft.description" :disabled="promptTemplateDraft.isBuiltin" :placeholder="t('plugins.promptTemplates.editor.descPlaceholder')" :aria-label="t('plugins.promptTemplates.editor.descAria')">
187
193
  </div>
188
194
 
189
195
  <div class="prompt-editor-body">
@@ -194,7 +200,7 @@
194
200
  :disabled="promptTemplateDraft.isBuiltin"
195
201
  rows="10"
196
202
  spellcheck="false"
197
- aria-label="Template body"
203
+ :aria-label="t('plugins.promptTemplates.editor.templateAria')"
198
204
  :placeholder="t('plugins.promptTemplates.editor.templatePlaceholder')"></textarea>
199
205
 
200
206
  <div class="prompt-vars-block">
@@ -226,7 +232,7 @@
226
232
  </div>
227
233
  <button type="button" class="btn-mini" @click="copyRenderedPrompt" :disabled="pluginsLoading">{{ t('plugins.promptTemplates.preview.copy') }}</button>
228
234
  </div>
229
- <textarea class="form-input prompt-preview-textarea" :value="renderedPrompt" rows="10" readonly spellcheck="false" aria-label="Rendered prompt"></textarea>
235
+ <textarea class="form-input prompt-preview-textarea" :value="renderedPrompt" rows="10" readonly spellcheck="false" :aria-label="t('plugins.promptTemplates.preview.outputAria')"></textarea>
230
236
  </div>
231
237
  </div>
232
238
  </template>
@@ -28,6 +28,9 @@
28
28
  <button class="btn-tool btn-tool-compact" @click="loadSessions({ forceRefresh: true })" :disabled="sessionsLoading">
29
29
  {{ sessionsLoading ? t('sessions.refreshing') : t('sessions.refresh') }}
30
30
  </button>
31
+ <button class="btn-tool btn-tool-compact" type="button" @click="switchMainTab('dashboard')">
32
+ {{ t('dashboard.doctor.title') }}
33
+ </button>
31
34
  </div>
32
35
  </div>
33
36
  <div class="session-toolbar">
@@ -77,7 +80,7 @@
77
80
  <option value="90d">{{ t('sessions.time.90d') }}</option>
78
81
  </select>
79
82
  <button class="btn-tool btn-tool-compact" type="button" @click="copySessionsFilterShareUrl" :disabled="sessionsLoading">
80
- 复制筛选链接
83
+ {{ t('sessions.filters.copyLink') }}
81
84
  </button>
82
85
  <button class="btn-tool btn-tool-compact" type="button" @click="clearSessionFilters" :disabled="sessionsLoading">
83
86
  {{ t('common.resetFilters') }}
@@ -5,7 +5,7 @@
5
5
  id="panel-settings"
6
6
  role="tabpanel"
7
7
  :aria-labelledby="'tab-settings'">
8
- <div class="config-subtabs settings-subtabs" role="tablist" aria-label="Settings tabs">
8
+ <div class="config-subtabs settings-subtabs" role="tablist" :aria-label="t('settings.tabs.aria')">
9
9
  <button
10
10
  id="settings-tab-backup"
11
11
  role="tab"
@@ -14,6 +14,7 @@
14
14
  <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '30d' }" @click="setSessionsUsageTimeRange('30d')">{{ t('usage.range.30d') }}</button>
15
15
  <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === 'all' }" @click="setSessionsUsageTimeRange('all')">{{ t('usage.range.all') }}</button>
16
16
  <button type="button" class="usage-range-btn" @click="loadSessionsUsage({ forceRefresh: true, range: sessionsUsageTimeRange })" :disabled="sessionsUsageLoading">{{ sessionsUsageLoading ? t('usage.refreshing') : t('usage.refresh') }}</button>
17
+ <button type="button" class="usage-range-btn" @click="switchMainTab('dashboard')">{{ t('dashboard.doctor.title') }}</button>
17
18
  </div>
18
19
  </div>
19
20
 
@@ -299,7 +300,7 @@
299
300
  </div>
300
301
  </section>
301
302
 
302
- <section class="usage-card">
303
+ <section class="usage-card usage-card-top-paths">
303
304
  <div class="usage-card-title">{{ t('usage.paths.title') }}</div>
304
305
  <div v-if="!sessionUsageCharts.topPaths.length" class="usage-list-value">{{ t('usage.paths.empty') }}</div>
305
306
  <div v-else class="usage-list usage-list-scroll usage-list-top-paths">
@@ -86,6 +86,7 @@
86
86
  .selector-actions {
87
87
  display: flex;
88
88
  gap: var(--spacing-xs);
89
+ align-items: center;
89
90
  }
90
91
 
91
92
  .health-report {
@@ -360,10 +361,16 @@
360
361
  }
361
362
 
362
363
  .selector-header .btn-tool-compact {
363
- padding: 3px 7px;
364
+ display: inline-flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ width: auto;
368
+ min-width: 0;
369
+ padding: 2px 6px;
364
370
  font-size: 10px;
365
371
  line-height: 1;
366
372
  box-shadow: none;
367
- min-height: 24px;
373
+ height: 22px;
374
+ min-height: 22px;
368
375
  white-space: nowrap;
369
376
  }