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.
@@ -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 v-if="healthCheckResult" class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
34
- <div class="doctor-health-title">
35
- {{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
36
- <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
37
- </div>
38
- </div>
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
- <div class="doctor-action-impact">{{ issue.impact || (issue.impactKey ? t(issue.impactKey, issue.impactParams) : '') }}</div>
47
- <div
48
- v-if="issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target)"
49
- class="doctor-action-buttons">
50
- <template v-for="(action, index) in issue.actions" :key="issue.id + '-action-' + index">
51
- <button
52
- v-if="action.type === 'navigate' && action.target"
53
- type="button"
54
- class="btn-tool btn-tool-compact"
55
- @click="action.target ? switchMainTab(action.target) : null">
56
- {{ action.label || (action.labelKey ? t(action.labelKey, action.labelParams) : t('dashboard.doctor.open')) }}
57
- </button>
58
- </template>
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-action-footer">
63
- <button
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">