Extract memind_adm admin server, add local dev tooling, and remove image-generation.
Split platform admin and ops APIs into standalone admin-server.mjs with network guards; simplify billing to RMB token pricing, refactor user auth, and add rsync deploy plus local-test scripts and docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Point Memind LLM + local goosed (127.0.0.1:18006) at a working relay profile.
|
||||
*
|
||||
* Prefers ~/.config/goose/custom_providers/andu_deepseek.json (same as Goose Desktop),
|
||||
* then H5_LOCAL_RELAY_* env overrides.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/fix-local-goose-relay.mjs
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import {
|
||||
createLlmProviderService,
|
||||
resolveChatCompletionsUrl,
|
||||
testRelayConnection,
|
||||
} from '../llm-providers.mjs';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eq = trimmed.indexOf('=');
|
||||
if (eq < 0) continue;
|
||||
const key = trimmed.slice(0, eq).trim();
|
||||
const value = trimmed.slice(eq + 1).trim();
|
||||
if (!process.env[key]) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function loadGooseAnduDeepseekProfile() {
|
||||
const profilePath = path.join(
|
||||
os.homedir(),
|
||||
'.config/goose/custom_providers/andu_deepseek.json',
|
||||
);
|
||||
if (!fs.existsSync(profilePath)) return null;
|
||||
const raw = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
|
||||
const authHeader = raw?.headers?.Authorization ?? '';
|
||||
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
||||
const baseUrl = String(raw?.base_url ?? '').trim();
|
||||
if (!token || !baseUrl) return null;
|
||||
const models = (raw?.models ?? []).map((item) => item.name).filter(Boolean);
|
||||
return {
|
||||
name: raw.display_name ?? 'Local Goose DeepSeek',
|
||||
apiUrl: resolveChatCompletionsUrl(baseUrl),
|
||||
apiKey: token,
|
||||
models: models.length ? models : ['deepseek-chat'],
|
||||
defaultModel: raw?.models?.[0]?.name ?? 'deepseek-chat',
|
||||
relayProvider: 'deepseek',
|
||||
};
|
||||
}
|
||||
|
||||
loadEnvFile(path.join(root, '.env'));
|
||||
|
||||
const gooseProfile = loadGooseAnduDeepseekProfile();
|
||||
const relayUrl =
|
||||
process.env.H5_LOCAL_RELAY_URL ??
|
||||
gooseProfile?.apiUrl ??
|
||||
'http://127.0.0.1:18300/relay/buyer/v1/chat/completions';
|
||||
const relayToken =
|
||||
process.env.H5_LOCAL_RELAY_TOKEN ?? gooseProfile?.apiKey ?? '';
|
||||
const relayModel = process.env.H5_LOCAL_RELAY_MODEL ?? gooseProfile?.defaultModel ?? 'deepseek-chat';
|
||||
const relayModels = process.env.H5_LOCAL_RELAY_MODELS
|
||||
? process.env.H5_LOCAL_RELAY_MODELS.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
: (gooseProfile?.models ?? ['deepseek-chat', 'deepseek-reasoner']);
|
||||
const configName =
|
||||
process.env.H5_LOCAL_RELAY_NAME ?? gooseProfile?.name ?? 'Local Goose DeepSeek';
|
||||
|
||||
if (!relayToken) {
|
||||
console.error('缺少 relay token:设置 H5_LOCAL_RELAY_TOKEN 或配置 ~/.config/goose/custom_providers/andu_deepseek.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pool = createDbPool();
|
||||
const svc = createLlmProviderService(pool, {
|
||||
apiTarget: process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006',
|
||||
apiSecret: process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret',
|
||||
});
|
||||
|
||||
const [rows] = await pool.query(
|
||||
'SELECT id FROM h5_llm_provider_keys WHERE is_selected = 1 LIMIT 1',
|
||||
);
|
||||
const id = rows[0]?.id;
|
||||
if (!id) {
|
||||
console.error('未找到已启用的 LLM 配置');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const updated = await svc.updateKey(id, {
|
||||
name: configName,
|
||||
apiUrl: relayUrl,
|
||||
apiKey: relayToken,
|
||||
models: relayModels,
|
||||
defaultModel: relayModel,
|
||||
relayProvider: 'deepseek',
|
||||
});
|
||||
|
||||
if (!updated.ok) {
|
||||
console.error('更新失败:', updated.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const synced = await svc.syncSelectedToGoosed();
|
||||
if (!synced.ok) {
|
||||
console.error('同步 goosed 失败:', synced.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const test = await testRelayConnection({
|
||||
apiUrl: relayUrl,
|
||||
apiKey: relayToken,
|
||||
model: relayModel,
|
||||
relayProvider: 'deepseek',
|
||||
});
|
||||
|
||||
console.log('LLM 已对接本地 goosed:', process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006');
|
||||
console.log('Relay:', relayUrl);
|
||||
console.log('goosed provider:', synced.providerId, 'model:', synced.model);
|
||||
console.log('连通测试:', test.ok ? `OK (${test.latencyMs}ms)` : test.message);
|
||||
|
||||
await pool.end();
|
||||
process.exit(test.ok ? 0 : 1);
|
||||
Reference in New Issue
Block a user