codexmate 0.0.33 → 0.0.36
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/cli/agents-files.js +6 -0
- package/cli/archive-helpers.js +11 -4
- package/cli/local-bridge.js +9 -4
- package/cli/openai-bridge.js +1 -1
- package/cli/update.js +11 -2
- package/cli.js +133 -64
- package/lib/cli-webhook.js +29 -1
- package/package.json +2 -1
- package/web-ui/app.js +37 -2
- package/web-ui/index.html +2 -1
- package/web-ui/logic.claude.mjs +4 -0
- package/web-ui/logic.sessions.mjs +6 -5
- package/web-ui/modules/app.computed.dashboard.mjs +4 -0
- package/web-ui/modules/app.computed.session.mjs +147 -6
- package/web-ui/modules/app.methods.claude-config.mjs +41 -0
- package/web-ui/modules/app.methods.codex-config.mjs +11 -3
- package/web-ui/modules/app.methods.navigation.mjs +32 -2
- package/web-ui/modules/app.methods.session-browser.mjs +12 -6
- package/web-ui/modules/app.methods.session-trash.mjs +30 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +9 -0
- package/web-ui/modules/app.methods.webhook.mjs +8 -0
- package/web-ui/modules/i18n.dict.mjs +8 -0
- package/web-ui/modules/sessions-filters-url.mjs +65 -12
- package/web-ui/modules/skills.methods.mjs +31 -0
- package/web-ui/partials/index/layout-header.html +17 -12
- package/web-ui/partials/index/modal-webhook.html +42 -0
- package/web-ui/partials/index/modals-basic.html +50 -0
- package/web-ui/partials/index/panel-config-claude.html +13 -22
- package/web-ui/partials/index/panel-config-codex.html +8 -22
- package/web-ui/partials/index/panel-market.html +76 -149
- package/web-ui/partials/index/panel-sessions.html +2 -2
- package/web-ui/partials/index/panel-settings.html +119 -149
- package/web-ui/partials/index/panel-usage.html +115 -68
- package/web-ui/res/vue.runtime.global.prod.js +7 -0
- package/web-ui/res/web-ui-render.precompiled.js +7274 -0
- package/web-ui/session-helpers.mjs +15 -4
- package/web-ui/source-bundle.cjs +73 -1
- package/web-ui/styles/base-theme.css +10 -0
- package/web-ui/styles/bridge-pool.css +69 -0
- package/web-ui/styles/layout-shell.css +66 -27
- package/web-ui/styles/navigation-panels.css +8 -0
- package/web-ui/styles/responsive.css +50 -9
- package/web-ui/styles/sessions-usage.css +336 -319
- package/web-ui/styles/settings-panel.css +300 -234
- package/web-ui/styles/skills-market.css +294 -0
- package/web-ui/styles/titles-cards.css +14 -0
- package/web-ui/styles/webhook.css +38 -4
package/web-ui/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Codex Mate</title>
|
|
7
7
|
<link rel="icon" type="image/webp" href="/res/logo-pack.webp">
|
|
8
8
|
<link rel="apple-touch-icon" href="/res/logo-pack.webp">
|
|
9
|
-
<script src="/res/vue.global.prod.js"></script>
|
|
9
|
+
<script src="/res/vue.runtime.global.prod.js"></script>
|
|
10
10
|
<script src="/res/json5.min.js"></script>
|
|
11
11
|
<link rel="stylesheet" href="/web-ui/styles.css">
|
|
12
12
|
</head>
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<!-- @include ./partials/index/panel-plugins.html -->
|
|
27
27
|
<!-- @include ./partials/index/layout-footer.html -->
|
|
28
28
|
<!-- @include ./partials/index/modals-basic.html -->
|
|
29
|
+
<!-- @include ./partials/index/modal-webhook.html -->
|
|
29
30
|
<!-- @include ./partials/index/modal-openclaw-config.html -->
|
|
30
31
|
<!-- @include ./partials/index/modal-config-template-agents.html -->
|
|
31
32
|
<!-- @include ./partials/index/modal-skills.html -->
|
package/web-ui/logic.claude.mjs
CHANGED
|
@@ -111,6 +111,10 @@ export function matchClaudeConfigFromSettings(claudeConfigs = {}, env = {}) {
|
|
|
111
111
|
if (!normalizedSettings.baseUrl || !normalizedSettings.model || !hasClaudeCredential(normalizedSettings)) {
|
|
112
112
|
return '';
|
|
113
113
|
}
|
|
114
|
+
// 检测本地桥接 URL
|
|
115
|
+
if (typeof normalizedSettings.baseUrl === 'string' && normalizedSettings.baseUrl.includes('/bridge/claude-local/')) {
|
|
116
|
+
return 'claude-local';
|
|
117
|
+
}
|
|
114
118
|
const comparableSettingsUrl = normalizeClaudeComparableUrl(normalizedSettings.baseUrl);
|
|
115
119
|
const entries = Object.entries(claudeConfigs || {});
|
|
116
120
|
for (const [name, config] of entries) {
|
|
@@ -180,12 +180,9 @@ export function formatSessionTimelineTimestamp(timestamp) {
|
|
|
180
180
|
if (!value) return '';
|
|
181
181
|
|
|
182
182
|
const matched = value.match(/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2}))?/);
|
|
183
|
-
if (matched)
|
|
184
|
-
const second = matched[6] || '00';
|
|
185
|
-
return `${matched[2]}-${matched[3]} ${matched[4]}:${matched[5]}:${second}`;
|
|
186
|
-
}
|
|
183
|
+
if (!matched) return value;
|
|
187
184
|
|
|
188
|
-
return
|
|
185
|
+
return `${matched[1]}-${matched[2]}-${matched[3]} ${matched[4]}:${matched[5]}`;
|
|
189
186
|
}
|
|
190
187
|
|
|
191
188
|
function normalizeUsageRange(range) {
|
|
@@ -549,11 +546,15 @@ export function buildUsageChartGroups(sessions = [], options = {}) {
|
|
|
549
546
|
String(messageCount),
|
|
550
547
|
String(sessionIndex)
|
|
551
548
|
].join(':'),
|
|
549
|
+
sessionId: session.sessionId || '',
|
|
550
|
+
filePath: session.filePath || '',
|
|
552
551
|
title: normalizedTitle,
|
|
553
552
|
source,
|
|
554
553
|
sourceLabel,
|
|
555
554
|
cwd,
|
|
556
555
|
messageCount,
|
|
556
|
+
totalTokens: sessionTotalTokens,
|
|
557
|
+
contextWindow: sessionContextWindow,
|
|
557
558
|
updatedAt: session.updatedAt || '',
|
|
558
559
|
updatedAtMs,
|
|
559
560
|
updatedAtLabel: formatSessionTimelineTimestamp(session.updatedAt || ''),
|
|
@@ -92,6 +92,10 @@ export function createDashboardComputed() {
|
|
|
92
92
|
return list;
|
|
93
93
|
},
|
|
94
94
|
|
|
95
|
+
isLocalProviderDisabled() {
|
|
96
|
+
return this.configMode === 'codex';
|
|
97
|
+
},
|
|
98
|
+
|
|
95
99
|
displayProviderUrl() {
|
|
96
100
|
return (provider) => {
|
|
97
101
|
if (provider && provider.name === 'local') return '';
|
|
@@ -602,11 +602,10 @@ export function createSessionComputed() {
|
|
|
602
602
|
const sessions = this.sessionUsageCharts && Array.isArray(this.sessionUsageCharts.filteredSessions)
|
|
603
603
|
? this.sessionUsageCharts.filteredSessions
|
|
604
604
|
: this.sessionsUsageList;
|
|
605
|
-
const compareEnabled = this.sessionsUsageCompareEnabled === true && this.sessionsUsageTimeRange !== 'all';
|
|
606
605
|
const rangeDays = this.sessionsUsageTimeRange === '30d' ? 30 : 7;
|
|
607
606
|
const dayMs = 24 * 60 * 60 * 1000;
|
|
608
607
|
const baseMs = Date.parse(`${dayKey}T00:00:00.000Z`);
|
|
609
|
-
const prevKey =
|
|
608
|
+
const prevKey = Number.isFinite(baseMs)
|
|
610
609
|
? new Date(baseMs - (rangeDays * dayMs)).toISOString().slice(0, 10)
|
|
611
610
|
: '';
|
|
612
611
|
let sessionCount = 0;
|
|
@@ -636,7 +635,7 @@ export function createSessionComputed() {
|
|
|
636
635
|
} else if (isPrev) {
|
|
637
636
|
prevTokenTotal += sessionTokens;
|
|
638
637
|
}
|
|
639
|
-
|
|
638
|
+
const model = typeof session.model === 'string' ? session.model.trim() : '';
|
|
640
639
|
if (isCurrent && model) {
|
|
641
640
|
modelMap.set(model, (modelMap.get(model) || 0) + 1);
|
|
642
641
|
}
|
|
@@ -667,20 +666,162 @@ export function createSessionComputed() {
|
|
|
667
666
|
}));
|
|
668
667
|
return {
|
|
669
668
|
dayKey,
|
|
670
|
-
compareEnabled,
|
|
671
669
|
prevKey,
|
|
672
670
|
sessionCount,
|
|
673
671
|
messageCount,
|
|
674
672
|
tokenTotal,
|
|
675
673
|
tokenLabel: formatUsageSummaryNumber(tokenTotal),
|
|
676
674
|
prevTokenTotal,
|
|
677
|
-
prevTokenLabel:
|
|
678
|
-
deltaTokenLabel:
|
|
675
|
+
prevTokenLabel: prevKey ? formatUsageSummaryNumber(prevTokenTotal) : null,
|
|
676
|
+
deltaTokenLabel: prevKey ? formatSignedUsageSummaryNumber(tokenTotal - prevTokenTotal) : null,
|
|
679
677
|
topSessions,
|
|
680
678
|
topModels
|
|
681
679
|
};
|
|
682
680
|
},
|
|
683
681
|
|
|
682
|
+
usageHeroMainValue() {
|
|
683
|
+
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
|
|
684
|
+
? this.sessionUsageCharts.summary
|
|
685
|
+
: null;
|
|
686
|
+
if (!summary) return '0';
|
|
687
|
+
return formatCompactUsageSummaryNumber(summary.totalTokens || 0);
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
usageHeroSubLabel() {
|
|
691
|
+
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
|
|
692
|
+
? this.sessionUsageCharts.summary
|
|
693
|
+
: null;
|
|
694
|
+
if (!summary) return '';
|
|
695
|
+
const t = typeof this.t === 'function' ? this.t : null;
|
|
696
|
+
const sessionCount = summary.totalSessions || 0;
|
|
697
|
+
const rangeLabel = this.sessionsUsageTimeRange === '30d' ? '30天' : (this.sessionsUsageTimeRange === 'all' ? '全部' : '7天');
|
|
698
|
+
const rangeText = t ? t('usage.range.' + this.sessionsUsageTimeRange) : rangeLabel;
|
|
699
|
+
return `${formatUsageSummaryNumber(sessionCount)} sessions · ${rangeText}`;
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
usageHeroDelta() {
|
|
703
|
+
const range = this.sessionsUsageTimeRange;
|
|
704
|
+
if (range === 'all') return null;
|
|
705
|
+
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
|
|
706
|
+
? this.sessionUsageCharts.summary
|
|
707
|
+
: null;
|
|
708
|
+
if (!summary || !summary.totalTokens) return null;
|
|
709
|
+
|
|
710
|
+
const rangeDays = range === '30d' ? 30 : 7;
|
|
711
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
712
|
+
const nowMs = Date.now();
|
|
713
|
+
const prevStartMs = nowMs - (rangeDays * 2 * dayMs);
|
|
714
|
+
const prevEndMs = nowMs - (rangeDays * dayMs);
|
|
715
|
+
|
|
716
|
+
let prevTokens = 0;
|
|
717
|
+
for (const session of (Array.isArray(this.sessionsUsageList) ? this.sessionsUsageList : [])) {
|
|
718
|
+
if (!session || typeof session !== 'object') continue;
|
|
719
|
+
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
720
|
+
if (!Number.isFinite(updatedAtMs)) continue;
|
|
721
|
+
if (updatedAtMs >= prevStartMs && updatedAtMs < prevEndMs) {
|
|
722
|
+
const sessionTokens = Number.isFinite(Number(session.totalTokens))
|
|
723
|
+
? Math.max(0, Math.floor(Number(session.totalTokens)))
|
|
724
|
+
: 0;
|
|
725
|
+
prevTokens += sessionTokens;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (prevTokens === 0) return null;
|
|
730
|
+
const currentTokens = summary.totalTokens;
|
|
731
|
+
const delta = currentTokens - prevTokens;
|
|
732
|
+
const deltaPercent = prevTokens > 0 ? Math.round((delta / prevTokens) * 100) : 0;
|
|
733
|
+
const arrow = delta > 0 ? '↑' : (delta < 0 ? '↓' : '–');
|
|
734
|
+
const sign = delta >= 0 ? '+' : '';
|
|
735
|
+
return `${arrow} ${sign}${deltaPercent}%`;
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
usageHeroDeltaClass() {
|
|
739
|
+
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
|
|
740
|
+
? this.sessionUsageCharts.summary
|
|
741
|
+
: null;
|
|
742
|
+
if (!summary || !summary.totalTokens) return '';
|
|
743
|
+
|
|
744
|
+
const range = this.sessionsUsageTimeRange;
|
|
745
|
+
if (range === 'all') return '';
|
|
746
|
+
|
|
747
|
+
const rangeDays = range === '30d' ? 30 : 7;
|
|
748
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
749
|
+
const nowMs = Date.now();
|
|
750
|
+
const prevStartMs = nowMs - (rangeDays * 2 * dayMs);
|
|
751
|
+
const prevEndMs = nowMs - (rangeDays * dayMs);
|
|
752
|
+
|
|
753
|
+
let prevTokens = 0;
|
|
754
|
+
for (const session of (Array.isArray(this.sessionsUsageList) ? this.sessionsUsageList : [])) {
|
|
755
|
+
if (!session || typeof session !== 'object') continue;
|
|
756
|
+
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
757
|
+
if (!Number.isFinite(updatedAtMs)) continue;
|
|
758
|
+
if (updatedAtMs >= prevStartMs && updatedAtMs < prevEndMs) {
|
|
759
|
+
const sessionTokens = Number.isFinite(Number(session.totalTokens))
|
|
760
|
+
? Math.max(0, Math.floor(Number(session.totalTokens)))
|
|
761
|
+
: 0;
|
|
762
|
+
prevTokens += sessionTokens;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (prevTokens === 0) return '';
|
|
767
|
+
const currentTokens = summary.totalTokens;
|
|
768
|
+
return currentTokens >= prevTokens ? 'delta-up' : 'delta-down';
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
sessionsUsageSelectedDay() {
|
|
772
|
+
return this.sessionsUsageSelectedDayKey || '';
|
|
773
|
+
},
|
|
774
|
+
|
|
775
|
+
sessionUsageWave() {
|
|
776
|
+
const daily = this.sessionUsageDaily && typeof this.sessionUsageDaily === 'object'
|
|
777
|
+
? this.sessionUsageDaily
|
|
778
|
+
: null;
|
|
779
|
+
if (!daily || !Array.isArray(daily.rows) || daily.rows.length === 0) {
|
|
780
|
+
return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0 };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const rows = daily.rows;
|
|
784
|
+
const maxTokens = daily.maxTokens || 1;
|
|
785
|
+
const width = 800;
|
|
786
|
+
const height = 140;
|
|
787
|
+
const padding = { top: 10, bottom: 30, left: 0, right: 0 };
|
|
788
|
+
const chartWidth = width - padding.left - padding.right;
|
|
789
|
+
const chartHeight = height - padding.top - padding.bottom;
|
|
790
|
+
|
|
791
|
+
const points = rows.map((row, index) => {
|
|
792
|
+
const x = padding.left + (index / (rows.length - 1 || 1)) * chartWidth;
|
|
793
|
+
const normalizedValue = maxTokens > 0 ? (row.tokenTotal / maxTokens) : 0;
|
|
794
|
+
const y = padding.top + chartHeight - (normalizedValue * chartHeight);
|
|
795
|
+
return { x, y, key: row.key, value: row.tokenTotal, label: row.label };
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
const linePath = points.length > 1
|
|
799
|
+
? `M ${points.map(p => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' L ')}`
|
|
800
|
+
: '';
|
|
801
|
+
|
|
802
|
+
const areaPath = points.length > 1
|
|
803
|
+
? `${linePath} L ${points[points.length - 1].x.toFixed(1)},${(padding.top + chartHeight).toFixed(1)} L ${points[0].x.toFixed(1)},${(padding.top + chartHeight).toFixed(1)} Z`
|
|
804
|
+
: '';
|
|
805
|
+
|
|
806
|
+
const selectedKey = this.sessionsUsageSelectedDayKey;
|
|
807
|
+
const selectedPoint = points.find(p => p.key === selectedKey) || points[points.length - 1] || null;
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
points,
|
|
811
|
+
labels: rows.map((row, index) => ({
|
|
812
|
+
key: row.key,
|
|
813
|
+
text: row.label
|
|
814
|
+
})),
|
|
815
|
+
linePath,
|
|
816
|
+
areaPath,
|
|
817
|
+
width,
|
|
818
|
+
height,
|
|
819
|
+
maxTokens,
|
|
820
|
+
hoverX: selectedPoint ? selectedPoint.x : 0,
|
|
821
|
+
hoverY: selectedPoint ? selectedPoint.y : 0
|
|
822
|
+
};
|
|
823
|
+
},
|
|
824
|
+
|
|
684
825
|
visibleSessionTrashItems() {
|
|
685
826
|
const items = Array.isArray(this.sessionTrashItems) ? this.sessionTrashItems : [];
|
|
686
827
|
const visibleCount = Number(this.sessionTrashVisibleCount);
|
|
@@ -264,6 +264,47 @@ export function createClaudeConfigMethods(options = {}) {
|
|
|
264
264
|
|
|
265
265
|
claudeLocalBridgeConfigured() {
|
|
266
266
|
return this.claudeLocalBridgeCandidateProviders().some(p => p.hasKey);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
isClaudeLocalBridgeDisabled() {
|
|
270
|
+
return this.configMode === 'claude';
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async applyClaudeLocalBridge() {
|
|
274
|
+
this.currentClaudeConfig = 'claude-local';
|
|
275
|
+
try { localStorage.setItem('currentClaudeConfig', 'claude-local'); } catch (_) {}
|
|
276
|
+
this.refreshClaudeModelContext();
|
|
277
|
+
|
|
278
|
+
const candidates = this.claudeLocalBridgeCandidateProviders();
|
|
279
|
+
if (candidates.length === 0) {
|
|
280
|
+
return this.showMessage('请先添加并配置至少一个 Claude 提供商', 'error');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const res = await api('claude-local-bridge-toggle', { enable: true });
|
|
285
|
+
if (res.error) {
|
|
286
|
+
this.showMessage(res.error || '启用本地负载均衡失败', 'error');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.showMessage('Claude 本地负载均衡已启用', 'success');
|
|
290
|
+
} catch (e) {
|
|
291
|
+
this.showMessage('启用本地负载均衡失败', 'error');
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
async openClaudeConfigTemplateEditor() {
|
|
296
|
+
try {
|
|
297
|
+
const res = await api('get-claude-settings-raw');
|
|
298
|
+
if (res.error) {
|
|
299
|
+
this.showMessage(res.error, 'error');
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
this.configTemplateContent = res.content || '{}';
|
|
303
|
+
this.configTemplateContext = 'claude';
|
|
304
|
+
this.showConfigTemplateModal = true;
|
|
305
|
+
} catch (e) {
|
|
306
|
+
this.showMessage('加载 Claude settings 失败', 'error');
|
|
307
|
+
}
|
|
267
308
|
}
|
|
268
309
|
};
|
|
269
310
|
}
|
|
@@ -558,6 +558,7 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
558
558
|
template = `${template.trimEnd()}\n\n${appendBlock}\n`;
|
|
559
559
|
}
|
|
560
560
|
this.configTemplateContent = template;
|
|
561
|
+
this.configTemplateContext = 'codex';
|
|
561
562
|
this.showConfigTemplateModal = true;
|
|
562
563
|
} catch (e) {
|
|
563
564
|
this.showMessage('加载模板失败', 'error');
|
|
@@ -807,9 +808,16 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
807
808
|
const performApply = async () => {
|
|
808
809
|
this.configTemplateApplying = true;
|
|
809
810
|
try {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
811
|
+
let res;
|
|
812
|
+
if (this.configTemplateContext === 'claude') {
|
|
813
|
+
res = await api('apply-claude-settings-raw', {
|
|
814
|
+
content: this.configTemplateContent
|
|
815
|
+
});
|
|
816
|
+
} else {
|
|
817
|
+
res = await api('apply-config-template', {
|
|
818
|
+
template: this.configTemplateContent
|
|
819
|
+
});
|
|
820
|
+
}
|
|
813
821
|
if (res.error) {
|
|
814
822
|
this.showMessage(res.error, 'error');
|
|
815
823
|
return;
|
|
@@ -56,6 +56,32 @@
|
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
|
+
|
|
60
|
+
const canonicalizeWebUiRuntimeUrl = () => {
|
|
61
|
+
if (typeof window === 'undefined' || !window.location) return;
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(window.location.href);
|
|
64
|
+
if (url.pathname === '/session') return;
|
|
65
|
+
let pathname = url.pathname;
|
|
66
|
+
let previousPathname = '';
|
|
67
|
+
do {
|
|
68
|
+
previousPathname = pathname;
|
|
69
|
+
pathname = pathname.replace(/\/+web-ui\/+web-ui\/+/, '/web-ui/');
|
|
70
|
+
} while (pathname !== previousPathname);
|
|
71
|
+
if (pathname === '/web-ui' || pathname === '/web-ui/' || pathname === '/web-ui/index.html') {
|
|
72
|
+
pathname = '/';
|
|
73
|
+
}
|
|
74
|
+
url.pathname = pathname;
|
|
75
|
+
url.search = '';
|
|
76
|
+
url.hash = '';
|
|
77
|
+
const nextUrl = url.toString();
|
|
78
|
+
if (nextUrl === window.location.href) return;
|
|
79
|
+
if (window.history && typeof window.history.replaceState === 'function') {
|
|
80
|
+
window.history.replaceState(null, '', nextUrl);
|
|
81
|
+
}
|
|
82
|
+
} catch (_) {}
|
|
83
|
+
};
|
|
84
|
+
|
|
59
85
|
const persistNavState = (vm, overrides = null) => {
|
|
60
86
|
if (!vm || vm.__navStateRestoring) return;
|
|
61
87
|
if (typeof localStorage === 'undefined') return;
|
|
@@ -138,6 +164,7 @@
|
|
|
138
164
|
const normalizedMode = typeof mode === 'string'
|
|
139
165
|
? mode.trim().toLowerCase()
|
|
140
166
|
: '';
|
|
167
|
+
canonicalizeWebUiRuntimeUrl();
|
|
141
168
|
this.cancelTouchNavIntentReset();
|
|
142
169
|
if (typeof this.ensureMainTabSwitchState === 'function') {
|
|
143
170
|
this.ensureMainTabSwitchState().pendingConfigMode = '';
|
|
@@ -412,6 +439,7 @@
|
|
|
412
439
|
: '';
|
|
413
440
|
const targetTab = normalizedTab || tab;
|
|
414
441
|
if (!targetTab) return;
|
|
442
|
+
canonicalizeWebUiRuntimeUrl();
|
|
415
443
|
if (targetTab === 'orchestration' && this.taskOrchestrationTabEnabled !== true) {
|
|
416
444
|
return this.switchMainTab('config');
|
|
417
445
|
}
|
|
@@ -419,6 +447,7 @@
|
|
|
419
447
|
mainTab: targetTab,
|
|
420
448
|
configMode: targetTab === 'config' ? this.configMode : this.configMode
|
|
421
449
|
});
|
|
450
|
+
// URL 保持静态,不写入任何状态
|
|
422
451
|
this.cancelTouchNavIntentReset();
|
|
423
452
|
if (targetTab === 'sessions') {
|
|
424
453
|
this.cancelScheduledSessionTabDeferredTeardown();
|
|
@@ -460,9 +489,10 @@
|
|
|
460
489
|
return;
|
|
461
490
|
}
|
|
462
491
|
const isLeavingSessions = previousTab === 'sessions' && targetTab !== 'sessions';
|
|
463
|
-
const
|
|
492
|
+
const shouldPreserveSessionRender = isLeavingSessions && this.preserveSessionRenderOnTabLeave === true;
|
|
493
|
+
const shouldDeferApply = isLeavingSessions && !shouldPreserveSessionRender;
|
|
464
494
|
if (isLeavingSessions && !this.isSessionPanelFastHidden()) {
|
|
465
|
-
this.setSessionPanelFastHidden(
|
|
495
|
+
this.setSessionPanelFastHidden(!shouldPreserveSessionRender);
|
|
466
496
|
}
|
|
467
497
|
if (shouldDeferApply && typeof this.suspendSessionTabRender === 'function') {
|
|
468
498
|
this.suspendSessionTabRender();
|
|
@@ -264,6 +264,13 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
264
264
|
});
|
|
265
265
|
if (urlState) {
|
|
266
266
|
applySessionsFilterUrlState(this, urlState);
|
|
267
|
+
// 清理 URL,保持静态
|
|
268
|
+
try {
|
|
269
|
+
const url = new URL(window.location.href);
|
|
270
|
+
url.search = '';
|
|
271
|
+
url.hash = '';
|
|
272
|
+
window.history.replaceState(null, '', url.toString());
|
|
273
|
+
} catch (_) {}
|
|
267
274
|
try {
|
|
268
275
|
const sortCache = localStorage.getItem('codexmateSessionSortMode');
|
|
269
276
|
this.sessionSortMode = normalizeSortMode(sortCache);
|
|
@@ -479,14 +486,13 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
479
486
|
if (typeof text !== 'string' || !text) return text;
|
|
480
487
|
var tokens = this.queryTokens;
|
|
481
488
|
if (!tokens || tokens.length === 0) return text;
|
|
482
|
-
var
|
|
489
|
+
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
483
490
|
for (var i = 0; i < tokens.length; i++) {
|
|
484
|
-
var token = tokens[i];
|
|
485
|
-
var
|
|
486
|
-
|
|
487
|
-
result = result.replace(re, '<mark>$1</mark>');
|
|
491
|
+
var token = tokens[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
492
|
+
var re = new RegExp('(' + token + ')', 'gi');
|
|
493
|
+
escaped = escaped.replace(re, '<mark>$1</mark>');
|
|
488
494
|
}
|
|
489
|
-
return
|
|
495
|
+
return escaped;
|
|
490
496
|
},
|
|
491
497
|
|
|
492
498
|
async onSessionSourceChange(event) {
|
|
@@ -218,6 +218,36 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
218
218
|
await this.switchSettingsTab(tab);
|
|
219
219
|
},
|
|
220
220
|
|
|
221
|
+
async onSettingsTabKeydown(event, tab) {
|
|
222
|
+
if (!event) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const tabs = ['general', 'data'];
|
|
226
|
+
const currentTab = this.normalizeSettingsTab(tab || this.settingsTab);
|
|
227
|
+
const currentIndex = Math.max(0, tabs.indexOf(currentTab));
|
|
228
|
+
let nextIndex = currentIndex;
|
|
229
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
230
|
+
nextIndex = (currentIndex + 1) % tabs.length;
|
|
231
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
232
|
+
nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
|
233
|
+
} else if (event.key === 'Home') {
|
|
234
|
+
nextIndex = 0;
|
|
235
|
+
} else if (event.key === 'End') {
|
|
236
|
+
nextIndex = tabs.length - 1;
|
|
237
|
+
} else {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
event.preventDefault();
|
|
241
|
+
const nextTab = tabs[nextIndex];
|
|
242
|
+
await this.switchSettingsTab(nextTab);
|
|
243
|
+
const target = typeof document !== 'undefined'
|
|
244
|
+
? document.getElementById(`settings-tab-${nextTab}`)
|
|
245
|
+
: null;
|
|
246
|
+
if (target && typeof target.focus === 'function') {
|
|
247
|
+
target.focus();
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
|
|
221
251
|
async switchSettingsTab(tab, options = {}) {
|
|
222
252
|
const nextTab = this.normalizeSettingsTab(tab);
|
|
223
253
|
this.settingsTab = nextTab;
|
|
@@ -383,6 +383,15 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
383
383
|
},
|
|
384
384
|
|
|
385
385
|
syncClaudeModelFromConfig() {
|
|
386
|
+
if (this.currentClaudeConfig === 'claude-local') {
|
|
387
|
+
const candidates = this.claudeLocalBridgeCandidateProviders
|
|
388
|
+
? this.claudeLocalBridgeCandidateProviders()
|
|
389
|
+
: [];
|
|
390
|
+
const active = candidates.find(cp => !this.isClaudeLocalBridgeExcluded(cp.name));
|
|
391
|
+
this.currentClaudeModel = active && active.model ? active.model : '';
|
|
392
|
+
this.claudeCustomModelDraft = this.currentClaudeModel;
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
386
395
|
const config = this.getCurrentClaudeConfig();
|
|
387
396
|
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
388
397
|
this.claudeCustomModelDraft = this.currentClaudeModel;
|
|
@@ -2,6 +2,14 @@ import { api } from './api.mjs';
|
|
|
2
2
|
|
|
3
3
|
export function createWebhookMethods() {
|
|
4
4
|
return {
|
|
5
|
+
openWebhookModal() {
|
|
6
|
+
this.showWebhookModal = true;
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
closeWebhookModal() {
|
|
10
|
+
this.showWebhookModal = false;
|
|
11
|
+
},
|
|
12
|
+
|
|
5
13
|
async loadWebhookSettings() {
|
|
6
14
|
try {
|
|
7
15
|
const data = await api('get-webhook-config');
|
|
@@ -532,6 +532,7 @@ const DICT = Object.freeze({
|
|
|
532
532
|
'sessions.loadingList': '会话加载中...',
|
|
533
533
|
'sessions.empty': '暂无可用会话记录',
|
|
534
534
|
'sessions.unknownTime': '未知时间',
|
|
535
|
+
|
|
535
536
|
'sessions.query.placeholder.enabled': '关键词检索(支持 Codex/Claude/Gemini/CodeBuddy,例:claude code)',
|
|
536
537
|
'sessions.query.placeholder.disabled': '当前来源暂不支持关键词检索',
|
|
537
538
|
'sessions.pin': '置顶',
|
|
@@ -910,6 +911,7 @@ const DICT = Object.freeze({
|
|
|
910
911
|
'settings.tab.general': '通用',
|
|
911
912
|
'settings.tab.data': '数据',
|
|
912
913
|
'settings.tabs.aria': '设置分类',
|
|
914
|
+
'settings.quickSettings.title': '快捷设置',
|
|
913
915
|
'settings.sharePrefix.title': '分享命令前缀',
|
|
914
916
|
'settings.sharePrefix.meta': '影响 Web UI 里“复制分享命令”的前缀',
|
|
915
917
|
'settings.sharePrefix.label': '前缀',
|
|
@@ -1601,6 +1603,8 @@ const DICT = Object.freeze({
|
|
|
1601
1603
|
'sessions.loadingList': 'セッション一覧を読み込み中...',
|
|
1602
1604
|
'sessions.empty': 'セッションがありません',
|
|
1603
1605
|
'sessions.unknownTime': '不明な時間',
|
|
1606
|
+
|
|
1607
|
+
|
|
1604
1608
|
'sessions.query.placeholder.enabled': 'セッションを検索...',
|
|
1605
1609
|
'sessions.query.placeholder.disabled': '現在のソースでは検索は利用できません',
|
|
1606
1610
|
'sessions.pin': 'ピン留め',
|
|
@@ -1965,6 +1969,7 @@ const DICT = Object.freeze({
|
|
|
1965
1969
|
'settings.tab.general': '一般',
|
|
1966
1970
|
'settings.tab.data': 'データ',
|
|
1967
1971
|
'settings.tabs.aria': '設定カテゴリ',
|
|
1972
|
+
'settings.quickSettings.title': 'クイック設定',
|
|
1968
1973
|
'settings.sharePrefix.title': '共有コマンドプレフィックス',
|
|
1969
1974
|
'settings.sharePrefix.meta': 'Web UI の「共有コマンドをコピー」のプレフィックスに影響',
|
|
1970
1975
|
'settings.sharePrefix.label': 'プレフィックス',
|
|
@@ -2656,6 +2661,8 @@ const DICT = Object.freeze({
|
|
|
2656
2661
|
'sessions.loadingList': 'Loading sessions...',
|
|
2657
2662
|
'sessions.empty': 'No sessions found',
|
|
2658
2663
|
'sessions.unknownTime': 'unknown time',
|
|
2664
|
+
|
|
2665
|
+
|
|
2659
2666
|
'sessions.query.placeholder.enabled': 'Search keywords (Codex/Claude/Gemini/CodeBuddy, e.g. claude code)',
|
|
2660
2667
|
'sessions.query.placeholder.disabled': 'Keyword search is not available for this source',
|
|
2661
2668
|
'sessions.pin': 'Pin',
|
|
@@ -3034,6 +3041,7 @@ const DICT = Object.freeze({
|
|
|
3034
3041
|
'settings.tab.general': 'General',
|
|
3035
3042
|
'settings.tab.data': 'Data',
|
|
3036
3043
|
'settings.tabs.aria': 'Settings categories',
|
|
3044
|
+
'settings.quickSettings.title': 'Quick Settings',
|
|
3037
3045
|
'settings.sharePrefix.title': 'Share command prefix',
|
|
3038
3046
|
'settings.sharePrefix.meta': 'Used as the prefix for “Copy share command” in the Web UI',
|
|
3039
3047
|
'settings.sharePrefix.label': 'Prefix',
|
|
@@ -55,20 +55,76 @@ export function applySessionsFilterUrlState(vm, state) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function canonicalizeWebUiUrl(url) {
|
|
59
|
+
if (!url || typeof url !== 'object') return url;
|
|
60
|
+
if (url.pathname === '/web-ui/index.html') {
|
|
61
|
+
url.pathname = '/';
|
|
62
|
+
}
|
|
63
|
+
return url;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function canonicalizeWebUiHistoryUrl(value) {
|
|
67
|
+
if (typeof window === 'undefined' || !window.location) return value;
|
|
68
|
+
if (typeof value === 'undefined' || value === null) return value;
|
|
69
|
+
try {
|
|
70
|
+
const url = canonicalizeWebUiUrl(new URL(String(value), window.location.href));
|
|
71
|
+
return url && url.pathname === '/' ? url.href : value;
|
|
72
|
+
} catch (_) {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function normalizeCurrentWebUiUrl() {
|
|
78
|
+
try {
|
|
79
|
+
const url = canonicalizeWebUiUrl(new URL(window.location.href));
|
|
80
|
+
if (url && url.href !== window.location.href) {
|
|
81
|
+
window.history.replaceState(null, '', url.href);
|
|
82
|
+
}
|
|
83
|
+
return url;
|
|
84
|
+
} catch (_) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function installWebUiUrlCanonicalization() {
|
|
90
|
+
if (typeof window === 'undefined' || !window.history) return false;
|
|
91
|
+
if (window.__codexmateWebUiUrlCanonicalizationInstalled) {
|
|
92
|
+
normalizeCurrentWebUiUrl();
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const originalReplaceState = window.history.replaceState;
|
|
97
|
+
const originalPushState = window.history.pushState;
|
|
98
|
+
if (typeof originalReplaceState === 'function') {
|
|
99
|
+
window.history.replaceState = function replaceState(state, title, url) {
|
|
100
|
+
return originalReplaceState.call(this, state, title, canonicalizeWebUiHistoryUrl(url));
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (typeof originalPushState === 'function') {
|
|
104
|
+
window.history.pushState = function pushState(state, title, url) {
|
|
105
|
+
return originalPushState.call(this, state, title, canonicalizeWebUiHistoryUrl(url));
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
window.__codexmateWebUiUrlCanonicalizationInstalled = true;
|
|
109
|
+
normalizeCurrentWebUiUrl();
|
|
110
|
+
return true;
|
|
111
|
+
} catch (_) {
|
|
112
|
+
normalizeCurrentWebUiUrl();
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
58
117
|
export function buildSessionsFilterShareUrl(vm) {
|
|
59
118
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
119
|
+
// 使用干净的根路径作为基础 URL,避免把 /web-ui/index.html 或 /session 带进分享链接。
|
|
120
|
+
const baseUrl = window.location.origin + '/';
|
|
121
|
+
const url = canonicalizeWebUiUrl(new URL(baseUrl));
|
|
122
|
+
url.searchParams.set('tab', 'sessions');
|
|
62
123
|
url.searchParams.set('s_source', String(vm.sessionFilterSource || 'all'));
|
|
63
124
|
if (vm.sessionPathFilter) url.searchParams.set('s_path', String(vm.sessionPathFilter || ''));
|
|
64
|
-
else url.searchParams.delete('s_path');
|
|
65
125
|
if (vm.sessionQuery && isSessionQueryEnabled(vm.sessionFilterSource)) url.searchParams.set('s_query', String(vm.sessionQuery || ''));
|
|
66
|
-
else url.searchParams.delete('s_query');
|
|
67
126
|
if (vm.sessionRoleFilter && vm.sessionRoleFilter !== 'all') url.searchParams.set('s_role', String(vm.sessionRoleFilter || 'all'));
|
|
68
|
-
else url.searchParams.delete('s_role');
|
|
69
127
|
if (vm.sessionTimePreset && vm.sessionTimePreset !== 'all') url.searchParams.set('s_time', String(vm.sessionTimePreset || 'all'));
|
|
70
|
-
else url.searchParams.delete('s_time');
|
|
71
|
-
url.searchParams.set('tab', 'sessions');
|
|
72
128
|
return url.toString();
|
|
73
129
|
} catch (_) {
|
|
74
130
|
return '';
|
|
@@ -76,10 +132,7 @@ export function buildSessionsFilterShareUrl(vm) {
|
|
|
76
132
|
}
|
|
77
133
|
|
|
78
134
|
export function syncSessionsFilterUrl(vm) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
window.history.replaceState(null, '', url);
|
|
83
|
-
} catch (_) {}
|
|
135
|
+
// URL 保持静态,不同步状态到 URL
|
|
136
|
+
// 所有状态通过 localStorage 管理
|
|
84
137
|
}
|
|
85
138
|
|