@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.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/cli/helpers/args.js +48 -0
- package/cli/index.js +374 -0
- package/cli/pid-manager.js +87 -0
- package/client/index.html +25 -0
- package/client/public/favicon.svg +1 -0
- package/client/public/icons.svg +24 -0
- package/client/src/App.vue +136 -0
- package/client/src/assets/vite.svg +1 -0
- package/client/src/assets/vue.svg +1 -0
- package/client/src/components/ChatBox.vue +320 -0
- package/client/src/components/CheckList.vue +52 -0
- package/client/src/components/CodeBlock.vue +99 -0
- package/client/src/components/DetailBlock.vue +47 -0
- package/client/src/components/HelloWorld.vue +95 -0
- package/client/src/components/LangSwitch.vue +32 -0
- package/client/src/components/LinkList.vue +32 -0
- package/client/src/components/OsSwitch.vue +42 -0
- package/client/src/components/OsToggle.vue +15 -0
- package/client/src/components/PageHeader.vue +31 -0
- package/client/src/components/StepList.vue +92 -0
- package/client/src/composables/useClipboard.js +26 -0
- package/client/src/composables/useOs.js +22 -0
- package/client/src/data/docs/agent.js +1002 -0
- package/client/src/data/docs/auth.js +631 -0
- package/client/src/data/docs/builtin-tools.js +322 -0
- package/client/src/data/docs/chat.js +181 -0
- package/client/src/data/docs/index.js +9 -0
- package/client/src/data/docs/skills.js +396 -0
- package/client/src/data/docs/spec-conversion.js +1042 -0
- package/client/src/data/freeModels/bluesliu.js +97 -0
- package/client/src/data/freeModels/index.js +11 -0
- package/client/src/data/freeModels/nvidia.js +126 -0
- package/client/src/data/freeModels/openrouter.js +116 -0
- package/client/src/data/harness/claude.js +89 -0
- package/client/src/data/harness/codex.js +66 -0
- package/client/src/data/harness/dsh.js +86 -0
- package/client/src/data/harness/hermes.js +56 -0
- package/client/src/data/harness/index.js +22 -0
- package/client/src/data/harness/opencode.js +77 -0
- package/client/src/data/proxy/api-relay.js +53 -0
- package/client/src/data/proxy/builtin-proxy.js +54 -0
- package/client/src/data/proxy/cf-workers.js +67 -0
- package/client/src/data/proxy/ecs-forward.js +75 -0
- package/client/src/data/proxy/ecs-reverse.js +89 -0
- package/client/src/data/proxy/index.js +15 -0
- package/client/src/docs/claudecode.md +44 -0
- package/client/src/docs/codex.md +90 -0
- package/client/src/docs/hermesagent.md +42 -0
- package/client/src/docs/opencode.md +103 -0
- package/client/src/locales/en.js +436 -0
- package/client/src/locales/index.js +31 -0
- package/client/src/locales/zh-CN.js +461 -0
- package/client/src/main.js +16 -0
- package/client/src/router/index.js +76 -0
- package/client/src/style.css +15 -0
- package/client/src/views/AdminModelsView.vue +214 -0
- package/client/src/views/DocsView.vue +1604 -0
- package/client/src/views/FreeModelsView.vue +518 -0
- package/client/src/views/HarnessView.vue +314 -0
- package/client/src/views/HomeView.vue +147 -0
- package/client/src/views/ProxyView.vue +112 -0
- package/client/src/views/TokenMarketView.vue +26 -0
- package/client/vite.config.js +41 -0
- package/package.json +85 -0
- package/scripts/build-zip.sh +61 -0
- package/scripts/postinstall.js +7 -0
- package/server/src/config/targets.json +1 -0
- package/server/src/index.js +52 -0
- package/server/src/lib/coding-test.js +215 -0
- package/server/src/lib/database.js +353 -0
- package/server/src/lib/run-test.js +15 -0
- package/server/src/lib/scheduler.js +21 -0
- package/server/src/lib/tester.js +310 -0
- package/server/src/routes/admin.js +48 -0
- package/server/src/routes/proxy.js +64 -0
- package/server/src/routes/speed.js +87 -0
- package/src/config.js +45 -0
- package/src/config.json5 +27 -0
- package/src/index.js +10 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
|
3
|
+
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
|
4
|
+
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
|
5
|
+
</symbol>
|
|
6
|
+
<symbol id="discord-icon" viewBox="0 0 20 19">
|
|
7
|
+
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
|
8
|
+
</symbol>
|
|
9
|
+
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
|
10
|
+
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
|
11
|
+
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
|
12
|
+
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
|
13
|
+
</symbol>
|
|
14
|
+
<symbol id="github-icon" viewBox="0 0 19 19">
|
|
15
|
+
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
|
16
|
+
</symbol>
|
|
17
|
+
<symbol id="social-icon" viewBox="0 0 20 20">
|
|
18
|
+
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
|
19
|
+
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
|
20
|
+
</symbol>
|
|
21
|
+
<symbol id="x-icon" viewBox="0 0 19 19">
|
|
22
|
+
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
|
23
|
+
</symbol>
|
|
24
|
+
</svg>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { RouterView, RouterLink } from 'vue-router'
|
|
4
|
+
import { useRoute } from 'vue-router'
|
|
5
|
+
import { useI18n } from 'vue-i18n'
|
|
6
|
+
import { ElConfigProvider } from 'element-plus'
|
|
7
|
+
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
8
|
+
import en from 'element-plus/es/locale/lang/en'
|
|
9
|
+
import LangSwitch from './components/LangSwitch.vue'
|
|
10
|
+
import OsSwitch from './components/OsSwitch.vue'
|
|
11
|
+
|
|
12
|
+
const route = useRoute()
|
|
13
|
+
const { t, locale } = useI18n()
|
|
14
|
+
|
|
15
|
+
const elLocale = computed(() => (locale.value === 'zh-CN' ? zhCn : en))
|
|
16
|
+
|
|
17
|
+
const navItems = computed(() => [
|
|
18
|
+
{ index: '/', label: t('nav.home') },
|
|
19
|
+
{ index: '/harness', label: t('nav.harness') },
|
|
20
|
+
{ index: '/free-models', label: t('nav.freeModels') },
|
|
21
|
+
{ index: '/proxy', label: t('nav.proxy') },
|
|
22
|
+
{ index: '/token-market', label: t('nav.tokenMarket') },
|
|
23
|
+
{ index: '/docs', label: t('nav.docs') },
|
|
24
|
+
{ index: '/admin/models', label: '管理' },
|
|
25
|
+
])
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<el-config-provider :locale="elLocale">
|
|
30
|
+
<el-container class="app-container">
|
|
31
|
+
<el-header class="app-header">
|
|
32
|
+
<RouterLink to="/" class="logo">
|
|
33
|
+
<span class="logo-icon">AI</span>
|
|
34
|
+
<span class="logo-text">FreeToken</span>
|
|
35
|
+
</RouterLink>
|
|
36
|
+
<el-menu
|
|
37
|
+
mode="horizontal"
|
|
38
|
+
:default-active="route.path"
|
|
39
|
+
:ellipsis="false"
|
|
40
|
+
router
|
|
41
|
+
class="app-menu"
|
|
42
|
+
>
|
|
43
|
+
<el-menu-item
|
|
44
|
+
v-for="item in navItems"
|
|
45
|
+
:key="item.index"
|
|
46
|
+
:index="item.index"
|
|
47
|
+
>
|
|
48
|
+
{{ item.label }}
|
|
49
|
+
</el-menu-item>
|
|
50
|
+
</el-menu>
|
|
51
|
+
<div class="header-actions">
|
|
52
|
+
<OsSwitch class="os-switch" />
|
|
53
|
+
<LangSwitch class="lang-switch" />
|
|
54
|
+
</div>
|
|
55
|
+
</el-header>
|
|
56
|
+
<el-main class="app-main">
|
|
57
|
+
<RouterView />
|
|
58
|
+
</el-main>
|
|
59
|
+
<el-footer class="app-footer">
|
|
60
|
+
<p>{{ $t('footer.tagline') }}</p>
|
|
61
|
+
<p class="footer-note">{{ $t('footer.note') }}</p>
|
|
62
|
+
</el-footer>
|
|
63
|
+
</el-container>
|
|
64
|
+
</el-config-provider>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<style scoped>
|
|
68
|
+
.app-container {
|
|
69
|
+
min-height: 100vh;
|
|
70
|
+
}
|
|
71
|
+
.app-header {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
border-bottom: 1px solid var(--el-border-color);
|
|
75
|
+
padding: 0 24px;
|
|
76
|
+
height: 60px;
|
|
77
|
+
}
|
|
78
|
+
.logo {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
text-decoration: none;
|
|
83
|
+
margin-right: 40px;
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
}
|
|
86
|
+
.logo-icon {
|
|
87
|
+
background: var(--el-color-primary);
|
|
88
|
+
color: #fff;
|
|
89
|
+
width: 32px;
|
|
90
|
+
height: 32px;
|
|
91
|
+
border-radius: 8px;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
font-weight: 700;
|
|
96
|
+
font-size: 14px;
|
|
97
|
+
}
|
|
98
|
+
.logo-text {
|
|
99
|
+
font-size: 18px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: var(--el-text-color-primary);
|
|
102
|
+
}
|
|
103
|
+
.app-menu {
|
|
104
|
+
border-bottom: none !important;
|
|
105
|
+
flex: 1;
|
|
106
|
+
}
|
|
107
|
+
.header-actions {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: 16px;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
}
|
|
113
|
+
.lang-switch {
|
|
114
|
+
flex-shrink: 0;
|
|
115
|
+
}
|
|
116
|
+
.app-main {
|
|
117
|
+
max-width: 1600px;
|
|
118
|
+
width: 100%;
|
|
119
|
+
margin: 0 auto;
|
|
120
|
+
padding: 32px 24px;
|
|
121
|
+
}
|
|
122
|
+
.app-footer {
|
|
123
|
+
text-align: center;
|
|
124
|
+
padding: 24px;
|
|
125
|
+
border-top: 1px solid var(--el-border-color);
|
|
126
|
+
}
|
|
127
|
+
.app-footer p {
|
|
128
|
+
margin: 4px 0;
|
|
129
|
+
font-size: 13px;
|
|
130
|
+
color: var(--el-text-color-secondary);
|
|
131
|
+
}
|
|
132
|
+
.footer-note {
|
|
133
|
+
font-size: 12px !important;
|
|
134
|
+
opacity: 0.7;
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="77" height="47" fill="none" aria-labelledby="vite-logo-title" viewBox="0 0 77 47"><title id="vite-logo-title">Vite</title><style>.parenthesis{fill:#000}@media (prefers-color-scheme:dark){.parenthesis{fill:#fff}}</style><path fill="#9135ff" d="M40.151 45.71c-.663.844-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.493c-.92 0-1.457-1.04-.92-1.788l7.479-10.471c1.07-1.498 0-3.578-1.842-3.578H15.443c-.92 0-1.456-1.04-.92-1.788l9.696-13.576c.213-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.472c-1.07 1.497 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.087.89 1.83L40.153 45.712z"/><mask id="a" width="48" height="47" x="14" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M40.047 45.71c-.663.843-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.389c-.92 0-1.457-1.04-.92-1.788l7.479-10.472c1.07-1.497 0-3.578-1.842-3.578H15.34c-.92 0-1.456-1.04-.92-1.788l9.696-13.575c.213-.297.556-.474.92-.474H53.93c.92 0 1.456 1.04.92 1.788L47.37 13.03c-1.07 1.498 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.088.89 1.831L40.049 45.712z"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#eee6ff" rx="5.508" ry="14.704" transform="rotate(269.814 20.96 11.29)scale(-1 1)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#eee6ff" rx="10.399" ry="29.851" transform="rotate(89.814 -16.902 -8.275)scale(1 -1)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#8900ff" rx="5.508" ry="30.487" transform="rotate(89.814 -19.197 -7.127)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.928 4.177)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.738 5.52)scale(1 -1)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#eee6ff" rx="14.072" ry="22.078" transform="rotate(93.35 31.245 55.578)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx="14.592" cy="9.743" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(39.51 14.592 9.743)"/></g><g filter="url(#k)"><ellipse cx="61.728" cy="-5.321" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 61.728 -5.32)"/></g><g filter="url(#l)"><ellipse cx="55.618" cy="7.104" fill="#00c2ff" rx="5.971" ry="9.665" transform="rotate(37.892 55.618 7.104)"/></g><g filter="url(#m)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#n)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#o)"><ellipse cx="49.857" cy="30.678" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 49.857 30.678)"/></g><g filter="url(#p)"><ellipse cx="52.623" cy="33.171" fill="#00c2ff" rx="5.971" ry="15.297" transform="rotate(37.892 52.623 33.17)"/></g></g><path d="M6.919 0c-9.198 13.166-9.252 33.575 0 46.789h6.215c-9.25-13.214-9.196-33.623 0-46.789zm62.424 0h-6.215c9.198 13.166 9.252 33.575 0 46.789h6.215c9.25-13.214 9.196-33.623 0-46.789" class="parenthesis"/><defs><filter id="b" width="60.045" height="41.654" x="-5.564" y="16.92" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-40.407" y="-6.762" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-35.435" y="2.801" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-30.84" y="20.8" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-29.307" y="21.949" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="29.961" y="-17.13" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-13.43" y="-22.082" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="34.321" y="-37.644" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="38.847" y="-10.552" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="22.45" y="-1.645" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="32.919" y="11.36" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, nextTick, watch } from 'vue'
|
|
3
|
+
import { marked } from 'marked'
|
|
4
|
+
import { Loading } from '@element-plus/icons-vue'
|
|
5
|
+
import axios from 'axios'
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
baseUrl: {
|
|
9
|
+
type: String,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
apiKey: {
|
|
13
|
+
type: String,
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
model: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: 'MiniMax-M2.7'
|
|
19
|
+
},
|
|
20
|
+
provider: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: ''
|
|
23
|
+
},
|
|
24
|
+
placeholder: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: '输入消息开始对话...'
|
|
27
|
+
},
|
|
28
|
+
useProxy: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: true
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const messages = ref([])
|
|
35
|
+
const inputMessage = ref('')
|
|
36
|
+
const isLoading = ref(false)
|
|
37
|
+
const chatContainer = ref(null)
|
|
38
|
+
const cancelTokenSource = ref(null)
|
|
39
|
+
|
|
40
|
+
const apiUrl = computed(() => {
|
|
41
|
+
if (props.useProxy && props.provider) {
|
|
42
|
+
return `/api/proxy/${props.provider}`
|
|
43
|
+
}
|
|
44
|
+
return props.baseUrl
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const sendMessage = async () => {
|
|
48
|
+
if (!inputMessage.value.trim() || isLoading.value) return
|
|
49
|
+
|
|
50
|
+
const userMessage = {
|
|
51
|
+
role: 'user',
|
|
52
|
+
content: inputMessage.value.trim()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
messages.value.push(userMessage)
|
|
56
|
+
const currentInput = inputMessage.value
|
|
57
|
+
inputMessage.value = ''
|
|
58
|
+
isLoading.value = true
|
|
59
|
+
cancelTokenSource.value = axios.CancelToken.source()
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const response = await axios.post(
|
|
63
|
+
`${apiUrl.value}/chat/completions`,
|
|
64
|
+
{
|
|
65
|
+
model: props.model,
|
|
66
|
+
messages: messages.value.map(m => ({
|
|
67
|
+
role: m.role,
|
|
68
|
+
content: m.content
|
|
69
|
+
})),
|
|
70
|
+
max_tokens: 1024
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
headers: {
|
|
74
|
+
'Authorization': `Bearer ${props.apiKey}`,
|
|
75
|
+
'Content-Type': 'application/json'
|
|
76
|
+
},
|
|
77
|
+
timeout: 60000,
|
|
78
|
+
cancelToken: cancelTokenSource.value.token
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const assistantMessage = {
|
|
83
|
+
role: 'assistant',
|
|
84
|
+
content: response.data.choices[0].message.content
|
|
85
|
+
}
|
|
86
|
+
messages.value.push(assistantMessage)
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('API call failed:', error)
|
|
89
|
+
|
|
90
|
+
if (axios.isCancel(error)) {
|
|
91
|
+
messages.value.push({
|
|
92
|
+
role: 'assistant',
|
|
93
|
+
content: '请求已取消'
|
|
94
|
+
})
|
|
95
|
+
} else {
|
|
96
|
+
let errorMessage = '请求失败'
|
|
97
|
+
|
|
98
|
+
if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
|
|
99
|
+
errorMessage = '请求超时(60秒),API 响应时间过长,请稍后重试或检查网络连接'
|
|
100
|
+
} else if (error.response) {
|
|
101
|
+
errorMessage = `错误: ${error.response.status} - ${error.response.data?.error?.message || '未知错误'}`
|
|
102
|
+
} else if (error.request) {
|
|
103
|
+
errorMessage = '网络错误,无法连接到 API 服务器'
|
|
104
|
+
} else {
|
|
105
|
+
errorMessage = `错误: ${error.message}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
messages.value.push({
|
|
109
|
+
role: 'assistant',
|
|
110
|
+
content: errorMessage
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
114
|
+
isLoading.value = false
|
|
115
|
+
await nextTick()
|
|
116
|
+
scrollToBottom()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const scrollToBottom = () => {
|
|
121
|
+
if (chatContainer.value) {
|
|
122
|
+
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const clearMessages = () => {
|
|
127
|
+
messages.value = []
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const cancelRequest = () => {
|
|
131
|
+
if (cancelTokenSource.value) {
|
|
132
|
+
cancelTokenSource.value.cancel('用户取消请求')
|
|
133
|
+
isLoading.value = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
defineExpose({
|
|
138
|
+
clearMessages
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const renderMarkdown = (content) => {
|
|
142
|
+
return marked(content)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
watch(messages, () => {
|
|
146
|
+
nextTick(() => scrollToBottom())
|
|
147
|
+
}, { deep: true })
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<template>
|
|
151
|
+
<div class="chat-box">
|
|
152
|
+
<div class="chat-messages" ref="chatContainer">
|
|
153
|
+
<div v-if="messages.length === 0" class="empty-state">
|
|
154
|
+
<p>{{ placeholder }}</p>
|
|
155
|
+
</div>
|
|
156
|
+
<div
|
|
157
|
+
v-for="(message, index) in messages"
|
|
158
|
+
:key="index"
|
|
159
|
+
:class="['message', `message-${message.role}`]"
|
|
160
|
+
>
|
|
161
|
+
<div class="message-avatar">
|
|
162
|
+
<el-avatar v-if="message.role === 'user'" :size="28">U</el-avatar>
|
|
163
|
+
<el-avatar v-else :size="28" style="background: #409eff">AI</el-avatar>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="message-content">
|
|
166
|
+
<div v-html="renderMarkdown(message.content)" class="message-text"></div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
<div v-if="isLoading" class="message message-assistant">
|
|
170
|
+
<div class="message-avatar">
|
|
171
|
+
<el-avatar :size="28" style="background: #409eff">AI</el-avatar>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="message-content">
|
|
174
|
+
<div class="loading-indicator">
|
|
175
|
+
<el-icon class="is-loading"><Loading /></el-icon>
|
|
176
|
+
<span>正在思考...</span>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div class="chat-input">
|
|
183
|
+
<el-input
|
|
184
|
+
v-model="inputMessage"
|
|
185
|
+
:placeholder="placeholder"
|
|
186
|
+
@keyup.enter="sendMessage"
|
|
187
|
+
:disabled="isLoading"
|
|
188
|
+
>
|
|
189
|
+
<template #append>
|
|
190
|
+
<el-button v-if="!isLoading" @click="sendMessage" :disabled="!inputMessage.trim()">
|
|
191
|
+
发送
|
|
192
|
+
</el-button>
|
|
193
|
+
<el-button v-else @click="cancelRequest" type="danger">
|
|
194
|
+
取消
|
|
195
|
+
</el-button>
|
|
196
|
+
</template>
|
|
197
|
+
</el-input>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</template>
|
|
201
|
+
|
|
202
|
+
<style scoped>
|
|
203
|
+
.chat-box {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
height: calc(100vh - 100px);
|
|
207
|
+
border: 1px solid var(--el-border-color-light);
|
|
208
|
+
border-radius: 8px;
|
|
209
|
+
background: var(--el-bg-color);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.chat-messages {
|
|
213
|
+
flex: 1;
|
|
214
|
+
overflow-y: auto;
|
|
215
|
+
padding: 16px;
|
|
216
|
+
background: var(--el-fill-color-blank);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.empty-state {
|
|
220
|
+
display: flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
height: 100%;
|
|
224
|
+
color: var(--el-text-color-secondary);
|
|
225
|
+
font-size: 14px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.message {
|
|
229
|
+
display: flex;
|
|
230
|
+
gap: 8px;
|
|
231
|
+
margin-bottom: 12px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.message-user {
|
|
235
|
+
flex-direction: row-reverse;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.message-avatar {
|
|
239
|
+
flex-shrink: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.message-content {
|
|
243
|
+
flex: 1;
|
|
244
|
+
min-width: 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.message-text {
|
|
248
|
+
padding: 8px 12px;
|
|
249
|
+
border-radius: 6px;
|
|
250
|
+
word-wrap: break-word;
|
|
251
|
+
overflow-wrap: break-word;
|
|
252
|
+
line-height: 1.5;
|
|
253
|
+
font-size: 13px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.message-user .message-text {
|
|
257
|
+
background: var(--el-color-primary-light-9);
|
|
258
|
+
color: var(--el-color-primary);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.message-assistant .message-text {
|
|
262
|
+
background: var(--el-fill-color-light);
|
|
263
|
+
color: var(--el-text-color-primary);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.message-text :deep(pre) {
|
|
267
|
+
background: var(--el-fill-color);
|
|
268
|
+
padding: 8px;
|
|
269
|
+
border-radius: 4px;
|
|
270
|
+
overflow-x: auto;
|
|
271
|
+
font-size: 12px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.message-text :deep(code) {
|
|
275
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
276
|
+
font-size: 12px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.message-text :deep(p) {
|
|
280
|
+
margin: 4px 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.message-text :deep(ul),
|
|
284
|
+
.message-text :deep(ol) {
|
|
285
|
+
margin: 4px 0;
|
|
286
|
+
padding-left: 20px;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.loading-indicator {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 6px;
|
|
293
|
+
padding: 8px 12px;
|
|
294
|
+
background: var(--el-fill-color-light);
|
|
295
|
+
border-radius: 6px;
|
|
296
|
+
color: var(--el-text-color-secondary);
|
|
297
|
+
font-size: 13px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.chat-input {
|
|
301
|
+
padding: 12px;
|
|
302
|
+
border-top: 1px solid var(--el-border-color-light);
|
|
303
|
+
display: flex;
|
|
304
|
+
gap: 8px;
|
|
305
|
+
align-items: center;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.chat-input .el-input {
|
|
309
|
+
flex: 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.empty-state {
|
|
313
|
+
display: flex;
|
|
314
|
+
align-items: center;
|
|
315
|
+
justify-content: center;
|
|
316
|
+
height: 100%;
|
|
317
|
+
color: var(--el-text-color-secondary);
|
|
318
|
+
font-size: 13px;
|
|
319
|
+
}
|
|
320
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
items: { type: Array, default: () => [] },
|
|
4
|
+
small: { type: Boolean, default: false },
|
|
5
|
+
})
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<ul class="check-list" :class="{ 'check-list--small': small }">
|
|
10
|
+
<li v-for="(item, i) in items" :key="i" class="check-item">
|
|
11
|
+
<el-icon class="check-icon"><Select /></el-icon>
|
|
12
|
+
<span class="check-text">{{ item }}</span>
|
|
13
|
+
</li>
|
|
14
|
+
</ul>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<style scoped>
|
|
18
|
+
.check-list {
|
|
19
|
+
margin: 0;
|
|
20
|
+
padding: 0;
|
|
21
|
+
list-style: none;
|
|
22
|
+
}
|
|
23
|
+
.check-item {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
gap: 8px;
|
|
27
|
+
padding: 4px 0;
|
|
28
|
+
}
|
|
29
|
+
.check-icon {
|
|
30
|
+
color: var(--el-color-success);
|
|
31
|
+
font-size: 16px;
|
|
32
|
+
margin-top: 3px;
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
}
|
|
35
|
+
.check-text {
|
|
36
|
+
line-height: 1.6;
|
|
37
|
+
color: var(--el-text-color-primary);
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.check-list--small .check-item {
|
|
42
|
+
padding: 3px 0;
|
|
43
|
+
}
|
|
44
|
+
.check-list--small .check-icon {
|
|
45
|
+
font-size: 14px;
|
|
46
|
+
margin-top: 2px;
|
|
47
|
+
}
|
|
48
|
+
.check-list--small .check-text {
|
|
49
|
+
font-size: 13px;
|
|
50
|
+
line-height: 1.5;
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { useClipboard } from '../composables/useClipboard'
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
code: { type: String, default: '' },
|
|
7
|
+
lang: { type: String, default: 'bash' },
|
|
8
|
+
filename: { type: String, default: '' },
|
|
9
|
+
copyText: { type: String, default: '' },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const { copy } = useClipboard()
|
|
13
|
+
const copied = ref(false)
|
|
14
|
+
|
|
15
|
+
function stripComments(code) {
|
|
16
|
+
return code
|
|
17
|
+
.split('\n')
|
|
18
|
+
.filter(line => {
|
|
19
|
+
const trimmed = line.trim()
|
|
20
|
+
return trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('//')
|
|
21
|
+
})
|
|
22
|
+
.join('\n')
|
|
23
|
+
.trim()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleCopy() {
|
|
27
|
+
const text = props.copyText || props.code
|
|
28
|
+
copy(stripComments(text))
|
|
29
|
+
copied.value = true
|
|
30
|
+
setTimeout(() => (copied.value = false), 2000)
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="code-block">
|
|
36
|
+
<div class="code-block-header">
|
|
37
|
+
<input
|
|
38
|
+
v-if="filename"
|
|
39
|
+
class="code-filename"
|
|
40
|
+
:value="filename"
|
|
41
|
+
/>
|
|
42
|
+
<span v-else class="code-lang">{{ lang }}</span>
|
|
43
|
+
<el-button text size="small" :icon="copied ? 'Check' : 'DocumentCopy'" @click="handleCopy">
|
|
44
|
+
{{ copied ? $t('common.copied') : $t('common.copy') }}
|
|
45
|
+
</el-button>
|
|
46
|
+
</div>
|
|
47
|
+
<pre><code :class="`language-${lang}`">{{ code }}</code></pre>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<style scoped>
|
|
52
|
+
.code-block {
|
|
53
|
+
border-radius: 8px;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
border: 1px solid var(--el-border-color);
|
|
56
|
+
background: #1e1e1e;
|
|
57
|
+
}
|
|
58
|
+
.code-block-header {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
padding: 4px 12px;
|
|
63
|
+
background: #2d2d2d;
|
|
64
|
+
border-bottom: 1px solid #404040;
|
|
65
|
+
gap: 8px;
|
|
66
|
+
}
|
|
67
|
+
.code-filename {
|
|
68
|
+
flex: 1;
|
|
69
|
+
background: transparent;
|
|
70
|
+
border: none;
|
|
71
|
+
outline: none;
|
|
72
|
+
color: #ccc;
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
75
|
+
padding: 2px 0;
|
|
76
|
+
}
|
|
77
|
+
.code-filename:focus {
|
|
78
|
+
color: #fff;
|
|
79
|
+
border-bottom: 1px solid #555;
|
|
80
|
+
}
|
|
81
|
+
.code-lang {
|
|
82
|
+
color: #999;
|
|
83
|
+
font-size: 12px;
|
|
84
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
85
|
+
}
|
|
86
|
+
.code-block pre {
|
|
87
|
+
margin: 0;
|
|
88
|
+
padding: 16px;
|
|
89
|
+
overflow-x: auto;
|
|
90
|
+
background: #1e1e1e;
|
|
91
|
+
}
|
|
92
|
+
.code-block code {
|
|
93
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
line-height: 1.6;
|
|
96
|
+
color: #d4d4d4;
|
|
97
|
+
white-space: pre;
|
|
98
|
+
}
|
|
99
|
+
</style>
|