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
|
@@ -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,12 +1333,29 @@ 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',
|
|
1333
1343
|
'pi.providers.models.remoteLoading': 'Đang tải danh sách mô hình từ xa…',
|
|
1334
1344
|
'pi.providers.models.remoteFailed': 'Bạn vẫn có thể nhập ID mô hình thủ công.',
|
|
1335
1345
|
'pi.providers.models.noMatch': 'Không có mô hình phù hợp',
|
|
1346
|
+
'pi.providers.models.importTitle': 'Nhập mô hình từ endpoint',
|
|
1347
|
+
'pi.providers.models.addSelected': 'Thêm đã chọn ({count})',
|
|
1348
|
+
'pi.providers.models.name': 'Tên',
|
|
1349
|
+
'pi.providers.models.reasoning': 'Hỗ trợ suy luận',
|
|
1350
|
+
'pi.providers.models.contextWindow': 'Cửa sổ ngữ cảnh',
|
|
1351
|
+
'pi.providers.models.maxTokens': 'Đầu ra tối đa',
|
|
1352
|
+
'pi.providers.models.catalogFill': 'Tự động điền',
|
|
1353
|
+
'pi.providers.models.catalogFilling': 'Đang điền…',
|
|
1354
|
+
'pi.picker.searchPlaceholder': 'Tìm kiếm nhà cung cấp…',
|
|
1355
|
+
'pi.picker.group.custom': 'Endpoint tùy chỉnh',
|
|
1356
|
+
'pi.picker.group.apiKey': 'Nhà cung cấp API key',
|
|
1357
|
+
'pi.picker.empty': 'Không có mẫu nhà cung cấp phù hợp',
|
|
1358
|
+
'pi.picker.back': 'Quay lại chọn mẫu',
|
|
1336
1359
|
'pi.providers.delete': 'Xóa nhà cung cấp',
|
|
1337
1360
|
'pi.providers.addModal.title': 'Thêm nhà cung cấp',
|
|
1338
1361
|
'pi.providers.addModal.subtitle': 'Tạo nhà cung cấp Piper mới',
|
|
@@ -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,12 +1362,29 @@ 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} 個可用模型,點擊新增',
|
|
1362
1372
|
'pi.providers.models.remoteLoading': '正在取得遠端模型清單…',
|
|
1363
1373
|
'pi.providers.models.remoteFailed': '可手動輸入模型 ID。',
|
|
1364
1374
|
'pi.providers.models.noMatch': '無符合模型',
|
|
1375
|
+
'pi.providers.models.importTitle': '從端點匯入模型',
|
|
1376
|
+
'pi.providers.models.addSelected': '新增選取({count})',
|
|
1377
|
+
'pi.providers.models.name': '名稱',
|
|
1378
|
+
'pi.providers.models.reasoning': '支援推理',
|
|
1379
|
+
'pi.providers.models.contextWindow': '上下文視窗',
|
|
1380
|
+
'pi.providers.models.maxTokens': '最大輸出',
|
|
1381
|
+
'pi.providers.models.catalogFill': '自動填寫',
|
|
1382
|
+
'pi.providers.models.catalogFilling': '填寫中…',
|
|
1383
|
+
'pi.picker.searchPlaceholder': '搜尋供應商…',
|
|
1384
|
+
'pi.picker.group.custom': '自訂端點',
|
|
1385
|
+
'pi.picker.group.apiKey': 'API Key 服務商',
|
|
1386
|
+
'pi.picker.empty': '沒有符合的供應商範本',
|
|
1387
|
+
'pi.picker.back': '返回選擇範本',
|
|
1365
1388
|
'pi.providers.delete': '刪除供應商',
|
|
1366
1389
|
'pi.providers.addModal.title': '新增供應商',
|
|
1367
1390
|
'pi.providers.addModal.subtitle': '建立新的 Piper 供應商',
|
|
@@ -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,12 +1362,29 @@ 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} 个可用模型,点击添加',
|
|
1362
1372
|
'pi.providers.models.remoteLoading': '正在获取远端模型列表…',
|
|
1363
1373
|
'pi.providers.models.remoteFailed': '可手动输入模型 ID。',
|
|
1364
1374
|
'pi.providers.models.noMatch': '无匹配模型',
|
|
1375
|
+
'pi.providers.models.importTitle': '从端点导入模型',
|
|
1376
|
+
'pi.providers.models.addSelected': '添加选中({count})',
|
|
1377
|
+
'pi.providers.models.name': '名称',
|
|
1378
|
+
'pi.providers.models.reasoning': '支持推理',
|
|
1379
|
+
'pi.providers.models.contextWindow': '上下文窗口',
|
|
1380
|
+
'pi.providers.models.maxTokens': '最大输出',
|
|
1381
|
+
'pi.providers.models.catalogFill': '自动填充',
|
|
1382
|
+
'pi.providers.models.catalogFilling': '填充中…',
|
|
1383
|
+
'pi.picker.searchPlaceholder': '搜索供应商…',
|
|
1384
|
+
'pi.picker.group.custom': '自定义端点',
|
|
1385
|
+
'pi.picker.group.apiKey': 'API Key 服务商',
|
|
1386
|
+
'pi.picker.empty': '没有匹配的供应商模板',
|
|
1387
|
+
'pi.picker.back': '返回选择模板',
|
|
1365
1388
|
'pi.providers.delete': '删除供应商',
|
|
1366
1389
|
'pi.providers.addModal.title': '添加供应商',
|
|
1367
1390
|
'pi.providers.addModal.subtitle': '创建新的 Piper 供应商',
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const PI_PROVIDER_TEMPLATE_GROUPS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'custom',
|
|
4
|
+
i18nKey: 'pi.picker.group.custom',
|
|
5
|
+
templates: [
|
|
6
|
+
{ id: 'custom-openai', name: 'OpenAI Compatible', baseUrl: '', api: 'openai-completions' },
|
|
7
|
+
{ id: 'custom-anthropic', name: 'Anthropic Compatible', baseUrl: '', api: 'anthropic-messages' }
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: 'apiKey',
|
|
12
|
+
i18nKey: 'pi.picker.group.apiKey',
|
|
13
|
+
templates: [
|
|
14
|
+
{ id: 'openai', name: 'OpenAI', baseUrl: 'https://api.openai.com/v1', api: 'openai-responses' },
|
|
15
|
+
{ id: 'anthropic', name: 'Anthropic', baseUrl: 'https://api.anthropic.com', api: 'anthropic-messages' },
|
|
16
|
+
{ id: 'google-gemini', name: 'Google Gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1beta', api: 'google-generative-ai' },
|
|
17
|
+
{ id: 'deepseek', name: 'DeepSeek', baseUrl: 'https://api.deepseek.com', api: 'openai-completions' },
|
|
18
|
+
{ id: 'moonshot-kimi', name: 'Moonshot Kimi', baseUrl: 'https://api.moonshot.cn/v1', api: 'openai-completions' },
|
|
19
|
+
{ id: 'zhipu-glm', name: 'Zhipu GLM', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', api: 'openai-completions' },
|
|
20
|
+
{ id: 'openrouter', name: 'OpenRouter', baseUrl: 'https://openrouter.ai/api/v1', api: 'openai-completions' },
|
|
21
|
+
{ id: 'xai-grok', name: 'xAI Grok', baseUrl: 'https://api.x.ai/v1', api: 'openai-completions' },
|
|
22
|
+
{ id: 'groq', name: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', api: 'openai-completions' },
|
|
23
|
+
{ id: 'mistral', name: 'Mistral', baseUrl: 'https://api.mistral.ai/v1', api: 'openai-completions' }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
];
|
|
@@ -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') }}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<!-- Pi 新增供应商模板选择模态框 -->
|
|
2
|
+
<div v-if="showAddPiProviderModal && !editingPiProvider" class="modal-overlay" @click.self="cancelAddPiProviderModal">
|
|
3
|
+
<div class="modal modal-wide" role="dialog" aria-modal="true" aria-labelledby="pi-provider-picker-modal-title">
|
|
4
|
+
<div class="modal-title" id="pi-provider-picker-modal-title">{{ t('pi.providers.addModal.title') }}</div>
|
|
5
|
+
<div class="config-template-hint">{{ t('pi.providers.addModal.subtitle') }}</div>
|
|
6
|
+
<div class="pi-picker">
|
|
7
|
+
<input class="form-input" type="text" v-model="piProviderPickerQuery" autocomplete="off" spellcheck="false" :placeholder="t('pi.picker.searchPlaceholder')">
|
|
8
|
+
<div class="pi-picker-group" v-for="group in piProviderTemplateGroups()" :key="group.id">
|
|
9
|
+
<div class="pi-picker-group-title">{{ t(group.i18nKey) }}</div>
|
|
10
|
+
<div class="pi-picker-grid">
|
|
11
|
+
<button type="button" class="card pi-picker-card" v-for="tpl in group.templates" :key="tpl.id" @click="selectPiProviderTemplate(tpl.id)">
|
|
12
|
+
<span class="card-icon">{{ tpl.name.charAt(0).toUpperCase() }}</span>
|
|
13
|
+
<span class="pi-picker-card-name">{{ tpl.name }}</span>
|
|
14
|
+
<span v-if="tpl.baseUrl" class="pi-picker-card-url">{{ tpl.baseUrl }}</span>
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<p v-if="piProviderPickerQuery.trim() && !piProviderTemplateGroups().length" class="config-template-hint">{{ t('pi.picker.empty') }}</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="btn-group">
|
|
21
|
+
<button class="btn btn-cancel" @click="cancelAddPiProviderModal">{{ t('common.cancel') }}</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- Pi 供应商编辑器模态框 -->
|
|
27
|
+
<div v-if="editingPiProvider" class="modal-overlay" @click.self="closePiProviderEditor">
|
|
28
|
+
<div class="modal modal-wide" role="dialog" aria-modal="true" aria-labelledby="pi-provider-editor-modal-title">
|
|
29
|
+
<div class="modal-title" id="pi-provider-editor-modal-title">{{ editingPiProvider.isNew ? t('pi.providers.addModal.title') : t('pi.providers.editorTitle') }}</div>
|
|
30
|
+
<div v-if="!editingPiProvider.isNew" class="form-group">
|
|
31
|
+
<label class="form-label">{{ t('pi.providers.fields.name') }}</label>
|
|
32
|
+
<input class="form-input" type="text" :value="piProviderName(editingPiProvider.id)" readonly>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="form-group">
|
|
35
|
+
<label class="form-label">{{ t('pi.providers.fields.baseUrl') }}</label>
|
|
36
|
+
<input class="form-input" type="url" v-model="editingPiProvider.form.baseUrl" autocomplete="off" spellcheck="false" placeholder="https://" @change="fetchPiRemoteModels">
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form-group">
|
|
39
|
+
<label class="form-label">{{ t('pi.providers.fields.api') }}</label>
|
|
40
|
+
<select class="form-input" v-model="editingPiProvider.form.api" :disabled="!isToolConfigWriteAllowed('pi')">
|
|
41
|
+
<option value="openai-completions">openai-completions — {{ t('pi.providers.addModal.api.openaiCompletions') }}</option>
|
|
42
|
+
<option value="openai-responses">openai-responses — {{ t('pi.providers.addModal.api.openaiResponses') }}</option>
|
|
43
|
+
<option value="anthropic-messages">anthropic-messages — {{ t('pi.providers.addModal.api.anthropicMessages') }}</option>
|
|
44
|
+
<option value="google-generative-ai">google-generative-ai — {{ t('pi.providers.addModal.api.googleGenerativeAi') }}</option>
|
|
45
|
+
<option v-if="!['openai-completions', 'openai-responses', 'anthropic-messages', 'google-generative-ai'].includes(editingPiProvider.form.api)" :value="editingPiProvider.form.api">{{ editingPiProvider.form.api || '—' }}</option>
|
|
46
|
+
</select>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="form-group">
|
|
49
|
+
<label class="form-label">{{ t('pi.providers.fields.apiKey') }}</label>
|
|
50
|
+
<div class="input-with-toggle">
|
|
51
|
+
<input class="form-input" v-model="editingPiProvider.form.apiKey" :type="piShowKey ? 'text' : 'password'" autocomplete="off" spellcheck="false" placeholder="sk-..." @change="fetchPiRemoteModels">
|
|
52
|
+
<button type="button" class="input-toggle-btn" @mousedown.prevent @click="piShowKey = !piShowKey" :title="piShowKey ? t('common.hide') : t('common.show')" :aria-label="piShowKey ? t('common.hide') : t('common.show')">
|
|
53
|
+
<svg v-if="!piShowKey" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M10 4C5 4 1.73 8.11 1 10c.73 1.89 4 6 9 6s8.27-4.11 9-6c-.73-1.89-4-6-9-6z"/><circle cx="10" cy="10" r="3"/></svg>
|
|
54
|
+
<svg v-else viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M2 2l16 16M8.2 4.2A9.9 9.9 0 0 1 10 4c5 0 8.27 4.11 9 6-.44.94-1.5 2.7-3.2 4.2M14.5 14.5A5.9 5.9 0 0 1 10 16c-5 0-8.27-4.11-9-6 .76-1.66 2.2-3.6 4.3-5"/></svg>
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="form-group">
|
|
59
|
+
<label class="form-label">{{ t('pi.providers.fields.models') }}</label>
|
|
60
|
+
<p v-if="piRemoteModelsLoading" class="config-template-hint">{{ t('pi.providers.models.remoteLoading') }}</p>
|
|
61
|
+
<p v-else-if="piRemoteModelError" class="config-template-hint">{{ piRemoteModelError }} · {{ t('pi.providers.models.remoteFailed') }}</p>
|
|
62
|
+
<div class="card-list">
|
|
63
|
+
<div class="card" v-for="(model, index) in editingPiProvider.form.models" :key="index">
|
|
64
|
+
<div class="input-with-toggle">
|
|
65
|
+
<input class="form-input" type="text" v-model="model.id" autocomplete="off" spellcheck="false" placeholder="claude-sonnet-4" :disabled="!isToolConfigWriteAllowed('pi')">
|
|
66
|
+
<button type="button" class="btn-tool input-toggle-btn" @click="editingPiProvider.form.models.splice(index, 1)" :disabled="!isToolConfigWriteAllowed('pi')">{{ t('pi.providers.models.remove') }}</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="card">
|
|
70
|
+
<div class="input-with-toggle">
|
|
71
|
+
<input class="form-input" type="text" v-model="piModelSearch" autocomplete="off" spellcheck="false" :placeholder="t('pi.providers.models.search')" @keydown.enter.prevent="addPiRemoteModel(piModelSearch.trim())">
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="pi-model-import-section">
|
|
75
|
+
<span class="selector-title">{{ t('pi.providers.models.importTitle') }}</span>
|
|
76
|
+
<div class="pi-model-import-list" v-if="!piRemoteModelsLoading && piRemoteSelectableModels().length > 0">
|
|
77
|
+
<label class="pi-model-import-item" v-for="id in piRemoteSelectableModels()" :key="id">
|
|
78
|
+
<input type="checkbox" :checked="isPiRemoteChecked(id)" @change="togglePiRemoteChecked(id)" :disabled="!isToolConfigWriteAllowed('pi')">
|
|
79
|
+
<span class="pi-model-import-id">{{ id }}</span>
|
|
80
|
+
</label>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="settings-tab-actions" v-if="!piRemoteModelsLoading && piRemoteSelectableModels().length > 0">
|
|
83
|
+
<button type="button" class="btn-tool" :disabled="!isToolConfigWriteAllowed('pi') || piCheckedRemoteCount() === 0" @click="addSelectedPiRemoteModels">{{ t('pi.providers.models.addSelected', { count: piCheckedRemoteCount() }) }}</button>
|
|
84
|
+
</div>
|
|
85
|
+
<p v-if="!piRemoteModelsLoading && !piRemoteModelError && piRemoteModels.length" class="config-template-hint">{{ t('pi.providers.models.remoteCount', { count: piRemoteModels.length }) }}</p>
|
|
86
|
+
<p v-if="!piRemoteModelsLoading && !piRemoteModelError && piModelSearch.trim() && piRemoteSelectableModels().length === 0" class="config-template-hint">{{ t('pi.providers.models.noMatch') }}</p>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="btn-group">
|
|
91
|
+
<button class="btn btn-cancel" @click="closePiProviderEditor">{{ t('common.cancel') }}</button>
|
|
92
|
+
<button v-if="!editingPiProvider.isNew" class="btn btn-confirm btn-danger" @click="confirmDeletePiProvider" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t('pi.providers.delete') }}</button>
|
|
93
|
+
<button class="btn btn-confirm" @click="savePiProvider" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t(piSaving ? 'common.saving' : 'common.save') }}</button>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
@@ -56,128 +56,6 @@
|
|
|
56
56
|
<div v-if="!piProviderIds.length" class="config-template-hint">{{ t('pi.providers.empty') }}</div>
|
|
57
57
|
</section>
|
|
58
58
|
|
|
59
|
-
<section class="selector-section" v-if="editingPiProvider">
|
|
60
|
-
<div class="selector-header">
|
|
61
|
-
<span class="selector-title">{{ t('pi.providers.editorTitle') }}</span>
|
|
62
|
-
<div class="settings-tab-actions">
|
|
63
|
-
<button type="button" class="btn-tool" @click="savePiProvider" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t('common.save') }}</button>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
<div class="codex-config-grid">
|
|
67
|
-
<div class="form-group codex-config-field">
|
|
68
|
-
<label class="form-label">{{ t('pi.providers.fields.name') }}</label>
|
|
69
|
-
<input class="form-input" type="text" :value="piProviderName(editingPiProvider.id)" readonly>
|
|
70
|
-
</div>
|
|
71
|
-
<div class="form-group codex-config-field">
|
|
72
|
-
<label class="form-label">{{ t('pi.providers.fields.baseUrl') }}</label>
|
|
73
|
-
<input class="form-input" type="url" v-model="editingPiProvider.form.baseUrl" autocomplete="off" spellcheck="false" placeholder="https://">
|
|
74
|
-
</div>
|
|
75
|
-
<div class="form-group codex-config-field">
|
|
76
|
-
<label class="form-label">{{ t('pi.providers.fields.api') }}</label>
|
|
77
|
-
<select class="form-input" v-model="editingPiProvider.form.api" :disabled="!isToolConfigWriteAllowed('pi')">
|
|
78
|
-
<option value="openai-completions">openai-completions — {{ t('pi.providers.addModal.api.openaiCompletions') }}</option>
|
|
79
|
-
<option value="openai-responses">openai-responses — {{ t('pi.providers.addModal.api.openaiResponses') }}</option>
|
|
80
|
-
<option value="anthropic-messages">anthropic-messages — {{ t('pi.providers.addModal.api.anthropicMessages') }}</option>
|
|
81
|
-
<option value="google-generative-ai">google-generative-ai — {{ t('pi.providers.addModal.api.googleGenerativeAi') }}</option>
|
|
82
|
-
<option v-if="!['openai-completions', 'openai-responses', 'anthropic-messages', 'google-generative-ai'].includes(editingPiProvider.form.api)" :value="editingPiProvider.form.api">{{ editingPiProvider.form.api || '—' }}</option>
|
|
83
|
-
</select>
|
|
84
|
-
</div>
|
|
85
|
-
<div class="form-group codex-config-field">
|
|
86
|
-
<label class="form-label">{{ t('pi.providers.fields.apiKey') }}</label>
|
|
87
|
-
<div class="input-with-toggle">
|
|
88
|
-
<input class="form-input" v-model="editingPiProvider.form.apiKey" :type="piShowKey ? 'text' : 'password'" autocomplete="off" spellcheck="false" placeholder="sk-...">
|
|
89
|
-
<button type="button" class="input-toggle-btn" @mousedown.prevent @click="piShowKey = !piShowKey" :title="piShowKey ? t('common.hide') : t('common.show')" :aria-label="piShowKey ? t('common.hide') : t('common.show')">
|
|
90
|
-
<svg v-if="!piShowKey" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M10 4C5 4 1.73 8.11 1 10c.73 1.89 4 6 9 6s8.27-4.11 9-6c-.73-1.89-4-6-9-6z"/><circle cx="10" cy="10" r="3"/></svg>
|
|
91
|
-
<svg v-else viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M2 2l16 16M8.2 4.2A9.9 9.9 0 0 1 10 4c5 0 8.27 4.11 9 6-.44.94-1.5 2.7-3.2 4.2M14.5 14.5A5.9 5.9 0 0 1 10 16c-5 0-8.27-4.11-9-6 .76-1.66 2.2-3.6 4.3-5"/></svg>
|
|
92
|
-
</button>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
<div class="form-group">
|
|
97
|
-
<label class="form-label">{{ t('pi.providers.fields.models') }}</label>
|
|
98
|
-
<p v-if="piRemoteModelsLoading" class="config-template-hint">{{ t('pi.providers.models.remoteLoading') }}</p>
|
|
99
|
-
<p v-else-if="piRemoteModelError" class="config-template-hint">{{ piRemoteModelError }} · {{ t('pi.providers.models.remoteFailed') }}</p>
|
|
100
|
-
<div class="card-list">
|
|
101
|
-
<div class="card" v-for="(model, index) in editingPiProvider.form.models" :key="index">
|
|
102
|
-
<div class="input-with-toggle">
|
|
103
|
-
<input class="form-input" type="text" v-model="model.id" autocomplete="off" spellcheck="false" placeholder="claude-sonnet-4" :disabled="!isToolConfigWriteAllowed('pi')" @input="piModelSearch = model.id">
|
|
104
|
-
<button type="button" class="btn-tool input-toggle-btn" @click="editingPiProvider.form.models.splice(index, 1)" :disabled="!isToolConfigWriteAllowed('pi')">{{ t('pi.providers.models.remove') }}</button>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
<div class="card" v-if="editingPiProvider.form.models.length === 0">
|
|
108
|
-
<input class="form-input" type="text" v-model="piModelSearch" autocomplete="off" spellcheck="false" :placeholder="t('pi.providers.models.search')" @keydown.enter.prevent="addPiRemoteModel(piModelSearch.trim())">
|
|
109
|
-
</div>
|
|
110
|
-
<div class="card" v-if="!piRemoteModelsLoading && !piRemoteModelError && piModelSearch.trim() && piFilteredRemoteModels().length > 0">
|
|
111
|
-
<button type="button" class="btn-tool" style="width: 100%; text-align: left; justify-content: flex-start;" v-for="id in piFilteredRemoteModels()" :key="id" @click="addPiRemoteModel(id)">{{ id }}</button>
|
|
112
|
-
<p class="config-template-hint">{{ t('pi.providers.models.remoteCount', { count: piRemoteModels.length }) }}</p>
|
|
113
|
-
</div>
|
|
114
|
-
<p v-if="!piRemoteModelsLoading && !piRemoteModelError && piModelSearch.trim() && piFilteredRemoteModels().length === 0" class="config-template-hint">{{ t('pi.providers.models.noMatch') }}</p>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
<div class="settings-tab-actions">
|
|
118
|
-
<button type="button" class="btn-tool" @click="confirmDeletePiProvider" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t('pi.providers.delete') }}</button>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
<section class="selector-section">
|
|
122
|
-
<div class="selector-header">
|
|
123
|
-
<span class="selector-title">{{ t('pi.providers.json.title') }}</span>
|
|
124
|
-
</div>
|
|
125
|
-
<p class="config-template-hint">{{ t('pi.providers.json.desc') }}</p>
|
|
126
|
-
<label class="form-label">{{ t('pi.providers.json.previewTitle') }}</label>
|
|
127
|
-
<pre class="config-json-preview">{{ piProviderPreviewJson() }}</pre>
|
|
128
|
-
<label class="form-label">{{ t('pi.providers.json.editTitle') }}</label>
|
|
129
|
-
<textarea
|
|
130
|
-
class="template-textarea config-json-editor"
|
|
131
|
-
v-model="editingPiProvider.form.configJsonDraft"
|
|
132
|
-
spellcheck="false"
|
|
133
|
-
autocomplete="off"
|
|
134
|
-
:disabled="!isToolConfigWriteAllowed('pi')"
|
|
135
|
-
@input="piEditorJsonDraftInput"
|
|
136
|
-
placeholder="{}"></textarea>
|
|
137
|
-
<p v-if="piEditorJsonError" class="config-template-hint error-text">{{ piEditorJsonError }}</p>
|
|
138
|
-
</section>
|
|
139
|
-
</section>
|
|
140
|
-
|
|
141
|
-
<section class="selector-section" v-if="showAddPiProviderModal">
|
|
142
|
-
<div class="selector-header">
|
|
143
|
-
<span class="selector-title">{{ t('pi.providers.addModal.title') }}</span>
|
|
144
|
-
<div class="settings-tab-actions">
|
|
145
|
-
<button type="button" class="btn-tool" @click="addPiProviderFromModal" :disabled="!isToolConfigWriteAllowed('pi') || piSaving">{{ t('common.save') }}</button>
|
|
146
|
-
<button type="button" class="btn-tool" @click="cancelAddPiProviderModal">{{ t('common.cancel') }}</button>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
<div class="config-template-hint">{{ t('pi.providers.addModal.subtitle') }}</div>
|
|
150
|
-
<div class="codex-config-grid">
|
|
151
|
-
<div class="form-group codex-config-field">
|
|
152
|
-
<label class="form-label">{{ t('pi.providers.addModal.fields.baseUrl') }}</label>
|
|
153
|
-
<input class="form-input" type="url" v-model="addingPiProviderBaseUrl" autocomplete="off" spellcheck="false" placeholder="https://" @input="onAddingPiEndpointChange">
|
|
154
|
-
</div>
|
|
155
|
-
<div class="form-group codex-config-field">
|
|
156
|
-
<label class="form-label">{{ t('pi.providers.addModal.fields.api') }}</label>
|
|
157
|
-
<select class="form-input" v-model="addingPiProviderApi">
|
|
158
|
-
<option value="openai-completions">openai-completions — {{ t('pi.providers.addModal.api.openaiCompletions') }}</option>
|
|
159
|
-
<option value="openai-responses">openai-responses — {{ t('pi.providers.addModal.api.openaiResponses') }}</option>
|
|
160
|
-
<option value="anthropic-messages">anthropic-messages — {{ t('pi.providers.addModal.api.anthropicMessages') }}</option>
|
|
161
|
-
<option value="google-generative-ai">google-generative-ai — {{ t('pi.providers.addModal.api.googleGenerativeAi') }}</option>
|
|
162
|
-
</select>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="form-group codex-config-field">
|
|
165
|
-
<label class="form-label">{{ t('pi.providers.addModal.fields.apiKey') }}</label>
|
|
166
|
-
<input class="form-input" type="password" v-model="addingPiProviderApiKey" autocomplete="off" spellcheck="false" :placeholder="t('common.optional')" @input="onAddingPiEndpointChange">
|
|
167
|
-
</div>
|
|
168
|
-
<div class="form-group codex-config-field">
|
|
169
|
-
<label class="form-label">{{ t('pi.providers.fields.models') }}</label>
|
|
170
|
-
<p v-if="addingPiRemoteLoading" class="config-template-hint">{{ t('pi.providers.models.remoteLoading') }}</p>
|
|
171
|
-
<p v-else-if="addingPiRemoteError" class="config-template-hint">{{ addingPiRemoteError }} · {{ t('pi.providers.models.remoteFailed') }}</p>
|
|
172
|
-
<input class="form-input" type="text" v-model="addingPiProviderModel" autocomplete="off" spellcheck="false" :placeholder="addingPiRemoteModels.length ? t('pi.providers.models.search') : 'claude-sonnet-4'">
|
|
173
|
-
<template v-if="!addingPiRemoteLoading && addingPiProviderModel.trim() && piFilteredAddingRemoteModels().length > 0">
|
|
174
|
-
<button type="button" class="btn-tool" style="width: 100%; text-align: left; justify-content: flex-start; margin-top: 8px;" v-for="id in piFilteredAddingRemoteModels()" :key="id" @click="addingPiProviderModel = id">{{ id }}</button>
|
|
175
|
-
<p class="config-template-hint">{{ t('pi.providers.models.remoteCount', { count: addingPiRemoteModels.length }) }}</p>
|
|
176
|
-
</template>
|
|
177
|
-
<p v-else-if="!addingPiRemoteLoading && !addingPiRemoteError && addingPiProviderModel.trim() && addingPiRemoteModels.length > 0" class="config-template-hint">{{ t('pi.providers.models.noMatch') }}</p>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</section>
|
|
181
59
|
<section class="selector-section">
|
|
182
60
|
<div class="selector-header">
|
|
183
61
|
<span class="selector-title">{{ t('pi.files.title') }}</span>
|
|
@@ -195,7 +73,29 @@
|
|
|
195
73
|
<p v-if="piSettingsJsonError" class="config-template-hint error-text">{{ piSettingsJsonError }}</p>
|
|
196
74
|
<div class="settings-tab-actions">
|
|
197
75
|
<button type="button" class="btn-tool" @click="savePiSettingsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piSettingsJsonError">{{ t('common.save') }}</button>
|
|
76
|
+
<button type="button" class="btn-mini" @click="openPiConfigHistory('settings')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
|
|
198
77
|
</div>
|
|
78
|
+
<details v-if="piHistoryTarget === 'settings'" class="prompt-history-panel" open>
|
|
79
|
+
<summary class="prompt-history-summary">
|
|
80
|
+
<span>{{ t('common.history') }}</span>
|
|
81
|
+
<button type="button" class="btn-mini prompt-history-close" @click="closePiConfigHistory" :title="t('common.close')">✕</button>
|
|
82
|
+
</summary>
|
|
83
|
+
<div class="prompt-history-body">
|
|
84
|
+
<ul v-if="!piHistoryLoading && !piHistoryError && piHistoryItems.length" class="prompt-history-list">
|
|
85
|
+
<li v-for="item in piHistoryItems" :key="item.id" :class="['prompt-history-item', { active: piHistoryPreviewId === item.id }]" @click="viewPiConfigHistoryItem(item)">
|
|
86
|
+
<span class="prompt-history-time">{{ item.id }}</span>
|
|
87
|
+
<span class="prompt-history-size">{{ Math.round(item.size / 1024 * 10) / 10 }} KB</span>
|
|
88
|
+
</li>
|
|
89
|
+
</ul>
|
|
90
|
+
<div v-if="piHistoryLoading" class="state-message">{{ t('diff.generating') }}</div>
|
|
91
|
+
<div v-else-if="piHistoryError" class="state-message error">{{ piHistoryError }}</div>
|
|
92
|
+
<div v-else-if="!piHistoryItems.length" class="state-message">{{ t('toast.history.empty') }}</div>
|
|
93
|
+
<div v-if="piHistoryPreviewContent" class="prompt-history-preview-wrap">
|
|
94
|
+
<pre class="prompt-history-preview">{{ piHistoryPreviewContent }}</pre>
|
|
95
|
+
<button type="button" class="btn-mini btn-confirm-mini" @click="applyPiConfigHistory" :disabled="piHistoryApplying || !isToolConfigWriteAllowed('pi')">{{ t('pi.history.apply') }}</button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</details>
|
|
199
99
|
<label class="form-label">{{ t('pi.files.modelsTitle') }}</label>
|
|
200
100
|
<textarea
|
|
201
101
|
class="template-textarea config-json-editor"
|
|
@@ -208,7 +108,29 @@
|
|
|
208
108
|
<p v-if="piModelsJsonError" class="config-template-hint error-text">{{ piModelsJsonError }}</p>
|
|
209
109
|
<div class="settings-tab-actions">
|
|
210
110
|
<button type="button" class="btn-tool" @click="savePiModelsJson" :disabled="!isToolConfigWriteAllowed('pi') || piSaving || piFileJsonSaving || !!piModelsJsonError">{{ t('common.save') }}</button>
|
|
111
|
+
<button type="button" class="btn-mini" @click="openPiConfigHistory('models')" :disabled="piHistoryLoading">{{ t('common.history') }}</button>
|
|
211
112
|
</div>
|
|
113
|
+
<details v-if="piHistoryTarget === 'models'" class="prompt-history-panel" open>
|
|
114
|
+
<summary class="prompt-history-summary">
|
|
115
|
+
<span>{{ t('common.history') }}</span>
|
|
116
|
+
<button type="button" class="btn-mini prompt-history-close" @click="closePiConfigHistory" :title="t('common.close')">✕</button>
|
|
117
|
+
</summary>
|
|
118
|
+
<div class="prompt-history-body">
|
|
119
|
+
<ul v-if="!piHistoryLoading && !piHistoryError && piHistoryItems.length" class="prompt-history-list">
|
|
120
|
+
<li v-for="item in piHistoryItems" :key="item.id" :class="['prompt-history-item', { active: piHistoryPreviewId === item.id }]" @click="viewPiConfigHistoryItem(item)">
|
|
121
|
+
<span class="prompt-history-time">{{ item.id }}</span>
|
|
122
|
+
<span class="prompt-history-size">{{ Math.round(item.size / 1024 * 10) / 10 }} KB</span>
|
|
123
|
+
</li>
|
|
124
|
+
</ul>
|
|
125
|
+
<div v-if="piHistoryLoading" class="state-message">{{ t('diff.generating') }}</div>
|
|
126
|
+
<div v-else-if="piHistoryError" class="state-message error">{{ piHistoryError }}</div>
|
|
127
|
+
<div v-else-if="!piHistoryItems.length" class="state-message">{{ t('toast.history.empty') }}</div>
|
|
128
|
+
<div v-if="piHistoryPreviewContent" class="prompt-history-preview-wrap">
|
|
129
|
+
<pre class="prompt-history-preview">{{ piHistoryPreviewContent }}</pre>
|
|
130
|
+
<button type="button" class="btn-mini btn-confirm-mini" @click="applyPiConfigHistory" :disabled="piHistoryApplying || !isToolConfigWriteAllowed('pi')">{{ t('pi.history.apply') }}</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</details>
|
|
212
134
|
</section>
|
|
213
135
|
<div v-if="!isToolConfigWriteAllowed('pi')" class="tool-config-write-overlay">
|
|
214
136
|
<div class="tool-config-write-overlay-card">
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
<div class="docs-toolbar-grid">
|
|
13
13
|
<div class="docs-toolbar-card">
|
|
14
|
-
<label class="form-label"
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
</
|
|
14
|
+
<label class="form-label">{{ t('common.packageManager') }}</label>
|
|
15
|
+
<div class="install-action-tabs">
|
|
16
|
+
<button type="button" class="btn-mini" :class="{ active: installPackageManager === 'npm' }" @click="installPackageManager = 'npm'">npm</button>
|
|
17
|
+
<button type="button" class="btn-mini" :class="{ active: installPackageManager === 'pnpm' }" @click="installPackageManager = 'pnpm'">pnpm</button>
|
|
18
|
+
<button type="button" class="btn-mini" :class="{ active: installPackageManager === 'bun' }" @click="installPackageManager = 'bun'">bun</button>
|
|
19
|
+
</div>
|
|
20
20
|
</div>
|
|
21
|
-
<div class="docs-toolbar-card
|
|
21
|
+
<div class="docs-toolbar-card">
|
|
22
22
|
<label class="form-label">{{ t('common.mirror') }}</label>
|
|
23
23
|
<div class="install-action-tabs">
|
|
24
24
|
<button type="button" class="btn-mini" :class="{ active: installRegistryPreset === 'npmmirror' }" @click="setInstallRegistryPreset('npmmirror')">npmmirror</button>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<div class="form-hint install-registry-hint" v-if="installRegistryPreview">{{ t('docs.registryHintPrefix') }} --registry={{ installRegistryPreview }}</div>
|
|
30
30
|
<div class="form-hint install-registry-hint" v-else-if="installRegistryPreset === 'custom'">{{ t('docs.registryHintCustom') }}</div>
|
|
31
31
|
</div>
|
|
32
|
-
<div class="docs-toolbar-card
|
|
32
|
+
<div class="docs-toolbar-card">
|
|
33
33
|
<label class="form-label">{{ t('common.action') }}</label>
|
|
34
34
|
<div class="install-action-tabs">
|
|
35
35
|
<button type="button" class="btn-mini" :class="{ active: installCommandAction === 'install' }" @click="setInstallCommandAction('install')">{{ t('common.install') }}</button>
|
|
@@ -39,20 +39,6 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
|
-
<div class="docs-summary-strip">
|
|
43
|
-
<div class="docs-summary-item">
|
|
44
|
-
<span class="docs-summary-label">{{ t('common.targets') }}</span>
|
|
45
|
-
<strong class="docs-summary-value">{{ installTargetCards.length }}</strong>
|
|
46
|
-
</div>
|
|
47
|
-
<div class="docs-summary-item">
|
|
48
|
-
<span class="docs-summary-label">{{ t('common.currentPm') }}</span>
|
|
49
|
-
<strong class="docs-summary-value">{{ String(installPackageManager || 'npm').toUpperCase() }}</strong>
|
|
50
|
-
</div>
|
|
51
|
-
<div class="docs-summary-item docs-summary-item-wide">
|
|
52
|
-
<span class="docs-summary-label">{{ t('common.registry') }}</span>
|
|
53
|
-
<strong class="docs-summary-value">{{ installRegistryPreview || 'npmmirror' }}</strong>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
42
|
</div>
|
|
57
43
|
|
|
58
44
|
<div class="selector-section">
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
@click="setSkillsTargetApp('claude', { silent: false })">
|
|
26
26
|
Claude Code
|
|
27
27
|
</button>
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
:class="['skills-target-chip', { active: skillsTargetApp === 'pi' }]"
|
|
31
|
+
:aria-pressed="skillsTargetApp === 'pi'"
|
|
32
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
33
|
+
@click="setSkillsTargetApp('pi', { silent: false })">
|
|
34
|
+
Pi
|
|
35
|
+
</button>
|
|
28
36
|
</div>
|
|
29
37
|
</div>
|
|
30
38
|
<div class="skills-header-actions">
|