Successfully replaced 3 block(s) in /root/.openclaw/workspace/versions/user_driven_final_v3.0.1/manhua_studio_web_8000/user_driven_task_system.py.

      ...
 2074                                     ratio="9:16",
 2075                                     duration=shot.get("duration_seconds", 5),
 2076                                     download_dir=str(videos_dir),
 2077                                     # v3.21: 镜头级风格预设驱动视频参数
-2078                                     **_merge_shot_video_params(shot),
+2078                                     **self._merge_shot_video_params(task_data, shot),
 2079                             except Exception as img_err:
 2080                                 # 图生视频失败(如敏感内容),降级到文生视频
 2081                                 if "SensitiveContent" in str(img_err) or "sensitive" in str(img_err).lower():
 2082                                     logger.warning(f"  ⚠ 图生视频触发敏感内容检测,降级到文生视频: 镜头{shot_id}")
      ...
 2763                                     model=_video_model if _video_model and _video_model != "auto" else None,
 2764                                     duration=sh.get("duration_seconds", 5),
 2765                                     download_dir=str(videos_dir),
 2766                                     # v3.21: 镜头级风格预设驱动视频参数
-2767                                     **_merge_shot_video_params(sh),
+2767                                     **self._merge_shot_video_params(task_data, sh),
 2768                                 )
 2769                             else:
 2770                                 result = generate_video(
 2771                                     prompt=vp,
 2772                                     model=_video_model if _video_model and _video_model != "auto" else None,
 2773                                     duration=sh.get("duration_seconds", 5),
 2774                                     download_dir=str(videos_dir),
 2775                                     # v3.21: 镜头级风格预设驱动视频参数
-2776                                     **_merge_shot_video_params(sh),
+2776                                     **self._merge_shot_video_params(task_data, sh),
 2777                                 )
 2778
 2779                             # 更新对应shot的视频信息
 2780                             for es in existing_shots:
      ...
 3280                         model=task_data.get("video_model") if task_data.get("video_model") != "auto" else None,
 3281                         duration=(original_shot or {}).get("duration_seconds", 5),
 3282                         download_dir=str(videos_dir),
 3283                         # v3.21: 镜头级风格预设驱动视频参数
-3284                         **_merge_shot_video_params(original_shot or {}),
+3284                         **self._merge_shot_video_params(task_data, original_shot or {}),
 3285                     )
 3286                 except Exception as img_err:
 3287                     if "SensitiveContent" in str(img_err) or "sensitive" in str(img_err).lower():
 3288                         logger.warning(f"单镜头重新生成: 图生视频触发敏感内容检测,降级到文生视频")
      ...


好的。现在我来处理前端的改动。这是最复杂的部分。我需要：

1. **需求1**：将负面提示词按类型（角色/场景/镜头）分组，并显示在对应的正面提示词参数下方
2. **需求2**：为每个镜头卡片添加镜头级视频参数（摄像机类型/转场）
3. **需求3**：将风格预设和图片参数从左侧面板移到步骤2区域
4. **需求4**：已通过扩展 `style_presets.py` 处理

让我来更新 HTML 和 JS：


Successfully replaced 1 block(s) in /root/.openclaw/workspace/versions/user_driven_final_v3.0.1/manhua_studio_web_8000/static/user_driven_coordinator_final.html.

     ...
   2 <html lang="zh-CN">
   3 <head>
   4     <meta charset="UTF-8">
   5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  6     <title>用户主导的慢剧生成系统 v3.20</title>
+  6     <title>用户主导的慢剧生成系统 v3.21</title>
   7     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
   8     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
   9     <style>
  10         body { background-color: #f8f9fa; padding-top: 20px; }
     ...


edit

版本 3.21 - 镜头级参数+8种风格预设

版本 3.18 - 参数调优+一致性控制

版本 3.21 - 镜头级参数+8种风格预设

版本 3.18 - 参数调优+一致性控制


...
 183
 184

 185

 186
用户主导的慢剧生成系统

-187
版本 3.18 - 参数调优+一致性控制

+187
版本 3.21 - 镜头级参数+8种风格预设

 188             您控制每一步流程，随时修改或重新生成结果
 189

 190
 191
     ...


现在，将风格预设和图片参数从左侧面板移动到步骤2（写入步骤）。我需要从左侧面板中移除风格预设部分，并将其添加到写入步骤区域：

<!-- v3.21: 风格预设和图片参数已移到步骤2创作区域 -->

                                    🎨 风格预设
                                    <select class="form-select form-select-sm" id="cfgStylePreset">
                                        <option value="">自动推荐</option>
                                    </select>

                                        ⚙ 高级视觉参数

选择风格后显示参数

<!-- v3.21: 风格预设和图片参数已移到步骤2创作区域 -->


...
 265                                         <input type="checkbox" class="form-check-input cfg-ch" value="微信视频号" id="cfgChWX"> 视频号
 266

 267

 268
-269
-270

-271                                     🎨 风格预设
-272                                     <select class="form-select form-select-sm" id="cfgStylePreset">
-273                                         <option value="">自动推荐</option>
-274                                     </select>
-275

-276

+269
 277
-278
-279

-280
-281                                         ⚙ 高级视觉参数
-282
-283

-284

-285
选择风格后显示参数

-286

-287

-288

-289
 290                                 <button class="btn btn-sm btn-primary w-100" id="btnSaveConfig">保存配置</button>
 291

 292

 293
     ...


现在将风格预设面板添加到步骤2（写入步骤）区域。我需要在写入步骤的头部和操作按钮之后添加它：

                        <button class="btn btn-info" id="btnExecuteWrite" disabled>执行创作</button>
                        <button class="btn btn-outline-info" id="btnModifyWrite" disabled>修改</button>
                        <button class="btn btn-outline-success" id="btnReviewWrite" disabled>审核</button>
                        <button class="btn btn-outline-info" id="btnRegenerateWrite" disabled style="display:none">重新生成</button>
                        <button class="btn btn-success" id="btnConfirmDraft" disabled style="display:none">确认草稿并生成场景图</button>
                        <button class="btn btn-outline-info" id="btnViewWrite" disabled>查看结果</button>

风格预设与视觉参数

                                 展开/收起

                                🎨 风格预设
                                <select class="form-select form-select-sm" id="cfgStylePreset">
                                    <option value="">自动推荐</option>
                                </select>

选择风格后显示参数


...
 370                         <button class="btn btn-outline-info" id="btnRegenerateWrite" disabled style="display:none">重新生成</button>
 371                         <button class="btn btn-success" id="btnConfirmDraft" disabled style="display:none">确认草稿并生成场景图</button>
 372                         <button class="btn btn-outline-info" id="btnViewWrite" disabled>查看结果</button>
 373

+374
+375

+376

+377
风格预设与视觉参数

+378
+379                                  展开/收起
+380
+381

+382

+383

+384                                 🎨 风格预设
+385                                 <select class="form-select form-select-sm" id="cfgStylePreset">
+386                                     <option value="">自动推荐</option>
+387                                 </select>
+388

+389

+390

+391

+392

+393
选择风格后显示参数

+394

+395

+396

 374

 375

 376

 377

     ...


现在更新 JS 缓存清除：


现在最重要的部分是——更新 JS。我需要做以下几点：

1. **需求1**：重新组织 `onStylePresetChange` 函数，将负面提示词按类型（角色/场景/镜头）分组，并与相应的正面提示词参数一起显示
2. **需求2**：为每个镜头卡片添加镜头级摄像机/转场控制
3. **需求3**：风格预设已移至步骤2（在 HTML 中完成）

让我更新 JS：

// 选择风格预设后渲染参数滑块
// v3.21: 负面提示词按类型分组显示在对应正面参数下面
async function onStylePresetChange(presetId) {
    const desc = document.getElementById('stylePresetDesc');
    const sliders = document.getElementById('styleParamsSliders');

    if (!presetId) {
        if (desc) desc.textContent = '将根据描述自动推荐风格';
        if (sliders) sliders.innerHTML = '
选择风格后显示参数
';
        currentStylePreset = null;
        currentStyleParams = {};
        return;
    }

    try {
        const r = await fetch(`${API_BASE}/style-presets/${presetId}`);
        const d = await r.json();
        if (!d.success) return;

        const preset = d.preset;
        currentStylePreset = presetId;
        currentStyleParams = {};

        if (desc) desc.textContent = preset.description;

        // v3.21: 分组渲染 - 角色图参数(含角色负面) / 场景图参数(含场景负面) / 镜头图参数(含镜头负面) / 视频参数
        let htmlCharImg = '';   // 角色图正面参数
        let htmlCharNeg = '';   // 角色负面提示词
        let htmlSceneImg = '';  // 场景图正面参数
        let htmlSceneNeg = '';  // 场景负面提示词
        let htmlShotImg = '';   // 镜头图正面参数
        let htmlShotNeg = '';   // 镜头负面提示词
        let htmlVideo = '';     // 视频参数
        let datalistId = 0;

        for (const [pname, pdef] of Object.entries(preset.params)) {
            currentStyleParams[pname] = pdef.default;
            const isNeg = pname.startsWith('negative_prompt_');
            const isVideo = ['slow_motion_ratio','camera_move_speed','shot_duration',
                             'dynamic_amplitude','camera_type','transition_style','frame_rate'].includes(pname);

            // 构建参数HTML块
            function buildParamBlock(pname, pdef, isDatalist) {
                datalistId++;
                if (pdef.allow_custom && pdef.options) {
                    const dlId = `dl_${pname}_${datalistId}`;
                    let opts = pdef.options.map(o => `<option value="${o}">`).join('');
                    return `

${pdef.label} (可选项或自定义)

                        <input type="text" class="form-control form-control-sm" id="sp_${pname}"
                            list="${dlId}" value="${pdef.default}"
                            oninput="onStyleParamChange('${pname}', this.value)" style="font-size:0.9em">
                        <datalist id="${dlId}">${opts}</datalist>

${pdef.desc}

`;
                } else if (pdef.min !== null && pdef.min !== undefined) {
                    return `

                            ${pdef.label}
                            ${pdef.default}

                        <input type="range" class="form-range" style="height:6px"
                            id="sp_${pname}"
                            min="${pdef.min}" max="${pdef.max}" step="${pdef.step}" value="${pdef.default}"
                            oninput="onStyleParamChange('${pname}', this.value)">

                            ${pdef.min}${pdef.max}

${pdef.desc}

`;
                } else if (pdef.options) {
                    return `

${pdef.label}

                        <select class="form-select form-select-sm" id="sp_${pname}"
                            onchange="onStyleParamChange('${pname}', this.value)" style="font-size:0.9em">
                            ${pdef.options.map(o => `<option value="${o}" ${o === pdef.default ? 'selected' : ''}>${o}</option>`).join('')}
                        </select>

${pdef.desc}

`;
                } else {
                    return `

${pdef.label}

                        <input type="text" class="form-control form-control-sm" id="sp_${pname}"
                            value="${pdef.default}"
                            oninput="onStyleParamChange('${pname}', this.value)" style="font-size:0.9em">

${pdef.desc}

`;
                }
            }

            // v3.21: 按图片类型分组
            if (isNeg) {
                const negBlock = `

${pdef.img_type === 'character' ? '👤' : pdef.img_type === 'scene' ? '🏞' : '🎬'} ${pdef.label}

                    <textarea class="form-control form-control-sm" id="sp_${pname}" rows="2"
                        oninput="onStyleParamChange('${pname}', this.value)"
                        style="font-size:0.85em;resize:vertical">${pdef.default}</textarea>

${pdef.desc}

`;
                if (pdef.img_type === 'character') htmlCharNeg += negBlock;
                else if (pdef.img_type === 'scene') htmlSceneNeg += negBlock;
                else if (pdef.img_type === 'shot') htmlShotNeg += negBlock;
            } else if (isVideo) {
                htmlVideo += buildParamBlock(pname, pdef);
            } else {
                // 图片参数 - 按类型分: 一致性/风格/质量/细节/色调/光影属于角色图, 场景细节属于场景图
                // 简化: 所有图片正面参数放角色组, 场景/镜头组只有负面词
                const block = buildParamBlock(pname, pdef);
                htmlCharImg += block;
            }
        }

        // v3.21: 组装 - 角色图(正面+负面) / 场景图(负面) / 镜头图(负面) / 视频参数
        let html = '';
        // 角色图参数组
        html += `<div class="mb-3"><div style="font-weight:600;border-bottom:1px solid #dee2e6;padding-bottom:2px;margin-bottom:4px">👤 角色图参数</div>${htmlCharImg}`;
        if (htmlCharNeg) html += `<div class="mt-1 p-1 border rounded" style="background:#fff5f5;border-color:#f5c6cb!important">${htmlCharNeg}</div>`;
        html += `</div>`;
        // 场景图参数组
        if (htmlSceneNeg) {
            html += `<div class="mb-3"><div style="font-weight:600;border-bottom:1px solid #dee2e6;padding-bottom:2px;margin-bottom:4px">🏞 场景图参数</div>`;
            html += `<div class="p-1 border rounded" style="background:#f5fff5;border-color:#c3e6cb!important">${htmlSceneNeg}</div>`;
            html += `</div>`;
        }
        // 镜头图参数组
        if (htmlShotNeg) {
            html += `<div class="mb-3"><div style="font-weight:600;border-bottom:1px solid #dee2e6;padding-bottom:2px;margin-bottom:4px">🎬 镜头图参数</div>`;
            html += `<div class="p-1 border rounded" style="background:#f5f5ff;border-color:#b8daff!important">${htmlShotNeg}</div>`;
            html += `</div>`;
        }
        // 视频参数组
        if (htmlVideo) {
            html += `

🎥 视频参数 (全局默认,可在每个镜头中单独设置)
${htmlVideo}
`;
        }

        if (sliders) sliders.innerHTML = html;

    } catch(e) { console.warn('加载风格预设详情失败:', e); }
}

// 选择风格预设后渲染参数滑块
async function onStylePresetChange(presetId) {
    const desc = document.getElementById('stylePresetDesc');
    const sliders = document.getElementById('styleParamsSliders');

    if (!presetId) {
        if (desc) desc.textContent = '将根据描述自动推荐风格';
        if (sliders) sliders.innerHTML = '
选择风格后显示参数
';
        currentStylePreset = null;
        currentStyleParams = {};
        return;
    }

    try {
        const r = await fetch(`${API_BASE}/style-presets/${presetId}`);
        const d = await r.json();
        if (!d.success) return;

        const preset = d.preset;
        currentStylePreset = presetId;
        currentStyleParams = {};

        if (desc) desc.textContent = preset.description;

        // v3.20: 分组渲染 - 图片参数 / 负面提示词 / 视频参数
        let htmlImg = '';
        let htmlNeg = '';
        let htmlVideo = '';
        let negDatalistId = 0;

        for (const [pname, pdef] of Object.entries(preset.params)) {
            currentStyleParams[pname] = pdef.default;
            const isNeg = pname.startsWith('negative_prompt_');
            const isVideo = ['slow_motion_ratio','camera_move_speed','shot_duration',
                             'dynamic_amplitude','camera_type','transition_style','frame_rate'].includes(pname);

            if (isNeg) {
                // v3.20: 负面提示词 - textarea, 和提示词放在一起
                const icon = pdef.img_type === 'character' ? '👤' : pdef.img_type === 'scene' ? '🏞' : '🎬';
                htmlNeg += `

${icon} ${pdef.label}

                    <textarea class="form-control form-control-sm" id="sp_${pname}" rows="2"
                        oninput="onStyleParamChange('${pname}', this.value)"
                        style="font-size:0.85em;resize:vertical">${pdef.default}</textarea>

${pdef.desc}

`;
            } else if (pdef.allow_custom && pdef.options) {
                // v3.20: 可选+可输入 (运镜/转场) - input + datalist
                const dlId = `dl_${pname}_${++negDatalistId}`;
                let opts = pdef.options.map(o => `<option value="${o}">`).join('');
                htmlVideo += `

${pdef.label} (可选项或自定义)

                    <input type="text" class="form-control form-control-sm" id="sp_${pname}"
                        list="${dlId}" value="${pdef.default}"
                        oninput="onStyleParamChange('${pname}', this.value)" style="font-size:0.9em">
                    <datalist id="${dlId}">${opts}</datalist>

${pdef.desc}

`;
            } else if (pdef.min !== null && pdef.min !== undefined) {
                // 数值滑块
                const target = isVideo ? htmlVideo : htmlImg;
                const block = `

                        ${pdef.label}
                        ${pdef.default}

                    <input type="range" class="form-range" style="height:6px"
                        id="sp_${pname}"
                        min="${pdef.min}" max="${pdef.max}" step="${pdef.step}" value="${pdef.default}"
                        oninput="onStyleParamChange('${pname}', this.value)">

                        ${pdef.min}${pdef.max}

${pdef.desc}

`;
                if (isVideo) htmlVideo += block; else htmlImg += block;
            } else if (pdef.options) {
                // 选项下拉(不允许自定义)
                const block = `

${pdef.label}

                    <select class="form-select form-select-sm" id="sp_${pname}"
                        onchange="onStyleParamChange('${pname}', this.value)" style="font-size:0.9em">
                        ${pdef.options.map(o => `<option value="${o}" ${o === pdef.default ? 'selected' : ''}>${o}</option>`).join('')}
                    </select>

${pdef.desc}

`;
                if (isVideo) htmlVideo += block; else htmlImg += block;
            } else {
                // 普通文本
                const block = `

${pdef.label}

                    <input type="text" class="form-control form-control-sm" id="sp_${pname}"
                        value="${pdef.default}"
                        oninput="onStyleParamChange('${pname}', this.value)" style="font-size:0.9em">

${pdef.desc}

`;
                if (isVideo) htmlVideo += block; else htmlImg += block;
            }
        }

        // 组装: 图片参数 + 负面提示词 + 视频参数
        let html = '';
        if (htmlImg) html += `<div class="mb-2"><div style="font-weight:600;border-bottom:1px solid #dee2e6;padding-bottom:2px;margin-bottom:4px">🖼 图片参数</div>${htmlImg}</div>`;
        if (htmlNeg) html += `<div class="mb-2"><div style="font-weight:600;border-bottom:1px solid #dee2e6;padding-bottom:2px;margin-bottom:4px">🚫 负面提示词</div>${htmlNeg}</div>`;
        if (htmlVideo) html += `<div class="mb-2"><div style="font-weight:600;border-bottom:1px solid #dee2e6;padding-bottom:2px;margin-bottom:4px">🎬 视频参数</div>${htmlVideo}</div>`;

        if (sliders) sliders.innerHTML = html;

    } catch(e) { console.warn('加载风格预设详情失败:', e); }
}


Successfully replaced 1 block(s) in /root/.openclaw/workspace/versions/user_driven_final_v3.0.1/manhua_studio_web_8000/static/user_driven_coordinator.js.


Context overflow: estimated context size exceeds safe threshold during tool loop.