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.
- package/dist/web/assets/{Analytics-BTmqXkR-.js → Analytics-CcGKjDkP.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-z3w0kyNl.js → ConfigTemplates-DPOa1w5D.js} +1 -1
- package/dist/web/assets/{Home-DU-NVre0.js → Home-BP1LxGtp.js} +1 -1
- package/dist/web/assets/{PluginManager-44r9LaWV.js → PluginManager-BVbHUdey.js} +1 -1
- package/dist/web/assets/{ProjectList-DW_MEKRy.js → ProjectList-BQrz2FKo.js} +1 -1
- package/dist/web/assets/SessionList-CRGer64T.js +1 -0
- package/dist/web/assets/{SkillManager-DnzRfF_o.js → SkillManager-BhJKtWYM.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-BuRpVJRU.js → WorkspaceManager-pc6tpF3m.js} +1 -1
- package/dist/web/assets/{icons-U50_Cfbe.js → icons-QFXzEgqM.js} +1 -1
- package/dist/web/assets/index-0isNzXNY.css +1 -0
- package/dist/web/assets/index-BsdIJwLf.js +2 -0
- package/dist/web/assets/{naive-ui-Cjh6GLto.js → naive-ui-DM897mIP.js} +1 -1
- package/dist/web/assets/{vendors--KP7Ux0s.js → vendors-DQA4Hw9v.js} +1 -1
- package/dist/web/assets/{vue-vendor-C5aMkRmm.js → vue-vendor-B5RqjEjQ.js} +1 -1
- package/dist/web/index.html +6 -6
- package/package.json +1 -1
- package/src/commands/daemon.js +13 -4
- package/src/config/paths.js +1 -0
- package/src/server/api/channels.js +4 -2
- package/src/server/api/codex-channels.js +3 -1
- package/src/server/api/codex-sessions.js +24 -1
- package/src/server/api/gemini-channels.js +4 -2
- package/src/server/api/opencode-channels.js +4 -2
- package/src/server/services/channel-balance.js +172 -14
- package/src/server/services/gemini-channels.js +8 -0
- package/src/server/services/opencode-channels.js +6 -0
- package/dist/web/assets/SessionList-B8upyqsp.js +0 -1
- package/dist/web/assets/index-BkLQex1R.css +0 -1
- 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
|
|
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
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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};
|