codexmate 0.1.17 → 0.1.19
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.js +130 -3
- package/package.json +1 -1
- package/web-ui/app.js +5 -10
- package/web-ui/index.html +1 -0
- package/web-ui/modules/app.methods.navigation.mjs +2 -2
- package/web-ui/modules/app.methods.pi-config.mjs +208 -120
- package/web-ui/modules/i18n/locales/en.mjs +14 -0
- package/web-ui/modules/i18n/locales/ja.mjs +14 -0
- package/web-ui/modules/i18n/locales/vi.mjs +14 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +14 -0
- package/web-ui/modules/i18n/locales/zh.mjs +14 -0
- package/web-ui/modules/pi-provider-templates.mjs +26 -0
- package/web-ui/partials/index/modals-pi-provider.html +96 -0
- package/web-ui/partials/index/panel-config-pi.html +0 -122
- package/web-ui/partials/index/panel-dashboard.html +51 -44
- package/web-ui/res/web-ui-render.precompiled.js +404 -421
- package/web-ui/session-helpers.mjs +12 -23
- package/web-ui/styles/dashboard.css +52 -158
- package/web-ui/styles/settings-panel.css +12 -0
|
@@ -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>
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
role="tabpanel"
|
|
6
6
|
:aria-labelledby="'tab-dashboard'">
|
|
7
7
|
<div class="selector-section doctor-hero">
|
|
8
|
-
<div class="selector-header">
|
|
8
|
+
<div class="selector-header doctor-header">
|
|
9
9
|
<span class="selector-title">{{ t('dashboard.doctor.title') }}</span>
|
|
10
10
|
<div class="selector-actions">
|
|
11
11
|
<button
|
|
12
|
-
class="btn-tool btn-tool-compact"
|
|
12
|
+
class="btn-tool btn-tool-compact doctor-run-btn"
|
|
13
13
|
@click="runHealthCheck"
|
|
14
14
|
:disabled="loading || !!initError || healthCheckLoading">
|
|
15
15
|
{{ healthCheckLoading ? t('dashboard.doctor.checking') : t('dashboard.doctor.runChecks') }}
|
|
@@ -30,52 +30,59 @@
|
|
|
30
30
|
<strong>{{ inspectorModelLoadStatus }}</strong>
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
|
33
|
-
<div
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<div v-if="healthCheckResult && healthCheckResult.report" class="doctor-action-list">
|
|
40
|
-
<template v-if="healthCheckResult.report.issues && healthCheckResult.report.issues.length">
|
|
41
|
-
<div v-for="issue in healthCheckResult.report.issues" :key="issue.id" class="doctor-action-card">
|
|
42
|
-
<div class="doctor-action-head">
|
|
43
|
-
<div class="doctor-action-title">{{ issue.problem || (issue.problemKey ? t(issue.problemKey, issue.problemParams) : '') }}</div>
|
|
44
|
-
<div :class="['doctor-action-severity', issue.severity]">{{ issue.severityLabel || issue.severity }}</div>
|
|
33
|
+
<div class="doctor-result-section">
|
|
34
|
+
<template v-if="healthCheckResult">
|
|
35
|
+
<div class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
|
|
36
|
+
<div class="doctor-health-title">
|
|
37
|
+
{{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
|
|
38
|
+
<span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
|
|
45
39
|
</div>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class="doctor-action-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
</div>
|
|
41
|
+
<div v-if="healthCheckResult.report" class="doctor-action-list">
|
|
42
|
+
<template v-if="healthCheckResult.report.issues && healthCheckResult.report.issues.length">
|
|
43
|
+
<div v-for="issue in healthCheckResult.report.issues" :key="issue.id" class="doctor-action-card">
|
|
44
|
+
<div class="doctor-action-head">
|
|
45
|
+
<div class="doctor-action-title">{{ issue.problem || (issue.problemKey ? t(issue.problemKey, issue.problemParams) : '') }}</div>
|
|
46
|
+
<div :class="['doctor-action-severity', issue.severity]">{{ issue.severityLabel || issue.severity }}</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="doctor-action-impact">{{ issue.impact || (issue.impactKey ? t(issue.impactKey, issue.impactParams) : '') }}</div>
|
|
49
|
+
<div
|
|
50
|
+
v-if="issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target)"
|
|
51
|
+
class="doctor-action-buttons">
|
|
52
|
+
<template v-for="(action, index) in issue.actions" :key="issue.id + '-action-' + index">
|
|
53
|
+
<button
|
|
54
|
+
v-if="action.type === 'navigate' && action.target"
|
|
55
|
+
type="button"
|
|
56
|
+
class="btn-tool btn-tool-compact"
|
|
57
|
+
@click="action.target ? switchMainTab(action.target) : null">
|
|
58
|
+
{{ action.label || (action.labelKey ? t(action.labelKey, action.labelParams) : t('dashboard.doctor.open')) }}
|
|
59
|
+
</button>
|
|
60
|
+
</template>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
<div class="doctor-action-footer">
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
class="btn-tool btn-tool-compact"
|
|
68
|
+
@click="healthCheckResult && healthCheckResult.report
|
|
69
|
+
? downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
|
|
70
|
+
: null">
|
|
71
|
+
{{ t('dashboard.doctor.export.json') }}
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="btn-tool btn-tool-compact"
|
|
76
|
+
@click="healthCheckResult && healthCheckResult.report
|
|
77
|
+
? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
|
|
78
|
+
: null">
|
|
79
|
+
{{ t('dashboard.doctor.export.md') }}
|
|
80
|
+
</button>
|
|
59
81
|
</div>
|
|
60
82
|
</div>
|
|
61
83
|
</template>
|
|
62
|
-
<div class="doctor-
|
|
63
|
-
<
|
|
64
|
-
type="button"
|
|
65
|
-
class="btn-tool btn-tool-compact"
|
|
66
|
-
@click="healthCheckResult && healthCheckResult.report
|
|
67
|
-
? downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
|
|
68
|
-
: null">
|
|
69
|
-
{{ t('dashboard.doctor.export.json') }}
|
|
70
|
-
</button>
|
|
71
|
-
<button
|
|
72
|
-
type="button"
|
|
73
|
-
class="btn-tool btn-tool-compact"
|
|
74
|
-
@click="healthCheckResult && healthCheckResult.report
|
|
75
|
-
? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
|
|
76
|
-
: null">
|
|
77
|
-
{{ t('dashboard.doctor.export.md') }}
|
|
78
|
-
</button>
|
|
84
|
+
<div v-else class="doctor-result-empty">
|
|
85
|
+
<span class="doctor-result-empty-text">{{ t('dashboard.doctor.empty') }}</span>
|
|
79
86
|
</div>
|
|
80
87
|
</div>
|
|
81
88
|
<div class="doctor-grid">
|