@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
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wwkit/freetoken",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"author": "bluesliu <langcai163@163.com>",
|
|
5
|
+
"description": "Free LLM API token manager — speed test, proxy, and web dashboard (Vue 3 + Express)",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "src/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"freetoken": "cli/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"cli",
|
|
13
|
+
"src",
|
|
14
|
+
"server/src",
|
|
15
|
+
"client/src",
|
|
16
|
+
"client/public",
|
|
17
|
+
"client/index.html",
|
|
18
|
+
"client/vite.config.js",
|
|
19
|
+
"scripts",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20.0.0"
|
|
25
|
+
},
|
|
26
|
+
"jest": {
|
|
27
|
+
"transform": {},
|
|
28
|
+
"testMatch": [
|
|
29
|
+
"**/tests/**/*.test.js"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"freetoken",
|
|
34
|
+
"llm",
|
|
35
|
+
"api",
|
|
36
|
+
"proxy",
|
|
37
|
+
"speed-test",
|
|
38
|
+
"vue",
|
|
39
|
+
"express"
|
|
40
|
+
],
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"registry": "https://registry.npmjs.org/",
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"axios": "^1.19.0",
|
|
48
|
+
"dotenv": "^16.4.0",
|
|
49
|
+
"express": "^4.21.0",
|
|
50
|
+
"http-proxy-middleware": "^3.0.0",
|
|
51
|
+
"node-cron": "^3.0.3",
|
|
52
|
+
"sql.js": "1.14.1",
|
|
53
|
+
"@wwkit/shared": "1.0.25"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@element-plus/icons-vue": "^2.3.2",
|
|
57
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
58
|
+
"element-plus": "^2.14.4",
|
|
59
|
+
"highlight.js": "^11.12.0",
|
|
60
|
+
"jest": "^29.7.0",
|
|
61
|
+
"json5": "^2.2.3",
|
|
62
|
+
"marked": "^18.0.9",
|
|
63
|
+
"pinia": "^4.0.3",
|
|
64
|
+
"unplugin-auto-import": "^21.1.0",
|
|
65
|
+
"unplugin-vue-components": "^32.1.0",
|
|
66
|
+
"vite": "^8.2.0",
|
|
67
|
+
"vue": "^3.5.40",
|
|
68
|
+
"vue-i18n": "^9.14.5",
|
|
69
|
+
"vue-router": "^4.6.4"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"postinstall": "node scripts/postinstall.js",
|
|
73
|
+
"start": "node cli/index.js web start",
|
|
74
|
+
"stop": "node cli/index.js web stop",
|
|
75
|
+
"status": "node cli/index.js status",
|
|
76
|
+
"restart": "node cli/index.js web restart",
|
|
77
|
+
"build": "vite build --config client/vite.config.js --root client",
|
|
78
|
+
"speed-test": "node cli/index.js speed-test",
|
|
79
|
+
"build:zip": "bash scripts/build-zip.sh",
|
|
80
|
+
"check": "node --check cli/index.js",
|
|
81
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache tests",
|
|
82
|
+
"test:all": "node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache tests",
|
|
83
|
+
"release": "node ../../packages/shared/bin/release-pkg.js"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# 打包 monorepo 工程源码为 zip
|
|
4
|
+
# 排除: node_modules, client/dist, server/db, .git
|
|
5
|
+
# 默认输出目录: /usr1/mytodos/binary/public/blues
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# 配置
|
|
10
|
+
OUTPUT_DIR="${OUTPUT_DIR:-/usr1/mytodos/binary/public/blues}"
|
|
11
|
+
|
|
12
|
+
# 读取 package.json(ESM 兼容,使用 import 而非 require)
|
|
13
|
+
read_pkg_field() {
|
|
14
|
+
node --input-type=module -e "
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
|
|
17
|
+
const value = $1;
|
|
18
|
+
console.log(value);
|
|
19
|
+
"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
PROJECT_NAME=$(read_pkg_field "pkg.name.split('/').pop()")
|
|
23
|
+
|
|
24
|
+
# 自动增加版本号(patch + 1)
|
|
25
|
+
echo "增加版本号..."
|
|
26
|
+
node --input-type=module -e "
|
|
27
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
28
|
+
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
|
|
29
|
+
const parts = pkg.version.split('.').map(Number);
|
|
30
|
+
parts[2] += 1;
|
|
31
|
+
pkg.version = parts.join('.');
|
|
32
|
+
writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
33
|
+
console.log('版本: ' + pkg.version);
|
|
34
|
+
"
|
|
35
|
+
|
|
36
|
+
VERSION=$(read_pkg_field "pkg.version")
|
|
37
|
+
ZIP_NAME="${PROJECT_NAME}_${VERSION}.zip"
|
|
38
|
+
|
|
39
|
+
echo "=========================================="
|
|
40
|
+
echo "打包源码 ${PROJECT_NAME} v${VERSION}"
|
|
41
|
+
echo "=========================================="
|
|
42
|
+
|
|
43
|
+
# 创建 zip 压缩包(排除指定目录)
|
|
44
|
+
echo "创建 zip 压缩包..."
|
|
45
|
+
zip -r "${ZIP_NAME}" . \
|
|
46
|
+
-x "node_modules/*" \
|
|
47
|
+
-x "client/dist/*" \
|
|
48
|
+
-x "server/db/*" \
|
|
49
|
+
-x ".git/*" \
|
|
50
|
+
-x "*.zip"
|
|
51
|
+
|
|
52
|
+
# 移动到目标目录
|
|
53
|
+
echo "移动到目标目录: ${OUTPUT_DIR}"
|
|
54
|
+
mkdir -p "${OUTPUT_DIR}"
|
|
55
|
+
mv "${ZIP_NAME}" "${OUTPUT_DIR}/"
|
|
56
|
+
|
|
57
|
+
echo "=========================================="
|
|
58
|
+
echo "✅ 打包完成!"
|
|
59
|
+
echo "输出文件: ${OUTPUT_DIR}/${ZIP_NAME}"
|
|
60
|
+
echo "文件大小: $(du -h "${OUTPUT_DIR}/${ZIP_NAME}" | cut -f1)"
|
|
61
|
+
echo "=========================================="
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"provider": "bluesliu", "baseUrl": "http://7.237.94.74:32527/v1", "models": [{"name": "MiniMax-M2.7", "apiKeyEnv": "BLUESLIU_API_KEY"}, {"name": "maas-glm-5-aliyun-codeagent", "apiKeyEnv": "BLUESLIU_API_KEY"}, {"name": "maas-glm-5.1-zhipu", "apiKeyEnv": "BLUESLIU_API_KEY"}]}, {"provider": "openrouter", "baseUrl": "https://openrouter.ai/api/v1", "models": [{"name": "stealth/ox-alpha", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "z-ai/glm-5.2:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "minimax/minimax-m2.7:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "minimax/minimax-m3:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "nvidia/nemotron-3-super-120b-a12b:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "nvidia/nemotron-3-ultra-550b-a55b:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "poolside/laguna-s-2.1:free", "apiKeyEnv": "OPENROUTER_API_KEY"}, {"name": "cohere/north-mini-code:free", "apiKeyEnv": "OPENROUTER_API_KEY"}]}, {"provider": "nvidia", "baseUrl": "https://integrate.api.nvidia.com/v1", "models": [{"name": "meta/llama-3.3-70b-instruct", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "meta/llama-3.1-405b-instruct", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "mistralai/mistral-nemo-12b", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "qwen/qwen2.5-7b-instruct", "apiKeyEnv": "NVIDIA_API_KEY"}, {"name": "google/gemma-2-9b", "apiKeyEnv": "NVIDIA_API_KEY"}]}]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import dotenv from 'dotenv'
|
|
5
|
+
import proxyRoutes from './routes/proxy.js'
|
|
6
|
+
import speedRoutes from './routes/speed.js'
|
|
7
|
+
import adminRoutes from './routes/admin.js'
|
|
8
|
+
import { initScheduler } from './lib/scheduler.js'
|
|
9
|
+
import { initDatabase, initModelsFromConfig } from './lib/database.js'
|
|
10
|
+
import { getServerPort } from '../../src/config.js'
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
13
|
+
const __dirname = path.dirname(__filename)
|
|
14
|
+
|
|
15
|
+
dotenv.config({ path: path.join(__dirname, '../.env') })
|
|
16
|
+
|
|
17
|
+
const app = express()
|
|
18
|
+
const PORT = getServerPort()
|
|
19
|
+
|
|
20
|
+
app.use(express.json())
|
|
21
|
+
|
|
22
|
+
// API routes
|
|
23
|
+
app.use('/api/proxy', proxyRoutes)
|
|
24
|
+
app.use('/api/speed', speedRoutes)
|
|
25
|
+
app.use('/api/admin', adminRoutes)
|
|
26
|
+
|
|
27
|
+
// Static files - serve client/dist in production
|
|
28
|
+
const clientDistPath = path.join(__dirname, '../../client/dist')
|
|
29
|
+
app.use(express.static(clientDistPath))
|
|
30
|
+
|
|
31
|
+
// SPA fallback - serve index.html for non-API routes
|
|
32
|
+
app.get('*', (req, res) => {
|
|
33
|
+
res.sendFile(path.join(clientDistPath, 'index.html'))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
async function start() {
|
|
37
|
+
await initDatabase()
|
|
38
|
+
console.log('Database initialized')
|
|
39
|
+
|
|
40
|
+
initModelsFromConfig()
|
|
41
|
+
console.log('Models initialized from config')
|
|
42
|
+
|
|
43
|
+
app.listen(PORT, () => {
|
|
44
|
+
console.log(`Server running on port ${PORT}`)
|
|
45
|
+
initScheduler()
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
start().catch(err => {
|
|
50
|
+
console.error('Failed to start server:', err)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
})
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
|
|
3
|
+
const TIMEOUT_MS = 60000
|
|
4
|
+
|
|
5
|
+
// 编程测试题
|
|
6
|
+
const codingTasks = [
|
|
7
|
+
{
|
|
8
|
+
id: 'bubble_sort',
|
|
9
|
+
difficulty: 'basic',
|
|
10
|
+
weight: 20,
|
|
11
|
+
prompt: `请用 JavaScript 实现冒泡排序函数。
|
|
12
|
+
|
|
13
|
+
要求:
|
|
14
|
+
1. 函数名: bubbleSort
|
|
15
|
+
2. 参数: 一个数字数组
|
|
16
|
+
3. 返回值: 排序后的数组(升序)
|
|
17
|
+
4. 只返回函数代码,不要解释
|
|
18
|
+
|
|
19
|
+
示例:
|
|
20
|
+
输入: [5, 3, 1, 4, 2]
|
|
21
|
+
输出: [1, 2, 3, 4, 5]`,
|
|
22
|
+
testFn: (code) => {
|
|
23
|
+
try {
|
|
24
|
+
const fn = new Function('return ' + code)()
|
|
25
|
+
const result = fn([5, 3, 1, 4, 2])
|
|
26
|
+
const expected = [1, 2, 3, 4, 5]
|
|
27
|
+
return JSON.stringify(result) === JSON.stringify(expected)
|
|
28
|
+
} catch {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'reverse_words',
|
|
35
|
+
difficulty: 'medium',
|
|
36
|
+
weight: 30,
|
|
37
|
+
prompt: `请用 JavaScript 实现一个函数,反转字符串中的单词顺序。
|
|
38
|
+
|
|
39
|
+
要求:
|
|
40
|
+
1. 函数名: reverseWords
|
|
41
|
+
2. 参数: 一个字符串
|
|
42
|
+
3. 返回值: 单词顺序反转后的字符串
|
|
43
|
+
4. 只返回函数代码,不要解释
|
|
44
|
+
|
|
45
|
+
示例:
|
|
46
|
+
输入: "hello world"
|
|
47
|
+
输出: "world hello"
|
|
48
|
+
|
|
49
|
+
输入: "the quick brown fox"
|
|
50
|
+
输出: "fox brown quick the"`,
|
|
51
|
+
testFn: (code) => {
|
|
52
|
+
try {
|
|
53
|
+
const fn = new Function('return ' + code)()
|
|
54
|
+
return fn('hello world') === 'world hello' &&
|
|
55
|
+
fn('the quick brown fox') === 'fox brown quick the'
|
|
56
|
+
} catch {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'fetch_with_retry',
|
|
63
|
+
difficulty: 'advanced',
|
|
64
|
+
weight: 50,
|
|
65
|
+
prompt: `请用 JavaScript 实现一个带超时和重试的 fetch 函数。
|
|
66
|
+
|
|
67
|
+
要求:
|
|
68
|
+
1. 函数名: fetchWithRetry
|
|
69
|
+
2. 参数: url (字符串), options (对象, 可选), timeout (毫秒), retries (重试次数)
|
|
70
|
+
3. 返回值: Promise,resolve 为 fetch 响应,reject 为错误
|
|
71
|
+
4. 超时后应该中止请求并重试
|
|
72
|
+
5. 只返回函数代码,不要解释
|
|
73
|
+
|
|
74
|
+
注意: 使用 AbortController 实现超时`,
|
|
75
|
+
testFn: (code) => {
|
|
76
|
+
try {
|
|
77
|
+
// 语法检查
|
|
78
|
+
new Function('return ' + code)()
|
|
79
|
+
// 检查关键代码
|
|
80
|
+
const hasAbortController = code.includes('AbortController')
|
|
81
|
+
const hasTimeout = code.includes('timeout') || code.includes('setTimeout')
|
|
82
|
+
const hasRetry = code.includes('retries') || code.includes('retry')
|
|
83
|
+
const hasFetch = code.includes('fetch')
|
|
84
|
+
|
|
85
|
+
return hasAbortController && hasTimeout && hasRetry && hasFetch
|
|
86
|
+
} catch {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
// 提取代码块
|
|
94
|
+
function extractCode(text) {
|
|
95
|
+
// 尝试提取 ```javascript ... ``` 或 ``` ... ``` 中的代码
|
|
96
|
+
const codeBlockMatch = text.match(/```(?:javascript|js)?\s*([\s\S]*?)```/)
|
|
97
|
+
if (codeBlockMatch) {
|
|
98
|
+
return codeBlockMatch[1].trim()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 尝试提取函数定义
|
|
102
|
+
const funcMatch = text.match(/(function\s+\w+[\s\S]*|const\s+\w+\s*=\s*(?:async\s*)?\(?.*?\)?\s*=>[\s\S]*|async\s+function[\s\S]*)/)
|
|
103
|
+
if (funcMatch) {
|
|
104
|
+
return funcMatch[1].trim()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 直接返回文本(假设整个响应就是代码)
|
|
108
|
+
return text.trim()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 评估代码质量 (0-100)
|
|
112
|
+
function evaluateCodeQuality(code) {
|
|
113
|
+
let score = 50 // 基础分
|
|
114
|
+
|
|
115
|
+
// 有注释 +10
|
|
116
|
+
if (code.includes('//') || code.includes('/*')) {
|
|
117
|
+
score += 10
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 使用 const/let +10
|
|
121
|
+
if (code.includes('const ') || code.includes('let ')) {
|
|
122
|
+
score += 10
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 有错误处理 +15
|
|
126
|
+
if (code.includes('try') && code.includes('catch')) {
|
|
127
|
+
score += 15
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 有参数验证 +10
|
|
131
|
+
if (code.includes('if') && (code.includes('null') || code.includes('undefined'))) {
|
|
132
|
+
score += 10
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 代码简洁 (少于 500 字符) +5
|
|
136
|
+
if (code.length < 500) {
|
|
137
|
+
score += 5
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return Math.min(100, score)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 测试单个编程题
|
|
144
|
+
async function testCodingTask(baseUrl, apiKey, model, task) {
|
|
145
|
+
try {
|
|
146
|
+
const response = await axios({
|
|
147
|
+
method: 'POST',
|
|
148
|
+
url: `${baseUrl}/chat/completions`,
|
|
149
|
+
headers: {
|
|
150
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
151
|
+
'Content-Type': 'application/json'
|
|
152
|
+
},
|
|
153
|
+
data: {
|
|
154
|
+
model,
|
|
155
|
+
messages: [{ role: 'user', content: task.prompt }],
|
|
156
|
+
max_tokens: 1000,
|
|
157
|
+
stream: false
|
|
158
|
+
},
|
|
159
|
+
timeout: TIMEOUT_MS
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const content = response.data.choices?.[0]?.message?.content
|
|
163
|
+
|| response.data.choices?.[0]?.message?.reasoning_content
|
|
164
|
+
|| ''
|
|
165
|
+
const code = extractCode(content)
|
|
166
|
+
|
|
167
|
+
// 运行测试
|
|
168
|
+
const passed = task.testFn(code)
|
|
169
|
+
const qualityScore = evaluateCodeQuality(code)
|
|
170
|
+
|
|
171
|
+
// 综合得分 = 通过得分 * 0.7 + 代码质量 * 0.3
|
|
172
|
+
const score = passed ? (task.weight * 0.7 + qualityScore * task.weight / 100 * 0.3) : 0
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
id: task.id,
|
|
176
|
+
difficulty: task.difficulty,
|
|
177
|
+
passed,
|
|
178
|
+
score: Math.round(score),
|
|
179
|
+
qualityScore,
|
|
180
|
+
code: code.substring(0, 200)
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
id: task.id,
|
|
185
|
+
difficulty: task.difficulty,
|
|
186
|
+
passed: false,
|
|
187
|
+
score: 0,
|
|
188
|
+
error: error.message
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 执行编码能力测试
|
|
194
|
+
export async function testCodingAbility(baseUrl, apiKey, model) {
|
|
195
|
+
let totalScore = 0
|
|
196
|
+
let passedCount = 0
|
|
197
|
+
const results = []
|
|
198
|
+
|
|
199
|
+
for (const task of codingTasks) {
|
|
200
|
+
const result = await testCodingTask(baseUrl, apiKey, model, task)
|
|
201
|
+
results.push(result)
|
|
202
|
+
|
|
203
|
+
if (result.passed) {
|
|
204
|
+
passedCount++
|
|
205
|
+
}
|
|
206
|
+
totalScore += result.score
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
score: totalScore,
|
|
211
|
+
passed: passedCount,
|
|
212
|
+
total: codingTasks.length,
|
|
213
|
+
details: results
|
|
214
|
+
}
|
|
215
|
+
}
|