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.
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/cli/builtin-proxy.js +430 -4
- package/cli/openai-bridge.js +498 -13
- package/cli.js +130 -41
- package/lib/cli-models-utils.js +71 -10
- package/lib/cli-webhook.js +126 -0
- package/package.json +76 -74
- package/plugins/prompt-templates/computed.mjs +1 -1
- package/plugins/prompt-templates/methods.mjs +0 -66
- package/plugins/prompt-templates/overview.mjs +1 -0
- package/web-ui/app.js +21 -16
- package/web-ui/index.html +1 -0
- package/web-ui/logic.codex.mjs +69 -0
- package/web-ui/modules/app.computed.dashboard.mjs +54 -0
- package/web-ui/modules/app.computed.session.mjs +22 -17
- package/web-ui/modules/app.methods.claude-config.mjs +24 -8
- package/web-ui/modules/app.methods.codex-config.mjs +35 -3
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.navigation.mjs +21 -3
- package/web-ui/modules/app.methods.providers.mjs +96 -7
- package/web-ui/modules/app.methods.session-actions.mjs +3 -6
- package/web-ui/modules/app.methods.session-browser.mjs +1 -6
- package/web-ui/modules/app.methods.session-trash.mjs +6 -7
- package/web-ui/modules/app.methods.startup-claude.mjs +8 -1
- package/web-ui/modules/app.methods.webhook.mjs +79 -0
- package/web-ui/modules/i18n.dict.mjs +1104 -104
- package/web-ui/modules/i18n.mjs +9 -3
- package/web-ui/modules/provider-url-display.mjs +17 -0
- package/web-ui/partials/index/layout-header.html +25 -0
- package/web-ui/partials/index/modals-basic.html +0 -3
- package/web-ui/partials/index/panel-config-claude.html +10 -3
- package/web-ui/partials/index/panel-config-codex.html +44 -4
- package/web-ui/partials/index/panel-plugins.html +3 -29
- package/web-ui/partials/index/panel-sessions.html +0 -10
- package/web-ui/partials/index/panel-settings.html +93 -177
- package/web-ui/partials/index/panel-trash.html +88 -0
- package/web-ui/session-helpers.mjs +2 -2
- package/web-ui/styles/base-theme.css +47 -34
- package/web-ui/styles/controls-forms.css +27 -28
- package/web-ui/styles/docs-panel.css +63 -39
- package/web-ui/styles/layout-shell.css +69 -46
- package/web-ui/styles/modals-core.css +12 -10
- package/web-ui/styles/navigation-panels.css +36 -35
- package/web-ui/styles/responsive.css +4 -4
- package/web-ui/styles/sessions-list.css +10 -6
- package/web-ui/styles/settings-panel.css +197 -33
- package/web-ui/styles/titles-cards.css +90 -26
- package/web-ui/styles/trash-panel.css +90 -0
- package/web-ui/styles/webhook.css +81 -0
- 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="
|
|
8
|
+
<div class="settings-subtabs-bar" role="tablist" :aria-label="t('settings.tabs.aria')">
|
|
9
9
|
<button
|
|
10
|
-
id="settings-tab-
|
|
10
|
+
id="settings-tab-general"
|
|
11
11
|
role="tab"
|
|
12
|
-
aria-controls="settings-panel-
|
|
13
|
-
:aria-selected="settingsTab === '
|
|
14
|
-
:tabindex="settingsTab === '
|
|
15
|
-
:class="['
|
|
16
|
-
@click="onSettingsTabClick('
|
|
17
|
-
{{ t('settings.tab.
|
|
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-
|
|
20
|
+
id="settings-tab-data"
|
|
21
21
|
role="tab"
|
|
22
|
-
aria-controls="settings-panel-
|
|
23
|
-
:aria-selected="settingsTab === '
|
|
24
|
-
:tabindex="settingsTab === '
|
|
25
|
-
:class="['
|
|
26
|
-
@click="onSettingsTabClick('
|
|
27
|
-
{{ t('settings.tab.
|
|
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 === '
|
|
44
|
-
id="settings-panel-
|
|
32
|
+
v-show="settingsTab === 'general'"
|
|
33
|
+
id="settings-panel-general"
|
|
45
34
|
role="tabpanel"
|
|
46
|
-
aria-labelledby="settings-tab-
|
|
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.
|
|
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.
|
|
75
|
-
<div class="settings-card-meta">{{ t('settings.
|
|
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
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
96
|
-
<div class="settings-card-header">
|
|
97
|
-
<div
|
|
98
|
-
|
|
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="
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 === '
|
|
123
|
-
id="settings-panel-
|
|
107
|
+
v-show="settingsTab === 'data'"
|
|
108
|
+
id="settings-panel-data"
|
|
124
109
|
role="tabpanel"
|
|
125
|
-
aria-labelledby="settings-tab-
|
|
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.
|
|
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.
|
|
164
|
-
<div class="settings-card-meta">{{ t('settings.
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
<
|
|
245
|
-
<
|
|
246
|
-
type="checkbox"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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 === '
|
|
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 !== '
|
|
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
|
-
/*
|
|
8
|
-
--color-brand: #
|
|
9
|
-
--color-brand-dark: #
|
|
10
|
-
--color-brand-light: rgba(
|
|
11
|
-
--color-brand-subtle: rgba(
|
|
12
|
-
|
|
13
|
-
--color-bg: #
|
|
14
|
-
--color-surface: #
|
|
15
|
-
--color-surface-alt: #
|
|
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,
|
|
18
|
-
--color-text-primary: #
|
|
19
|
-
--color-text-secondary: #
|
|
20
|
-
--color-text-tertiary: #
|
|
21
|
-
--color-text-muted: #
|
|
22
|
-
--color-border:
|
|
23
|
-
--color-border-soft: rgba(
|
|
24
|
-
--color-border-strong: rgba(
|
|
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
|
-
|
|
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(
|
|
33
|
-
--color-bg-topbar-soft: rgba(
|
|
34
|
-
--color-bg-topbar-clear: rgba(
|
|
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:
|
|
68
|
-
--radius-md:
|
|
69
|
-
--radius-lg:
|
|
70
|
-
--radius-xl:
|
|
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(
|
|
75
|
-
--shadow-card: 0
|
|
76
|
-
--shadow-card-hover: 0
|
|
77
|
-
--shadow-float: 0
|
|
78
|
-
--shadow-raised: 0
|
|
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
|
|
81
|
-
0
|
|
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(
|
|
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
|
+
}
|