@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,48 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import { getAllModels, addModel, updateModel, deleteModel } from '../lib/database.js'
|
|
3
|
+
|
|
4
|
+
const router = express.Router()
|
|
5
|
+
|
|
6
|
+
// 获取所有模型
|
|
7
|
+
router.get('/', (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const models = getAllModels()
|
|
10
|
+
res.json({ models })
|
|
11
|
+
} catch (error) {
|
|
12
|
+
res.status(500).json({ error: error.message })
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// 添加模型
|
|
17
|
+
router.post('/', (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const model = addModel(req.body)
|
|
20
|
+
res.json({ success: true, model })
|
|
21
|
+
} catch (error) {
|
|
22
|
+
res.status(500).json({ error: error.message })
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// 更新模型
|
|
27
|
+
router.put('/:id', (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const id = parseInt(req.params.id)
|
|
30
|
+
const model = updateModel(id, req.body)
|
|
31
|
+
res.json({ success: true, model })
|
|
32
|
+
} catch (error) {
|
|
33
|
+
res.status(500).json({ error: error.message })
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// 删除模型
|
|
38
|
+
router.delete('/:id', (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const id = parseInt(req.params.id)
|
|
41
|
+
deleteModel(id)
|
|
42
|
+
res.json({ success: true, id })
|
|
43
|
+
} catch (error) {
|
|
44
|
+
res.status(500).json({ error: error.message })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
export default router
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import axios from 'axios'
|
|
3
|
+
import { readFileSync } from 'fs'
|
|
4
|
+
import { dirname, join } from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
+
const __dirname = dirname(__filename)
|
|
9
|
+
|
|
10
|
+
const router = express.Router()
|
|
11
|
+
|
|
12
|
+
function loadTargets() {
|
|
13
|
+
const targetsPath = join(__dirname, '../config/targets.json')
|
|
14
|
+
const raw = readFileSync(targetsPath, 'utf-8')
|
|
15
|
+
return JSON.parse(raw)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 动态代理:/api/proxy/:provider/* -> {provider.baseUrl}/*
|
|
19
|
+
router.use('/:provider/*', async (req, res) => {
|
|
20
|
+
const provider = req.params.provider
|
|
21
|
+
const targets = loadTargets()
|
|
22
|
+
const target = targets.find(t => t.provider === provider)
|
|
23
|
+
|
|
24
|
+
if (!target) {
|
|
25
|
+
return res.status(404).json({ error: `Provider ${provider} not found` })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 提取原始路径(去掉 /api/proxy/:provider 前缀)
|
|
29
|
+
const originalUrl = req.originalUrl
|
|
30
|
+
const prefix = `/api/proxy/${provider}`
|
|
31
|
+
const subPath = originalUrl.substring(prefix.length)
|
|
32
|
+
|
|
33
|
+
const targetUrl = `${target.baseUrl}${subPath}`
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const response = await axios({
|
|
37
|
+
method: req.method,
|
|
38
|
+
url: targetUrl,
|
|
39
|
+
headers: {
|
|
40
|
+
'Authorization': req.headers['authorization'] || '',
|
|
41
|
+
'Content-Type': req.headers['content-type'] || 'application/json',
|
|
42
|
+
},
|
|
43
|
+
data: req.method !== 'GET' ? req.body : undefined,
|
|
44
|
+
timeout: 120000,
|
|
45
|
+
responseType: 'stream',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
res.status(response.status)
|
|
49
|
+
response.data.pipe(res)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.response) {
|
|
52
|
+
res.status(error.response.status)
|
|
53
|
+
if (error.response.data) {
|
|
54
|
+
error.response.data.pipe(res)
|
|
55
|
+
} else {
|
|
56
|
+
res.json({ error: error.message })
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
res.status(500).json({ error: error.message })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export default router
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import { runSpeedTest, getLatestResults, getHistory, getTargets } from '../lib/tester.js'
|
|
3
|
+
import { getAllModels } from '../lib/database.js'
|
|
4
|
+
|
|
5
|
+
const router = express.Router()
|
|
6
|
+
|
|
7
|
+
// 获取 provider 的 API key
|
|
8
|
+
router.get('/apikey/:provider', (req, res) => {
|
|
9
|
+
const provider = req.params.provider
|
|
10
|
+
const envVarMap = {
|
|
11
|
+
'bluesliu': 'BLUESLIU_API_KEY',
|
|
12
|
+
'openrouter': 'OPENROUTER_API_KEY',
|
|
13
|
+
'nvidia': 'NVIDIA_API_KEY',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const envVar = envVarMap[provider]
|
|
17
|
+
if (!envVar) {
|
|
18
|
+
return res.json({ apiKey: '' })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const apiKey = process.env[envVar] || ''
|
|
22
|
+
res.json({ apiKey })
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// 获取最新测试结果
|
|
26
|
+
router.get('/latest', async (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const results = await getLatestResults()
|
|
29
|
+
res.json({
|
|
30
|
+
lastTested: results.length > 0 ? results[0].testedAt : null,
|
|
31
|
+
results
|
|
32
|
+
})
|
|
33
|
+
} catch (error) {
|
|
34
|
+
res.status(500).json({ error: error.message })
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// 获取历史记录
|
|
39
|
+
router.get('/history', async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const { model, limit = 20 } = req.query
|
|
42
|
+
if (!model) {
|
|
43
|
+
return res.status(400).json({ error: 'model parameter required' })
|
|
44
|
+
}
|
|
45
|
+
const history = await getHistory(model, parseInt(limit))
|
|
46
|
+
res.json({ model, history })
|
|
47
|
+
} catch (error) {
|
|
48
|
+
res.status(500).json({ error: error.message })
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// 获取测试目标列表
|
|
53
|
+
router.get('/targets', async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const targets = await getTargets()
|
|
56
|
+
res.json({ targets })
|
|
57
|
+
} catch (error) {
|
|
58
|
+
res.status(500).json({ error: error.message })
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// 获取所有模型
|
|
63
|
+
router.get('/models', async (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const models = await getAllModels()
|
|
66
|
+
res.json({ models })
|
|
67
|
+
} catch (error) {
|
|
68
|
+
res.status(500).json({ error: error.message })
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// 手动触发测试
|
|
73
|
+
router.post('/run', async (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
const results = await runSpeedTest()
|
|
76
|
+
res.json({
|
|
77
|
+
success: true,
|
|
78
|
+
testedAt: new Date().toISOString(),
|
|
79
|
+
count: results.length,
|
|
80
|
+
results
|
|
81
|
+
})
|
|
82
|
+
} catch (error) {
|
|
83
|
+
res.status(500).json({ error: error.message })
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export default router
|
package/src/config.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { createConfigLoader, getXdgConfigDir } from '@wwkit/shared'
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
const DEFAULT_CONFIG_PATH = path.join(__dirname, 'config.json5')
|
|
7
|
+
|
|
8
|
+
export function getConfigDir() {
|
|
9
|
+
return getXdgConfigDir('freetoken')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getConfigPath() {
|
|
13
|
+
return path.join(getConfigDir(), 'config.json5')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const _loader = createConfigLoader({
|
|
17
|
+
builtinConfigPath: DEFAULT_CONFIG_PATH,
|
|
18
|
+
userConfigDir: getConfigDir(),
|
|
19
|
+
envContentVar: 'FREETOKEN_CONFIG_CONTENT',
|
|
20
|
+
envDirVar: 'FREETOKEN_CONFIG_DIR',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export function loadConfig() {
|
|
24
|
+
return _loader.getConfig()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function mergeBuiltinConfig() {
|
|
28
|
+
return _loader.mergeBuiltinConfig()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getServerPort() {
|
|
32
|
+
return loadConfig().server?.port ?? 5905
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getClientPort() {
|
|
36
|
+
return loadConfig().client?.port ?? 7905
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getSpeedTestConfig() {
|
|
40
|
+
return loadConfig().speedTest ?? { cron: '0 9 * * *', runOnStart: true }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function shouldOpenBrowser() {
|
|
44
|
+
return loadConfig().web?.openBrowser ?? true
|
|
45
|
+
}
|
package/src/config.json5
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
// freetoken 基础配置
|
|
3
|
+
// 用户配置文件: ~/.config/freetoken/config.json5
|
|
4
|
+
// 环境变量注入: FREETOKEN_CONFIG_CONTENT
|
|
5
|
+
// 配置目录覆盖: FREETOKEN_CONFIG_DIR
|
|
6
|
+
|
|
7
|
+
// 后端 Express 服务
|
|
8
|
+
server: {
|
|
9
|
+
port: 5905,
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
// 前端 Vite 开发服务
|
|
13
|
+
client: {
|
|
14
|
+
port: 7905,
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
// 测速调度
|
|
18
|
+
speedTest: {
|
|
19
|
+
cron: '0 9 * * *',
|
|
20
|
+
runOnStart: true,
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Web 行为
|
|
24
|
+
web: {
|
|
25
|
+
openBrowser: true,
|
|
26
|
+
},
|
|
27
|
+
}
|