codexmate 0.1.15 → 0.1.16

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.js CHANGED
@@ -1222,28 +1222,34 @@ function readPiModels(params = {}) {
1222
1222
  const dir = getPiAgentDir();
1223
1223
  const filePath = path.join(dir, 'models.json');
1224
1224
  if (!fs.existsSync(filePath)) {
1225
- return { providers: {} };
1225
+ return { providers: {}, file: {} };
1226
1226
  }
1227
1227
  const raw = fs.readFileSync(filePath, 'utf-8');
1228
1228
  const data = JSON.parse(raw);
1229
1229
  const providers = (data && typeof data === 'object' && data.providers && typeof data.providers === 'object')
1230
1230
  ? data.providers
1231
1231
  : {};
1232
- return { providers };
1232
+ const file = data && typeof data === 'object' && !Array.isArray(data) ? data : { providers };
1233
+ return { providers, file };
1233
1234
  } catch (e) {
1234
1235
  return { error: e && e.message ? e.message : '读取 Pi models.json 失败' };
1235
1236
  }
1236
1237
  }
1237
1238
 
1238
1239
  function writePiModels(params = {}) {
1240
+ const file = params && typeof params.file === 'object' && params.file !== null && !Array.isArray(params.file) ? params.file : null;
1239
1241
  const providers = params && typeof params.providers === 'object' ? params.providers : null;
1240
- if (providers === null) {
1241
- return { error: 'providers 不能为空' };
1242
+ if (!file && providers === null) {
1243
+ return { error: '缺少可写入的内容' };
1242
1244
  }
1243
1245
  try {
1244
1246
  const dir = getPiAgentDir();
1245
1247
  fs.mkdirSync(dir, { recursive: true });
1246
1248
  const filePath = path.join(dir, 'models.json');
1249
+ if (file) {
1250
+ fs.writeFileSync(filePath, JSON.stringify(file, null, 2), 'utf-8');
1251
+ return { success: true };
1252
+ }
1247
1253
  let existing = {};
1248
1254
  if (fs.existsSync(filePath)) {
1249
1255
  try {
@@ -1294,16 +1300,21 @@ function readPiSettings(params = {}) {
1294
1300
  }
1295
1301
 
1296
1302
  function writePiSettings(params = {}) {
1303
+ const fullSettings = params && typeof params.settings === 'object' && params.settings !== null && !Array.isArray(params.settings) ? params.settings : null;
1297
1304
  const updates = {};
1298
1305
  if (typeof (params && params.defaultProvider) === 'string') updates.defaultProvider = params.defaultProvider;
1299
1306
  if (typeof (params && params.defaultModel) === 'string') updates.defaultModel = params.defaultModel;
1300
- if (Object.keys(updates).length === 0) {
1307
+ if (!fullSettings && Object.keys(updates).length === 0) {
1301
1308
  return { error: '缺少可写入的设置项' };
1302
1309
  }
1303
1310
  try {
1304
1311
  const dir = getPiAgentDir();
1305
1312
  fs.mkdirSync(dir, { recursive: true });
1306
1313
  const filePath = path.join(dir, 'settings.json');
1314
+ if (fullSettings) {
1315
+ fs.writeFileSync(filePath, JSON.stringify(fullSettings, null, 2), 'utf-8');
1316
+ return { success: true, settings: fullSettings };
1317
+ }
1307
1318
  let existing = {};
1308
1319
  if (fs.existsSync(filePath)) {
1309
1320
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -148,6 +148,11 @@ document.addEventListener('DOMContentLoaded', () => {
148
148
  piActiveProvider: '',
149
149
  piActiveModel: '',
150
150
  piShowKey: false,
151
+ piSettingsJsonDraft: '',
152
+ piSettingsJsonError: '',
153
+ piModelsJsonDraft: '',
154
+ piModelsJsonError: '',
155
+ piFileJsonSaving: false,
151
156
  // Plugins
152
157
  pluginsActiveId: 'prompt-templates',
153
158
  pluginsLoading: false,
@@ -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() : '';
@@ -1030,6 +1030,10 @@ const en = Object.freeze({
1030
1030
  'status.opencodeConfig': 'OpenCode config',
1031
1031
  'status.workspaceFile': 'Workspace file',
1032
1032
  'status.configMode': 'Config mode',
1033
+ 'status.kilocodeProvider': 'Kilocode provider',
1034
+ 'status.kilocodeModel': 'Kilocode model',
1035
+ 'status.piProvider': 'Pi provider',
1036
+ 'status.piModel': 'Pi model',
1033
1037
  'status.currentProvider': 'Current provider',
1034
1038
  'status.currentModel': 'Current model',
1035
1039
  'status.quickSwitchProvider': 'Quick switch provider',
@@ -1350,6 +1354,10 @@ const en = Object.freeze({
1350
1354
  '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
1355
  'pi.providers.json.previewTitle': 'Preview',
1352
1356
  'pi.providers.json.editTitle': 'Edit JSON',
1357
+ 'pi.files.title': 'File JSON',
1358
+ '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
+ 'pi.files.settingsTitle': 'settings.json (active settings)',
1360
+ 'pi.files.modelsTitle': 'models.json (providers and models)',
1353
1361
  'pi.providers.models.remove': 'Remove',
1354
1362
  'pi.providers.models.search': 'Search available models…',
1355
1363
  'pi.providers.models.remoteCount': '{count} models available, click to add',
@@ -1033,6 +1033,10 @@ const ja = Object.freeze({
1033
1033
  'status.opencodeConfig': 'OpenCode 設定',
1034
1034
  'status.workspaceFile': 'ワークスペースファイル',
1035
1035
  'status.configMode': '設定モード',
1036
+ 'status.kilocodeProvider': 'Kilocode プロバイダー',
1037
+ 'status.kilocodeModel': 'Kilocode モデル',
1038
+ 'status.piProvider': 'Pi プロバイダー',
1039
+ 'status.piModel': 'Pi モデル',
1036
1040
  'status.currentProvider': '現在の Provider',
1037
1041
  'status.currentModel': '現在のモデル',
1038
1042
  'status.quickSwitchProvider': 'プロバイダーをクイック切替',
@@ -1351,6 +1355,10 @@ const ja = Object.freeze({
1351
1355
  'pi.providers.json.desc': 'フォーム変更と拡張フィールドを統合した最終レコードをリアルタイムにプレビュー。保存時に models.json へ統合します。',
1352
1356
  'pi.providers.json.previewTitle': 'プレビュー',
1353
1357
  'pi.providers.json.editTitle': 'JSON を編集',
1358
+ 'pi.files.title': 'ファイル JSON',
1359
+ 'pi.files.desc': 'Pi の settings.json と models.json を全文表示して直接編集します。保存前に JSON の正当性を検証し、不正な内容は書き込みません。',
1360
+ 'pi.files.settingsTitle': 'settings.json(有効な設定)',
1361
+ 'pi.files.modelsTitle': 'models.json(プロバイダーとモデル)',
1354
1362
  'pi.providers.models.remove': '削除',
1355
1363
  'pi.providers.models.search': '利用可能なモデルを検索…',
1356
1364
  'pi.providers.models.remoteCount': '{count} 個のモデルを取得しました。クリックで追加',
@@ -1128,6 +1128,10 @@ const vi = Object.freeze({
1128
1128
  'status.openclawConfig': 'Cấu hình OpenClaw',
1129
1129
  'status.workspaceFile': 'File không gian làm việc',
1130
1130
  'status.configMode': 'Chế độ cấu hình',
1131
+ 'status.kilocodeProvider': 'Nhà cung cấp Kilocode',
1132
+ 'status.kilocodeModel': 'Mô hình Kilocode',
1133
+ 'status.piProvider': 'Nhà cung cấp Pi',
1134
+ 'status.piModel': 'Mô hình Pi',
1131
1135
  'status.currentProvider': 'Nhà cung cấp hiện tại',
1132
1136
  'status.currentModel': 'Model hiện tại',
1133
1137
  'status.quickSwitchProvider': 'Chuyển nhanh nhà cung cấp',
@@ -1319,6 +1323,10 @@ const vi = Object.freeze({
1319
1323
  'pi.providers.json.desc': 'Xem trước bản ghi cuối từ thay đổi biểu mẫu; các chỉnh sửa được gộp vào models.json khi lưu.',
1320
1324
  'pi.providers.json.previewTitle': 'Xem trước',
1321
1325
  'pi.providers.json.editTitle': 'Chỉnh sửa JSON',
1326
+ 'pi.files.title': 'JSON tập tin',
1327
+ '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
+ 'pi.files.settingsTitle': 'settings.json (cài đặt đang dùng)',
1329
+ 'pi.files.modelsTitle': 'models.json (nhà cung cấp và mô hình)',
1322
1330
  'pi.providers.models.remove': 'Xóa',
1323
1331
  'pi.providers.models.search': 'Tìm kiếm mô hình khả dụng…',
1324
1332
  'pi.providers.models.remoteCount': 'Đã lấy {count} mô hình, bấm để thêm',
@@ -1029,6 +1029,10 @@ const zhTw = Object.freeze({
1029
1029
  'status.opencodeConfig': 'OpenCode 設定',
1030
1030
  'status.workspaceFile': '工作區文件',
1031
1031
  'status.configMode': '設定模式',
1032
+ 'status.kilocodeProvider': 'Kilocode 供應商',
1033
+ 'status.kilocodeModel': 'Kilocode 模型',
1034
+ 'status.piProvider': 'Pi 供應商',
1035
+ 'status.piModel': 'Pi 模型',
1032
1036
  'status.currentProvider': '目前 Provider',
1033
1037
  'status.currentModel': '目前模型',
1034
1038
  'status.quickSwitchProvider': '快速切換提供商',
@@ -1348,6 +1352,10 @@ const zhTw = Object.freeze({
1348
1352
  'pi.providers.json.desc': '即時預覽表單異動合併後的最終紀錄;儲存時併入 models.json。',
1349
1353
  'pi.providers.json.previewTitle': '預覽',
1350
1354
  'pi.providers.json.editTitle': '編輯 JSON',
1355
+ 'pi.files.title': '檔案 JSON',
1356
+ 'pi.files.desc': '完整展示並直接編輯 Pi 的 settings.json 與 models.json;儲存前校驗 JSON 合法性,非法內容不會寫入。',
1357
+ 'pi.files.settingsTitle': 'settings.json(生效設定)',
1358
+ 'pi.files.modelsTitle': 'models.json(供應商與模型)',
1351
1359
  'pi.providers.models.remove': '刪除',
1352
1360
  'pi.providers.models.search': '搜尋可用模型…',
1353
1361
  'pi.providers.models.remoteCount': '已取得 {count} 個可用模型,點擊新增',
@@ -1029,6 +1029,10 @@ const zh = Object.freeze({
1029
1029
  'status.opencodeConfig': 'OpenCode 配置',
1030
1030
  'status.workspaceFile': '工作区文件',
1031
1031
  'status.configMode': '配置模式',
1032
+ 'status.kilocodeProvider': 'Kilocode 提供商',
1033
+ 'status.kilocodeModel': 'Kilocode 模型',
1034
+ 'status.piProvider': 'Pi 提供商',
1035
+ 'status.piModel': 'Pi 模型',
1032
1036
  'status.currentProvider': '当前 Provider',
1033
1037
  'status.currentModel': '当前模型',
1034
1038
  'status.quickSwitchProvider': '快速切换提供商',
@@ -1348,6 +1352,10 @@ const zh = Object.freeze({
1348
1352
  'pi.providers.json.desc': '实时预览表单改动合并后的最终记录;保存时并入 models.json。',
1349
1353
  'pi.providers.json.previewTitle': '预览',
1350
1354
  'pi.providers.json.editTitle': '编辑 JSON',
1355
+ 'pi.files.title': '文件 JSON',
1356
+ 'pi.files.desc': '完整展示并直接编辑 Pi 的 settings.json 与 models.json;保存前校验 JSON 合法性,非法内容不会写入。',
1357
+ 'pi.files.settingsTitle': 'settings.json(生效设置)',
1358
+ 'pi.files.modelsTitle': 'models.json(供应商与模型)',
1351
1359
  'pi.providers.models.remove': '删除',
1352
1360
  'pi.providers.models.search': '搜索可用模型…',
1353
1361
  'pi.providers.models.remoteCount': '已拉取 {count} 个可用模型,点击添加',
@@ -471,6 +471,26 @@
471
471
  <span class="value">{{ opencodeConfigPath || t('common.notSelected') }}</span>
472
472
  </div>
473
473
  </template>
474
+ <template v-else-if="configMode === 'kilocode'">
475
+ <div class="status-chip">
476
+ <span class="label">{{ t('status.kilocodeProvider') }}</span>
477
+ <span class="value">{{ kilocodeProvider || t('common.notSelected') }}</span>
478
+ </div>
479
+ <div class="status-chip">
480
+ <span class="label">{{ t('status.kilocodeModel') }}</span>
481
+ <span class="value">{{ kilocodeModel || t('common.notSelected') }}</span>
482
+ </div>
483
+ </template>
484
+ <template v-else-if="configMode === 'pi'">
485
+ <div class="status-chip">
486
+ <span class="label">{{ t('status.piProvider') }}</span>
487
+ <span class="value">{{ piActiveProvider || t('common.notSelected') }}</span>
488
+ </div>
489
+ <div class="status-chip">
490
+ <span class="label">{{ t('status.piModel') }}</span>
491
+ <span class="value">{{ piActiveModel || t('common.notSelected') }}</span>
492
+ </div>
493
+ </template>
474
494
  <template v-else>
475
495
  <div class="status-chip">
476
496
  <span class="label">{{ t('status.configMode') }}</span>
@@ -178,6 +178,38 @@
178
178
  </div>
179
179
  </div>
180
180
  </section>
181
+ <section class="selector-section">
182
+ <div class="selector-header">
183
+ <span class="selector-title">{{ t('pi.files.title') }}</span>
184
+ </div>
185
+ <div class="config-template-hint">{{ t('pi.files.desc') }}</div>
186
+ <label class="form-label">{{ t('pi.files.settingsTitle') }}</label>
187
+ <textarea
188
+ class="template-textarea config-json-editor"
189
+ v-model="piSettingsJsonDraft"
190
+ spellcheck="false"
191
+ autocomplete="off"
192
+ :disabled="!isToolConfigWriteAllowed('pi')"
193
+ @input="piSettingsJsonInput"
194
+ placeholder="{}"></textarea>
195
+ <p v-if="piSettingsJsonError" class="config-template-hint error-text">{{ piSettingsJsonError }}</p>
196
+ <div class="settings-tab-actions">
197
+ <button type="button" class="btn-tool" @click="savePiSettingsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piSettingsJsonError">{{ t('common.save') }}</button>
198
+ </div>
199
+ <label class="form-label">{{ t('pi.files.modelsTitle') }}</label>
200
+ <textarea
201
+ class="template-textarea config-json-editor"
202
+ v-model="piModelsJsonDraft"
203
+ spellcheck="false"
204
+ autocomplete="off"
205
+ :disabled="!isToolConfigWriteAllowed('pi')"
206
+ @input="piModelsJsonInput"
207
+ placeholder="{}"></textarea>
208
+ <p v-if="piModelsJsonError" class="config-template-hint error-text">{{ piModelsJsonError }}</p>
209
+ <div class="settings-tab-actions">
210
+ <button type="button" class="btn-tool" @click="savePiModelsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piModelsJsonError">{{ t('common.save') }}</button>
211
+ </div>
212
+ </section>
181
213
  <div v-if="!isToolConfigWriteAllowed('pi')" class="tool-config-write-overlay">
182
214
  <div class="tool-config-write-overlay-card">
183
215
  <div class="tool-config-write-overlay-title">{{ t('toolConfig.pi.lockedTitle') }}</div>
@@ -661,13 +661,35 @@ return function render(_ctx, _cache) {
661
661
  _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.opencodeConfigPath || _ctx.t('common.notSelected')), 1 /* TEXT */)
662
662
  ])
663
663
  ], 64 /* STABLE_FRAGMENT */))
664
- : (_openBlock(), _createElementBlock("div", {
665
- key: 4,
666
- class: "status-chip"
667
- }, [
668
- _createElementVNode("span", { class: "label" }, _toDisplayString(_ctx.t('status.configMode')), 1 /* TEXT */),
669
- _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.t('common.notSelected')), 1 /* TEXT */)
670
- ]))
664
+ : (_ctx.configMode === 'kilocode')
665
+ ? (_openBlock(), _createElementBlock(_Fragment, { key: 4 }, [
666
+ _createElementVNode("div", { class: "status-chip" }, [
667
+ _createElementVNode("span", { class: "label" }, _toDisplayString(_ctx.t('status.kilocodeProvider')), 1 /* TEXT */),
668
+ _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.kilocodeProvider || _ctx.t('common.notSelected')), 1 /* TEXT */)
669
+ ]),
670
+ _createElementVNode("div", { class: "status-chip" }, [
671
+ _createElementVNode("span", { class: "label" }, _toDisplayString(_ctx.t('status.kilocodeModel')), 1 /* TEXT */),
672
+ _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.kilocodeModel || _ctx.t('common.notSelected')), 1 /* TEXT */)
673
+ ])
674
+ ], 64 /* STABLE_FRAGMENT */))
675
+ : (_ctx.configMode === 'pi')
676
+ ? (_openBlock(), _createElementBlock(_Fragment, { key: 5 }, [
677
+ _createElementVNode("div", { class: "status-chip" }, [
678
+ _createElementVNode("span", { class: "label" }, _toDisplayString(_ctx.t('status.piProvider')), 1 /* TEXT */),
679
+ _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.piActiveProvider || _ctx.t('common.notSelected')), 1 /* TEXT */)
680
+ ]),
681
+ _createElementVNode("div", { class: "status-chip" }, [
682
+ _createElementVNode("span", { class: "label" }, _toDisplayString(_ctx.t('status.piModel')), 1 /* TEXT */),
683
+ _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.piActiveModel || _ctx.t('common.notSelected')), 1 /* TEXT */)
684
+ ])
685
+ ], 64 /* STABLE_FRAGMENT */))
686
+ : (_openBlock(), _createElementBlock("div", {
687
+ key: 6,
688
+ class: "status-chip"
689
+ }, [
690
+ _createElementVNode("span", { class: "label" }, _toDisplayString(_ctx.t('status.configMode')), 1 /* TEXT */),
691
+ _createElementVNode("span", { class: "value" }, _toDisplayString(_ctx.t('common.notSelected')), 1 /* TEXT */)
692
+ ]))
671
693
  ]))
672
694
  : (!_ctx.sessionStandalone && _ctx.mainTab === 'sessions')
673
695
  ? (_openBlock(), _createElementBlock("div", {
@@ -3598,6 +3620,64 @@ return function render(_ctx, _cache) {
3598
3620
  ])
3599
3621
  ]))
3600
3622
  : _createCommentVNode("v-if", true),
3623
+ _createElementVNode("section", { class: "selector-section" }, [
3624
+ _createElementVNode("div", { class: "selector-header" }, [
3625
+ _createElementVNode("span", { class: "selector-title" }, _toDisplayString(_ctx.t('pi.files.title')), 1 /* TEXT */)
3626
+ ]),
3627
+ _createElementVNode("div", { class: "config-template-hint" }, _toDisplayString(_ctx.t('pi.files.desc')), 1 /* TEXT */),
3628
+ _createElementVNode("label", { class: "form-label" }, _toDisplayString(_ctx.t('pi.files.settingsTitle')), 1 /* TEXT */),
3629
+ _withDirectives(_createElementVNode("textarea", {
3630
+ class: "template-textarea config-json-editor",
3631
+ "onUpdate:modelValue": $event => ((_ctx.piSettingsJsonDraft) = $event),
3632
+ spellcheck: "false",
3633
+ autocomplete: "off",
3634
+ disabled: !_ctx.isToolConfigWriteAllowed('pi'),
3635
+ onInput: _ctx.piSettingsJsonInput,
3636
+ placeholder: "{}"
3637
+ }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "disabled", "onInput"]), [
3638
+ [_vModelText, _ctx.piSettingsJsonDraft]
3639
+ ]),
3640
+ (_ctx.piSettingsJsonError)
3641
+ ? (_openBlock(), _createElementBlock("p", {
3642
+ key: 0,
3643
+ class: "config-template-hint error-text"
3644
+ }, _toDisplayString(_ctx.piSettingsJsonError), 1 /* TEXT */))
3645
+ : _createCommentVNode("v-if", true),
3646
+ _createElementVNode("div", { class: "settings-tab-actions" }, [
3647
+ _createElementVNode("button", {
3648
+ type: "button",
3649
+ class: "btn-tool",
3650
+ onClick: _ctx.savePiSettingsJson,
3651
+ disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving || _ctx.piFileJsonSaving || !!_ctx.piSettingsJsonError
3652
+ }, _toDisplayString(_ctx.t('common.save')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
3653
+ ]),
3654
+ _createElementVNode("label", { class: "form-label" }, _toDisplayString(_ctx.t('pi.files.modelsTitle')), 1 /* TEXT */),
3655
+ _withDirectives(_createElementVNode("textarea", {
3656
+ class: "template-textarea config-json-editor",
3657
+ "onUpdate:modelValue": $event => ((_ctx.piModelsJsonDraft) = $event),
3658
+ spellcheck: "false",
3659
+ autocomplete: "off",
3660
+ disabled: !_ctx.isToolConfigWriteAllowed('pi'),
3661
+ onInput: _ctx.piModelsJsonInput,
3662
+ placeholder: "{}"
3663
+ }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "disabled", "onInput"]), [
3664
+ [_vModelText, _ctx.piModelsJsonDraft]
3665
+ ]),
3666
+ (_ctx.piModelsJsonError)
3667
+ ? (_openBlock(), _createElementBlock("p", {
3668
+ key: 1,
3669
+ class: "config-template-hint error-text"
3670
+ }, _toDisplayString(_ctx.piModelsJsonError), 1 /* TEXT */))
3671
+ : _createCommentVNode("v-if", true),
3672
+ _createElementVNode("div", { class: "settings-tab-actions" }, [
3673
+ _createElementVNode("button", {
3674
+ type: "button",
3675
+ class: "btn-tool",
3676
+ onClick: _ctx.savePiModelsJson,
3677
+ disabled: !_ctx.isToolConfigWriteAllowed('pi') || _ctx.piSaving || _ctx.piFileJsonSaving || !!_ctx.piModelsJsonError
3678
+ }, _toDisplayString(_ctx.t('common.save')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
3679
+ ])
3680
+ ]),
3601
3681
  (!_ctx.isToolConfigWriteAllowed('pi'))
3602
3682
  ? (_openBlock(), _createElementBlock("div", {
3603
3683
  key: 2,