@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/src/web/server.ts
CHANGED
|
@@ -18,15 +18,38 @@ import { documentReader } from '../documents/reader.js';
|
|
|
18
18
|
import { initMinimax, getMinimax } from '../constraints/index.js';
|
|
19
19
|
import { createAgentSession, type AgentSession, type StreamCallback, type StreamEvent } from '../agents/pi-sdk.js';
|
|
20
20
|
import { llmConfigStore, type ModelProvider, PROVIDER_INFO } from '../llm/config-store.js';
|
|
21
|
+
import { videoConfigStore, type VideoProvider } from '../llm/video-config-store.js';
|
|
22
|
+
import { audioConfigStore, type AudioProvider } from '../llm/audio-config-store.js';
|
|
21
23
|
import { irohTransport } from '../network/iroh-transport.js';
|
|
22
24
|
import { createAgentDelegateApp } from './agent-delegate-server.js';
|
|
23
25
|
import { createIrohDelegateTransport } from './iroh-delegate-transport.js';
|
|
24
26
|
import { verifyMessage, isAddress, getAddress } from 'viem';
|
|
25
27
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// 前端资源路径: 兼容 src 运行 + dist 运行 + npm 全局安装
|
|
29
|
+
// - src 跑 (tsx): __dirname = .../src/web → .../dist/web
|
|
30
|
+
// - dist 跑 (npm): __dirname = .../dist/web → 自身就是 web 根
|
|
31
|
+
// - 环境变量覆盖: BOLLOON_WEB_ROOT=xxx
|
|
32
|
+
// ESM scope 没有 __dirname, 这里自己声明
|
|
33
|
+
const __filename_local = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname_local = dirname(__filename_local);
|
|
35
|
+
let _baseDirname = __dirname_local;
|
|
36
|
+
function resolveWebRoot(): string {
|
|
37
|
+
if (process.env.BOLLOON_WEB_ROOT && fsSync.existsSync(process.env.BOLLOON_WEB_ROOT)) {
|
|
38
|
+
return process.env.BOLLOON_WEB_ROOT;
|
|
39
|
+
}
|
|
40
|
+
const d = _baseDirname;
|
|
41
|
+
const candidates = [
|
|
42
|
+
path.join(d), // dist/web
|
|
43
|
+
path.join(d, '..', '..', 'dist', 'web'), // src/web → dist/web
|
|
44
|
+
path.join(d, '..', 'web'), // dist/ → web/ 兄弟
|
|
45
|
+
];
|
|
46
|
+
for (const c of candidates) {
|
|
47
|
+
if (fsSync.existsSync(path.join(c, 'index.html'))) return c;
|
|
48
|
+
}
|
|
49
|
+
return candidates[1];
|
|
50
|
+
}
|
|
51
|
+
const webRoot = resolveWebRoot();
|
|
52
|
+
console.log(`[web] webRoot = ${webRoot}`);
|
|
30
53
|
|
|
31
54
|
const SHARED_SESSION_PATH = path.join(process.env.HOME || '/tmp', '.bolloon', 'sessions');
|
|
32
55
|
const SESSION_CACHE_PATH = path.join(SHARED_SESSION_PATH, 'cache');
|
|
@@ -2255,6 +2278,130 @@ app.get('/channels', async (_req, res) => {
|
|
|
2255
2278
|
}
|
|
2256
2279
|
});
|
|
2257
2280
|
|
|
2281
|
+
// ==================== 视频生成配置 (Seedance 等) ====================
|
|
2282
|
+
|
|
2283
|
+
// 获取视频生成配置
|
|
2284
|
+
app.get('/api/video-config', async (req, res) => {
|
|
2285
|
+
try {
|
|
2286
|
+
const config = await videoConfigStore.getConfig();
|
|
2287
|
+
const providerInfo = videoConfigStore.getAllProviderInfo();
|
|
2288
|
+
|
|
2289
|
+
// 脱敏:不返回 apiKey 明文
|
|
2290
|
+
const masked = Object.fromEntries(
|
|
2291
|
+
Object.entries(config.providers).map(([key, val]) => [
|
|
2292
|
+
key,
|
|
2293
|
+
{ ...val, apiKey: val.apiKey ? '***' + val.apiKey.slice(-4) : '' }
|
|
2294
|
+
])
|
|
2295
|
+
);
|
|
2296
|
+
|
|
2297
|
+
res.json({
|
|
2298
|
+
activeProvider: config.activeProvider,
|
|
2299
|
+
providers: masked,
|
|
2300
|
+
providerInfo
|
|
2301
|
+
});
|
|
2302
|
+
} catch (err: any) {
|
|
2303
|
+
res.status(500).json({ error: err.message });
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
// 更新视频供应商配置
|
|
2308
|
+
app.post('/api/video-config', async (req, res) => {
|
|
2309
|
+
try {
|
|
2310
|
+
const { provider, config } = req.body;
|
|
2311
|
+
|
|
2312
|
+
if (!provider || !config) {
|
|
2313
|
+
return res.status(400).json({ error: 'provider and config required' });
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// 如果前端发的是掩码(***xxx),从当前配置里取真实 key
|
|
2317
|
+
const currentConfig = await videoConfigStore.getProvider(provider as VideoProvider);
|
|
2318
|
+
if (currentConfig && config.apiKey && config.apiKey.startsWith('***')) {
|
|
2319
|
+
config.apiKey = currentConfig.apiKey;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
await videoConfigStore.updateProvider(provider as VideoProvider, config);
|
|
2323
|
+
res.json({ ok: true });
|
|
2324
|
+
} catch (err: any) {
|
|
2325
|
+
res.status(500).json({ error: err.message });
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
// 测试视频供应商连接
|
|
2330
|
+
app.post('/api/video-test', async (req, res) => {
|
|
2331
|
+
try {
|
|
2332
|
+
const { provider } = req.body;
|
|
2333
|
+
|
|
2334
|
+
if (!provider) {
|
|
2335
|
+
return res.status(400).json({ error: 'provider required' });
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
const result = await videoConfigStore.testProvider(provider as VideoProvider);
|
|
2339
|
+
res.json(result);
|
|
2340
|
+
} catch (err: any) {
|
|
2341
|
+
res.status(500).json({ error: err.message });
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
|
|
2345
|
+
// ==================== 音频生成配置 (TTS / Music) ====================
|
|
2346
|
+
|
|
2347
|
+
// 获取音频配置
|
|
2348
|
+
app.get('/api/audio-config', async (req, res) => {
|
|
2349
|
+
try {
|
|
2350
|
+
const config = await audioConfigStore.getConfig();
|
|
2351
|
+
const providerInfo = audioConfigStore.getAllProviderInfo();
|
|
2352
|
+
|
|
2353
|
+
const masked = Object.fromEntries(
|
|
2354
|
+
Object.entries(config.providers).map(([key, val]) => [
|
|
2355
|
+
key,
|
|
2356
|
+
{ ...val, apiKey: val.apiKey ? '***' + val.apiKey.slice(-4) : '' }
|
|
2357
|
+
])
|
|
2358
|
+
);
|
|
2359
|
+
|
|
2360
|
+
res.json({
|
|
2361
|
+
activeProvider: config.activeProvider,
|
|
2362
|
+
providers: masked,
|
|
2363
|
+
providerInfo
|
|
2364
|
+
});
|
|
2365
|
+
} catch (err: any) {
|
|
2366
|
+
res.status(500).json({ error: err.message });
|
|
2367
|
+
}
|
|
2368
|
+
});
|
|
2369
|
+
|
|
2370
|
+
// 更新音频供应商配置
|
|
2371
|
+
app.post('/api/audio-config', async (req, res) => {
|
|
2372
|
+
try {
|
|
2373
|
+
const { provider, config } = req.body;
|
|
2374
|
+
if (!provider || !config) {
|
|
2375
|
+
return res.status(400).json({ error: 'provider and config required' });
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// 掩码回写真实 key
|
|
2379
|
+
const currentConfig = await audioConfigStore.getProvider(provider as AudioProvider);
|
|
2380
|
+
if (currentConfig && config.apiKey && config.apiKey.startsWith('***')) {
|
|
2381
|
+
config.apiKey = currentConfig.apiKey;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
await audioConfigStore.updateProvider(provider as AudioProvider, config);
|
|
2385
|
+
res.json({ ok: true });
|
|
2386
|
+
} catch (err: any) {
|
|
2387
|
+
res.status(500).json({ error: err.message });
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2390
|
+
|
|
2391
|
+
// 测试音频供应商连接
|
|
2392
|
+
app.post('/api/audio-test', async (req, res) => {
|
|
2393
|
+
try {
|
|
2394
|
+
const { provider } = req.body;
|
|
2395
|
+
if (!provider) {
|
|
2396
|
+
return res.status(400).json({ error: 'provider required' });
|
|
2397
|
+
}
|
|
2398
|
+
const result = await audioConfigStore.testProvider(provider as AudioProvider);
|
|
2399
|
+
res.json(result);
|
|
2400
|
+
} catch (err: any) {
|
|
2401
|
+
res.status(500).json({ error: err.message });
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
|
|
2258
2405
|
// 统一 AI 解析入口:CLI / 接收方节点 调这里完成 LLM + judgment + harness
|
|
2259
2406
|
// 入参: { text, mimeType, fileName, fromNodeId, source }
|
|
2260
2407
|
// 出参: { summary, qualityScore, judgmentId?, gateArtifact? }
|
package/src/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 {
|