Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions projects/xiaoyue-web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# 小易伴侣 - 环境变量配置示例
# ==========================================

# 智谱 AI API Key (必需)
# 获取地址: https://open.bigmodel.cn/
ZHIPU_API_KEY=your-zhipu-api-key-here
# MiniMax API Key (required)
# Get one at https://platform.minimax.io/ or https://platform.minimaxi.com/
MINIMAX_API_KEY=your-minimax-api-key-here
# MINIMAX_REGION=global_en

# 服务器端口 (可选,默认 3000)
PORT=3000
Expand Down
153 changes: 153 additions & 0 deletions projects/xiaoyue-web/lib/minimax-chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const axios = require('axios');

const MINIMAX_PROVIDER_NAME = 'MiniMax';

const MINIMAX_TEXT_MODEL_CONFIG = {
reason_codes: {
provider_add: 'provider-add',
model_add: 'model-add',
parameter_refresh: 'parameter-refresh',
input_capability: 'input-capability',
},
model_id: 'MiniMax-M3',
model_ids: ['MiniMax-M3', 'MiniMax-M2.7'],
models: [
{
model_id: 'MiniMax-M3',
context_window: 1000000,
pricing_usd_per_million_tokens: {
input: 0.6,
output: 2.4,
cache_read: 0.12,
cache_write: null,
},
input_modalities: ['text', 'image', 'video'],
thinking: ['adaptive', 'disabled'],
},
{
model_id: 'MiniMax-M2.7',
context_window: 204800,
pricing_usd_per_million_tokens: {
input: 0.3,
output: 1.2,
cache_read: 0.06,
cache_write: 0.375,
},
input_modalities: ['text'],
thinking: ['always_on'],
},
],
anthropic_base_url: 'https://api.minimax.io/anthropic',
openai_base_url: 'https://api.minimax.io/v1',
context_window: 1000000,
pricing_usd_per_million_tokens: {
input: 0.6,
output: 2.4,
cache_read: 0.12,
cache_write: null,
},
thinking: ['adaptive', 'disabled'],
};

const MINIMAX_REGIONAL_ENDPOINTS = [
{
region: 'global_en',
openai_base_url: 'https://api.minimax.io/v1',
anthropic_base_url: 'https://api.minimax.io/anthropic',
docs_root: 'https://platform.minimax.io/docs',
},
{
region: 'cn_zh',
openai_base_url: 'https://api.minimaxi.com/v1',
anthropic_base_url: 'https://api.minimaxi.com/anthropic',
docs_root: 'https://platform.minimaxi.com/docs',
},
];

const MINIMAX_REGION_MAP = Object.fromEntries(
MINIMAX_REGIONAL_ENDPOINTS.map((entry) => [entry.region, entry]),
);

const MINIMAX_MODEL_MAP = Object.fromEntries(
MINIMAX_TEXT_MODEL_CONFIG.models.map((model) => [model.model_id, model]),
);

function normalizeMiniMaxRegion(region) {
return region === 'cn_zh' ? 'cn_zh' : 'global_en';
}

function resolveMiniMaxConfig(overrides = {}) {
const region = normalizeMiniMaxRegion(
overrides.region || process.env.MINIMAX_REGION || MINIMAX_REGIONAL_ENDPOINTS[0].region,
);
const regionConfig = MINIMAX_REGION_MAP[region] || MINIMAX_REGIONAL_ENDPOINTS[0];
const requestedModelId = overrides.modelId || overrides.model || process.env.MINIMAX_MODEL || MINIMAX_TEXT_MODEL_CONFIG.model_id;
const modelId = MINIMAX_MODEL_MAP[requestedModelId] ? requestedModelId : MINIMAX_TEXT_MODEL_CONFIG.model_id;
const modelSpec = MINIMAX_MODEL_MAP[modelId];
const apiKey = overrides.apiKey || process.env.MINIMAX_API_KEY || process.env.ZHIPU_API_KEY || '';
const apiBaseUrl = overrides.apiBaseUrl || process.env.MINIMAX_API_BASE || regionConfig.openai_base_url;
const anthropicBaseUrl = overrides.anthropicBaseUrl || process.env.MINIMAX_ANTHROPIC_BASE_URL || regionConfig.anthropic_base_url;

return {
providerName: MINIMAX_PROVIDER_NAME,
apiKey,
apiBaseUrl,
anthropicBaseUrl,
region,
modelId,
modelSpec,
modelIds: MINIMAX_TEXT_MODEL_CONFIG.model_ids.slice(),
textModelConfig: MINIMAX_TEXT_MODEL_CONFIG,
regionalEndpoints: MINIMAX_REGIONAL_ENDPOINTS,
};
}

async function postMiniMaxChatCompletion(options = {}) {
const config = resolveMiniMaxConfig(options);

if (!config.apiKey) {
const error = new Error('MiniMax API key is not configured');
error.code = 'MINIMAX_API_KEY_MISSING';
throw error;
}

const payload = {
model: config.modelId,
messages: options.messages || [],
};

if (options.temperature !== undefined) {
payload.temperature = options.temperature;
}
if (options.top_p !== undefined) {
payload.top_p = options.top_p;
}
if (options.max_tokens !== undefined) {
payload.max_tokens = options.max_tokens;
}
if (options.stream !== undefined) {
payload.stream = options.stream;
}

const response = await axios.post(
`${config.apiBaseUrl}/chat/completions`,
payload,
{
headers: {
Authorization: `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
},
timeout: options.timeout ?? 30000,
},
);

return { config, response };
}

module.exports = {
MINIMAX_PROVIDER_NAME,
MINIMAX_TEXT_MODEL_CONFIG,
MINIMAX_REGIONAL_ENDPOINTS,
resolveMiniMaxConfig,
postMiniMaxChatCompletion,
};
3 changes: 1 addition & 2 deletions projects/xiaoyue-web/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ <h1>小易伴侣</h1>
statusText.textContent = '在线';
} else {
statusText.textContent = '需要配置';
addSystemMessage('⚠️ 请在服务器配置 ZHIPU_API_KEY 环境变量才能使用');
addSystemMessage(`⚠️ Please configure the ${data.provider || 'MiniMax'} API key on the server before using this service.`);
}
}
} catch (error) {
Expand Down Expand Up @@ -549,4 +549,3 @@ <h1>小易伴侣</h1>
</body>
</html>


47 changes: 21 additions & 26 deletions projects/xiaoyue-web/server-openclaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');
require('dotenv').config();
const {
resolveMiniMaxConfig,
postMiniMaxChatCompletion,
} = require('./lib/minimax-chat');

const app = express();
const PORT = process.env.PORT || 3000;
Expand All @@ -16,9 +20,8 @@ app.use(express.json());
const publicPath = path.join(__dirname, 'public');
app.use(express.static(publicPath));

// 智谱 AI 配置
const ZHIPU_API_KEY = process.env.ZHIPU_API_KEY || process.env.OPENCLAW_TOKEN;
const ZHIPU_API_BASE = 'https://open.bigmodel.cn/api/paas/v4';
// MiniMax configuration
const MINIMAX_DEFAULT_CONFIG = resolveMiniMaxConfig();

// OpenClaw 配置 - 硬编码配置
const OPENCLAW_ENABLED = true; // 强制启用
Expand Down Expand Up @@ -147,15 +150,13 @@ const XIAOYI_PERSONA = `你是小易(知易),一个融合中国传统文
// API 路由
app.post('/api/chat', async (req, res) => {
try {
const { message, sessionId = 'default', apiKey } = req.body;
const { message, sessionId = 'default', apiKey, model, region } = req.body;
const chatConfig = resolveMiniMaxConfig({ apiKey, modelId: model, region });

// 优先使用请求中的 API Key,其次是环境变量
const useApiKey = apiKey || ZHIPU_API_KEY;

if (!useApiKey) {
if (!chatConfig.apiKey) {
return res.json({
success: false,
error: '请配置 ZHIPU_API_KEY 环境变量或在请求中提供 apiKey'
error: 'Please configure the MiniMax API key or provide apiKey in the request'
});
}

Expand Down Expand Up @@ -192,22 +193,13 @@ app.post('/api/chat', async (req, res) => {

messages.push({ role: 'user', content: message });

// 调用智谱 AI
const response = await axios.post(
`${ZHIPU_API_BASE}/chat/completions`,
{
model: 'glm-4.7-flash',
messages: messages,
temperature: 0.9,
max_tokens: 200
},
{
headers: {
'Authorization': `Bearer ${useApiKey}`,
'Content-Type': 'application/json'
}
}
);
// Call MiniMax chat completions
const { response } = await postMiniMaxChatCompletion({
...chatConfig,
messages: messages,
temperature: 0.9,
max_tokens: 200
});

const reply = response.data.choices?.[0]?.message?.content || '抱歉,我没有收到有效的回复';

Expand Down Expand Up @@ -259,7 +251,10 @@ app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
apiKeyConfigured: !!ZHIPU_API_KEY,
provider: MINIMAX_DEFAULT_CONFIG.providerName,
model: MINIMAX_DEFAULT_CONFIG.modelId,
region: MINIMAX_DEFAULT_CONFIG.region,
apiKeyConfigured: !!MINIMAX_DEFAULT_CONFIG.apiKey,
openclawEnabled: OPENCLAW_ENABLED,
openclawApi: OPENCLAW_API
});
Expand Down
Loading