codexmate 0.0.15 → 0.0.17
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 +13 -3
- package/README.md +13 -3
- package/cli.js +683 -15
- package/package.json +2 -2
- package/web-ui/app.js +219 -46
- package/web-ui/index.html +15 -1
- package/web-ui/logic.mjs +113 -13
- package/web-ui/styles.css +203 -25
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
normalizeSessionPathFilter,
|
|
13
13
|
buildSessionFilterCacheState,
|
|
14
14
|
buildSessionTimelineNodes,
|
|
15
|
-
normalizeSessionMessageRole
|
|
15
|
+
normalizeSessionMessageRole,
|
|
16
|
+
runLatestOnlyQueue,
|
|
17
|
+
shouldForceCompactLayoutMode
|
|
16
18
|
} from './logic.mjs';
|
|
17
19
|
import {
|
|
18
20
|
CONFIG_MODE_SET,
|
|
@@ -170,6 +172,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
170
172
|
claudeSpeedLoading: {},
|
|
171
173
|
claudeShareLoading: {},
|
|
172
174
|
providerShareLoading: {},
|
|
175
|
+
providerSwitchInProgress: false,
|
|
176
|
+
pendingProviderSwitch: '',
|
|
173
177
|
installPackageManager: 'npm',
|
|
174
178
|
installCommandAction: 'install',
|
|
175
179
|
installRegistryPreset: 'default',
|
|
@@ -291,11 +295,13 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
291
295
|
proxyStarting: false,
|
|
292
296
|
proxyStopping: false,
|
|
293
297
|
proxyApplying: false,
|
|
294
|
-
showProxyAdvanced: false
|
|
298
|
+
showProxyAdvanced: false,
|
|
299
|
+
forceCompactLayout: false
|
|
295
300
|
}
|
|
296
301
|
},
|
|
297
302
|
mounted() {
|
|
298
303
|
this.initSessionStandalone();
|
|
304
|
+
this.updateCompactLayoutMode();
|
|
299
305
|
const savedSessionYolo = localStorage.getItem('codexmateSessionResumeYolo');
|
|
300
306
|
if (savedSessionYolo === '0' || savedSessionYolo === 'false') {
|
|
301
307
|
this.sessionResumeWithYolo = false;
|
|
@@ -343,6 +349,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
343
349
|
this.cancelSessionTimelineSync();
|
|
344
350
|
this.disconnectSessionPreviewHeaderResizeObserver();
|
|
345
351
|
window.removeEventListener('resize', this.onWindowResize);
|
|
352
|
+
this.applyCompactLayoutClass(false);
|
|
346
353
|
this.sessionPreviewScrollEl = null;
|
|
347
354
|
this.sessionPreviewContainerEl = null;
|
|
348
355
|
this.sessionPreviewHeaderEl = null;
|
|
@@ -542,7 +549,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
542
549
|
}
|
|
543
550
|
},
|
|
544
551
|
|
|
545
|
-
async loadModelsForProvider(providerName) {
|
|
552
|
+
async loadModelsForProvider(providerName, options = {}) {
|
|
553
|
+
const silentError = !!options.silentError;
|
|
546
554
|
this.codexModelsLoading = true;
|
|
547
555
|
if (!providerName) {
|
|
548
556
|
this.models = [];
|
|
@@ -560,7 +568,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
560
568
|
return;
|
|
561
569
|
}
|
|
562
570
|
if (res.error) {
|
|
563
|
-
|
|
571
|
+
if (!silentError) {
|
|
572
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
573
|
+
}
|
|
564
574
|
this.models = [];
|
|
565
575
|
this.modelsSource = 'error';
|
|
566
576
|
this.modelsHasCurrent = true;
|
|
@@ -571,7 +581,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
571
581
|
this.modelsSource = res.source || 'remote';
|
|
572
582
|
this.modelsHasCurrent = !!this.currentModel && list.includes(this.currentModel);
|
|
573
583
|
} catch (e) {
|
|
574
|
-
|
|
584
|
+
if (!silentError) {
|
|
585
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
586
|
+
}
|
|
575
587
|
this.models = [];
|
|
576
588
|
this.modelsSource = 'error';
|
|
577
589
|
this.modelsHasCurrent = true;
|
|
@@ -603,15 +615,58 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
603
615
|
return findDuplicateClaudeConfigName(this.claudeConfigs, config);
|
|
604
616
|
},
|
|
605
617
|
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
618
|
+
mergeClaudeConfig(existing = {}, updates = {}) {
|
|
619
|
+
const previous = this.normalizeClaudeConfig(existing);
|
|
620
|
+
const next = this.normalizeClaudeConfig({ ...existing, ...updates });
|
|
621
|
+
const externalCredentialType = next.apiKey
|
|
622
|
+
? ''
|
|
623
|
+
: (next.externalCredentialType || previous.externalCredentialType || '');
|
|
624
|
+
return {
|
|
625
|
+
apiKey: next.apiKey,
|
|
626
|
+
baseUrl: next.baseUrl,
|
|
627
|
+
model: next.model || previous.model || 'glm-4.7',
|
|
628
|
+
hasKey: !!(next.apiKey || externalCredentialType),
|
|
629
|
+
externalCredentialType
|
|
630
|
+
};
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
buildClaudeImportedConfigName(baseUrl) {
|
|
634
|
+
const normalizedUrl = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
635
|
+
if (!normalizedUrl) return '导入配置';
|
|
636
|
+
try {
|
|
637
|
+
const parsed = new URL(normalizedUrl);
|
|
638
|
+
const host = typeof parsed.host === 'string' ? parsed.host.trim() : '';
|
|
639
|
+
if (host) return `导入-${host}`;
|
|
640
|
+
} catch (_) {
|
|
641
|
+
// keep generic fallback name
|
|
642
|
+
}
|
|
643
|
+
return '导入配置';
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
ensureClaudeConfigFromSettings(env = {}) {
|
|
647
|
+
const normalized = this.normalizeClaudeSettingsEnv(env);
|
|
648
|
+
const hasCredential = !!(normalized.apiKey || normalized.authToken || normalized.useKey);
|
|
649
|
+
if (!normalized.baseUrl || !hasCredential) return '';
|
|
650
|
+
|
|
651
|
+
const duplicateName = this.findDuplicateClaudeConfigName(normalized);
|
|
652
|
+
if (duplicateName) return duplicateName;
|
|
653
|
+
|
|
654
|
+
const preferredName = this.buildClaudeImportedConfigName(normalized.baseUrl);
|
|
655
|
+
let candidateName = preferredName;
|
|
656
|
+
let suffix = 2;
|
|
657
|
+
while (this.claudeConfigs[candidateName]) {
|
|
658
|
+
candidateName = `${preferredName}-${suffix}`;
|
|
659
|
+
suffix += 1;
|
|
613
660
|
}
|
|
661
|
+
|
|
662
|
+
this.claudeConfigs[candidateName] = this.mergeClaudeConfig({}, normalized);
|
|
663
|
+
this.saveClaudeConfigs();
|
|
664
|
+
return candidateName;
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
async refreshClaudeSelectionFromSettings(options = {}) {
|
|
614
668
|
const silent = !!options.silent;
|
|
669
|
+
const silentModelError = !!options.silentModelError || silent;
|
|
615
670
|
try {
|
|
616
671
|
const res = await api('get-claude-settings');
|
|
617
672
|
if (res && res.error) {
|
|
@@ -625,7 +680,18 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
625
680
|
if (this.currentClaudeConfig !== matchName) {
|
|
626
681
|
this.currentClaudeConfig = matchName;
|
|
627
682
|
}
|
|
628
|
-
this.refreshClaudeModelContext();
|
|
683
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const importedName = this.ensureClaudeConfigFromSettings((res && res.env) || {});
|
|
687
|
+
if (importedName) {
|
|
688
|
+
if (this.currentClaudeConfig !== importedName) {
|
|
689
|
+
this.currentClaudeConfig = importedName;
|
|
690
|
+
}
|
|
691
|
+
this.refreshClaudeModelContext({ silentError: silentModelError });
|
|
692
|
+
if (!silent) {
|
|
693
|
+
this.showMessage(`检测到外部 Claude 配置,已自动导入:${importedName}`, 'success');
|
|
694
|
+
}
|
|
629
695
|
return;
|
|
630
696
|
}
|
|
631
697
|
this.currentClaudeConfig = '';
|
|
@@ -649,9 +715,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
649
715
|
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
650
716
|
},
|
|
651
717
|
|
|
652
|
-
refreshClaudeModelContext() {
|
|
718
|
+
refreshClaudeModelContext(options = {}) {
|
|
653
719
|
this.syncClaudeModelFromConfig();
|
|
654
|
-
this.loadClaudeModels();
|
|
720
|
+
return this.loadClaudeModels(options);
|
|
655
721
|
},
|
|
656
722
|
|
|
657
723
|
resetClaudeModelsState() {
|
|
@@ -666,7 +732,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
666
732
|
this.claudeModelsHasCurrent = !!currentModel && this.claudeModels.includes(currentModel);
|
|
667
733
|
},
|
|
668
734
|
|
|
669
|
-
async loadClaudeModels() {
|
|
735
|
+
async loadClaudeModels(options = {}) {
|
|
736
|
+
const silentError = !!options.silentError;
|
|
670
737
|
const config = this.getCurrentClaudeConfig();
|
|
671
738
|
if (!config) {
|
|
672
739
|
this.resetClaudeModelsState();
|
|
@@ -674,11 +741,20 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
674
741
|
}
|
|
675
742
|
const baseUrl = (config.baseUrl || '').trim();
|
|
676
743
|
const apiKey = (config.apiKey || '').trim();
|
|
744
|
+
const externalCredentialType = typeof config.externalCredentialType === 'string'
|
|
745
|
+
? config.externalCredentialType.trim()
|
|
746
|
+
: '';
|
|
677
747
|
|
|
678
748
|
if (!baseUrl) {
|
|
679
749
|
this.resetClaudeModelsState();
|
|
680
750
|
return;
|
|
681
751
|
}
|
|
752
|
+
if (!apiKey && externalCredentialType) {
|
|
753
|
+
this.claudeModels = [];
|
|
754
|
+
this.claudeModelsSource = 'unlimited';
|
|
755
|
+
this.claudeModelsHasCurrent = true;
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
682
758
|
|
|
683
759
|
this.claudeModelsLoading = true;
|
|
684
760
|
try {
|
|
@@ -690,7 +766,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
690
766
|
return;
|
|
691
767
|
}
|
|
692
768
|
if (res.error) {
|
|
693
|
-
|
|
769
|
+
if (!silentError) {
|
|
770
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
771
|
+
}
|
|
694
772
|
this.claudeModels = [];
|
|
695
773
|
this.claudeModelsSource = 'error';
|
|
696
774
|
this.claudeModelsHasCurrent = true;
|
|
@@ -701,7 +779,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
701
779
|
this.claudeModelsSource = res.source || 'remote';
|
|
702
780
|
this.updateClaudeModelsCurrent();
|
|
703
781
|
} catch (e) {
|
|
704
|
-
|
|
782
|
+
if (!silentError) {
|
|
783
|
+
this.showMessage('获取模型列表失败', 'error');
|
|
784
|
+
}
|
|
705
785
|
this.claudeModels = [];
|
|
706
786
|
this.claudeModelsSource = 'error';
|
|
707
787
|
this.claudeModelsHasCurrent = true;
|
|
@@ -996,6 +1076,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
996
1076
|
const name = typeof payload.name === 'string' ? payload.name.trim() : '';
|
|
997
1077
|
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
|
|
998
1078
|
const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
|
|
1079
|
+
const model = typeof payload.model === 'string' ? payload.model.trim() : '';
|
|
999
1080
|
if (!name || !baseUrl) return '';
|
|
1000
1081
|
|
|
1001
1082
|
const nameArg = this.quoteShellArg(name);
|
|
@@ -1005,7 +1086,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1005
1086
|
const addCmd = apiKey
|
|
1006
1087
|
? `codexmate add ${nameArg} ${urlArg} ${keyArg}`
|
|
1007
1088
|
: `codexmate add ${nameArg} ${urlArg}`;
|
|
1008
|
-
|
|
1089
|
+
const modelCmd = model ? ` && codexmate use ${this.quoteShellArg(model)}` : '';
|
|
1090
|
+
return `${addCmd} && ${switchCmd}${modelCmd}`;
|
|
1009
1091
|
},
|
|
1010
1092
|
|
|
1011
1093
|
buildClaudeShareCommand(payload) {
|
|
@@ -1399,9 +1481,59 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1399
1481
|
this.scheduleSessionTimelineSync();
|
|
1400
1482
|
},
|
|
1401
1483
|
onWindowResize() {
|
|
1484
|
+
this.updateCompactLayoutMode();
|
|
1402
1485
|
this.updateSessionTimelineOffset();
|
|
1403
1486
|
this.scheduleSessionTimelineSync();
|
|
1404
1487
|
},
|
|
1488
|
+
shouldForceCompactLayout() {
|
|
1489
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
1490
|
+
return false;
|
|
1491
|
+
}
|
|
1492
|
+
const doc = typeof document !== 'undefined' ? document : null;
|
|
1493
|
+
const viewportWidth = Math.max(
|
|
1494
|
+
0,
|
|
1495
|
+
Number(window.innerWidth || 0),
|
|
1496
|
+
Number(doc && doc.documentElement ? doc.documentElement.clientWidth : 0)
|
|
1497
|
+
);
|
|
1498
|
+
const screenWidth = Number(window.screen && window.screen.width ? window.screen.width : 0);
|
|
1499
|
+
const screenHeight = Number(window.screen && window.screen.height ? window.screen.height : 0);
|
|
1500
|
+
const shortEdge = screenWidth > 0 && screenHeight > 0
|
|
1501
|
+
? Math.min(screenWidth, screenHeight)
|
|
1502
|
+
: 0;
|
|
1503
|
+
const touchPoints = Number(navigator.maxTouchPoints || 0);
|
|
1504
|
+
const userAgent = String(navigator.userAgent || '');
|
|
1505
|
+
const isMobileUa = /(Android|iPhone|iPad|iPod|Mobile)/i.test(userAgent);
|
|
1506
|
+
let coarsePointer = false;
|
|
1507
|
+
let noHover = false;
|
|
1508
|
+
try {
|
|
1509
|
+
coarsePointer = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
|
|
1510
|
+
} catch (_) {}
|
|
1511
|
+
try {
|
|
1512
|
+
noHover = !!(window.matchMedia && window.matchMedia('(hover: none)').matches);
|
|
1513
|
+
} catch (_) {}
|
|
1514
|
+
return shouldForceCompactLayoutMode({
|
|
1515
|
+
viewportWidth,
|
|
1516
|
+
screenWidth,
|
|
1517
|
+
screenHeight,
|
|
1518
|
+
shortEdge,
|
|
1519
|
+
maxTouchPoints: touchPoints,
|
|
1520
|
+
userAgent,
|
|
1521
|
+
isMobileUa,
|
|
1522
|
+
coarsePointer,
|
|
1523
|
+
noHover
|
|
1524
|
+
});
|
|
1525
|
+
},
|
|
1526
|
+
applyCompactLayoutClass(enabled) {
|
|
1527
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
document.body.classList.toggle('force-compact', !!enabled);
|
|
1531
|
+
},
|
|
1532
|
+
updateCompactLayoutMode() {
|
|
1533
|
+
const enabled = this.shouldForceCompactLayout();
|
|
1534
|
+
this.forceCompactLayout = enabled;
|
|
1535
|
+
this.applyCompactLayoutClass(enabled);
|
|
1536
|
+
},
|
|
1405
1537
|
syncSessionTimelineActiveFromScroll() {
|
|
1406
1538
|
const nodes = Array.isArray(this.sessionTimelineNodes) ? this.sessionTimelineNodes : [];
|
|
1407
1539
|
if (!nodes.length) {
|
|
@@ -1751,17 +1883,75 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1751
1883
|
}
|
|
1752
1884
|
},
|
|
1753
1885
|
|
|
1754
|
-
async
|
|
1886
|
+
async quickSwitchProvider(name) {
|
|
1887
|
+
const target = String(name || '').trim();
|
|
1888
|
+
if (!target || target === this.pendingProviderSwitch) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (!this.providerSwitchInProgress && target === this.currentProvider) {
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
await this.switchProvider(target);
|
|
1895
|
+
},
|
|
1896
|
+
|
|
1897
|
+
async waitForCodexApplyIdle(maxWaitMs = 20000) {
|
|
1898
|
+
const startedAt = Date.now();
|
|
1899
|
+
while (this.codexApplying) {
|
|
1900
|
+
if ((Date.now() - startedAt) > maxWaitMs) {
|
|
1901
|
+
throw new Error('等待配置应用完成超时');
|
|
1902
|
+
}
|
|
1903
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
|
|
1907
|
+
async performProviderSwitch(name) {
|
|
1908
|
+
await this.waitForCodexApplyIdle();
|
|
1755
1909
|
this.currentProvider = name;
|
|
1756
1910
|
await this.loadModelsForProvider(name);
|
|
1757
1911
|
if (this.modelsSource === 'remote' && this.models.length > 0 && !this.models.includes(this.currentModel)) {
|
|
1758
1912
|
this.currentModel = this.models[0];
|
|
1759
1913
|
}
|
|
1760
1914
|
if (getProviderConfigModeMeta(this.configMode)) {
|
|
1915
|
+
await this.waitForCodexApplyIdle();
|
|
1761
1916
|
await this.applyCodexConfigDirect({ silent: true });
|
|
1762
1917
|
}
|
|
1763
1918
|
},
|
|
1764
1919
|
|
|
1920
|
+
async switchProvider(name) {
|
|
1921
|
+
const target = String(name || '').trim();
|
|
1922
|
+
if (!target) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (this.providerSwitchInProgress) {
|
|
1926
|
+
this.pendingProviderSwitch = target;
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
this.providerSwitchInProgress = true;
|
|
1930
|
+
let lastError = '';
|
|
1931
|
+
try {
|
|
1932
|
+
this.pendingProviderSwitch = '';
|
|
1933
|
+
const result = await runLatestOnlyQueue(target, {
|
|
1934
|
+
perform: async (queuedTarget) => {
|
|
1935
|
+
await this.performProviderSwitch(queuedTarget);
|
|
1936
|
+
},
|
|
1937
|
+
consumePending: () => {
|
|
1938
|
+
const queued = this.pendingProviderSwitch;
|
|
1939
|
+
this.pendingProviderSwitch = '';
|
|
1940
|
+
return queued;
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
if (result && typeof result.lastError === 'string') {
|
|
1944
|
+
lastError = result.lastError;
|
|
1945
|
+
}
|
|
1946
|
+
} finally {
|
|
1947
|
+
this.providerSwitchInProgress = false;
|
|
1948
|
+
this.pendingProviderSwitch = '';
|
|
1949
|
+
}
|
|
1950
|
+
if (lastError) {
|
|
1951
|
+
this.showMessage(lastError, 'error');
|
|
1952
|
+
}
|
|
1953
|
+
},
|
|
1954
|
+
|
|
1765
1955
|
async onModelChange() {
|
|
1766
1956
|
await this.applyCodexConfigDirect();
|
|
1767
1957
|
},
|
|
@@ -1963,8 +2153,8 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
1963
2153
|
}
|
|
1964
2154
|
},
|
|
1965
2155
|
|
|
1966
|
-
...createSkillsMethods({ api }),
|
|
1967
|
-
|
|
2156
|
+
...createSkillsMethods({ api }),
|
|
2157
|
+
|
|
1968
2158
|
async openOpenclawAgentsEditor() {
|
|
1969
2159
|
this.setAgentsModalContext('openclaw');
|
|
1970
2160
|
this.agentsLoading = true;
|
|
@@ -2341,12 +2531,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2341
2531
|
}
|
|
2342
2532
|
const existing = this.claudeConfigs[name] || {};
|
|
2343
2533
|
this.currentClaudeModel = model;
|
|
2344
|
-
this.claudeConfigs[name] = {
|
|
2345
|
-
apiKey: existing.apiKey || '',
|
|
2346
|
-
baseUrl: existing.baseUrl || '',
|
|
2347
|
-
model: model,
|
|
2348
|
-
hasKey: !!existing.apiKey
|
|
2349
|
-
};
|
|
2534
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig(existing, { model });
|
|
2350
2535
|
this.saveClaudeConfigs();
|
|
2351
2536
|
this.updateClaudeModelsCurrent();
|
|
2352
2537
|
if (!this.claudeConfigs[name].apiKey) {
|
|
@@ -2373,12 +2558,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2373
2558
|
|
|
2374
2559
|
updateConfig() {
|
|
2375
2560
|
const name = this.editingConfig.name;
|
|
2376
|
-
this.claudeConfigs[name] =
|
|
2377
|
-
apiKey: this.editingConfig.apiKey,
|
|
2378
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
2379
|
-
model: this.editingConfig.model,
|
|
2380
|
-
hasKey: !!this.editingConfig.apiKey
|
|
2381
|
-
};
|
|
2561
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
|
|
2382
2562
|
this.saveClaudeConfigs();
|
|
2383
2563
|
this.showMessage('操作成功', 'success');
|
|
2384
2564
|
this.closeEditConfigModal();
|
|
@@ -2394,12 +2574,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2394
2574
|
|
|
2395
2575
|
async saveAndApplyConfig() {
|
|
2396
2576
|
const name = this.editingConfig.name;
|
|
2397
|
-
this.claudeConfigs[name] =
|
|
2398
|
-
apiKey: this.editingConfig.apiKey,
|
|
2399
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
2400
|
-
model: this.editingConfig.model,
|
|
2401
|
-
hasKey: !!this.editingConfig.apiKey
|
|
2402
|
-
};
|
|
2577
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
|
|
2403
2578
|
this.saveClaudeConfigs();
|
|
2404
2579
|
|
|
2405
2580
|
const config = this.claudeConfigs[name];
|
|
@@ -2438,12 +2613,7 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2438
2613
|
return this.showMessage('配置已存在', 'info');
|
|
2439
2614
|
}
|
|
2440
2615
|
|
|
2441
|
-
this.claudeConfigs[name] = {
|
|
2442
|
-
apiKey: this.newClaudeConfig.apiKey,
|
|
2443
|
-
baseUrl: this.newClaudeConfig.baseUrl,
|
|
2444
|
-
model: this.newClaudeConfig.model,
|
|
2445
|
-
hasKey: !!this.newClaudeConfig.apiKey
|
|
2446
|
-
};
|
|
2616
|
+
this.claudeConfigs[name] = this.mergeClaudeConfig({}, this.newClaudeConfig);
|
|
2447
2617
|
|
|
2448
2618
|
this.currentClaudeConfig = name;
|
|
2449
2619
|
this.saveClaudeConfigs();
|
|
@@ -2474,6 +2644,9 @@ import { createSkillsMethods } from './modules/skills.methods.mjs';
|
|
|
2474
2644
|
const config = this.claudeConfigs[name];
|
|
2475
2645
|
|
|
2476
2646
|
if (!config.apiKey) {
|
|
2647
|
+
if (config.externalCredentialType) {
|
|
2648
|
+
return this.showMessage('检测到外部 Claude 认证状态;当前仅支持展示,若需由 codexmate 接管请补充 API Key', 'info');
|
|
2649
|
+
}
|
|
2477
2650
|
return this.showMessage('请先配置 API Key', 'error');
|
|
2478
2651
|
}
|
|
2479
2652
|
|
package/web-ui/index.html
CHANGED
|
@@ -282,6 +282,20 @@
|
|
|
282
282
|
<span class="value">{{ sessionsList.length }}</span>
|
|
283
283
|
</div>
|
|
284
284
|
</div>
|
|
285
|
+
<div
|
|
286
|
+
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && providersList.length > 1"
|
|
287
|
+
class="provider-fast-switch">
|
|
288
|
+
<label class="provider-fast-switch-label" for="provider-fast-switch-select">快速切换提供商</label>
|
|
289
|
+
<select
|
|
290
|
+
id="provider-fast-switch-select"
|
|
291
|
+
class="provider-fast-switch-select"
|
|
292
|
+
:value="currentProvider"
|
|
293
|
+
@change="quickSwitchProvider($event.target.value)">
|
|
294
|
+
<option v-for="provider in providersList" :key="'quick-switch-' + provider.name" :value="provider.name">
|
|
295
|
+
{{ provider.name }}
|
|
296
|
+
</option>
|
|
297
|
+
</select>
|
|
298
|
+
</div>
|
|
285
299
|
|
|
286
300
|
<div v-if="false && mainTab === 'config' && !sessionStandalone" class="config-subtabs">
|
|
287
301
|
<button :class="['config-subtab', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
|
|
@@ -672,7 +686,7 @@
|
|
|
672
686
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
673
687
|
</svg>
|
|
674
688
|
</button>
|
|
675
|
-
<button class="card-action-btn" :class="{ loading: claudeShareLoading[name] }" @click="copyClaudeShareCommand(name)" title="
|
|
689
|
+
<button class="card-action-btn" :class="{ loading: claudeShareLoading[name] }" @click="copyClaudeShareCommand(name)" disabled title="分享导入命令(暂时禁用)" aria-label="Share import command">
|
|
676
690
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
677
691
|
<path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
|
|
678
692
|
<path d="M16 6l-4-4-4 4"/>
|
package/web-ui/logic.mjs
CHANGED
|
@@ -8,33 +8,63 @@ export function normalizeClaudeConfig(config) {
|
|
|
8
8
|
return {
|
|
9
9
|
apiKey: normalizeClaudeValue(safe.apiKey),
|
|
10
10
|
baseUrl: normalizeClaudeValue(safe.baseUrl),
|
|
11
|
-
model: normalizeClaudeValue(safe.model)
|
|
11
|
+
model: normalizeClaudeValue(safe.model),
|
|
12
|
+
authToken: normalizeClaudeValue(safe.authToken),
|
|
13
|
+
useKey: normalizeClaudeValue(safe.useKey),
|
|
14
|
+
externalCredentialType: normalizeClaudeValue(safe.externalCredentialType)
|
|
12
15
|
};
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export function normalizeClaudeSettingsEnv(env) {
|
|
16
19
|
const safe = env && typeof env === 'object' ? env : {};
|
|
20
|
+
const apiKey = normalizeClaudeValue(safe.ANTHROPIC_API_KEY);
|
|
21
|
+
const authToken = normalizeClaudeValue(safe.ANTHROPIC_AUTH_TOKEN);
|
|
22
|
+
const useKey = normalizeClaudeValue(safe.CLAUDE_CODE_USE_KEY);
|
|
17
23
|
return {
|
|
18
|
-
apiKey
|
|
24
|
+
apiKey,
|
|
19
25
|
baseUrl: normalizeClaudeValue(safe.ANTHROPIC_BASE_URL),
|
|
20
|
-
model: normalizeClaudeValue(safe.ANTHROPIC_MODEL)
|
|
26
|
+
model: normalizeClaudeValue(safe.ANTHROPIC_MODEL) || 'glm-4.7',
|
|
27
|
+
authToken,
|
|
28
|
+
useKey,
|
|
29
|
+
externalCredentialType: apiKey
|
|
30
|
+
? ''
|
|
31
|
+
: (authToken ? 'auth-token' : (useKey ? 'claude-code-use-key' : ''))
|
|
21
32
|
};
|
|
22
33
|
}
|
|
23
34
|
|
|
35
|
+
function normalizeClaudeComparableUrl(value) {
|
|
36
|
+
const trimmed = normalizeClaudeValue(value);
|
|
37
|
+
if (!trimmed) return '';
|
|
38
|
+
return trimmed.replace(/\/+$/g, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasClaudeCredential(config = {}) {
|
|
42
|
+
return !!(config.apiKey || config.authToken || config.useKey);
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
export function matchClaudeConfigFromSettings(claudeConfigs = {}, env = {}) {
|
|
25
46
|
const normalizedSettings = normalizeClaudeSettingsEnv(env);
|
|
26
|
-
if (!normalizedSettings.
|
|
47
|
+
if (!normalizedSettings.baseUrl || !normalizedSettings.model || !hasClaudeCredential(normalizedSettings)) {
|
|
27
48
|
return '';
|
|
28
49
|
}
|
|
50
|
+
const comparableSettingsUrl = normalizeClaudeComparableUrl(normalizedSettings.baseUrl);
|
|
29
51
|
const entries = Object.entries(claudeConfigs || {});
|
|
30
52
|
for (const [name, config] of entries) {
|
|
31
53
|
const normalizedConfig = normalizeClaudeConfig(config);
|
|
32
|
-
if (!normalizedConfig.
|
|
54
|
+
if (!normalizedConfig.baseUrl || !normalizedConfig.model) {
|
|
33
55
|
continue;
|
|
34
56
|
}
|
|
35
|
-
if (normalizedConfig.
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
if (normalizeClaudeComparableUrl(normalizedConfig.baseUrl) !== comparableSettingsUrl
|
|
58
|
+
|| normalizedConfig.model !== normalizedSettings.model) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (normalizedSettings.apiKey && normalizedConfig.apiKey === normalizedSettings.apiKey) {
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
if (!normalizedSettings.apiKey
|
|
65
|
+
&& normalizedConfig.apiKey === ''
|
|
66
|
+
&& normalizedConfig.externalCredentialType
|
|
67
|
+
&& normalizedConfig.externalCredentialType === normalizedSettings.externalCredentialType) {
|
|
38
68
|
return name;
|
|
39
69
|
}
|
|
40
70
|
}
|
|
@@ -43,18 +73,30 @@ export function matchClaudeConfigFromSettings(claudeConfigs = {}, env = {}) {
|
|
|
43
73
|
|
|
44
74
|
export function findDuplicateClaudeConfigName(claudeConfigs = {}, config) {
|
|
45
75
|
const normalized = normalizeClaudeConfig(config);
|
|
46
|
-
if (!normalized.
|
|
76
|
+
if (!normalized.baseUrl || !normalized.model) {
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
const comparableUrl = normalizeClaudeComparableUrl(normalized.baseUrl);
|
|
80
|
+
const isExternal = !normalized.apiKey && !!normalized.externalCredentialType;
|
|
81
|
+
if (!normalized.apiKey && !isExternal) {
|
|
47
82
|
return '';
|
|
48
83
|
}
|
|
49
84
|
const entries = Object.entries(claudeConfigs || {});
|
|
50
85
|
for (const [name, existing] of entries) {
|
|
51
86
|
const normalizedExisting = normalizeClaudeConfig(existing);
|
|
52
|
-
if (!normalizedExisting.
|
|
87
|
+
if (!normalizedExisting.baseUrl || !normalizedExisting.model) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (normalizeClaudeComparableUrl(normalizedExisting.baseUrl) !== comparableUrl
|
|
91
|
+
|| normalizedExisting.model !== normalized.model) {
|
|
53
92
|
continue;
|
|
54
93
|
}
|
|
55
|
-
if (normalizedExisting.apiKey === normalized.apiKey
|
|
56
|
-
|
|
57
|
-
|
|
94
|
+
if (normalized.apiKey && normalizedExisting.apiKey === normalized.apiKey) {
|
|
95
|
+
return name;
|
|
96
|
+
}
|
|
97
|
+
if (isExternal
|
|
98
|
+
&& !normalizedExisting.apiKey
|
|
99
|
+
&& normalizedExisting.externalCredentialType === normalized.externalCredentialType) {
|
|
58
100
|
return name;
|
|
59
101
|
}
|
|
60
102
|
}
|
|
@@ -126,6 +168,64 @@ export function buildSpeedTestIssue(name, result) {
|
|
|
126
168
|
return null;
|
|
127
169
|
}
|
|
128
170
|
|
|
171
|
+
export async function runLatestOnlyQueue(initialTarget, options = {}) {
|
|
172
|
+
const perform = typeof options.perform === 'function'
|
|
173
|
+
? options.perform
|
|
174
|
+
: async () => {};
|
|
175
|
+
const consumePending = typeof options.consumePending === 'function'
|
|
176
|
+
? options.consumePending
|
|
177
|
+
: () => '';
|
|
178
|
+
let currentTarget = typeof initialTarget === 'string' ? initialTarget.trim() : '';
|
|
179
|
+
let lastError = '';
|
|
180
|
+
|
|
181
|
+
while (currentTarget) {
|
|
182
|
+
try {
|
|
183
|
+
await perform(currentTarget);
|
|
184
|
+
lastError = '';
|
|
185
|
+
} catch (e) {
|
|
186
|
+
lastError = e && e.message ? e.message : 'queue task failed';
|
|
187
|
+
}
|
|
188
|
+
const queued = String(consumePending() || '').trim();
|
|
189
|
+
if (!queued || queued === currentTarget) {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
currentTarget = queued;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
lastTarget: currentTarget,
|
|
197
|
+
lastError
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function shouldForceCompactLayoutMode(options = {}) {
|
|
202
|
+
const viewportWidth = Number(options.viewportWidth || 0);
|
|
203
|
+
const screenWidth = Number(options.screenWidth || 0);
|
|
204
|
+
const screenHeight = Number(options.screenHeight || 0);
|
|
205
|
+
const shortEdge = Number(options.shortEdge || (screenWidth > 0 && screenHeight > 0 ? Math.min(screenWidth, screenHeight) : 0));
|
|
206
|
+
const maxTouchPoints = Number(options.maxTouchPoints || 0);
|
|
207
|
+
const userAgent = typeof options.userAgent === 'string' ? options.userAgent : '';
|
|
208
|
+
const isMobileUa = typeof options.isMobileUa === 'boolean'
|
|
209
|
+
? options.isMobileUa
|
|
210
|
+
: /(Android|iPhone|iPad|iPod|Mobile)/i.test(userAgent);
|
|
211
|
+
const coarsePointer = !!options.coarsePointer;
|
|
212
|
+
const noHover = !!options.noHover;
|
|
213
|
+
const isSmallPhysicalScreen = shortEdge > 0 && shortEdge <= 920;
|
|
214
|
+
const isNarrowViewport = viewportWidth > 0 && viewportWidth <= 960;
|
|
215
|
+
const pointerSuggestsTouchOnly = coarsePointer && noHover;
|
|
216
|
+
|
|
217
|
+
if (isMobileUa) {
|
|
218
|
+
return isNarrowViewport || isSmallPhysicalScreen;
|
|
219
|
+
}
|
|
220
|
+
if (!pointerSuggestsTouchOnly) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (maxTouchPoints <= 0) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return isSmallPhysicalScreen;
|
|
227
|
+
}
|
|
228
|
+
|
|
129
229
|
// Session filtering helpers
|
|
130
230
|
export function isSessionQueryEnabled(source) {
|
|
131
231
|
const normalized = normalizeSessionSource(source, '');
|