codexmate 0.1.16 → 0.1.18
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 +1 -0
- package/README.vi.md +1 -0
- package/README.zh.md +1 -0
- package/cli/skills.js +18 -1
- package/cli.js +176 -4
- package/package.json +1 -1
- package/plugins/prompt-templates/computed.mjs +5 -0
- package/web-ui/app.js +12 -10
- package/web-ui/index.html +1 -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 +288 -120
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +3 -3
- package/web-ui/modules/i18n/locales/en.mjs +23 -0
- package/web-ui/modules/i18n/locales/ja.mjs +23 -0
- package/web-ui/modules/i18n/locales/vi.mjs +23 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +23 -0
- package/web-ui/modules/i18n/locales/zh.mjs +23 -0
- package/web-ui/modules/pi-provider-templates.mjs +26 -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/modals-pi-provider.html +96 -0
- package/web-ui/partials/index/panel-config-pi.html +44 -122
- 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 +795 -619
- 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/web-ui/styles/settings-panel.css +12 -0
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
[](LICENSE)
|
|
23
23
|
[](https://github.com/SakuraByteCore/codexmate/stargazers)
|
|
24
24
|
[](https://github.com/SakuraByteCore/codexmate/issues)
|
|
25
|
+
[](https://stackmap.shipwithai.xyz/repos/SakuraByteCore/codexmate?utm_source=badge)
|
|
25
26
|
|
|
26
27
|
<br />
|
|
27
28
|
|
package/README.vi.md
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
[](LICENSE)
|
|
23
23
|
[](https://github.com/SakuraByteCore/codexmate/stargazers)
|
|
24
24
|
[](https://github.com/SakuraByteCore/codexmate/issues)
|
|
25
|
+
[](https://stackmap.shipwithai.xyz/repos/SakuraByteCore/codexmate?utm_source=badge)
|
|
25
26
|
|
|
26
27
|
<br />
|
|
27
28
|
|
package/README.zh.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
[](LICENSE)
|
|
22
22
|
[](https://github.com/SakuraByteCore/codexmate/stargazers)
|
|
23
23
|
[](https://github.com/SakuraByteCore/codexmate/issues)
|
|
24
|
+
[](https://stackmap.shipwithai.xyz/repos/SakuraByteCore/codexmate?utm_source=badge)
|
|
24
25
|
|
|
25
26
|
<br />
|
|
26
27
|
|
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
|
}
|
|
@@ -1284,6 +1287,130 @@ async function fetchPiRemoteModels(params = {}) {
|
|
|
1284
1287
|
}
|
|
1285
1288
|
}
|
|
1286
1289
|
|
|
1290
|
+
const PI_MODELS_DEV_API_URL = 'https://models.dev/api.json';
|
|
1291
|
+
const PI_MODELS_CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
1292
|
+
const PI_MODELS_DEV_TIMEOUT_MS = 15000;
|
|
1293
|
+
let g_piModelsDevCatalogCache = null;
|
|
1294
|
+
let g_piModelsDevCatalogCachedAt = 0;
|
|
1295
|
+
|
|
1296
|
+
function fetchPiModelsDevCatalog() {
|
|
1297
|
+
if (g_piModelsDevCatalogCache && Date.now() - g_piModelsDevCatalogCachedAt < PI_MODELS_CATALOG_CACHE_TTL_MS) {
|
|
1298
|
+
return Promise.resolve(g_piModelsDevCatalogCache);
|
|
1299
|
+
}
|
|
1300
|
+
return new Promise((resolve) => {
|
|
1301
|
+
const finish = (payload) => {
|
|
1302
|
+
if (payload && !payload.error) {
|
|
1303
|
+
g_piModelsDevCatalogCache = payload;
|
|
1304
|
+
g_piModelsDevCatalogCachedAt = Date.now();
|
|
1305
|
+
}
|
|
1306
|
+
resolve(payload);
|
|
1307
|
+
};
|
|
1308
|
+
const req = https.get(PI_MODELS_DEV_API_URL, {
|
|
1309
|
+
headers: {
|
|
1310
|
+
'User-Agent': 'codexmate-models',
|
|
1311
|
+
'Accept': 'application/json'
|
|
1312
|
+
},
|
|
1313
|
+
agent: HTTPS_KEEP_ALIVE_AGENT
|
|
1314
|
+
}, (res) => {
|
|
1315
|
+
const status = res.statusCode || 0;
|
|
1316
|
+
if (status >= 400) {
|
|
1317
|
+
res.resume();
|
|
1318
|
+
return finish({ error: `HTTP ${status}` });
|
|
1319
|
+
}
|
|
1320
|
+
let body = '';
|
|
1321
|
+
res.on('data', chunk => {
|
|
1322
|
+
body += chunk;
|
|
1323
|
+
});
|
|
1324
|
+
res.on('end', () => {
|
|
1325
|
+
try {
|
|
1326
|
+
const catalog = JSON.parse(body || '{}');
|
|
1327
|
+
if (!catalog || typeof catalog !== 'object' || Array.isArray(catalog)) {
|
|
1328
|
+
return finish({ error: '响应内容不是有效的目录对象' });
|
|
1329
|
+
}
|
|
1330
|
+
finish(catalog);
|
|
1331
|
+
} catch (e) {
|
|
1332
|
+
finish({ error: e && e.message ? e.message : '解析目录响应失败' });
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
});
|
|
1336
|
+
req.setTimeout(PI_MODELS_DEV_TIMEOUT_MS, () => {
|
|
1337
|
+
req.destroy(new Error('请求超时'));
|
|
1338
|
+
});
|
|
1339
|
+
req.on('error', (err) => {
|
|
1340
|
+
finish({ error: err && err.message ? err.message : '请求失败' });
|
|
1341
|
+
});
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function normalizePiCatalogUrl(url) {
|
|
1346
|
+
return typeof url === 'string' ? url.trim().replace(/\/+$/, '') : '';
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function findPiCatalogModelInProvider(provider, modelId) {
|
|
1350
|
+
const models = provider && typeof provider === 'object' ? provider.models : null;
|
|
1351
|
+
if (!models || typeof models !== 'object') return null;
|
|
1352
|
+
const hit = models[modelId];
|
|
1353
|
+
return hit && typeof hit === 'object' ? hit : null;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function findPiCatalogModel(catalog, lookup) {
|
|
1357
|
+
if (lookup.providerId && catalog[lookup.providerId]) {
|
|
1358
|
+
return findPiCatalogModelInProvider(catalog[lookup.providerId], lookup.modelId);
|
|
1359
|
+
}
|
|
1360
|
+
const entries = Object.entries(catalog);
|
|
1361
|
+
for (const [key, provider] of entries) {
|
|
1362
|
+
const api = normalizePiCatalogUrl(provider && typeof provider === 'object' ? provider.api : '');
|
|
1363
|
+
if ((lookup.baseUrl && api === lookup.baseUrl) || (lookup.providerId && key === lookup.providerId)) {
|
|
1364
|
+
return findPiCatalogModelInProvider(provider, lookup.modelId);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
for (const [, provider] of entries) {
|
|
1368
|
+
const hit = findPiCatalogModelInProvider(provider, lookup.modelId);
|
|
1369
|
+
if (hit) return hit;
|
|
1370
|
+
}
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function toPiCatalogModelInfo(raw) {
|
|
1375
|
+
const numericOrNull = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
|
|
1376
|
+
const limit = raw && typeof raw.limit === 'object' && raw.limit !== null ? raw.limit : {};
|
|
1377
|
+
const cost = raw && typeof raw.cost === 'object' && raw.cost !== null ? raw.cost : {};
|
|
1378
|
+
return {
|
|
1379
|
+
name: typeof raw.name === 'string' ? raw.name : '',
|
|
1380
|
+
reasoning: !!raw.reasoning,
|
|
1381
|
+
contextWindow: numericOrNull(limit.context),
|
|
1382
|
+
maxTokens: numericOrNull(limit.output),
|
|
1383
|
+
cost: {
|
|
1384
|
+
input: numericOrNull(cost.input),
|
|
1385
|
+
output: numericOrNull(cost.output),
|
|
1386
|
+
cacheRead: numericOrNull(cost.cache_read),
|
|
1387
|
+
cacheWrite: numericOrNull(cost.cache_write)
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
async function fetchPiModelsCatalog(params = {}) {
|
|
1393
|
+
const modelId = typeof params.modelId === 'string' ? params.modelId.trim() : '';
|
|
1394
|
+
if (!modelId) {
|
|
1395
|
+
return { error: '模型 ID 不能为空' };
|
|
1396
|
+
}
|
|
1397
|
+
const lookup = {
|
|
1398
|
+
modelId,
|
|
1399
|
+
providerId: typeof params.providerId === 'string' ? params.providerId.trim() : '',
|
|
1400
|
+
baseUrl: normalizePiCatalogUrl(params.baseUrl)
|
|
1401
|
+
};
|
|
1402
|
+
const catalog = await fetchPiModelsDevCatalog();
|
|
1403
|
+
if (!catalog || typeof catalog !== 'object' || typeof catalog.error === 'string') {
|
|
1404
|
+
const reason = catalog && catalog.error ? catalog.error : '响应结构无效';
|
|
1405
|
+
return { error: `models.dev 目录请求失败:${reason}` };
|
|
1406
|
+
}
|
|
1407
|
+
const hit = findPiCatalogModel(catalog, lookup);
|
|
1408
|
+
if (!hit) {
|
|
1409
|
+
return { error: 'models.dev 目录中未找到该模型' };
|
|
1410
|
+
}
|
|
1411
|
+
return { ok: true, model: toPiCatalogModelInfo(hit) };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1287
1414
|
function readPiSettings(params = {}) {
|
|
1288
1415
|
try {
|
|
1289
1416
|
const dir = getPiAgentDir();
|
|
@@ -1312,6 +1439,7 @@ function writePiSettings(params = {}) {
|
|
|
1312
1439
|
fs.mkdirSync(dir, { recursive: true });
|
|
1313
1440
|
const filePath = path.join(dir, 'settings.json');
|
|
1314
1441
|
if (fullSettings) {
|
|
1442
|
+
historyBackup('pi-settings', filePath);
|
|
1315
1443
|
fs.writeFileSync(filePath, JSON.stringify(fullSettings, null, 2), 'utf-8');
|
|
1316
1444
|
return { success: true, settings: fullSettings };
|
|
1317
1445
|
}
|
|
@@ -1332,6 +1460,43 @@ function writePiSettings(params = {}) {
|
|
|
1332
1460
|
}
|
|
1333
1461
|
}
|
|
1334
1462
|
|
|
1463
|
+
function applyPiConfigHistory(params = {}) {
|
|
1464
|
+
const target = typeof params.target === 'string' ? params.target.trim() : '';
|
|
1465
|
+
if (target !== 'settings' && target !== 'models') {
|
|
1466
|
+
return { error: '无效的历史记录目标' };
|
|
1467
|
+
}
|
|
1468
|
+
const bucket = target === 'settings' ? 'pi-settings' : 'pi-models';
|
|
1469
|
+
const fileName = target === 'settings' ? 'settings.json' : 'models.json';
|
|
1470
|
+
let entry;
|
|
1471
|
+
try {
|
|
1472
|
+
entry = readPromptHistory(bucket, String(params.id || ''));
|
|
1473
|
+
} catch (_) {
|
|
1474
|
+
return { error: '历史记录不存在或读取失败' };
|
|
1475
|
+
}
|
|
1476
|
+
const content = entry && typeof entry.content === 'string' ? entry.content : '';
|
|
1477
|
+
let parsed = null;
|
|
1478
|
+
if (content) {
|
|
1479
|
+
try {
|
|
1480
|
+
parsed = JSON.parse(content);
|
|
1481
|
+
} catch (_) {
|
|
1482
|
+
parsed = null;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
1486
|
+
return { error: '历史记录内容不是有效的 JSON 对象' };
|
|
1487
|
+
}
|
|
1488
|
+
try {
|
|
1489
|
+
const filePath = path.join(getPiAgentDir(), fileName);
|
|
1490
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1491
|
+
historyBackup(bucket, filePath);
|
|
1492
|
+
fs.writeFileSync(filePath, JSON.stringify(parsed, null, 2), 'utf-8');
|
|
1493
|
+
} catch (e) {
|
|
1494
|
+
return { error: (e && e.message) || '应用历史记录失败' };
|
|
1495
|
+
}
|
|
1496
|
+
if (target === 'settings') return { success: true, settings: parsed };
|
|
1497
|
+
return { success: true, file: parsed };
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1335
1500
|
function setToolConfigPermission(params = {}) {
|
|
1336
1501
|
const target = normalizeToolConfigTarget(params && params.target);
|
|
1337
1502
|
if (!target) return { error: '未知配置对象' };
|
|
@@ -2839,7 +3004,6 @@ function addProviderToConfig(params = {}) {
|
|
|
2839
3004
|
let baseUrlForConfig = normalizedUrl;
|
|
2840
3005
|
let authKeyForConfig = key;
|
|
2841
3006
|
const extraLines = [];
|
|
2842
|
-
const requiresOpenaiAuth = useTransform;
|
|
2843
3007
|
|
|
2844
3008
|
if (useTransform) {
|
|
2845
3009
|
const saveRes = upsertOpenaiBridgeProvider(OPENAI_BRIDGE_SETTINGS_FILE, name, normalizedUrl, key, undefined, { maxRetries: openaiBridgeMaxRetries });
|
|
@@ -2864,7 +3028,7 @@ function addProviderToConfig(params = {}) {
|
|
|
2864
3028
|
`name = "${safeName}"`,
|
|
2865
3029
|
`base_url = "${safeUrl}"`,
|
|
2866
3030
|
`wire_api = "responses"`,
|
|
2867
|
-
`requires_openai_auth =
|
|
3031
|
+
`requires_openai_auth = true`,
|
|
2868
3032
|
`preferred_auth_method = "${safeKey}"`,
|
|
2869
3033
|
`models = [{ id = "${escapeTomlBasicString(model)}", name = "${escapeTomlBasicString(model)}" }]`,
|
|
2870
3034
|
...extraLines,
|
|
@@ -10366,7 +10530,7 @@ ${buildModelProviderTableHeader(providerName)}
|
|
|
10366
10530
|
name = "${safeName}"
|
|
10367
10531
|
base_url = "${safeBaseUrl}"
|
|
10368
10532
|
wire_api = "responses"
|
|
10369
|
-
requires_openai_auth =
|
|
10533
|
+
requires_openai_auth = true
|
|
10370
10534
|
preferred_auth_method = "${safeApiKey}"
|
|
10371
10535
|
request_max_retries = 4
|
|
10372
10536
|
stream_max_retries = 10
|
|
@@ -13315,10 +13479,18 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13315
13479
|
result = writePiSettings(params || {});
|
|
13316
13480
|
break;
|
|
13317
13481
|
}
|
|
13482
|
+
case 'apply-pi-config-history': {
|
|
13483
|
+
result = applyPiConfigHistory(params || {});
|
|
13484
|
+
break;
|
|
13485
|
+
}
|
|
13318
13486
|
case 'fetch-pi-remote-models': {
|
|
13319
13487
|
result = await fetchPiRemoteModels(params || {});
|
|
13320
13488
|
break;
|
|
13321
13489
|
}
|
|
13490
|
+
case 'pi-models-catalog': {
|
|
13491
|
+
result = await fetchPiModelsCatalog(params || {});
|
|
13492
|
+
break;
|
|
13493
|
+
}
|
|
13322
13494
|
case 'install-status':
|
|
13323
13495
|
result = buildInstallStatusReport();
|
|
13324
13496
|
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
|
@@ -128,16 +128,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
128
128
|
piProviders: {},
|
|
129
129
|
piProviderIds: [],
|
|
130
130
|
editingPiProvider: null,
|
|
131
|
-
addingPiProviderId: '',
|
|
132
|
-
addingPiProviderName: '',
|
|
133
|
-
addingPiProviderBaseUrl: '',
|
|
134
|
-
addingPiProviderApi: 'openai-completions',
|
|
135
|
-
addingPiProviderApiKey: '',
|
|
136
|
-
addingPiProviderModel: '',
|
|
137
|
-
addingPiRemoteModels: [],
|
|
138
|
-
addingPiRemoteLoading: false,
|
|
139
|
-
addingPiRemoteError: '',
|
|
140
|
-
addingPiRemoteDebounce: null,
|
|
141
131
|
showAddPiProviderModal: false,
|
|
142
132
|
piProviderLoading: false,
|
|
143
133
|
piSaving: false,
|
|
@@ -145,6 +135,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
145
135
|
piRemoteModelsLoading: false,
|
|
146
136
|
piRemoteModelError: '',
|
|
147
137
|
piModelSearch: '',
|
|
138
|
+
piProviderPickerQuery: '',
|
|
139
|
+
piSelectedProviderTemplate: '',
|
|
140
|
+
piRemoteChecked: {},
|
|
141
|
+
piCatalogFillIndex: -1,
|
|
142
|
+
piCatalogFillError: '',
|
|
148
143
|
piActiveProvider: '',
|
|
149
144
|
piActiveModel: '',
|
|
150
145
|
piShowKey: false,
|
|
@@ -153,6 +148,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
153
148
|
piModelsJsonDraft: '',
|
|
154
149
|
piModelsJsonError: '',
|
|
155
150
|
piFileJsonSaving: false,
|
|
151
|
+
piHistoryTarget: '',
|
|
152
|
+
piHistoryLoading: false,
|
|
153
|
+
piHistoryItems: [],
|
|
154
|
+
piHistoryPreviewId: '',
|
|
155
|
+
piHistoryPreviewContent: '',
|
|
156
|
+
piHistoryError: '',
|
|
157
|
+
piHistoryApplying: false,
|
|
156
158
|
// Plugins
|
|
157
159
|
pluginsActiveId: 'prompt-templates',
|
|
158
160
|
pluginsLoading: false,
|
package/web-ui/index.html
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
<!-- @include ./partials/index/panel-config-opencode.html -->
|
|
20
20
|
<!-- @include ./partials/index/panel-config-kilocode.html -->
|
|
21
21
|
<!-- @include ./partials/index/panel-config-pi.html -->
|
|
22
|
+
<!-- @include ./partials/index/modals-pi-provider.html -->
|
|
22
23
|
<!-- @include ./partials/index/panel-sessions.html -->
|
|
23
24
|
<!-- @include ./partials/index/panel-usage.html -->
|
|
24
25
|
<!-- @include ./partials/index/panel-docs.html -->
|
|
@@ -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',
|