126 lines
5.7 KiB
JavaScript
126 lines
5.7 KiB
JavaScript
import { spawn } from 'child_process';
|
||
import { DEFAULT_COARSE_PROMPT, DEFAULT_FINE_PROMPT } from './ai.mjs';
|
||
|
||
const HERMES_BIN = process.env.HERMES_BIN || '/Users/chick/.local/bin/hermes';
|
||
|
||
function extractFirstJsonObject(text = '') {
|
||
const raw = String(text || '').trim().replace(/```json\s*/g, '').replace(/```/g, '').trim();
|
||
const direct = tryParse(raw);
|
||
if (direct) return direct;
|
||
|
||
const start = raw.indexOf('{');
|
||
if (start < 0) throw new Error('Hermes 返回中没有 JSON 对象');
|
||
let depth = 0;
|
||
let inString = false;
|
||
let escape = false;
|
||
for (let i = start; i < raw.length; i++) {
|
||
const ch = raw[i];
|
||
if (escape) { escape = false; continue; }
|
||
if (ch === '\\') { escape = true; continue; }
|
||
if (ch === '"') { inString = !inString; continue; }
|
||
if (inString) continue;
|
||
if (ch === '{') depth++;
|
||
if (ch === '}') {
|
||
depth--;
|
||
if (depth === 0) {
|
||
const parsed = tryParse(raw.slice(start, i + 1));
|
||
if (parsed) return parsed;
|
||
}
|
||
}
|
||
}
|
||
throw new Error('Hermes 返回的 JSON 无法解析');
|
||
}
|
||
|
||
function tryParse(text) {
|
||
try { return JSON.parse(text); } catch { return null; }
|
||
}
|
||
|
||
function buildLocalOptimizedTask({ seed = '', product = '', budget = '', region = '', currentRequirements = '' }) {
|
||
const text = `${product || seed}`.trim();
|
||
const queries = [...new Set(text.split(/[,,、]/).map(s => s.trim()).filter(Boolean))];
|
||
if (queries.length === 0 && text) queries.push(text);
|
||
const budgetNum = Number(String(budget).match(/\d+(?:\.\d+)?/)?.[0] || '') || null;
|
||
return {
|
||
name: text ? `${text} 监测` : '闲鱼商品监测',
|
||
queries: queries.slice(0, 6),
|
||
priceMin: null,
|
||
priceMax: budgetNum,
|
||
region,
|
||
maxPages: 2,
|
||
maxItems: 40,
|
||
personalSeller: true,
|
||
autoAnalyze: true,
|
||
autoNotify: true,
|
||
customRequirements: `${currentRequirements || `目标:${text}${budgetNum ? `,预算${budgetNum}左右` : ''}`};只做采集分析,不自动联系卖家;排除配件/维修/求购/租赁/回收/商家批量/标题党;优先个人自用、描述清楚、价格合理、可核验。`,
|
||
coarsePrompt: DEFAULT_COARSE_PROMPT,
|
||
finePrompt: DEFAULT_FINE_PROMPT,
|
||
};
|
||
}
|
||
|
||
function sanitizeTask(candidate, fallback) {
|
||
const merged = { ...fallback, ...(candidate || {}) };
|
||
const queries = Array.isArray(merged.queries)
|
||
? merged.queries.map(q => String(q || '').trim()).filter(Boolean)
|
||
: fallback.queries;
|
||
return {
|
||
name: String(merged.name || fallback.name || '闲鱼商品监测').slice(0, 80),
|
||
queries: queries.length ? [...new Set(queries)].slice(0, 6) : fallback.queries,
|
||
priceMin: merged.priceMin == null || merged.priceMin === '' ? null : Number(merged.priceMin),
|
||
priceMax: merged.priceMax == null || merged.priceMax === '' ? fallback.priceMax : Number(merged.priceMax),
|
||
region: String(merged.region || fallback.region || '').slice(0, 30),
|
||
maxPages: Math.max(1, Math.min(20, Number(merged.maxPages || fallback.maxPages || 2))),
|
||
maxItems: Math.max(1, Math.min(500, Number(merged.maxItems || fallback.maxItems || 40))),
|
||
personalSeller: merged.personalSeller !== false,
|
||
autoAnalyze: merged.autoAnalyze !== false,
|
||
autoNotify: merged.autoNotify !== false,
|
||
customRequirements: String(merged.customRequirements || fallback.customRequirements || '').slice(0, 1200),
|
||
coarsePrompt: String(merged.coarsePrompt || fallback.coarsePrompt || DEFAULT_COARSE_PROMPT).slice(0, 2000),
|
||
finePrompt: String(merged.finePrompt || fallback.finePrompt || DEFAULT_FINE_PROMPT).slice(0, 2000),
|
||
};
|
||
}
|
||
|
||
export async function optimizeTaskWithHermes(input = {}) {
|
||
const fallback = buildLocalOptimizedTask(input);
|
||
const prompt = `你是 Hermes Agent,正在为 BOSS 的闲鱼监控项目润色任务配置。请直接返回一个 JSON 对象,不要 Markdown,不要解释。
|
||
|
||
输出字段必须是:
|
||
{"name":string,"queries":string[],"priceMin":number|null,"priceMax":number|null,"region":string,"maxPages":number,"maxItems":number,"personalSeller":boolean,"autoAnalyze":boolean,"autoNotify":boolean,"customRequirements":string,"coarsePrompt":string,"finePrompt":string}
|
||
|
||
要求:
|
||
- queries 生成 3-6 个适合闲鱼/Goofish 搜索的关键词,包含别名、空格/无空格、大小写/中文变体。
|
||
- customRequirements 要写成可执行筛选规则,明确排除配件、维修、坏件、求购、租赁、回收、商家批量、异常低价、标题党。
|
||
- 保持安全:只采集和分析,不自动联系卖家、不下单、不承诺交易。
|
||
- maxPages 建议 1-3,maxItems 建议 20-80。
|
||
- autoAnalyze 和 autoNotify 默认 true。
|
||
- 如果预算存在,priceMax 使用预算数字;priceMin 通常为 null。
|
||
|
||
用户输入:${JSON.stringify(input, null, 2)}
|
||
本地兜底参考:${JSON.stringify(fallback, null, 2)}`;
|
||
|
||
const stdout = await new Promise((resolve, reject) => {
|
||
const child = spawn(HERMES_BIN, ['-z', prompt, '--ignore-rules', '-t', 'terminal'], {
|
||
cwd: process.env.XIANHU_ROOT || process.cwd(),
|
||
env: { ...process.env, HERMES_ACCEPT_HOOKS: '1' },
|
||
});
|
||
let out = '';
|
||
let err = '';
|
||
const timer = setTimeout(() => {
|
||
child.kill('SIGKILL');
|
||
reject(new Error('Hermes 润色超时'));
|
||
}, Number(process.env.HERMES_OPTIMIZE_TIMEOUT_MS || 90000));
|
||
child.stdout.on('data', d => { out += d.toString(); });
|
||
child.stderr.on('data', d => { err += d.toString(); });
|
||
child.on('error', e => { clearTimeout(timer); reject(e); });
|
||
child.on('close', code => {
|
||
clearTimeout(timer);
|
||
if (code !== 0) return reject(new Error((err || out || `Hermes 退出码 ${code}`).slice(0, 800)));
|
||
resolve(out);
|
||
});
|
||
});
|
||
|
||
const parsed = extractFirstJsonObject(stdout);
|
||
return { source: 'hermes', task: sanitizeTask(parsed, fallback), raw: stdout.trim() };
|
||
}
|
||
|
||
export { buildLocalOptimizedTask };
|