feat(workshop): AI创作弹窗v4-上传需求附件(doc/docx/txt/pdf)→AI打磨携带+创作上下文

* attachment-parser.js 零依赖(node:zlib)txt/docx/pdf纯文本提取;.doc明确400报错
* server.js 新增 POST /api/req/parse-attachment(20MB/30MB/3万字符限);
  /api/ai/clarify 入参增 attach 拼【附件素材】段(2万字符)
* data-pages.js confirmAiScript:req-input-box内左侧回形针(新ICON_PATHS paperclip)
  + 隐藏 file input + attach chip 行(paperclip+文件名+字数+×);
  空输入但有附件自动发引导语;sendTurn 携带 attach;
  buildCreatePrompt 增第5参 attachText→【附件素材】段并入创作 prompt
* style.css:req-attach-row/req-chip/chip-x/req-paper 系列样式;req-input-box padding-left 调
* 端到端 4 路径 API 验证 OK;CDP 9224 冒烟(纯Runtime+DataTransfer 注入 + 持久WS)全绿;
  真实 Dify 一轮打磨确认附件诉求已吸收进需求清单
* 测试样例 tmp/attach-tests/{req.txt,req.docx,old.doc}.b64(make_samples.py)
  9-02 16:31 已改中立内容,避免冒烟带真实账号名
This commit is contained in:
maogeigei committed 2026-09-02 16:31:57 +08:00
1 parent 047f2d0b9c
commit a92d84dcc9
5 files changed
+294 -13

No files matched your search

@@ -10,6 +10,7 @@ const os = require('os');
const { execFileSync } = require('child_process');
const { URL } = require('url');
const dsh = require('./dsh-data');
const { parseAttachment } = require('./scripts/attachment-parser');
// 09-01 默认端口由 8899 改为 8900:本机 8899 被小米 PC 管家 MiPCAudio.exe 系统服务占用(0.0.0.0 监听且自动复活),
// 此时 127.0.0.1:8899 绑定不生效 → 默认直接起在 8900;仍可 `node server.js <端口>` 显式换端口
@@ -657,7 +658,7 @@ const server = http.createServer((req, res) => {
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 || '{}');
const { accountName, turn, reqMd, history, attach } = JSON.parse(body || '{}');
if (!accountName || typeof accountName !== 'string') return sendErr(400, '缺少账号名');
if (!turn || typeof turn !== 'string' || !turn.trim()) return sendErr(400, '缺少用户输入');
// 1) 账号设定摘要(工作台库,与选题接口同源)
@@ -688,10 +689,12 @@ const server = http.createServer((req, res) => {
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 attachText = String(attach || '').trim().slice(0, 20000);
const query = '你是资深短视频编导,正在创作前通过多轮对话,帮达人「' + accountName + '」把创作需求打磨清楚。'
+ '你的职责只限需求澄清、整理与追问;严禁生成脚本正文、台词、分镜或完整故事。\n\n'
+ '【账号设定摘要】\n' + (personaText || '(暂无可用账号设定,按通用编导经验澄清)') + '\n'
+ (contentText ? '【账号定位】\n' + contentText + '\n' : '')
+ (attachText ? '【附件素材】(达人上传的需求文档/资料,打磨与最终创作都须充分参考;把其中的关键诉求/约束吸收进需求清单)\n' + attachText + '\n' : '')
+ '【当前需求清单】\n' + (reqMdText || '(空:等待用户先给出初步想法)') + '\n'
+ (histText ? '【对话历史】\n' + histText + '\n' : '')
+ '【达人本轮输入】\n' + turn.trim() + '\n\n'
@@ -727,6 +730,27 @@ const server = http.createServer((req, res) => {
return;
}
// 需求附件解析(09-02 新增):txt/docx/pdf → 纯文本(供 AI 打磨与创作上下文)
if (p === '/api/req/parse-attachment' && req.method === 'POST') {
let body = '';
req.on('data', (c) => { body += c; if (body.length > 30e6) req.destroy(); }); // 附件 base64 上限约 22MB 原文件
req.on('end', async () => {
try {
const { name, data } = JSON.parse(body || '{}');
if (!name || typeof name !== 'string' || !data || typeof data !== 'string') return sendErr(400, '缺少文件参数');
const buf = Buffer.from(data, 'base64');
if (buf.length > 20e6) return sendErr(413, '文件过大(上限 20MB)');
const r = parseAttachment(name, buf);
if (r.error) return sendErr(400, r.error);
const text = String(r.text || '').slice(0, 30000); // 打磨上下文上限 3 万字符
return dshOk(200, { text, chars: text.length, warn: r.warn || '', sourceChars: r.chars });
} catch (e) {
return sendErr(500, '附件解析失败: ' + e.message);
}
});
return;
}
// 分析产物同步入库(09-01 新增):扫描账号产出目录 → 写工作台库 mcn-plugin.db
if (p === '/api/import/account' && req.method === 'POST') {
let body = '';