codexmate 0.0.17 → 0.0.18
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.en.md +42 -23
- package/README.md +42 -23
- package/cli.js +1447 -157
- package/lib/text-diff.js +303 -0
- package/package.json +1 -1
- package/web-ui/app.js +1728 -202
- package/web-ui/index.html +306 -77
- package/web-ui/logic.mjs +390 -0
- package/web-ui/modules/skills.methods.mjs +7 -1
- package/web-ui/session-helpers.mjs +350 -0
- package/web-ui/styles.css +481 -11
package/web-ui/index.html
CHANGED
|
@@ -56,48 +56,56 @@
|
|
|
56
56
|
<button class="top-tab"
|
|
57
57
|
id="tab-config-codex"
|
|
58
58
|
role="tab"
|
|
59
|
+
data-main-tab="config"
|
|
60
|
+
data-config-mode="codex"
|
|
59
61
|
:tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
|
|
60
62
|
:aria-selected="mainTab === 'config' && configMode === 'codex'"
|
|
61
|
-
:aria-pressed="mainTab === 'config' && configMode === 'codex'"
|
|
62
63
|
aria-controls="panel-config-provider"
|
|
63
|
-
:class="{ active:
|
|
64
|
-
@
|
|
64
|
+
:class="{ active: isConfigModeNavActive('codex') }"
|
|
65
|
+
@pointerdown="onConfigTabPointerDown('codex', $event)"
|
|
66
|
+
@click="onConfigTabClick('codex', $event)">Codex 配置</button>
|
|
65
67
|
<button class="top-tab"
|
|
66
68
|
id="tab-config-claude"
|
|
67
69
|
role="tab"
|
|
70
|
+
data-main-tab="config"
|
|
71
|
+
data-config-mode="claude"
|
|
68
72
|
:tabindex="mainTab === 'config' && configMode === 'claude' ? 0 : -1"
|
|
69
73
|
:aria-selected="mainTab === 'config' && configMode === 'claude'"
|
|
70
|
-
:aria-pressed="mainTab === 'config' && configMode === 'claude'"
|
|
71
74
|
aria-controls="panel-config-claude"
|
|
72
|
-
:class="{ active:
|
|
73
|
-
@
|
|
75
|
+
:class="{ active: isConfigModeNavActive('claude') }"
|
|
76
|
+
@pointerdown="onConfigTabPointerDown('claude', $event)"
|
|
77
|
+
@click="onConfigTabClick('claude', $event)">Claude Code 配置</button>
|
|
74
78
|
<button class="top-tab"
|
|
75
79
|
id="tab-config-openclaw"
|
|
76
80
|
role="tab"
|
|
81
|
+
data-main-tab="config"
|
|
82
|
+
data-config-mode="openclaw"
|
|
77
83
|
:tabindex="mainTab === 'config' && configMode === 'openclaw' ? 0 : -1"
|
|
78
84
|
:aria-selected="mainTab === 'config' && configMode === 'openclaw'"
|
|
79
|
-
:aria-pressed="mainTab === 'config' && configMode === 'openclaw'"
|
|
80
85
|
aria-controls="panel-config-openclaw"
|
|
81
|
-
:class="{ active:
|
|
82
|
-
@
|
|
86
|
+
:class="{ active: isConfigModeNavActive('openclaw') }"
|
|
87
|
+
@pointerdown="onConfigTabPointerDown('openclaw', $event)"
|
|
88
|
+
@click="onConfigTabClick('openclaw', $event)">OpenClaw 配置</button>
|
|
83
89
|
<button class="top-tab"
|
|
84
90
|
id="tab-sessions"
|
|
85
91
|
role="tab"
|
|
92
|
+
data-main-tab="sessions"
|
|
86
93
|
:tabindex="mainTab === 'sessions' ? 0 : -1"
|
|
87
94
|
:aria-selected="mainTab === 'sessions'"
|
|
88
|
-
:aria-pressed="mainTab === 'sessions'"
|
|
89
95
|
aria-controls="panel-sessions"
|
|
90
|
-
:class="{ active:
|
|
91
|
-
@
|
|
96
|
+
:class="{ active: isMainTabNavActive('sessions') }"
|
|
97
|
+
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
98
|
+
@click="onMainTabClick('sessions', $event)">会话浏览</button>
|
|
92
99
|
<button class="top-tab"
|
|
93
100
|
id="tab-settings"
|
|
94
101
|
role="tab"
|
|
102
|
+
data-main-tab="settings"
|
|
95
103
|
:tabindex="mainTab === 'settings' ? 0 : -1"
|
|
96
104
|
:aria-selected="mainTab === 'settings'"
|
|
97
|
-
:aria-pressed="mainTab === 'settings'"
|
|
98
105
|
aria-controls="panel-settings"
|
|
99
|
-
:class="{ active:
|
|
100
|
-
@
|
|
106
|
+
:class="{ active: isMainTabNavActive('settings') }"
|
|
107
|
+
@pointerdown="onMainTabPointerDown('settings', $event)"
|
|
108
|
+
@click="onMainTabClick('settings', $event)">设置</button>
|
|
101
109
|
</div>
|
|
102
110
|
|
|
103
111
|
<div :class="['app-shell', { standalone: sessionStandalone }]">
|
|
@@ -137,12 +145,14 @@
|
|
|
137
145
|
<button
|
|
138
146
|
role="tab"
|
|
139
147
|
id="side-tab-config-codex"
|
|
148
|
+
data-main-tab="config"
|
|
149
|
+
data-config-mode="codex"
|
|
140
150
|
aria-controls="panel-config-provider"
|
|
141
151
|
:tabindex="mainTab === 'config' && configMode === 'codex' ? 0 : -1"
|
|
142
152
|
:aria-selected="mainTab === 'config' && configMode === 'codex'"
|
|
143
|
-
:
|
|
144
|
-
|
|
145
|
-
@click="
|
|
153
|
+
:class="['side-item', { active: isConfigModeNavActive('codex') }]"
|
|
154
|
+
@pointerdown="onConfigTabPointerDown('codex', $event)"
|
|
155
|
+
@click="onConfigTabClick('codex', $event)">
|
|
146
156
|
<div class="side-item-title">Codex 配置</div>
|
|
147
157
|
<div class="side-item-meta">
|
|
148
158
|
<span>提供商 / 模型</span>
|
|
@@ -152,12 +162,14 @@
|
|
|
152
162
|
<button
|
|
153
163
|
role="tab"
|
|
154
164
|
id="side-tab-config-claude"
|
|
165
|
+
data-main-tab="config"
|
|
166
|
+
data-config-mode="claude"
|
|
155
167
|
aria-controls="panel-config-claude"
|
|
156
168
|
:tabindex="mainTab === 'config' && configMode === 'claude' ? 0 : -1"
|
|
157
169
|
:aria-selected="mainTab === 'config' && configMode === 'claude'"
|
|
158
|
-
:
|
|
159
|
-
|
|
160
|
-
@click="
|
|
170
|
+
:class="['side-item', { active: isConfigModeNavActive('claude') }]"
|
|
171
|
+
@pointerdown="onConfigTabPointerDown('claude', $event)"
|
|
172
|
+
@click="onConfigTabClick('claude', $event)">
|
|
161
173
|
<div class="side-item-title">Claude Code 配置</div>
|
|
162
174
|
<div class="side-item-meta">
|
|
163
175
|
<span>Base URL / Key</span>
|
|
@@ -167,12 +179,14 @@
|
|
|
167
179
|
<button
|
|
168
180
|
role="tab"
|
|
169
181
|
id="side-tab-config-openclaw"
|
|
182
|
+
data-main-tab="config"
|
|
183
|
+
data-config-mode="openclaw"
|
|
170
184
|
aria-controls="panel-config-openclaw"
|
|
171
185
|
:tabindex="mainTab === 'config' && configMode === 'openclaw' ? 0 : -1"
|
|
172
186
|
:aria-selected="mainTab === 'config' && configMode === 'openclaw'"
|
|
173
|
-
:
|
|
174
|
-
|
|
175
|
-
@click="
|
|
187
|
+
:class="['side-item', { active: isConfigModeNavActive('openclaw') }]"
|
|
188
|
+
@pointerdown="onConfigTabPointerDown('openclaw', $event)"
|
|
189
|
+
@click="onConfigTabClick('openclaw', $event)">
|
|
176
190
|
<div class="side-item-title">OpenClaw 配置</div>
|
|
177
191
|
<div class="side-item-meta">
|
|
178
192
|
<span>JSON5 / Workspace</span>
|
|
@@ -186,12 +200,13 @@
|
|
|
186
200
|
<button
|
|
187
201
|
role="tab"
|
|
188
202
|
id="side-tab-sessions"
|
|
203
|
+
data-main-tab="sessions"
|
|
189
204
|
aria-controls="panel-sessions"
|
|
190
205
|
:tabindex="mainTab === 'sessions' ? 0 : -1"
|
|
191
206
|
:aria-selected="mainTab === 'sessions'"
|
|
192
|
-
:
|
|
193
|
-
|
|
194
|
-
@click="
|
|
207
|
+
:class="['side-item', { active: isMainTabNavActive('sessions') }]"
|
|
208
|
+
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
209
|
+
@click="onMainTabClick('sessions', $event)">
|
|
195
210
|
<div class="side-item-title">会话浏览</div>
|
|
196
211
|
<div class="side-item-meta">
|
|
197
212
|
<span>快速预览 / 导出</span>
|
|
@@ -205,12 +220,13 @@
|
|
|
205
220
|
<button
|
|
206
221
|
role="tab"
|
|
207
222
|
id="side-tab-settings"
|
|
223
|
+
data-main-tab="settings"
|
|
208
224
|
aria-controls="panel-settings"
|
|
209
225
|
:tabindex="mainTab === 'settings' ? 0 : -1"
|
|
210
226
|
:aria-selected="mainTab === 'settings'"
|
|
211
|
-
:
|
|
212
|
-
|
|
213
|
-
@click="
|
|
227
|
+
:class="['side-item', { active: isMainTabNavActive('settings') }]"
|
|
228
|
+
@pointerdown="onMainTabPointerDown('settings', $event)"
|
|
229
|
+
@click="onMainTabClick('settings', $event)">
|
|
214
230
|
<div class="side-item-title">设置</div>
|
|
215
231
|
<div class="side-item-meta">
|
|
216
232
|
<span>数据管理 / 下载</span>
|
|
@@ -298,13 +314,13 @@
|
|
|
298
314
|
</div>
|
|
299
315
|
|
|
300
316
|
<div v-if="false && mainTab === 'config' && !sessionStandalone" class="config-subtabs">
|
|
301
|
-
<button :class="['config-subtab', { active: configMode === 'codex' }]" @click="
|
|
317
|
+
<button :class="['config-subtab', { active: configMode === 'codex' }]" @click="onConfigTabClick('codex', $event)">
|
|
302
318
|
Codex 配置
|
|
303
319
|
</button>
|
|
304
|
-
<button :class="['config-subtab', { active: configMode === 'claude' }]" @click="
|
|
320
|
+
<button :class="['config-subtab', { active: configMode === 'claude' }]" @click="onConfigTabClick('claude', $event)">
|
|
305
321
|
Claude Code 配置
|
|
306
322
|
</button>
|
|
307
|
-
<button :class="['config-subtab', { active: configMode === 'openclaw' }]" @click="
|
|
323
|
+
<button :class="['config-subtab', { active: configMode === 'openclaw' }]" @click="onConfigTabClick('openclaw', $event)">
|
|
308
324
|
OpenClaw 配置
|
|
309
325
|
</button>
|
|
310
326
|
</div>
|
|
@@ -569,10 +585,11 @@
|
|
|
569
585
|
<button
|
|
570
586
|
v-if="!provider.readOnly"
|
|
571
587
|
class="card-action-btn"
|
|
572
|
-
:class="{ loading: providerShareLoading[provider.name]
|
|
573
|
-
|
|
588
|
+
:class="{ loading: providerShareLoading[provider.name] }"
|
|
589
|
+
disabled
|
|
574
590
|
@click="copyProviderShareCommand(provider)"
|
|
575
|
-
|
|
591
|
+
title="分享导入命令(暂时禁用)"
|
|
592
|
+
aria-label="Share import command">
|
|
576
593
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
577
594
|
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
578
595
|
<path d="M16 6l-4-4-4 4"/>
|
|
@@ -885,14 +902,16 @@
|
|
|
885
902
|
</div>
|
|
886
903
|
|
|
887
904
|
<div v-else :class="['session-layout', { 'session-standalone': sessionStandalone }]">
|
|
888
|
-
<div v-if="!sessionStandalone" class="session-list">
|
|
905
|
+
<div v-if="!sessionStandalone && sessionListRenderEnabled" class="session-list">
|
|
889
906
|
<div
|
|
890
|
-
v-for="session in
|
|
907
|
+
v-for="session in sortedSessionsList"
|
|
891
908
|
:key="session.source + '-' + session.sessionId + '-' + session.filePath"
|
|
909
|
+
v-memo="[activeSessionExportKey === getSessionExportKey(session), session.messageCount, session.updatedAt, session.title, session.sourceLabel, isSessionPinned(session), sessionsLoading]"
|
|
892
910
|
:class="[
|
|
893
911
|
'session-item',
|
|
894
912
|
{
|
|
895
|
-
active:
|
|
913
|
+
active: activeSessionExportKey === getSessionExportKey(session),
|
|
914
|
+
pinned: isSessionPinned(session)
|
|
896
915
|
}
|
|
897
916
|
]"
|
|
898
917
|
@click="selectSession(session)">
|
|
@@ -902,6 +921,20 @@
|
|
|
902
921
|
<span class="session-count-badge">{{ session.messageCount ?? 0 }}</span>
|
|
903
922
|
</div>
|
|
904
923
|
<div class="session-item-actions">
|
|
924
|
+
<button
|
|
925
|
+
class="session-item-copy session-item-pin"
|
|
926
|
+
@click.stop="toggleSessionPin(session)"
|
|
927
|
+
:disabled="sessionsLoading"
|
|
928
|
+
:aria-label="isSessionPinned(session) ? '取消置顶' : '置顶'"
|
|
929
|
+
:title="isSessionPinned(session) ? '取消置顶' : '置顶'"
|
|
930
|
+
:aria-pressed="isSessionPinned(session)">
|
|
931
|
+
<svg v-if="isSessionPinned(session)" class="pin-icon" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.6">
|
|
932
|
+
<path d="M12 22s8-6 8-12a8 8 0 1 0-16 0c0 6 8 12 8 12z"></path>
|
|
933
|
+
</svg>
|
|
934
|
+
<svg v-else class="pin-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
|
935
|
+
<path d="M12 22s8-6 8-12a8 8 0 1 0-16 0c0 6 8 12 8 12z"></path>
|
|
936
|
+
</svg>
|
|
937
|
+
</button>
|
|
905
938
|
<button
|
|
906
939
|
v-if="isResumeCommandAvailable(session)"
|
|
907
940
|
class="session-item-copy"
|
|
@@ -922,6 +955,7 @@
|
|
|
922
955
|
</div>
|
|
923
956
|
</div>
|
|
924
957
|
</div>
|
|
958
|
+
<div v-else-if="!sessionStandalone" class="session-list session-list-placeholder"></div>
|
|
925
959
|
|
|
926
960
|
<div :class="['session-preview', { active: !!activeSession }]" :ref="setSessionPreviewContainerRef">
|
|
927
961
|
<template v-if="activeSession">
|
|
@@ -946,7 +980,7 @@
|
|
|
946
980
|
class="btn-session-delete"
|
|
947
981
|
@click="deleteSession(activeSession)"
|
|
948
982
|
:disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]">
|
|
949
|
-
{{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '
|
|
983
|
+
{{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '移入中...' : '移入回收站' }}
|
|
950
984
|
</button>
|
|
951
985
|
<button
|
|
952
986
|
class="btn-session-export"
|
|
@@ -964,7 +998,7 @@
|
|
|
964
998
|
</div>
|
|
965
999
|
</div>
|
|
966
1000
|
|
|
967
|
-
<div v-if="sessionDetailLoading" class="session-preview-empty">
|
|
1001
|
+
<div v-if="sessionDetailLoading && !sessionPreviewLoadingMore" class="session-preview-empty">
|
|
968
1002
|
正在加载会话内容...
|
|
969
1003
|
</div>
|
|
970
1004
|
|
|
@@ -976,16 +1010,42 @@
|
|
|
976
1010
|
当前会话暂无可展示消息
|
|
977
1011
|
</div>
|
|
978
1012
|
|
|
1013
|
+
<div v-else-if="sessionPreviewRenderEnabled && !activeSessionVisibleMessages.length" class="session-preview-empty">
|
|
1014
|
+
<span>正在渲染会话内容...</span>
|
|
1015
|
+
<button class="btn-session-refresh" @click="primeSessionPreviewMessageRender" :disabled="sessionDetailLoading">
|
|
1016
|
+
重新渲染
|
|
1017
|
+
</button>
|
|
1018
|
+
</div>
|
|
1019
|
+
|
|
1020
|
+
<div v-else-if="!sessionPreviewRenderEnabled" class="session-preview-empty">
|
|
1021
|
+
正在准备会话内容...
|
|
1022
|
+
</div>
|
|
1023
|
+
|
|
979
1024
|
<div v-else class="session-preview-body">
|
|
980
1025
|
<div class="session-preview-messages">
|
|
981
1026
|
<div v-if="activeSessionDetailClipped" class="session-item-sub session-item-wrap">
|
|
982
1027
|
仅展示最近 {{ activeSessionMessages.length }} 条消息。
|
|
983
1028
|
</div>
|
|
984
1029
|
<div
|
|
985
|
-
v-
|
|
1030
|
+
v-if="canLoadMoreSessionMessages"
|
|
1031
|
+
class="session-item-sub session-item-wrap"
|
|
1032
|
+
style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
1033
|
+
<span>已显示 {{ activeSessionVisibleMessages.length }} / {{ activeSessionMessages.length }} 条</span>
|
|
1034
|
+
<button class="btn-session-refresh" @click="loadMoreSessionMessages()" :disabled="sessionDetailLoading || sessionPreviewLoadingMore">
|
|
1035
|
+
{{ sessionPreviewLoadingMore ? '加载中...' : ('加载更多(剩余 ' + sessionPreviewRemainingCount + ')') }}
|
|
1036
|
+
</button>
|
|
1037
|
+
</div>
|
|
1038
|
+
<div
|
|
1039
|
+
v-if="sessionPreviewLoadingMore"
|
|
1040
|
+
class="session-item-sub session-item-wrap">
|
|
1041
|
+
正在加载更早消息...
|
|
1042
|
+
</div>
|
|
1043
|
+
<div
|
|
1044
|
+
v-for="(msg, idx) in activeSessionVisibleMessages"
|
|
986
1045
|
:key="getRecordRenderKey(msg, idx)"
|
|
1046
|
+
v-memo="[msg.text, msg.timestamp, msg.roleLabel, msg.normalizedRole]"
|
|
987
1047
|
:data-message-key="getRecordRenderKey(msg, idx)"
|
|
988
|
-
:ref="(
|
|
1048
|
+
:ref="getSessionMessageRefBinder(getRecordRenderKey(msg, idx))"
|
|
989
1049
|
:class="['session-msg', msg.normalizedRole === 'user' ? 'user' : (msg.normalizedRole === 'system' ? 'system' : 'assistant')]">
|
|
990
1050
|
<div class="session-msg-header">
|
|
991
1051
|
<div class="session-msg-meta">
|
|
@@ -998,11 +1058,12 @@
|
|
|
998
1058
|
</div>
|
|
999
1059
|
</div>
|
|
1000
1060
|
</div>
|
|
1001
|
-
<aside v-if="sessionTimelineNodes.length" class="session-timeline" aria-label="会话时间轴">
|
|
1061
|
+
<aside v-if="sessionPreviewRenderEnabled && sessionTimelineNodes.length" class="session-timeline" aria-label="会话时间轴">
|
|
1002
1062
|
<div class="session-timeline-track"></div>
|
|
1003
1063
|
<button
|
|
1004
1064
|
v-for="node in sessionTimelineNodes"
|
|
1005
1065
|
:key="'timeline-' + node.key"
|
|
1066
|
+
v-memo="[sessionTimelineActiveKey === node.key, node.safePercent, node.title]"
|
|
1006
1067
|
type="button"
|
|
1007
1068
|
:class="['session-timeline-node', { active: sessionTimelineActiveKey === node.key }]"
|
|
1008
1069
|
:aria-current="sessionTimelineActiveKey === node.key ? 'true' : null"
|
|
@@ -1033,39 +1094,140 @@
|
|
|
1033
1094
|
id="panel-settings"
|
|
1034
1095
|
role="tabpanel"
|
|
1035
1096
|
:aria-labelledby="'tab-settings'">
|
|
1036
|
-
<div class="
|
|
1037
|
-
<
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1097
|
+
<div class="config-subtabs settings-subtabs" role="tablist" aria-label="设置子标签">
|
|
1098
|
+
<button
|
|
1099
|
+
id="settings-tab-backup"
|
|
1100
|
+
role="tab"
|
|
1101
|
+
aria-controls="settings-panel-backup"
|
|
1102
|
+
:aria-selected="settingsTab === 'backup'"
|
|
1103
|
+
tabindex="0"
|
|
1104
|
+
:class="['config-subtab', { active: settingsTab === 'backup' }]"
|
|
1105
|
+
@click="onSettingsTabClick('backup')">
|
|
1106
|
+
备份与导入
|
|
1042
1107
|
</button>
|
|
1043
|
-
<button
|
|
1044
|
-
|
|
1108
|
+
<button
|
|
1109
|
+
id="settings-tab-trash"
|
|
1110
|
+
role="tab"
|
|
1111
|
+
aria-controls="settings-panel-trash"
|
|
1112
|
+
:aria-selected="settingsTab === 'trash'"
|
|
1113
|
+
tabindex="0"
|
|
1114
|
+
:class="['config-subtab', { active: settingsTab === 'trash' }]"
|
|
1115
|
+
@click="onSettingsTabClick('trash')">
|
|
1116
|
+
回收站
|
|
1117
|
+
<span class="settings-tab-badge">{{ sessionTrashCount }}</span>
|
|
1045
1118
|
</button>
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1119
|
+
</div>
|
|
1120
|
+
|
|
1121
|
+
<div
|
|
1122
|
+
v-show="settingsTab === 'backup'"
|
|
1123
|
+
id="settings-panel-backup"
|
|
1124
|
+
role="tabpanel"
|
|
1125
|
+
aria-labelledby="settings-tab-backup">
|
|
1126
|
+
<div class="selector-section">
|
|
1127
|
+
<div class="selector-header">
|
|
1128
|
+
<span class="selector-title">Claude 配置</span>
|
|
1129
|
+
</div>
|
|
1130
|
+
<button class="btn-tool" @click="downloadClaudeDirectory" :disabled="claudeDownloadLoading">
|
|
1131
|
+
{{ claudeDownloadLoading ? ('备份中 ' + claudeDownloadProgress + '%') : '一键备份 ~/.claude' }}
|
|
1132
|
+
</button>
|
|
1133
|
+
<button class="btn-tool" @click="triggerClaudeImport" :disabled="claudeImportLoading">
|
|
1134
|
+
{{ claudeImportLoading ? '导入中...' : '导入 ~/.claude 备份' }}
|
|
1135
|
+
</button>
|
|
1136
|
+
<input
|
|
1137
|
+
ref="claudeImportInput"
|
|
1138
|
+
class="sr-only"
|
|
1139
|
+
type="file"
|
|
1140
|
+
accept=".zip"
|
|
1141
|
+
@change="handleClaudeImportChange">
|
|
1142
|
+
</div>
|
|
1143
|
+
<div class="selector-section">
|
|
1144
|
+
<div class="selector-header">
|
|
1145
|
+
<span class="selector-title">Codex 配置</span>
|
|
1146
|
+
</div>
|
|
1147
|
+
<button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
1148
|
+
{{ codexDownloadLoading ? ('备份中 ' + codexDownloadProgress + '%') : '一键备份 ~/.codex' }}
|
|
1149
|
+
</button>
|
|
1150
|
+
<button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
|
|
1151
|
+
{{ codexImportLoading ? '导入中...' : '导入 ~/.codex 备份' }}
|
|
1152
|
+
</button>
|
|
1153
|
+
<input
|
|
1154
|
+
ref="codexImportInput"
|
|
1155
|
+
class="sr-only"
|
|
1156
|
+
type="file"
|
|
1157
|
+
accept=".zip"
|
|
1158
|
+
@change="handleCodexImportChange">
|
|
1159
|
+
</div>
|
|
1160
|
+
</div>
|
|
1161
|
+
|
|
1162
|
+
<div
|
|
1163
|
+
v-show="settingsTab === 'trash'"
|
|
1164
|
+
id="settings-panel-trash"
|
|
1165
|
+
role="tabpanel"
|
|
1166
|
+
aria-labelledby="settings-tab-trash">
|
|
1167
|
+
<div class="selector-section">
|
|
1168
|
+
<div class="selector-header settings-tab-header">
|
|
1169
|
+
<div>
|
|
1170
|
+
<span class="selector-title">会话回收站</span>
|
|
1171
|
+
</div>
|
|
1172
|
+
<div class="settings-tab-actions">
|
|
1173
|
+
<button class="btn-tool btn-tool-compact" @click="loadSessionTrash({ forceRefresh: true })" :disabled="sessionTrashLoading || sessionTrashClearing">
|
|
1174
|
+
{{ sessionTrashLoading ? '刷新中...' : '刷新列表' }}
|
|
1175
|
+
</button>
|
|
1176
|
+
<button class="btn-tool btn-tool-compact" @click="clearSessionTrash" :disabled="sessionTrashClearing || sessionTrashLoading || !(Number(sessionTrashCount) > 0)">
|
|
1177
|
+
{{ sessionTrashClearing ? '清空中...' : '清空回收站' }}
|
|
1178
|
+
</button>
|
|
1179
|
+
</div>
|
|
1180
|
+
</div>
|
|
1181
|
+
|
|
1182
|
+
<div v-if="getSessionTrashViewState() === 'loading'" class="session-empty">
|
|
1183
|
+
正在加载回收站...
|
|
1184
|
+
</div>
|
|
1185
|
+
<div v-else-if="getSessionTrashViewState() === 'empty'" class="session-empty">
|
|
1186
|
+
回收站为空
|
|
1187
|
+
</div>
|
|
1188
|
+
<div v-else-if="getSessionTrashViewState() === 'retry'" class="session-empty">
|
|
1189
|
+
回收站列表加载失败,请刷新重试
|
|
1190
|
+
</div>
|
|
1191
|
+
<div v-else class="trash-list">
|
|
1192
|
+
<div v-for="item in visibleSessionTrashItems" :key="item.trashId" class="trash-item session-item session-card">
|
|
1193
|
+
<div class="trash-item-header session-item-header">
|
|
1194
|
+
<div class="trash-item-main">
|
|
1195
|
+
<div class="trash-item-mainline">
|
|
1196
|
+
<div class="trash-item-title">{{ item.title || item.sessionId }}</div>
|
|
1197
|
+
<span class="session-count-badge">{{ item.messageCount ?? 0 }}</span>
|
|
1198
|
+
</div>
|
|
1199
|
+
<div class="trash-item-meta session-item-meta">
|
|
1200
|
+
<span class="session-source">{{ item.sourceLabel }}</span>
|
|
1201
|
+
</div>
|
|
1202
|
+
</div>
|
|
1203
|
+
<div class="trash-item-side">
|
|
1204
|
+
<div class="trash-item-actions session-item-actions">
|
|
1205
|
+
<button class="btn-mini" @click="restoreSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
|
|
1206
|
+
{{ sessionTrashRestoring[getSessionTrashActionKey(item)] ? '恢复中...' : '恢复' }}
|
|
1207
|
+
</button>
|
|
1208
|
+
<button class="btn-mini delete" @click="purgeSessionTrash(item)" :disabled="sessionTrashLoading || sessionTrashClearing || isSessionTrashActionBusy(item)">
|
|
1209
|
+
{{ sessionTrashPurging[getSessionTrashActionKey(item)] ? '删除中...' : '彻底删除' }}
|
|
1210
|
+
</button>
|
|
1211
|
+
</div>
|
|
1212
|
+
<div class="trash-item-time session-item-time">{{ item.deletedAt || item.updatedAt || 'unknown time' }}</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
</div>
|
|
1215
|
+
<div v-if="item.cwd" class="trash-item-path session-item-sub session-item-wrap">
|
|
1216
|
+
<span class="trash-item-label">工作区</span>
|
|
1217
|
+
<span>{{ item.cwd }}</span>
|
|
1218
|
+
</div>
|
|
1219
|
+
<div class="trash-item-path session-item-sub session-item-wrap">
|
|
1220
|
+
<span class="trash-item-label">原文件</span>
|
|
1221
|
+
<span>{{ item.originalFilePath }}</span>
|
|
1222
|
+
</div>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div v-if="sessionTrashHasMoreItems" class="trash-list-footer">
|
|
1225
|
+
<button class="btn-tool btn-tool-compact" @click="loadMoreSessionTrashItems" :disabled="sessionTrashLoading || sessionTrashClearing">
|
|
1226
|
+
加载更多(剩余 {{ sessionTrashHiddenCount }} 项)
|
|
1227
|
+
</button>
|
|
1228
|
+
</div>
|
|
1229
|
+
</div>
|
|
1056
1230
|
</div>
|
|
1057
|
-
<button class="btn-tool" @click="downloadCodexDirectory" :disabled="codexDownloadLoading">
|
|
1058
|
-
{{ codexDownloadLoading ? ('备份中 ' + codexDownloadProgress + '%') : '一键备份 ~/.codex' }}
|
|
1059
|
-
</button>
|
|
1060
|
-
<button class="btn-tool" @click="triggerCodexImport" :disabled="codexImportLoading">
|
|
1061
|
-
{{ codexImportLoading ? '导入中...' : '导入 ~/.codex 备份' }}
|
|
1062
|
-
</button>
|
|
1063
|
-
<input
|
|
1064
|
-
ref="codexImportInput"
|
|
1065
|
-
class="sr-only"
|
|
1066
|
-
type="file"
|
|
1067
|
-
accept=".zip"
|
|
1068
|
-
@change="handleCodexImportChange">
|
|
1069
1231
|
</div>
|
|
1070
1232
|
</div>
|
|
1071
1233
|
|
|
@@ -1669,24 +1831,70 @@
|
|
|
1669
1831
|
</div>
|
|
1670
1832
|
</div>
|
|
1671
1833
|
|
|
1834
|
+
|
|
1672
1835
|
<div class="form-group">
|
|
1673
1836
|
<label class="form-label">AGENTS.md 内容</label>
|
|
1837
|
+
<div
|
|
1838
|
+
v-if="!agentsLoading && (hasAgentsContentChanged() || agentsDiffVisible)"
|
|
1839
|
+
class="agents-diff-save-alert">
|
|
1840
|
+
{{ agentsDiffVisible ? '预览模式:当前改动尚未保存,只有点击“应用”后才会写入文件。' : '检测到未保存改动:关闭页面或应用前请先保存。' }}
|
|
1841
|
+
</div>
|
|
1842
|
+
<div v-if="agentsDiffVisible">
|
|
1843
|
+
<div
|
|
1844
|
+
v-if="!agentsDiffLoading && !agentsDiffError && !agentsDiffTruncated && (agentsDiffStats.added || agentsDiffStats.removed)"
|
|
1845
|
+
class="agents-diff-summary">
|
|
1846
|
+
<span class="agents-diff-stat add">+{{ agentsDiffStats.added }}</span>
|
|
1847
|
+
<span class="agents-diff-stat del">-{{ agentsDiffStats.removed }}</span>
|
|
1848
|
+
</div>
|
|
1849
|
+
<div v-if="agentsDiffLoading" class="state-message">生成差异中...</div>
|
|
1850
|
+
<div v-else-if="agentsDiffError" class="state-message error">{{ agentsDiffError }}</div>
|
|
1851
|
+
<div v-else-if="agentsDiffTruncated" class="agents-diff-empty">内容过大,已跳过逐行差异预览</div>
|
|
1852
|
+
<div v-else-if="!agentsDiffHasChanges" class="agents-diff-empty">未检测到改动</div>
|
|
1853
|
+
<div v-else class="agents-diff-view agents-diff-editor">
|
|
1854
|
+
<div
|
|
1855
|
+
v-for="(line, index) in agentsDiffLines"
|
|
1856
|
+
:key="line.key || (line.type + '-' + index)"
|
|
1857
|
+
:class="['agents-diff-line', line.type]">
|
|
1858
|
+
<span class="agents-diff-line-sign">
|
|
1859
|
+
{{ line.type === 'add' ? '+' : (line.type === 'del' ? '-' : ' ') }}
|
|
1860
|
+
</span>
|
|
1861
|
+
<span class="agents-diff-line-text">{{ line.value }}</span>
|
|
1862
|
+
</div>
|
|
1863
|
+
</div>
|
|
1864
|
+
</div>
|
|
1674
1865
|
<textarea
|
|
1866
|
+
v-else
|
|
1675
1867
|
v-model="agentsContent"
|
|
1676
1868
|
class="form-input template-editor"
|
|
1677
1869
|
spellcheck="false"
|
|
1678
1870
|
:readonly="agentsLoading"
|
|
1871
|
+
@input="onAgentsContentInput"
|
|
1679
1872
|
placeholder="在这里编辑 AGENTS.md 内容"></textarea>
|
|
1680
1873
|
<div class="template-editor-warning">
|
|
1681
1874
|
{{ agentsModalHint }}
|
|
1875
|
+
<div class="agents-diff-hint">快捷键:Esc(差异预览时返回编辑,编辑时关闭窗口)。</div>
|
|
1876
|
+
<div v-if="!agentsDiffVisible" class="agents-diff-hint">保存需两步:先点击“确认”预览差异,再点击“应用”保存。</div>
|
|
1877
|
+
<div v-else-if="agentsDiffLoading || agentsSaving" class="agents-diff-hint">正在生成差异或应用中,操作暂不可用。</div>
|
|
1878
|
+
<div v-else-if="agentsDiffError" class="agents-diff-hint">差异预览失败,请返回编辑后重试。</div>
|
|
1879
|
+
<div v-else-if="!agentsDiffHasChanges" class="agents-diff-hint">未检测到改动,可返回编辑继续修改或取消退出。</div>
|
|
1880
|
+
<div v-else-if="agentsDiffTruncated" class="agents-diff-hint">内容过大,已跳过预览,可点击“应用”保存或“返回编辑”继续修改。</div>
|
|
1881
|
+
<div v-else class="agents-diff-hint">当前为预览模式,可点击“应用”保存或“返回编辑”继续修改。</div>
|
|
1682
1882
|
</div>
|
|
1683
1883
|
</div>
|
|
1884
|
+
|
|
1684
1885
|
</div>
|
|
1685
1886
|
|
|
1686
1887
|
<div class="btn-group modal-editor-footer">
|
|
1687
|
-
<button class="btn btn-cancel" @click="closeAgentsModal">取消</button>
|
|
1688
|
-
<button
|
|
1689
|
-
|
|
1888
|
+
<button class="btn btn-cancel" @click="closeAgentsModal" :disabled="agentsSaving || agentsDiffLoading">取消</button>
|
|
1889
|
+
<button
|
|
1890
|
+
v-if="agentsDiffVisible"
|
|
1891
|
+
class="btn"
|
|
1892
|
+
@click="resetAgentsDiffState"
|
|
1893
|
+
:disabled="agentsSaving || agentsDiffLoading">
|
|
1894
|
+
返回编辑
|
|
1895
|
+
</button>
|
|
1896
|
+
<button class="btn btn-confirm" @click="applyAgentsContent" :disabled="agentsSaving || agentsLoading || agentsDiffLoading || (agentsDiffVisible && !agentsDiffHasChanges)">
|
|
1897
|
+
{{ agentsSaving ? (agentsDiffVisible ? '应用中...' : '确认中...') : (agentsDiffVisible ? '应用' : '确认') }}
|
|
1690
1898
|
</button>
|
|
1691
1899
|
</div>
|
|
1692
1900
|
</div>
|
|
@@ -1854,6 +2062,27 @@
|
|
|
1854
2062
|
</div>
|
|
1855
2063
|
</div>
|
|
1856
2064
|
|
|
2065
|
+
<div v-if="showConfirmDialog" class="modal-overlay" @click.self="closeConfirmDialog">
|
|
2066
|
+
<div
|
|
2067
|
+
class="modal confirm-dialog"
|
|
2068
|
+
role="alertdialog"
|
|
2069
|
+
aria-modal="true"
|
|
2070
|
+
aria-describedby="confirm-dialog-message"
|
|
2071
|
+
:aria-labelledby="confirmDialogTitle ? 'confirm-dialog-title' : null"
|
|
2072
|
+
:aria-label="confirmDialogTitle ? null : '确认操作'">
|
|
2073
|
+
<div id="confirm-dialog-title" class="modal-title">{{ confirmDialogTitle }}</div>
|
|
2074
|
+
<div id="confirm-dialog-message" class="confirm-dialog-message">{{ confirmDialogMessage }}</div>
|
|
2075
|
+
<div class="btn-group confirm-dialog-actions">
|
|
2076
|
+
<button class="btn btn-cancel" @click="closeConfirmDialog">{{ confirmDialogCancelText }}</button>
|
|
2077
|
+
<button
|
|
2078
|
+
:class="['btn', 'btn-confirm', confirmDialogDanger ? 'btn-danger' : '']"
|
|
2079
|
+
@click="resolveConfirmDialog(true)">
|
|
2080
|
+
{{ confirmDialogConfirmText }}
|
|
2081
|
+
</button>
|
|
2082
|
+
</div>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
|
|
1857
2086
|
<!-- Toast通知 -->
|
|
1858
2087
|
|
|
1859
2088
|
<!-- Toast -->
|