codexmate 0.0.20 → 0.0.22

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 (96) hide show
  1. package/README.md +289 -152
  2. package/README.zh.md +321 -0
  3. package/cli/agents-files.js +224 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +1044 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/openai-bridge.js +950 -0
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/session-usage.concurrent.js +28 -0
  12. package/cli/session-usage.js +112 -0
  13. package/cli/session-usage.models.js +176 -0
  14. package/cli/skills.js +1141 -0
  15. package/cli/zip-commands.js +510 -0
  16. package/cli.js +9408 -9719
  17. package/lib/cli-models-utils.js +109 -1
  18. package/lib/cli-path-utils.js +69 -0
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/download-artifacts.js +77 -0
  21. package/lib/task-orchestrator.js +869 -0
  22. package/package.json +14 -10
  23. package/res/logo.png +0 -0
  24. package/res/vue.global.prod.js +13 -0
  25. package/web-ui/app.js +193 -15
  26. package/web-ui/index.html +5 -1
  27. package/web-ui/logic.agents-diff.mjs +1 -1
  28. package/web-ui/logic.claude.mjs +60 -0
  29. package/web-ui/logic.runtime.mjs +11 -7
  30. package/web-ui/logic.sessions.mjs +372 -21
  31. package/web-ui/modules/api.mjs +22 -1
  32. package/web-ui/modules/app.computed.dashboard.mjs +23 -10
  33. package/web-ui/modules/app.computed.index.mjs +4 -0
  34. package/web-ui/modules/app.computed.main-tabs.mjs +198 -0
  35. package/web-ui/modules/app.computed.session.mjs +521 -9
  36. package/web-ui/modules/app.methods.agents.mjs +62 -11
  37. package/web-ui/modules/app.methods.codex-config.mjs +189 -34
  38. package/web-ui/modules/app.methods.index.mjs +7 -1
  39. package/web-ui/modules/app.methods.install.mjs +24 -20
  40. package/web-ui/modules/app.methods.navigation.mjs +142 -1
  41. package/web-ui/modules/app.methods.openclaw-core.mjs +339 -39
  42. package/web-ui/modules/app.methods.openclaw-editing.mjs +39 -4
  43. package/web-ui/modules/app.methods.openclaw-persist.mjs +122 -4
  44. package/web-ui/modules/app.methods.providers.mjs +192 -53
  45. package/web-ui/modules/app.methods.session-actions.mjs +99 -19
  46. package/web-ui/modules/app.methods.session-browser.mjs +196 -5
  47. package/web-ui/modules/app.methods.session-timeline.mjs +22 -15
  48. package/web-ui/modules/app.methods.session-trash.mjs +3 -0
  49. package/web-ui/modules/app.methods.startup-claude.mjs +70 -71
  50. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  51. package/web-ui/modules/config-mode.computed.mjs +2 -0
  52. package/web-ui/modules/config-template-confirm-pref.mjs +33 -0
  53. package/web-ui/modules/i18n.mjs +1609 -0
  54. package/web-ui/modules/plugins.computed.mjs +220 -0
  55. package/web-ui/modules/plugins.methods.mjs +620 -0
  56. package/web-ui/modules/plugins.storage.mjs +37 -0
  57. package/web-ui/partials/index/layout-footer.html +1 -57
  58. package/web-ui/partials/index/layout-header.html +299 -175
  59. package/web-ui/partials/index/modal-config-template-agents.html +79 -29
  60. package/web-ui/partials/index/modal-confirm-toast.html +1 -1
  61. package/web-ui/partials/index/modal-health-check.html +14 -14
  62. package/web-ui/partials/index/modal-openclaw-config.html +47 -42
  63. package/web-ui/partials/index/modal-skills.html +130 -114
  64. package/web-ui/partials/index/modals-basic.html +71 -102
  65. package/web-ui/partials/index/panel-config-claude.html +50 -12
  66. package/web-ui/partials/index/panel-config-codex.html +34 -37
  67. package/web-ui/partials/index/panel-config-openclaw.html +10 -16
  68. package/web-ui/partials/index/panel-docs.html +147 -0
  69. package/web-ui/partials/index/panel-market.html +38 -38
  70. package/web-ui/partials/index/panel-orchestration.html +397 -0
  71. package/web-ui/partials/index/panel-plugins.html +243 -0
  72. package/web-ui/partials/index/panel-sessions.html +51 -146
  73. package/web-ui/partials/index/panel-settings.html +188 -96
  74. package/web-ui/partials/index/panel-usage.html +353 -0
  75. package/web-ui/session-helpers.mjs +221 -10
  76. package/web-ui/styles/base-theme.css +120 -229
  77. package/web-ui/styles/controls-forms.css +59 -51
  78. package/web-ui/styles/docs-panel.css +247 -0
  79. package/web-ui/styles/layout-shell.css +394 -128
  80. package/web-ui/styles/modals-core.css +18 -3
  81. package/web-ui/styles/navigation-panels.css +184 -183
  82. package/web-ui/styles/plugins-panel.css +518 -0
  83. package/web-ui/styles/responsive.css +102 -62
  84. package/web-ui/styles/sessions-list.css +13 -27
  85. package/web-ui/styles/sessions-preview.css +13 -7
  86. package/web-ui/styles/sessions-toolbar-trash.css +25 -0
  87. package/web-ui/styles/sessions-usage.css +581 -6
  88. package/web-ui/styles/settings-panel.css +166 -0
  89. package/web-ui/styles/skills-list.css +16 -11
  90. package/web-ui/styles/skills-market.css +63 -2
  91. package/web-ui/styles/task-orchestration.css +776 -0
  92. package/web-ui/styles/titles-cards.css +67 -66
  93. package/web-ui/styles.css +4 -0
  94. package/README.en.md +0 -259
  95. package/res/screenshot.png +0 -0
  96. package/res/vue.global.js +0 -18552
@@ -7,7 +7,7 @@
7
7
  :aria-labelledby="'tab-sessions'">
8
8
  <div v-if="sessionStandalone" class="session-standalone-page">
9
9
  <div v-if="sessionStandaloneLoading" class="state-message">
10
- 加载中...
10
+ {{ t('sessions.loading') }}
11
11
  </div>
12
12
  <div v-else-if="sessionStandaloneError" class="state-message error">
13
13
  {{ sessionStandaloneError }}
@@ -20,128 +20,30 @@
20
20
  <pre class="session-standalone-text">{{ sessionStandaloneText }}</pre>
21
21
  </div>
22
22
  </div>
23
-
24
23
  <div v-else>
25
- <div class="sessions-subtabs" role="tablist" aria-label="会话视图切换">
26
- <button
27
- class="sessions-subtab"
28
- :class="{ active: sessionsViewMode === 'browser' }"
29
- type="button"
30
- role="tab"
31
- :aria-selected="sessionsViewMode === 'browser'"
32
- @click="sessionsViewMode = 'browser'">
33
- Sessions
34
- </button>
35
- <button
36
- class="sessions-subtab"
37
- :class="{ active: sessionsViewMode === 'usage' }"
38
- type="button"
39
- role="tab"
40
- :aria-selected="sessionsViewMode === 'usage'"
41
- @click="sessionsViewMode = 'usage'">
42
- Usage
43
- </button>
44
- </div>
45
-
46
- <div v-if="sessionsViewMode === 'usage'">
47
- <div class="usage-toolbar">
48
- <div class="selector-header" style="padding:0;border:0;background:none;">
49
- <span class="selector-title">本地使用概览</span>
50
- </div>
51
- <div class="usage-range-group" role="group" aria-label="Usage 时间范围">
52
- <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '7d' }" @click="sessionsUsageTimeRange = '7d'">近 7 天</button>
53
- <button type="button" class="usage-range-btn" :class="{ active: sessionsUsageTimeRange === '30d' }" @click="sessionsUsageTimeRange = '30d'">近 30 天</button>
54
- </div>
55
- </div>
56
-
57
- <div v-if="!sessionsList.length" class="usage-empty">暂无可用于统计的会话数据</div>
58
- <template v-else>
59
- <div class="usage-summary-grid">
60
- <div v-for="card in sessionUsageSummaryCards" :key="card.key" class="usage-summary-card">
61
- <div class="usage-summary-label">{{ card.label }}</div>
62
- <div class="usage-summary-value">{{ card.value }}</div>
63
- </div>
64
- </div>
65
-
66
- <div class="usage-chart-grid">
67
- <section class="usage-card">
68
- <div class="usage-card-title">会话趋势</div>
69
- <div class="usage-legend">
70
- <span><span class="usage-legend-dot" style="background:#4f8cff"></span>Codex</span>
71
- <span><span class="usage-legend-dot" style="background:#b277ff"></span>Claude</span>
72
- </div>
73
- <div class="usage-bars">
74
- <div v-for="bucket in sessionUsageCharts.buckets" :key="bucket.key" class="usage-bar-group">
75
- <div class="usage-bar-stack">
76
- <div class="usage-bar codex" :style="{ height: ((bucket.codex / Math.max(sessionUsageCharts.maxSessionBucket, 1)) * 100) + '%' }" :title="`Codex ${bucket.codex}`"></div>
77
- <div class="usage-bar claude" :style="{ height: ((bucket.claude / Math.max(sessionUsageCharts.maxSessionBucket, 1)) * 100) + '%' }" :title="`Claude ${bucket.claude}`"></div>
78
- </div>
79
- <div class="usage-bar-label">{{ bucket.label }}</div>
80
- </div>
81
- </div>
82
- </section>
83
-
84
- <section class="usage-card">
85
- <div class="usage-card-title">来源占比</div>
86
- <div class="usage-list">
87
- <div v-for="item in sessionUsageCharts.sourceShare" :key="item.key" class="usage-list-row">
88
- <div class="usage-list-label">{{ item.label }}</div>
89
- <div class="usage-progress"><div class="usage-progress-fill" :style="{ width: item.percent + '%' }"></div></div>
90
- <div class="usage-list-value">{{ item.percent }}%</div>
91
- </div>
92
- </div>
93
- </section>
94
-
95
- <section class="usage-card">
96
- <div class="usage-card-title">消息趋势</div>
97
- <div class="usage-bars">
98
- <div v-for="bucket in sessionUsageCharts.buckets" :key="bucket.key + '-messages'" class="usage-bar-group">
99
- <div class="usage-bar-stack">
100
- <div class="usage-bar codex" style="width:100%" :style="{ height: ((bucket.totalMessages / Math.max(sessionUsageCharts.maxMessageBucket, 1)) * 100) + '%' }" :title="`${bucket.totalMessages} messages`"></div>
101
- </div>
102
- <div class="usage-bar-label">{{ bucket.label }}</div>
103
- </div>
104
- </div>
105
- </section>
106
-
107
- <section class="usage-card">
108
- <div class="usage-card-title">高频路径</div>
109
- <div v-if="!sessionUsageCharts.topPaths.length" class="usage-list-value">暂无路径数据</div>
110
- <div v-else class="usage-list">
111
- <div v-for="item in sessionUsageCharts.topPaths" :key="item.path" class="usage-list-row">
112
- <div class="usage-list-label">{{ item.count }} 次</div>
113
- <div class="usage-progress"><div class="usage-progress-fill" :style="{ width: ((item.count / Math.max(sessionUsageCharts.topPaths[0]?.count || 1, 1)) * 100) + '%' }"></div></div>
114
- <div class="usage-list-value" :title="item.path">{{ item.path }}</div>
115
- </div>
116
- </div>
117
- </section>
118
- </div>
119
- </template>
120
- </div>
121
-
122
- <template v-else>
123
24
  <div class="selector-section">
124
25
  <div class="selector-header">
125
- <span class="selector-title">会话来源</span>
26
+ <span class="selector-title">{{ t('sessions.sourceTitle') }}</span>
126
27
  <div class="selector-actions">
127
- <button class="btn-tool btn-tool-compact" @click="loadSessions" :disabled="sessionsLoading">
128
- {{ sessionsLoading ? '刷新中...' : '刷新会话' }}
28
+ <button class="btn-tool btn-tool-compact" @click="loadSessions({ forceRefresh: true })" :disabled="sessionsLoading">
29
+ {{ sessionsLoading ? t('sessions.refreshing') : t('sessions.refresh') }}
129
30
  </button>
130
31
  </div>
131
32
  </div>
132
33
  <div class="session-toolbar">
133
- <div class="session-toolbar-group">
34
+ <div class="session-toolbar-group session-toolbar-primary">
134
35
  <select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange" :disabled="sessionsLoading">
135
- <option value="all">全部</option>
136
- <option value="codex">Codex</option>
137
- <option value="claude">Claude Code</option>
36
+ <option value="all">{{ t('common.all') }}</option>
37
+ <option value="codex">{{ t('sessions.source.codex') }}</option>
38
+ <option value="claude">{{ t('sessions.source.claudeCode') }}</option>
138
39
  </select>
139
40
  <select
140
41
  class="session-path-select"
141
42
  v-model="sessionPathFilter"
142
43
  @change="onSessionPathFilterChange"
44
+ @focus="loadSessionPathOptions({ source: sessionFilterSource })"
143
45
  :disabled="sessionsLoading">
144
- <option value="">全部路径</option>
46
+ <option value="">{{ t('sessions.allPaths') }}</option>
145
47
  <option v-for="cwd in sessionPathOptions" :key="cwd" :value="cwd">{{ cwd }}</option>
146
48
  </select>
147
49
  </div>
@@ -153,26 +55,26 @@
153
55
  :disabled="sessionsLoading || !isSessionQueryEnabled"
154
56
  :placeholder="sessionQueryPlaceholder">
155
57
  </div>
156
- <div class="session-toolbar-group">
58
+ <div class="session-toolbar-group session-toolbar-secondary">
157
59
  <select
158
60
  class="session-role-select"
159
61
  v-model="sessionRoleFilter"
160
62
  @change="onSessionFilterChange"
161
63
  disabled>
162
- <option value="all">全部角色</option>
163
- <option value="user">仅 User</option>
164
- <option value="assistant">仅 Assistant</option>
165
- <option value="system">仅 System</option>
64
+ <option value="all">{{ t('sessions.role.all') }}</option>
65
+ <option value="user">{{ t('sessions.role.user') }}</option>
66
+ <option value="assistant">{{ t('sessions.role.assistant') }}</option>
67
+ <option value="system">{{ t('sessions.role.system') }}</option>
166
68
  </select>
167
69
  <select
168
70
  class="session-time-select"
169
71
  v-model="sessionTimePreset"
170
72
  @change="onSessionFilterChange"
171
73
  disabled>
172
- <option value="all">全部时间</option>
173
- <option value="7d">近 7 天</option>
174
- <option value="30d">近 30 天</option>
175
- <option value="90d">近 90 天</option>
74
+ <option value="all">{{ t('sessions.time.all') }}</option>
75
+ <option value="7d">{{ t('sessions.time.7d') }}</option>
76
+ <option value="30d">{{ t('sessions.time.30d') }}</option>
77
+ <option value="90d">{{ t('sessions.time.90d') }}</option>
176
78
  </select>
177
79
  </div>
178
80
  </div>
@@ -183,23 +85,27 @@
183
85
  v-model="sessionResumeWithYolo"
184
86
  @change="onSessionResumeYoloChange"
185
87
  >
186
- 复制恢复命令附带 --yolo
88
+ {{ t('sessions.resumeYolo') }}
187
89
  </label>
188
90
  </div>
189
91
  </div>
190
92
 
191
93
  <div v-if="sessionsLoading" class="state-message">
192
- 会话加载中...
94
+ {{ t('sessions.loadingList') }}
193
95
  </div>
194
96
 
195
97
  <div v-else-if="sessionsList.length === 0" class="session-empty">
196
- 暂无可用会话记录
98
+ {{ t('sessions.empty') }}
197
99
  </div>
198
100
 
199
101
  <div v-else class="session-layout">
200
- <div v-if="sessionListRenderEnabled" class="session-list">
102
+ <div
103
+ v-if="sessionListRenderEnabled"
104
+ class="session-list"
105
+ :ref="setSessionListRef"
106
+ @scroll.passive="onSessionListScroll">
201
107
  <div
202
- v-for="session in sortedSessionsList"
108
+ v-for="session in visibleSessionsList"
203
109
  :key="session.source + '-' + session.sessionId + '-' + session.filePath"
204
110
  v-memo="[activeSessionExportKey === getSessionExportKey(session), session.messageCount, session.updatedAt, session.title, session.sourceLabel, isSessionPinned(session), sessionsLoading]"
205
111
  :class="[
@@ -218,15 +124,15 @@
218
124
  <div class="session-item-header">
219
125
  <div class="session-item-main">
220
126
  <div class="session-item-title">{{ session.title || session.sessionId }}</div>
221
- <span class="session-count-badge">{{ session.messageCount ?? 0 }}</span>
127
+ <span class="session-count-badge">{{ session.messageCount == null ? 0 : session.messageCount }}</span>
222
128
  </div>
223
129
  <div class="session-item-actions">
224
130
  <button
225
131
  class="session-item-copy session-item-pin"
226
132
  @click.stop="toggleSessionPin(session)"
227
133
  :disabled="sessionsLoading"
228
- :aria-label="isSessionPinned(session) ? '取消置顶' : '置顶'"
229
- :title="isSessionPinned(session) ? '取消置顶' : '置顶'"
134
+ :aria-label="isSessionPinned(session) ? t('sessions.unpin') : t('sessions.pin')"
135
+ :title="isSessionPinned(session) ? t('sessions.unpin') : t('sessions.pin')"
230
136
  :aria-pressed="isSessionPinned(session)">
231
137
  <svg v-if="isSessionPinned(session)" class="pin-icon" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.6">
232
138
  <path d="M12 22s8-6 8-12a8 8 0 1 0-16 0c0 6 8 12 8 12z"></path>
@@ -240,8 +146,8 @@
240
146
  class="session-item-copy"
241
147
  @click.stop="copyResumeCommand(session)"
242
148
  :disabled="sessionsLoading"
243
- aria-label="复制恢复命令"
244
- title="复制恢复命令">
149
+ :aria-label="t('sessions.copyResume')"
150
+ :title="t('sessions.copyResume')">
245
151
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
246
152
  <rect x="8" y="8" width="12" height="12" rx="2"></rect>
247
153
  <path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"></path>
@@ -251,7 +157,7 @@
251
157
  </div>
252
158
  <div class="session-item-meta">
253
159
  <span class="session-source">{{ session.sourceLabel }}</span>
254
- <span class="session-item-time">{{ session.updatedAt || 'unknown time' }}</span>
160
+ <span class="session-item-time">{{ session.updatedAt || t('sessions.unknownTime') }}</span>
255
161
  </div>
256
162
  </div>
257
163
  </div>
@@ -265,7 +171,7 @@
265
171
  <div class="session-preview-title">{{ activeSession.title || activeSession.sessionId }}</div>
266
172
  <div class="session-preview-meta">
267
173
  <span class="session-preview-meta-item">{{ activeSession.sourceLabel }}</span>
268
- <span class="session-preview-meta-item">{{ activeSession.updatedAt || 'unknown time' }}</span>
174
+ <span class="session-preview-meta-item">{{ activeSession.updatedAt || t('sessions.unknownTime') }}</span>
269
175
  </div>
270
176
  <div class="session-preview-meta" v-if="activeSession.cwd">
271
177
  <span class="session-preview-meta-item">{{ activeSession.cwd }}</span>
@@ -273,32 +179,32 @@
273
179
  </div>
274
180
  <div class="session-actions">
275
181
  <button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession">
276
- {{ sessionDetailLoading ? '加载中...' : '刷新内容' }}
182
+ {{ sessionDetailLoading ? t('sessions.preview.loading') : t('sessions.preview.refresh') }}
277
183
  </button>
278
184
  <button
279
185
  v-if="isDeleteAvailable(activeSession)"
280
186
  class="btn-session-delete"
281
187
  @click="deleteSession(activeSession)"
282
188
  :disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]">
283
- {{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '移入中...' : '移入回收站' }}
189
+ {{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? (sessionTrashEnabled === false ? t('sessions.preview.deleting') : t('sessions.preview.moving')) : (sessionTrashEnabled === false ? t('sessions.preview.deleteHard') : t('sessions.preview.moveToTrash')) }}
284
190
  </button>
285
191
  <button
286
192
  class="btn-session-export"
287
193
  @click="exportSession(activeSession)"
288
194
  :disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]">
289
- {{ (activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? '导出中...' : '导出记录' }}
195
+ {{ (activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? t('sessions.preview.exporting') : t('sessions.preview.export') }}
290
196
  </button>
291
197
  <button
292
198
  class="btn-session-open"
293
199
  @click="openSessionStandalone(activeSession)"
294
200
  :disabled="!activeSession">
295
- 新页查看
201
+ {{ t('sessions.preview.openStandalone') }}
296
202
  </button>
297
203
  </div>
298
204
  </div>
299
205
 
300
206
  <div v-if="sessionDetailLoading && !sessionPreviewLoadingMore" class="session-preview-empty">
301
- 正在加载会话内容...
207
+ {{ t('sessions.preview.loadingBody') }}
302
208
  </div>
303
209
 
304
210
  <div v-else-if="activeSessionDetailError" class="session-preview-empty">
@@ -306,38 +212,38 @@
306
212
  </div>
307
213
 
308
214
  <div v-else-if="!activeSessionMessages.length" class="session-preview-empty">
309
- 当前会话暂无可展示消息
215
+ {{ t('sessions.preview.emptyMsgs') }}
310
216
  </div>
311
217
 
312
218
  <div v-else-if="sessionPreviewRenderEnabled && !activeSessionVisibleMessages.length" class="session-preview-empty">
313
- <span>正在渲染会话内容...</span>
219
+ <span>{{ t('sessions.preview.rendering') }}</span>
314
220
  <button class="btn-session-refresh" @click="primeSessionPreviewMessageRender" :disabled="sessionDetailLoading">
315
- 重新渲染
221
+ {{ t('sessions.preview.rerender') }}
316
222
  </button>
317
223
  </div>
318
224
 
319
225
  <div v-else-if="!sessionPreviewRenderEnabled" class="session-preview-empty">
320
- 正在准备会话内容...
226
+ {{ t('sessions.preview.preparing') }}
321
227
  </div>
322
228
 
323
229
  <div v-else class="session-preview-body">
324
230
  <div class="session-preview-messages">
325
231
  <div v-if="activeSessionDetailClipped" class="session-item-sub session-item-wrap">
326
- 仅展示最近 {{ activeSessionMessages.length }} 条消息。
232
+ {{ t('sessions.preview.clipped', { count: activeSessionMessages.length }) }}
327
233
  </div>
328
234
  <div
329
235
  v-if="canLoadMoreSessionMessages"
330
236
  class="session-item-sub session-item-wrap"
331
237
  style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
332
- <span>已显示 {{ activeSessionVisibleMessages.length }} / {{ activeSessionMessages.length }} 条</span>
238
+ <span>{{ t('sessions.preview.shownCount', { shown: activeSessionVisibleMessages.length, total: activeSessionMessages.length }) }}</span>
333
239
  <button class="btn-session-refresh" @click="loadMoreSessionMessages()" :disabled="sessionDetailLoading || sessionPreviewLoadingMore">
334
- {{ sessionPreviewLoadingMore ? '加载中...' : ('加载更多(剩余 ' + sessionPreviewRemainingCount + ')') }}
240
+ {{ sessionPreviewLoadingMore ? t('sessions.preview.loading') : t('sessions.preview.loadMore', { remain: sessionPreviewRemainingCount }) }}
335
241
  </button>
336
242
  </div>
337
243
  <div
338
244
  v-if="sessionPreviewLoadingMore"
339
245
  class="session-item-sub session-item-wrap">
340
- 正在加载更早消息...
246
+ {{ t('sessions.preview.loadingMore') }}
341
247
  </div>
342
248
  <div
343
249
  v-for="(msg, idx) in activeSessionVisibleMessages"
@@ -348,7 +254,7 @@
348
254
  :class="['session-msg', msg.normalizedRole === 'user' ? 'user' : (msg.normalizedRole === 'system' ? 'system' : 'assistant')]">
349
255
  <div class="session-msg-header">
350
256
  <div class="session-msg-meta">
351
- <span class="session-msg-role">{{ msg.roleLabel || (msg.normalizedRole === 'user' ? 'User' : (msg.normalizedRole === 'system' ? 'System' : 'Assistant')) }}</span>
257
+ <span class="session-msg-role">{{ msg.roleLabel || (msg.normalizedRole === 'user' ? t('sessions.roleLabel.user') : (msg.normalizedRole === 'system' ? t('sessions.roleLabel.system') : t('sessions.roleLabel.assistant'))) }}</span>
352
258
  <span class="session-msg-time">{{ msg.timestamp || '' }}</span>
353
259
  </div>
354
260
  </div>
@@ -357,7 +263,7 @@
357
263
  </div>
358
264
  </div>
359
265
  </div>
360
- <aside v-if="sessionPreviewRenderEnabled && sessionTimelineNodes.length" class="session-timeline" aria-label="会话时间轴">
266
+ <aside v-if="sessionPreviewRenderEnabled && sessionTimelineNodes.length" class="session-timeline" :aria-label="t('sessions.timeline.aria')">
361
267
  <div class="session-timeline-track"></div>
362
268
  <button
363
269
  v-for="node in sessionTimelineNodes"
@@ -378,10 +284,9 @@
378
284
  </template>
379
285
 
380
286
  <div v-else class="session-preview-empty">
381
- <span>请先在左侧选择一个会话</span>
287
+ <span>{{ t('sessions.selectHint') }}</span>
382
288
  </div>
383
289
  </div>
384
290
  </div>
385
- </template>
386
291
  </div>
387
292
  </div>