mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-06 20:02:49 +08:00
feat: upgrade MiniMax provider to api.minimax.io and add M2.7/M2.5 model presets (#163)
- Fix MiniMax API base URL from api.minimax.chat to api.minimax.io - Add MiniMax model presets (M2.7, M2.7-highspeed, M2.5, M2.5-highspeed) with correct context window sizes (1M for M2.7, 204K for M2.5) - Update MiniMax description to reflect latest M2.7/M2.5 model series - Add MiniMax to README provider tables (Chinese + English) - Add 12 unit tests for model presets validation Co-authored-by: PR Bot <pr-bot@minimaxi.com>
This commit is contained in:
@@ -146,7 +146,7 @@ docker run -d --name clawpanel --restart unless-stopped \
|
||||
## Quick Start
|
||||
|
||||
1. **Initial Setup** — First launch auto-detects Node.js, Git, OpenClaw. One-click install if missing.
|
||||
2. **Configure Models** — Add AI providers (DeepSeek, OpenAI, Ollama, etc.) with API keys. Test connectivity.
|
||||
2. **Configure Models** — Add AI providers (DeepSeek, MiniMax, OpenAI, Ollama, etc.) with API keys. Test connectivity.
|
||||
3. **Start Gateway** — Go to Service Management, click Start. Green status = ready.
|
||||
4. **Start Chatting** — Go to Live Chat, select model, start conversation with streaming & Markdown.
|
||||
|
||||
|
||||
@@ -489,6 +489,7 @@ Web 版功能与桌面版一致,后端通过 `scripts/dev-api.js` 调用本机
|
||||
| 服务商 | 获取 API Key |
|
||||
|--------|-------------|
|
||||
| DeepSeek | [platform.deepseek.com](https://platform.deepseek.com/) |
|
||||
| MiniMax | [platform.minimaxi.com](https://platform.minimaxi.com/) |
|
||||
| OpenAI | [platform.openai.com](https://platform.openai.com/) |
|
||||
| 阿里通义 | [dashscope.console.aliyun.com](https://dashscope.console.aliyun.com/) |
|
||||
| Ollama(本地) | 免费,无需 Key,安装后自动检测 |
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PROVIDER_PRESETS = [
|
||||
{ key: 'volcengine', label: '火山引擎', baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', api: 'openai-completions', site: 'https://volcengine.com/L/Ph1OP5I3_GY', desc: '字节跳动旗下云平台,支持豆包等模型' },
|
||||
{ key: 'aliyun', label: '阿里云百炼', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', api: 'openai-completions', site: 'https://www.aliyun.com/benefit/ai/aistar?userCode=keahn2zr&clubBiz=subTask..12435175..10263..', desc: '阿里云 AI 大模型平台,支持通义千问全系列' },
|
||||
{ key: 'zhipu', label: '智谱 AI', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', api: 'openai-completions', site: 'https://www.bigmodel.cn/glm-coding?ic=3F6F9XYKTS', desc: '国产大模型领军企业,支持 GLM-4 全系列' },
|
||||
{ key: 'minimax', label: 'MiniMax', baseUrl: 'https://api.minimax.chat/v1', api: 'openai-completions', site: 'https://platform.minimaxi.com/subscribe/coding-plan?code=7pUc5oLo4K&source=link', desc: '国产多模态大模型,支持 MiniMax-Text 系列' },
|
||||
{ key: 'minimax', label: 'MiniMax', baseUrl: 'https://api.minimax.io/v1', api: 'openai-completions', site: 'https://platform.minimaxi.com/', desc: '国产多模态大模型,支持 MiniMax-M2.7 / M2.5 系列,兼容 OpenAI 接口' },
|
||||
{ key: 'openai', label: 'OpenAI 官方', baseUrl: 'https://api.openai.com/v1', api: 'openai-completions' },
|
||||
{ key: 'anthropic', label: 'Anthropic 官方', baseUrl: 'https://api.anthropic.com', api: 'anthropic-messages' },
|
||||
{ key: 'deepseek', label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', api: 'openai-completions' },
|
||||
@@ -70,6 +70,12 @@ export const MODEL_PRESETS = {
|
||||
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', contextWindow: 1000000, reasoning: true },
|
||||
{ id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', contextWindow: 1000000 },
|
||||
],
|
||||
minimax: [
|
||||
{ id: 'MiniMax-M2.7', name: 'MiniMax M2.7', contextWindow: 1000000 },
|
||||
{ id: 'MiniMax-M2.7-highspeed', name: 'MiniMax M2.7 Highspeed', contextWindow: 1000000 },
|
||||
{ id: 'MiniMax-M2.5', name: 'MiniMax M2.5', contextWindow: 204000 },
|
||||
{ id: 'MiniMax-M2.5-highspeed', name: 'MiniMax M2.5 Highspeed', contextWindow: 204000 },
|
||||
],
|
||||
ollama: [
|
||||
{ id: 'qwen2.5:7b', name: 'Qwen 2.5 7B', contextWindow: 32768 },
|
||||
{ id: 'llama3.2', name: 'Llama 3.2', contextWindow: 8192 },
|
||||
|
||||
104
tests/model-presets.test.js
Normal file
104
tests/model-presets.test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
API_TYPES,
|
||||
PROVIDER_PRESETS,
|
||||
MODEL_PRESETS,
|
||||
} from '../src/lib/model-presets.js'
|
||||
|
||||
// ===== Provider Presets =====
|
||||
|
||||
test('PROVIDER_PRESETS contains MiniMax entry', () => {
|
||||
const minimax = PROVIDER_PRESETS.find(p => p.key === 'minimax')
|
||||
assert.ok(minimax, 'MiniMax provider preset should exist')
|
||||
assert.equal(minimax.label, 'MiniMax')
|
||||
assert.equal(minimax.api, 'openai-completions')
|
||||
})
|
||||
|
||||
test('MiniMax provider preset uses correct API base URL', () => {
|
||||
const minimax = PROVIDER_PRESETS.find(p => p.key === 'minimax')
|
||||
assert.equal(minimax.baseUrl, 'https://api.minimax.io/v1')
|
||||
})
|
||||
|
||||
test('MiniMax provider preset has site and description', () => {
|
||||
const minimax = PROVIDER_PRESETS.find(p => p.key === 'minimax')
|
||||
assert.ok(minimax.site, 'MiniMax should have a site URL')
|
||||
assert.ok(minimax.desc, 'MiniMax should have a description')
|
||||
})
|
||||
|
||||
test('all provider presets have required fields', () => {
|
||||
for (const p of PROVIDER_PRESETS) {
|
||||
assert.ok(p.key, `preset missing key`)
|
||||
assert.ok(p.label, `preset ${p.key} missing label`)
|
||||
assert.ok(p.baseUrl, `preset ${p.key} missing baseUrl`)
|
||||
assert.ok(p.api, `preset ${p.key} missing api type`)
|
||||
const valid = API_TYPES.map(t => t.value)
|
||||
assert.ok(valid.includes(p.api), `preset ${p.key} has invalid api type: ${p.api}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('no duplicate provider preset keys', () => {
|
||||
const keys = PROVIDER_PRESETS.map(p => p.key)
|
||||
const unique = new Set(keys)
|
||||
assert.equal(keys.length, unique.size, 'provider preset keys must be unique')
|
||||
})
|
||||
|
||||
// ===== Model Presets =====
|
||||
|
||||
test('MODEL_PRESETS contains MiniMax models', () => {
|
||||
assert.ok(MODEL_PRESETS.minimax, 'MODEL_PRESETS should have a minimax key')
|
||||
assert.ok(Array.isArray(MODEL_PRESETS.minimax), 'minimax presets should be an array')
|
||||
assert.ok(MODEL_PRESETS.minimax.length >= 2, 'should have at least 2 MiniMax models')
|
||||
})
|
||||
|
||||
test('MiniMax model presets include M2.7 and M2.5 variants', () => {
|
||||
const ids = MODEL_PRESETS.minimax.map(m => m.id)
|
||||
assert.ok(ids.includes('MiniMax-M2.7'), 'should include MiniMax-M2.7')
|
||||
assert.ok(ids.includes('MiniMax-M2.7-highspeed'), 'should include MiniMax-M2.7-highspeed')
|
||||
assert.ok(ids.includes('MiniMax-M2.5'), 'should include MiniMax-M2.5')
|
||||
assert.ok(ids.includes('MiniMax-M2.5-highspeed'), 'should include MiniMax-M2.5-highspeed')
|
||||
})
|
||||
|
||||
test('MiniMax model presets have required fields', () => {
|
||||
for (const m of MODEL_PRESETS.minimax) {
|
||||
assert.ok(m.id, `model missing id`)
|
||||
assert.ok(m.name, `model ${m.id} missing name`)
|
||||
assert.ok(typeof m.contextWindow === 'number' && m.contextWindow > 0,
|
||||
`model ${m.id} should have a positive contextWindow`)
|
||||
}
|
||||
})
|
||||
|
||||
test('MiniMax M2.7 models have 1M context window', () => {
|
||||
const m27 = MODEL_PRESETS.minimax.find(m => m.id === 'MiniMax-M2.7')
|
||||
assert.equal(m27.contextWindow, 1000000)
|
||||
const m27hs = MODEL_PRESETS.minimax.find(m => m.id === 'MiniMax-M2.7-highspeed')
|
||||
assert.equal(m27hs.contextWindow, 1000000)
|
||||
})
|
||||
|
||||
test('MiniMax M2.5 models have 204K context window', () => {
|
||||
const m25 = MODEL_PRESETS.minimax.find(m => m.id === 'MiniMax-M2.5')
|
||||
assert.equal(m25.contextWindow, 204000)
|
||||
const m25hs = MODEL_PRESETS.minimax.find(m => m.id === 'MiniMax-M2.5-highspeed')
|
||||
assert.equal(m25hs.contextWindow, 204000)
|
||||
})
|
||||
|
||||
test('all model preset groups have valid structure', () => {
|
||||
for (const [group, models] of Object.entries(MODEL_PRESETS)) {
|
||||
assert.ok(Array.isArray(models), `${group} should be an array`)
|
||||
for (const m of models) {
|
||||
assert.ok(m.id, `model in ${group} missing id`)
|
||||
assert.ok(m.name, `model ${m.id} in ${group} missing name`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// ===== Integration: Provider ↔ Model Presets alignment =====
|
||||
|
||||
test('each MODEL_PRESETS group has a matching PROVIDER_PRESETS entry', () => {
|
||||
const providerKeys = new Set(PROVIDER_PRESETS.map(p => p.key))
|
||||
for (const group of Object.keys(MODEL_PRESETS)) {
|
||||
assert.ok(providerKeys.has(group),
|
||||
`MODEL_PRESETS group "${group}" has no matching PROVIDER_PRESETS entry`)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user