@wwkit/freetoken 1.0.1

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/cli/helpers/args.js +48 -0
  4. package/cli/index.js +374 -0
  5. package/cli/pid-manager.js +87 -0
  6. package/client/index.html +25 -0
  7. package/client/public/favicon.svg +1 -0
  8. package/client/public/icons.svg +24 -0
  9. package/client/src/App.vue +136 -0
  10. package/client/src/assets/vite.svg +1 -0
  11. package/client/src/assets/vue.svg +1 -0
  12. package/client/src/components/ChatBox.vue +320 -0
  13. package/client/src/components/CheckList.vue +52 -0
  14. package/client/src/components/CodeBlock.vue +99 -0
  15. package/client/src/components/DetailBlock.vue +47 -0
  16. package/client/src/components/HelloWorld.vue +95 -0
  17. package/client/src/components/LangSwitch.vue +32 -0
  18. package/client/src/components/LinkList.vue +32 -0
  19. package/client/src/components/OsSwitch.vue +42 -0
  20. package/client/src/components/OsToggle.vue +15 -0
  21. package/client/src/components/PageHeader.vue +31 -0
  22. package/client/src/components/StepList.vue +92 -0
  23. package/client/src/composables/useClipboard.js +26 -0
  24. package/client/src/composables/useOs.js +22 -0
  25. package/client/src/data/docs/agent.js +1002 -0
  26. package/client/src/data/docs/auth.js +631 -0
  27. package/client/src/data/docs/builtin-tools.js +322 -0
  28. package/client/src/data/docs/chat.js +181 -0
  29. package/client/src/data/docs/index.js +9 -0
  30. package/client/src/data/docs/skills.js +396 -0
  31. package/client/src/data/docs/spec-conversion.js +1042 -0
  32. package/client/src/data/freeModels/bluesliu.js +97 -0
  33. package/client/src/data/freeModels/index.js +11 -0
  34. package/client/src/data/freeModels/nvidia.js +126 -0
  35. package/client/src/data/freeModels/openrouter.js +116 -0
  36. package/client/src/data/harness/claude.js +89 -0
  37. package/client/src/data/harness/codex.js +66 -0
  38. package/client/src/data/harness/dsh.js +86 -0
  39. package/client/src/data/harness/hermes.js +56 -0
  40. package/client/src/data/harness/index.js +22 -0
  41. package/client/src/data/harness/opencode.js +77 -0
  42. package/client/src/data/proxy/api-relay.js +53 -0
  43. package/client/src/data/proxy/builtin-proxy.js +54 -0
  44. package/client/src/data/proxy/cf-workers.js +67 -0
  45. package/client/src/data/proxy/ecs-forward.js +75 -0
  46. package/client/src/data/proxy/ecs-reverse.js +89 -0
  47. package/client/src/data/proxy/index.js +15 -0
  48. package/client/src/docs/claudecode.md +44 -0
  49. package/client/src/docs/codex.md +90 -0
  50. package/client/src/docs/hermesagent.md +42 -0
  51. package/client/src/docs/opencode.md +103 -0
  52. package/client/src/locales/en.js +436 -0
  53. package/client/src/locales/index.js +31 -0
  54. package/client/src/locales/zh-CN.js +461 -0
  55. package/client/src/main.js +16 -0
  56. package/client/src/router/index.js +76 -0
  57. package/client/src/style.css +15 -0
  58. package/client/src/views/AdminModelsView.vue +214 -0
  59. package/client/src/views/DocsView.vue +1604 -0
  60. package/client/src/views/FreeModelsView.vue +518 -0
  61. package/client/src/views/HarnessView.vue +314 -0
  62. package/client/src/views/HomeView.vue +147 -0
  63. package/client/src/views/ProxyView.vue +112 -0
  64. package/client/src/views/TokenMarketView.vue +26 -0
  65. package/client/vite.config.js +41 -0
  66. package/package.json +85 -0
  67. package/scripts/build-zip.sh +61 -0
  68. package/scripts/postinstall.js +7 -0
  69. package/server/src/config/targets.json +1 -0
  70. package/server/src/index.js +52 -0
  71. package/server/src/lib/coding-test.js +215 -0
  72. package/server/src/lib/database.js +353 -0
  73. package/server/src/lib/run-test.js +15 -0
  74. package/server/src/lib/scheduler.js +21 -0
  75. package/server/src/lib/tester.js +310 -0
  76. package/server/src/routes/admin.js +48 -0
  77. package/server/src/routes/proxy.js +64 -0
  78. package/server/src/routes/speed.js +87 -0
  79. package/src/config.js +45 -0
  80. package/src/config.json5 +27 -0
  81. package/src/index.js +10 -0
@@ -0,0 +1,518 @@
1
+ <script setup>
2
+ import { ref, computed, onMounted } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+ import { freeModelProviders } from '../data/freeModels/index.js'
5
+ import PageHeader from '../components/PageHeader.vue'
6
+ import CodeBlock from '../components/CodeBlock.vue'
7
+ import DetailBlock from '../components/DetailBlock.vue'
8
+ import StepList from '../components/StepList.vue'
9
+ import CheckList from '../components/CheckList.vue'
10
+ import LinkList from '../components/LinkList.vue'
11
+ import ChatBox from '../components/ChatBox.vue'
12
+ import axios from 'axios'
13
+
14
+ import { useOs } from '../composables/useOs'
15
+
16
+ const { t, tm } = useI18n()
17
+ const { os } = useOs()
18
+
19
+ const activeId = ref(freeModelProviders[0].id)
20
+ const activeProvider = ref(freeModelProviders[0])
21
+ const activeSubTab = ref('apiSpec')
22
+ const selectedModel = ref('')
23
+ const inputApiKey = ref('')
24
+ const drawerVisible = ref(false)
25
+ const chatBoxRef = ref(null)
26
+
27
+ // 数据库模型数据
28
+ const dbModels = ref([])
29
+
30
+ // 测速数据
31
+ const speedResults = ref([])
32
+ const isLoading = ref(false)
33
+ const lastTested = ref(null)
34
+
35
+ const subTabs = computed(() => [
36
+ { id: 'apiSpec', label: t('freeModels.apiSpec') },
37
+ { id: 'models', label: t('freeModels.modelList') },
38
+ { id: 'registration', label: t('freeModels.registration') },
39
+ { id: 'usage', label: t('freeModels.usage') },
40
+ { id: 'tips', label: t('freeModels.tips') },
41
+ ])
42
+
43
+ const apiUrl = computed(() => {
44
+ if (import.meta.env.DEV) {
45
+ return '/api/proxy'
46
+ } else {
47
+ return '/api/blues'
48
+ }
49
+ })
50
+
51
+ // 当前 provider 的模型列表(来自数据库)
52
+ const providerModels = computed(() => {
53
+ return dbModels.value.filter(m => m.provider === activeId.value)
54
+ })
55
+
56
+ // 合并数据库模型和测速数据
57
+ const tableData = computed(() => {
58
+ return providerModels.value.map(m => {
59
+ const tested = speedResults.value.find(r => r.model === m.model)
60
+ const isTested = !!tested?.testedAt
61
+ return {
62
+ name: m.model,
63
+ provider: m.provider,
64
+ context: m.contextWindow || '-',
65
+ apiKeyEnv: tested?.apiKeyEnv || m.apiKeyEnv || '-',
66
+ isTested,
67
+ available: tested?.available ?? false,
68
+ ttftMs: tested?.ttftMs ?? null,
69
+ e2eMs: tested?.e2eMs ?? null,
70
+ throughput: tested?.throughput ?? null,
71
+ codingScore: tested?.codingScore ?? null,
72
+ codingPassed: tested?.codingPassed ?? null,
73
+ codingTotal: tested?.codingTotal ?? null,
74
+ testedAt: tested?.testedAt ?? null,
75
+ errorMsg: tested?.errorMsg ?? null,
76
+ description: m.description || '-',
77
+ }
78
+ })
79
+ })
80
+
81
+ const registrationSteps = computed(() => activeProvider.value.registration || [])
82
+ const tipsItems = computed(() => activeProvider.value.tips || [])
83
+
84
+ function selectProvider(p) {
85
+ activeId.value = p.id
86
+ activeProvider.value = p
87
+ activeSubTab.value = 'apiSpec'
88
+ const models = dbModels.value.filter(m => m.provider === p.id)
89
+ selectedModel.value = models[0]?.model || ''
90
+ fetchApiKey(p.id)
91
+ }
92
+
93
+ // 获取数据库模型列表
94
+ async function fetchDbModels() {
95
+ try {
96
+ const res = await axios.get('/api/admin/')
97
+ dbModels.value = res.data.models || []
98
+ const models = dbModels.value.filter(m => m.provider === activeId.value)
99
+ selectedModel.value = models[0]?.model || ''
100
+ } catch (error) {
101
+ console.error('Failed to fetch models:', error)
102
+ }
103
+ }
104
+
105
+ // 获取 provider 的 API key
106
+ async function fetchApiKey(provider) {
107
+ try {
108
+ const res = await axios.get(`/api/speed/apikey/${provider}`)
109
+ inputApiKey.value = res.data.apiKey || ''
110
+ } catch (error) {
111
+ console.error('Failed to fetch API key:', error)
112
+ inputApiKey.value = ''
113
+ }
114
+ }
115
+
116
+ // 获取测速结果
117
+ async function fetchSpeedResults() {
118
+ isLoading.value = true
119
+ try {
120
+ const res = await axios.get('/api/speed/latest')
121
+ speedResults.value = res.data.results || []
122
+ lastTested.value = res.data.lastTested
123
+ } catch (error) {
124
+ console.error('Failed to fetch speed results:', error)
125
+ } finally {
126
+ isLoading.value = false
127
+ }
128
+ }
129
+
130
+ // 手动触发测速
131
+ async function triggerSpeedTest() {
132
+ try {
133
+ await axios.post('/api/speed/run')
134
+ await fetchSpeedResults()
135
+ } catch (error) {
136
+ console.error('Failed to trigger speed test:', error)
137
+ }
138
+ }
139
+
140
+ // 格式化时间
141
+ function formatTime(isoString) {
142
+ if (!isoString) return '-'
143
+ const date = new Date(isoString)
144
+ return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
145
+ }
146
+
147
+ // 格式化吞吐率
148
+ function formatThroughput(value) {
149
+ if (value === null || value === undefined) return '-'
150
+ return `${value} t/s`
151
+ }
152
+
153
+ // 格式化编码分数
154
+ function formatCodingScore(passed, total, score) {
155
+ if (score === null || score === undefined) return '-'
156
+ return `${passed}/${total} (${score}分)`
157
+ }
158
+
159
+ function openChatDrawer() {
160
+ if (!inputApiKey.value.trim()) {
161
+ return
162
+ }
163
+ drawerVisible.value = true
164
+ }
165
+
166
+ function closeChatDrawer() {
167
+ drawerVisible.value = false
168
+ }
169
+
170
+ function startNewSession() {
171
+ if (chatBoxRef.value) {
172
+ chatBoxRef.value.clearMessages()
173
+ }
174
+ }
175
+
176
+ onMounted(() => {
177
+ fetchDbModels()
178
+ fetchSpeedResults()
179
+ fetchApiKey(freeModelProviders[0].id)
180
+ })
181
+ </script>
182
+
183
+ <template>
184
+ <div class="models-page">
185
+ <PageHeader
186
+ :title="$t('freeModels.pageTitle')"
187
+ :description="$t('freeModels.pageDesc')"
188
+ />
189
+
190
+ <el-tabs v-model="activeId" @tab-change="selectProvider(freeModelProviders.find(p => p.id === activeId))">
191
+ <el-tab-pane
192
+ v-for="p in freeModelProviders"
193
+ :key="p.id"
194
+ :name="p.id"
195
+ >
196
+ <template #label>
197
+ <span>{{ p.name }}</span>
198
+ </template>
199
+ </el-tab-pane>
200
+ </el-tabs>
201
+
202
+ <div v-if="activeProvider" class="provider-detail">
203
+ <div class="provider-header">
204
+ <h2 class="provider-name">{{ activeProvider.name }}</h2>
205
+ <el-tag type="success">{{ activeProvider.badge }}</el-tag>
206
+ </div>
207
+ <p class="provider-tagline">{{ activeProvider.tagline }}</p>
208
+ <p class="provider-description">{{ activeProvider.description }}</p>
209
+
210
+ <el-tabs v-model="activeSubTab" class="sub-tabs">
211
+ <el-tab-pane :label="subTabs[0].label" name="apiSpec">
212
+ <DetailBlock :title="$t('freeModels.apiSpec')">
213
+ <div v-if="providerModels.length > 0" class="api-spec-item">
214
+ <h4>{{ $t('freeModels.openaiStyle') }}</h4>
215
+ <div class="spec-row">
216
+ <span class="spec-label">{{ $t('freeModels.baseUrl') }}:</span>
217
+ <code class="spec-value">{{ providerModels[0]?.baseUrl }}</code>
218
+ </div>
219
+ <div class="spec-row">
220
+ <span class="spec-label">{{ $t('freeModels.apiKey') }}:</span>
221
+ <code class="spec-value">{{ inputApiKey || '(从环境变量读取)' }}</code>
222
+ </div>
223
+ </div>
224
+ <div v-if="activeProvider.apiSpec.anthropic" class="api-spec-item">
225
+ <h4>{{ $t('freeModels.anthropicStyle') }}</h4>
226
+ <div class="spec-row">
227
+ <span class="spec-label">{{ $t('freeModels.baseUrl') }}:</span>
228
+ <code class="spec-value">{{ activeProvider.apiSpec.anthropic.baseUrl }}</code>
229
+ </div>
230
+ <div class="spec-row">
231
+ <span class="spec-label">{{ $t('freeModels.apiKey') }}:</span>
232
+ <code class="spec-value">{{ activeProvider.apiSpec.anthropic.apiKey }}</code>
233
+ </div>
234
+ </div>
235
+ <div v-if="!activeProvider.apiSpec.openai && !activeProvider.apiSpec.anthropic" class="no-spec">
236
+ <p>暂无 API 规格信息</p>
237
+ </div>
238
+ </DetailBlock>
239
+
240
+ <DetailBlock :title="$t('freeModels.apiTest')" style="margin-top: 24px;">
241
+ <div class="try-model-form">
242
+ <div class="try-model-row">
243
+ <el-select v-model="selectedModel" :placeholder="$t('freeModels.selectModel')" style="width: 220px;">
244
+ <el-option
245
+ v-for="model in providerModels"
246
+ :key="model.id"
247
+ :label="model.model"
248
+ :value="model.model"
249
+ />
250
+ </el-select>
251
+
252
+ <el-input
253
+ v-model="inputApiKey"
254
+ :placeholder="$t('freeModels.inputApiKey')"
255
+ type="password"
256
+ show-password
257
+ />
258
+
259
+ <el-button
260
+ type="primary"
261
+ @click="openChatDrawer"
262
+ :disabled="!inputApiKey.trim()"
263
+ >
264
+ {{ $t('freeModels.startChat') }}
265
+ </el-button>
266
+ </div>
267
+
268
+ <p class="api-hint">{{ $t('freeModels.apiHint') }}</p>
269
+ </div>
270
+ </DetailBlock>
271
+ </el-tab-pane>
272
+
273
+ <el-tab-pane :label="subTabs[1].label" name="models">
274
+ <DetailBlock :title="$t('freeModels.modelList')">
275
+ <div class="test-info">
276
+ <span v-if="lastTested" class="test-time">
277
+ 最后测试: {{ formatTime(lastTested) }}
278
+ </span>
279
+ <el-button size="small" @click="fetchSpeedResults" :loading="isLoading">
280
+ 刷新
281
+ </el-button>
282
+ </div>
283
+
284
+ <el-table :data="tableData" border stripe v-loading="isLoading">
285
+ <el-table-column prop="provider" :label="$t('freeModels.tableColumns.provider')" width="120" />
286
+ <el-table-column prop="name" :label="$t('freeModels.tableColumns.name')" min-width="200" />
287
+ <el-table-column prop="context" :label="$t('freeModels.tableColumns.context')" width="120" />
288
+ <el-table-column prop="apiKeyEnv" label="环境变量" width="180">
289
+ <template #default="scope">
290
+ <code v-if="scope.row.apiKeyEnv !== '-'" class="env-var">{{ scope.row.apiKeyEnv }}</code>
291
+ <span v-else>-</span>
292
+ </template>
293
+ </el-table-column>
294
+ <el-table-column :label="$t('freeModels.tableColumns.available')" width="80">
295
+ <template #default="scope">
296
+ <span v-if="!scope.row.isTested">-</span>
297
+ <el-tag v-else :type="scope.row.available ? 'success' : 'danger'" size="small">
298
+ {{ scope.row.available ? '✓' : '✗' }}
299
+ </el-tag>
300
+ </template>
301
+ </el-table-column>
302
+ <el-table-column label="状态信息" min-width="200">
303
+ <template #default="scope">
304
+ <span v-if="scope.row.errorMsg" class="error-msg">{{ scope.row.errorMsg }}</span>
305
+ <span v-else-if="scope.row.isTested" class="success-msg">OK</span>
306
+ <span v-else>-</span>
307
+ </template>
308
+ </el-table-column>
309
+ <el-table-column :label="$t('freeModels.tableColumns.ttft')" width="100">
310
+ <template #default="scope">
311
+ {{ scope.row.ttftMs ? `${scope.row.ttftMs}ms` : '-' }}
312
+ </template>
313
+ </el-table-column>
314
+ <el-table-column :label="$t('freeModels.tableColumns.throughput')" width="100">
315
+ <template #default="scope">
316
+ {{ formatThroughput(scope.row.throughput) }}
317
+ </template>
318
+ </el-table-column>
319
+ <el-table-column :label="$t('freeModels.tableColumns.coding')" width="120">
320
+ <template #default="scope">
321
+ {{ formatCodingScore(scope.row.codingPassed, scope.row.codingTotal, scope.row.codingScore) }}
322
+ </template>
323
+ </el-table-column>
324
+ <el-table-column :label="$t('freeModels.tableColumns.testedAt')" width="80">
325
+ <template #default="scope">
326
+ {{ formatTime(scope.row.testedAt) }}
327
+ </template>
328
+ </el-table-column>
329
+ <el-table-column prop="description" :label="$t('freeModels.tableColumns.note')" min-width="150" />
330
+ </el-table>
331
+ </DetailBlock>
332
+ </el-tab-pane>
333
+
334
+ <el-tab-pane :label="subTabs[2].label" name="registration">
335
+ <DetailBlock :title="$t('freeModels.registration')">
336
+ <StepList :steps="registrationSteps" small linkable />
337
+ </DetailBlock>
338
+ </el-tab-pane>
339
+
340
+ <el-tab-pane :label="subTabs[3].label" name="usage">
341
+ <div class="usage-section">
342
+ <DetailBlock :title="$t('freeModels.usageCurl')">
343
+ <CodeBlock :code="activeProvider.usageCurl" :copy-text="activeProvider.usageCurlCopy[os]" lang="bash" filename="terminal" />
344
+ </DetailBlock>
345
+ <DetailBlock :title="$t('freeModels.usageConfig')">
346
+ <CodeBlock :code="activeProvider.usageConfig" lang="json" :filename="activeProvider.usageFilename" />
347
+ </DetailBlock>
348
+ </div>
349
+ </el-tab-pane>
350
+
351
+ <el-tab-pane :label="subTabs[4].label" name="tips">
352
+ <DetailBlock :title="$t('freeModels.tips')">
353
+ <CheckList :items="tipsItems" />
354
+ </DetailBlock>
355
+ </el-tab-pane>
356
+ </el-tabs>
357
+
358
+ <DetailBlock :title="$t('freeModels.relatedLinks')">
359
+ <LinkList :links="activeProvider.links" />
360
+ </DetailBlock>
361
+ </div>
362
+
363
+ <!-- 抽屉式对话框 -->
364
+ <el-drawer
365
+ v-model="drawerVisible"
366
+ :title="$t('freeModels.chatTitle')"
367
+ direction="rtl"
368
+ size="500px"
369
+ :before-close="closeChatDrawer"
370
+ >
371
+ <template #header>
372
+ <div class="drawer-header">
373
+ <span class="drawer-title">{{ $t('freeModels.chatTitle') }}</span>
374
+ <el-button @click="startNewSession" size="small">
375
+ {{ $t('freeModels.newSession') }}
376
+ </el-button>
377
+ </div>
378
+ </template>
379
+
380
+ <ChatBox
381
+ v-if="drawerVisible && providerModels.length > 0"
382
+ ref="chatBoxRef"
383
+ :base-url="providerModels[0]?.baseUrl"
384
+ :api-key="inputApiKey"
385
+ :model="selectedModel"
386
+ :provider="activeId"
387
+ :placeholder="$t('freeModels.tryModelPlaceholder')"
388
+ :use-proxy="activeId === 'bluesliu'"
389
+ />
390
+ </el-drawer>
391
+ </div>
392
+ </template>
393
+
394
+ <style scoped>
395
+ .provider-detail {
396
+ margin-top: 16px;
397
+ }
398
+ .provider-header {
399
+ display: flex;
400
+ align-items: center;
401
+ gap: 12px;
402
+ margin-bottom: 8px;
403
+ }
404
+ .provider-name {
405
+ margin: 0;
406
+ font-size: 22px;
407
+ font-weight: 600;
408
+ }
409
+ .provider-tagline {
410
+ margin: 0 0 8px;
411
+ font-size: 14px;
412
+ color: var(--el-text-color-secondary);
413
+ }
414
+ .provider-description {
415
+ margin: 0 0 24px;
416
+ font-size: 14px;
417
+ line-height: 1.8;
418
+ color: var(--el-text-color-primary);
419
+ }
420
+ .sub-tabs {
421
+ margin-bottom: 24px;
422
+ }
423
+ .usage-section {
424
+ display: flex;
425
+ flex-direction: column;
426
+ gap: 16px;
427
+ }
428
+ .api-spec-item {
429
+ margin-bottom: 24px;
430
+ }
431
+ .api-spec-item h4 {
432
+ margin: 0 0 12px;
433
+ font-size: 16px;
434
+ font-weight: 600;
435
+ color: var(--el-text-color-primary);
436
+ }
437
+ .api-spec-item:not(:last-child) {
438
+ padding-bottom: 24px;
439
+ border-bottom: 1px solid var(--el-border-color-light);
440
+ }
441
+ .spec-row {
442
+ display: flex;
443
+ align-items: center;
444
+ margin-bottom: 12px;
445
+ }
446
+ .spec-label {
447
+ min-width: 100px;
448
+ font-weight: 500;
449
+ color: var(--el-text-color-regular);
450
+ }
451
+ .spec-value {
452
+ padding: 4px 12px;
453
+ background: var(--el-fill-color-light);
454
+ border-radius: 4px;
455
+ font-family: 'Monaco', 'Menlo', monospace;
456
+ font-size: 13px;
457
+ }
458
+ .no-spec {
459
+ text-align: center;
460
+ padding: 24px;
461
+ color: var(--el-text-color-secondary);
462
+ }
463
+ .try-model-form {
464
+ max-width: 100%;
465
+ }
466
+ .try-model-section {
467
+ max-width: 100%;
468
+ }
469
+ .api-hint {
470
+ margin-top: 12px;
471
+ font-size: 12px;
472
+ color: var(--el-text-color-secondary);
473
+ line-height: 1.5;
474
+ }
475
+ .try-model-row {
476
+ display: flex;
477
+ gap: 12px;
478
+ align-items: center;
479
+ }
480
+ .try-model-row .el-input {
481
+ flex: 1;
482
+ }
483
+ .drawer-header {
484
+ display: flex;
485
+ align-items: center;
486
+ justify-content: space-between;
487
+ width: 100%;
488
+ }
489
+ .drawer-title {
490
+ font-size: 18px;
491
+ font-weight: 600;
492
+ }
493
+ .test-info {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 16px;
497
+ margin-bottom: 16px;
498
+ }
499
+ .test-time {
500
+ font-size: 13px;
501
+ color: var(--el-text-color-secondary);
502
+ }
503
+ .env-var {
504
+ padding: 2px 6px;
505
+ background: var(--el-fill-color-light);
506
+ border-radius: 4px;
507
+ font-family: 'Monaco', 'Menlo', monospace;
508
+ font-size: 11px;
509
+ }
510
+ .error-msg {
511
+ color: var(--el-color-danger);
512
+ font-size: 12px;
513
+ }
514
+ .success-msg {
515
+ color: var(--el-color-success);
516
+ font-size: 12px;
517
+ }
518
+ </style>