@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,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>