codexmate 0.1.16 → 0.1.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/cli/skills.js +18 -1
- package/cli.js +46 -1
- package/package.json +1 -1
- package/plugins/prompt-templates/computed.mjs +5 -0
- package/web-ui/app.js +7 -0
- package/web-ui/modules/app.computed.session.mjs +83 -12
- package/web-ui/modules/app.methods.navigation.mjs +1 -1
- package/web-ui/modules/app.methods.pi-config.mjs +80 -0
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +3 -3
- package/web-ui/modules/i18n/locales/en.mjs +10 -0
- package/web-ui/modules/i18n/locales/ja.mjs +10 -0
- package/web-ui/modules/i18n/locales/vi.mjs +10 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +10 -0
- package/web-ui/modules/i18n/locales/zh.mjs +10 -0
- package/web-ui/modules/skills.computed.mjs +6 -2
- package/web-ui/modules/skills.methods.mjs +1 -1
- package/web-ui/partials/index/modal-skills.html +8 -0
- package/web-ui/partials/index/panel-config-pi.html +44 -0
- package/web-ui/partials/index/panel-docs.html +8 -22
- package/web-ui/partials/index/panel-market.html +8 -0
- package/web-ui/partials/index/panel-plugins.html +14 -13
- package/web-ui/partials/index/panel-usage.html +27 -25
- package/web-ui/res/web-ui-render.precompiled.js +432 -231
- package/web-ui/styles/docs-panel.css +9 -56
- package/web-ui/styles/plugins-panel.css +18 -18
- package/web-ui/styles/sessions-usage.css +104 -91
package/cli/skills.js
CHANGED
|
@@ -24,6 +24,7 @@ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
|
24
24
|
const CODEX_SKILLS_DIR = path.join(CONFIG_DIR, 'skills');
|
|
25
25
|
const CLAUDE_SKILLS_DIR = path.join(CLAUDE_DIR, 'skills');
|
|
26
26
|
const AGENTS_SKILLS_DIR = path.join(os.homedir(), '.agents', 'skills');
|
|
27
|
+
const PI_SKILLS_DIR = path.join(os.homedir(), '.pi', 'agent', 'skills');
|
|
27
28
|
|
|
28
29
|
const MAX_UPLOAD_SIZE = 200 * 1024 * 1024;
|
|
29
30
|
const MAX_SKILLS_ZIP_UPLOAD_SIZE = 20 * 1024 * 1024;
|
|
@@ -72,9 +73,24 @@ function getClaudeSkillsDir() {
|
|
|
72
73
|
return resolveExistingDir([homeConfigDir], CLAUDE_SKILLS_DIR);
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
function getPiSkillsDir() {
|
|
77
|
+
const joinPath = (basePath, ...segments) => {
|
|
78
|
+
const base = typeof basePath === 'string' ? basePath.trim() : '';
|
|
79
|
+
const pathApi = base.includes('/') && !base.includes('\\') && path.posix ? path.posix : path;
|
|
80
|
+
return pathApi.join(base, ...segments);
|
|
81
|
+
};
|
|
82
|
+
const envPiDir = typeof process.env.PI_CODING_AGENT_DIR === 'string' ? process.env.PI_CODING_AGENT_DIR.trim() : '';
|
|
83
|
+
if (envPiDir) {
|
|
84
|
+
const target = joinPath(envPiDir, 'skills');
|
|
85
|
+
return resolveExistingDir([target], target);
|
|
86
|
+
}
|
|
87
|
+
return resolveExistingDir([PI_SKILLS_DIR], PI_SKILLS_DIR);
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
const SKILL_TARGETS = Object.freeze([
|
|
76
91
|
Object.freeze({ app: 'codex', label: 'Codex', dir: getCodexSkillsDir() }),
|
|
77
|
-
Object.freeze({ app: 'claude', label: 'Claude Code', dir: getClaudeSkillsDir() })
|
|
92
|
+
Object.freeze({ app: 'claude', label: 'Claude Code', dir: getClaudeSkillsDir() }),
|
|
93
|
+
Object.freeze({ app: 'pi', label: 'Pi', dir: getPiSkillsDir() })
|
|
78
94
|
]);
|
|
79
95
|
|
|
80
96
|
const SKILL_IMPORT_SOURCES = Object.freeze([
|
|
@@ -1119,6 +1135,7 @@ module.exports = {
|
|
|
1119
1135
|
SKILL_IMPORT_SOURCES,
|
|
1120
1136
|
getCodexSkillsDir,
|
|
1121
1137
|
getClaudeSkillsDir,
|
|
1138
|
+
getPiSkillsDir,
|
|
1122
1139
|
normalizeSkillTargetApp,
|
|
1123
1140
|
getSkillTargetByApp,
|
|
1124
1141
|
resolveSkillTarget,
|
package/cli.js
CHANGED
|
@@ -1094,7 +1094,9 @@ function normalizeWebUiPreferences(value = {}) {
|
|
|
1094
1094
|
mainTab: normalizeMainTabPreference(navigation.mainTab),
|
|
1095
1095
|
configMode: normalizeConfigModePreference(navigation.configMode),
|
|
1096
1096
|
settingsTab: normalizeSettingsTabPreference(navigation.settingsTab),
|
|
1097
|
-
skillsTargetApp: navigation.skillsTargetApp === 'claude'
|
|
1097
|
+
skillsTargetApp: navigation.skillsTargetApp === 'claude' || navigation.skillsTargetApp === 'pi'
|
|
1098
|
+
? navigation.skillsTargetApp
|
|
1099
|
+
: 'codex',
|
|
1098
1100
|
promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose'
|
|
1099
1101
|
}
|
|
1100
1102
|
};
|
|
@@ -1247,6 +1249,7 @@ function writePiModels(params = {}) {
|
|
|
1247
1249
|
fs.mkdirSync(dir, { recursive: true });
|
|
1248
1250
|
const filePath = path.join(dir, 'models.json');
|
|
1249
1251
|
if (file) {
|
|
1252
|
+
historyBackup('pi-models', filePath);
|
|
1250
1253
|
fs.writeFileSync(filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
1251
1254
|
return { success: true };
|
|
1252
1255
|
}
|
|
@@ -1312,6 +1315,7 @@ function writePiSettings(params = {}) {
|
|
|
1312
1315
|
fs.mkdirSync(dir, { recursive: true });
|
|
1313
1316
|
const filePath = path.join(dir, 'settings.json');
|
|
1314
1317
|
if (fullSettings) {
|
|
1318
|
+
historyBackup('pi-settings', filePath);
|
|
1315
1319
|
fs.writeFileSync(filePath, JSON.stringify(fullSettings, null, 2), 'utf-8');
|
|
1316
1320
|
return { success: true, settings: fullSettings };
|
|
1317
1321
|
}
|
|
@@ -1332,6 +1336,43 @@ function writePiSettings(params = {}) {
|
|
|
1332
1336
|
}
|
|
1333
1337
|
}
|
|
1334
1338
|
|
|
1339
|
+
function applyPiConfigHistory(params = {}) {
|
|
1340
|
+
const target = typeof params.target === 'string' ? params.target.trim() : '';
|
|
1341
|
+
if (target !== 'settings' && target !== 'models') {
|
|
1342
|
+
return { error: '无效的历史记录目标' };
|
|
1343
|
+
}
|
|
1344
|
+
const bucket = target === 'settings' ? 'pi-settings' : 'pi-models';
|
|
1345
|
+
const fileName = target === 'settings' ? 'settings.json' : 'models.json';
|
|
1346
|
+
let entry;
|
|
1347
|
+
try {
|
|
1348
|
+
entry = readPromptHistory(bucket, String(params.id || ''));
|
|
1349
|
+
} catch (_) {
|
|
1350
|
+
return { error: '历史记录不存在或读取失败' };
|
|
1351
|
+
}
|
|
1352
|
+
const content = entry && typeof entry.content === 'string' ? entry.content : '';
|
|
1353
|
+
let parsed = null;
|
|
1354
|
+
if (content) {
|
|
1355
|
+
try {
|
|
1356
|
+
parsed = JSON.parse(content);
|
|
1357
|
+
} catch (_) {
|
|
1358
|
+
parsed = null;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
1362
|
+
return { error: '历史记录内容不是有效的 JSON 对象' };
|
|
1363
|
+
}
|
|
1364
|
+
try {
|
|
1365
|
+
const filePath = path.join(getPiAgentDir(), fileName);
|
|
1366
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1367
|
+
historyBackup(bucket, filePath);
|
|
1368
|
+
fs.writeFileSync(filePath, JSON.stringify(parsed, null, 2), 'utf-8');
|
|
1369
|
+
} catch (e) {
|
|
1370
|
+
return { error: (e && e.message) || '应用历史记录失败' };
|
|
1371
|
+
}
|
|
1372
|
+
if (target === 'settings') return { success: true, settings: parsed };
|
|
1373
|
+
return { success: true, file: parsed };
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1335
1376
|
function setToolConfigPermission(params = {}) {
|
|
1336
1377
|
const target = normalizeToolConfigTarget(params && params.target);
|
|
1337
1378
|
if (!target) return { error: '未知配置对象' };
|
|
@@ -13315,6 +13356,10 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13315
13356
|
result = writePiSettings(params || {});
|
|
13316
13357
|
break;
|
|
13317
13358
|
}
|
|
13359
|
+
case 'apply-pi-config-history': {
|
|
13360
|
+
result = applyPiConfigHistory(params || {});
|
|
13361
|
+
break;
|
|
13362
|
+
}
|
|
13318
13363
|
case 'fetch-pi-remote-models': {
|
|
13319
13364
|
result = await fetchPiRemoteModels(params || {});
|
|
13320
13365
|
break;
|
package/package.json
CHANGED
|
@@ -167,6 +167,11 @@ export function createPluginsComputed() {
|
|
|
167
167
|
.map((meta) => localizePluginMeta(meta, this.t));
|
|
168
168
|
},
|
|
169
169
|
|
|
170
|
+
pluginsSidebarVisible() {
|
|
171
|
+
const catalog = Array.isArray(this.pluginsCatalog) ? this.pluginsCatalog : [];
|
|
172
|
+
return catalog.length > 1;
|
|
173
|
+
},
|
|
174
|
+
|
|
170
175
|
pluginsActiveMeta() {
|
|
171
176
|
const id = typeof this.pluginsActiveId === 'string' ? this.pluginsActiveId.trim() : '';
|
|
172
177
|
const entry = pluginsRegistry.find((item) => item && item.id === id) || null;
|
package/web-ui/app.js
CHANGED
|
@@ -153,6 +153,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
153
153
|
piModelsJsonDraft: '',
|
|
154
154
|
piModelsJsonError: '',
|
|
155
155
|
piFileJsonSaving: false,
|
|
156
|
+
piHistoryTarget: '',
|
|
157
|
+
piHistoryLoading: false,
|
|
158
|
+
piHistoryItems: [],
|
|
159
|
+
piHistoryPreviewId: '',
|
|
160
|
+
piHistoryPreviewContent: '',
|
|
161
|
+
piHistoryError: '',
|
|
162
|
+
piHistoryApplying: false,
|
|
156
163
|
// Plugins
|
|
157
164
|
pluginsActiveId: 'prompt-templates',
|
|
158
165
|
pluginsLoading: false,
|
|
@@ -896,26 +896,97 @@ export function createSessionComputed() {
|
|
|
896
896
|
};
|
|
897
897
|
},
|
|
898
898
|
|
|
899
|
-
|
|
899
|
+
usageKpiCards() {
|
|
900
900
|
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
|
|
901
901
|
? this.sessionUsageCharts.summary
|
|
902
902
|
: null;
|
|
903
|
-
if (!summary) return
|
|
904
|
-
|
|
903
|
+
if (!summary) return [];
|
|
904
|
+
const t = typeof this.t === 'function' ? this.t : null;
|
|
905
|
+
const totalTokens = summary.totalTokens || 0;
|
|
906
|
+
const activeDays = summary.activeDays || 0;
|
|
907
|
+
const dailyAvgTokens = activeDays > 0 ? Math.round(totalTokens / activeDays) : 0;
|
|
908
|
+
const noneLabel = t ? t('common.none') : '暂无';
|
|
909
|
+
const busiestDay = summary.busiestDay;
|
|
910
|
+
return [
|
|
911
|
+
{
|
|
912
|
+
key: 'tokens',
|
|
913
|
+
label: t ? t('usage.kpi.tokens') : '总 Token',
|
|
914
|
+
value: formatCompactUsageSummaryNumber(totalTokens),
|
|
915
|
+
title: formatUsageSummaryNumber(totalTokens),
|
|
916
|
+
delta: this.usageHeroDelta,
|
|
917
|
+
deltaClass: this.usageHeroDeltaClass
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
key: 'sessions',
|
|
921
|
+
label: t ? t('usage.kpi.sessions') : '会话数',
|
|
922
|
+
value: formatUsageSummaryNumber(summary.totalSessions || 0)
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
key: 'messages',
|
|
926
|
+
label: t ? t('usage.kpi.messages') : '消息数',
|
|
927
|
+
value: formatUsageSummaryNumber(summary.totalMessages || 0)
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
key: 'daily-avg',
|
|
931
|
+
label: t ? t('usage.kpi.dailyAvg') : '日均 Token',
|
|
932
|
+
value: formatCompactUsageSummaryNumber(dailyAvgTokens),
|
|
933
|
+
title: formatUsageSummaryNumber(dailyAvgTokens)
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
key: 'busiest-day',
|
|
937
|
+
label: t ? t('usage.kpi.busiestDay') : '最忙日',
|
|
938
|
+
value: busiestDay && busiestDay.totalSessions > 0
|
|
939
|
+
? `${busiestDay.label} · ${busiestDay.totalSessions}`
|
|
940
|
+
: noneLabel
|
|
941
|
+
}
|
|
942
|
+
];
|
|
905
943
|
},
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
? this.sessionUsageCharts.summary
|
|
944
|
+
usageWaveHeaderSummary() {
|
|
945
|
+
const daily = this.sessionUsageDaily && typeof this.sessionUsageDaily === 'object'
|
|
946
|
+
? this.sessionUsageDaily
|
|
910
947
|
: null;
|
|
911
|
-
|
|
948
|
+
const rows = daily && Array.isArray(daily.rows) ? daily.rows : [];
|
|
949
|
+
if (!rows.length) return '';
|
|
950
|
+
let totalTokens = 0;
|
|
951
|
+
let peakRow = rows[0];
|
|
952
|
+
for (const row of rows) {
|
|
953
|
+
const tokens = Number.isFinite(Number(row && row.tokenTotal)) ? Number(row.tokenTotal) : 0;
|
|
954
|
+
totalTokens += tokens;
|
|
955
|
+
if (tokens > (Number(peakRow.tokenTotal) || 0)) peakRow = row;
|
|
956
|
+
}
|
|
912
957
|
const t = typeof this.t === 'function' ? this.t : null;
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
958
|
+
const params = {
|
|
959
|
+
total: formatCompactUsageSummaryNumber(totalTokens),
|
|
960
|
+
peakDay: peakRow.key || '',
|
|
961
|
+
peakValue: formatCompactUsageSummaryNumber(peakRow.tokenTotal || 0)
|
|
962
|
+
};
|
|
963
|
+
return t
|
|
964
|
+
? t('usage.wave.summary', params)
|
|
965
|
+
: `本期共 ${params.total} · 峰值 ${params.peakDay}(${params.peakValue})`;
|
|
917
966
|
},
|
|
918
967
|
|
|
968
|
+
usageRankedLists() {
|
|
969
|
+
const charts = this.sessionUsageCharts && typeof this.sessionUsageCharts === 'object'
|
|
970
|
+
? this.sessionUsageCharts
|
|
971
|
+
: {};
|
|
972
|
+
const withBars = (list, pickValue) => {
|
|
973
|
+
const items = Array.isArray(list) ? list : [];
|
|
974
|
+
const max = items.reduce((acc, item) => Math.max(acc, pickValue(item) || 0), 0);
|
|
975
|
+
return items.map((item) => ({
|
|
976
|
+
...item,
|
|
977
|
+
barPercent: max > 0
|
|
978
|
+
? Math.max(6, Math.round(((pickValue(item) || 0) / max) * 1000) / 10)
|
|
979
|
+
: 0
|
|
980
|
+
}));
|
|
981
|
+
};
|
|
982
|
+
return {
|
|
983
|
+
topSessions: withBars(charts.topSessionsByMessages, (item) => item && item.messageCount),
|
|
984
|
+
recentSessions: withBars(charts.recentSessions, (item) => item && item.messageCount),
|
|
985
|
+
topPaths: withBars(charts.topPaths, (item) => item && item.count)
|
|
986
|
+
};
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
|
|
919
990
|
usageHeroDelta() {
|
|
920
991
|
const range = this.sessionsUsageTimeRange;
|
|
921
992
|
if (range === 'all') return null;
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
const mainTab = normalizeMainTab(mainTabSource);
|
|
109
109
|
const configMode = typeof configModeSource === 'string' ? configModeSource.trim().toLowerCase() : '';
|
|
110
110
|
const settingsTab = typeof vm.settingsTab === 'string' ? vm.settingsTab.trim().toLowerCase() : 'general';
|
|
111
|
-
const skillsTargetApp = typeof vm.skillsTargetApp === 'string' && (vm.skillsTargetApp === 'codex' || vm.skillsTargetApp === 'claude') ? vm.skillsTargetApp : 'codex';
|
|
111
|
+
const skillsTargetApp = typeof vm.skillsTargetApp === 'string' && (vm.skillsTargetApp === 'codex' || vm.skillsTargetApp === 'claude' || vm.skillsTargetApp === 'pi') ? vm.skillsTargetApp : 'codex';
|
|
112
112
|
const promptTemplatesMode = typeof vm.promptTemplatesMode === 'string' && (vm.promptTemplatesMode === 'compose' || vm.promptTemplatesMode === 'manage') ? vm.promptTemplatesMode : 'compose';
|
|
113
113
|
const snapshot = {
|
|
114
114
|
settingsTab: settingsTab === 'data' ? 'data' : 'general',
|
|
@@ -475,6 +475,86 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
475
475
|
piEditorJsonDraftInput() {
|
|
476
476
|
const draft = this.piParseEditorJsonDraft();
|
|
477
477
|
this.piEditorJsonError = draft.ok ? '' : draft.error;
|
|
478
|
+
},
|
|
479
|
+
piHistoryBucketFor(target) {
|
|
480
|
+
return target === 'settings' ? 'pi-settings' : 'pi-models';
|
|
481
|
+
},
|
|
482
|
+
async openPiConfigHistory(target) {
|
|
483
|
+
if (this.piHistoryLoading) return;
|
|
484
|
+
const tgt = target === 'settings' ? 'settings' : (target === 'models' ? 'models' : '');
|
|
485
|
+
if (!tgt) return;
|
|
486
|
+
this.piHistoryTarget = tgt;
|
|
487
|
+
this.piHistoryItems = [];
|
|
488
|
+
this.piHistoryPreviewId = '';
|
|
489
|
+
this.piHistoryPreviewContent = '';
|
|
490
|
+
this.piHistoryError = '';
|
|
491
|
+
this.piHistoryLoading = true;
|
|
492
|
+
try {
|
|
493
|
+
const res = await api('list-prompt-history', { bucket: this.piHistoryBucketFor(tgt) });
|
|
494
|
+
if (res && res.error) {
|
|
495
|
+
this.piHistoryError = res.error;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.piHistoryItems = Array.isArray(res) ? res : [];
|
|
499
|
+
} catch (e) {
|
|
500
|
+
this.piHistoryError = this.t('toast.load.fail');
|
|
501
|
+
} finally {
|
|
502
|
+
this.piHistoryLoading = false;
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
closePiConfigHistory() {
|
|
506
|
+
this.piHistoryTarget = '';
|
|
507
|
+
this.piHistoryItems = [];
|
|
508
|
+
this.piHistoryPreviewId = '';
|
|
509
|
+
this.piHistoryPreviewContent = '';
|
|
510
|
+
this.piHistoryError = '';
|
|
511
|
+
this.piHistoryLoading = false;
|
|
512
|
+
},
|
|
513
|
+
async viewPiConfigHistoryItem(item) {
|
|
514
|
+
if (!item || !item.id) return;
|
|
515
|
+
if (this.piHistoryLoading) return;
|
|
516
|
+
if (this.piHistoryPreviewId === item.id && this.piHistoryPreviewContent) return;
|
|
517
|
+
this.piHistoryPreviewId = item.id;
|
|
518
|
+
this.piHistoryPreviewContent = '';
|
|
519
|
+
try {
|
|
520
|
+
const res = await api('get-prompt-history', { bucket: this.piHistoryBucketFor(this.piHistoryTarget), id: item.id });
|
|
521
|
+
if (res && res.error) {
|
|
522
|
+
this.piHistoryError = res.error;
|
|
523
|
+
this.piHistoryPreviewId = '';
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.piHistoryPreviewContent = typeof res.content === 'string' ? res.content : '';
|
|
527
|
+
} catch (e) {
|
|
528
|
+
this.piHistoryError = this.t('toast.load.fail');
|
|
529
|
+
this.piHistoryPreviewId = '';
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
async applyPiConfigHistory() {
|
|
533
|
+
if (this.piHistoryApplying || this.piHistoryLoading) return;
|
|
534
|
+
if (!this.piHistoryTarget || !this.piHistoryPreviewId) return;
|
|
535
|
+
const confirmed = await this.requestConfirmDialog({
|
|
536
|
+
title: this.t('common.history'),
|
|
537
|
+
message: this.t('pi.history.confirm'),
|
|
538
|
+
confirmText: this.t('confirm.ok'),
|
|
539
|
+
cancelText: this.t('confirm.cancel'),
|
|
540
|
+
danger: true
|
|
541
|
+
});
|
|
542
|
+
if (!confirmed) return;
|
|
543
|
+
this.piHistoryApplying = true;
|
|
544
|
+
try {
|
|
545
|
+
const res = await api('apply-pi-config-history', { target: this.piHistoryTarget, id: this.piHistoryPreviewId });
|
|
546
|
+
if (res && res.error) {
|
|
547
|
+
this.showMessage(res.error, 'error');
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this.showMessage(this.t('pi.history.applied'), 'success');
|
|
551
|
+
this.closePiConfigHistory();
|
|
552
|
+
await this.loadPiSources();
|
|
553
|
+
} catch (e) {
|
|
554
|
+
this.showMessage(this.t('pi.history.applyFailed'), 'error');
|
|
555
|
+
} finally {
|
|
556
|
+
this.piHistoryApplying = false;
|
|
557
|
+
}
|
|
478
558
|
}
|
|
479
559
|
};
|
|
480
560
|
}
|
|
@@ -102,13 +102,13 @@ function normalizeSessionSortMode(value) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function normalizeNavigationSnapshot(vm, source = {}) {
|
|
105
|
-
const currentSkillsTargetApp = vm.skillsTargetApp === 'claude'
|
|
105
|
+
const currentSkillsTargetApp = vm.skillsTargetApp === 'claude' || vm.skillsTargetApp === 'pi' ? vm.skillsTargetApp : 'codex';
|
|
106
106
|
const currentPromptTemplatesMode = vm.promptTemplatesMode === 'manage' ? 'manage' : 'compose';
|
|
107
107
|
return {
|
|
108
108
|
mainTab: typeof source.mainTab === 'string' ? source.mainTab : vm.mainTab,
|
|
109
109
|
configMode: typeof source.configMode === 'string' ? source.configMode : vm.configMode,
|
|
110
110
|
settingsTab: typeof source.settingsTab === 'string' ? source.settingsTab : vm.settingsTab,
|
|
111
|
-
skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex'
|
|
111
|
+
skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex' || source.skillsTargetApp === 'pi'
|
|
112
112
|
? source.skillsTargetApp
|
|
113
113
|
: currentSkillsTargetApp,
|
|
114
114
|
promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
|
|
@@ -477,7 +477,7 @@ export function createWebUiPreferencesMethods(options = {}) {
|
|
|
477
477
|
this.mainTab = nav.mainTab;
|
|
478
478
|
}
|
|
479
479
|
}
|
|
480
|
-
if (nav.skillsTargetApp === 'codex' || nav.skillsTargetApp === 'claude') this.skillsTargetApp = nav.skillsTargetApp;
|
|
480
|
+
if (nav.skillsTargetApp === 'codex' || nav.skillsTargetApp === 'claude' || nav.skillsTargetApp === 'pi') this.skillsTargetApp = nav.skillsTargetApp;
|
|
481
481
|
if (nav.promptTemplatesMode === 'compose' || nav.promptTemplatesMode === 'manage') this.promptTemplatesMode = nav.promptTemplatesMode;
|
|
482
482
|
if (typeof this.saveNavState === 'function') this.saveNavState();
|
|
483
483
|
}
|
|
@@ -827,6 +827,12 @@ const en = Object.freeze({
|
|
|
827
827
|
'usage.summary.avgMessagesPerSession': 'Avg messages/session',
|
|
828
828
|
'usage.summary.busiestDay': 'Busiest day',
|
|
829
829
|
'usage.summary.busiestHour': 'Peak hour',
|
|
830
|
+
'usage.kpi.tokens': 'Total tokens',
|
|
831
|
+
'usage.kpi.sessions': 'Sessions',
|
|
832
|
+
'usage.kpi.messages': 'Messages',
|
|
833
|
+
'usage.kpi.dailyAvg': 'Avg tokens / day',
|
|
834
|
+
'usage.kpi.busiestDay': 'Busiest day',
|
|
835
|
+
'usage.wave.summary': '{total} total · peak {peakDay} ({peakValue})',
|
|
830
836
|
'usage.currentSession.title': 'Current session',
|
|
831
837
|
'usage.currentSession.apiDuration': 'API duration',
|
|
832
838
|
'usage.currentSession.totalDuration': 'Total duration',
|
|
@@ -1358,6 +1364,10 @@ const en = Object.freeze({
|
|
|
1358
1364
|
'pi.files.desc': 'View and edit Pi settings.json and models.json in full; JSON validity is checked before saving and invalid content is never written.',
|
|
1359
1365
|
'pi.files.settingsTitle': 'settings.json (active settings)',
|
|
1360
1366
|
'pi.files.modelsTitle': 'models.json (providers and models)',
|
|
1367
|
+
'pi.history.apply': 'Apply this record',
|
|
1368
|
+
'pi.history.applied': 'History record applied',
|
|
1369
|
+
'pi.history.applyFailed': 'Failed to apply history record',
|
|
1370
|
+
'pi.history.confirm': 'Apply this history record to the file? The current file content will be snapshotted first.',
|
|
1361
1371
|
'pi.providers.models.remove': 'Remove',
|
|
1362
1372
|
'pi.providers.models.search': 'Search available models…',
|
|
1363
1373
|
'pi.providers.models.remoteCount': '{count} models available, click to add',
|
|
@@ -830,6 +830,12 @@ const ja = Object.freeze({
|
|
|
830
830
|
'usage.summary.avgMessagesPerSession': 'セッションあたり平均メッセージ',
|
|
831
831
|
'usage.summary.busiestDay': '最も忙しい日',
|
|
832
832
|
'usage.summary.busiestHour': 'ピーク時間帯',
|
|
833
|
+
'usage.kpi.tokens': '合計 Token',
|
|
834
|
+
'usage.kpi.sessions': 'セッション数',
|
|
835
|
+
'usage.kpi.messages': 'メッセージ数',
|
|
836
|
+
'usage.kpi.dailyAvg': '1 日あたり Token',
|
|
837
|
+
'usage.kpi.busiestDay': '最も忙しい日',
|
|
838
|
+
'usage.wave.summary': '合計 {total} · ピーク {peakDay}({peakValue})',
|
|
833
839
|
'usage.currentSession.title': '現在のセッション',
|
|
834
840
|
'usage.currentSession.apiDuration': 'API 時間',
|
|
835
841
|
'usage.currentSession.totalDuration': '総時間',
|
|
@@ -1359,6 +1365,10 @@ const ja = Object.freeze({
|
|
|
1359
1365
|
'pi.files.desc': 'Pi の settings.json と models.json を全文表示して直接編集します。保存前に JSON の正当性を検証し、不正な内容は書き込みません。',
|
|
1360
1366
|
'pi.files.settingsTitle': 'settings.json(有効な設定)',
|
|
1361
1367
|
'pi.files.modelsTitle': 'models.json(プロバイダーとモデル)',
|
|
1368
|
+
'pi.history.apply': 'この履歴を適用',
|
|
1369
|
+
'pi.history.applied': '履歴を適用しました',
|
|
1370
|
+
'pi.history.applyFailed': '履歴の適用に失敗しました',
|
|
1371
|
+
'pi.history.confirm': 'この履歴をファイルに適用しますか?現在の内容は先にスナップショット保存されます。',
|
|
1362
1372
|
'pi.providers.models.remove': '削除',
|
|
1363
1373
|
'pi.providers.models.search': '利用可能なモデルを検索…',
|
|
1364
1374
|
'pi.providers.models.remoteCount': '{count} 個のモデルを取得しました。クリックで追加',
|
|
@@ -1000,6 +1000,12 @@ const vi = Object.freeze({
|
|
|
1000
1000
|
'usage.summary.avgMessagesPerSession': 'TB tin nhắn/phiên',
|
|
1001
1001
|
'usage.summary.busiestDay': 'Ngày bận nhất',
|
|
1002
1002
|
'usage.summary.busiestHour': 'Giờ cao điểm',
|
|
1003
|
+
'usage.kpi.tokens': 'Tổng token',
|
|
1004
|
+
'usage.kpi.sessions': 'Số phiên',
|
|
1005
|
+
'usage.kpi.messages': 'Số tin nhắn',
|
|
1006
|
+
'usage.kpi.dailyAvg': 'Token trung bình/ngày',
|
|
1007
|
+
'usage.kpi.busiestDay': 'Ngày bận nhất',
|
|
1008
|
+
'usage.wave.summary': 'Tổng {total} · đỉnh {peakDay} ({peakValue})',
|
|
1003
1009
|
'usage.currentSession.title': 'Phiên hiện tại',
|
|
1004
1010
|
'usage.currentSession.apiDuration': 'Thời gian API',
|
|
1005
1011
|
'usage.currentSession.totalDuration': 'Tổng thời gian',
|
|
@@ -1327,6 +1333,10 @@ const vi = Object.freeze({
|
|
|
1327
1333
|
'pi.files.desc': 'Hiển thị đầy đủ và chỉnh sửa trực tiếp settings.json và models.json của Pi; kiểm tra tính hợp lệ của JSON trước khi lưu, nội dung không hợp lệ sẽ không được ghi.',
|
|
1328
1334
|
'pi.files.settingsTitle': 'settings.json (cài đặt đang dùng)',
|
|
1329
1335
|
'pi.files.modelsTitle': 'models.json (nhà cung cấp và mô hình)',
|
|
1336
|
+
'pi.history.apply': 'Áp dụng bản ghi này',
|
|
1337
|
+
'pi.history.applied': 'Đã áp dụng lịch sử',
|
|
1338
|
+
'pi.history.applyFailed': 'Áp dụng lịch sử thất bại',
|
|
1339
|
+
'pi.history.confirm': 'Áp dụng bản ghi lịch sử này vào tệp? Nội dung hiện tại sẽ được chụp nhanh trước.',
|
|
1330
1340
|
'pi.providers.models.remove': 'Xóa',
|
|
1331
1341
|
'pi.providers.models.search': 'Tìm kiếm mô hình khả dụng…',
|
|
1332
1342
|
'pi.providers.models.remoteCount': 'Đã lấy {count} mô hình, bấm để thêm',
|
|
@@ -826,6 +826,12 @@ const zhTw = Object.freeze({
|
|
|
826
826
|
'usage.summary.avgMessagesPerSession': '平均每會話消息',
|
|
827
827
|
'usage.summary.busiestDay': '最忙日',
|
|
828
828
|
'usage.summary.busiestHour': '高峰時段',
|
|
829
|
+
'usage.kpi.tokens': '總 Token',
|
|
830
|
+
'usage.kpi.sessions': '會話數',
|
|
831
|
+
'usage.kpi.messages': '訊息數',
|
|
832
|
+
'usage.kpi.dailyAvg': '日均 Token',
|
|
833
|
+
'usage.kpi.busiestDay': '最忙日',
|
|
834
|
+
'usage.wave.summary': '本期共 {total} · 峰值 {peakDay}({peakValue})',
|
|
829
835
|
'usage.currentSession.title': '目前會話',
|
|
830
836
|
'usage.currentSession.apiDuration': 'API時長',
|
|
831
837
|
'usage.currentSession.totalDuration': '總時長',
|
|
@@ -1356,6 +1362,10 @@ const zhTw = Object.freeze({
|
|
|
1356
1362
|
'pi.files.desc': '完整展示並直接編輯 Pi 的 settings.json 與 models.json;儲存前校驗 JSON 合法性,非法內容不會寫入。',
|
|
1357
1363
|
'pi.files.settingsTitle': 'settings.json(生效設定)',
|
|
1358
1364
|
'pi.files.modelsTitle': 'models.json(供應商與模型)',
|
|
1365
|
+
'pi.history.apply': '套用此紀錄',
|
|
1366
|
+
'pi.history.applied': '已套用歷史紀錄',
|
|
1367
|
+
'pi.history.applyFailed': '套用歷史紀錄失敗',
|
|
1368
|
+
'pi.history.confirm': '將此歷史紀錄套用到檔案?目前檔案內容會先被快照儲存。',
|
|
1359
1369
|
'pi.providers.models.remove': '刪除',
|
|
1360
1370
|
'pi.providers.models.search': '搜尋可用模型…',
|
|
1361
1371
|
'pi.providers.models.remoteCount': '已取得 {count} 個可用模型,點擊新增',
|
|
@@ -826,6 +826,12 @@ const zh = Object.freeze({
|
|
|
826
826
|
'usage.summary.avgMessagesPerSession': '平均每会话消息',
|
|
827
827
|
'usage.summary.busiestDay': '最忙日',
|
|
828
828
|
'usage.summary.busiestHour': '高峰时段',
|
|
829
|
+
'usage.kpi.tokens': '总 Token',
|
|
830
|
+
'usage.kpi.sessions': '会话数',
|
|
831
|
+
'usage.kpi.messages': '消息数',
|
|
832
|
+
'usage.kpi.dailyAvg': '日均 Token',
|
|
833
|
+
'usage.kpi.busiestDay': '最忙日',
|
|
834
|
+
'usage.wave.summary': '本期共 {total} · 峰值 {peakDay}({peakValue})',
|
|
829
835
|
'usage.currentSession.title': '当前会话',
|
|
830
836
|
'usage.currentSession.apiDuration': 'API时长',
|
|
831
837
|
'usage.currentSession.totalDuration': '总时长',
|
|
@@ -1356,6 +1362,10 @@ const zh = Object.freeze({
|
|
|
1356
1362
|
'pi.files.desc': '完整展示并直接编辑 Pi 的 settings.json 与 models.json;保存前校验 JSON 合法性,非法内容不会写入。',
|
|
1357
1363
|
'pi.files.settingsTitle': 'settings.json(生效设置)',
|
|
1358
1364
|
'pi.files.modelsTitle': 'models.json(供应商与模型)',
|
|
1365
|
+
'pi.history.apply': '应用此记录',
|
|
1366
|
+
'pi.history.applied': '已应用历史记录',
|
|
1367
|
+
'pi.history.applyFailed': '应用历史记录失败',
|
|
1368
|
+
'pi.history.confirm': '将此历史记录应用到文件?当前文件内容会先被快照保存。',
|
|
1359
1369
|
'pi.providers.models.remove': '删除',
|
|
1360
1370
|
'pi.providers.models.search': '搜索可用模型…',
|
|
1361
1371
|
'pi.providers.models.remoteCount': '已拉取 {count} 个可用模型,点击添加',
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export function createSkillsComputed() {
|
|
2
2
|
return {
|
|
3
3
|
skillsTargetLabel() {
|
|
4
|
-
|
|
4
|
+
if (this.skillsTargetApp === 'claude') return 'Claude Code';
|
|
5
|
+
if (this.skillsTargetApp === 'pi') return 'Pi';
|
|
6
|
+
return 'Codex';
|
|
5
7
|
},
|
|
6
8
|
skillsDefaultRootPath() {
|
|
7
|
-
|
|
9
|
+
if (this.skillsTargetApp === 'claude') return '~/.claude/skills';
|
|
10
|
+
if (this.skillsTargetApp === 'pi') return '~/.pi/agent/skills';
|
|
11
|
+
return '~/.codex/skills';
|
|
8
12
|
},
|
|
9
13
|
filteredSkillsList() {
|
|
10
14
|
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
@@ -12,7 +12,7 @@ export function createSkillsMethods({ api }) {
|
|
|
12
12
|
if (app == null) {
|
|
13
13
|
return 'codex';
|
|
14
14
|
}
|
|
15
|
-
if (app === 'codex' || app === 'claude') {
|
|
15
|
+
if (app === 'codex' || app === 'claude' || app === 'pi') {
|
|
16
16
|
return app;
|
|
17
17
|
}
|
|
18
18
|
throw createUnsupportedSkillsTargetAppError(app);
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
@click="setSkillsTargetApp('claude', { silent: false })">
|
|
24
24
|
Claude Code
|
|
25
25
|
</button>
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'pi' }]"
|
|
29
|
+
:aria-pressed="skillsTargetApp === 'pi'"
|
|
30
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
31
|
+
@click="setSkillsTargetApp('pi', { silent: false })">
|
|
32
|
+
Pi
|
|
33
|
+
</button>
|
|
26
34
|
</div>
|
|
27
35
|
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
28
36
|
{{ skillsLoading ? t('common.refreshing') : t('common.refresh') }}
|
|
@@ -195,7 +195,29 @@
|
|
|
195
195
|
<p v-if="piSettingsJsonError" class="config-template-hint error-text">{{ piSettingsJsonError }}</p>
|
|
196
196
|
<div class="settings-tab-actions">
|
|
197
197
|
<button type="button" class="btn-tool" @click="savePiSettingsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piSettingsJsonError">{{ t('common.save') }}</button>
|
|
198
|
+
<button type="button" class="btn-mini" @click="openPiConfigHistory('settings')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
|
|
198
199
|
</div>
|
|
200
|
+
<details v-if="piHistoryTarget === 'settings'" class="prompt-history-panel" open>
|
|
201
|
+
<summary class="prompt-history-summary">
|
|
202
|
+
<span>{{ t('common.history') }}</span>
|
|
203
|
+
<button type="button" class="btn-mini prompt-history-close" @click="closePiConfigHistory" :title="t('common.close')">✕</button>
|
|
204
|
+
</summary>
|
|
205
|
+
<div class="prompt-history-body">
|
|
206
|
+
<ul v-if="!piHistoryLoading && !piHistoryError && piHistoryItems.length" class="prompt-history-list">
|
|
207
|
+
<li v-for="item in piHistoryItems" :key="item.id" :class="['prompt-history-item', { active: piHistoryPreviewId === item.id }]" @click="viewPiConfigHistoryItem(item)">
|
|
208
|
+
<span class="prompt-history-time">{{ item.id }}</span>
|
|
209
|
+
<span class="prompt-history-size">{{ Math.round(item.size / 1024 * 10) / 10 }} KB</span>
|
|
210
|
+
</li>
|
|
211
|
+
</ul>
|
|
212
|
+
<div v-if="piHistoryLoading" class="state-message">{{ t('diff.generating') }}</div>
|
|
213
|
+
<div v-else-if="piHistoryError" class="state-message error">{{ piHistoryError }}</div>
|
|
214
|
+
<div v-else-if="!piHistoryItems.length" class="state-message">{{ t('toast.history.empty') }}</div>
|
|
215
|
+
<div v-if="piHistoryPreviewContent" class="prompt-history-preview-wrap">
|
|
216
|
+
<pre class="prompt-history-preview">{{ piHistoryPreviewContent }}</pre>
|
|
217
|
+
<button type="button" class="btn-mini btn-confirm-mini" @click="applyPiConfigHistory" :disabled="piHistoryApplying || !isToolConfigWriteAllowed('pi')">{{ t('pi.history.apply') }}</button>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</details>
|
|
199
221
|
<label class="form-label">{{ t('pi.files.modelsTitle') }}</label>
|
|
200
222
|
<textarea
|
|
201
223
|
class="template-textarea config-json-editor"
|
|
@@ -208,7 +230,29 @@
|
|
|
208
230
|
<p v-if="piModelsJsonError" class="config-template-hint error-text">{{ piModelsJsonError }}</p>
|
|
209
231
|
<div class="settings-tab-actions">
|
|
210
232
|
<button type="button" class="btn-tool" @click="savePiModelsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piModelsJsonError">{{ t('common.save') }}</button>
|
|
233
|
+
<button type="button" class="btn-mini" @click="openPiConfigHistory('models')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
|
|
211
234
|
</div>
|
|
235
|
+
<details v-if="piHistoryTarget === 'models'" class="prompt-history-panel" open>
|
|
236
|
+
<summary class="prompt-history-summary">
|
|
237
|
+
<span>{{ t('common.history') }}</span>
|
|
238
|
+
<button type="button" class="btn-mini prompt-history-close" @click="closePiConfigHistory" :title="t('common.close')">✕</button>
|
|
239
|
+
</summary>
|
|
240
|
+
<div class="prompt-history-body">
|
|
241
|
+
<ul v-if="!piHistoryLoading && !piHistoryError && piHistoryItems.length" class="prompt-history-list">
|
|
242
|
+
<li v-for="item in piHistoryItems" :key="item.id" :class="['prompt-history-item', { active: piHistoryPreviewId === item.id }]" @click="viewPiConfigHistoryItem(item)">
|
|
243
|
+
<span class="prompt-history-time">{{ item.id }}</span>
|
|
244
|
+
<span class="prompt-history-size">{{ Math.round(item.size / 1024 * 10) / 10 }} KB</span>
|
|
245
|
+
</li>
|
|
246
|
+
</ul>
|
|
247
|
+
<div v-if="piHistoryLoading" class="state-message">{{ t('diff.generating') }}</div>
|
|
248
|
+
<div v-else-if="piHistoryError" class="state-message error">{{ piHistoryError }}</div>
|
|
249
|
+
<div v-else-if="!piHistoryItems.length" class="state-message">{{ t('toast.history.empty') }}</div>
|
|
250
|
+
<div v-if="piHistoryPreviewContent" class="prompt-history-preview-wrap">
|
|
251
|
+
<pre class="prompt-history-preview">{{ piHistoryPreviewContent }}</pre>
|
|
252
|
+
<button type="button" class="btn-mini btn-confirm-mini" @click="applyPiConfigHistory" :disabled="piHistoryApplying || !isToolConfigWriteAllowed('pi')">{{ t('pi.history.apply') }}</button>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</details>
|
|
212
256
|
</section>
|
|
213
257
|
<div v-if="!isToolConfigWriteAllowed('pi')" class="tool-config-write-overlay">
|
|
214
258
|
<div class="tool-config-write-overlay-card">
|