codexmate 0.0.20 → 0.0.21

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.
Files changed (102) hide show
  1. package/README.en.md +349 -259
  2. package/README.md +284 -252
  3. package/cli/agents-files.js +162 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +580 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/config-health.js +338 -338
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/skills.js +1141 -0
  12. package/cli/zip-commands.js +510 -0
  13. package/cli.js +13101 -13497
  14. package/lib/cli-file-utils.js +151 -151
  15. package/lib/cli-models-utils.js +419 -311
  16. package/lib/cli-network-utils.js +164 -164
  17. package/lib/cli-path-utils.js +69 -0
  18. package/lib/cli-session-utils.js +121 -121
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/cli-utils.js +155 -155
  21. package/lib/download-artifacts.js +77 -0
  22. package/lib/mcp-stdio.js +440 -440
  23. package/lib/task-orchestrator.js +869 -0
  24. package/lib/text-diff.js +303 -303
  25. package/lib/workflow-engine.js +340 -340
  26. package/package.json +74 -70
  27. package/res/json5.min.js +1 -1
  28. package/res/vue.global.prod.js +13 -0
  29. package/web-ui/app.js +530 -397
  30. package/web-ui/index.html +33 -30
  31. package/web-ui/logic.agents-diff.mjs +386 -386
  32. package/web-ui/logic.claude.mjs +168 -108
  33. package/web-ui/logic.mjs +5 -5
  34. package/web-ui/logic.runtime.mjs +124 -124
  35. package/web-ui/logic.sessions.mjs +581 -263
  36. package/web-ui/modules/api.mjs +90 -69
  37. package/web-ui/modules/app.computed.dashboard.mjs +113 -113
  38. package/web-ui/modules/app.computed.index.mjs +15 -13
  39. package/web-ui/modules/app.computed.main-tabs.mjs +195 -0
  40. package/web-ui/modules/app.computed.session.mjs +507 -141
  41. package/web-ui/modules/app.constants.mjs +15 -15
  42. package/web-ui/modules/app.methods.agents.mjs +493 -493
  43. package/web-ui/modules/app.methods.claude-config.mjs +174 -174
  44. package/web-ui/modules/app.methods.codex-config.mjs +640 -640
  45. package/web-ui/modules/app.methods.index.mjs +88 -86
  46. package/web-ui/modules/app.methods.install.mjs +149 -157
  47. package/web-ui/modules/app.methods.navigation.mjs +619 -478
  48. package/web-ui/modules/app.methods.openclaw-core.mjs +814 -514
  49. package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -337
  50. package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -251
  51. package/web-ui/modules/app.methods.providers.mjs +363 -265
  52. package/web-ui/modules/app.methods.runtime.mjs +323 -323
  53. package/web-ui/modules/app.methods.session-actions.mjs +520 -457
  54. package/web-ui/modules/app.methods.session-browser.mjs +626 -435
  55. package/web-ui/modules/app.methods.session-timeline.mjs +448 -441
  56. package/web-ui/modules/app.methods.session-trash.mjs +422 -419
  57. package/web-ui/modules/app.methods.startup-claude.mjs +412 -406
  58. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  59. package/web-ui/modules/config-mode.computed.mjs +126 -124
  60. package/web-ui/modules/skills.computed.mjs +107 -107
  61. package/web-ui/modules/skills.methods.mjs +481 -481
  62. package/web-ui/partials/index/layout-footer.html +13 -69
  63. package/web-ui/partials/index/layout-header.html +402 -337
  64. package/web-ui/partials/index/modal-config-template-agents.html +125 -125
  65. package/web-ui/partials/index/modal-confirm-toast.html +32 -32
  66. package/web-ui/partials/index/modal-health-check.html +72 -72
  67. package/web-ui/partials/index/modal-openclaw-config.html +280 -275
  68. package/web-ui/partials/index/modal-skills.html +184 -184
  69. package/web-ui/partials/index/modals-basic.html +156 -196
  70. package/web-ui/partials/index/panel-config-claude.html +126 -100
  71. package/web-ui/partials/index/panel-config-codex.html +237 -237
  72. package/web-ui/partials/index/panel-config-openclaw.html +78 -84
  73. package/web-ui/partials/index/panel-docs.html +130 -0
  74. package/web-ui/partials/index/panel-market.html +174 -174
  75. package/web-ui/partials/index/panel-orchestration.html +397 -0
  76. package/web-ui/partials/index/panel-sessions.html +292 -387
  77. package/web-ui/partials/index/panel-settings.html +190 -166
  78. package/web-ui/partials/index/panel-usage.html +213 -0
  79. package/web-ui/session-helpers.mjs +559 -362
  80. package/web-ui/source-bundle.cjs +233 -233
  81. package/web-ui/styles/base-theme.css +271 -373
  82. package/web-ui/styles/controls-forms.css +360 -354
  83. package/web-ui/styles/docs-panel.css +182 -0
  84. package/web-ui/styles/feedback.css +108 -108
  85. package/web-ui/styles/health-check-dialog.css +144 -144
  86. package/web-ui/styles/layout-shell.css +376 -330
  87. package/web-ui/styles/modals-core.css +464 -449
  88. package/web-ui/styles/navigation-panels.css +348 -381
  89. package/web-ui/styles/openclaw-structured.css +266 -266
  90. package/web-ui/styles/responsive.css +450 -416
  91. package/web-ui/styles/sessions-list.css +400 -414
  92. package/web-ui/styles/sessions-preview.css +411 -405
  93. package/web-ui/styles/sessions-toolbar-trash.css +243 -243
  94. package/web-ui/styles/sessions-usage.css +628 -276
  95. package/web-ui/styles/skills-list.css +296 -298
  96. package/web-ui/styles/skills-market.css +335 -335
  97. package/web-ui/styles/task-orchestration.css +776 -0
  98. package/web-ui/styles/titles-cards.css +408 -407
  99. package/web-ui/styles.css +18 -16
  100. package/web-ui.html +17 -17
  101. package/res/screenshot.png +0 -0
  102. package/res/vue.global.js +0 -18552
@@ -1,237 +1,237 @@
1
- <!-- Provider 配置模式(Codex) -->
2
- <div
3
- v-show="mainTab === 'config' && isProviderConfigMode"
4
- class="mode-content mode-cards"
5
- id="panel-config-provider"
6
- role="tabpanel"
7
- :aria-labelledby="'tab-config-' + configMode">
8
- <!-- 添加提供商按钮 -->
9
- <button class="btn-add" @click="showAddModal = true" v-if="!loading && !initError">
10
- <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
11
- <path d="M10 4v12M4 10h12"/>
12
- </svg>
13
- 新增提供商
14
- </button>
15
-
16
- <!-- 模型选择器 -->
17
- <div class="selector-section">
18
- <div class="selector-header">
19
- <span class="selector-title">模型</span>
20
- <div class="selector-actions">
21
- <button class="btn-icon" @click="showModelModal = true" aria-label="Add model" title="添加模型" v-if="modelsSource === 'legacy'">+</button>
22
- <button class="btn-icon" @click="showModelListModal = true" aria-label="Manage models" title="管理模型" v-if="modelsSource === 'legacy'">≡</button>
23
- </div>
24
- </div>
25
- <select
26
- v-if="codexModelsLoading || modelsSource === 'remote'"
27
- class="model-select"
28
- v-model="currentModel"
29
- @change="onModelChange"
30
- :disabled="codexModelsLoading"
31
- >
32
- <option v-if="codexModelsLoading" value="">加载中...</option>
33
- <option v-else v-for="model in models" :key="model" :value="model">{{ model }}</option>
34
- </select>
35
- <input
36
- v-if="!codexModelsLoading && (modelsSource !== 'remote' || !modelsHasCurrent)"
37
- class="model-input"
38
- v-model="currentModel"
39
- @blur="onModelChange"
40
- :placeholder="activeProviderModelPlaceholder"
41
- >
42
- <div class="config-template-hint" v-if="modelsSource === 'unlimited'">
43
- 当前无模型列表,可手填。
44
- </div>
45
- <div class="config-template-hint" v-if="modelsSource === 'error'">
46
- 模型列表获取失败,可手填。
47
- </div>
48
- <div class="config-template-hint" v-if="modelsSource === 'remote' && !modelsHasCurrent">
49
- {{ isCodexConfigMode ? '当前模型不在列表,可手填或改模板。' : '当前模型不在列表,可手填。' }}
50
- </div>
51
- <div class="config-template-hint" v-if="isCodexConfigMode">
52
- 先改模板,再应用。
53
- </div>
54
- <div class="config-template-hint" v-else-if="activeProviderBridgeHint">
55
- {{ activeProviderBridgeHint }} 模板仅限 Codex 编辑。
56
- </div>
57
- <button class="btn-tool btn-template-editor" v-if="isCodexConfigMode" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
58
- 打开模板编辑器
59
- </button>
60
- </div>
61
-
62
- <template v-if="isCodexConfigMode">
63
- <div class="selector-section">
64
- <div class="selector-header">
65
- <span class="selector-title">服务档</span>
66
- </div>
67
- <select class="model-select" v-model="serviceTier" @change="onServiceTierChange">
68
- <option value="fast">fast(默认)</option>
69
- <option value="standard">standard</option>
70
- </select>
71
- <div class="config-template-hint">
72
- 仅 fast 写入 <code>service_tier</code>。
73
- </div>
74
- </div>
75
-
76
- <div class="selector-section">
77
- <div class="selector-header">
78
- <span class="selector-title">推理强度</span>
79
- </div>
80
- <select class="model-select" v-model="modelReasoningEffort" @change="onReasoningEffortChange">
81
- <option value="high">high</option>
82
- <option value="medium">medium(默认)</option>
83
- <option value="low">low</option>
84
- <option value="xhigh">xhigh</option>
85
- </select>
86
- <div class="config-template-hint">
87
- 控制推理深度;high 更深。
88
- </div>
89
- </div>
90
-
91
- <div class="selector-section">
92
- <div class="selector-header">
93
- <span class="selector-title">压缩阈值</span>
94
- <div class="selector-actions">
95
- <button
96
- class="btn-tool btn-tool-compact"
97
- @click="resetCodexContextBudgetDefaults"
98
- :disabled="loading || !!initError || codexApplying">
99
- 重置
100
- </button>
101
- </div>
102
- </div>
103
- <div class="codex-config-grid">
104
- <div class="form-group codex-config-field">
105
- <label class="form-label" for="codex-model-context-window">model_context_window</label>
106
- <input
107
- id="codex-model-context-window"
108
- v-model="modelContextWindowInput"
109
- class="form-input"
110
- inputmode="numeric"
111
- autocomplete="off"
112
- placeholder="例如: 190000"
113
- @focus="editingCodexBudgetField = 'modelContextWindowInput'"
114
- @input="sanitizePositiveIntegerDraft('modelContextWindowInput')"
115
- @blur="onModelContextWindowBlur"
116
- @keydown.enter.prevent="onModelContextWindowBlur">
117
- <div class="form-hint">上下文上限,默认 190000。</div>
118
- </div>
119
- <div class="form-group codex-config-field">
120
- <label class="form-label" for="codex-model-auto-compact-token-limit">model_auto_compact_token_limit</label>
121
- <input
122
- id="codex-model-auto-compact-token-limit"
123
- v-model="modelAutoCompactTokenLimitInput"
124
- class="form-input"
125
- inputmode="numeric"
126
- autocomplete="off"
127
- placeholder="例如: 185000"
128
- @focus="editingCodexBudgetField = 'modelAutoCompactTokenLimitInput'"
129
- @input="sanitizePositiveIntegerDraft('modelAutoCompactTokenLimitInput')"
130
- @blur="onModelAutoCompactTokenLimitBlur"
131
- @keydown.enter.prevent="onModelAutoCompactTokenLimitBlur">
132
- <div class="form-hint">自动压缩阈值,默认 185000。</div>
133
- </div>
134
- </div>
135
- </div>
136
-
137
- <div class="selector-section">
138
- <div class="selector-header">
139
- <span class="selector-title">AGENTS.md</span>
140
- </div>
141
- <button class="btn-tool" @click="openAgentsEditor" :disabled="loading || !!initError || agentsLoading">
142
- {{ agentsLoading ? '加载中...' : '打开 AGENTS.md' }}
143
- </button>
144
- </div>
145
-
146
- </template>
147
-
148
- <div class="selector-section">
149
- <div class="selector-header">
150
- <span class="selector-title">健康检测</span>
151
- </div>
152
- <button class="btn-tool" @click="openHealthCheckDialog()" :disabled="loading || !!initError">
153
- 检测对话
154
- </button>
155
- </div>
156
-
157
- <div v-if="!loading && !initError" class="card-list">
158
- <div v-for="provider in displayProvidersList" :key="provider.name"
159
- :class="['card', { active: displayCurrentProvider === provider.name }]"
160
- @click="switchProvider(provider.name)"
161
- @keydown.enter.self.prevent="switchProvider(provider.name)"
162
- @keydown.space.self.prevent="switchProvider(provider.name)"
163
- tabindex="0"
164
- role="button"
165
- :aria-current="displayCurrentProvider === provider.name ? 'true' : null">
166
- <div class="card-leading">
167
- <div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}</div>
168
- <div class="card-content">
169
- <div class="card-title">
170
- <span>{{ provider.name }}</span>
171
- <span v-if="provider.readOnly" class="provider-readonly-badge">系统</span>
172
- </div>
173
- <div class="card-subtitle">
174
- {{ provider.url || '未设 URL' }}
175
- </div>
176
- </div>
177
- </div>
178
- <div class="card-trailing">
179
- <span :class="['pill', providerPillConfigured(provider) ? 'configured' : 'empty']">
180
- {{ providerPillText(provider) }}
181
- </span>
182
- <span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">
183
- {{ formatLatency(speedResults[provider.name]) }}
184
- </span>
185
- <div class="card-actions" @click.stop>
186
- <button class="card-action-btn" @click="openHealthCheckDialog({ providerName: provider.name, locked: true })" :aria-label="`Open health dialog for ${provider.name}`" title="检测对话">
187
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
188
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
189
- <path d="M8 9h8"/>
190
- <path d="M8 13h5"/>
191
- </svg>
192
- </button>
193
- <button
194
- v-if="!provider.readOnly"
195
- class="card-action-btn"
196
- :class="{ loading: providerShareLoading[provider.name] }"
197
- disabled
198
- @click="copyProviderShareCommand(provider)"
199
- title="分享命令(暂不可用)"
200
- aria-label="Share import command">
201
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
202
- <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
203
- <path d="M16 6l-4-4-4 4"/>
204
- <path d="M12 2v14"/>
205
- </svg>
206
- </button>
207
- <button
208
- v-if="!provider.readOnly"
209
- class="card-action-btn"
210
- :class="{ disabled: !shouldShowProviderEdit(provider) }"
211
- :disabled="!shouldShowProviderEdit(provider)"
212
- @click="openEditModal(provider)"
213
- :aria-label="`Edit provider ${provider.name}`"
214
- :title="shouldShowProviderEdit(provider) ? '编辑' : '不可编辑'">
215
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
216
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
217
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
218
- </svg>
219
- </button>
220
- <button
221
- v-if="!provider.readOnly"
222
- class="card-action-btn delete"
223
- :class="{ disabled: !shouldShowProviderDelete(provider) }"
224
- :disabled="!shouldShowProviderDelete(provider)"
225
- @click="deleteProvider(provider.name)"
226
- :aria-label="`Delete provider ${provider.name}`"
227
- :title="shouldShowProviderDelete(provider) ? '删除' : '不可删除'">
228
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
229
- <path d="M3 6h18"/>
230
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
231
- </svg>
232
- </button>
233
- </div>
234
- </div>
235
- </div>
236
- </div>
237
- </div>
1
+ <!-- Provider 配置模式(Codex) -->
2
+ <div
3
+ v-show="mainTab === 'config' && isProviderConfigMode"
4
+ class="mode-content mode-cards"
5
+ id="panel-config-provider"
6
+ role="tabpanel"
7
+ :aria-labelledby="'tab-config-' + configMode">
8
+ <!-- 添加提供商按钮 -->
9
+ <button class="btn-add" @click="showAddModal = true" v-if="!loading && !initError">
10
+ <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
11
+ <path d="M10 4v12M4 10h12"/>
12
+ </svg>
13
+ 新增提供商
14
+ </button>
15
+
16
+ <!-- 模型选择器 -->
17
+ <div class="selector-section">
18
+ <div class="selector-header">
19
+ <span class="selector-title">模型</span>
20
+ <div class="selector-actions">
21
+ <button class="btn-icon" @click="showModelModal = true" aria-label="Add model" title="添加模型" v-if="modelsSource === 'legacy'">+</button>
22
+ <button class="btn-icon" @click="showModelListModal = true" aria-label="Manage models" title="管理模型" v-if="modelsSource === 'legacy'">≡</button>
23
+ </div>
24
+ </div>
25
+ <select
26
+ v-if="codexModelsLoading || modelsSource === 'remote'"
27
+ class="model-select"
28
+ v-model="currentModel"
29
+ @change="onModelChange"
30
+ :disabled="codexModelsLoading"
31
+ >
32
+ <option v-if="codexModelsLoading" value="">加载中...</option>
33
+ <option v-else v-for="model in models" :key="model" :value="model">{{ model }}</option>
34
+ </select>
35
+ <input
36
+ v-if="!codexModelsLoading && (modelsSource !== 'remote' || !modelsHasCurrent)"
37
+ class="model-input"
38
+ v-model="currentModel"
39
+ @blur="onModelChange"
40
+ :placeholder="activeProviderModelPlaceholder"
41
+ >
42
+ <div class="config-template-hint" v-if="modelsSource === 'unlimited'">
43
+ 当前无模型列表,可手填。
44
+ </div>
45
+ <div class="config-template-hint" v-if="modelsSource === 'error'">
46
+ 模型列表获取失败,可手填。
47
+ </div>
48
+ <div class="config-template-hint" v-if="modelsSource === 'remote' && !modelsHasCurrent">
49
+ {{ isCodexConfigMode ? '当前模型不在列表,可手填或改模板。' : '当前模型不在列表,可手填。' }}
50
+ </div>
51
+ <div class="config-template-hint" v-if="isCodexConfigMode">
52
+ 先改模板,再应用。
53
+ </div>
54
+ <div class="config-template-hint" v-else-if="activeProviderBridgeHint">
55
+ {{ activeProviderBridgeHint }} 模板仅限 Codex 编辑。
56
+ </div>
57
+ <button class="btn-tool btn-template-editor" v-if="isCodexConfigMode" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
58
+ 打开模板编辑器
59
+ </button>
60
+ </div>
61
+
62
+ <template v-if="isCodexConfigMode">
63
+ <div class="selector-section">
64
+ <div class="selector-header">
65
+ <span class="selector-title">服务档</span>
66
+ </div>
67
+ <select class="model-select" v-model="serviceTier" @change="onServiceTierChange">
68
+ <option value="fast">fast(默认)</option>
69
+ <option value="standard">standard</option>
70
+ </select>
71
+ <div class="config-template-hint">
72
+ 仅 fast 写入 <code>service_tier</code>。
73
+ </div>
74
+ </div>
75
+
76
+ <div class="selector-section">
77
+ <div class="selector-header">
78
+ <span class="selector-title">推理强度</span>
79
+ </div>
80
+ <select class="model-select" v-model="modelReasoningEffort" @change="onReasoningEffortChange">
81
+ <option value="high">high</option>
82
+ <option value="medium">medium(默认)</option>
83
+ <option value="low">low</option>
84
+ <option value="xhigh">xhigh</option>
85
+ </select>
86
+ <div class="config-template-hint">
87
+ 控制推理深度;high 更深。
88
+ </div>
89
+ </div>
90
+
91
+ <div class="selector-section">
92
+ <div class="selector-header">
93
+ <span class="selector-title">压缩阈值</span>
94
+ <div class="selector-actions">
95
+ <button
96
+ class="btn-tool btn-tool-compact"
97
+ @click="resetCodexContextBudgetDefaults"
98
+ :disabled="loading || !!initError || codexApplying">
99
+ 重置
100
+ </button>
101
+ </div>
102
+ </div>
103
+ <div class="codex-config-grid">
104
+ <div class="form-group codex-config-field">
105
+ <label class="form-label" for="codex-model-context-window">model_context_window</label>
106
+ <input
107
+ id="codex-model-context-window"
108
+ v-model="modelContextWindowInput"
109
+ class="form-input"
110
+ inputmode="numeric"
111
+ autocomplete="off"
112
+ placeholder="例如: 190000"
113
+ @focus="editingCodexBudgetField = 'modelContextWindowInput'"
114
+ @input="sanitizePositiveIntegerDraft('modelContextWindowInput')"
115
+ @blur="onModelContextWindowBlur"
116
+ @keydown.enter.prevent="onModelContextWindowBlur">
117
+ <div class="form-hint">上下文上限,默认 190000。</div>
118
+ </div>
119
+ <div class="form-group codex-config-field">
120
+ <label class="form-label" for="codex-model-auto-compact-token-limit">model_auto_compact_token_limit</label>
121
+ <input
122
+ id="codex-model-auto-compact-token-limit"
123
+ v-model="modelAutoCompactTokenLimitInput"
124
+ class="form-input"
125
+ inputmode="numeric"
126
+ autocomplete="off"
127
+ placeholder="例如: 185000"
128
+ @focus="editingCodexBudgetField = 'modelAutoCompactTokenLimitInput'"
129
+ @input="sanitizePositiveIntegerDraft('modelAutoCompactTokenLimitInput')"
130
+ @blur="onModelAutoCompactTokenLimitBlur"
131
+ @keydown.enter.prevent="onModelAutoCompactTokenLimitBlur">
132
+ <div class="form-hint">自动压缩阈值,默认 185000。</div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <div class="selector-section">
138
+ <div class="selector-header">
139
+ <span class="selector-title">AGENTS.md</span>
140
+ </div>
141
+ <button class="btn-tool" @click="openAgentsEditor" :disabled="loading || !!initError || agentsLoading">
142
+ {{ agentsLoading ? '加载中...' : '打开 AGENTS.md' }}
143
+ </button>
144
+ </div>
145
+
146
+ </template>
147
+
148
+ <div class="selector-section">
149
+ <div class="selector-header">
150
+ <span class="selector-title">健康检测</span>
151
+ </div>
152
+ <button class="btn-tool" @click="openHealthCheckDialog()" :disabled="loading || !!initError">
153
+ 检测对话
154
+ </button>
155
+ </div>
156
+
157
+ <div v-if="!loading && !initError" class="card-list">
158
+ <div v-for="provider in displayProvidersList" :key="provider.name"
159
+ :class="['card', { active: displayCurrentProvider === provider.name }]"
160
+ @click="switchProvider(provider.name)"
161
+ @keydown.enter.self.prevent="switchProvider(provider.name)"
162
+ @keydown.space.self.prevent="switchProvider(provider.name)"
163
+ tabindex="0"
164
+ role="button"
165
+ :aria-current="displayCurrentProvider === provider.name ? 'true' : null">
166
+ <div class="card-leading">
167
+ <div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}</div>
168
+ <div class="card-content">
169
+ <div class="card-title">
170
+ <span>{{ provider.name }}</span>
171
+ <span v-if="provider.readOnly" class="provider-readonly-badge">系统</span>
172
+ </div>
173
+ <div class="card-subtitle">
174
+ {{ provider.url || '未设 URL' }}
175
+ </div>
176
+ </div>
177
+ </div>
178
+ <div class="card-trailing">
179
+ <span :class="['pill', providerPillConfigured(provider) ? 'configured' : 'empty']">
180
+ {{ providerPillText(provider) }}
181
+ </span>
182
+ <span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">
183
+ {{ formatLatency(speedResults[provider.name]) }}
184
+ </span>
185
+ <div class="card-actions" @click.stop>
186
+ <button class="card-action-btn" @click="openHealthCheckDialog({ providerName: provider.name, locked: true })" :aria-label="`Open health dialog for ${provider.name}`" title="检测对话">
187
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
188
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
189
+ <path d="M8 9h8"/>
190
+ <path d="M8 13h5"/>
191
+ </svg>
192
+ </button>
193
+ <button
194
+ v-if="!provider.readOnly"
195
+ class="card-action-btn"
196
+ :class="{ loading: providerShareLoading[provider.name], disabled: !shouldAllowProviderShare(provider) }"
197
+ :disabled="providerShareLoading[provider.name] || !shouldAllowProviderShare(provider)"
198
+ @click="copyProviderShareCommand(provider)"
199
+ :title="shouldAllowProviderShare(provider) ? '分享命令' : '不可分享'"
200
+ aria-label="Share import command">
201
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
202
+ <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/>
203
+ <path d="M16 6l-4-4-4 4"/>
204
+ <path d="M12 2v14"/>
205
+ </svg>
206
+ </button>
207
+ <button
208
+ v-if="!provider.readOnly"
209
+ class="card-action-btn"
210
+ :class="{ disabled: !shouldShowProviderEdit(provider) }"
211
+ :disabled="!shouldShowProviderEdit(provider)"
212
+ @click="openEditModal(provider)"
213
+ :aria-label="`Edit provider ${provider.name}`"
214
+ :title="shouldShowProviderEdit(provider) ? '编辑' : '不可编辑'">
215
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
216
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
217
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
218
+ </svg>
219
+ </button>
220
+ <button
221
+ v-if="!provider.readOnly"
222
+ class="card-action-btn delete"
223
+ :class="{ disabled: !shouldShowProviderDelete(provider) }"
224
+ :disabled="!shouldShowProviderDelete(provider)"
225
+ @click="deleteProvider(provider.name)"
226
+ :aria-label="`Delete provider ${provider.name}`"
227
+ :title="shouldShowProviderDelete(provider) ? '删除' : '不可删除'">
228
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
229
+ <path d="M3 6h18"/>
230
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
231
+ </svg>
232
+ </button>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>