AI创作弹窗 v3:需求维度块+一体输入条(图标钮·去取消)

按用户 UI 要求收紧弹窗体验:

HTML(data-pages.js confirmAiScript):
- 弹窗加高 width 720→760 + height:min(840px,92vh) flex column
- 需求区改为 req-field 维度块(rf-title 标题在上,rf-val 内容在下),CSS 2 列 grid
- 输入条一体化:req-bar = [reqCreate ✓图标] [req-input-box 圆角胶囊(内嵌 textarea + 右侧 reqSend ↑图标)]
- 取消按钮整段删除(保留右上 × 关闭);提交创作改用 #reqCreate id + svg check 图标
- 附 ICON_PATHS 加 arrow-up/check 路径

CSS(style.css .req-* 段全重写 L209-272):
- .req-modal flex column gap:10px
- .req-sheet grid 2 列 + .req-field 卡片底(empty 浅色)
- .req-input-box 圆角 12 + focus-within halo + 无边框 textarea + 34x34 primary 蓝圆发送钮
- .req-send busy 状态:disabled 灰 + 14x14 白色旋转 spinner(@keyframes req-spin)
- .req-create 44x44 描边 ✓ 图标钮(hover 浅蓝底)

附带红利:弹窗提交按钮改 id 后,与账号列表 [data-create] 同名选择器歧义自然消失(之前 CDP 冒烟需 .req-modal [data-create] 限定,现改 #reqCreate 直接命中)

冒烟 cdp-req-v3-smoke.mjs 24s 一轮真打磨+提交捕获全绿:fields:10/empty:10/modalH:784/sendIsIcon:true/createIsIcon:true/hasCancel:false + 提交{label:创作指令, skills:[短视频脚本创作], hasPersona:true, reqRows:6, modalClosed:true}
This commit is contained in:
maogeigei committed 2026-09-02 16:11:00 +08:00
1 parent 7984520728
commit e8fcb02be4
5 files changed
+79 -35

No files matched your search

+13
View File
@@ -191,3 +191,16 @@
- **截图产出** `tmp/req-1-empty.png` / `req-2-after-turn1.png` / `req-3-after-turn2.png` + 报告 `tmp/req-dialog-report.json`
- **工作台页面** 按"改完必重开"约定 present_files `?v=20260902-req#/accounts`
- **经验沉淀**:①账号列表/弹窗内同名 `data-create` 触发选择器歧义——统一加 modal 作用域或换名(如 `data-confirm-create`)②CDP Page.navigate 同 URL 不可靠——浏览器/CDP 跨会话残留必须强制 reload + 显式等清空;冒烟脚本应假设首状态脏
## AI创作弹窗 UI v3 升级(16:00-16:12,用户要求)
- **需求原话**:①弹窗高度可以增加 ②需求展示部分 维度作为标题 内容在标题下面的展现形式 ③发送按钮改成图标 和输入框真正合在一个分块中(参考 WorkBuddy 输入框)④提交创作改为图标 放在输入框功能区域左边 ⑤不需要取消按钮(已经有关闭按钮了)
- **实现**(data-pages.js confirmAiScript L219-251 HTML 重组 + style.css L209-272 整段重写 + app.js ICON_PATHS 加 'arrow-up'/'check'):
- 弹窗高度:`.req-modal` width 720→760,**height: min(840px, 92vh)** + display:flex/flex-direction:column,gap:10px;title-row/head/sheet/bar 各自 flex none
- 需求区:`.req-sheet` grid 2 列(gap 8×10px),每块 `.req-field` = `.rf-title` 标题(12px 灰小字加粗)在上 + `.rf-val` 内容(14px)在下;卡片底 #f6f8fb 圆角 10 padding 7 11,empty 时浅 #fafbfd + rf-val 灰 #c6ccd6
- 输入条:`.req-bar` flex row `[#reqCreate] [.req-input-box]`,input-box = 圆角 12 胶囊(border+focus-within 主色描边+halo),内嵌无边框 textarea(透明)+ 右侧 34×34 primary 蓝圆发送钮(svg `arrow-up` ↑);busy 时钮 disabled 灰 + 中心 14×14 白色 spinner(@keyframes req-spin)
- 提交创作:`#reqCreate` 44×44 描边圆角方块,蓝 ✓ 图标(svg `check`),hover 浅蓝底
- 关闭:右上 `data-x` × 保留;**取消按钮整段删除**(HTML data-cancel + 监听器 + .modal-actions 容器)
- **附带红利**:弹窗提交按钮改 id 后,**账号列表 [data-create] 与弹窗内提交同名选择器歧义自然消失**(CDP 冒烟脚本④ selector 同步改 `#reqCreate`,旧 `.req-modal [data-create]` 写法作废)
- **冒烟脚本 `tmp/cdp-req-v3-smoke.mjs`**(CDP 9224 24s 一轮真打磨+提交捕获)全绿:①fields:10/empty:10/title="AI创作 · 旧梦留声机"/hasBar/createIsIcon/sendIsIcon/hasCancel:false/modalH:784/modalW:760 ②首轮 filled 0→6,首块"一句话创意与选题"=「离异母亲带娃整理房间,娃越帮越忙的搞笑反转日常」③提交 label=创作指令/skills=['短视频脚本创作']/hasPersona:true/reqRows:6/modalClosed:true
- **截图** `tmp/reqv3-1-empty.png` / `reqv3-2-after-turn1.png` / `reqv3-3-after-create.png` + 报告 `tmp/req-v3-report.json`
- **工作台重开** `?v=20260902-v3#/accounts`
+1
View File
@@ -18,6 +18,7 @@
- **关键设计**:每轮重发 reqMd + 最近 2 轮 history 做无状态上下文;AI 任何字段都允许更新、严禁生成脚本正文;reqLines 仅取非空字段 + 「未填由 AI 按人设自决」提示
- **冒烟 CDP 坑**(3 轮迭代定稿):①querySelector('[data-create]') 命中背后列表按钮(弹窗内外同名 data-create)→ 必须 `.req-modal [data-create]` 限定;②Page.navigate 同 URL 不真正 reload 致旧弹窗叠加 → `?v=Date.now()` 强制 reload + `waitFor('!document.querySelector(".req-modal")')` 等清场
- **冒烟脚本** `tmp/cdp-req-dialog-smoke.mjs`(4 步端到端 CDP 9224)= 结构/双轮真实 Dify 打磨/需求框递增/提交捕获(拦截 executeTask 验 label+skills+persona+reqRows+modalClosed),端到端 ~50s 全绿
- **v3 UI 升级(09-02,弹窗体验收紧)**:①弹窗高 720→760+height:min(840px,92vh) flex column;②需求区 2 列 grid 维度块(rf-title 标题在 rf-val 内容上方,empty 灰显);③输入条一体化:`.req-input-box` 圆角胶囊内嵌无边框 textarea + 右侧 primary 蓝圆发送钮(svg `arrow-up`),busy 时 disabled 灰+白色旋转 spinner(@keyframes req-spin);④提交创作=44×44 描边圆角 ✓ 图标钮(svg `check`),置于 `.req-bar` 左侧;⑤取消按钮删除(仅留右上 ×);**附带红利**:提交按钮改 id 后与列表 [data-create] 同名歧义自然消失
## 创作技能关键规则(权威源:SKILL.md + references/创作流程规范.md)
1. 三态创作:⚡自动(默认)=静默执行+只落最终交付物;🤝共创=+每确认门询问;🛠️开发=+每步全落盘。7确认门不可合并;角色边界(分析不做创作)
@@ -166,6 +166,8 @@ const ICON_PATHS = {
'image': '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
'arrow-left': '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
'x': '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
'arrow-up': '<path d="m5 12 7-7 7 7"/><path d="M12 19V5"/>',
'check': '<path d="M20 6 9 17l-5-5"/>',
'check-square': '<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>',
'trash': '<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>',
'zap': '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
@@ -218,31 +218,29 @@ async function confirmAiScript(accountName, btn) {
const mask = document.createElement('div'); mask.className = 'modal-mask';
mask.innerHTML = `<div class="modal req-modal">
<div class="modal-title-row"><span>AI创作 · ${esc(accountName)}</span><button class="btn" data-x title="关闭">${svgIcon('x', 14)}</button></div>
<div class="req-head">在下方输入想法,AI 会边聊边把需求整理到「创作需求」框里;聊够了点「提交创作」。</div>
<div class="modal-title-row"><span>AI创作 · ${esc(accountName)}</span><button class="btn btn-icon" data-x title="关闭">${svgIcon('x', 16)}</button></div>
<div class="req-head">在下方输入想法,AI 会边聊边把需求整理到上方「创作需求」里;打磨满意后,点输入框左侧的 ✓ 提交创作。</div>
<div class="req-sheet" id="reqSheet"></div>
<div class="req-dialog" id="reqDialog"></div>
<div class="req-input-row">
<textarea id="reqInput" class="req-input" rows="2" placeholder="说说你的想法,例如:想拍一个亲子互动的搞笑短剧…&#10;(Enter 发送,Shift+Enter 换行)"></textarea>
<button type="button" class="primary req-send" id="reqSend">发送</button>
</div>
<div class="modal-actions">
<button class="ghost" data-cancel>取消</button>
<button class="primary" data-create>提交创作</button>
<div class="req-bar">
<button type="button" class="req-create" id="reqCreate" title="提交创作(生成完整脚本)">${svgIcon('check', 20)}</button>
<div class="req-input-box">
<textarea id="reqInput" class="req-input" rows="1" placeholder="说说你的想法,例如:想拍一个亲子互动的搞笑短剧…&#10;(Enter 发送,Shift+Enter 换行)"></textarea>
<button type="button" class="req-send" id="reqSend" title="发送">${svgIcon('arrow-up', 18)}</button>
</div>
</div>
</div>`;
document.body.appendChild(mask);
const close = () => mask.remove();
mask.addEventListener('click', (e) => { if (e.target === mask) close(); });
mask.querySelector('[data-x]').addEventListener('click', close);
mask.querySelector('[data-cancel]').addEventListener('click', close);
const q = (s) => mask.querySelector(s);
/* —— 需求框渲染:10 行字段,空值灰显 —— */
/* —— 需求框渲染:10 个维度块,维度名作标题、内容在下方 —— */
const renderSheet = () => {
const rows = REQ_FIELDS.map(([k, label]) => {
const v = String(w[k] || '').trim();
return `<div class="req-sheet-row${v ? '' : ' empty'}"><span class="rs-label">${label}</span><span class="rs-val">${v ? esc(v) : '(待定 · 由 AI 按人设自决)'}</span></div>`;
return `<div class="req-field${v ? '' : ' empty'}"><div class="rf-title">${label}</div><div class="rf-val">${v ? esc(v) : '(待定 · 由 AI 按人设自决)'}</div></div>`;
}).join('');
q('#reqSheet').innerHTML = rows;
};
@@ -256,10 +254,10 @@ async function confirmAiScript(accountName, btn) {
: `<div class="req-dialog-empty">在这里输入一个初步想法,AI 会引导你补齐形态 / 时长 / 情绪 / 受众 / 情节 / 结尾 / 植入等需求;每条输入都会即时更新上方的创作需求框。</div>`;
el.scrollTop = el.scrollHeight;
};
const setBusy = (b, txt) => {
const setBusy = (b) => {
busy = b;
const s = q('#reqSend');
if (s) { s.disabled = b; s.textContent = b ? (txt || '整理中…') : '发送'; }
if (s) { s.disabled = b; s.title = b ? 'AI 整理中…' : '发送'; s.innerHTML = b ? '<span class="req-send-busy"></span>' : svgIcon('arrow-up', 18); }
};
/* —— 发送一轮:调 /api/ai/clarify(同步打磨,返回 reply + ==REQ== 段) —— */
const sendTurn = async (raw) => {
@@ -312,7 +310,7 @@ async function confirmAiScript(accountName, btn) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendTurn(inputEl.value); }
});
q('#reqSend').addEventListener('click', () => sendTurn(inputEl.value));
mask.querySelector('[data-create]').addEventListener('click', async () => {
q('#reqCreate').addEventListener('click', async () => {
if (busy) return;
await loadPersona();
const prompt = buildCreatePrompt(accountName, w, personaBlock(), pHas);
@@ -206,23 +206,28 @@ main { height: calc(100vh - 54px); }
color: var(--dim); font-size: 13px; word-break: break-all;
padding: 0 2px 4px; margin-bottom: 6px;
}
/* AI创作 弹窗(09-02 v2):需求框 + 对话流 + 输入框 对话式打磨 */
.req-modal { width: 720px; max-width: 96vw; }
.req-head { color: var(--dim); font-size: 13px; margin-bottom: 10px; line-height: 1.6; }
/* AI创作 弹窗(09-02 v3):需求维度块 + 对话流 + 一体输入条(发送图标内嵌,参考 WorkBuddy 输入框) */
.req-modal {
width: 760px; max-width: 96vw; height: min(840px, 92vh); box-sizing: border-box;
display: flex; flex-direction: column; gap: 10px;
}
.req-modal .modal-title-row { flex: none; margin-bottom: 0; }
.req-head { flex: none; color: var(--dim); font-size: 13px; line-height: 1.6; }
/* 需求区:10 个维度块(维度名=标题在上,内容在下),双列网格,超高内部滚动 */
.req-sheet {
border: 1px solid var(--border); border-radius: 8px; background: #fbfcfe;
max-height: 218px; overflow: auto; padding: 2px 12px;
flex: none; display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px;
max-height: 290px; overflow: auto; align-content: start; padding: 2px;
}
.req-sheet-row {
display: flex; gap: 10px; padding: 6px 0; border-bottom: 1px dashed #eceff3;
font-size: 14px; line-height: 1.55;
.req-field {
background: #f6f8fb; border: 1px solid #edf0f4; border-radius: 10px;
padding: 7px 11px 8px; min-height: 42px;
}
.req-sheet-row:last-child { border-bottom: none; }
.req-sheet-row.empty .rs-val { color: #c6ccd6; }
.rs-label { flex: none; width: 108px; color: var(--dim); font-size: 13px; padding-top: 1px; }
.rs-val { flex: 1; word-break: break-word; }
.req-field.empty { background: #fafbfd; }
.rf-title { font-size: 12px; color: #7a8494; font-weight: 600; letter-spacing: .2px; margin-bottom: 2px; }
.rf-val { font-size: 14px; color: var(--text); line-height: 1.5; word-break: break-word; white-space: pre-wrap; }
.req-field.empty .rf-val { color: #c6ccd6; font-size: 13px; }
.req-dialog {
margin-top: 10px; max-height: 172px; overflow: auto;
flex: 1 1 auto; min-height: 130px; overflow: auto;
display: flex; flex-direction: column; gap: 6px; padding: 2px;
}
.req-dialog-empty { color: var(--dim); font-size: 13px; line-height: 1.7; padding: 2px; }
@@ -235,14 +240,39 @@ main { height: calc(100vh - 54px); }
}
.req-msg.mine .req-bubble { background: var(--primary); color: #fff; border-bottom-right-radius: 3px; }
.req-msg.ai .req-bubble { background: #f0f2f5; color: var(--text); border-bottom-left-radius: 3px; }
.req-input-row { display: flex; gap: 8px; margin-top: 10px; align-items: flex-end; }
.req-input {
flex: 1; border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
font-size: 14px; font-family: inherit; line-height: 1.5; box-sizing: border-box;
resize: vertical; min-height: 44px; max-height: 132px;
/* 底部功能条:提交创作图标(左)+ 一体输入条(圆角块内嵌 textarea + 右侧发送圆钮) */
.req-bar { flex: none; display: flex; align-items: flex-end; gap: 10px; }
.req-create {
flex: none; width: 44px; height: 44px; border-radius: 12px;
border: 1px solid var(--border); background: #fff; color: var(--primary);
display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.req-input:focus { outline: none; border-color: var(--primary); }
.req-send { min-width: 68px; }
.req-create:hover { border-color: var(--primary); background: var(--primary-soft); }
.req-input-box {
flex: 1; display: flex; align-items: flex-end; gap: 6px;
border: 1px solid var(--border); border-radius: 12px; padding: 5px 5px 5px 13px;
background: #fff; transition: border-color .15s, box-shadow .15s;
}
.req-input-box:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.req-input {
flex: 1; border: none; outline: none; resize: none; background: transparent;
font-size: 14px; font-family: inherit; line-height: 1.5;
min-height: 30px; max-height: 100px; padding: 6px 0 5px;
}
.req-input::placeholder { color: #a6aebc; }
.req-send {
flex: none; width: 34px; height: 34px; border-radius: 9px; border: none;
background: var(--primary); color: #fff; cursor: pointer;
display: flex; align-items: center; justify-content: center;
}
.req-send:hover:not(:disabled) { background: #2a5fd0; }
.req-send:disabled { background: #c7cedb; cursor: default; }
.req-send-busy {
width: 14px; height: 14px; border-radius: 50%;
border: 2px solid rgba(255,255,255,.45); border-top-color: #fff;
display: inline-block; animation: req-spin .8s linear infinite;
}
@keyframes req-spin { to { transform: rotate(360deg); } }
.root-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.root-item {
display: flex; align-items: center; gap: 8px;