codexmate 0.1.15 → 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 +62 -6
- package/package.json +1 -1
- package/plugins/prompt-templates/computed.mjs +5 -0
- package/web-ui/app.js +12 -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 +164 -1
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +3 -3
- package/web-ui/modules/i18n/locales/en.mjs +18 -0
- package/web-ui/modules/i18n/locales/ja.mjs +18 -0
- package/web-ui/modules/i18n/locales/vi.mjs +18 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +18 -0
- package/web-ui/modules/i18n/locales/zh.mjs +18 -0
- package/web-ui/modules/skills.computed.mjs +6 -2
- package/web-ui/modules/skills.methods.mjs +1 -1
- package/web-ui/partials/index/layout-header.html +20 -0
- package/web-ui/partials/index/modal-skills.html +8 -0
- package/web-ui/partials/index/panel-config-pi.html +76 -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 +515 -234
- 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
|
};
|
|
@@ -1222,28 +1224,35 @@ function readPiModels(params = {}) {
|
|
|
1222
1224
|
const dir = getPiAgentDir();
|
|
1223
1225
|
const filePath = path.join(dir, 'models.json');
|
|
1224
1226
|
if (!fs.existsSync(filePath)) {
|
|
1225
|
-
return { providers: {} };
|
|
1227
|
+
return { providers: {}, file: {} };
|
|
1226
1228
|
}
|
|
1227
1229
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
1228
1230
|
const data = JSON.parse(raw);
|
|
1229
1231
|
const providers = (data && typeof data === 'object' && data.providers && typeof data.providers === 'object')
|
|
1230
1232
|
? data.providers
|
|
1231
1233
|
: {};
|
|
1232
|
-
|
|
1234
|
+
const file = data && typeof data === 'object' && !Array.isArray(data) ? data : { providers };
|
|
1235
|
+
return { providers, file };
|
|
1233
1236
|
} catch (e) {
|
|
1234
1237
|
return { error: e && e.message ? e.message : '读取 Pi models.json 失败' };
|
|
1235
1238
|
}
|
|
1236
1239
|
}
|
|
1237
1240
|
|
|
1238
1241
|
function writePiModels(params = {}) {
|
|
1242
|
+
const file = params && typeof params.file === 'object' && params.file !== null && !Array.isArray(params.file) ? params.file : null;
|
|
1239
1243
|
const providers = params && typeof params.providers === 'object' ? params.providers : null;
|
|
1240
|
-
if (providers === null) {
|
|
1241
|
-
return { error: '
|
|
1244
|
+
if (!file && providers === null) {
|
|
1245
|
+
return { error: '缺少可写入的内容' };
|
|
1242
1246
|
}
|
|
1243
1247
|
try {
|
|
1244
1248
|
const dir = getPiAgentDir();
|
|
1245
1249
|
fs.mkdirSync(dir, { recursive: true });
|
|
1246
1250
|
const filePath = path.join(dir, 'models.json');
|
|
1251
|
+
if (file) {
|
|
1252
|
+
historyBackup('pi-models', filePath);
|
|
1253
|
+
fs.writeFileSync(filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
1254
|
+
return { success: true };
|
|
1255
|
+
}
|
|
1247
1256
|
let existing = {};
|
|
1248
1257
|
if (fs.existsSync(filePath)) {
|
|
1249
1258
|
try {
|
|
@@ -1294,16 +1303,22 @@ function readPiSettings(params = {}) {
|
|
|
1294
1303
|
}
|
|
1295
1304
|
|
|
1296
1305
|
function writePiSettings(params = {}) {
|
|
1306
|
+
const fullSettings = params && typeof params.settings === 'object' && params.settings !== null && !Array.isArray(params.settings) ? params.settings : null;
|
|
1297
1307
|
const updates = {};
|
|
1298
1308
|
if (typeof (params && params.defaultProvider) === 'string') updates.defaultProvider = params.defaultProvider;
|
|
1299
1309
|
if (typeof (params && params.defaultModel) === 'string') updates.defaultModel = params.defaultModel;
|
|
1300
|
-
if (Object.keys(updates).length === 0) {
|
|
1310
|
+
if (!fullSettings && Object.keys(updates).length === 0) {
|
|
1301
1311
|
return { error: '缺少可写入的设置项' };
|
|
1302
1312
|
}
|
|
1303
1313
|
try {
|
|
1304
1314
|
const dir = getPiAgentDir();
|
|
1305
1315
|
fs.mkdirSync(dir, { recursive: true });
|
|
1306
1316
|
const filePath = path.join(dir, 'settings.json');
|
|
1317
|
+
if (fullSettings) {
|
|
1318
|
+
historyBackup('pi-settings', filePath);
|
|
1319
|
+
fs.writeFileSync(filePath, JSON.stringify(fullSettings, null, 2), 'utf-8');
|
|
1320
|
+
return { success: true, settings: fullSettings };
|
|
1321
|
+
}
|
|
1307
1322
|
let existing = {};
|
|
1308
1323
|
if (fs.existsSync(filePath)) {
|
|
1309
1324
|
try {
|
|
@@ -1321,6 +1336,43 @@ function writePiSettings(params = {}) {
|
|
|
1321
1336
|
}
|
|
1322
1337
|
}
|
|
1323
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
|
+
|
|
1324
1376
|
function setToolConfigPermission(params = {}) {
|
|
1325
1377
|
const target = normalizeToolConfigTarget(params && params.target);
|
|
1326
1378
|
if (!target) return { error: '未知配置对象' };
|
|
@@ -13304,6 +13356,10 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13304
13356
|
result = writePiSettings(params || {});
|
|
13305
13357
|
break;
|
|
13306
13358
|
}
|
|
13359
|
+
case 'apply-pi-config-history': {
|
|
13360
|
+
result = applyPiConfigHistory(params || {});
|
|
13361
|
+
break;
|
|
13362
|
+
}
|
|
13307
13363
|
case 'fetch-pi-remote-models': {
|
|
13308
13364
|
result = await fetchPiRemoteModels(params || {});
|
|
13309
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
|
@@ -148,6 +148,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
148
148
|
piActiveProvider: '',
|
|
149
149
|
piActiveModel: '',
|
|
150
150
|
piShowKey: false,
|
|
151
|
+
piSettingsJsonDraft: '',
|
|
152
|
+
piSettingsJsonError: '',
|
|
153
|
+
piModelsJsonDraft: '',
|
|
154
|
+
piModelsJsonError: '',
|
|
155
|
+
piFileJsonSaving: false,
|
|
156
|
+
piHistoryTarget: '',
|
|
157
|
+
piHistoryLoading: false,
|
|
158
|
+
piHistoryItems: [],
|
|
159
|
+
piHistoryPreviewId: '',
|
|
160
|
+
piHistoryPreviewContent: '',
|
|
161
|
+
piHistoryError: '',
|
|
162
|
+
piHistoryApplying: false,
|
|
151
163
|
// Plugins
|
|
152
164
|
pluginsActiveId: 'prompt-templates',
|
|
153
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',
|
|
@@ -66,7 +66,8 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
66
66
|
try {
|
|
67
67
|
const [providers] = await Promise.all([
|
|
68
68
|
this.loadPiProviders(),
|
|
69
|
-
this.loadPiSettings()
|
|
69
|
+
this.loadPiSettings(),
|
|
70
|
+
this.loadPiFileJsons()
|
|
70
71
|
]);
|
|
71
72
|
this.piProviders = providers || {};
|
|
72
73
|
this.piProviderIds = Object.keys(this.piProviders || {}).sort();
|
|
@@ -342,6 +343,88 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
342
343
|
return [baseUrl, api].filter(Boolean).join(' • ') || 'Piper provider';
|
|
343
344
|
}
|
|
344
345
|
,
|
|
346
|
+
piParseFileJsonDraft(draft, label) {
|
|
347
|
+
const name = label || 'JSON';
|
|
348
|
+
const text = String(draft || '').trim();
|
|
349
|
+
if (!text) return { ok: false, value: null, error: name + ' 内容不能为空' };
|
|
350
|
+
try {
|
|
351
|
+
const parsed = JSON.parse(text);
|
|
352
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
353
|
+
return { ok: false, value: null, error: name + ' 必须是 JSON 对象' };
|
|
354
|
+
}
|
|
355
|
+
return { ok: true, value: parsed, error: '' };
|
|
356
|
+
} catch (e) {
|
|
357
|
+
return { ok: false, value: null, error: name + ' 解析失败:' + (e && e.message ? e.message : '') };
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
async loadPiFileJsons() {
|
|
361
|
+
const [modelsResult, settingsResult] = await Promise.all([
|
|
362
|
+
apiClient('read-pi-models'),
|
|
363
|
+
apiClient('read-pi-settings')
|
|
364
|
+
]);
|
|
365
|
+
const modelsFile = isPiPlainObject(modelsResult && modelsResult.file) ? modelsResult.file : {};
|
|
366
|
+
const settings = isPiPlainObject(settingsResult && settingsResult.settings) ? settingsResult.settings : {};
|
|
367
|
+
this.piModelsJsonDraft = JSON.stringify(modelsFile, null, 2);
|
|
368
|
+
this.piSettingsJsonDraft = JSON.stringify(settings, null, 2);
|
|
369
|
+
this.piModelsJsonError = '';
|
|
370
|
+
this.piSettingsJsonError = '';
|
|
371
|
+
},
|
|
372
|
+
piSettingsJsonInput() {
|
|
373
|
+
const parsed = this.piParseFileJsonDraft(this.piSettingsJsonDraft, 'settings.json');
|
|
374
|
+
this.piSettingsJsonError = parsed.ok ? '' : parsed.error;
|
|
375
|
+
},
|
|
376
|
+
piModelsJsonInput() {
|
|
377
|
+
const parsed = this.piParseFileJsonDraft(this.piModelsJsonDraft, 'models.json');
|
|
378
|
+
this.piModelsJsonError = parsed.ok ? '' : parsed.error;
|
|
379
|
+
},
|
|
380
|
+
async savePiSettingsJson() {
|
|
381
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving || this.piFileJsonSaving) return;
|
|
382
|
+
const parsed = this.piParseFileJsonDraft(this.piSettingsJsonDraft, 'settings.json');
|
|
383
|
+
if (!parsed.ok) {
|
|
384
|
+
this.piSettingsJsonError = parsed.error;
|
|
385
|
+
this.message = parsed.error;
|
|
386
|
+
this.messageType = 'error';
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
this.piFileJsonSaving = true;
|
|
390
|
+
try {
|
|
391
|
+
const result = await apiClient('write-pi-settings', { settings: parsed.value });
|
|
392
|
+
if (result && result.error) throw new Error(result.error);
|
|
393
|
+
this.piSettingsJsonError = '';
|
|
394
|
+
this.piSettingsJsonDraft = JSON.stringify(parsed.value, null, 2);
|
|
395
|
+
await this.loadPiSettings();
|
|
396
|
+
this.message = 'settings.json 已保存';
|
|
397
|
+
this.messageType = 'success';
|
|
398
|
+
} catch (e) {
|
|
399
|
+
this.message = e && e.message ? e.message : '保存 settings.json 失败';
|
|
400
|
+
this.messageType = 'error';
|
|
401
|
+
} finally {
|
|
402
|
+
this.piFileJsonSaving = false;
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
async savePiModelsJson() {
|
|
406
|
+
if (!this.isToolConfigWriteAllowed('pi') || this.piSaving || this.piFileJsonSaving) return;
|
|
407
|
+
const parsed = this.piParseFileJsonDraft(this.piModelsJsonDraft, 'models.json');
|
|
408
|
+
if (!parsed.ok) {
|
|
409
|
+
this.piModelsJsonError = parsed.error;
|
|
410
|
+
this.message = parsed.error;
|
|
411
|
+
this.messageType = 'error';
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
this.piFileJsonSaving = true;
|
|
415
|
+
try {
|
|
416
|
+
const result = await apiClient('write-pi-models', { file: parsed.value });
|
|
417
|
+
if (result && result.error) throw new Error(result.error);
|
|
418
|
+
await this.loadPiSources();
|
|
419
|
+
this.message = 'models.json 已保存';
|
|
420
|
+
this.messageType = 'success';
|
|
421
|
+
} catch (e) {
|
|
422
|
+
this.message = e && e.message ? e.message : '保存 models.json 失败';
|
|
423
|
+
this.messageType = 'error';
|
|
424
|
+
} finally {
|
|
425
|
+
this.piFileJsonSaving = false;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
345
428
|
piParseEditorJsonDraft() {
|
|
346
429
|
const provider = this.editingPiProvider;
|
|
347
430
|
const draft = provider && provider.form ? String(provider.form.configJsonDraft || '').trim() : '';
|
|
@@ -392,6 +475,86 @@ export function createPiConfigMethods({ api: apiClient }) {
|
|
|
392
475
|
piEditorJsonDraftInput() {
|
|
393
476
|
const draft = this.piParseEditorJsonDraft();
|
|
394
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
|
+
}
|
|
395
558
|
}
|
|
396
559
|
};
|
|
397
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',
|
|
@@ -1030,6 +1036,10 @@ const en = Object.freeze({
|
|
|
1030
1036
|
'status.opencodeConfig': 'OpenCode config',
|
|
1031
1037
|
'status.workspaceFile': 'Workspace file',
|
|
1032
1038
|
'status.configMode': 'Config mode',
|
|
1039
|
+
'status.kilocodeProvider': 'Kilocode provider',
|
|
1040
|
+
'status.kilocodeModel': 'Kilocode model',
|
|
1041
|
+
'status.piProvider': 'Pi provider',
|
|
1042
|
+
'status.piModel': 'Pi model',
|
|
1033
1043
|
'status.currentProvider': 'Current provider',
|
|
1034
1044
|
'status.currentModel': 'Current model',
|
|
1035
1045
|
'status.quickSwitchProvider': 'Quick switch provider',
|
|
@@ -1350,6 +1360,14 @@ const en = Object.freeze({
|
|
|
1350
1360
|
'pi.providers.json.desc': 'Live preview of the final provider record merged from form changes and extras; edits are merged into models.json on save.',
|
|
1351
1361
|
'pi.providers.json.previewTitle': 'Preview',
|
|
1352
1362
|
'pi.providers.json.editTitle': 'Edit JSON',
|
|
1363
|
+
'pi.files.title': 'File JSON',
|
|
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.',
|
|
1365
|
+
'pi.files.settingsTitle': 'settings.json (active settings)',
|
|
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.',
|
|
1353
1371
|
'pi.providers.models.remove': 'Remove',
|
|
1354
1372
|
'pi.providers.models.search': 'Search available models…',
|
|
1355
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': '総時間',
|
|
@@ -1033,6 +1039,10 @@ const ja = Object.freeze({
|
|
|
1033
1039
|
'status.opencodeConfig': 'OpenCode 設定',
|
|
1034
1040
|
'status.workspaceFile': 'ワークスペースファイル',
|
|
1035
1041
|
'status.configMode': '設定モード',
|
|
1042
|
+
'status.kilocodeProvider': 'Kilocode プロバイダー',
|
|
1043
|
+
'status.kilocodeModel': 'Kilocode モデル',
|
|
1044
|
+
'status.piProvider': 'Pi プロバイダー',
|
|
1045
|
+
'status.piModel': 'Pi モデル',
|
|
1036
1046
|
'status.currentProvider': '現在の Provider',
|
|
1037
1047
|
'status.currentModel': '現在のモデル',
|
|
1038
1048
|
'status.quickSwitchProvider': 'プロバイダーをクイック切替',
|
|
@@ -1351,6 +1361,14 @@ const ja = Object.freeze({
|
|
|
1351
1361
|
'pi.providers.json.desc': 'フォーム変更と拡張フィールドを統合した最終レコードをリアルタイムにプレビュー。保存時に models.json へ統合します。',
|
|
1352
1362
|
'pi.providers.json.previewTitle': 'プレビュー',
|
|
1353
1363
|
'pi.providers.json.editTitle': 'JSON を編集',
|
|
1364
|
+
'pi.files.title': 'ファイル JSON',
|
|
1365
|
+
'pi.files.desc': 'Pi の settings.json と models.json を全文表示して直接編集します。保存前に JSON の正当性を検証し、不正な内容は書き込みません。',
|
|
1366
|
+
'pi.files.settingsTitle': 'settings.json(有効な設定)',
|
|
1367
|
+
'pi.files.modelsTitle': 'models.json(プロバイダーとモデル)',
|
|
1368
|
+
'pi.history.apply': 'この履歴を適用',
|
|
1369
|
+
'pi.history.applied': '履歴を適用しました',
|
|
1370
|
+
'pi.history.applyFailed': '履歴の適用に失敗しました',
|
|
1371
|
+
'pi.history.confirm': 'この履歴をファイルに適用しますか?現在の内容は先にスナップショット保存されます。',
|
|
1354
1372
|
'pi.providers.models.remove': '削除',
|
|
1355
1373
|
'pi.providers.models.search': '利用可能なモデルを検索…',
|
|
1356
1374
|
'pi.providers.models.remoteCount': '{count} 個のモデルを取得しました。クリックで追加',
|