coding-tool-x 3.6.4 → 3.6.6

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 (29) hide show
  1. package/dist/web/assets/{Analytics-BTmqXkR-.js → Analytics-CcGKjDkP.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-z3w0kyNl.js → ConfigTemplates-DPOa1w5D.js} +1 -1
  3. package/dist/web/assets/{Home-DU-NVre0.js → Home-BP1LxGtp.js} +1 -1
  4. package/dist/web/assets/{PluginManager-44r9LaWV.js → PluginManager-BVbHUdey.js} +1 -1
  5. package/dist/web/assets/{ProjectList-DW_MEKRy.js → ProjectList-BQrz2FKo.js} +1 -1
  6. package/dist/web/assets/SessionList-CRGer64T.js +1 -0
  7. package/dist/web/assets/{SkillManager-DnzRfF_o.js → SkillManager-BhJKtWYM.js} +1 -1
  8. package/dist/web/assets/{WorkspaceManager-BuRpVJRU.js → WorkspaceManager-pc6tpF3m.js} +1 -1
  9. package/dist/web/assets/{icons-U50_Cfbe.js → icons-QFXzEgqM.js} +1 -1
  10. package/dist/web/assets/index-0isNzXNY.css +1 -0
  11. package/dist/web/assets/index-BsdIJwLf.js +2 -0
  12. package/dist/web/assets/{naive-ui-Cjh6GLto.js → naive-ui-DM897mIP.js} +1 -1
  13. package/dist/web/assets/{vendors--KP7Ux0s.js → vendors-DQA4Hw9v.js} +1 -1
  14. package/dist/web/assets/{vue-vendor-C5aMkRmm.js → vue-vendor-B5RqjEjQ.js} +1 -1
  15. package/dist/web/index.html +6 -6
  16. package/package.json +1 -1
  17. package/src/commands/daemon.js +13 -4
  18. package/src/config/paths.js +1 -0
  19. package/src/server/api/channels.js +4 -2
  20. package/src/server/api/codex-channels.js +3 -1
  21. package/src/server/api/codex-sessions.js +24 -1
  22. package/src/server/api/gemini-channels.js +4 -2
  23. package/src/server/api/opencode-channels.js +4 -2
  24. package/src/server/services/channel-balance.js +172 -14
  25. package/src/server/services/gemini-channels.js +8 -0
  26. package/src/server/services/opencode-channels.js +6 -0
  27. package/dist/web/assets/SessionList-B8upyqsp.js +0 -1
  28. package/dist/web/assets/index-BkLQex1R.css +0 -1
  29. package/dist/web/assets/index-gIbIMdSO.js +0 -2
@@ -1,7 +1,10 @@
1
1
  const http = require('http');
2
2
  const https = require('https');
3
3
  const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
4
6
  const { URL } = require('url');
7
+ const { PATHS } = require('../../config/paths');
5
8
  const { loadUIConfig } = require('./ui-config');
6
9
 
7
10
  const CACHE_TTL_MS = 5 * 60 * 1000;
@@ -16,6 +19,66 @@ const UNSUPPORTED_BALANCE_PLATFORMS = new Set(['dashscope', 'modelscope']);
16
19
 
17
20
  const balanceCache = new Map();
18
21
  const balanceStrategyCache = new Map();
22
+ let strategyCacheLoaded = false;
23
+
24
+ function getStrategyCacheFilePath() {
25
+ return PATHS.channelBalanceStrategies
26
+ || path.join(PATHS.storage || path.dirname(PATHS.channelModels), 'cache', 'channel-balance-strategies.json');
27
+ }
28
+
29
+ function loadPersistedStrategyCache() {
30
+ if (strategyCacheLoaded) return;
31
+ strategyCacheLoaded = true;
32
+
33
+ try {
34
+ const filePath = getStrategyCacheFilePath();
35
+ if (!fs.existsSync(filePath)) return;
36
+ const payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
+ const entries = payload && typeof payload === 'object' ? payload.entries : null;
38
+ if (!entries || typeof entries !== 'object') return;
39
+ Object.entries(entries).forEach(([key, value]) => {
40
+ const strategy = value?.strategy && typeof value.strategy === 'object' ? value.strategy : null;
41
+ if (strategy?.type) {
42
+ balanceStrategyCache.set(key, strategy);
43
+ }
44
+ });
45
+ } catch {
46
+ // A corrupt strategy cache should only cost a fresh probe, not break balance display.
47
+ }
48
+ }
49
+
50
+ function writePersistedStrategyCache() {
51
+ try {
52
+ const filePath = getStrategyCacheFilePath();
53
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
54
+ const entries = {};
55
+ balanceStrategyCache.forEach((strategy, key) => {
56
+ if (strategy?.type) {
57
+ entries[key] = {
58
+ strategy,
59
+ updatedAt: nowIso()
60
+ };
61
+ }
62
+ });
63
+ fs.writeFileSync(filePath, JSON.stringify({ version: 1, entries }, null, 2), 'utf8');
64
+ } catch {
65
+ // Persisting the optimization is best-effort; in-memory cache still works.
66
+ }
67
+ }
68
+
69
+ function rememberBalanceStrategy(key, strategy) {
70
+ if (!key || !strategy?.type) return;
71
+ loadPersistedStrategyCache();
72
+ balanceStrategyCache.set(key, strategy);
73
+ writePersistedStrategyCache();
74
+ }
75
+
76
+ function forgetBalanceStrategy(key) {
77
+ loadPersistedStrategyCache();
78
+ if (balanceStrategyCache.delete(key)) {
79
+ writePersistedStrategyCache();
80
+ }
81
+ }
19
82
 
20
83
  function nowIso(now = Date.now()) {
21
84
  return new Date(now).toISOString();
@@ -38,6 +101,29 @@ function resolveBalanceToken(channel = {}) {
38
101
  );
39
102
  }
40
103
 
104
+ function resolveBalanceUserId(channel = {}) {
105
+ const raw = channel.balanceUserId
106
+ ?? channel.platformUserId
107
+ ?? channel.newApiUserId
108
+ ?? null;
109
+ const parsed = parseFiniteNumber(raw);
110
+ if (parsed == null || parsed <= 0) return null;
111
+ return Math.trunc(parsed);
112
+ }
113
+
114
+ function buildUserIdHeaders(userId) {
115
+ if (!userId) return {};
116
+ const value = String(userId);
117
+ return {
118
+ 'New-Api-User': value,
119
+ 'Veloera-User': value,
120
+ 'voapi-user': value,
121
+ 'User-id': value,
122
+ 'Rix-Api-User': value,
123
+ 'neo-api-user': value
124
+ };
125
+ }
126
+
41
127
  function buildAuthHeaders(token, extra = {}) {
42
128
  return {
43
129
  Accept: 'application/json',
@@ -46,6 +132,38 @@ function buildAuthHeaders(token, extra = {}) {
46
132
  };
47
133
  }
48
134
 
135
+ function hasDedicatedBalanceToken(channel = {}) {
136
+ return !!String(
137
+ channel.balanceToken
138
+ || channel.balanceApiKey
139
+ || channel.balanceAuthToken
140
+ || ''
141
+ ).trim();
142
+ }
143
+
144
+ function buildCookieCandidates(token) {
145
+ const raw = stripBearer(token);
146
+ if (!raw) return [];
147
+
148
+ const candidates = [];
149
+ const normalized = raw.replace(/^cookie:\s*/i, '').trim();
150
+ if (!normalized) return [];
151
+ if (normalized.includes('=')) {
152
+ candidates.push(normalized);
153
+ }
154
+ candidates.push(`session=${normalized}`);
155
+ candidates.push(`token=${normalized}`);
156
+ return Array.from(new Set(candidates));
157
+ }
158
+
159
+ function shouldTryCookieAuth(token, channel = {}, hintedPlatform = null) {
160
+ const raw = stripBearer(token);
161
+ if (!raw) return false;
162
+ return hasDedicatedBalanceToken(channel)
163
+ || raw.includes('=')
164
+ || (hintedPlatform === 'anyrouter' && !isLikelyModelApiKey(raw));
165
+ }
166
+
49
167
  function isLikelyModelApiKey(token) {
50
168
  return /^sk-[a-z0-9]/i.test(stripBearer(token));
51
169
  }
@@ -887,7 +1005,9 @@ function buildBalanceProbeStrategies(baseUrl, channel = {}, token = '') {
887
1005
  type: 'hub-user-self',
888
1006
  baseUrl,
889
1007
  platform: HUB_PLATFORMS.has(hintedPlatform) ? hintedPlatform : null,
890
- hintedPlatform
1008
+ hintedPlatform,
1009
+ userId: resolveBalanceUserId(channel),
1010
+ allowCookieAuth: shouldTryCookieAuth(token, channel, hintedPlatform)
891
1011
  });
892
1012
  };
893
1013
  const addNewApiTokenUsageStrategy = () => {
@@ -991,8 +1111,29 @@ async function runSub2ApiUsageStrategy(strategy, token) {
991
1111
  return buildSub2ApiUsageSnapshot(payload);
992
1112
  }
993
1113
 
1114
+ async function fetchHubUserSelfWithCookie(baseUrl, token, platform, quotaUnit, userId) {
1115
+ for (const cookie of buildCookieCandidates(token)) {
1116
+ const payload = await tryRequestJson(joinUrl(baseUrl, '/api/user/self'), {
1117
+ headers: {
1118
+ Accept: 'application/json',
1119
+ Cookie: cookie,
1120
+ ...buildUserIdHeaders(userId)
1121
+ }
1122
+ });
1123
+ const snapshot = buildHubBalanceSnapshot(platform, {
1124
+ ...payload,
1125
+ data: payload?.data && typeof payload.data === 'object'
1126
+ ? { ...payload.data, quota_per_unit: quotaUnit || payload.data.quota_per_unit }
1127
+ : payload?.data
1128
+ });
1129
+ if (snapshot.visible) return snapshot;
1130
+ }
1131
+ return makeHiddenSnapshot(platform);
1132
+ }
1133
+
994
1134
  async function runHubUserSelfStrategy(strategy, token) {
995
- const headers = buildAuthHeaders(token);
1135
+ const userId = strategy.userId || null;
1136
+ const headers = buildAuthHeaders(token, buildUserIdHeaders(userId));
996
1137
  let platform = strategy.platform;
997
1138
  let quotaUnit = null;
998
1139
  if (!HUB_PLATFORMS.has(platform)) {
@@ -1002,13 +1143,21 @@ async function runHubUserSelfStrategy(strategy, token) {
1002
1143
  }
1003
1144
  if (!HUB_PLATFORMS.has(platform)) return { snapshot: makeHiddenSnapshot(platform), strategy };
1004
1145
 
1005
- const payload = await requestJson(joinUrl(strategy.baseUrl, '/api/user/self'), { headers });
1006
- const snapshot = buildHubBalanceSnapshot(platform, {
1007
- ...payload,
1008
- data: payload?.data && typeof payload.data === 'object'
1009
- ? { ...payload.data, quota_per_unit: quotaUnit || payload.data.quota_per_unit }
1010
- : payload?.data
1011
- });
1146
+ let snapshot = makeHiddenSnapshot(platform);
1147
+ try {
1148
+ const payload = await requestJson(joinUrl(strategy.baseUrl, '/api/user/self'), { headers });
1149
+ snapshot = buildHubBalanceSnapshot(platform, {
1150
+ ...payload,
1151
+ data: payload?.data && typeof payload.data === 'object'
1152
+ ? { ...payload.data, quota_per_unit: quotaUnit || payload.data.quota_per_unit }
1153
+ : payload?.data
1154
+ });
1155
+ } catch (error) {
1156
+ if (!strategy.allowCookieAuth) throw error;
1157
+ }
1158
+ if (!snapshot.visible && strategy.allowCookieAuth) {
1159
+ snapshot = await fetchHubUserSelfWithCookie(strategy.baseUrl, token, platform, quotaUnit, userId);
1160
+ }
1012
1161
  return {
1013
1162
  snapshot,
1014
1163
  strategy: {
@@ -1189,24 +1338,25 @@ async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
1189
1338
 
1190
1339
  let lastSnapshot = null;
1191
1340
  const token = resolveBalanceToken(channel);
1341
+ loadPersistedStrategyCache();
1192
1342
  const cachedStrategy = balanceStrategyCache.get(key);
1193
1343
  if (cachedStrategy) {
1194
1344
  const strategyResult = await tryBalanceStrategy(cachedStrategy, token, channel);
1195
1345
  if (strategyResult?.snapshot?.visible) {
1196
- balanceStrategyCache.set(key, strategyResult.strategy);
1346
+ rememberBalanceStrategy(key, strategyResult.strategy);
1197
1347
  balanceCache.set(key, {
1198
1348
  snapshot: strategyResult.snapshot,
1199
1349
  expiresAt: currentTime + CACHE_TTL_MS
1200
1350
  });
1201
1351
  return strategyResult.snapshot;
1202
1352
  }
1203
- balanceStrategyCache.delete(key);
1353
+ forgetBalanceStrategy(key);
1204
1354
  }
1205
1355
 
1206
1356
  for (const baseUrl of bases) {
1207
1357
  const result = await probeBalanceFromBase(baseUrl, channel, token);
1208
1358
  if (result?.snapshot?.visible) {
1209
- balanceStrategyCache.set(key, result.strategy);
1359
+ rememberBalanceStrategy(key, result.strategy);
1210
1360
  balanceCache.set(key, {
1211
1361
  snapshot: result.snapshot,
1212
1362
  expiresAt: currentTime + CACHE_TTL_MS
@@ -1247,6 +1397,10 @@ function getChannelsForSource(source) {
1247
1397
  return [];
1248
1398
  }
1249
1399
 
1400
+ function getEnabledBalanceChannels(source) {
1401
+ return getChannelsForSource(source).filter(channel => channel?.enabled !== false);
1402
+ }
1403
+
1250
1404
  function isBalanceDisplayEnabled() {
1251
1405
  try {
1252
1406
  return loadUIConfig().channelBalance?.showRemaining === true;
@@ -1283,7 +1437,7 @@ async function getChannelBalances(source, options = {}) {
1283
1437
  return { enabled: false, source: normalizedSource, balances: {} };
1284
1438
  }
1285
1439
 
1286
- const channels = getChannelsForSource(normalizedSource);
1440
+ const channels = getEnabledBalanceChannels(normalizedSource);
1287
1441
  const entries = await mapWithConcurrency(channels, 4, async (channel) => {
1288
1442
  const snapshot = await refreshChannelBalanceSnapshot(normalizedSource, channel, options);
1289
1443
  return [channel.id, snapshot];
@@ -1302,7 +1456,7 @@ async function refreshChannelBalance(source, channelId) {
1302
1456
  return { enabled: false, source: normalizedSource, channelId, balance: makeHiddenSnapshot() };
1303
1457
  }
1304
1458
 
1305
- const channels = getChannelsForSource(normalizedSource);
1459
+ const channels = getEnabledBalanceChannels(normalizedSource);
1306
1460
  const channel = channels.find(item => String(item.id) === String(channelId));
1307
1461
  if (!channel) {
1308
1462
  const error = new Error('Channel not found');
@@ -1322,6 +1476,7 @@ async function refreshChannelBalance(source, channelId) {
1322
1476
  function clearBalanceCache() {
1323
1477
  balanceCache.clear();
1324
1478
  balanceStrategyCache.clear();
1479
+ strategyCacheLoaded = false;
1325
1480
  }
1326
1481
 
1327
1482
  module.exports = {
@@ -1341,12 +1496,15 @@ module.exports = {
1341
1496
  buildSub2ApiUsageSnapshot,
1342
1497
  resolveStatusPlatform,
1343
1498
  resolveBalanceToken,
1499
+ resolveBalanceUserId,
1500
+ buildCookieCandidates,
1344
1501
  build88CodeApiBaseCandidates,
1345
1502
  buildOpenRouterApiBaseCandidates,
1346
1503
  buildSiliconFlowApiBaseCandidates,
1347
1504
  build88CodeUsageSnapshot,
1348
1505
  build88CodeSubscriptionSnapshot,
1349
1506
  buildBalanceProbeStrategies,
1507
+ getEnabledBalanceChannels,
1350
1508
  runBalanceStrategy,
1351
1509
  refreshChannelBalanceSnapshot,
1352
1510
  makeVisibleSnapshot,
@@ -114,6 +114,8 @@ function loadChannels() {
114
114
  enabled: ch.enabled !== false, // 默认启用
115
115
  weight: ch.weight || 1,
116
116
  maxConcurrency: ch.maxConcurrency || null,
117
+ balanceToken: ch.balanceToken || '',
118
+ balanceUserId: ch.balanceUserId || null,
117
119
  modelRedirects: ch.modelRedirects || [],
118
120
  speedTestModel: ch.speedTestModel || null,
119
121
  gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'gemini')
@@ -165,6 +167,8 @@ function initializeFromEnv() {
165
167
  enabled: true,
166
168
  weight: 1,
167
169
  maxConcurrency: null,
170
+ balanceToken: '',
171
+ balanceUserId: null,
168
172
  gatewaySourceType: 'gemini',
169
173
  createdAt: Date.now(),
170
174
  updatedAt: Date.now()
@@ -222,6 +226,8 @@ function createChannel(name, baseUrl, apiKey, model = 'gemini-2.5-pro', extraCon
222
226
  enabled: extraConfig.enabled !== false, // 默认启用
223
227
  weight: extraConfig.weight || 1,
224
228
  maxConcurrency: extraConfig.maxConcurrency || null,
229
+ balanceToken: extraConfig.balanceToken || '',
230
+ balanceUserId: extraConfig.balanceUserId || null,
225
231
  modelRedirects: extraConfig.modelRedirects || [],
226
232
  speedTestModel: extraConfig.speedTestModel || null,
227
233
  gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'gemini'),
@@ -269,6 +275,8 @@ function updateChannel(channelId, updates) {
269
275
  createdAt: oldChannel.createdAt, // 保持创建时间
270
276
  modelRedirects: updates.modelRedirects !== undefined ? updates.modelRedirects : (oldChannel.modelRedirects || []),
271
277
  speedTestModel: updates.speedTestModel !== undefined ? updates.speedTestModel : (oldChannel.speedTestModel || null),
278
+ balanceToken: updates.balanceToken !== undefined ? updates.balanceToken : (oldChannel.balanceToken || ''),
279
+ balanceUserId: updates.balanceUserId !== undefined ? updates.balanceUserId : (oldChannel.balanceUserId || null),
272
280
  gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'gemini'),
273
281
  updatedAt: Date.now()
274
282
  };
@@ -77,6 +77,8 @@ function loadChannels() {
77
77
  enabled: ch.enabled !== false,
78
78
  weight: ch.weight || 1,
79
79
  maxConcurrency: ch.maxConcurrency || null,
80
+ balanceToken: ch.balanceToken || '',
81
+ balanceUserId: ch.balanceUserId || null,
80
82
  modelRedirects: ch.modelRedirects || [],
81
83
  speedTestModel: ch.speedTestModel || null,
82
84
  wireApi: ch.wireApi || 'openai', // OpenCode 默认使用 OpenAI 兼容格式
@@ -155,6 +157,8 @@ function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
155
157
  enabled: extraConfig.enabled !== false,
156
158
  weight: extraConfig.weight || 1,
157
159
  maxConcurrency: extraConfig.maxConcurrency || null,
160
+ balanceToken: extraConfig.balanceToken || '',
161
+ balanceUserId: extraConfig.balanceUserId || null,
158
162
  modelRedirects: extraConfig.modelRedirects || [],
159
163
  speedTestModel: extraConfig.speedTestModel || null,
160
164
  model: extraConfig.model || null,
@@ -207,6 +211,8 @@ function updateChannel(channelId, updates) {
207
211
  createdAt: oldChannel.createdAt,
208
212
  modelRedirects: updates.modelRedirects !== undefined ? updates.modelRedirects : (oldChannel.modelRedirects || []),
209
213
  speedTestModel: updates.speedTestModel !== undefined ? updates.speedTestModel : (oldChannel.speedTestModel || null),
214
+ balanceToken: updates.balanceToken !== undefined ? updates.balanceToken : (oldChannel.balanceToken || ''),
215
+ balanceUserId: updates.balanceUserId !== undefined ? updates.balanceUserId : (oldChannel.balanceUserId || null),
210
216
  gatewaySourceType: normalizeGatewaySourceType(
211
217
  updates.gatewaySourceType !== undefined
212
218
  ? updates.gatewaySourceType
@@ -1 +0,0 @@
1
- import{a8 as e,a5 as a,f as s,r as l,w as t,h as n,K as i,c as o,o as u,a as r,X as d,Y as c,W as v,Z as f,N as p,p as m,$ as h,F as y,a6 as g,L as k,_,s as w,G as b,y as I,a0 as C,v as j}from"./vue-vendor-C5aMkRmm.js";import{d as z}from"./vendors--KP7Ux0s.js";import{_ as x,m as N,t as F,C as S,v as $,o as M,p as B,w as U,r as L}from"./index-gIbIMdSO.js";import{X as P,T,ac as A,ax as E,j as K,G as V,a2 as G,ay as H,p as O,h as R,V as q}from"./icons-U50_Cfbe.js";import{N as D,B as W,_ as X,c as Y,a as Z,G as J,q as Q,g as ee,x as ae,s as se,n as le,h as te,p as ne,k as ie}from"./naive-ui-Cjh6GLto.js";import"./markdown-BNJfEgCA.js";const oe={class:"session-list-container"},ue={class:"header"},re={class:"title-bar"},de={class:"title-section"},ce={class:"title-with-count"},ve={class:"header-actions"},fe={key:0,class:"loading-container"},pe=["onMouseenter","onClick"],me={key:0,class:"drag-handle"},he={class:"session-left"},ye={class:"session-icon"},ge={class:"session-info"},ke={class:"session-header"},_e={class:"session-title-row"},we={class:"session-title"},be={class:"session-meta"},Ie={class:"session-right"},Ce={class:"session-tags-area"},je={key:0,class:"session-actions"},ze={key:0,style:{"max-height":"70vh","overflow-y":"auto"}},xe={class:"search-result-header"},Ne={class:"search-result-title"},Fe="__full__",Se=x({__name:"SessionList",props:{projectName:{type:String,required:!0}},setup(x){const Se=x,$e=e(),Me=a(),Be=N(),{addFavorite:Ue,removeFavorite:Le,isFavorite:Pe}=F(),Te=s(()=>Me.meta.channel||"claude"),Ae=l(Se.projectName),Ee=s(()=>Ae.value||Se.projectName),Ke=l(""),Ve=l(!1),Ge=l(null),He=l(""),Oe=l(null),Re=l([]),qe=l(null),De=l(!1),We=l(null),Xe=l(!1),Ye=l(!1),Ze=l([]),Je=l(!1),Qe=l(!1),ea=l(null),aa=l(""),sa=l(Fe),la=l(!1),ta=l(!1),na=l([]),ia=l(!1),oa=l(""),ua=l(""),ra=l(null),da={group:{name:`${Te.value}-sessions`,pull:!1,put:!1},forceFallback:!0,fallbackOnBody:!1,fallbackTolerance:4,scroll:!0},ca=s(()=>{var e;return(null==(e=Be.currentProjectInfo)?void 0:e.displayName)||Se.projectName}),va=s(()=>{var e;return(null==(e=Be.currentProjectInfo)?void 0:e.fullPath)||Ee.value}),fa=s(()=>new Set(Ze.value)),pa=s(()=>Ze.value.length),ma=s(()=>Re.value.length>0&&Re.value.every(e=>fa.value.has(e.sessionId))),ha=s(()=>pa.value>0&&!ma.value);async function ya(){const e=await async function(){Be.projects.length||await Be.fetchProjects();const e=Be.projects.find(e=>e.name===Se.projectName);if(e)return Ae.value=e.name,e.name;const a=Be.projects.find(e=>e.displayName===Se.projectName||e.fullPath===Se.projectName);return a?(Ae.value=a.name,a.name!==Se.projectName&&await $e.replace({name:`${Te.value}-sessions`,params:{projectName:a.name}}),a.name):(Ae.value=Se.projectName,Se.projectName)}();await Be.fetchSessions(e)}t(()=>Be.sessionsWithAlias,e=>{Re.value=[...e];const a=new Set(e.map(e=>e.sessionId));Ze.value=Ze.value.filter(e=>a.has(e)),Ye.value&&0===e.length&&Ia()},{immediate:!0});const ga=s(()=>{if(!Ke.value)return Re.value;const e=Ke.value.toLowerCase();return Re.value.filter(a=>a.sessionId.toLowerCase().includes(e)||a.alias&&a.alias.toLowerCase().includes(e)||a.firstMessage&&a.firstMessage.toLowerCase().includes(e)||a.gitBranch&&a.gitBranch.toLowerCase().includes(e))});function ka(){const e=Me.meta.channel||"claude";$e.push({name:`${e}-projects`})}async function _a(){if(Ke.value){Xe.value=!0;try{const e=await $(Ee.value,Ke.value,35,Te.value);qe.value=e,De.value=!0}catch(e){M.error("搜索失败: "+e.message)}finally{Xe.value=!1}}}async function wa(){const e=Re.value.map(e=>e.sessionId);await Be.saveSessionOrder(e)}function ba(){Ye.value=!0,Oe.value=null}function Ia(){Ye.value=!1,Ze.value=[],Oe.value=null}function Ca(e){return fa.value.has(e)}function ja(e,a=!Ca(e)){const s=new Set(Ze.value);a?s.add(e):s.delete(e),Ze.value=Array.from(s)}function za(e){Ze.value=e?Re.value.map(e=>e.sessionId):[]}function xa(e){Ye.value?ja(e.sessionId):function(e){oa.value=e.sessionId,ua.value=e.alias||"",ia.value=!0,j(()=>{var e;null==(e=ra.value)||e.open()})}(e)}async function Na(){if(Ge.value)try{const e=Ge.value.sessionId;He.value?(await Be.setAlias(e,He.value),M.success("别名设置成功")):(await Be.deleteAlias(e),M.success("别名已删除")),Ve.value=!1,Ge.value=null,He.value=""}catch(e){M.error("操作失败: "+e.message)}}function Fa(){Qe.value=!1,ea.value=null,aa.value="",sa.value=Fe,na.value=[],la.value=!1,ta.value=!1}async function Sa(){var e;if(ea.value)try{ta.value=!0,await Be.forkSession(ea.value.sessionId,{afterUserMessageNumber:sa.value===Fe?null:sa.value,alias:(null==(e=aa.value)?void 0:e.trim())||void 0}),M.success("Fork 成功!"),Fa()}catch(a){M.error("Fork 失败: "+a.message)}finally{ta.value=!1}}function $a(e){M.error(e)}function Ma(e){const a=new Set((e||[]).filter(Boolean));0!==a.size&&(Ze.value=Ze.value.filter(e=>!a.has(e)),oa.value&&a.has(oa.value)&&(ia.value=!1,oa.value="",ua.value=""))}async function Ba(e){try{const{copyResult:a}=await L(Ee.value,e,Te.value);if("manual"===(null==a?void 0:a.method))return void M.warning("自动复制失败,已弹出手动复制框");M.success("启动命令已复制到剪贴板")}catch(a){M.error("复制失败: "+a.message)}}function Ua(){if(0===pa.value)return void M.warning("请先选择要删除的会话");const e=[...Ze.value];B.warning({title:"批量删除会话",content:`确定要删除选中的 ${e.length} 个会话吗?此操作不可恢复!`,positiveText:"确定删除",negativeText:"取消",onPositiveClick:async()=>{Je.value=!0;try{const a=await Be.deleteSessions(e);if(Ma(a.deletedSessionIds),0===a.failed.length)return M.success(`已删除 ${a.deletedSessionIds.length} 个会话`),void Ia();Ze.value=a.failed.map(e=>e.sessionId),a.deletedSessionIds.length>0?M.warning(`已删除 ${a.deletedSessionIds.length} 个会话,${a.failed.length} 个删除失败`):M.error(`删除失败: ${a.failed[0].error.message}`)}finally{Je.value=!1}}})}function La(e){if(!e)return"";return new Date(e).toLocaleString("zh-CN")}function Pa(e){if(!e)return"0 B";const a=1024;return e<a?e+" B":e<a*a?(e/a).toFixed(1)+" KB":(e/a/a).toFixed(1)+" MB"}function Ta(e,a){if(!a||!e)return e;const s=new RegExp(`(${a})`,"gi");return e.replace(s,'<mark style="background-color: #ffd700; padding: 2px 4px; border-radius: 2px; font-weight: 600;">$1</mark>')}function Aa(e,a=80){return e?e.length>a?e.substring(0,a)+"...":e:""}return t([Te,()=>Se.projectName],([e])=>{Ia(),Be.setChannel(e),ya()},{immediate:!0}),n(()=>{}),i(()=>{}),(e,a)=>(u(),o("div",oe,[r("div",ue,[r("div",re,[d(p(W),{size:"small",onClick:ka,class:"back-button"},{icon:f(()=>[d(p(D),{size:"18"},{default:f(()=>[d(p(P))]),_:1})]),_:1}),r("div",de,[r("div",ce,[d(p(X),null,{default:f(()=>[m(h(ca.value),1)]),_:1}),d(p(Y),{depth:"3",class:"session-count"},{default:f(()=>[m("("+h(p(Be).sessions.length)+" 个对话)",1)]),_:1}),d(p(Z),{size:"small",bordered:!1,type:"info",class:"total-size-tag"},{default:f(()=>[m(h(Pa(p(Be).totalSize)),1)]),_:1}),r("div",ve,[Ye.value?(u(),o(y,{key:0},[d(p(J),{checked:ma.value,indeterminate:ha.value,disabled:0===Re.value.length||Je.value,"onUpdate:checked":za},{default:f(()=>[...a[12]||(a[12]=[m(" 全选 ",-1)])]),_:1},8,["checked","indeterminate","disabled"]),d(p(Y),{depth:"3"},{default:f(()=>[m("已选 "+h(pa.value)+" 个",1)]),_:1}),d(p(W),{size:"small",type:"error",disabled:0===pa.value,loading:Je.value,onClick:Ua},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(T))]),_:1})]),default:f(()=>[a[13]||(a[13]=m(" 删除 ",-1))]),_:1},8,["disabled","loading"]),d(p(W),{size:"small",disabled:Je.value,onClick:Ia},{default:f(()=>[...a[14]||(a[14]=[m(" 完成 ",-1)])]),_:1},8,["disabled"])],64)):(u(),c(p(W),{key:1,size:"small",type:"error",secondary:"",onClick:ba},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(T))]),_:1})]),default:f(()=>[a[15]||(a[15]=m(" 管理 ",-1))]),_:1}))])]),d(p(Y),{depth:"3",class:"project-path"},{default:f(()=>[m(h(va.value),1)]),_:1})]),d(p(Q),{value:Ke.value,"onUpdate:value":a[0]||(a[0]=e=>Ke.value=e),placeholder:"搜索会话...",clearable:"",class:"search-input",onKeyup:g(_a,["enter"]),disabled:Xe.value},{prefix:f(()=>[d(p(D),null,{default:f(()=>[d(p(A))]),_:1})]),suffix:f(()=>[d(p(W),{text:"",onClick:_a,disabled:!Ke.value||Xe.value,loading:Xe.value},{default:f(()=>[...a[16]||(a[16]=[m(" 搜索 ",-1)])]),_:1},8,["disabled","loading"])]),_:1},8,["value","disabled"])])]),r("div",{class:"content",ref_key:"contentEl",ref:We},[p(Be).loading?(u(),o("div",fe,[d(p(ee),{size:"large"},{description:f(()=>[...a[17]||(a[17]=[m(" 加载会话列表... ",-1)])]),_:1})])):p(Be).error?(u(),c(p(ae),{key:1,type:"error",title:"加载失败",style:{"margin-bottom":"16px"}},{default:f(()=>[m(h(p(Be).error),1)]),_:1})):ga.value.length>0?(u(),c(p(z),I({key:2,modelValue:Re.value,"onUpdate:modelValue":a[3]||(a[3]=e=>Re.value=e),"item-key":"sessionId",class:"sessions-list",handle:".drag-handle",disabled:Ye.value||Je.value},da,{"ghost-class":"ghost","chosen-class":"chosen",animation:"200",onEnd:wa}),{item:f(({element:e})=>[r("div",{class:k(["session-item",{"session-item-selected":Ca(e.sessionId),"session-item-selection-mode":Ye.value}]),onMouseenter:a=>Oe.value=e.sessionId,onMouseleave:a[2]||(a[2]=e=>Oe.value=null),onClick:a=>xa(e)},[Ye.value?(u(),o("div",{key:1,class:"selection-checkbox",onClick:a[1]||(a[1]=_(()=>{},["stop"]))},[d(p(J),{checked:Ca(e.sessionId),"onUpdate:checked":a=>ja(e.sessionId,a)},null,8,["checked","onUpdate:checked"])])):(u(),o("div",me,[d(p(D),{size:"16",color:"#999"},{default:f(()=>[d(p(E))]),_:1})])),r("div",he,[r("div",ye,[d(p(D),{size:"24",color:"#18a058"},{default:f(()=>[d(p(K))]),_:1})]),r("div",ge,[r("div",ke,[r("div",_e,[r("span",we,h(e.alias?`${e.alias} (${e.sessionId.substring(0,8)})`:e.sessionId),1),e.forkedFrom?(u(),c(p(se),{key:0,placement:"top"},{trigger:f(()=>[d(p(Z),{size:"small",type:"warning",bordered:!1,style:{"margin-left":"8px",cursor:"help"}},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(V))]),_:1})]),default:f(()=>[a[18]||(a[18]=m(" Fork ",-1))]),_:1})]),default:f(()=>[m(" Fork 自: "+h(e.forkedFrom),1)]),_:2},1024)):v("",!0)])]),r("div",be,[d(p(Y),{depth:"3"},{default:f(()=>[m(h(La(e.mtime)),1)]),_:2},1024),d(p(Y),{depth:"3"},{default:f(()=>[...a[19]||(a[19]=[m("•",-1)])]),_:1}),d(p(Z),{size:"small",bordered:!1},{default:f(()=>[m(h(Pa(e.size)),1)]),_:2},1024)]),e.firstMessage?(u(),c(p(Y),{key:0,depth:"3",class:"session-message"},{default:f(()=>[m(h(Aa(e.firstMessage,80)),1)]),_:2},1024)):e.gitBranch||e.summary?v("",!0):(u(),c(p(Y),{key:1,depth:"3",class:"session-message session-message-empty"},{default:f(()=>[...a[20]||(a[20]=[m(" 暂未读取到对话内容 ",-1)])]),_:1}))])]),r("div",Ie,[r("div",Ce,[e.gitBranch?(u(),c(p(Z),{key:0,size:"small",type:"info",bordered:!1},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(V))]),_:1})]),default:f(()=>[m(" "+h(e.gitBranch),1)]),_:2},1024)):v("",!0)]),Ye.value?v("",!0):(u(),o("div",je,[d(p(le),null,{default:f(()=>[w(d(p(W),{size:"small",type:"error",onClick:_(a=>{return s=e.sessionId,void B.warning({title:"删除会话",content:"确定要删除这个会话吗?此操作不可恢复!",positiveText:"确定删除",negativeText:"取消",onPositiveClick:async()=>{try{await Be.deleteSession(s),Ma([s]),M.success("会话已删除")}catch(e){M.error("删除失败: "+e.message)}}});var s},["stop"])},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(T))]),_:1})]),default:f(()=>[a[21]||(a[21]=m(" 删除 ",-1))]),_:1},8,["onClick"]),[[b,Oe.value===e.sessionId]]),d(p(W),{size:"small",onClick:_(a=>function(e){Ge.value=e,He.value=e.alias||"",Ve.value=!0}(e),["stop"])},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(G))]),_:1})]),default:f(()=>[a[22]||(a[22]=m(" 别名 ",-1))]),_:1},8,["onClick"]),d(p(W),{size:"small",type:p(Pe)(Te.value,Ee.value,e.sessionId)?"warning":"default",onClick:_(a=>async function(e){const a=Te.value,s=Pe(a,Ee.value,e.sessionId);try{if(s)await Le(a,Ee.value,e.sessionId),M.success("已取消收藏");else{const s={sessionId:e.sessionId,projectName:Ee.value,projectDisplayName:ca.value,projectFullPath:va.value,alias:e.alias||"",firstMessage:e.firstMessage||"",mtime:e.mtime,size:e.size,gitBranch:e.gitBranch||"",forkedFrom:e.forkedFrom||""};await Ue(a,s),M.success("已添加到收藏")}}catch(l){M.error("操作失败: "+l.message)}}(e),["stop"])},{icon:f(()=>[d(p(D),null,{default:f(()=>[p(Pe)(Te.value,Ee.value,e.sessionId)?(u(),c(p(H),{key:0})):(u(),c(p(O),{key:1}))]),_:2},1024)]),default:f(()=>[m(" "+h(p(Pe)(Te.value,Ee.value,e.sessionId)?"已收藏":"收藏"),1)]),_:2},1032,["type","onClick"]),"opencode"!==Te.value?(u(),c(p(W),{key:0,size:"small",onClick:_(a=>async function(e){if("opencode"!==Te.value)try{const a=Re.value.find(a=>a.sessionId===e);ea.value=a||{sessionId:e},aa.value="",sa.value=Fe,na.value=[{label:"完整会话末尾(保留全部消息)",value:Fe}],Qe.value=!0,la.value=!0;const s=await U(Ee.value,e,Te.value),l=Array.isArray(null==s?void 0:s.items)?s.items:[];na.value=[{label:"完整会话末尾(保留全部消息)",value:Fe},...l.map(e=>({label:`保留到第 ${e.userMessageNumber} 条用户消息这一轮 · ${e.preview}`,value:e.userMessageNumber}))]}catch(a){M.error("加载 Fork 位置失败: "+a.message),Fa()}finally{la.value=!1}else M.warning("OpenCode 当前不支持该 Fork 操作")}(e.sessionId),["stop"])},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(V))]),_:1})]),default:f(()=>[a[23]||(a[23]=m(" Fork ",-1))]),_:1},8,["onClick"])):v("",!0),d(p(W),{size:"small",type:"primary",onClick:_(a=>Ba(e.sessionId),["stop"])},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(R))]),_:1})]),default:f(()=>[a[24]||(a[24]=m(" 使用对话 ",-1))]),_:1},8,["onClick"])]),_:2},1024)]))])],42,pe)]),_:1},16,["modelValue","disabled"])):(u(),c(p(te),{key:3,description:"没有找到会话",style:{"margin-top":"60px"}},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(q))]),_:1})]),_:1}))],512),d(p(ne),{show:Ve.value,"onUpdate:show":a[6]||(a[6]=e=>Ve.value=e),preset:"dialog",title:"设置别名"},{action:f(()=>[d(p(le),null,{default:f(()=>[d(p(W),{onClick:a[5]||(a[5]=e=>Ve.value=!1)},{default:f(()=>[...a[25]||(a[25]=[m("取消",-1)])]),_:1}),d(p(W),{type:"primary",onClick:Na},{default:f(()=>[...a[26]||(a[26]=[m("确定",-1)])]),_:1})]),_:1})]),default:f(()=>[d(p(Q),{value:He.value,"onUpdate:value":a[4]||(a[4]=e=>He.value=e),placeholder:"输入别名(留空删除)",onKeyup:g(Na,["enter"])},null,8,["value"])]),_:1},8,["show"]),d(p(ne),{show:Qe.value,"onUpdate:show":a[9]||(a[9]=e=>Qe.value=e),preset:"dialog",title:"Fork 对话"},{action:f(()=>[d(p(le),null,{default:f(()=>[d(p(W),{disabled:ta.value,onClick:Fa},{default:f(()=>[...a[28]||(a[28]=[m("取消",-1)])]),_:1},8,["disabled"]),d(p(W),{type:"primary",loading:ta.value,onClick:Sa},{default:f(()=>[...a[29]||(a[29]=[m(" 确认 Fork ",-1)])]),_:1},8,["loading"])]),_:1})]),default:f(()=>[d(p(le),{vertical:"",size:14},{default:f(()=>[d(p(Y),{depth:"3"},{default:f(()=>[...a[27]||(a[27]=[m(" 可选择保留到第几条用户消息所在轮次;会包含这条消息后 AI 的完整输出。 ",-1)])]),_:1}),d(p(ie),{value:sa.value,"onUpdate:value":a[7]||(a[7]=e=>sa.value=e),options:na.value,loading:la.value,clearable:"",placeholder:"选择保留到哪一轮(默认完整会话)"},null,8,["value","options","loading"]),d(p(Q),{value:aa.value,"onUpdate:value":a[8]||(a[8]=e=>aa.value=e),placeholder:"输入新会话别名(可选)",maxlength:"120"},null,8,["value"])]),_:1})]),_:1},8,["show"]),d(p(ne),{show:De.value,"onUpdate:show":a[10]||(a[10]=e=>De.value=e),preset:"card",title:"搜索结果",style:{width:"1200px"}},{default:f(()=>[qe.value?(u(),o("div",ze,[d(p(ae),{type:"info",style:{"margin-bottom":"16px"}},{default:f(()=>[m(' 关键词 "'+h(qe.value.keyword)+'" 共找到 '+h(qe.value.totalMatches)+" 处匹配 ",1)]),_:1}),(u(!0),o(y,null,C(qe.value.sessions,e=>(u(),o("div",{key:e.sessionId,class:"search-result-item"},[r("div",xe,[r("div",Ne,[d(p(Y),{strong:""},{default:f(()=>[m(h(e.alias?`${e.alias} (${e.sessionId.substring(0,8)})`:e.sessionId.substring(0,8)),1)]),_:2},1024),d(p(Z),{size:"small",bordered:!1},{default:f(()=>[m(h(e.matchCount)+" 个匹配",1)]),_:2},1024)]),d(p(W),{size:"small",type:"primary",onClick:a=>Ba(e.sessionId)},{icon:f(()=>[d(p(D),null,{default:f(()=>[d(p(R))]),_:1})]),default:f(()=>[a[30]||(a[30]=m(" 使用对话 ",-1))]),_:1},8,["onClick"])]),(u(!0),o(y,null,C(e.matches,(e,a)=>(u(),o("div",{key:a,class:"search-match"},[d(p(Z),{size:"tiny",type:"user"===e.role?"info":"success",bordered:!1},{default:f(()=>[m(h("user"===e.role?"用户":"助手"),1)]),_:2},1032,["type"]),d(p(Y),{depth:"3",class:"search-match-text",innerHTML:Ta(e.context,qe.value.keyword)},null,8,["innerHTML"])]))),128))]))),128)),0===qe.value.sessions.length?(u(),c(p(te),{key:0,description:"没有找到匹配的内容"})):v("",!0)])):v("",!0)]),_:1},8,["show"]),oa.value?(u(),c(S,{key:0,ref_key:"chatHistoryRef",ref:ra,show:ia.value,"onUpdate:show":a[11]||(a[11]=e=>ia.value=e),"project-name":Ee.value,"session-id":oa.value,"session-alias":ua.value,channel:Te.value,onError:$a},null,8,["show","project-name","session-id","session-alias","channel"])):v("",!0)]))}},[["__scopeId","data-v-528553dd"]]);export{Se as default};