@bolloon/bolloon-agent 0.1.29 → 0.1.32
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/dist/index.js +13 -1
- package/dist/llm/config-store.js +64 -0
- package/dist/llm/pi-ai.js +28 -0
- package/dist/llm/video-config-store.js +171 -0
- package/dist/web/api-config.html +296 -53
- package/dist/web/client.js +43 -15
- package/dist/web/server.js +81 -4
- package/dist/web/style.css +83 -0
- package/package.json +1 -1
- package/src/index.ts +13 -1
- package/src/llm/audio-config-store.ts +241 -0
- package/src/llm/config-store.ts +61 -1
- package/src/llm/pi-ai.ts +25 -1
- package/src/llm/video-config-store.ts +251 -0
- package/src/web/api-config.html +296 -53
- package/src/web/client.js +43 -15
- package/src/web/server.ts +151 -4
- package/src/web/style.css +83 -0
package/dist/web/server.js
CHANGED
|
@@ -10,14 +10,37 @@ import { documentReader } from '../documents/reader.js';
|
|
|
10
10
|
import { initMinimax, getMinimax } from '../constraints/index.js';
|
|
11
11
|
import { createAgentSession } from '../agents/pi-sdk.js';
|
|
12
12
|
import { llmConfigStore } from '../llm/config-store.js';
|
|
13
|
+
import { videoConfigStore } from '../llm/video-config-store.js';
|
|
13
14
|
import { irohTransport } from '../network/iroh-transport.js';
|
|
14
15
|
import { createAgentDelegateApp } from './agent-delegate-server.js';
|
|
15
16
|
import { createIrohDelegateTransport } from './iroh-delegate-transport.js';
|
|
16
17
|
import { verifyMessage, isAddress, getAddress } from 'viem';
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// 前端资源路径: 兼容 src 运行 + dist 运行 + npm 全局安装
|
|
19
|
+
// - src 跑 (tsx): __dirname = .../src/web → .../dist/web
|
|
20
|
+
// - dist 跑 (npm): __dirname = .../dist/web → 自身就是 web 根
|
|
21
|
+
// - 环境变量覆盖: BOLLOON_WEB_ROOT=xxx
|
|
22
|
+
// ESM scope 没有 __dirname, 这里自己声明
|
|
23
|
+
const __filename_local = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname_local = dirname(__filename_local);
|
|
25
|
+
let _baseDirname = __dirname_local;
|
|
26
|
+
function resolveWebRoot() {
|
|
27
|
+
if (process.env.BOLLOON_WEB_ROOT && fsSync.existsSync(process.env.BOLLOON_WEB_ROOT)) {
|
|
28
|
+
return process.env.BOLLOON_WEB_ROOT;
|
|
29
|
+
}
|
|
30
|
+
const d = _baseDirname;
|
|
31
|
+
const candidates = [
|
|
32
|
+
path.join(d), // dist/web
|
|
33
|
+
path.join(d, '..', '..', 'dist', 'web'), // src/web → dist/web
|
|
34
|
+
path.join(d, '..', 'web'), // dist/ → web/ 兄弟
|
|
35
|
+
];
|
|
36
|
+
for (const c of candidates) {
|
|
37
|
+
if (fsSync.existsSync(path.join(c, 'index.html')))
|
|
38
|
+
return c;
|
|
39
|
+
}
|
|
40
|
+
return candidates[1];
|
|
41
|
+
}
|
|
42
|
+
const webRoot = resolveWebRoot();
|
|
43
|
+
console.log(`[web] webRoot = ${webRoot}`);
|
|
21
44
|
const SHARED_SESSION_PATH = path.join(process.env.HOME || '/tmp', '.bolloon', 'sessions');
|
|
22
45
|
const SESSION_CACHE_PATH = path.join(SHARED_SESSION_PATH, 'cache');
|
|
23
46
|
const CHANNELS_PATH = path.join(SHARED_SESSION_PATH, 'channels.json');
|
|
@@ -1988,6 +2011,60 @@ export async function createWebServer(port = 3000, options = {}) {
|
|
|
1988
2011
|
res.status(500).json({ error: err.message });
|
|
1989
2012
|
}
|
|
1990
2013
|
});
|
|
2014
|
+
// ==================== 视频生成配置 (Seedance 等) ====================
|
|
2015
|
+
// 获取视频生成配置
|
|
2016
|
+
app.get('/api/video-config', async (req, res) => {
|
|
2017
|
+
try {
|
|
2018
|
+
const config = await videoConfigStore.getConfig();
|
|
2019
|
+
const providerInfo = videoConfigStore.getAllProviderInfo();
|
|
2020
|
+
// 脱敏:不返回 apiKey 明文
|
|
2021
|
+
const masked = Object.fromEntries(Object.entries(config.providers).map(([key, val]) => [
|
|
2022
|
+
key,
|
|
2023
|
+
{ ...val, apiKey: val.apiKey ? '***' + val.apiKey.slice(-4) : '' }
|
|
2024
|
+
]));
|
|
2025
|
+
res.json({
|
|
2026
|
+
activeProvider: config.activeProvider,
|
|
2027
|
+
providers: masked,
|
|
2028
|
+
providerInfo
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
catch (err) {
|
|
2032
|
+
res.status(500).json({ error: err.message });
|
|
2033
|
+
}
|
|
2034
|
+
});
|
|
2035
|
+
// 更新视频供应商配置
|
|
2036
|
+
app.post('/api/video-config', async (req, res) => {
|
|
2037
|
+
try {
|
|
2038
|
+
const { provider, config } = req.body;
|
|
2039
|
+
if (!provider || !config) {
|
|
2040
|
+
return res.status(400).json({ error: 'provider and config required' });
|
|
2041
|
+
}
|
|
2042
|
+
// 如果前端发的是掩码(***xxx),从当前配置里取真实 key
|
|
2043
|
+
const currentConfig = await videoConfigStore.getProvider(provider);
|
|
2044
|
+
if (currentConfig && config.apiKey && config.apiKey.startsWith('***')) {
|
|
2045
|
+
config.apiKey = currentConfig.apiKey;
|
|
2046
|
+
}
|
|
2047
|
+
await videoConfigStore.updateProvider(provider, config);
|
|
2048
|
+
res.json({ ok: true });
|
|
2049
|
+
}
|
|
2050
|
+
catch (err) {
|
|
2051
|
+
res.status(500).json({ error: err.message });
|
|
2052
|
+
}
|
|
2053
|
+
});
|
|
2054
|
+
// 测试视频供应商连接
|
|
2055
|
+
app.post('/api/video-test', async (req, res) => {
|
|
2056
|
+
try {
|
|
2057
|
+
const { provider } = req.body;
|
|
2058
|
+
if (!provider) {
|
|
2059
|
+
return res.status(400).json({ error: 'provider required' });
|
|
2060
|
+
}
|
|
2061
|
+
const result = await videoConfigStore.testProvider(provider);
|
|
2062
|
+
res.json(result);
|
|
2063
|
+
}
|
|
2064
|
+
catch (err) {
|
|
2065
|
+
res.status(500).json({ error: err.message });
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
1991
2068
|
// 统一 AI 解析入口:CLI / 接收方节点 调这里完成 LLM + judgment + harness
|
|
1992
2069
|
// 入参: { text, mimeType, fileName, fromNodeId, source }
|
|
1993
2070
|
// 出参: { summary, qualityScore, judgmentId?, gateArtifact? }
|
package/dist/web/style.css
CHANGED
|
@@ -2989,6 +2989,89 @@ body {
|
|
|
2989
2989
|
max-width: 900px;
|
|
2990
2990
|
margin: 0 auto;
|
|
2991
2991
|
padding: 24px;
|
|
2992
|
+
min-height: 100vh;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
/* Standalone api-config page: enable page-level scrolling.
|
|
2996
|
+
Default body has overflow:hidden (for app shell with sidebar). */
|
|
2997
|
+
body:has(> .api-config-page) {
|
|
2998
|
+
height: auto;
|
|
2999
|
+
min-height: 100vh;
|
|
3000
|
+
overflow-y: auto;
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
/* Tab switcher */
|
|
3004
|
+
.api-tabs {
|
|
3005
|
+
display: flex;
|
|
3006
|
+
gap: 4px;
|
|
3007
|
+
border-bottom: 1px solid var(--border);
|
|
3008
|
+
margin-bottom: 24px;
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
.api-tab {
|
|
3012
|
+
display: flex;
|
|
3013
|
+
align-items: center;
|
|
3014
|
+
gap: 8px;
|
|
3015
|
+
padding: 12px 20px;
|
|
3016
|
+
background: transparent;
|
|
3017
|
+
border: none;
|
|
3018
|
+
border-bottom: 2px solid transparent;
|
|
3019
|
+
color: var(--text-muted);
|
|
3020
|
+
font-size: 14px;
|
|
3021
|
+
font-weight: 500;
|
|
3022
|
+
cursor: pointer;
|
|
3023
|
+
transition: all 0.2s;
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
.api-tab:hover {
|
|
3027
|
+
color: var(--text);
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
.api-tab.active {
|
|
3031
|
+
color: var(--accent);
|
|
3032
|
+
border-bottom-color: var(--accent);
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
.api-tab-icon {
|
|
3036
|
+
font-size: 16px;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
.api-panel {
|
|
3040
|
+
animation: fadeIn 0.2s ease;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
@keyframes fadeIn {
|
|
3044
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
3045
|
+
to { opacity: 1; transform: translateY(0); }
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
.video-intro {
|
|
3049
|
+
background: var(--bg-sidebar);
|
|
3050
|
+
border: 1px solid var(--border);
|
|
3051
|
+
border-radius: var(--radius);
|
|
3052
|
+
padding: 12px 16px;
|
|
3053
|
+
margin-bottom: 16px;
|
|
3054
|
+
color: var(--text-muted);
|
|
3055
|
+
font-size: 13px;
|
|
3056
|
+
line-height: 1.6;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
.video-intro p {
|
|
3060
|
+
margin: 0;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
.provider-docs {
|
|
3064
|
+
display: inline-block;
|
|
3065
|
+
margin-top: 4px;
|
|
3066
|
+
font-size: 12px;
|
|
3067
|
+
color: var(--accent);
|
|
3068
|
+
text-decoration: none;
|
|
3069
|
+
opacity: 0.8;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
.provider-docs:hover {
|
|
3073
|
+
opacity: 1;
|
|
3074
|
+
text-decoration: underline;
|
|
2992
3075
|
}
|
|
2993
3076
|
|
|
2994
3077
|
.loading-state {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1499,6 +1499,10 @@ function printHelp(): void {
|
|
|
1499
1499
|
|
|
1500
1500
|
环境变量:
|
|
1501
1501
|
MINIMAX_API_KEY MiniMax API 密钥
|
|
1502
|
+
DEEPSEEK_API_KEY DeepSeek API 密钥
|
|
1503
|
+
KIMI_API_KEY / MOONSHOT_API_KEY Kimi/Moonshot API 密钥
|
|
1504
|
+
GLM_API_KEY / ZHIPU_API_KEY 智谱 GLM API 密钥
|
|
1505
|
+
QWEN_API_KEY / DASHSCOPE_API_KEY 通义千问 API 密钥
|
|
1502
1506
|
OPENAI_API_KEY OpenAI API 密钥(Pi SDK)
|
|
1503
1507
|
ANTHROPIC_API_KEY Anthropic API 密钥(Pi SDK)
|
|
1504
1508
|
PORT Web 服务端口(默认 54188)
|
|
@@ -1563,6 +1567,10 @@ async function main() {
|
|
|
1563
1567
|
const hasOpenAI = !!process.env.OPENAI_API_KEY;
|
|
1564
1568
|
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
|
1565
1569
|
const hasMinimax = !!process.env.MINIMAX_API_KEY;
|
|
1570
|
+
const hasDeepSeek = !!process.env.DEEPSEEK_API_KEY;
|
|
1571
|
+
const hasKimi = !!(process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY);
|
|
1572
|
+
const hasGlm = !!(process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY);
|
|
1573
|
+
const hasQwen = !!(process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY);
|
|
1566
1574
|
const hasOpenRouter = !!process.env.OPENROUTER_API_KEY;
|
|
1567
1575
|
const hasGemini = !!process.env.GEMINI_API_KEY;
|
|
1568
1576
|
const hasOllama = !!process.env.OLLAMA_BASE_URL;
|
|
@@ -1572,7 +1580,11 @@ async function main() {
|
|
|
1572
1580
|
hasOpenRouter ? 'OpenRouter' :
|
|
1573
1581
|
hasGemini ? 'Gemini' :
|
|
1574
1582
|
hasOllama ? 'Ollama' :
|
|
1575
|
-
hasMinimax ? 'MiniMax' :
|
|
1583
|
+
hasMinimax ? 'MiniMax' :
|
|
1584
|
+
hasDeepSeek ? 'DeepSeek' :
|
|
1585
|
+
hasKimi ? 'Kimi' :
|
|
1586
|
+
hasGlm ? 'GLM' :
|
|
1587
|
+
hasQwen ? 'Qwen' : null;
|
|
1576
1588
|
|
|
1577
1589
|
if (llmProvider) {
|
|
1578
1590
|
s.step(0, 4, `LLM: ${llmProvider}`, 'ok');
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio Generation API Configuration Store
|
|
3
|
+
*
|
|
4
|
+
* 音频模型配置:MiniMax 提供的 Speech(TTS+ASR)与 Music(文生音乐)。
|
|
5
|
+
* 与 LLM / 视频配置完全独立,持久化到 ~/.bolloon/audio-config.json。
|
|
6
|
+
*
|
|
7
|
+
* 复用 LLM 那一套 MINIMAX_API_KEY 即可(同源)。
|
|
8
|
+
* - TTS: POST /audio/speech (OpenAI 兼容,body 含 model/voice/input)
|
|
9
|
+
* - ASR: POST /audio/transcriptions
|
|
10
|
+
* - Music: POST /music_generation (MiniMax 自有端点)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'fs/promises';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
|
|
16
|
+
export type AudioProvider = 'minimax-speech' | 'minimax-music';
|
|
17
|
+
|
|
18
|
+
export interface AudioProviderConfig {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
apiKey: string;
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
model: string;
|
|
23
|
+
/** TTS 音色:male / female / ... */
|
|
24
|
+
voice?: string;
|
|
25
|
+
/** TTS 语速:0.5-2.0 */
|
|
26
|
+
speed?: number;
|
|
27
|
+
/** TTS 输出格式:mp3 / pcm / wav */
|
|
28
|
+
format?: string;
|
|
29
|
+
/** 音乐生成:instrumental / lyrics */
|
|
30
|
+
mode?: string;
|
|
31
|
+
/** 默认时长(秒) */
|
|
32
|
+
duration?: number;
|
|
33
|
+
requiresApiKey?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AudioConfig {
|
|
37
|
+
activeProvider: AudioProvider;
|
|
38
|
+
providers: Record<AudioProvider, AudioProviderConfig>;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CONFIG_DIR = path.join(process.env.HOME || '/tmp', '.bolloon');
|
|
43
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'audio-config.json');
|
|
44
|
+
|
|
45
|
+
export const DEFAULT_AUDIO_PROVIDER_CONFIGS: Record<AudioProvider, AudioProviderConfig> = {
|
|
46
|
+
'minimax-speech': {
|
|
47
|
+
enabled: false,
|
|
48
|
+
apiKey: '',
|
|
49
|
+
baseUrl: 'https://api.minimaxi.com/v1',
|
|
50
|
+
model: 'speech-01',
|
|
51
|
+
voice: 'male-qn-jingying',
|
|
52
|
+
speed: 1.0,
|
|
53
|
+
format: 'mp3',
|
|
54
|
+
requiresApiKey: true
|
|
55
|
+
},
|
|
56
|
+
'minimax-music': {
|
|
57
|
+
enabled: false,
|
|
58
|
+
apiKey: '',
|
|
59
|
+
baseUrl: 'https://api.minimaxi.com/v1',
|
|
60
|
+
model: 'music-01',
|
|
61
|
+
mode: 'instrumental',
|
|
62
|
+
duration: 30,
|
|
63
|
+
requiresApiKey: true
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const AUDIO_PROVIDER_INFO: Record<AudioProvider, { name: string; description: string; requiresApiKey: boolean; docs?: string; kind: 'speech' | 'music' }> = {
|
|
68
|
+
'minimax-speech': {
|
|
69
|
+
name: 'MiniMax Speech',
|
|
70
|
+
description: 'TTS 文生语音 / ASR 语音转写',
|
|
71
|
+
requiresApiKey: true,
|
|
72
|
+
docs: 'https://platform.minimaxi.com/document/T2A%20V2',
|
|
73
|
+
kind: 'speech'
|
|
74
|
+
},
|
|
75
|
+
'minimax-music': {
|
|
76
|
+
name: 'MiniMax Music',
|
|
77
|
+
description: '文生音乐 (纯音乐 / 带歌词)',
|
|
78
|
+
requiresApiKey: true,
|
|
79
|
+
docs: 'https://platform.minimaxi.com/document/Music%20Generation',
|
|
80
|
+
kind: 'music'
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function getDefaultConfig(): AudioConfig {
|
|
85
|
+
const envConfigs: Partial<Record<AudioProvider, AudioProviderConfig>> = {};
|
|
86
|
+
|
|
87
|
+
const sharedKey = process.env.MINIMAX_API_KEY || '';
|
|
88
|
+
if (sharedKey) {
|
|
89
|
+
envConfigs['minimax-speech'] = {
|
|
90
|
+
...DEFAULT_AUDIO_PROVIDER_CONFIGS['minimax-speech'],
|
|
91
|
+
enabled: true,
|
|
92
|
+
apiKey: sharedKey
|
|
93
|
+
};
|
|
94
|
+
envConfigs['minimax-music'] = {
|
|
95
|
+
...DEFAULT_AUDIO_PROVIDER_CONFIGS['minimax-music'],
|
|
96
|
+
enabled: true,
|
|
97
|
+
apiKey: sharedKey
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const activeProvider: AudioProvider = 'minimax-speech';
|
|
102
|
+
|
|
103
|
+
const providers = { ...DEFAULT_AUDIO_PROVIDER_CONFIGS };
|
|
104
|
+
for (const [provider, config] of Object.entries(envConfigs)) {
|
|
105
|
+
if (config) {
|
|
106
|
+
providers[provider as AudioProvider] = config;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
activeProvider,
|
|
112
|
+
providers,
|
|
113
|
+
updatedAt: new Date().toISOString()
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class AudioConfigStore {
|
|
118
|
+
private config: AudioConfig | null = null;
|
|
119
|
+
private initialized: boolean = false;
|
|
120
|
+
|
|
121
|
+
async initialize(): Promise<void> {
|
|
122
|
+
if (this.initialized) return;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
126
|
+
const data = await fs.readFile(CONFIG_PATH, 'utf-8');
|
|
127
|
+
const loadedConfig = JSON.parse(data);
|
|
128
|
+
|
|
129
|
+
// 补齐缺失的供应商
|
|
130
|
+
const defaultProviders = Object.keys(DEFAULT_AUDIO_PROVIDER_CONFIGS) as AudioProvider[];
|
|
131
|
+
for (const provider of defaultProviders) {
|
|
132
|
+
if (!loadedConfig.providers[provider]) {
|
|
133
|
+
loadedConfig.providers[provider] = { ...DEFAULT_AUDIO_PROVIDER_CONFIGS[provider] };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const activeProvider = loadedConfig.activeProvider as AudioProvider;
|
|
138
|
+
if (!activeProvider || !DEFAULT_AUDIO_PROVIDER_CONFIGS[activeProvider]) {
|
|
139
|
+
loadedConfig.activeProvider = 'minimax-speech';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.config = loadedConfig;
|
|
143
|
+
} catch {
|
|
144
|
+
this.config = getDefaultConfig();
|
|
145
|
+
await this.save();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.initialized = true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async save(): Promise<void> {
|
|
152
|
+
if (!this.config) return;
|
|
153
|
+
this.config.updatedAt = new Date().toISOString();
|
|
154
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(this.config, null, 2));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getConfig(): Promise<AudioConfig> {
|
|
158
|
+
await this.initialize();
|
|
159
|
+
return { ...this.config! };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getProvider(provider: AudioProvider): Promise<AudioProviderConfig | null> {
|
|
163
|
+
await this.initialize();
|
|
164
|
+
return this.config?.providers[provider] || null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getActiveProvider(): Promise<AudioProvider> {
|
|
168
|
+
await this.initialize();
|
|
169
|
+
return this.config?.activeProvider || 'minimax-speech';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getActiveProviderConfig(): Promise<AudioProviderConfig | null> {
|
|
173
|
+
await this.initialize();
|
|
174
|
+
const provider = this.config?.activeProvider;
|
|
175
|
+
if (!provider) return null;
|
|
176
|
+
return this.config?.providers[provider] || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async setActiveProvider(provider: AudioProvider): Promise<void> {
|
|
180
|
+
await this.initialize();
|
|
181
|
+
if (!this.config?.providers[provider]) {
|
|
182
|
+
throw new Error(`Unknown audio provider: ${provider}`);
|
|
183
|
+
}
|
|
184
|
+
this.config.activeProvider = provider;
|
|
185
|
+
await this.save();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async updateProvider(provider: AudioProvider, updates: Partial<AudioProviderConfig>): Promise<void> {
|
|
189
|
+
await this.initialize();
|
|
190
|
+
if (!this.config?.providers[provider]) {
|
|
191
|
+
throw new Error(`Unknown audio provider: ${provider}`);
|
|
192
|
+
}
|
|
193
|
+
this.config.providers[provider] = {
|
|
194
|
+
...this.config.providers[provider],
|
|
195
|
+
...updates
|
|
196
|
+
};
|
|
197
|
+
await this.save();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 测试连接:探测 /models 端点。
|
|
202
|
+
*/
|
|
203
|
+
async testProvider(provider: AudioProvider): Promise<{ success: boolean; error?: string; latency?: number }> {
|
|
204
|
+
await this.initialize();
|
|
205
|
+
|
|
206
|
+
const config = this.config?.providers[provider];
|
|
207
|
+
if (!config) return { success: false, error: 'Provider not configured' };
|
|
208
|
+
if (!config.enabled) return { success: false, error: 'Provider is not enabled' };
|
|
209
|
+
if (config.requiresApiKey && !config.apiKey) {
|
|
210
|
+
return { success: false, error: 'API key is required' };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const startTime = Date.now();
|
|
214
|
+
try {
|
|
215
|
+
const response = await fetch(`${config.baseUrl.replace(/\/$/, '')}/models`, {
|
|
216
|
+
method: 'GET',
|
|
217
|
+
headers: { 'Authorization': `Bearer ${config.apiKey}` }
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const latency = Date.now() - startTime;
|
|
221
|
+
if (response.ok) {
|
|
222
|
+
return { success: true, latency };
|
|
223
|
+
} else {
|
|
224
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
error: `HTTP ${response.status}: ${errorText.substring(0, 200)}`,
|
|
228
|
+
latency
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
} catch (error: any) {
|
|
232
|
+
return { success: false, error: error.message || 'Connection failed', latency: Date.now() - startTime };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getAllProviderInfo() {
|
|
237
|
+
return AUDIO_PROVIDER_INFO;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export const audioConfigStore = new AudioConfigStore();
|
package/src/llm/config-store.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as fs from 'fs/promises';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
|
|
9
|
-
export type ModelProvider = 'openai' | 'anthropic' | 'ollama' | 'openrouter' | 'gemini' | 'minimax' | 'local';
|
|
9
|
+
export type ModelProvider = 'openai' | 'anthropic' | 'ollama' | 'openrouter' | 'gemini' | 'minimax' | 'deepseek' | 'kimi' | 'glm' | 'qwen' | 'local';
|
|
10
10
|
|
|
11
11
|
export interface ProviderConfig {
|
|
12
12
|
enabled: boolean;
|
|
@@ -82,6 +82,42 @@ export const DEFAULT_PROVIDER_CONFIGS: Record<ModelProvider, ProviderConfig> = {
|
|
|
82
82
|
maxTokens: 4096,
|
|
83
83
|
requiresApiKey: true
|
|
84
84
|
},
|
|
85
|
+
deepseek: {
|
|
86
|
+
enabled: false,
|
|
87
|
+
apiKey: '',
|
|
88
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
89
|
+
model: 'deepseek-chat',
|
|
90
|
+
temperature: 0.7,
|
|
91
|
+
maxTokens: 4096,
|
|
92
|
+
requiresApiKey: true
|
|
93
|
+
},
|
|
94
|
+
kimi: {
|
|
95
|
+
enabled: false,
|
|
96
|
+
apiKey: '',
|
|
97
|
+
baseUrl: 'https://api.moonshot.cn/v1',
|
|
98
|
+
model: 'moonshot-v1-8k',
|
|
99
|
+
temperature: 0.7,
|
|
100
|
+
maxTokens: 4096,
|
|
101
|
+
requiresApiKey: true
|
|
102
|
+
},
|
|
103
|
+
glm: {
|
|
104
|
+
enabled: false,
|
|
105
|
+
apiKey: '',
|
|
106
|
+
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
107
|
+
model: 'glm-4-flash',
|
|
108
|
+
temperature: 0.7,
|
|
109
|
+
maxTokens: 4096,
|
|
110
|
+
requiresApiKey: true
|
|
111
|
+
},
|
|
112
|
+
qwen: {
|
|
113
|
+
enabled: false,
|
|
114
|
+
apiKey: '',
|
|
115
|
+
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
116
|
+
model: 'qwen-plus',
|
|
117
|
+
temperature: 0.7,
|
|
118
|
+
maxTokens: 4096,
|
|
119
|
+
requiresApiKey: true
|
|
120
|
+
},
|
|
85
121
|
local: {
|
|
86
122
|
enabled: false,
|
|
87
123
|
apiKey: '',
|
|
@@ -100,6 +136,10 @@ export const PROVIDER_INFO: Record<ModelProvider, { name: string; description: s
|
|
|
100
136
|
gemini: { name: 'Google Gemini', description: 'Gemini 系列模型', requiresApiKey: true },
|
|
101
137
|
ollama: { name: 'Ollama', description: '本地 LLM 运行框架', requiresApiKey: false },
|
|
102
138
|
minimax: { name: 'MiniMax', description: '国产大模型服务', requiresApiKey: true },
|
|
139
|
+
deepseek: { name: 'DeepSeek', description: '深度求索大模型', requiresApiKey: true },
|
|
140
|
+
kimi: { name: 'Kimi (月之暗面)', description: 'Moonshot 长上下文模型', requiresApiKey: true },
|
|
141
|
+
glm: { name: 'GLM (智谱)', description: '智谱 ChatGLM 系列模型', requiresApiKey: true },
|
|
142
|
+
qwen: { name: 'Qwen (通义千问)', description: '阿里云通义千问系列', requiresApiKey: true },
|
|
103
143
|
local: { name: '本地模型', description: '本地部署的模型服务', requiresApiKey: false }
|
|
104
144
|
};
|
|
105
145
|
|
|
@@ -121,6 +161,18 @@ function getDefaultConfig(): LLMConfig {
|
|
|
121
161
|
if (process.env.MINIMAX_API_KEY) {
|
|
122
162
|
envConfigs.minimax = { ...DEFAULT_PROVIDER_CONFIGS.minimax, enabled: true, apiKey: process.env.MINIMAX_API_KEY };
|
|
123
163
|
}
|
|
164
|
+
if (process.env.DEEPSEEK_API_KEY) {
|
|
165
|
+
envConfigs.deepseek = { ...DEFAULT_PROVIDER_CONFIGS.deepseek, enabled: true, apiKey: process.env.DEEPSEEK_API_KEY };
|
|
166
|
+
}
|
|
167
|
+
if (process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY) {
|
|
168
|
+
envConfigs.kimi = { ...DEFAULT_PROVIDER_CONFIGS.kimi, enabled: true, apiKey: process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || '' };
|
|
169
|
+
}
|
|
170
|
+
if (process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY) {
|
|
171
|
+
envConfigs.glm = { ...DEFAULT_PROVIDER_CONFIGS.glm, enabled: true, apiKey: process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY || '' };
|
|
172
|
+
}
|
|
173
|
+
if (process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY) {
|
|
174
|
+
envConfigs.qwen = { ...DEFAULT_PROVIDER_CONFIGS.qwen, enabled: true, apiKey: process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY || '' };
|
|
175
|
+
}
|
|
124
176
|
if (process.env.OLLAMA_BASE_URL) {
|
|
125
177
|
envConfigs.ollama = { ...DEFAULT_PROVIDER_CONFIGS.ollama, enabled: true, baseUrl: process.env.OLLAMA_BASE_URL };
|
|
126
178
|
}
|
|
@@ -131,6 +183,10 @@ function getDefaultConfig(): LLMConfig {
|
|
|
131
183
|
else if (process.env.OPENROUTER_API_KEY) activeProvider = 'openrouter';
|
|
132
184
|
else if (process.env.GEMINI_API_KEY) activeProvider = 'gemini';
|
|
133
185
|
else if (process.env.MINIMAX_API_KEY) activeProvider = 'minimax';
|
|
186
|
+
else if (process.env.DEEPSEEK_API_KEY) activeProvider = 'deepseek';
|
|
187
|
+
else if (process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY) activeProvider = 'kimi';
|
|
188
|
+
else if (process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY) activeProvider = 'glm';
|
|
189
|
+
else if (process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY) activeProvider = 'qwen';
|
|
134
190
|
else if (process.env.OLLAMA_BASE_URL) activeProvider = 'ollama';
|
|
135
191
|
|
|
136
192
|
const providers = { ...DEFAULT_PROVIDER_CONFIGS };
|
|
@@ -281,6 +337,10 @@ class LLMConfigStore {
|
|
|
281
337
|
case 'openai':
|
|
282
338
|
case 'openrouter':
|
|
283
339
|
case 'minimax':
|
|
340
|
+
case 'deepseek':
|
|
341
|
+
case 'kimi':
|
|
342
|
+
case 'glm':
|
|
343
|
+
case 'qwen':
|
|
284
344
|
if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
285
345
|
break;
|
|
286
346
|
case 'anthropic':
|
package/src/llm/pi-ai.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
|
|
4
|
-
export type ModelProvider = 'openai' | 'anthropic' | 'ollama' | 'openrouter' | 'gemini' | 'minimax' | 'local';
|
|
4
|
+
export type ModelProvider = 'openai' | 'anthropic' | 'ollama' | 'openrouter' | 'gemini' | 'minimax' | 'deepseek' | 'kimi' | 'glm' | 'qwen' | 'local';
|
|
5
5
|
|
|
6
6
|
export interface ModelConfig {
|
|
7
7
|
provider: ModelProvider;
|
|
@@ -105,6 +105,10 @@ export class PiAIModel {
|
|
|
105
105
|
switch (this.provider) {
|
|
106
106
|
case 'openai':
|
|
107
107
|
case 'minimax':
|
|
108
|
+
case 'deepseek':
|
|
109
|
+
case 'kimi':
|
|
110
|
+
case 'glm':
|
|
111
|
+
case 'qwen':
|
|
108
112
|
return this.callOpenAI(messages, temperature, maxTokens);
|
|
109
113
|
case 'anthropic':
|
|
110
114
|
return this.callAnthropic(messages, temperature, maxTokens);
|
|
@@ -133,6 +137,10 @@ export class PiAIModel {
|
|
|
133
137
|
openrouter: process.env.OPENROUTER_API_KEY || '',
|
|
134
138
|
gemini: process.env.GEMINI_API_KEY || '',
|
|
135
139
|
minimax: process.env.MINIMAX_API_KEY || '',
|
|
140
|
+
deepseek: process.env.DEEPSEEK_API_KEY || '',
|
|
141
|
+
kimi: process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || '',
|
|
142
|
+
glm: process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY || '',
|
|
143
|
+
qwen: process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY || '',
|
|
136
144
|
local: ''
|
|
137
145
|
};
|
|
138
146
|
return envVars[this.provider] || '';
|
|
@@ -151,6 +159,10 @@ export class PiAIModel {
|
|
|
151
159
|
openrouter: process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1',
|
|
152
160
|
gemini: 'https://generativelanguage.googleapis.com/v1beta',
|
|
153
161
|
minimax: process.env.MINIMAX_BASE_URL || 'https://api.minimaxi.com/v1',
|
|
162
|
+
deepseek: process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1',
|
|
163
|
+
kimi: process.env.KIMI_BASE_URL || process.env.MOONSHOT_BASE_URL || 'https://api.moonshot.cn/v1',
|
|
164
|
+
glm: process.env.GLM_BASE_URL || process.env.ZHIPU_BASE_URL || 'https://open.bigmodel.cn/api/paas/v4',
|
|
165
|
+
qwen: process.env.QWEN_BASE_URL || process.env.DASHSCOPE_BASE_URL || 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
154
166
|
local: 'http://localhost:11434'
|
|
155
167
|
};
|
|
156
168
|
|
|
@@ -165,6 +177,10 @@ export class PiAIModel {
|
|
|
165
177
|
openrouter: this.config.model || 'anthropic/claude-3.5-sonnet',
|
|
166
178
|
gemini: this.config.model || 'gemini-2.0-flash',
|
|
167
179
|
minimax: this.config.model || process.env.MINIMAX_MODEL || 'MiniMax-M2.7',
|
|
180
|
+
deepseek: this.config.model || process.env.DEEPSEEK_MODEL || 'deepseek-chat',
|
|
181
|
+
kimi: this.config.model || process.env.KIMI_MODEL || process.env.MOONSHOT_MODEL || 'moonshot-v1-8k',
|
|
182
|
+
glm: this.config.model || process.env.GLM_MODEL || process.env.ZHIPU_MODEL || 'glm-4-flash',
|
|
183
|
+
qwen: this.config.model || process.env.QWEN_MODEL || process.env.DASHSCOPE_MODEL || 'qwen-plus',
|
|
168
184
|
local: this.config.model || 'llama3.2'
|
|
169
185
|
};
|
|
170
186
|
return modelMap[this.provider];
|
|
@@ -451,6 +467,10 @@ function detectProvider(): ModelProvider {
|
|
|
451
467
|
if (process.env.GEMINI_API_KEY) return 'gemini';
|
|
452
468
|
if (process.env.OLLAMA_BASE_URL) return 'ollama';
|
|
453
469
|
if (process.env.MINIMAX_API_KEY) return 'minimax';
|
|
470
|
+
if (process.env.DEEPSEEK_API_KEY) return 'deepseek';
|
|
471
|
+
if (process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY) return 'kimi';
|
|
472
|
+
if (process.env.GLM_API_KEY || process.env.ZHIPU_API_KEY) return 'glm';
|
|
473
|
+
if (process.env.QWEN_API_KEY || process.env.DASHSCOPE_API_KEY) return 'qwen';
|
|
454
474
|
|
|
455
475
|
return 'openai';
|
|
456
476
|
}
|
|
@@ -463,6 +483,10 @@ function detectModel(provider: ModelProvider): string {
|
|
|
463
483
|
openrouter: 'anthropic/claude-3.5-sonnet',
|
|
464
484
|
gemini: 'gemini-2.0-flash',
|
|
465
485
|
minimax: 'MiniMax-M2.7',
|
|
486
|
+
deepseek: 'deepseek-chat',
|
|
487
|
+
kimi: 'moonshot-v1-8k',
|
|
488
|
+
glm: 'glm-4-flash',
|
|
489
|
+
qwen: 'qwen-plus',
|
|
466
490
|
local: 'llama3.2'
|
|
467
491
|
};
|
|
468
492
|
return defaults[provider];
|