AI创作弹窗对话打磨 v2:单弹窗需求框+输入框+AI全程引导
废弃旧分步向导+跨会话打磨+md载入路径,改为单弹窗 confirmAiScript 重写。
改动:
- public/data-pages.js: confirmAiScript v2(L199-326)= REQ_FIELDS 10 项常量 + 顶部10行需求框常显(空值灰显"待定·由AI按人设自决")+ 中部对话流(u/ai气泡)+ 底部输入行 + 取消/提交创作;无 step/chip/AI按钮/载入按钮/上下步
- public/style.css: 删 .wizard-modal/.wz-/.ai-topic 旧样式;新增 .req-modal/.req-sheet/.req-dialog/.req-msg.mine/.req-msg.ai/.req-bubble/.req-input-row/.req-input/.req-send 全套
- server.js: 新增 POST /api/ai/clarify(紧跟 /api/ai/topics 之后)= 同步阻塞 Dify chat-messages 100s AbortController;入参 {accountName,turn,reqMd,history};query 拼装账号人设+当前需求+最近2轮历史+本轮输入;强制输出"对话+==REQ==+10行需求"模板
- 记忆:MEMORY.md 核心技能段补"AI创作弹窗对话打磨 v2"长期记忆;2026-09-02.md 追加完整收尾日志
关键设计:每轮重发 reqMd + 最近 2 轮 history 做无状态上下文;AI 可更新任意字段但严禁生成脚本正文;reqLines 仅取非空字段 + "未填由AI按人设自决"提示。
冒烟 tmp/cdp-req-dialog-smoke.mjs(CDP 9224)4 步端到端 53s 全绿:结构✓/双轮真实Dify打磨✓/需求框0→6→8字段✓/提交捕获{label:创作指令,skills:[短视频脚本创作],hasPersona:true,reqRows:8,modalClosed:true}✓。
This commit is contained in:
1 parent
5b5bcf790f
commit
7984520728
6 files changed
+291
-57
No files matched your search
@@ -650,6 +650,83 @@ const server = http.createServer((req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------- AI 创作需求打磨(09-02 弹窗内对话:Dify chat-messages 同步、无状态多轮) ----------
|
||||
// body: { accountName, turn(本轮输入), reqMd(当前需求10行md), history(最近轮[{u,a}]) } → { answer }
|
||||
if (p === '/api/ai/clarify' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', (c) => { body += c; if (body.length > 1e6) req.destroy(); });
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const { accountName, turn, reqMd, history } = JSON.parse(body || '{}');
|
||||
if (!accountName || typeof accountName !== 'string') return sendErr(400, '缺少账号名');
|
||||
if (!turn || typeof turn !== 'string' || !turn.trim()) return sendErr(400, '缺少用户输入');
|
||||
// 1) 账号设定摘要(工作台库,与选题接口同源)
|
||||
let personaText = '', contentText = '';
|
||||
try {
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const db = new DatabaseSync(path.join(ROOT, 'mcn-plugin.db'), { readOnly: true });
|
||||
const acc = db.prepare('SELECT * FROM hot_accounts WHERE account_name=? LIMIT 1').get(accountName);
|
||||
if (acc) {
|
||||
contentText = String(acc.content || '').slice(0, 300);
|
||||
const p = db.prepare('SELECT content_json, summary FROM account_persona WHERE account_id=? ORDER BY id DESC LIMIT 1').get(acc.id);
|
||||
if (p) personaText = String(p.summary || p.content_json || '').slice(0, 900);
|
||||
}
|
||||
db.close();
|
||||
} catch (e) { /* 库不可用不阻塞 */ }
|
||||
// 2) Dify key:环境变量优先,其次技能脚本内置默认
|
||||
let apiKey = process.env.DIFY_MCN_CYLG_KEY || '';
|
||||
if (!apiKey) {
|
||||
const scriptPath = path.join(ROOT, '..', 'scripts', 'MCN_CYLG_API.py');
|
||||
try {
|
||||
const src = fs.readFileSync(scriptPath, 'utf8');
|
||||
const m = src.match(/DEFAULT_CYLG_KEY\s*=\s*"([^"]+)"/);
|
||||
if (m) apiKey = m[1];
|
||||
} catch (e) { /* 脚本不存在则走 env */ }
|
||||
}
|
||||
if (!apiKey) return sendErr(500, '未配置 DIFY_MCN_CYLG_KEY(环境变量或技能脚本内置 KEY)');
|
||||
// 3) 构造打磨 query(无状态:携带当前需求 + 最近对话,让 AI 输出 reply + ==REQ== 结构化段)
|
||||
const his = Array.isArray(history) ? history.filter((h) => h && h.u).slice(-2) : [];
|
||||
const histText = his.map((h) => '你:' + String(h.a || '') + '\n达人:' + String(h.u || '')).join('\n');
|
||||
const reqMdText = String(reqMd || '').trim();
|
||||
const query = '你是资深短视频编导,正在创作前通过多轮对话,帮达人「' + accountName + '」把创作需求打磨清楚。'
|
||||
+ '你的职责只限需求澄清、整理与追问;严禁生成脚本正文、台词、分镜或完整故事。\n\n'
|
||||
+ '【账号设定摘要】\n' + (personaText || '(暂无可用账号设定,按通用编导经验澄清)') + '\n'
|
||||
+ (contentText ? '【账号定位】\n' + contentText + '\n' : '')
|
||||
+ '【当前需求清单】\n' + (reqMdText || '(空:等待用户先给出初步想法)') + '\n'
|
||||
+ (histText ? '【对话历史】\n' + histText + '\n' : '')
|
||||
+ '【达人本轮输入】\n' + turn.trim() + '\n\n'
|
||||
+ '【行为要求】\n'
|
||||
+ '1. 理解本轮输入:可能是新想法、对上一问的回答、或想修改某一维度。将信息吸收进需求清单,可改写得更专业准确,但保留用户原意与账号结构性符号。\n'
|
||||
+ '2. 若关键维度仍有缺口且本轮输入不足以补齐:最多追问 1~2 个问题(优先给 A/B/C 选项供挑选),不要一次抛出全部问题,也不要重复已明确的维度。\n'
|
||||
+ '3. 若信息已基本齐备:用一两句话总结并提示用户可点击「提交创作」。\n'
|
||||
+ '4. 输出必须严格分两段:第一段是对用户说的话(120 字以内,含必要时的问题);\n'
|
||||
+ '然后另起一行单独输出 ==REQ==;\n'
|
||||
+ '接着输出 10 行需求清单(仍未确定的项留空值,严禁编造),模板:\n'
|
||||
+ '- 一句话创意与选题:\n- 叙事形态:\n- 时长目标:\n- 情绪基调:\n- 受众:\n- 核心情节:\n- 结尾落点:\n- 结构性符号:\n- 广告植入:\n- 其他约束:';
|
||||
// 4) Dify blocking 调用
|
||||
const difyUrl = String(process.env.DIFY_URL || 'https://mydify.youmanvideo.com/v1').replace(/\/+$/, '');
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), 100000);
|
||||
let resp;
|
||||
try {
|
||||
resp = await fetch(difyUrl + '/chat-messages', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'blocking', user: 'mcn-work-shop' }),
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
} finally { clearTimeout(timer); }
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) return sendErr(502, 'AI 接口错误: ' + (data.message || resp.status));
|
||||
log('AI 需求打磨: ' + accountName);
|
||||
return dshOk(200, { answer: data.answer || '' });
|
||||
} catch (e) {
|
||||
return sendErr(500, '需求打磨失败: ' + e.message);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 分析产物同步入库(09-01 新增):扫描账号产出目录 → 写工作台库 mcn-plugin.db
|
||||
if (p === '/api/import/account' && req.method === 'POST') {
|
||||
let body = '';
|
||||
|
||||
Reference in new issue
Block a user