codexmate 0.0.27 → 0.0.29

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 (51) hide show
  1. package/README.md +1 -1
  2. package/README.zh.md +1 -1
  3. package/cli/builtin-proxy.js +430 -4
  4. package/cli/openai-bridge.js +498 -13
  5. package/cli.js +130 -41
  6. package/lib/cli-models-utils.js +71 -10
  7. package/lib/cli-webhook.js +126 -0
  8. package/package.json +76 -74
  9. package/plugins/prompt-templates/computed.mjs +1 -1
  10. package/plugins/prompt-templates/methods.mjs +0 -66
  11. package/plugins/prompt-templates/overview.mjs +1 -0
  12. package/web-ui/app.js +21 -16
  13. package/web-ui/index.html +1 -0
  14. package/web-ui/logic.codex.mjs +69 -0
  15. package/web-ui/modules/app.computed.dashboard.mjs +54 -0
  16. package/web-ui/modules/app.computed.session.mjs +22 -17
  17. package/web-ui/modules/app.methods.claude-config.mjs +24 -8
  18. package/web-ui/modules/app.methods.codex-config.mjs +35 -3
  19. package/web-ui/modules/app.methods.index.mjs +2 -0
  20. package/web-ui/modules/app.methods.navigation.mjs +21 -3
  21. package/web-ui/modules/app.methods.providers.mjs +96 -7
  22. package/web-ui/modules/app.methods.session-actions.mjs +3 -6
  23. package/web-ui/modules/app.methods.session-browser.mjs +1 -6
  24. package/web-ui/modules/app.methods.session-trash.mjs +6 -7
  25. package/web-ui/modules/app.methods.startup-claude.mjs +8 -1
  26. package/web-ui/modules/app.methods.webhook.mjs +79 -0
  27. package/web-ui/modules/i18n.dict.mjs +1104 -104
  28. package/web-ui/modules/i18n.mjs +9 -3
  29. package/web-ui/modules/provider-url-display.mjs +17 -0
  30. package/web-ui/partials/index/layout-header.html +25 -0
  31. package/web-ui/partials/index/modals-basic.html +0 -3
  32. package/web-ui/partials/index/panel-config-claude.html +10 -3
  33. package/web-ui/partials/index/panel-config-codex.html +44 -4
  34. package/web-ui/partials/index/panel-plugins.html +3 -29
  35. package/web-ui/partials/index/panel-sessions.html +0 -10
  36. package/web-ui/partials/index/panel-settings.html +93 -177
  37. package/web-ui/partials/index/panel-trash.html +88 -0
  38. package/web-ui/session-helpers.mjs +2 -2
  39. package/web-ui/styles/base-theme.css +47 -34
  40. package/web-ui/styles/controls-forms.css +27 -28
  41. package/web-ui/styles/docs-panel.css +63 -39
  42. package/web-ui/styles/layout-shell.css +69 -46
  43. package/web-ui/styles/modals-core.css +12 -10
  44. package/web-ui/styles/navigation-panels.css +36 -35
  45. package/web-ui/styles/responsive.css +4 -4
  46. package/web-ui/styles/sessions-list.css +10 -6
  47. package/web-ui/styles/settings-panel.css +197 -33
  48. package/web-ui/styles/titles-cards.css +90 -26
  49. package/web-ui/styles/trash-panel.css +90 -0
  50. package/web-ui/styles/webhook.css +81 -0
  51. package/web-ui/styles.css +2 -0
@@ -1,49 +1,38 @@
1
- <!-- 设置面板 -->
1
+  <!-- 设置面板 -->
2
2
  <div
3
3
  v-show="mainTab === 'settings'"
4
4
  class="mode-content"
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="t('settings.tabs.aria')">
8
+ <div class="settings-subtabs-bar" role="tablist" :aria-label="t('settings.tabs.aria')">
9
9
  <button
10
- id="settings-tab-backup"
10
+ id="settings-tab-general"
11
11
  role="tab"
12
- aria-controls="settings-panel-backup"
13
- :aria-selected="settingsTab === 'backup'"
14
- :tabindex="settingsTab === 'backup' ? 0 : -1"
15
- :class="['config-subtab', { active: settingsTab === 'backup' }]"
16
- @click="onSettingsTabClick('backup')">
17
- {{ t('settings.tab.backup') }}
12
+ aria-controls="settings-panel-general"
13
+ :aria-selected="settingsTab === 'general'"
14
+ :tabindex="settingsTab === 'general' ? 0 : -1"
15
+ :class="['settings-subtab', { active: settingsTab === 'general' }]"
16
+ @click="onSettingsTabClick('general')">
17
+ {{ t('settings.tab.general') }}
18
18
  </button>
19
19
  <button
20
- id="settings-tab-trash"
20
+ id="settings-tab-data"
21
21
  role="tab"
22
- aria-controls="settings-panel-trash"
23
- :aria-selected="settingsTab === 'trash'"
24
- :tabindex="settingsTab === 'trash' ? 0 : -1"
25
- :class="['config-subtab', { active: settingsTab === 'trash' }]"
26
- @click="onSettingsTabClick('trash')">
27
- {{ t('settings.tab.trash') }}
28
- <span class="settings-tab-badge">{{ sessionTrashCount }}</span>
29
- </button>
30
- <button
31
- id="settings-tab-device"
32
- role="tab"
33
- aria-controls="settings-panel-device"
34
- :aria-selected="settingsTab === 'device'"
35
- :tabindex="settingsTab === 'device' ? 0 : -1"
36
- :class="['config-subtab', { active: settingsTab === 'device' }]"
37
- @click="onSettingsTabClick('device')">
38
- {{ t('settings.tab.device') }}
22
+ aria-controls="settings-panel-data"
23
+ :aria-selected="settingsTab === 'data'"
24
+ :tabindex="settingsTab === 'data' ? 0 : -1"
25
+ :class="['settings-subtab', { active: settingsTab === 'data' }]"
26
+ @click="onSettingsTabClick('data')">
27
+ {{ t('settings.tab.data') }}
39
28
  </button>
40
29
  </div>
41
30
 
42
31
  <div
43
- v-show="settingsTab === 'backup'"
44
- id="settings-panel-backup"
32
+ v-show="settingsTab === 'general'"
33
+ id="settings-panel-general"
45
34
  role="tabpanel"
46
- aria-labelledby="settings-tab-backup">
35
+ aria-labelledby="settings-tab-general">
47
36
  <div class="settings-layout">
48
37
  <div class="settings-grid">
49
38
  <section class="settings-card settings-card--wide" :aria-label="t('settings.sharePrefix.title')">
@@ -69,49 +58,45 @@
69
58
  </div>
70
59
  </section>
71
60
 
72
- <section class="settings-card" :aria-label="t('settings.claude.title')">
61
+ <section class="settings-card settings-card--wide" :aria-label="t('settings.templateConfirm.title')">
73
62
  <div class="settings-card-header">
74
- <div class="settings-card-title">{{ t('settings.claude.title') }}</div>
75
- <div class="settings-card-meta">{{ t('settings.claude.meta') }}</div>
63
+ <div class="settings-card-title">{{ t('settings.templateConfirm.title') }}</div>
64
+ <div class="settings-card-meta">{{ t('settings.templateConfirm.meta') }}</div>
76
65
  </div>
77
66
  <div class="settings-card-body">
78
- <div class="settings-actions">
79
- <button class="btn-tool" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
80
- {{ claudeDownloadLoading ? t('settings.backup.progress', { percent: claudeDownloadProgress }) : t('settings.backup.oneClickClaude') }}
81
- </button>
82
- <button class="btn-tool" @click="triggerClaudeImport" :disabled="claudeImportLoading">
83
- {{ claudeImportLoading ? t('settings.importing') : t('settings.backup.importClaude') }}
84
- </button>
67
+ <label class="health-remote-toggle settings-toggle">
68
+ <input
69
+ type="checkbox"
70
+ :checked="configTemplateDiffConfirmEnabled"
71
+ @change="setConfigTemplateDiffConfirmEnabled($event.target.checked)">
72
+ <span>{{ t('settings.templateConfirm.toggle') }}</span>
73
+ </label>
74
+ <div class="settings-card-hint">
75
+ {{ t('settings.templateConfirm.hint') }}
85
76
  </div>
86
- <input
87
- ref="claudeImportInput"
88
- class="sr-only"
89
- type="file"
90
- accept=".zip"
91
- @change="handleClaudeImportChange">
92
77
  </div>
93
78
  </section>
94
79
 
95
- <section class="settings-card" :aria-label="t('settings.codex.title')">
96
- <div class="settings-card-header">
97
- <div class="settings-card-title">{{ t('settings.codex.title') }}</div>
98
- <div class="settings-card-meta">{{ t('settings.codex.meta') }}</div>
80
+ <section class="settings-card settings-card--wide" :aria-label="t('settings.webhook.title')">
81
+ <div class="settings-card-header settings-card-header-row">
82
+ <div>
83
+ <div class="settings-card-title">Webhook</div>
84
+ <div class="settings-card-meta">配置变更外发通知</div>
85
+ </div>
86
+ <span :class="webhookConfig.enabled ? 'status-on' : 'status-off'">{{ webhookConfig.enabled ? '● 已启用' : '○ 已禁用' }}</span>
99
87
  </div>
100
88
  <div class="settings-card-body">
101
- <div class="settings-actions">
102
- <button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
103
- {{ codexDownloadLoading ? t('settings.backup.progress', { percent: codexDownloadProgress }) : t('settings.backup.oneClickCodex') }}
104
- </button>
105
- <button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
106
- {{ codexImportLoading ? t('settings.importing') : t('settings.backup.importCodex') }}
107
- </button>
89
+ <div v-if="webhookConfig.url" class="webhook-readonly-row">
90
+ <span class="webhook-readonly-label">URL</span>
91
+ <code class="webhook-readonly-value">{{ webhookConfig.url }}</code>
92
+ </div>
93
+ <div class="webhook-readonly-row">
94
+ <span class="webhook-readonly-label">事件</span>
95
+ <div class="webhook-readonly-tags">
96
+ <span v-for="ev in webhookConfig.events" :key="ev" class="webhook-event-tag">{{ ev }}</span>
97
+ <span v-if="!webhookConfig.events.length" class="settings-card-hint">无</span>
98
+ </div>
108
99
  </div>
109
- <input
110
- ref="codexImportInput"
111
- class="sr-only"
112
- type="file"
113
- accept=".zip"
114
- @change="handleCodexImportChange">
115
100
  </div>
116
101
  </section>
117
102
  </div>
@@ -119,141 +104,72 @@
119
104
  </div>
120
105
 
121
106
  <div
122
- v-show="settingsTab === 'trash'"
123
- id="settings-panel-trash"
107
+ v-show="settingsTab === 'data'"
108
+ id="settings-panel-data"
124
109
  role="tabpanel"
125
- aria-labelledby="settings-tab-trash">
110
+ aria-labelledby="settings-tab-data">
126
111
  <div class="settings-layout">
127
112
  <div class="settings-grid">
128
- <section class="settings-card settings-card--wide" :aria-label="t('settings.deleteBehavior.title')">
129
- <div class="settings-card-header">
130
- <div class="settings-card-title">{{ t('settings.deleteBehavior.title') }}</div>
131
- <div class="settings-card-meta">{{ t('settings.deleteBehavior.meta') }}</div>
132
- </div>
133
- <div class="settings-card-body">
134
- <label class="health-remote-toggle settings-toggle">
135
- <input type="checkbox" :checked="sessionTrashEnabled" @change="setSessionTrashEnabled($event.target.checked)">
136
- <span>{{ t('settings.deleteBehavior.toggle') }}</span>
137
- </label>
138
- <div class="settings-card-hint">
139
- {{ t('settings.deleteBehavior.hint') }}
140
- </div>
141
- </div>
142
- </section>
143
-
144
- <section class="settings-card settings-card--wide" :aria-label="t('settings.trash.retention')">
145
- <div class="settings-card-header">
146
- <div class="settings-card-title">{{ t('settings.trash.retention') }}</div>
147
- <div class="settings-card-meta">{{ t('settings.trash.retentionMeta') }}</div>
148
- </div>
149
- <div class="settings-card-body">
150
- <label class="settings-retention-row">
151
- <span>{{ t('settings.trash.retentionLabel') }}</span>
152
- <input type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
153
- </label>
154
- <div class="settings-card-hint">
155
- {{ t('settings.trash.retentionHint') }}
156
- </div>
157
- </div>
158
- </section>
159
-
160
- <section class="settings-card settings-card--wide" :aria-label="t('settings.trash.title')">
113
+ <section class="settings-card settings-card--wide" :aria-label="t('settings.backup.title')">
161
114
  <div class="settings-card-header settings-card-header-row">
162
115
  <div>
163
- <div class="settings-card-title">{{ t('settings.trash.title') }}</div>
164
- <div class="settings-card-meta">{{ t('settings.trash.meta') }}</div>
165
- </div>
166
- <div class="settings-card-actions">
167
- <button class="btn-tool btn-tool-compact" @click="loadSessionTrash({ forceRefresh: true })" :disabled="sessionTrashLoading || sessionTrashClearing">
168
- {{ sessionTrashLoading ? t('settings.trash.refreshing') : t('settings.trash.refresh') }}
169
- </button>
170
- <button class="btn-tool btn-tool-compact" @click="clearSessionTrash" :disabled="sessionTrashClearing || sessionTrashLoading || !(Number(sessionTrashCount) > 0)">
171
- {{ sessionTrashClearing ? t('settings.trash.clearing') : t('settings.trash.clear') }}
172
- </button>
116
+ <div class="settings-card-title">{{ t('settings.backup.title') }}</div>
117
+ <div class="settings-card-meta">{{ t('settings.backup.meta') }}</div>
173
118
  </div>
174
119
  </div>
175
-
176
120
  <div class="settings-card-body">
177
- <div v-if="getSessionTrashViewState() === 'loading'" class="session-empty">
178
- {{ t('settings.trash.loading') }}
179
- </div>
180
- <div v-else-if="getSessionTrashViewState() === 'empty'" class="session-empty">
181
- {{ t('settings.trash.empty') }}
182
- </div>
183
- <div v-else-if="getSessionTrashViewState() === 'retry'" class="session-empty">
184
- {{ t('settings.trash.retry') }}
185
- </div>
186
- <div v-else class="trash-list">
187
- <div v-for="item in visibleSessionTrashItems" :key="item.trashId" class="trash-item session-item session-card">
188
- <div class="trash-item-header session-item-header">
189
- <div class="trash-item-main">
190
- <div class="trash-item-mainline">
191
- <div class="trash-item-title">{{ item.title || item.sessionId }}</div>
192
- <span class="session-count-badge">{{ item.messageCount == null ? 0 : item.messageCount }}</span>
193
- </div>
194
- <div class="trash-item-meta session-item-meta">
195
- <span class="session-source">{{ item.sourceLabel }}</span>
196
- </div>
197
- </div>
198
- <div class="trash-item-side">
199
- <div class="trash-item-actions session-item-actions">
200
- <button class="btn-mini" @click="restoreSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
201
- {{ sessionTrashRestoring[getSessionTrashActionKey(item)] ? t('settings.trash.restoring') : t('settings.trash.restore') }}
202
- </button>
203
- <button class="btn-mini delete" @click="purgeSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
204
- {{ sessionTrashPurging[getSessionTrashActionKey(item)] ? t('settings.trash.purging') : t('settings.trash.purge') }}
205
- </button>
206
- </div>
207
- <div class="trash-item-time session-item-time">{{ item.deletedAt || item.updatedAt || t('sessions.unknownTime') }}</div>
208
- </div>
209
- </div>
210
- <div v-if="item.cwd" class="trash-item-path session-item-sub session-item-wrap">
211
- <span class="trash-item-label">{{ t('settings.trash.workspace') }}</span>
212
- <span>{{ item.cwd }}</span>
213
- </div>
214
- <div class="trash-item-path session-item-sub session-item-wrap">
215
- <span class="trash-item-label">{{ t('settings.trash.originalFile') }}</span>
216
- <span>{{ item.originalFilePath }}</span>
121
+ <div class="settings-backup-grid">
122
+ <div class="settings-backup-slot">
123
+ <div class="settings-backup-slot-label">Claude</div>
124
+ <div class="settings-actions">
125
+ <button class="btn-tool" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
126
+ {{ claudeDownloadLoading ? t('settings.backup.progress', { percent: claudeDownloadProgress }) : t('settings.backup.oneClickClaude') }}
127
+ </button>
128
+ <button class="btn-tool" @click="triggerClaudeImport" :disabled="claudeImportLoading">
129
+ {{ claudeImportLoading ? t('settings.importing') : t('settings.backup.importClaude') }}
130
+ </button>
217
131
  </div>
132
+ <input ref="claudeImportInput" class="sr-only" type="file" accept=".zip" @change="handleClaudeImportChange">
218
133
  </div>
219
- <div v-if="sessionTrashHasMoreItems" class="trash-list-footer">
220
- <button class="btn-tool btn-tool-compact" @click="loadMoreSessionTrashItems" :disabled="sessionTrashLoading || sessionTrashClearing">
221
- {{ t('settings.trash.loadMore', { count: sessionTrashHiddenCount }) }}
222
- </button>
134
+ <div class="settings-backup-slot">
135
+ <div class="settings-backup-slot-label">Codex</div>
136
+ <div class="settings-actions">
137
+ <button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
138
+ {{ codexDownloadLoading ? t('settings.backup.progress', { percent: codexDownloadProgress }) : t('settings.backup.oneClickCodex') }}
139
+ </button>
140
+ <button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
141
+ {{ codexImportLoading ? t('settings.importing') : t('settings.backup.importCodex') }}
142
+ </button>
143
+ </div>
144
+ <input ref="codexImportInput" class="sr-only" type="file" accept=".zip" @change="handleCodexImportChange">
223
145
  </div>
224
146
  </div>
225
147
  </div>
226
148
  </section>
227
- </div>
228
- </div>
229
- </div>
230
149
 
231
- <div
232
- v-show="settingsTab === 'device'"
233
- id="settings-panel-device"
234
- role="tabpanel"
235
- aria-labelledby="settings-tab-device">
236
- <div class="settings-layout">
237
- <div class="settings-grid">
238
- <section class="settings-card settings-card--wide" :aria-label="t('settings.templateConfirm.title')">
239
- <div class="settings-card-header">
240
- <div class="settings-card-title">{{ t('settings.templateConfirm.title') }}</div>
241
- <div class="settings-card-meta">{{ t('settings.templateConfirm.meta') }}</div>
150
+ <section class="settings-card settings-card--wide" :aria-label="t('settings.trashConfig.title')">
151
+ <div class="settings-card-header settings-card-header-row">
152
+ <div>
153
+ <div class="settings-card-title">{{ t('settings.trashConfig.title') }}</div>
154
+ <div class="settings-card-meta">{{ t('settings.trashConfig.meta') }}</div>
155
+ </div>
242
156
  </div>
243
157
  <div class="settings-card-body">
244
- <label class="health-remote-toggle settings-toggle">
245
- <input
246
- type="checkbox"
247
- :checked="configTemplateDiffConfirmEnabled"
248
- @change="setConfigTemplateDiffConfirmEnabled($event.target.checked)">
249
- <span>{{ t('settings.templateConfirm.toggle') }}</span>
250
- </label>
251
- <div class="settings-card-hint">
252
- {{ t('settings.templateConfirm.hint') }}
158
+ <div class="settings-trash-config-grid">
159
+ <label class="settings-toggle">
160
+ <input type="checkbox" :checked="sessionTrashEnabled" @change="setSessionTrashEnabled($event.target.checked)">
161
+ <span>{{ t('settings.deleteBehavior.toggle') }}</span>
162
+ </label>
163
+ <label class="settings-retention-row">
164
+ <span>{{ t('settings.trash.retentionLabel') }}</span>
165
+ <input type="number" min="1" max="365" :value="sessionTrashRetentionDays" @change="setSessionTrashRetentionDays(Number($event.target.value))" class="settings-retention-input" />
166
+ </label>
253
167
  </div>
168
+ <div class="settings-card-hint">{{ t('settings.trash.retentionHint') }}</div>
254
169
  </div>
255
170
  </section>
256
171
 
172
+
257
173
  <section class="settings-card settings-card--wide settings-card--danger" :aria-label="t('settings.reset.title')">
258
174
  <div class="settings-card-header">
259
175
  <div class="settings-card-title">{{ t('settings.reset.title') }}</div>
@@ -0,0 +1,88 @@
1
+  <!-- 回收站面板 — 极简·本质 -->
2
+ <div
3
+ v-show="mainTab === 'trash'"
4
+ class="mode-content"
5
+ id="panel-trash"
6
+ role="tabpanel"
7
+ aria-labelledby="tab-trash">
8
+ <div v-if="!loading" class="trash-panel-shell">
9
+ <!-- 空态 — 宁静的留白 -->
10
+ <div v-if="getSessionTrashViewState() === 'empty'" class="trash-empty-state">
11
+ <div class="trash-empty-icon">🗑</div>
12
+ <div class="trash-empty-title">{{ t('settings.trash.empty') }}</div>
13
+ <div class="trash-empty-hint">删除的会话将出现在这里,保留 {{ sessionTrashRetentionDays }} 天后自动清理</div>
14
+ </div>
15
+
16
+ <!-- 加载态 -->
17
+ <div v-else-if="getSessionTrashViewState() === 'loading'" class="trash-empty-state">
18
+ <div class="trash-empty-icon">⟳</div>
19
+ <div class="trash-empty-title">{{ t('settings.trash.loading') }}</div>
20
+ </div>
21
+
22
+ <!-- 错误态 -->
23
+ <div v-else-if="getSessionTrashViewState() === 'retry'" class="trash-empty-state">
24
+ <div class="trash-empty-icon">⚠</div>
25
+ <div class="trash-empty-title">{{ t('settings.trash.retry') }}</div>
26
+ <button class="btn-tool" @click="loadSessionTrash({ forceRefresh: true })">重试</button>
27
+ </div>
28
+
29
+ <!-- 列表态 -->
30
+ <template v-else>
31
+ <div class="trash-toolbar">
32
+ <div class="trash-toolbar-left">
33
+ <span class="trash-toolbar-count">{{ sessionTrashCount }} 个已删除会话</span>
34
+ <span class="trash-toolbar-retention">· {{ sessionTrashRetentionDays }} 天后自动清理</span>
35
+ </div>
36
+ <div class="trash-toolbar-right">
37
+ <button class="btn-mini" @click="loadSessionTrash({ forceRefresh: true })" :disabled="sessionTrashLoading">
38
+ {{ sessionTrashLoading ? '刷新中…' : '刷新' }}
39
+ </button>
40
+ <button class="btn-mini delete" @click="clearSessionTrash" :disabled="sessionTrashClearing || sessionTrashLoading || !(Number(sessionTrashCount) > 0)">
41
+ {{ sessionTrashClearing ? '清空中…' : '清空回收站' }}
42
+ </button>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="trash-list">
47
+ <div v-for="item in visibleSessionTrashItems" :key="item.trashId" class="trash-item session-item session-card">
48
+ <div class="trash-item-header session-item-header">
49
+ <div class="trash-item-main">
50
+ <div class="trash-item-mainline">
51
+ <div class="trash-item-title">{{ item.title || item.sessionId }}</div>
52
+ <span class="session-count-badge">{{ item.messageCount == null ? 0 : item.messageCount }}</span>
53
+ </div>
54
+ <div class="trash-item-meta session-item-meta">
55
+ <span class="session-source">{{ item.sourceLabel }}</span>
56
+ <span class="trash-item-dot">·</span>
57
+ <span class="trash-item-time">{{ item.deletedAt || item.updatedAt || t('sessions.unknownTime') }}</span>
58
+ </div>
59
+ </div>
60
+ <div class="trash-item-side">
61
+ <div class="trash-item-actions session-item-actions">
62
+ <button class="btn-mini" @click="restoreSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
63
+ {{ sessionTrashRestoring[getSessionTrashActionKey(item)] ? '恢复中…' : '恢复' }}
64
+ </button>
65
+ <button class="btn-mini delete" @click="purgeSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
66
+ {{ sessionTrashPurging[getSessionTrashActionKey(item)] ? '删除中…' : '彻底删除' }}
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ <div v-if="item.cwd" class="trash-item-path session-item-sub session-item-wrap">
72
+ <span class="trash-item-label">{{ t('settings.trash.workspace') }}</span>
73
+ <span>{{ item.cwd }}</span>
74
+ </div>
75
+ <div class="trash-item-path session-item-sub session-item-wrap">
76
+ <span class="trash-item-label">{{ t('settings.trash.originalFile') }}</span>
77
+ <span>{{ item.originalFilePath }}</span>
78
+ </div>
79
+ </div>
80
+ <div v-if="sessionTrashHasMoreItems" class="trash-list-footer">
81
+ <button class="btn-tool btn-tool-compact" @click="loadMoreSessionTrashItems" :disabled="sessionTrashLoading || sessionTrashClearing">
82
+ 加载更多(还有 {{ sessionTrashHiddenCount }} 条)
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </template>
87
+ </div>
88
+ </div>
@@ -160,7 +160,7 @@ export function switchMainTab(tab) {
160
160
  this.prepareSessionTabRender();
161
161
  }
162
162
  const shouldLoadTrashListOnSettingsEnter = nextTab === 'settings'
163
- && this.settingsTab === 'trash'
163
+ && this.settingsTab === 'data'
164
164
  && typeof this.loadSessionTrash === 'function';
165
165
  if (shouldLoadTrashListOnSettingsEnter) {
166
166
  this.loadSessionTrash({
@@ -168,7 +168,7 @@ export function switchMainTab(tab) {
168
168
  });
169
169
  }
170
170
  const shouldPrimeTrashCountOnSettingsEnter = nextTab === 'settings'
171
- && this.settingsTab !== 'trash'
171
+ && this.settingsTab !== 'data'
172
172
  && typeof this.loadSessionTrashCount === 'function';
173
173
  if (shouldPrimeTrashCountOnSettingsEnter) {
174
174
  this.sessionTrashLoadedOnce = false;
@@ -4,34 +4,36 @@
4
4
  设计系统 - Design Tokens
5
5
  ============================================ */
6
6
  :root {
7
- /* 色彩系统:中性灰(更贴近 craft/shadcn 观感)+ 品牌强调 */
8
- --color-brand: #C77462;
9
- --color-brand-dark: #B45E4E;
10
- --color-brand-light: rgba(199, 116, 98, 0.12);
11
- --color-brand-subtle: rgba(199, 116, 98, 0.18);
12
-
13
- --color-bg: #FAFAFA;
14
- --color-surface: #FFFFFF;
15
- --color-surface-alt: #F4F4F5;
7
+ /* 色彩系统:低饱和暖色 + 桌面助理式柔和层级 */
8
+ --color-brand: #C87963;
9
+ --color-brand-dark: #A95845;
10
+ --color-brand-light: rgba(200, 121, 99, 0.13);
11
+ --color-brand-subtle: rgba(200, 121, 99, 0.2);
12
+
13
+ --color-bg: #F7F0E9;
14
+ --color-surface: #FFFDFC;
15
+ --color-surface-alt: #F7EFE8;
16
16
  --color-surface-elevated: #FFFFFF;
17
- --color-surface-tint: rgba(255, 255, 255, 0.92);
18
- --color-text-primary: #18181B;
19
- --color-text-secondary: #3F3F46;
20
- --color-text-tertiary: #71717A;
21
- --color-text-muted: #A1A1AA;
22
- --color-border: #E4E4E7;
23
- --color-border-soft: rgba(24, 24, 27, 0.12);
24
- --color-border-strong: rgba(24, 24, 27, 0.24);
17
+ --color-surface-tint: rgba(255, 253, 250, 0.86);
18
+ --color-text-primary: #241F1C;
19
+ --color-text-secondary: #5A504A;
20
+ --color-text-tertiary: #82746A;
21
+ --color-text-muted: #A99B91;
22
+ --color-border: rgba(137, 111, 94, 0.18);
23
+ --color-border-soft: rgba(137, 111, 94, 0.13);
24
+ --color-border-strong: rgba(137, 111, 94, 0.34);
25
25
 
26
26
  --color-success: #4B8B6A;
27
27
  --color-error: #C44536;
28
28
 
29
29
  --bg-warm-gradient:
30
- linear-gradient(180deg, #FAFAFA 0%, #FAFAFA 100%);
30
+ radial-gradient(circle at 14% 8%, rgba(255, 219, 196, 0.5) 0%, rgba(255, 219, 196, 0) 32%),
31
+ radial-gradient(circle at 88% 0%, rgba(252, 239, 207, 0.58) 0%, rgba(252, 239, 207, 0) 30%),
32
+ linear-gradient(135deg, #FFF8F1 0%, #F7F0E9 46%, #F1E8DF 100%);
31
33
 
32
- --color-bg-topbar-strong: rgba(250, 250, 250, 0.96);
33
- --color-bg-topbar-soft: rgba(250, 250, 250, 0.9);
34
- --color-bg-topbar-clear: rgba(250, 250, 250, 0);
34
+ --color-bg-topbar-strong: rgba(255, 248, 241, 0.96);
35
+ --color-bg-topbar-soft: rgba(255, 248, 241, 0.86);
36
+ --color-bg-topbar-clear: rgba(255, 248, 241, 0);
35
37
 
36
38
  /* 字体系统 */
37
39
  --font-family-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
@@ -64,24 +66,24 @@
64
66
  --spacing-xl: 64px;
65
67
 
66
68
  /* 圆角系统 */
67
- --radius-sm: 8px;
68
- --radius-md: 10px;
69
- --radius-lg: 12px;
70
- --radius-xl: 16px;
69
+ --radius-sm: 10px;
70
+ --radius-md: 14px;
71
+ --radius-lg: 18px;
72
+ --radius-xl: 24px;
71
73
  --radius-full: 50px;
72
74
 
73
- /* 阴影系统 - 更偏中性与克制 */
74
- --shadow-subtle: 0 1px 2px rgba(24, 24, 27, 0.04);
75
- --shadow-card: 0 1px 3px rgba(24, 24, 27, 0.06);
76
- --shadow-card-hover: 0 8px 26px rgba(24, 24, 27, 0.12);
77
- --shadow-float: 0 14px 36px rgba(24, 24, 27, 0.16);
78
- --shadow-raised: 0 8px 18px rgba(24, 24, 27, 0.12);
75
+ /* 阴影系统 - 柔和桌面浮层 */
76
+ --shadow-subtle: 0 1px 2px rgba(60, 47, 38, 0.05);
77
+ --shadow-card: 0 12px 30px rgba(92, 68, 52, 0.08);
78
+ --shadow-card-hover: 0 18px 44px rgba(92, 68, 52, 0.14);
79
+ --shadow-float: 0 22px 56px rgba(70, 51, 39, 0.18);
80
+ --shadow-raised: 0 14px 30px rgba(92, 68, 52, 0.14);
79
81
  --shadow-modal:
80
- 0 10px 30px rgba(24, 24, 27, 0.14),
81
- 0 28px 72px rgba(24, 24, 27, 0.12);
82
+ 0 16px 42px rgba(70, 51, 39, 0.16),
83
+ 0 34px 84px rgba(70, 51, 39, 0.16);
82
84
  --shadow-input-focus:
83
85
  0 0 0 3px var(--color-brand-light),
84
- 0 1px 2px rgba(24, 24, 27, 0.06);
86
+ 0 1px 2px rgba(60, 47, 38, 0.06);
85
87
 
86
88
  /* 动画 - 更细腻的曲线 */
87
89
  --transition-instant: 100ms;
@@ -266,3 +268,14 @@ body {
266
268
  position: relative;
267
269
  overflow-x: hidden;
268
270
  }
271
+
272
+ body::before {
273
+ content: "";
274
+ position: fixed;
275
+ inset: 0;
276
+ pointer-events: none;
277
+ background:
278
+ radial-gradient(circle at 18% 16%, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0) 26%),
279
+ radial-gradient(circle at 86% 18%, rgba(255, 232, 206, 0.36), rgba(255, 232, 206, 0) 30%);
280
+ z-index: 0;
281
+ }