codexmate 0.0.27 → 0.0.28

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 (137) hide show
  1. package/README.md +421 -421
  2. package/README.zh.md +354 -354
  3. package/cli/agents-files.js +224 -224
  4. package/cli/archive-helpers.js +446 -446
  5. package/cli/auth-profiles.js +375 -375
  6. package/cli/builtin-proxy.js +1620 -1299
  7. package/cli/claude-proxy.js +1022 -1022
  8. package/cli/config-bootstrap.js +384 -384
  9. package/cli/config-health.js +338 -338
  10. package/cli/doctor-core.js +903 -903
  11. package/cli/import-skills-url.js +356 -356
  12. package/cli/openai-bridge.js +1489 -1091
  13. package/cli/openclaw-config.js +629 -629
  14. package/cli/session-convert-args.js +65 -65
  15. package/cli/session-convert-io.js +82 -82
  16. package/cli/session-convert.js +43 -43
  17. package/cli/session-usage.concurrent.js +28 -28
  18. package/cli/session-usage.js +118 -118
  19. package/cli/session-usage.models.js +176 -176
  20. package/cli/skills.js +1141 -1141
  21. package/cli/zip-commands.js +510 -510
  22. package/cli.js +15264 -15251
  23. package/lib/automation.js +404 -404
  24. package/lib/cli-file-utils.js +151 -151
  25. package/lib/cli-models-utils.js +440 -379
  26. package/lib/cli-network-utils.js +190 -190
  27. package/lib/cli-path-utils.js +85 -85
  28. package/lib/cli-session-utils.js +121 -121
  29. package/lib/cli-sessions.js +417 -417
  30. package/lib/cli-utils.js +155 -155
  31. package/lib/download-artifacts.js +92 -92
  32. package/lib/mcp-stdio.js +453 -453
  33. package/lib/task-orchestrator.js +869 -869
  34. package/lib/text-diff.js +303 -303
  35. package/lib/workflow-engine.js +340 -340
  36. package/package.json +3 -1
  37. package/plugins/README.md +20 -20
  38. package/plugins/README.zh-CN.md +20 -20
  39. package/plugins/prompt-templates/comment-polish/index.mjs +25 -25
  40. package/plugins/prompt-templates/computed.mjs +253 -253
  41. package/plugins/prompt-templates/index.mjs +8 -8
  42. package/plugins/prompt-templates/manifest.mjs +15 -15
  43. package/plugins/prompt-templates/methods.mjs +553 -619
  44. package/plugins/prompt-templates/overview.mjs +91 -90
  45. package/plugins/prompt-templates/ownership.mjs +19 -19
  46. package/plugins/prompt-templates/rule-ack/index.mjs +21 -21
  47. package/plugins/prompt-templates/storage.mjs +64 -64
  48. package/plugins/registry.mjs +16 -16
  49. package/web-ui/app.js +625 -634
  50. package/web-ui/index.html +35 -35
  51. package/web-ui/logic.agents-diff.mjs +386 -386
  52. package/web-ui/logic.claude.mjs +168 -168
  53. package/web-ui/logic.codex.mjs +56 -0
  54. package/web-ui/logic.mjs +5 -5
  55. package/web-ui/logic.runtime.mjs +128 -128
  56. package/web-ui/logic.session-convert.mjs +70 -70
  57. package/web-ui/logic.sessions.mjs +765 -765
  58. package/web-ui/modules/api.mjs +90 -90
  59. package/web-ui/modules/app.computed.dashboard.mjs +225 -171
  60. package/web-ui/modules/app.computed.index.mjs +17 -17
  61. package/web-ui/modules/app.computed.main-tabs.mjs +205 -205
  62. package/web-ui/modules/app.computed.session.mjs +994 -994
  63. package/web-ui/modules/app.constants.mjs +15 -15
  64. package/web-ui/modules/app.methods.agents.mjs +632 -632
  65. package/web-ui/modules/app.methods.claude-config.mjs +190 -184
  66. package/web-ui/modules/app.methods.codex-config.mjs +892 -860
  67. package/web-ui/modules/app.methods.index.mjs +92 -92
  68. package/web-ui/modules/app.methods.install.mjs +205 -205
  69. package/web-ui/modules/app.methods.navigation.mjs +743 -743
  70. package/web-ui/modules/app.methods.openclaw-core.mjs +814 -814
  71. package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -372
  72. package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -369
  73. package/web-ui/modules/app.methods.providers.mjs +412 -404
  74. package/web-ui/modules/app.methods.runtime.mjs +345 -345
  75. package/web-ui/modules/app.methods.session-actions.mjs +593 -596
  76. package/web-ui/modules/app.methods.session-browser.mjs +984 -989
  77. package/web-ui/modules/app.methods.session-timeline.mjs +479 -479
  78. package/web-ui/modules/app.methods.session-trash.mjs +439 -439
  79. package/web-ui/modules/app.methods.startup-claude.mjs +533 -526
  80. package/web-ui/modules/app.methods.task-orchestration.mjs +556 -556
  81. package/web-ui/modules/config-mode.computed.mjs +124 -124
  82. package/web-ui/modules/config-template-confirm-pref.mjs +33 -33
  83. package/web-ui/modules/i18n.dict.mjs +2109 -2131
  84. package/web-ui/modules/i18n.mjs +56 -56
  85. package/web-ui/modules/plugins.computed.mjs +3 -3
  86. package/web-ui/modules/plugins.methods.mjs +3 -3
  87. package/web-ui/modules/plugins.storage.mjs +11 -11
  88. package/web-ui/modules/provider-url-display.mjs +17 -0
  89. package/web-ui/modules/sessions-filters-url.mjs +85 -85
  90. package/web-ui/modules/skills.computed.mjs +107 -107
  91. package/web-ui/modules/skills.methods.mjs +481 -481
  92. package/web-ui/partials/index/layout-footer.html +13 -13
  93. package/web-ui/partials/index/layout-header.html +475 -475
  94. package/web-ui/partials/index/modal-config-template-agents.html +174 -174
  95. package/web-ui/partials/index/modal-confirm-toast.html +32 -32
  96. package/web-ui/partials/index/modal-health-check.html +45 -45
  97. package/web-ui/partials/index/modal-openclaw-config.html +280 -280
  98. package/web-ui/partials/index/modal-skills.html +200 -200
  99. package/web-ui/partials/index/modals-basic.html +165 -165
  100. package/web-ui/partials/index/panel-config-claude.html +188 -187
  101. package/web-ui/partials/index/panel-config-codex.html +312 -283
  102. package/web-ui/partials/index/panel-config-openclaw.html +83 -83
  103. package/web-ui/partials/index/panel-dashboard.html +186 -186
  104. package/web-ui/partials/index/panel-docs.html +147 -147
  105. package/web-ui/partials/index/panel-market.html +177 -177
  106. package/web-ui/partials/index/panel-orchestration.html +391 -391
  107. package/web-ui/partials/index/panel-plugins.html +253 -279
  108. package/web-ui/partials/index/panel-sessions.html +316 -326
  109. package/web-ui/partials/index/panel-settings.html +253 -274
  110. package/web-ui/partials/index/panel-usage.html +371 -371
  111. package/web-ui/res/json5.min.js +1 -1
  112. package/web-ui/res/vue.global.prod.js +13 -13
  113. package/web-ui/session-helpers.mjs +576 -576
  114. package/web-ui/source-bundle.cjs +233 -233
  115. package/web-ui/styles/base-theme.css +281 -268
  116. package/web-ui/styles/controls-forms.css +422 -423
  117. package/web-ui/styles/dashboard.css +274 -274
  118. package/web-ui/styles/docs-panel.css +247 -247
  119. package/web-ui/styles/feedback.css +108 -108
  120. package/web-ui/styles/health-check-dialog.css +144 -144
  121. package/web-ui/styles/layout-shell.css +606 -603
  122. package/web-ui/styles/modals-core.css +466 -464
  123. package/web-ui/styles/navigation-panels.css +391 -390
  124. package/web-ui/styles/openclaw-structured.css +266 -266
  125. package/web-ui/styles/plugins-panel.css +523 -523
  126. package/web-ui/styles/responsive.css +454 -454
  127. package/web-ui/styles/sessions-list.css +419 -415
  128. package/web-ui/styles/sessions-preview.css +411 -411
  129. package/web-ui/styles/sessions-toolbar-trash.css +330 -330
  130. package/web-ui/styles/sessions-usage.css +1040 -1040
  131. package/web-ui/styles/settings-panel.css +185 -185
  132. package/web-ui/styles/skills-list.css +303 -303
  133. package/web-ui/styles/skills-market.css +406 -406
  134. package/web-ui/styles/task-orchestration.css +822 -822
  135. package/web-ui/styles/titles-cards.css +472 -408
  136. package/web-ui/styles.css +21 -21
  137. package/web-ui.html +17 -17
@@ -1,475 +1,475 @@
1
- <div id="app" class="container" v-cloak>
2
- <div v-if="!sessionStandalone" class="top-tabs" role="tablist" :aria-label="t('nav.topTabs.aria')">
3
- <button class="top-tab"
4
- id="tab-dashboard"
5
- role="tab"
6
- data-main-tab="dashboard"
7
- :tabindex="mainTab === 'dashboard' ? 0 : -1"
8
- :aria-selected="mainTab === 'dashboard'"
9
- aria-controls="panel-dashboard"
10
- :class="{ active: isMainTabNavActive('dashboard') }"
11
- @pointerdown="onMainTabPointerDown('dashboard', $event)"
12
- @click="onMainTabClick('dashboard', $event)">{{ t('tab.dashboard') }}</button>
13
- <button class="top-tab"
14
- id="tab-docs"
15
- role="tab"
16
- data-main-tab="docs"
17
- :tabindex="mainTab === 'docs' ? 0 : -1"
18
- :aria-selected="mainTab === 'docs'"
19
- aria-controls="panel-docs"
20
- :class="{ active: isMainTabNavActive('docs') }"
21
- @pointerdown="onMainTabPointerDown('docs', $event)"
22
- @click="onMainTabClick('docs', $event)">{{ t('tab.docs') }}</button>
23
- <button class="top-tab"
24
- id="tab-config"
25
- role="tab"
26
- data-main-tab="config"
27
- :data-config-mode="configMode"
28
- :tabindex="mainTab === 'config' ? 0 : -1"
29
- :aria-selected="mainTab === 'config'"
30
- :aria-controls="configMode === 'claude' ? 'panel-config-claude' : (configMode === 'openclaw' ? 'panel-config-openclaw' : 'panel-config-provider')"
31
- :class="{ active: isMainTabNavActive('config') }"
32
- @pointerdown="onMainTabPointerDown('config', $event)"
33
- @click="onMainTabClick('config', $event)">{{ t('tab.config') }}</button>
34
- <button class="top-tab"
35
- id="tab-sessions"
36
- role="tab"
37
- data-main-tab="sessions"
38
- :tabindex="mainTab === 'sessions' ? 0 : -1"
39
- :aria-selected="mainTab === 'sessions'"
40
- aria-controls="panel-sessions"
41
- :class="{ active: isMainTabNavActive('sessions') }"
42
- @pointerdown="onMainTabPointerDown('sessions', $event)"
43
- @click="onMainTabClick('sessions', $event)">{{ t('tab.sessions') }}</button>
44
- <button class="top-tab"
45
- id="tab-usage"
46
- role="tab"
47
- data-main-tab="usage"
48
- :tabindex="mainTab === 'usage' ? 0 : -1"
49
- :aria-selected="mainTab === 'usage'"
50
- aria-controls="panel-usage"
51
- :class="{ active: isMainTabNavActive('usage') }"
52
- @pointerdown="onMainTabPointerDown('usage', $event)"
53
- @click="onMainTabClick('usage', $event)">{{ t('tab.usage') }}</button>
54
- <button v-if="taskOrchestrationTabEnabled" class="top-tab"
55
- id="tab-orchestration"
56
- role="tab"
57
- data-main-tab="orchestration"
58
- :tabindex="mainTab === 'orchestration' ? 0 : -1"
59
- :aria-selected="mainTab === 'orchestration'"
60
- aria-controls="panel-orchestration"
61
- :class="{ active: isMainTabNavActive('orchestration') }"
62
- @pointerdown="onMainTabPointerDown('orchestration', $event)"
63
- @click="onMainTabClick('orchestration', $event)">{{ t('tab.orchestration') }}</button>
64
- <button class="top-tab"
65
- id="tab-market"
66
- role="tab"
67
- data-main-tab="market"
68
- :tabindex="mainTab === 'market' ? 0 : -1"
69
- :aria-selected="mainTab === 'market'"
70
- aria-controls="panel-market"
71
- :class="{ active: isMainTabNavActive('market') }"
72
- @pointerdown="onMainTabPointerDown('market', $event)"
73
- @click="onMainTabClick('market', $event)">{{ t('tab.market') }}</button>
74
- <button class="top-tab"
75
- id="tab-plugins"
76
- role="tab"
77
- data-main-tab="plugins"
78
- :tabindex="mainTab === 'plugins' ? 0 : -1"
79
- :aria-selected="mainTab === 'plugins'"
80
- aria-controls="panel-plugins"
81
- :class="{ active: isMainTabNavActive('plugins') }"
82
- @pointerdown="onMainTabPointerDown('plugins', $event)"
83
- @click="onMainTabClick('plugins', $event)">{{ t('tab.plugins') }}</button>
84
- <button class="top-tab"
85
- id="tab-settings"
86
- role="tab"
87
- data-main-tab="settings"
88
- :tabindex="mainTab === 'settings' ? 0 : -1"
89
- :aria-selected="mainTab === 'settings'"
90
- aria-controls="panel-settings"
91
- :class="{ active: isMainTabNavActive('settings') }"
92
- @pointerdown="onMainTabPointerDown('settings', $event)"
93
- @click="onMainTabClick('settings', $event)">{{ t('tab.settings') }}</button>
94
- </div>
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
- </div>
111
- </div>
112
-
113
- <div :class="['app-shell', { standalone: sessionStandalone }]">
114
- <aside class="side-rail" v-if="!sessionStandalone">
115
- <div class="brand-block">
116
- <div class="brand-head">
117
- <img class="brand-logo" src="/res/logo-pack.webp" alt="Codex Mate logo">
118
- <div class="brand-copy">
119
- <div class="brand-kicker">{{ t('brand.kicker.workspace') }}</div>
120
- <div class="brand-title">Codex Mate</div>
121
- </div>
122
- </div>
123
- <div class="brand-subtitle">{{ t('brand.subtitle.localConfigSessionsWorkspace') }}</div>
124
- </div>
125
-
126
- <div class="side-rail-nav">
127
- <div class="side-section" role="navigation" :aria-label="t('side.overview')">
128
- <div class="side-section-title">{{ t('side.overview') }}</div>
129
- <button
130
- id="side-tab-dashboard"
131
- data-main-tab="dashboard"
132
- :aria-current="mainTab === 'dashboard' ? 'page' : null"
133
- :class="['side-item', { active: isMainTabNavActive('dashboard') }]"
134
- @pointerdown="onMainTabPointerDown('dashboard', $event)"
135
- @click="onMainTabClick('dashboard', $event)">
136
- <div class="side-item-title">{{ t('side.overview.doctor') }}</div>
137
- <div class="side-item-meta">
138
- <span>{{ t('side.overview.doctor.meta') }}</span>
139
- <span>{{ inspectorHealthStatus }}</span>
140
- </div>
141
- </button>
142
- </div>
143
- <div class="side-section" role="navigation" :aria-label="t('side.docs')">
144
- <div class="side-section-title">{{ t('side.docs') }}</div>
145
- <button
146
- id="side-tab-docs"
147
- data-main-tab="docs"
148
- :aria-current="mainTab === 'docs' ? 'page' : null"
149
- :class="['side-item', { active: isMainTabNavActive('docs') }]"
150
- @pointerdown="onMainTabPointerDown('docs', $event)"
151
- @click="onMainTabClick('docs', $event)">
152
- <div class="side-item-title">{{ t('side.docs.cliInstall') }}</div>
153
- <div class="side-item-meta">
154
- <span>{{ t('side.docs.cliInstall.meta') }}</span>
155
- <span>{{ String(installPackageManager || 'npm').toUpperCase() }} · {{ installCommandAction === 'update' ? t('common.update') : (installCommandAction === 'uninstall' ? t('common.uninstall') : t('common.install')) }}</span>
156
- </div>
157
- </button>
158
- </div>
159
- <div class="side-section" role="navigation" :aria-label="t('side.config')">
160
- <div class="side-section-title">{{ t('side.config') }}</div>
161
- <button
162
- id="side-tab-config-codex"
163
- data-main-tab="config"
164
- data-config-mode="codex"
165
- :aria-current="mainTab === 'config' && configMode === 'codex' ? 'page' : null"
166
- :class="['side-item', { active: isConfigModeNavActive('codex') }]"
167
- @pointerdown="onConfigTabPointerDown('codex', $event)"
168
- @click="onConfigTabClick('codex', $event)">
169
- <div class="side-item-title">{{ t('side.config.codex') }}</div>
170
- <div class="side-item-meta">
171
- <span>{{ t('side.config.codex.meta') }}</span>
172
- <span v-if="currentProvider">{{ t('common.current', { value: currentProvider }) }}</span>
173
- </div>
174
- </button>
175
- <button
176
- id="side-tab-config-claude"
177
- data-main-tab="config"
178
- data-config-mode="claude"
179
- :aria-current="mainTab === 'config' && configMode === 'claude' ? 'page' : null"
180
- :class="['side-item', { active: isConfigModeNavActive('claude') }]"
181
- @pointerdown="onConfigTabPointerDown('claude', $event)"
182
- @click="onConfigTabClick('claude', $event)">
183
- <div class="side-item-title">{{ t('side.config.claude') }}</div>
184
- <div class="side-item-meta">
185
- <span>{{ t('side.config.claude.meta') }}</span>
186
- <span v-if="currentClaudeConfig">{{ t('common.current', { value: currentClaudeConfig }) }}</span>
187
- </div>
188
- </button>
189
- <button
190
- id="side-tab-config-openclaw"
191
- data-main-tab="config"
192
- data-config-mode="openclaw"
193
- :aria-current="mainTab === 'config' && configMode === 'openclaw' ? 'page' : null"
194
- :class="['side-item', { active: isConfigModeNavActive('openclaw') }]"
195
- @pointerdown="onConfigTabPointerDown('openclaw', $event)"
196
- @click="onConfigTabClick('openclaw', $event)">
197
- <div class="side-item-title">{{ t('side.config.openclaw') }}</div>
198
- <div class="side-item-meta">
199
- <span>{{ t('side.config.openclaw.meta') }}</span>
200
- <span v-if="currentOpenclawConfig">{{ t('common.current', { value: currentOpenclawConfig }) }}</span>
201
- </div>
202
- </button>
203
- </div>
204
-
205
- <div class="side-section" role="navigation" :aria-label="t('side.sessions')">
206
- <div class="side-section-title">{{ t('side.sessions') }}</div>
207
- <button
208
- id="side-tab-sessions"
209
- data-main-tab="sessions"
210
- :aria-current="mainTab === 'sessions' ? 'page' : null"
211
- :class="['side-item', { active: isMainTabNavActive('sessions') }]"
212
- @pointerdown="onMainTabPointerDown('sessions', $event)"
213
- @click="onMainTabClick('sessions', $event)">
214
- <div class="side-item-title">{{ t('side.sessions.browser') }}</div>
215
- <div class="side-item-meta">
216
- <span>{{ t('side.sessions.browser.meta') }}</span>
217
- <span>{{ t('sessions.sourceLabel', { value: (sessionFilterSource === 'all' ? t('sessions.source.all') : (sessionFilterSource === 'claude' ? 'Claude Code' : (sessionFilterSource === 'gemini' ? 'Gemini CLI' : (sessionFilterSource === 'codebuddy' ? 'CodeBuddy Code' : 'Codex')))) }) }}</span>
218
- </div>
219
- </button>
220
- <button
221
- id="side-tab-usage"
222
- data-main-tab="usage"
223
- :aria-current="mainTab === 'usage' ? 'page' : null"
224
- :class="['side-item', { active: isMainTabNavActive('usage') }]"
225
- @pointerdown="onMainTabPointerDown('usage', $event)"
226
- @click="onMainTabClick('usage', $event)">
227
- <div class="side-item-title">{{ t('tab.usage') }}</div>
228
- <div class="side-item-meta">
229
- <span>{{ t('side.usage.meta') }}</span>
230
- <span>{{ t('usage.rangeLabel', { value: (sessionsUsageTimeRange === 'all' ? t('usage.range.all') : (sessionsUsageTimeRange === '30d' ? t('usage.range.30d.short') : t('usage.range.7d.short'))) }) }}</span>
231
- </div>
232
- </button>
233
- </div>
234
-
235
- <div v-if="taskOrchestrationTabEnabled" class="side-section" role="navigation" :aria-label="t('side.orchestration')">
236
- <div class="side-section-title">{{ t('side.orchestration') }}</div>
237
- <button
238
- id="side-tab-orchestration"
239
- data-main-tab="orchestration"
240
- :aria-current="mainTab === 'orchestration' ? 'page' : null"
241
- :class="['side-item', { active: isMainTabNavActive('orchestration') }]"
242
- @pointerdown="onMainTabPointerDown('orchestration', $event)"
243
- @click="onMainTabClick('orchestration', $event)">
244
- <div class="side-item-title">{{ t('tab.orchestration') }}</div>
245
- <div class="side-item-meta">
246
- <span>{{ t('side.orchestration.meta') }}</span>
247
- <span>{{ t('orchestration.queueStats', { running: taskOrchestrationQueueStats.running, queued: taskOrchestrationQueueStats.queued }) }}</span>
248
- </div>
249
- </button>
250
- </div>
251
-
252
- <div class="side-section" role="navigation" :aria-label="t('side.skills')">
253
- <div class="side-section-title">{{ t('side.skills') }}</div>
254
- <button
255
- id="side-tab-market"
256
- data-main-tab="market"
257
- :aria-current="mainTab === 'market' ? 'page' : null"
258
- :class="['side-item', { active: isMainTabNavActive('market') }]"
259
- @pointerdown="onMainTabPointerDown('market', $event)"
260
- @click="onMainTabClick('market', $event)">
261
- <div class="side-item-title">{{ t('tab.market') }}</div>
262
- <div class="side-item-meta">
263
- <span>{{ t('skills.localLabel', { target: skillsTargetLabel }) }}</span>
264
- <span>{{ t('skills.counts', { installed: skillsList.length, importable: skillsImportList.length }) }}</span>
265
- </div>
266
- </button>
267
- </div>
268
-
269
- <div class="side-section" role="navigation" :aria-label="t('side.plugins')">
270
- <div class="side-section-title">{{ t('side.plugins') }}</div>
271
- <button
272
- id="side-tab-plugins"
273
- data-main-tab="plugins"
274
- :aria-current="mainTab === 'plugins' ? 'page' : null"
275
- :class="['side-item', { active: isMainTabNavActive('plugins') }]"
276
- @pointerdown="onMainTabPointerDown('plugins', $event)"
277
- @click="onMainTabClick('plugins', $event)">
278
- <div class="side-item-title">{{ t('side.plugins.tools') }}</div>
279
- <div class="side-item-meta">
280
- <span>{{ t('side.plugins.tools.meta') }}</span>
281
- <span>{{ promptTemplatesList.length }} templates</span>
282
- </div>
283
- </button>
284
- </div>
285
-
286
- <div class="side-section" role="navigation" :aria-label="t('side.system')">
287
- <div class="side-section-title">{{ t('side.system') }}</div>
288
- <button
289
- id="side-tab-settings"
290
- data-main-tab="settings"
291
- :aria-current="mainTab === 'settings' ? 'page' : null"
292
- :class="['side-item', { active: isMainTabNavActive('settings') }]"
293
- @pointerdown="onMainTabPointerDown('settings', $event)"
294
- @click="onMainTabClick('settings', $event)">
295
- <div class="side-item-title">{{ t('side.system.settings') }}</div>
296
- <div class="side-item-meta">
297
- <span>{{ t('side.system.settings.meta') }}</span>
298
- </div>
299
- </button>
300
- </div>
301
- </div>
302
-
303
- <div class="side-rail-lang" role="group" :aria-label="t('lang.label')">
304
- <div class="lang-choice" role="group" :aria-label="t('lang.label')">
305
- <button
306
- type="button"
307
- class="lang-choice-btn"
308
- :aria-pressed="(lang || 'zh') === 'zh'"
309
- :class="{ active: (lang || 'zh') === 'zh' }"
310
- @click="setLang('zh')">ZH</button>
311
- <button
312
- type="button"
313
- class="lang-choice-btn"
314
- :aria-pressed="(lang || 'zh') === 'en'"
315
- :class="{ active: (lang || 'zh') === 'en' }"
316
- @click="setLang('en')">EN</button>
317
- </div>
318
- </div>
319
- </aside>
320
- <main class="main-panel">
321
- <div class="main-panel-topbar">
322
- <div class="panel-header panel-header-refined" v-if="!sessionStandalone">
323
- <div class="panel-header-copy">
324
- <div class="panel-kicker">{{ mainTabKicker }}</div>
325
- <h1 class="main-title">{{ mainTabTitle }}</h1>
326
- <p class="subtitle">{{ mainTabSubtitle }}</p>
327
- </div>
328
- </div>
329
-
330
- <div class="status-strip" v-if="!sessionStandalone && mainTab === 'config'">
331
- <template v-if="isProviderConfigMode">
332
- <div class="status-chip">
333
- <span class="label">{{ activeProviderConfigChipLabel }}</span>
334
- <span class="value">{{ currentProvider || t('common.notSelected') }}</span>
335
- </div>
336
- <div class="status-chip">
337
- <span class="label">{{ activeProviderModelChipLabel }}</span>
338
- <span class="value">{{ currentModel || t('common.notSelected') }}</span>
339
- </div>
340
- </template>
341
- <template v-else-if="configMode === 'claude'">
342
- <div class="status-chip">
343
- <span class="label">{{ t('status.claudeConfig') }}</span>
344
- <span class="value">{{ currentClaudeConfig || t('common.notSelected') }}</span>
345
- </div>
346
- <div class="status-chip">
347
- <span class="label">{{ t('status.claudeModel') }}</span>
348
- <span class="value">{{ currentClaudeModel || t('common.notSelected') }}</span>
349
- </div>
350
- </template>
351
- <template v-else-if="configMode === 'openclaw'">
352
- <div class="status-chip">
353
- <span class="label">{{ t('status.openclawConfig') }}</span>
354
- <span class="value">{{ currentOpenclawConfig || t('common.notSelected') }}</span>
355
- </div>
356
- <div class="status-chip">
357
- <span class="label">{{ t('status.workspaceFile') }}</span>
358
- <span class="value">{{ openclawWorkspaceFileName || t('common.notSelected') }}</span>
359
- </div>
360
- </template>
361
- <template v-else>
362
- <div class="status-chip">
363
- <span class="label">{{ t('status.configMode') }}</span>
364
- <span class="value">{{ t('common.notSelected') }}</span>
365
- </div>
366
- </template>
367
- </div>
368
- <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'sessions'">
369
- <div class="status-chip">
370
- <span class="label">{{ t('status.currentSource') }}</span>
371
- <span class="value">
372
- {{ sessionFilterSource === 'all'
373
- ? t('sessions.source.all')
374
- : (sessionFilterSource === 'codex'
375
- ? t('sessions.source.codex')
376
- : (sessionFilterSource === 'claude'
377
- ? t('sessions.source.claudeCode')
378
- : (sessionFilterSource === 'gemini'
379
- ? t('sessions.source.gemini')
380
- : (sessionFilterSource === 'codebuddy'
381
- ? t('sessions.source.codebuddy')
382
- : t('sessions.source.codex'))))) }}
383
- </span>
384
- </div>
385
- <div class="status-chip">
386
- <span class="label">{{ t('status.sessionCount') }}</span>
387
- <span class="value">{{ sessionsList.length }}</span>
388
- </div>
389
- </div>
390
- <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'usage'">
391
- <div class="status-chip">
392
- <span class="label">{{ t('status.range') }}</span>
393
- <span class="value">{{ sessionsUsageTimeRange === 'all' ? t('usage.range.all') : (sessionsUsageTimeRange === '30d' ? t('usage.range.30d.short') : t('usage.range.7d.short')) }}</span>
394
- </div>
395
- <div class="status-chip">
396
- <span class="label">{{ t('status.totalSessions') }}</span>
397
- <span class="value">{{ sessionUsageSummaryCards[0] ? sessionUsageSummaryCards[0].value : 0 }}</span>
398
- </div>
399
- <div class="status-chip">
400
- <span class="label">{{ t('status.totalMessages') }}</span>
401
- <span class="value">{{ sessionUsageSummaryCards[1] ? sessionUsageSummaryCards[1].value : 0 }}</span>
402
- </div>
403
- </div>
404
- <div class="status-strip" v-else-if="!sessionStandalone && taskOrchestrationTabEnabled && mainTab === 'orchestration'">
405
- <div class="status-chip">
406
- <span class="label">{{ t('status.engine') }}</span>
407
- <span class="value">{{ taskOrchestration.selectedEngine === 'workflow' ? 'Workflow' : 'Codex' }}</span>
408
- </div>
409
- <div class="status-chip">
410
- <span class="label">{{ t('status.concurrency') }}</span>
411
- <span class="value">{{ taskOrchestration.concurrency }}</span>
412
- </div>
413
- <div class="status-chip">
414
- <span class="label">{{ t('status.running') }}</span>
415
- <span class="value">{{ taskOrchestrationQueueStats.running }}</span>
416
- </div>
417
- <div class="status-chip">
418
- <span class="label">{{ t('status.queued') }}</span>
419
- <span class="value">{{ taskOrchestrationQueueStats.queued }}</span>
420
- </div>
421
- <div class="status-chip">
422
- <span class="label">{{ t('status.runs') }}</span>
423
- <span class="value">{{ taskOrchestration.runs.length }}</span>
424
- </div>
425
- </div>
426
- <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'market'">
427
- <div class="status-chip">
428
- <span class="label">{{ t('status.currentTarget') }}</span>
429
- <span class="value">{{ skillsTargetLabel }}</span>
430
- </div>
431
- <div class="status-chip">
432
- <span class="label">{{ t('status.localSkills') }}</span>
433
- <span class="value">{{ skillsList.length }}</span>
434
- </div>
435
- <div class="status-chip">
436
- <span class="label">{{ t('status.importable') }}</span>
437
- <span class="value">{{ skillsImportList.length }}</span>
438
- </div>
439
- <div class="status-chip">
440
- <span class="label">{{ t('status.importableDirect') }}</span>
441
- <span class="value">{{ skillsImportConfiguredCount }}</span>
442
- </div>
443
- </div>
444
- <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'docs'">
445
- <div class="status-chip">
446
- <span class="label">{{ t('status.pm') }}</span>
447
- <span class="value">{{ String(installPackageManager || 'npm').toUpperCase() }}</span>
448
- </div>
449
- <div class="status-chip">
450
- <span class="label">{{ t('status.action') }}</span>
451
- <span class="value">{{ installCommandAction === 'update' ? t('common.update') : (installCommandAction === 'uninstall' ? t('common.uninstall') : t('common.install')) }}</span>
452
- </div>
453
- <div class="status-chip">
454
- <span class="label">{{ t('status.registry') }}</span>
455
- <span class="value">{{ installRegistryPreview || t('common.defaultOfficial') }}</span>
456
- </div>
457
- </div>
458
- <div
459
- v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && displayProvidersList.length > 1"
460
- class="provider-fast-switch">
461
- <label class="provider-fast-switch-label" for="provider-fast-switch-select">{{ t('status.quickSwitchProvider') }}</label>
462
- <select
463
- id="provider-fast-switch-select"
464
- class="provider-fast-switch-select"
465
- :value="displayCurrentProvider"
466
- @change="quickSwitchProvider($event.target.value)">
467
- <option v-for="provider in displayProvidersList" :key="'quick-switch-' + provider.name" :value="provider.name">
468
- {{ provider.name }}
469
- </option>
470
- </select>
471
- </div>
472
- </div>
473
-
474
- <!-- 内容包裹器 - 稳定布局 -->
475
- <div class="content-wrapper">
1
+ <div id="app" class="container" v-cloak>
2
+ <div v-if="!sessionStandalone" class="top-tabs" role="tablist" :aria-label="t('nav.topTabs.aria')">
3
+ <button class="top-tab"
4
+ id="tab-dashboard"
5
+ role="tab"
6
+ data-main-tab="dashboard"
7
+ :tabindex="mainTab === 'dashboard' ? 0 : -1"
8
+ :aria-selected="mainTab === 'dashboard'"
9
+ aria-controls="panel-dashboard"
10
+ :class="{ active: isMainTabNavActive('dashboard') }"
11
+ @pointerdown="onMainTabPointerDown('dashboard', $event)"
12
+ @click="onMainTabClick('dashboard', $event)">{{ t('tab.dashboard') }}</button>
13
+ <button class="top-tab"
14
+ id="tab-docs"
15
+ role="tab"
16
+ data-main-tab="docs"
17
+ :tabindex="mainTab === 'docs' ? 0 : -1"
18
+ :aria-selected="mainTab === 'docs'"
19
+ aria-controls="panel-docs"
20
+ :class="{ active: isMainTabNavActive('docs') }"
21
+ @pointerdown="onMainTabPointerDown('docs', $event)"
22
+ @click="onMainTabClick('docs', $event)">{{ t('tab.docs') }}</button>
23
+ <button class="top-tab"
24
+ id="tab-config"
25
+ role="tab"
26
+ data-main-tab="config"
27
+ :data-config-mode="configMode"
28
+ :tabindex="mainTab === 'config' ? 0 : -1"
29
+ :aria-selected="mainTab === 'config'"
30
+ :aria-controls="configMode === 'claude' ? 'panel-config-claude' : (configMode === 'openclaw' ? 'panel-config-openclaw' : 'panel-config-provider')"
31
+ :class="{ active: isMainTabNavActive('config') }"
32
+ @pointerdown="onMainTabPointerDown('config', $event)"
33
+ @click="onMainTabClick('config', $event)">{{ t('tab.config') }}</button>
34
+ <button class="top-tab"
35
+ id="tab-sessions"
36
+ role="tab"
37
+ data-main-tab="sessions"
38
+ :tabindex="mainTab === 'sessions' ? 0 : -1"
39
+ :aria-selected="mainTab === 'sessions'"
40
+ aria-controls="panel-sessions"
41
+ :class="{ active: isMainTabNavActive('sessions') }"
42
+ @pointerdown="onMainTabPointerDown('sessions', $event)"
43
+ @click="onMainTabClick('sessions', $event)">{{ t('tab.sessions') }}</button>
44
+ <button class="top-tab"
45
+ id="tab-usage"
46
+ role="tab"
47
+ data-main-tab="usage"
48
+ :tabindex="mainTab === 'usage' ? 0 : -1"
49
+ :aria-selected="mainTab === 'usage'"
50
+ aria-controls="panel-usage"
51
+ :class="{ active: isMainTabNavActive('usage') }"
52
+ @pointerdown="onMainTabPointerDown('usage', $event)"
53
+ @click="onMainTabClick('usage', $event)">{{ t('tab.usage') }}</button>
54
+ <button v-if="taskOrchestrationTabEnabled" class="top-tab"
55
+ id="tab-orchestration"
56
+ role="tab"
57
+ data-main-tab="orchestration"
58
+ :tabindex="mainTab === 'orchestration' ? 0 : -1"
59
+ :aria-selected="mainTab === 'orchestration'"
60
+ aria-controls="panel-orchestration"
61
+ :class="{ active: isMainTabNavActive('orchestration') }"
62
+ @pointerdown="onMainTabPointerDown('orchestration', $event)"
63
+ @click="onMainTabClick('orchestration', $event)">{{ t('tab.orchestration') }}</button>
64
+ <button class="top-tab"
65
+ id="tab-market"
66
+ role="tab"
67
+ data-main-tab="market"
68
+ :tabindex="mainTab === 'market' ? 0 : -1"
69
+ :aria-selected="mainTab === 'market'"
70
+ aria-controls="panel-market"
71
+ :class="{ active: isMainTabNavActive('market') }"
72
+ @pointerdown="onMainTabPointerDown('market', $event)"
73
+ @click="onMainTabClick('market', $event)">{{ t('tab.market') }}</button>
74
+ <button class="top-tab"
75
+ id="tab-plugins"
76
+ role="tab"
77
+ data-main-tab="plugins"
78
+ :tabindex="mainTab === 'plugins' ? 0 : -1"
79
+ :aria-selected="mainTab === 'plugins'"
80
+ aria-controls="panel-plugins"
81
+ :class="{ active: isMainTabNavActive('plugins') }"
82
+ @pointerdown="onMainTabPointerDown('plugins', $event)"
83
+ @click="onMainTabClick('plugins', $event)">{{ t('tab.plugins') }}</button>
84
+ <button class="top-tab"
85
+ id="tab-settings"
86
+ role="tab"
87
+ data-main-tab="settings"
88
+ :tabindex="mainTab === 'settings' ? 0 : -1"
89
+ :aria-selected="mainTab === 'settings'"
90
+ aria-controls="panel-settings"
91
+ :class="{ active: isMainTabNavActive('settings') }"
92
+ @pointerdown="onMainTabPointerDown('settings', $event)"
93
+ @click="onMainTabClick('settings', $event)">{{ t('tab.settings') }}</button>
94
+ </div>
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
+ </div>
111
+ </div>
112
+
113
+ <div :class="['app-shell', { standalone: sessionStandalone }]">
114
+ <aside class="side-rail" v-if="!sessionStandalone">
115
+ <div class="brand-block">
116
+ <div class="brand-head">
117
+ <img class="brand-logo" src="/res/logo-pack.webp" alt="Codex Mate logo">
118
+ <div class="brand-copy">
119
+ <div class="brand-kicker">{{ t('brand.kicker.workspace') }}</div>
120
+ <div class="brand-title">Codex Mate</div>
121
+ </div>
122
+ </div>
123
+ <div class="brand-subtitle">{{ t('brand.subtitle.localConfigSessionsWorkspace') }}</div>
124
+ </div>
125
+
126
+ <div class="side-rail-nav">
127
+ <div class="side-section" role="navigation" :aria-label="t('side.overview')">
128
+ <div class="side-section-title">{{ t('side.overview') }}</div>
129
+ <button
130
+ id="side-tab-dashboard"
131
+ data-main-tab="dashboard"
132
+ :aria-current="mainTab === 'dashboard' ? 'page' : null"
133
+ :class="['side-item', { active: isMainTabNavActive('dashboard') }]"
134
+ @pointerdown="onMainTabPointerDown('dashboard', $event)"
135
+ @click="onMainTabClick('dashboard', $event)">
136
+ <div class="side-item-title">{{ t('side.overview.doctor') }}</div>
137
+ <div class="side-item-meta">
138
+ <span>{{ t('side.overview.doctor.meta') }}</span>
139
+ <span>{{ inspectorHealthStatus }}</span>
140
+ </div>
141
+ </button>
142
+ </div>
143
+ <div class="side-section" role="navigation" :aria-label="t('side.docs')">
144
+ <div class="side-section-title">{{ t('side.docs') }}</div>
145
+ <button
146
+ id="side-tab-docs"
147
+ data-main-tab="docs"
148
+ :aria-current="mainTab === 'docs' ? 'page' : null"
149
+ :class="['side-item', { active: isMainTabNavActive('docs') }]"
150
+ @pointerdown="onMainTabPointerDown('docs', $event)"
151
+ @click="onMainTabClick('docs', $event)">
152
+ <div class="side-item-title">{{ t('side.docs.cliInstall') }}</div>
153
+ <div class="side-item-meta">
154
+ <span>{{ t('side.docs.cliInstall.meta') }}</span>
155
+ <span>{{ String(installPackageManager || 'npm').toUpperCase() }} · {{ installCommandAction === 'update' ? t('common.update') : (installCommandAction === 'uninstall' ? t('common.uninstall') : t('common.install')) }}</span>
156
+ </div>
157
+ </button>
158
+ </div>
159
+ <div class="side-section" role="navigation" :aria-label="t('side.config')">
160
+ <div class="side-section-title">{{ t('side.config') }}</div>
161
+ <button
162
+ id="side-tab-config-codex"
163
+ data-main-tab="config"
164
+ data-config-mode="codex"
165
+ :aria-current="mainTab === 'config' && configMode === 'codex' ? 'page' : null"
166
+ :class="['side-item', { active: isConfigModeNavActive('codex') }]"
167
+ @pointerdown="onConfigTabPointerDown('codex', $event)"
168
+ @click="onConfigTabClick('codex', $event)">
169
+ <div class="side-item-title">{{ t('side.config.codex') }}</div>
170
+ <div class="side-item-meta">
171
+ <span>{{ t('side.config.codex.meta') }}</span>
172
+ <span v-if="currentProvider">{{ t('common.current', { value: currentProvider }) }}</span>
173
+ </div>
174
+ </button>
175
+ <button
176
+ id="side-tab-config-claude"
177
+ data-main-tab="config"
178
+ data-config-mode="claude"
179
+ :aria-current="mainTab === 'config' && configMode === 'claude' ? 'page' : null"
180
+ :class="['side-item', { active: isConfigModeNavActive('claude') }]"
181
+ @pointerdown="onConfigTabPointerDown('claude', $event)"
182
+ @click="onConfigTabClick('claude', $event)">
183
+ <div class="side-item-title">{{ t('side.config.claude') }}</div>
184
+ <div class="side-item-meta">
185
+ <span>{{ t('side.config.claude.meta') }}</span>
186
+ <span v-if="currentClaudeConfig">{{ t('common.current', { value: currentClaudeConfig }) }}</span>
187
+ </div>
188
+ </button>
189
+ <button
190
+ id="side-tab-config-openclaw"
191
+ data-main-tab="config"
192
+ data-config-mode="openclaw"
193
+ :aria-current="mainTab === 'config' && configMode === 'openclaw' ? 'page' : null"
194
+ :class="['side-item', { active: isConfigModeNavActive('openclaw') }]"
195
+ @pointerdown="onConfigTabPointerDown('openclaw', $event)"
196
+ @click="onConfigTabClick('openclaw', $event)">
197
+ <div class="side-item-title">{{ t('side.config.openclaw') }}</div>
198
+ <div class="side-item-meta">
199
+ <span>{{ t('side.config.openclaw.meta') }}</span>
200
+ <span v-if="currentOpenclawConfig">{{ t('common.current', { value: currentOpenclawConfig }) }}</span>
201
+ </div>
202
+ </button>
203
+ </div>
204
+
205
+ <div class="side-section" role="navigation" :aria-label="t('side.sessions')">
206
+ <div class="side-section-title">{{ t('side.sessions') }}</div>
207
+ <button
208
+ id="side-tab-sessions"
209
+ data-main-tab="sessions"
210
+ :aria-current="mainTab === 'sessions' ? 'page' : null"
211
+ :class="['side-item', { active: isMainTabNavActive('sessions') }]"
212
+ @pointerdown="onMainTabPointerDown('sessions', $event)"
213
+ @click="onMainTabClick('sessions', $event)">
214
+ <div class="side-item-title">{{ t('side.sessions.browser') }}</div>
215
+ <div class="side-item-meta">
216
+ <span>{{ t('side.sessions.browser.meta') }}</span>
217
+ <span>{{ t('sessions.sourceLabel', { value: (sessionFilterSource === 'all' ? t('sessions.source.all') : (sessionFilterSource === 'claude' ? 'Claude Code' : (sessionFilterSource === 'gemini' ? 'Gemini CLI' : (sessionFilterSource === 'codebuddy' ? 'CodeBuddy Code' : 'Codex')))) }) }}</span>
218
+ </div>
219
+ </button>
220
+ <button
221
+ id="side-tab-usage"
222
+ data-main-tab="usage"
223
+ :aria-current="mainTab === 'usage' ? 'page' : null"
224
+ :class="['side-item', { active: isMainTabNavActive('usage') }]"
225
+ @pointerdown="onMainTabPointerDown('usage', $event)"
226
+ @click="onMainTabClick('usage', $event)">
227
+ <div class="side-item-title">{{ t('tab.usage') }}</div>
228
+ <div class="side-item-meta">
229
+ <span>{{ t('side.usage.meta') }}</span>
230
+ <span>{{ t('usage.rangeLabel', { value: (sessionsUsageTimeRange === 'all' ? t('usage.range.all') : (sessionsUsageTimeRange === '30d' ? t('usage.range.30d.short') : t('usage.range.7d.short'))) }) }}</span>
231
+ </div>
232
+ </button>
233
+ </div>
234
+
235
+ <div v-if="taskOrchestrationTabEnabled" class="side-section" role="navigation" :aria-label="t('side.orchestration')">
236
+ <div class="side-section-title">{{ t('side.orchestration') }}</div>
237
+ <button
238
+ id="side-tab-orchestration"
239
+ data-main-tab="orchestration"
240
+ :aria-current="mainTab === 'orchestration' ? 'page' : null"
241
+ :class="['side-item', { active: isMainTabNavActive('orchestration') }]"
242
+ @pointerdown="onMainTabPointerDown('orchestration', $event)"
243
+ @click="onMainTabClick('orchestration', $event)">
244
+ <div class="side-item-title">{{ t('tab.orchestration') }}</div>
245
+ <div class="side-item-meta">
246
+ <span>{{ t('side.orchestration.meta') }}</span>
247
+ <span>{{ t('orchestration.queueStats', { running: taskOrchestrationQueueStats.running, queued: taskOrchestrationQueueStats.queued }) }}</span>
248
+ </div>
249
+ </button>
250
+ </div>
251
+
252
+ <div class="side-section" role="navigation" :aria-label="t('side.skills')">
253
+ <div class="side-section-title">{{ t('side.skills') }}</div>
254
+ <button
255
+ id="side-tab-market"
256
+ data-main-tab="market"
257
+ :aria-current="mainTab === 'market' ? 'page' : null"
258
+ :class="['side-item', { active: isMainTabNavActive('market') }]"
259
+ @pointerdown="onMainTabPointerDown('market', $event)"
260
+ @click="onMainTabClick('market', $event)">
261
+ <div class="side-item-title">{{ t('tab.market') }}</div>
262
+ <div class="side-item-meta">
263
+ <span>{{ t('skills.localLabel', { target: skillsTargetLabel }) }}</span>
264
+ <span>{{ t('skills.counts', { installed: skillsList.length, importable: skillsImportList.length }) }}</span>
265
+ </div>
266
+ </button>
267
+ </div>
268
+
269
+ <div class="side-section" role="navigation" :aria-label="t('side.plugins')">
270
+ <div class="side-section-title">{{ t('side.plugins') }}</div>
271
+ <button
272
+ id="side-tab-plugins"
273
+ data-main-tab="plugins"
274
+ :aria-current="mainTab === 'plugins' ? 'page' : null"
275
+ :class="['side-item', { active: isMainTabNavActive('plugins') }]"
276
+ @pointerdown="onMainTabPointerDown('plugins', $event)"
277
+ @click="onMainTabClick('plugins', $event)">
278
+ <div class="side-item-title">{{ t('side.plugins.tools') }}</div>
279
+ <div class="side-item-meta">
280
+ <span>{{ t('side.plugins.tools.meta') }}</span>
281
+ <span>{{ promptTemplatesList.length }} templates</span>
282
+ </div>
283
+ </button>
284
+ </div>
285
+
286
+ <div class="side-section" role="navigation" :aria-label="t('side.system')">
287
+ <div class="side-section-title">{{ t('side.system') }}</div>
288
+ <button
289
+ id="side-tab-settings"
290
+ data-main-tab="settings"
291
+ :aria-current="mainTab === 'settings' ? 'page' : null"
292
+ :class="['side-item', { active: isMainTabNavActive('settings') }]"
293
+ @pointerdown="onMainTabPointerDown('settings', $event)"
294
+ @click="onMainTabClick('settings', $event)">
295
+ <div class="side-item-title">{{ t('side.system.settings') }}</div>
296
+ <div class="side-item-meta">
297
+ <span>{{ t('side.system.settings.meta') }}</span>
298
+ </div>
299
+ </button>
300
+ </div>
301
+ </div>
302
+
303
+ <div class="side-rail-lang" role="group" :aria-label="t('lang.label')">
304
+ <div class="lang-choice" role="group" :aria-label="t('lang.label')">
305
+ <button
306
+ type="button"
307
+ class="lang-choice-btn"
308
+ :aria-pressed="(lang || 'zh') === 'zh'"
309
+ :class="{ active: (lang || 'zh') === 'zh' }"
310
+ @click="setLang('zh')">ZH</button>
311
+ <button
312
+ type="button"
313
+ class="lang-choice-btn"
314
+ :aria-pressed="(lang || 'zh') === 'en'"
315
+ :class="{ active: (lang || 'zh') === 'en' }"
316
+ @click="setLang('en')">EN</button>
317
+ </div>
318
+ </div>
319
+ </aside>
320
+ <main class="main-panel">
321
+ <div class="main-panel-topbar">
322
+ <div class="panel-header panel-header-refined" v-if="!sessionStandalone">
323
+ <div class="panel-header-copy">
324
+ <div class="panel-kicker">{{ mainTabKicker }}</div>
325
+ <h1 class="main-title">{{ mainTabTitle }}</h1>
326
+ <p class="subtitle">{{ mainTabSubtitle }}</p>
327
+ </div>
328
+ </div>
329
+
330
+ <div class="status-strip" v-if="!sessionStandalone && mainTab === 'config'">
331
+ <template v-if="isProviderConfigMode">
332
+ <div class="status-chip">
333
+ <span class="label">{{ activeProviderConfigChipLabel }}</span>
334
+ <span class="value">{{ currentProvider || t('common.notSelected') }}</span>
335
+ </div>
336
+ <div class="status-chip">
337
+ <span class="label">{{ activeProviderModelChipLabel }}</span>
338
+ <span class="value">{{ currentModel || t('common.notSelected') }}</span>
339
+ </div>
340
+ </template>
341
+ <template v-else-if="configMode === 'claude'">
342
+ <div class="status-chip">
343
+ <span class="label">{{ t('status.claudeConfig') }}</span>
344
+ <span class="value">{{ currentClaudeConfig || t('common.notSelected') }}</span>
345
+ </div>
346
+ <div class="status-chip">
347
+ <span class="label">{{ t('status.claudeModel') }}</span>
348
+ <span class="value">{{ currentClaudeModel || t('common.notSelected') }}</span>
349
+ </div>
350
+ </template>
351
+ <template v-else-if="configMode === 'openclaw'">
352
+ <div class="status-chip">
353
+ <span class="label">{{ t('status.openclawConfig') }}</span>
354
+ <span class="value">{{ currentOpenclawConfig || t('common.notSelected') }}</span>
355
+ </div>
356
+ <div class="status-chip">
357
+ <span class="label">{{ t('status.workspaceFile') }}</span>
358
+ <span class="value">{{ openclawWorkspaceFileName || t('common.notSelected') }}</span>
359
+ </div>
360
+ </template>
361
+ <template v-else>
362
+ <div class="status-chip">
363
+ <span class="label">{{ t('status.configMode') }}</span>
364
+ <span class="value">{{ t('common.notSelected') }}</span>
365
+ </div>
366
+ </template>
367
+ </div>
368
+ <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'sessions'">
369
+ <div class="status-chip">
370
+ <span class="label">{{ t('status.currentSource') }}</span>
371
+ <span class="value">
372
+ {{ sessionFilterSource === 'all'
373
+ ? t('sessions.source.all')
374
+ : (sessionFilterSource === 'codex'
375
+ ? t('sessions.source.codex')
376
+ : (sessionFilterSource === 'claude'
377
+ ? t('sessions.source.claudeCode')
378
+ : (sessionFilterSource === 'gemini'
379
+ ? t('sessions.source.gemini')
380
+ : (sessionFilterSource === 'codebuddy'
381
+ ? t('sessions.source.codebuddy')
382
+ : t('sessions.source.codex'))))) }}
383
+ </span>
384
+ </div>
385
+ <div class="status-chip">
386
+ <span class="label">{{ t('status.sessionCount') }}</span>
387
+ <span class="value">{{ sessionsList.length }}</span>
388
+ </div>
389
+ </div>
390
+ <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'usage'">
391
+ <div class="status-chip">
392
+ <span class="label">{{ t('status.range') }}</span>
393
+ <span class="value">{{ sessionsUsageTimeRange === 'all' ? t('usage.range.all') : (sessionsUsageTimeRange === '30d' ? t('usage.range.30d.short') : t('usage.range.7d.short')) }}</span>
394
+ </div>
395
+ <div class="status-chip">
396
+ <span class="label">{{ t('status.totalSessions') }}</span>
397
+ <span class="value">{{ sessionUsageSummaryCards[0] ? sessionUsageSummaryCards[0].value : 0 }}</span>
398
+ </div>
399
+ <div class="status-chip">
400
+ <span class="label">{{ t('status.totalMessages') }}</span>
401
+ <span class="value">{{ sessionUsageSummaryCards[1] ? sessionUsageSummaryCards[1].value : 0 }}</span>
402
+ </div>
403
+ </div>
404
+ <div class="status-strip" v-else-if="!sessionStandalone && taskOrchestrationTabEnabled && mainTab === 'orchestration'">
405
+ <div class="status-chip">
406
+ <span class="label">{{ t('status.engine') }}</span>
407
+ <span class="value">{{ taskOrchestration.selectedEngine === 'workflow' ? 'Workflow' : 'Codex' }}</span>
408
+ </div>
409
+ <div class="status-chip">
410
+ <span class="label">{{ t('status.concurrency') }}</span>
411
+ <span class="value">{{ taskOrchestration.concurrency }}</span>
412
+ </div>
413
+ <div class="status-chip">
414
+ <span class="label">{{ t('status.running') }}</span>
415
+ <span class="value">{{ taskOrchestrationQueueStats.running }}</span>
416
+ </div>
417
+ <div class="status-chip">
418
+ <span class="label">{{ t('status.queued') }}</span>
419
+ <span class="value">{{ taskOrchestrationQueueStats.queued }}</span>
420
+ </div>
421
+ <div class="status-chip">
422
+ <span class="label">{{ t('status.runs') }}</span>
423
+ <span class="value">{{ taskOrchestration.runs.length }}</span>
424
+ </div>
425
+ </div>
426
+ <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'market'">
427
+ <div class="status-chip">
428
+ <span class="label">{{ t('status.currentTarget') }}</span>
429
+ <span class="value">{{ skillsTargetLabel }}</span>
430
+ </div>
431
+ <div class="status-chip">
432
+ <span class="label">{{ t('status.localSkills') }}</span>
433
+ <span class="value">{{ skillsList.length }}</span>
434
+ </div>
435
+ <div class="status-chip">
436
+ <span class="label">{{ t('status.importable') }}</span>
437
+ <span class="value">{{ skillsImportList.length }}</span>
438
+ </div>
439
+ <div class="status-chip">
440
+ <span class="label">{{ t('status.importableDirect') }}</span>
441
+ <span class="value">{{ skillsImportConfiguredCount }}</span>
442
+ </div>
443
+ </div>
444
+ <div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'docs'">
445
+ <div class="status-chip">
446
+ <span class="label">{{ t('status.pm') }}</span>
447
+ <span class="value">{{ String(installPackageManager || 'npm').toUpperCase() }}</span>
448
+ </div>
449
+ <div class="status-chip">
450
+ <span class="label">{{ t('status.action') }}</span>
451
+ <span class="value">{{ installCommandAction === 'update' ? t('common.update') : (installCommandAction === 'uninstall' ? t('common.uninstall') : t('common.install')) }}</span>
452
+ </div>
453
+ <div class="status-chip">
454
+ <span class="label">{{ t('status.registry') }}</span>
455
+ <span class="value">{{ installRegistryPreview || t('common.defaultOfficial') }}</span>
456
+ </div>
457
+ </div>
458
+ <div
459
+ v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && displayProvidersList.length > 1"
460
+ class="provider-fast-switch">
461
+ <label class="provider-fast-switch-label" for="provider-fast-switch-select">{{ t('status.quickSwitchProvider') }}</label>
462
+ <select
463
+ id="provider-fast-switch-select"
464
+ class="provider-fast-switch-select"
465
+ :value="displayCurrentProvider"
466
+ @change="quickSwitchProvider($event.target.value)">
467
+ <option v-for="provider in displayProvidersList" :key="'quick-switch-' + provider.name" :value="provider.name">
468
+ {{ provider.name }}
469
+ </option>
470
+ </select>
471
+ </div>
472
+ </div>
473
+
474
+ <!-- 内容包裹器 - 稳定布局 -->
475
+ <div class="content-wrapper">