opc-agent 4.1.3 → 4.1.4

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/cli.js CHANGED
@@ -37,10 +37,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const commander_1 = require("commander");
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
- const os = __importStar(require("os"));
41
40
  const yaml = __importStar(require("js-yaml"));
42
41
  const readline = __importStar(require("readline"));
43
42
  const runtime_1 = require("./core/runtime");
43
+ const model_recommender_1 = require("./core/model-recommender");
44
44
  const customer_service_1 = require("./templates/customer-service");
45
45
  const sales_assistant_1 = require("./templates/sales-assistant");
46
46
  const knowledge_base_1 = require("./templates/knowledge-base");
@@ -366,32 +366,9 @@ export class EchoSkill extends BaseSkill {
366
366
  template = await select('Select a template:', Object.entries(TEMPLATES).map(([value, { label }]) => ({ value, label })));
367
367
  }
368
368
  // ── 硬件检测 + 智能模型推荐 ──
369
- const totalRAM = Math.round(os.totalmem() / (1024 ** 3)); // GB
370
- const freeMem = Math.round(os.freemem() / (1024 ** 3));
371
- const cpuCount = os.cpus().length;
372
- const MODEL_RECOMMENDATIONS = [
373
- // Tier 1: 超轻量 (≤4GB RAM)
374
- { name: 'qwen2.5:0.5b', size: '0.4GB', minRAM: 2, desc: '超轻量,适合低配机器', priority: 1 },
375
- { name: 'qwen2.5:1.5b', size: '1.0GB', minRAM: 4, desc: '轻量但更智能', priority: 2 },
376
- // Tier 2: 轻量 (4-8GB RAM)
377
- { name: 'qwen2.5:3b', size: '2.0GB', minRAM: 6, desc: '性价比最优', priority: 3 },
378
- { name: 'llama3.2:3b', size: '2.0GB', minRAM: 6, desc: 'Meta 最新轻量模型', priority: 3 },
379
- { name: 'phi3:mini', size: '2.3GB', minRAM: 6, desc: '微软高效小模型', priority: 3 },
380
- // Tier 3: 标准 (8-16GB RAM)
381
- { name: 'qwen2.5:7b', size: '4.7GB', minRAM: 8, desc: '推荐:中文最强 7B', priority: 4 },
382
- { name: 'llama3.1:8b', size: '4.7GB', minRAM: 8, desc: 'Meta 通用 8B', priority: 4 },
383
- { name: 'mistral:7b', size: '4.1GB', minRAM: 8, desc: 'Mistral 经典 7B', priority: 4 },
384
- { name: 'gemma2:9b', size: '5.4GB', minRAM: 10, desc: 'Google 高效 9B', priority: 4 },
385
- // Tier 4: 高配 (16-32GB RAM)
386
- { name: 'qwen2.5:14b', size: '9.0GB', minRAM: 16, desc: '中文强力 14B', priority: 5 },
387
- { name: 'deepseek-coder-v2:16b', size: '9.0GB', minRAM: 16, desc: '编程专用', priority: 5 },
388
- // Tier 5: 旗舰 (32GB+ RAM)
389
- { name: 'qwen2.5:32b', size: '20GB', minRAM: 32, desc: '接近 GPT-4 水平', priority: 6 },
390
- { name: 'llama3.1:70b', size: '40GB', minRAM: 64, desc: '开源最强', priority: 7 },
391
- ];
392
- // 根据可用内存筛选合适的模型
393
- const suitableModels = MODEL_RECOMMENDATIONS.filter(m => m.minRAM <= freeMem + 2); // +2GB 容差
394
- const bestRec = suitableModels.length > 0 ? suitableModels[suitableModels.length - 1] : MODEL_RECOMMENDATIONS[0];
369
+ // ── 硬件检测 + 远程模型推荐 ──
370
+ const sys = (0, model_recommender_1.detectSystem)();
371
+ const allModels = await (0, model_recommender_1.fetchModelList)();
395
372
  // ── LLM Provider 选择(Ollama-first)──
396
373
  let llmProvider = 'ollama';
397
374
  let llmModel = 'qwen2.5';
@@ -409,20 +386,23 @@ export class EchoSkill extends BaseSkill {
409
386
  modelNames = (ollamaData.models || []).map((m) => m.name || m.model);
410
387
  ollamaRunning = true;
411
388
  if (opts.yes && modelNames.length > 0) {
412
- // --yes 模式:优先用推荐模型(如果已安装),否则用第一个已有模型
413
- const bestInstalled = suitableModels.reverse().find(m => modelNames.includes(m.name));
389
+ const rec = (0, model_recommender_1.recommendModels)(allModels, sys, modelNames);
390
+ // --yes: prefer best installed recommended model
391
+ const bestInstalled = rec.installed.length > 0 ? rec.installed[rec.installed.length - 1] : null;
414
392
  llmModel = bestInstalled ? bestInstalled.name : modelNames[0];
415
393
  }
416
394
  }
417
395
  catch {
418
396
  ollamaRunning = false;
419
397
  }
398
+ // Compute recommendation (used by both interactive branches)
399
+ const rec = (0, model_recommender_1.recommendModels)(allModels, sys, modelNames);
420
400
  if (!opts.yes) {
421
401
  if (ollamaRunning) {
422
402
  console.log(`\n ${icon.info} ${color.dim('正在检测 Ollama...')}`);
423
403
  console.log(` ${icon.success} Ollama 已运行,发现 ${modelNames.length} 个模型`);
424
- console.log(` ${icon.info} 系统: ${totalRAM}GB RAM (${freeMem}GB 可用), ${cpuCount} CPU cores`);
425
- console.log(` ${icon.info} 推荐模型: ${color.cyan(bestRec.name)} (${bestRec.size}) - ${bestRec.desc}`);
404
+ console.log(` ${icon.info} 系统: ${sys.totalRAM}GB RAM (${sys.freeRAM}GB 可用), ${sys.cpuCount} CPU cores`);
405
+ console.log(` ${icon.info} 推荐模型: ${color.cyan(rec.best.name)} (${rec.best.size}) - ${rec.best.desc}`);
426
406
  // 选择 provider
427
407
  llmProvider = await select('选择 LLM 引擎:', [
428
408
  { value: 'ollama', label: '🟢 Ollama (免费本地,推荐) - 已检测到运行中' },
@@ -433,16 +413,14 @@ export class EchoSkill extends BaseSkill {
433
413
  ]);
434
414
  if (llmProvider === 'ollama') {
435
415
  // 已有模型 + 推荐未下载的模型
436
- const existingSet = new Set(modelNames);
437
- const recommendedNotInstalled = suitableModels.filter(m => !existingSet.has(m.name)).slice(-3); // 推荐最多3个未下载的
438
416
  const modelOptions = [
439
- ...modelNames.map((m) => {
440
- const rec = MODEL_RECOMMENDATIONS.find(r => r.name === m);
441
- const recLabel = rec ? ` (${rec.size}, ${rec.desc})` : '';
442
- const isBest = m === bestRec.name ? ' ⭐推荐' : '';
443
- return { value: m, label: `${m}${recLabel}${isBest} [已安装]` };
417
+ ...rec.installed.map((m) => {
418
+ const isBest = m.name === rec.best.name ? ' ⭐推荐' : '';
419
+ return { value: m.name, label: `${m.name} (${m.size}, ${m.desc})${isBest} [已安装]` };
444
420
  }),
445
- ...recommendedNotInstalled.map(m => ({
421
+ // Also show installed models not in recommendation list
422
+ ...modelNames.filter(n => !rec.installed.find(m => m.name === n)).map(n => ({ value: n, label: `${n} [已安装]` })),
423
+ ...rec.toDownload.map((m) => ({
446
424
  value: `pull:${m.name}`,
447
425
  label: `${m.name} (${m.size}, ${m.desc}) [需下载]`,
448
426
  })),
@@ -463,11 +441,11 @@ export class EchoSkill extends BaseSkill {
463
441
  else {
464
442
  // 没有本地模型,推荐下载
465
443
  console.log(` ${color.yellow('⚠️')} 没有发现已下载的模型`);
466
- console.log(` ${icon.info} 根据你的硬件 (${freeMem}GB 可用),推荐下载:`);
467
- for (const m of suitableModels.slice(-3)) {
444
+ console.log(` ${icon.info} 根据你的硬件 (${sys.freeRAM}GB 可用),推荐下载:`);
445
+ for (const m of rec.suitable.slice(-3)) {
468
446
  console.log(` ${color.cyan(`ollama pull ${m.name}`)} (${m.size}, ${m.desc})`);
469
447
  }
470
- llmModel = bestRec.name;
448
+ llmModel = rec.best.name;
471
449
  }
472
450
  }
473
451
  }
@@ -485,12 +463,12 @@ export class EchoSkill extends BaseSkill {
485
463
  if (llmProvider === 'ollama') {
486
464
  console.log(`\n ${icon.info} Ollama 安装指南:`);
487
465
  console.log(` 1. 访问 ${color.cyan('https://ollama.ai')} 下载并安装`);
488
- console.log(` ${icon.info} 根据你的硬件 (${totalRAM}GB RAM, ${freeMem}GB 可用),推荐:`);
489
- for (const m of suitableModels.slice(-3)) {
466
+ console.log(` ${icon.info} 根据你的硬件 (${sys.totalRAM}GB RAM, ${sys.freeRAM}GB 可用),推荐:`);
467
+ for (const m of rec.suitable.slice(-3)) {
490
468
  console.log(` ${color.cyan(`ollama pull ${m.name}`)} (${m.size}, ${m.desc})`);
491
469
  }
492
470
  console.log(` 3. 然后 ${color.cyan('opc run')} 即可开始对话\n`);
493
- llmModel = bestRec.name;
471
+ llmModel = rec.best.name;
494
472
  }
495
473
  }
496
474
  // 商业模型需要 API key
@@ -2436,6 +2414,64 @@ program
2436
2414
  await voice.start();
2437
2415
  `));
2438
2416
  });
2417
+ // ── Models command ──────────────────────────────────────────────
2418
+ program
2419
+ .command('models')
2420
+ .description('Show recommended Ollama models for your system')
2421
+ .option('--refresh', 'Force refresh model list from remote')
2422
+ .option('--json', 'Output as JSON')
2423
+ .action(async (opts) => {
2424
+ if (opts.refresh) {
2425
+ (0, model_recommender_1.clearModelCache)();
2426
+ console.log(`${icon.success} 模型推荐缓存已清除`);
2427
+ }
2428
+ const sys = (0, model_recommender_1.detectSystem)();
2429
+ const models = await (0, model_recommender_1.fetchModelList)();
2430
+ const cache = (0, model_recommender_1.cacheInfo)();
2431
+ // Detect Ollama
2432
+ let installedModels = [];
2433
+ try {
2434
+ const ctrl = new AbortController();
2435
+ const t = setTimeout(() => ctrl.abort(), 3000);
2436
+ const res = await fetch('http://localhost:11434/api/tags', { signal: ctrl.signal });
2437
+ clearTimeout(t);
2438
+ const data = await res.json();
2439
+ installedModels = (data.models || []).map((m) => m.name || m.model);
2440
+ }
2441
+ catch { /* Ollama not running */ }
2442
+ const rec = (0, model_recommender_1.recommendModels)(models, sys, installedModels);
2443
+ if (opts.json) {
2444
+ console.log(JSON.stringify({ system: sys, cache, recommendation: rec }, null, 2));
2445
+ return;
2446
+ }
2447
+ console.log(`\n${icon.rocket} ${color.bold('OPC 模型推荐')}\n`);
2448
+ console.log(` 系统: ${sys.totalRAM}GB RAM (${sys.freeRAM}GB 可用), ${sys.cpuCount} cores, ${sys.platform}/${sys.arch}`);
2449
+ if (cache.exists) {
2450
+ console.log(` 推荐列表: v${cache.version} (${cache.age})`);
2451
+ }
2452
+ else {
2453
+ console.log(` 推荐列表: 内置 (运行 ${color.cyan('opc models --refresh')} 获取最新)`);
2454
+ }
2455
+ console.log(` Ollama: ${installedModels.length > 0 ? color.green(`运行中, ${installedModels.length} 个模型`) : color.yellow('未运行')}`);
2456
+ console.log(`\n ${color.bold('⭐ 推荐:')} ${color.cyan(rec.best.name)} (${rec.best.size}) - ${rec.best.desc}\n`);
2457
+ // Table
2458
+ console.log(` ${'模型'.padEnd(28)} ${'大小'.padEnd(8)} ${'最低RAM'.padEnd(8)} ${'状态'.padEnd(10)} 说明`);
2459
+ console.log(` ${'─'.repeat(28)} ${'─'.repeat(8)} ${'─'.repeat(8)} ${'─'.repeat(10)} ${'─'.repeat(20)}`);
2460
+ for (const m of rec.suitable) {
2461
+ const installed = installedModels.includes(m.name);
2462
+ const isBest = m.name === rec.best.name;
2463
+ const status = installed ? color.green('已安装') : color.dim('未安装');
2464
+ const star = isBest ? ' ⭐' : (m.recommended ? ' 💎' : '');
2465
+ console.log(` ${(m.name + star).padEnd(28)} ${m.size.padEnd(8)} ${(m.minRAM + 'GB').padEnd(8)} ${status.padEnd(10)} ${m.desc}`);
2466
+ }
2467
+ if (rec.toDownload.length > 0) {
2468
+ console.log(`\n ${color.bold('推荐下载:')}`);
2469
+ for (const m of rec.toDownload) {
2470
+ console.log(` ${color.cyan(`ollama pull ${m.name}`)} (${m.size}, ${m.desc})`);
2471
+ }
2472
+ }
2473
+ console.log();
2474
+ });
2439
2475
  program.parse();
2440
2476
  // ── Keys command ──────────────────────────────────────────────
2441
2477
  const keys_1 = require("./security/keys");