130 lines
4.5 KiB
JavaScript
130 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
||
'use strict';
|
||
|
||
/**
|
||
* 用 System 会话配置 LibreDesk 的 AI Provider(Completion + Embedding)。
|
||
* 默认复用旧项目 crm-ai-demo/bridge/kb.config.json 里的 DashScope key(可用 DASHSCOPE_API_KEY 覆盖)。
|
||
*
|
||
* 用法:node scripts/configure-ai.js
|
||
*/
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const OLD_KB_CFG = '/home/dev/code/crm/crm-ai-demo/bridge/kb.config.json';
|
||
|
||
function loadEnv(file) {
|
||
const out = {};
|
||
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
||
const t = line.trim();
|
||
if (!t || t.startsWith('#')) continue;
|
||
const eq = t.indexOf('=');
|
||
if (eq < 0) continue;
|
||
let v = t.slice(eq + 1).trim();
|
||
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
||
out[t.slice(0, eq).trim()] = v;
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function createClient(baseURL) {
|
||
const cookies = new Map();
|
||
let csrf = '';
|
||
const cookieHeader = () => [...cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
|
||
function store(res) {
|
||
const list = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [res.headers.get('set-cookie')].filter(Boolean);
|
||
for (const c of list) {
|
||
const [pair] = c.split(';');
|
||
const eq = pair.indexOf('=');
|
||
if (eq > 0) {
|
||
const k = pair.slice(0, eq).trim();
|
||
const v = pair.slice(eq + 1).trim();
|
||
cookies.set(k, v);
|
||
if (k === 'csrf_token') csrf = v;
|
||
}
|
||
}
|
||
}
|
||
async function req(method, p, body) {
|
||
const headers = { Accept: 'application/json' };
|
||
if (cookies.size) headers.Cookie = cookieHeader();
|
||
if (body !== undefined) headers['Content-Type'] = 'application/json';
|
||
if (csrf && method !== 'GET') headers['X-CSRFTOKEN'] = csrf;
|
||
const res = await fetch(baseURL + p, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
|
||
store(res);
|
||
const text = await res.text();
|
||
let json = null;
|
||
try { json = text ? JSON.parse(text) : null; } catch { /* ignore */ }
|
||
if (!res.ok) throw new Error(`${method} ${p} -> HTTP ${res.status}: ${(json && json.message) || text.slice(0, 300)}`);
|
||
return json;
|
||
}
|
||
return { get: (p) => req('GET', p), post: (p, b) => req('POST', p, b), put: (p, b) => req('PUT', p, b) };
|
||
}
|
||
|
||
const mask = (k) => (k ? k.slice(0, 6) + '…' + k.slice(-4) : '(empty)');
|
||
|
||
async function main() {
|
||
const env = loadEnv(path.join(ROOT, '.env'));
|
||
const base = (env.LIBREDESK_BASE_URL || 'http://localhost:9000').replace(/\/+$/, '');
|
||
const password = env.LIBREDESK_SYSTEM_USER_PASSWORD;
|
||
const email = env.LIBREDESK_SYSTEM_EMAIL || 'System';
|
||
|
||
let apiKey = process.env.DASHSCOPE_API_KEY || '';
|
||
let baseURL = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
|
||
let completionModel = 'qwen-plus-latest';
|
||
let embeddingModel = 'text-embedding-v4';
|
||
let dimensions = 1024;
|
||
|
||
if (!apiKey && fs.existsSync(OLD_KB_CFG)) {
|
||
const kb = JSON.parse(fs.readFileSync(OLD_KB_CFG, 'utf8'));
|
||
apiKey = kb.apiKey || '';
|
||
baseURL = kb.baseURL || baseURL;
|
||
embeddingModel = kb.model || embeddingModel;
|
||
dimensions = kb.dim || dimensions;
|
||
}
|
||
if (!apiKey) throw new Error('没有可用的 DashScope key:设置 DASHSCOPE_API_KEY 或确认旧 kb.config.json 存在');
|
||
|
||
const c = createClient(base);
|
||
await c.post('/api/v1/auth/login', { email, password });
|
||
console.log(`登录成功:${email}`);
|
||
|
||
const completion = {
|
||
provider: 'openai',
|
||
base_url: baseURL,
|
||
api_key: apiKey,
|
||
model: completionModel,
|
||
temperature: 0.3,
|
||
max_tokens: 800,
|
||
vision: false,
|
||
};
|
||
const embedding = {
|
||
provider: 'openai',
|
||
base_url: baseURL,
|
||
api_key: apiKey,
|
||
model: embeddingModel,
|
||
dimensions,
|
||
embedding_max_tokens: 8192,
|
||
};
|
||
|
||
console.log(`Completion: ${completionModel} @ ${baseURL} key=${mask(apiKey)}`);
|
||
console.log(`Embedding : ${embeddingModel} dim=${dimensions} key=${mask(apiKey)}`);
|
||
|
||
console.log('测试 Completion…');
|
||
await c.post('/api/v1/ai/config/completion/test', completion);
|
||
console.log(' 通过');
|
||
|
||
console.log('测试 Embedding…');
|
||
await c.post('/api/v1/ai/config/embedding/test', embedding);
|
||
console.log(' 通过');
|
||
|
||
await c.put('/api/v1/ai/config/completion', completion);
|
||
console.log('已保存 Completion 配置');
|
||
|
||
await c.put('/api/v1/ai/config/embedding', embedding);
|
||
console.log('已保存 Embedding 配置(会自动开始重建知识库向量)');
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('[configure-ai]', err.message);
|
||
process.exit(1);
|
||
});
|