codexmate 0.0.23 → 0.0.25

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 (73) hide show
  1. package/README.md +32 -9
  2. package/README.zh.md +33 -9
  3. package/cli/auth-profiles.js +23 -7
  4. package/cli/builtin-proxy.js +35 -0
  5. package/cli/claude-proxy.js +24 -0
  6. package/cli/doctor-core.js +903 -0
  7. package/cli/import-skills-url.js +356 -0
  8. package/cli/openai-bridge.js +51 -4
  9. package/cli/session-usage.js +8 -2
  10. package/cli.js +1921 -399
  11. package/lib/automation.js +404 -0
  12. package/lib/cli-models-utils.js +0 -40
  13. package/lib/cli-network-utils.js +28 -2
  14. package/lib/cli-path-utils.js +21 -5
  15. package/lib/cli-sessions.js +32 -1
  16. package/lib/download-artifacts.js +17 -2
  17. package/lib/mcp-stdio.js +13 -0
  18. package/package.json +3 -3
  19. package/plugins/README.md +20 -0
  20. package/plugins/README.zh-CN.md +20 -0
  21. package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
  22. package/plugins/prompt-templates/computed.mjs +253 -0
  23. package/plugins/prompt-templates/index.mjs +8 -0
  24. package/plugins/prompt-templates/manifest.mjs +15 -0
  25. package/plugins/prompt-templates/methods.mjs +619 -0
  26. package/plugins/prompt-templates/overview.mjs +90 -0
  27. package/plugins/prompt-templates/ownership.mjs +19 -0
  28. package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
  29. package/plugins/prompt-templates/storage.mjs +64 -0
  30. package/plugins/registry.mjs +16 -0
  31. package/web-ui/app.js +21 -35
  32. package/web-ui/index.html +4 -3
  33. package/web-ui/logic.sessions.mjs +2 -2
  34. package/web-ui/modules/app.computed.dashboard.mjs +24 -22
  35. package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
  36. package/web-ui/modules/app.computed.session.mjs +17 -0
  37. package/web-ui/modules/app.methods.agents.mjs +91 -3
  38. package/web-ui/modules/app.methods.codex-config.mjs +153 -164
  39. package/web-ui/modules/app.methods.install.mjs +28 -0
  40. package/web-ui/modules/app.methods.navigation.mjs +34 -1
  41. package/web-ui/modules/app.methods.runtime.mjs +24 -2
  42. package/web-ui/modules/app.methods.session-actions.mjs +8 -1
  43. package/web-ui/modules/app.methods.session-browser.mjs +37 -6
  44. package/web-ui/modules/app.methods.session-trash.mjs +4 -2
  45. package/web-ui/modules/config-mode.computed.mjs +1 -3
  46. package/web-ui/modules/i18n.dict.mjs +2055 -0
  47. package/web-ui/modules/i18n.mjs +2 -1769
  48. package/web-ui/partials/index/layout-header.html +48 -34
  49. package/web-ui/partials/index/modal-config-template-agents.html +3 -4
  50. package/web-ui/partials/index/modal-health-check.html +33 -60
  51. package/web-ui/partials/index/panel-config-claude.html +35 -15
  52. package/web-ui/partials/index/panel-config-codex.html +47 -19
  53. package/web-ui/partials/index/panel-config-openclaw.html +8 -3
  54. package/web-ui/partials/index/panel-dashboard.html +186 -0
  55. package/web-ui/partials/index/panel-docs.html +1 -1
  56. package/web-ui/partials/index/panel-market.html +3 -0
  57. package/web-ui/partials/index/panel-orchestration.html +3 -0
  58. package/web-ui/partials/index/panel-plugins.html +16 -10
  59. package/web-ui/partials/index/panel-sessions.html +8 -3
  60. package/web-ui/partials/index/panel-settings.html +1 -1
  61. package/web-ui/partials/index/panel-usage.html +9 -1
  62. package/web-ui/res/logo-pack.webp +0 -0
  63. package/web-ui/styles/controls-forms.css +58 -4
  64. package/web-ui/styles/dashboard.css +274 -0
  65. package/web-ui/styles/layout-shell.css +3 -2
  66. package/web-ui/styles/responsive.css +0 -2
  67. package/web-ui/styles/sessions-list.css +5 -7
  68. package/web-ui/styles/sessions-toolbar-trash.css +4 -4
  69. package/web-ui/styles/sessions-usage.css +33 -0
  70. package/web-ui/styles.css +1 -0
  71. package/res/logo.png +0 -0
  72. /package/{res → web-ui/res}/json5.min.js +0 -0
  73. /package/{res → web-ui/res}/vue.global.prod.js +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>
@@ -24,18 +24,23 @@
24
24
  <div class="selector-section">
25
25
  <div class="selector-header">
26
26
  <span class="selector-title">{{ t('sessions.sourceTitle') }}</span>
27
- <div class="selector-actions">
27
+ <div class="selector-actions sessions-header-actions">
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">
34
37
  <div class="session-toolbar-group session-toolbar-primary">
35
- <select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange" :disabled="sessionsLoading">
38
+ <select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange($event)" :disabled="sessionsLoading">
36
39
  <option value="all">{{ t('common.all') }}</option>
37
40
  <option value="codex">{{ t('sessions.source.codex') }}</option>
38
41
  <option value="claude">{{ t('sessions.source.claudeCode') }}</option>
42
+ <option value="gemini">{{ t('sessions.source.gemini') }}</option>
43
+ <option value="codebuddy">{{ t('sessions.source.codebuddy') }}</option>
39
44
  </select>
40
45
  <select
41
46
  class="session-path-select"
@@ -77,7 +82,7 @@
77
82
  <option value="90d">{{ t('sessions.time.90d') }}</option>
78
83
  </select>
79
84
  <button class="btn-tool btn-tool-compact" type="button" @click="copySessionsFilterShareUrl" :disabled="sessionsLoading">
80
- 复制筛选链接
85
+ {{ t('sessions.filters.copyLink') }}
81
86
  </button>
82
87
  <button class="btn-tool btn-tool-compact" type="button" @click="clearSessionFilters" :disabled="sessionsLoading">
83
88
  {{ 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
 
@@ -41,6 +42,13 @@
41
42
  {{ t('usage.refreshOverlay') }}
42
43
  </div>
43
44
 
45
+ <div v-if="usageCurrentSessionStats" class="usage-current-session-bar">
46
+ <span class="usage-current-session-label">{{ usageCurrentSessionStats.label }}</span>
47
+ <span class="usage-current-session-item">{{ usageCurrentSessionStats.apiDurationText }} <strong>{{ usageCurrentSessionStats.apiDurationLabel }}</strong></span>
48
+ <span class="usage-current-session-item">{{ usageCurrentSessionStats.totalDurationText }} <strong>{{ usageCurrentSessionStats.totalDurationLabel }}</strong></span>
49
+ <span class="usage-current-session-item">{{ usageCurrentSessionStats.tokenText }} <strong>{{ usageCurrentSessionStats.tokenLabel }}</strong></span>
50
+ </div>
51
+
44
52
  <div class="usage-summary-grid">
45
53
  <div
46
54
  v-for="card in sessionUsageSummaryCards"
@@ -299,7 +307,7 @@
299
307
  </div>
300
308
  </section>
301
309
 
302
- <section class="usage-card">
310
+ <section class="usage-card usage-card-top-paths">
303
311
  <div class="usage-card-title">{{ t('usage.paths.title') }}</div>
304
312
  <div v-if="!sessionUsageCharts.topPaths.length" class="usage-list-value">{{ t('usage.paths.empty') }}</div>
305
313
  <div v-else class="usage-list usage-list-scroll usage-list-top-paths">
Binary file
@@ -18,6 +18,8 @@
18
18
  display: flex;
19
19
  justify-content: space-between;
20
20
  align-items: center;
21
+ flex-wrap: nowrap;
22
+ gap: 10px;
21
23
  margin-bottom: 2px;
22
24
  }
23
25
 
@@ -81,11 +83,57 @@
81
83
  text-transform: none;
82
84
  letter-spacing: 0.03em;
83
85
  opacity: 0.85;
86
+ min-width: 0;
87
+ overflow: hidden;
88
+ text-overflow: ellipsis;
89
+ white-space: nowrap;
84
90
  }
85
91
 
86
92
  .selector-actions {
87
- display: flex;
88
- gap: var(--spacing-xs);
93
+ display: inline-grid;
94
+ grid-auto-flow: column;
95
+ grid-auto-columns: max-content;
96
+ column-gap: var(--spacing-xs);
97
+ align-items: center;
98
+ justify-content: end;
99
+ white-space: nowrap;
100
+ flex: 0 0 auto;
101
+ }
102
+
103
+ .selector-actions > .btn-tool,
104
+ .selector-actions > .btn-tool-compact {
105
+ flex: 0 0 auto;
106
+ }
107
+
108
+ .sessions-header-actions {
109
+ display: flex !important;
110
+ flex-direction: row !important;
111
+ flex-wrap: nowrap !important;
112
+ align-items: center !important;
113
+ gap: 8px !important;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ .sessions-header-actions > .btn-tool,
118
+ .sessions-header-actions > .btn-tool-compact {
119
+ display: inline-flex !important;
120
+ width: auto !important;
121
+ margin: 0 !important;
122
+ white-space: nowrap;
123
+ flex: 0 0 auto;
124
+ height: 28px !important;
125
+ min-height: 28px !important;
126
+ padding: 0 10px !important;
127
+ line-height: 1 !important;
128
+ border-radius: 8px !important;
129
+ border: 1px solid var(--color-border-soft) !important;
130
+ background: rgba(255, 255, 255, 0.72) !important;
131
+ font-size: 12px !important;
132
+ font-weight: var(--font-weight-secondary) !important;
133
+ color: var(--color-text-secondary) !important;
134
+ box-shadow: none !important;
135
+ letter-spacing: -0.01em !important;
136
+ justify-content: center !important;
89
137
  }
90
138
 
91
139
  .health-report {
@@ -360,10 +408,16 @@
360
408
  }
361
409
 
362
410
  .selector-header .btn-tool-compact {
363
- padding: 3px 7px;
411
+ display: inline-flex;
412
+ align-items: center;
413
+ justify-content: center;
414
+ width: auto;
415
+ min-width: 0;
416
+ padding: 2px 6px;
364
417
  font-size: 10px;
365
418
  line-height: 1;
366
419
  box-shadow: none;
367
- min-height: 24px;
420
+ height: 22px;
421
+ min-height: 22px;
368
422
  white-space: nowrap;
369
423
  }