codexmate 0.0.34 → 0.0.37
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.md +14 -5
- package/README.zh.md +14 -5
- package/cli.js +74 -61
- package/package.json +2 -1
- package/web-ui/app.js +32 -2
- package/web-ui/index.html +1 -1
- 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 +4 -0
- package/web-ui/modules/app.methods.navigation.mjs +32 -16
- package/web-ui/modules/app.methods.session-browser.mjs +7 -0
- package/web-ui/modules/app.methods.session-trash.mjs +30 -0
- package/web-ui/modules/i18n.dict.mjs +5 -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 +20 -11
- package/web-ui/partials/index/panel-config-claude.html +5 -3
- package/web-ui/partials/index/panel-config-codex.html +1 -1
- 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 +4 -2
- package/web-ui/partials/index/panel-usage.html +111 -68
- package/web-ui/res/vue.runtime.global.prod.js +7 -0
- package/web-ui/res/web-ui-render.precompiled.js +7269 -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/layout-shell.css +65 -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 +501 -336
- package/web-ui/styles/skills-market.css +294 -0
- package/web-ui/styles/titles-cards.css +14 -0
|
@@ -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,20 +447,7 @@
|
|
|
419
447
|
mainTab: targetTab,
|
|
420
448
|
configMode: targetTab === 'config' ? this.configMode : this.configMode
|
|
421
449
|
});
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
const url = new URL(window.location.href);
|
|
425
|
-
if (url.pathname !== '/session') {
|
|
426
|
-
url.searchParams.delete('s_source');
|
|
427
|
-
url.searchParams.delete('s_path');
|
|
428
|
-
url.searchParams.delete('s_query');
|
|
429
|
-
url.searchParams.delete('s_role');
|
|
430
|
-
url.searchParams.delete('s_time');
|
|
431
|
-
url.searchParams.delete('tab');
|
|
432
|
-
window.history.replaceState(null, '', url.toString());
|
|
433
|
-
}
|
|
434
|
-
} catch (_) {}
|
|
435
|
-
}
|
|
450
|
+
// URL 保持静态,不写入任何状态
|
|
436
451
|
this.cancelTouchNavIntentReset();
|
|
437
452
|
if (targetTab === 'sessions') {
|
|
438
453
|
this.cancelScheduledSessionTabDeferredTeardown();
|
|
@@ -474,9 +489,10 @@
|
|
|
474
489
|
return;
|
|
475
490
|
}
|
|
476
491
|
const isLeavingSessions = previousTab === 'sessions' && targetTab !== 'sessions';
|
|
477
|
-
const
|
|
492
|
+
const shouldPreserveSessionRender = isLeavingSessions && this.preserveSessionRenderOnTabLeave === true;
|
|
493
|
+
const shouldDeferApply = isLeavingSessions && !shouldPreserveSessionRender;
|
|
478
494
|
if (isLeavingSessions && !this.isSessionPanelFastHidden()) {
|
|
479
|
-
this.setSessionPanelFastHidden(
|
|
495
|
+
this.setSessionPanelFastHidden(!shouldPreserveSessionRender);
|
|
480
496
|
}
|
|
481
497
|
if (shouldDeferApply && typeof this.suspendSessionTabRender === 'function') {
|
|
482
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);
|
|
@@ -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;
|
|
@@ -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': '置顶',
|
|
@@ -1602,6 +1603,8 @@ const DICT = Object.freeze({
|
|
|
1602
1603
|
'sessions.loadingList': 'セッション一覧を読み込み中...',
|
|
1603
1604
|
'sessions.empty': 'セッションがありません',
|
|
1604
1605
|
'sessions.unknownTime': '不明な時間',
|
|
1606
|
+
|
|
1607
|
+
|
|
1605
1608
|
'sessions.query.placeholder.enabled': 'セッションを検索...',
|
|
1606
1609
|
'sessions.query.placeholder.disabled': '現在のソースでは検索は利用できません',
|
|
1607
1610
|
'sessions.pin': 'ピン留め',
|
|
@@ -2658,6 +2661,8 @@ const DICT = Object.freeze({
|
|
|
2658
2661
|
'sessions.loadingList': 'Loading sessions...',
|
|
2659
2662
|
'sessions.empty': 'No sessions found',
|
|
2660
2663
|
'sessions.unknownTime': 'unknown time',
|
|
2664
|
+
|
|
2665
|
+
|
|
2661
2666
|
'sessions.query.placeholder.enabled': 'Search keywords (Codex/Claude/Gemini/CodeBuddy, e.g. claude code)',
|
|
2662
2667
|
'sessions.query.placeholder.disabled': 'Keyword search is not available for this source',
|
|
2663
2668
|
'sessions.pin': 'Pin',
|
|
@@ -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
|
|
|
@@ -477,6 +477,37 @@ export function createSkillsMethods({ api }) {
|
|
|
477
477
|
this.skillsDeleting = false;
|
|
478
478
|
await this.scanImportableSkills({ silent: true });
|
|
479
479
|
}
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
openSkillsMenu() {
|
|
483
|
+
// Open skills manager modal as menu
|
|
484
|
+
this.openSkillsManager();
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
async importSingleSkill(skill) {
|
|
488
|
+
if (this.skillsImporting || this.skillsZipImporting) return;
|
|
489
|
+
const key = this.buildSkillImportKey(skill);
|
|
490
|
+
this.skillsImporting = true;
|
|
491
|
+
try {
|
|
492
|
+
const res = await api('import-skills', {
|
|
493
|
+
targetApp: this.skillsTargetApp,
|
|
494
|
+
imports: [skill]
|
|
495
|
+
});
|
|
496
|
+
if (res && res.error) {
|
|
497
|
+
this.showMessage(res.error, 'error');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const imported = Array.isArray(res && res.imported) ? res.imported : [];
|
|
501
|
+
if (imported.length > 0) {
|
|
502
|
+
this.showMessage(`已导入 ${skill.displayName || skill.name}`, 'success');
|
|
503
|
+
await this.refreshSkillsList({ silent: true });
|
|
504
|
+
await this.scanImportableSkills({ silent: true });
|
|
505
|
+
}
|
|
506
|
+
} catch (e) {
|
|
507
|
+
this.showMessage('导入失败', 'error');
|
|
508
|
+
} finally {
|
|
509
|
+
this.skillsImporting = false;
|
|
510
|
+
}
|
|
480
511
|
}
|
|
481
512
|
};
|
|
482
513
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div id="app" class="container" v-cloak>
|
|
2
2
|
<div v-if="!sessionStandalone" class="top-tabs" role="tablist" :aria-label="t('nav.topTabs.aria')">
|
|
3
|
-
<button class="top-tab"
|
|
3
|
+
<button type="button" class="top-tab"
|
|
4
4
|
id="tab-dashboard"
|
|
5
5
|
role="tab"
|
|
6
6
|
data-main-tab="dashboard"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
:class="{ active: isMainTabNavActive('dashboard') }"
|
|
11
11
|
@pointerdown="onMainTabPointerDown('dashboard', $event)"
|
|
12
12
|
@click="onMainTabClick('dashboard', $event)">{{ t('tab.dashboard') }}</button>
|
|
13
|
-
<button class="top-tab"
|
|
13
|
+
<button type="button" class="top-tab"
|
|
14
14
|
id="tab-docs"
|
|
15
15
|
role="tab"
|
|
16
16
|
data-main-tab="docs"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:class="{ active: isMainTabNavActive('docs') }"
|
|
21
21
|
@pointerdown="onMainTabPointerDown('docs', $event)"
|
|
22
22
|
@click="onMainTabClick('docs', $event)">{{ t('tab.docs') }}</button>
|
|
23
|
-
<button class="top-tab"
|
|
23
|
+
<button type="button" class="top-tab"
|
|
24
24
|
id="tab-config"
|
|
25
25
|
role="tab"
|
|
26
26
|
data-main-tab="config"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
:class="{ active: isMainTabNavActive('config') }"
|
|
32
32
|
@pointerdown="onMainTabPointerDown('config', $event)"
|
|
33
33
|
@click="onMainTabClick('config', $event)">{{ t('tab.config') }}</button>
|
|
34
|
-
<button class="top-tab"
|
|
34
|
+
<button type="button" class="top-tab"
|
|
35
35
|
id="tab-sessions"
|
|
36
36
|
role="tab"
|
|
37
37
|
data-main-tab="sessions"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
:class="{ active: isMainTabNavActive('sessions') }"
|
|
42
42
|
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
43
43
|
@click="onMainTabClick('sessions', $event)">{{ t('tab.sessions') }}</button>
|
|
44
|
-
<button class="top-tab"
|
|
44
|
+
<button type="button" class="top-tab"
|
|
45
45
|
id="tab-usage"
|
|
46
46
|
role="tab"
|
|
47
47
|
data-main-tab="usage"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
:class="{ active: isMainTabNavActive('usage') }"
|
|
52
52
|
@pointerdown="onMainTabPointerDown('usage', $event)"
|
|
53
53
|
@click="onMainTabClick('usage', $event)">{{ t('tab.usage') }}</button>
|
|
54
|
-
<button v-if="taskOrchestrationTabEnabled" class="top-tab"
|
|
54
|
+
<button v-if="taskOrchestrationTabEnabled" type="button" class="top-tab"
|
|
55
55
|
id="tab-orchestration"
|
|
56
56
|
role="tab"
|
|
57
57
|
data-main-tab="orchestration"
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
:class="{ active: isMainTabNavActive('orchestration') }"
|
|
62
62
|
@pointerdown="onMainTabPointerDown('orchestration', $event)"
|
|
63
63
|
@click="onMainTabClick('orchestration', $event)">{{ t('tab.orchestration') }}</button>
|
|
64
|
-
<button class="top-tab"
|
|
64
|
+
<button type="button" class="top-tab"
|
|
65
65
|
id="tab-market"
|
|
66
66
|
role="tab"
|
|
67
67
|
data-main-tab="market"
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
:class="{ active: isMainTabNavActive('market') }"
|
|
72
72
|
@pointerdown="onMainTabPointerDown('market', $event)"
|
|
73
73
|
@click="onMainTabClick('market', $event)">{{ t('tab.market') }}</button>
|
|
74
|
-
<button class="top-tab"
|
|
74
|
+
<button type="button" class="top-tab"
|
|
75
75
|
id="tab-plugins"
|
|
76
76
|
role="tab"
|
|
77
77
|
data-main-tab="plugins"
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
:class="{ active: isMainTabNavActive('plugins') }"
|
|
82
82
|
@pointerdown="onMainTabPointerDown('plugins', $event)"
|
|
83
83
|
@click="onMainTabClick('plugins', $event)">{{ t('tab.plugins') }}</button>
|
|
84
|
-
<button class="top-tab"
|
|
84
|
+
<button type="button" class="top-tab"
|
|
85
85
|
id="tab-settings"
|
|
86
86
|
role="tab"
|
|
87
87
|
data-main-tab="settings"
|
|
@@ -122,10 +122,9 @@
|
|
|
122
122
|
<div class="brand-head">
|
|
123
123
|
<img class="brand-logo" src="/res/logo-pack.webp" alt="Codex Mate logo">
|
|
124
124
|
<div class="brand-copy">
|
|
125
|
-
<div class="brand-kicker">Codex Mate
|
|
125
|
+
<div class="brand-kicker">Codex Mate<span v-if="appVersion" class="brand-version"> v{{ appVersion }}</span></div>
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
128
|
-
<div class="brand-subtitle">{{ t('brand.subtitle.localConfigSessionsWorkspace') }}</div>
|
|
129
128
|
</div>
|
|
130
129
|
|
|
131
130
|
<div class="side-rail-nav">
|
|
@@ -316,6 +315,10 @@
|
|
|
316
315
|
</div>
|
|
317
316
|
</button>
|
|
318
317
|
</div>
|
|
318
|
+
<div id="side-tab-new" class="side-item side-item-ghost" tabindex="-1" aria-hidden="true">
|
|
319
|
+
<div class="side-item-title">New Tab</div>
|
|
320
|
+
<div class="side-item-meta"><span> </span></div>
|
|
321
|
+
</div>
|
|
319
322
|
</div>
|
|
320
323
|
|
|
321
324
|
<div class="side-rail-lang" role="group" :aria-label="t('lang.label')">
|
|
@@ -479,6 +482,12 @@
|
|
|
479
482
|
<span class="value">{{ installRegistryPreview || t('common.defaultOfficial') }}</span>
|
|
480
483
|
</div>
|
|
481
484
|
</div>
|
|
485
|
+
<div class="status-strip status-strip-placeholder" v-else-if="!sessionStandalone" aria-hidden="true">
|
|
486
|
+
<div class="status-chip">
|
|
487
|
+
<span class="label"> </span>
|
|
488
|
+
<span class="value"> </span>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
482
491
|
<div
|
|
483
492
|
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && displayProvidersList.length > 1"
|
|
484
493
|
class="provider-fast-switch">
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
</div>
|
|
101
101
|
|
|
102
102
|
<div class="card-list">
|
|
103
|
-
<div :class="['card', { active: currentClaudeConfig === 'claude-local' }]" @click="applyClaudeLocalBridge()" @keydown.enter.self.prevent="applyClaudeLocalBridge()" @keydown.space.self.prevent="applyClaudeLocalBridge()" tabindex="0" role="button" :aria-current="currentClaudeConfig === 'claude-local' ? 'true' : null">
|
|
103
|
+
<div :class="['card', { active: currentClaudeConfig === 'claude-local', disabled: isClaudeLocalBridgeDisabled() }]" @click="isClaudeLocalBridgeDisabled() ? null : applyClaudeLocalBridge()" @keydown.enter.self.prevent="isClaudeLocalBridgeDisabled() ? null : applyClaudeLocalBridge()" @keydown.space.self.prevent="isClaudeLocalBridgeDisabled() ? null : applyClaudeLocalBridge()" :tabindex="isClaudeLocalBridgeDisabled() ? -1 : 0" role="button" :aria-current="currentClaudeConfig === 'claude-local' ? 'true' : null">
|
|
104
104
|
<div class="card-leading">
|
|
105
105
|
<div class="card-icon">L</div>
|
|
106
106
|
<div class="card-content">
|
|
@@ -108,8 +108,10 @@
|
|
|
108
108
|
<svg class="bridge-pool-summary-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
|
|
109
109
|
<span class="bridge-pool-summary-text">{{ t('claude.localBridge.enabled') }} {{ claudeLocalBridgeCandidateProviders().filter(cp => !isClaudeLocalBridgeExcluded(cp.name)).length }} / {{ claudeLocalBridgeCandidateProviders().length }}</span>
|
|
110
110
|
</div>
|
|
111
|
-
<div class="card-title">
|
|
112
|
-
|
|
111
|
+
<div class="card-title">
|
|
112
|
+
<span>local</span>
|
|
113
|
+
<span class="provider-readonly-badge">{{ t('config.badge.system') }}</span>
|
|
114
|
+
</div>
|
|
113
115
|
</div>
|
|
114
116
|
</div>
|
|
115
117
|
<div class="card-trailing">
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
</template>
|
|
121
121
|
|
|
122
122
|
<div v-if="!loading && !initError" class="card-list">
|
|
123
|
-
<div v-for="provider in displayProvidersList" :key="provider.name" :class="['card', { active: displayCurrentProvider === provider.name }]" @click="switchProvider(provider.name)" @keydown.enter.self.prevent="switchProvider(provider.name)" @keydown.space.self.prevent="switchProvider(provider.name)" tabindex="0" role="button" :aria-current="displayCurrentProvider === provider.name ? 'true' : null">
|
|
123
|
+
<div v-for="provider in displayProvidersList" :key="provider.name" :class="['card', { active: displayCurrentProvider === provider.name, disabled: provider.name === 'local' && isLocalProviderDisabled }]" @click="(provider.name === 'local' && isLocalProviderDisabled) ? null : switchProvider(provider.name)" @keydown.enter.self.prevent="(provider.name === 'local' && isLocalProviderDisabled) ? null : switchProvider(provider.name)" @keydown.space.self.prevent="(provider.name === 'local' && isLocalProviderDisabled) ? null : switchProvider(provider.name)" :tabindex="provider.name === 'local' && isLocalProviderDisabled ? -1 : 0" role="button" :aria-current="displayCurrentProvider === provider.name ? 'true' : null">
|
|
124
124
|
<div class="card-leading">
|
|
125
125
|
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}<span v-if="isTransformProvider(provider)" class="card-icon-dot" title="通过内建转换适配"></span></div>
|
|
126
126
|
<div class="card-content">
|