note/zyp/newaishorts.html
2025-11-19 10:16:05 +08:00

638 lines
30 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>短视频脚本创作AI智能体 (多智能体版)</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Noto Sans SC', sans-serif; background-color: #f4f7f6; }
.agent-step { display: none; }
.agent-step.active { display: block; }
.step-indicator { transition: all 0.4s ease; }
.step-indicator.active { background-color: #2563EB; color: white; }
.step-indicator.completed { background-color: #16A34A; color: white; }
.option-card { transition: all 0.2s ease-in-out; border: 2px solid #E5E7EB; }
.option-card:hover { transform: translateY(-4px); box-shadow: 0 4px 10px rgba(0,0,0,0.05); border-color: #3B82F6; }
.option-card.selected { border-color: #2563EB; box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); transform: translateY(-4px); }
.loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.script-section h3 { font-size: 1.1rem; font-weight: bold; color: #1F2937; border-bottom: 2px solid #3B82F6; padding-bottom: 0.5rem; margin-bottom: 1rem; }
.script-dialogue { background-color: #F3F4F6; padding: 0.75rem; border-radius: 0.5rem; font-style: italic; }
#prompt-display { background-color: #111827; color: #d1d5db; padding: 1rem; border-radius: 0.5rem; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; font-size: 0.875rem; max-height: 300px; overflow-y: auto; }
.loading-overlay { position: absolute; inset: 0; background-color: rgba(255, 255, 255, 0.8); z-index: 10; display: flex; flex-direction: column; align-items: center; justify-content: center; }
</style>
</head>
<body class="text-gray-800">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<header class="text-center mb-8">
<h1 class="text-3xl md:text-4xl font-bold text-gray-900">短视频脚本创作AI智能体</h1>
<p class="text-md text-gray-600 mt-2">与AI专家团队协作四步生成爆款脚本</p>
</header>
<!-- Stepper -->
<div class="w-full px-8 mb-12">
<div class="relative flex items-center justify-between">
<div class="absolute left-0 top-1/2 -translate-y-1/2 w-full h-0.5 bg-gray-300"></div>
<div id="progress-bar" class="absolute left-0 top-1/2 -translate-y-1/2 h-0.5 bg-blue-600 transition-all duration-500" style="width: 0%;"></div>
<div class="relative flex justify-between w-full">
<div id="step-indicator-1" class="step-indicator active w-10 h-10 rounded-full bg-white border-2 border-gray-300 flex items-center justify-center font-bold text-lg" title="策略">1</div>
<div id="step-indicator-2" class="step-indicator w-10 h-10 rounded-full bg-white border-2 border-gray-300 flex items-center justify-center font-bold text-lg" title="钩子">2</div>
<div id="step-indicator-3" class="step-indicator w-10 h-10 rounded-full bg-white border-2 border-gray-300 flex items-center justify-center font-bold text-lg" title="大纲">3</div>
<div id="step-indicator-4" class="step-indicator w-10 h-10 rounded-full bg-white border-2 border-gray-300 flex items-center justify-center font-bold text-lg" title="脚本">4</div>
</div>
</div>
</div>
<main id="agent-workflow" class="relative">
<div id="main-loader" class="loading-overlay hidden">
<div class="loader"></div>
<p class="text-lg font-semibold text-gray-700 mt-4">AI正在思考中...</p>
</div>
<!-- Step 1: Topic Input -->
<div id="step-1" class="agent-step active bg-white p-8 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-1">你好,我是 <span class="text-blue-600">策略师</span></h2>
<p class="text-gray-600 mb-6">我们从一个核心想法开始。告诉我你想做什么样的视频?</p>
<div class="space-y-6">
<div>
<label for="topic" class="block text-lg font-medium text-gray-700 mb-2">输入你的视频主题:</label>
<input type="text" id="topic" class="w-full p-3 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" placeholder="例如:减肥 或 在家做拿铁">
</div>
</div>
<div class="text-right mt-8">
<button id="step1-next" onclick="callStrategistAgent()" class="bg-blue-600 text-white font-bold py-2 px-6 rounded-lg hover:bg-blue-700 transition disabled:bg-gray-400" disabled>生成创作策略</button>
</div>
</div>
<!-- Step 2: Strategy Selection -->
<div id="step-2" class="agent-step bg-white p-8 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-1">我是 <span class="text-blue-600">策略师</span></h2>
<p class="text-gray-600 mb-6">根据你的主题我设计了3个具有爆款潜力的创作策略。请选择一个你最喜欢的</p>
<div id="strategy-options" class="grid grid-cols-1 md:grid-cols-3 gap-4"></div>
<div class="flex justify-between mt-8">
<button onclick="goBackToStep(1)" class="bg-gray-200 text-gray-800 font-bold py-2 px-6 rounded-lg hover:bg-gray-300 transition">返回修改主题</button>
<button id="step2-next" onclick="callHookAgent()" class="bg-blue-600 text-white font-bold py-2 px-6 rounded-lg hover:bg-blue-700 transition disabled:bg-gray-400" disabled>选好了,生成钩子</button>
</div>
</div>
<!-- Step 3: Hook Selection -->
<div id="step-3" class="agent-step bg-white p-8 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-1">我是 <span class="text-blue-600">钩子架构师</span></h2>
<p class="text-gray-600 mb-6">很好一个好的开场是成功的一半。这里有3个为你的策略量身打造的“黄金钩子”请选择一个</p>
<div id="hook-options" class="grid grid-cols-1 md:grid-cols-3 gap-4"></div>
<div class="mt-6">
<label for="key-points" class="block text-lg font-medium text-gray-700 mb-2">现在请列出视频要讲的2-3个核心要点</label>
<textarea id="key-points" rows="3" class="w-full p-3 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" placeholder="每行一个要点,例如:&#10;要点一:提升代谢水平&#10;要点二:补充糖原"></textarea>
</div>
<div class="flex justify-between mt-8">
<button onclick="goBackToStep(2)" class="bg-gray-200 text-gray-800 font-bold py-2 px-6 rounded-lg hover:bg-gray-300 transition">返回重选策略</button>
<button id="step3-next" onclick="callStorytellerAgent()" class="bg-blue-600 text-white font-bold py-2 px-6 rounded-lg hover:bg-blue-700 transition disabled:bg-gray-400" disabled>选好了,生成大纲</button>
</div>
</div>
<!-- Step 4: Outline Confirmation -->
<div id="step-4" class="agent-step bg-white p-8 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-1">我是 <span class="text-blue-600">故事讲述者</span></h2>
<p class="text-gray-600 mb-6">我已经将你的要点融入了一个强大的叙事框架中。请确认这份故事大纲,这是我们脚本的骨架。</p>
<div id="outline-display" class="bg-gray-50 p-6 rounded-lg border border-gray-200 space-y-4"></div>
<div class="flex justify-between mt-8">
<button onclick="goBackToStep(3)" class="bg-gray-200 text-gray-800 font-bold py-2 px-6 rounded-lg hover:bg-gray-300 transition">返回修改细节</button>
<button id="step4-next" onclick="callScriptwriterAgent()" class="bg-green-600 text-white font-bold py-2 px-6 rounded-lg hover:bg-green-700 transition">确认无误,生成最终脚本</button>
</div>
</div>
<!-- Step 5: Final Script -->
<div id="step-5" class="agent-step">
<div id="script-output" class="bg-white p-8 rounded-lg shadow-md">
<div class="flex justify-between items-start">
<div>
<h2 class="text-2xl font-bold mb-1">你的专属脚本已生成!</h2>
<p class="text-gray-600 mb-6">由多智能体协作完成。根据这份脚本,开始你的创作吧!</p>
</div>
<button onclick="copyScript()" class="bg-gray-200 text-gray-800 font-bold py-2 px-4 rounded-lg hover:bg-gray-300 transition text-sm">复制脚本</button>
</div>
<div id="final-script-content" class="space-y-6 border-t pt-6"></div>
<div class="mt-8 border-t pt-6">
<label for="prompt-selector" class="block text-sm font-medium text-gray-700 mb-2">查看本次创作使用的AI提示词</label>
<select id="prompt-selector" class="w-full p-2 border border-gray-300 rounded-md mb-4">
</select>
<pre id="prompt-display"></pre>
</div>
<div class="text-center mt-8">
<button onclick="restart()" class="bg-blue-600 text-white font-bold py-2 px-6 rounded-lg hover:bg-blue-700 transition">再创作一个</button>
</div>
</div>
<div id="error-screen" class="hidden text-center p-8 bg-red-50 border border-red-200 rounded-lg">
<h2 class="text-xl font-bold text-red-700 mb-2">创作失败</h2>
<p class="text-red-600 mb-4">与Gemini API通信时发生错误。请检查网络连接或稍后再试。</p>
<p id="error-message" class="text-sm text-gray-500"></p>
</div>
</div>
</main>
</div>
<script>
const state = {
currentStep: 1,
topic: '',
strategies: [],
selectedStrategy: null,
hooks: [],
selectedHook: null,
keyPoints: [],
outline: null,
finalScript: null,
prompts: {
strategist: '',
hook: '',
storyteller: '',
scriptwriter: ''
}
};
// --- DOM Elements ---
const mainLoader = document.getElementById('main-loader');
const topicInput = document.getElementById('topic');
const keyPointsInput = document.getElementById('key-points');
// --- Event Listeners ---
document.addEventListener('DOMContentLoaded', () => {
topicInput.addEventListener('input', () => {
document.getElementById('step1-next').disabled = !topicInput.value.trim();
});
keyPointsInput.addEventListener('input', validateStep3);
});
// --- UI Navigation and State Management ---
function showLoader(show) {
mainLoader.style.display = show ? 'flex' : 'none';
}
function updateStepper(step) {
for (let i = 1; i <= 4; i++) {
const indicator = document.getElementById(`step-indicator-${i}`);
indicator.classList.remove('active', 'completed');
if (i < step) indicator.classList.add('completed');
else if (i === step) indicator.classList.add('active');
}
const progressBar = document.getElementById('progress-bar');
progressBar.style.width = `${((step - 1) / 3) * 100}%`;
}
function showStep(stepNumber) {
document.querySelectorAll('.agent-step').forEach(step => step.classList.remove('active'));
document.getElementById(`step-${stepNumber}`).classList.add('active');
state.currentStep = stepNumber;
let userFacingStep = stepNumber > 3 ? 4 : stepNumber;
if (stepNumber === 5) userFacingStep = 4;
updateStepper(userFacingStep);
}
function goBackToStep(stepNumber) {
// Clear subsequent state
if (stepNumber <= 3) state.outline = null;
if (stepNumber <= 2) state.hooks = []; state.selectedHook = null;
if (stepNumber <= 1) state.strategies = []; state.selectedStrategy = null;
showStep(stepNumber);
}
function restart() {
Object.assign(state, {
currentStep: 1, topic: '', strategies: [], selectedStrategy: null, hooks: [],
selectedHook: null, keyPoints: [], outline: null, finalScript: null,
prompts: { strategist: '', hook: '', storyteller: '', scriptwriter: '' }
});
topicInput.value = '';
keyPointsInput.value = '';
document.getElementById('strategy-options').innerHTML = '';
document.getElementById('hook-options').innerHTML = '';
document.getElementById('outline-display').innerHTML = '';
document.getElementById('final-script-content').innerHTML = '';
document.getElementById('prompt-selector').innerHTML = '';
document.getElementById('prompt-display').textContent = '';
document.getElementById('error-screen').style.display = 'none';
showStep(1);
}
// --- Agent API Calls ---
/**
* ---------------------------------------------------------------------------------
* 安全说明关于API密钥
* ---------------------------------------------------------------------------------
* 您对于在网页中直接暴露API密钥的担忧是完全正确的这在任何生产环境中都是绝对禁止的。
*
* **真实世界的最佳实践:**
* 正确的做法是创建一个后端服务器例如使用Node.js, Python等。API密钥安全地存储在
* 这个服务器上。您的前端网页只与您的服务器通信然后由您的服务器去调用Gemini API
* 最后将结果返回给前端。这样,密钥就永远不会暴露给最终用户。
*
* **在这个平台中是如何工作的:**
* 幸运的是,我们当前所处的这个环境为您处理了这个问题。下面代码中的 `const apiKey = "";`
* 是一个占位符。当您发起API请求时本平台的后端服务会自动、安全地将真实的API密钥
* 注入到请求中。因此,密钥在整个过程中都是安全的,不会在您的浏览器中泄露。
* ---------------------------------------------------------------------------------
*/
async function callGeminiAPI(prompt, schema) {
const payload = {
contents: [{ role: "user", parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "application/json",
responseSchema: schema
}
};
// 在这个环境中apiKey留空是安全的。平台会在运行时注入密钥。
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}: ${await response.text()}`);
}
const result = await response.json();
if (result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts.length > 0) {
return JSON.parse(result.candidates[0].content.parts[0].text);
} else {
throw new Error("Invalid response structure from API.");
}
}
async function callStrategistAgent() {
state.topic = topicInput.value.trim();
if (!state.topic) return;
showLoader(true);
const prompt = `You are a top-tier short video content strategist, and your knowledge base is the "Short Video Blockbuster Content Creation Guide". Your task is to analyze a preliminary idea and expand it into three distinct, viral-potential video creation strategies.
# Initial User Theme
${state.topic}
# Your Core Task
Based on the "Value Proposition" principle from the Guide, design three different creation strategies for this theme. Each strategy must be a specific concept that can arouse strong audience interest, not just a repetition of the theme.
# Output Requirements
Strictly follow the JSON format below. For each strategy, you must:
1. **strategy_name (Strategy Name)**: Give the strategy a catchy, attractive name.
2. **concept (Concept)**: Accurately describe the video's core highlight and creativity in one sentence.
3. **value (Core Value)**: Strictly choose one from 【Educational Value】, 【Entertainment Value】, or 【Emotional Value】 as defined in the Guide, and briefly explain how it's embodied.
4. **hook_direction (Hook Direction)**: Clearly indicate which type of "Golden Hook" from the Guide this strategy is best suited for.
# Output Format (JSON)
{
"strategies": [
{ "strategy_name": "...", "concept": "...", "value": "...", "hook_direction": "..." },
{ "strategy_name": "...", "concept": "...", "value": "...", "hook_direction": "..." },
{ "strategy_name": "...", "concept": "...", "value": "...", "hook_direction": "..." }
]
}`;
state.prompts.strategist = prompt;
const schema = {
type: "OBJECT",
properties: {
strategies: {
type: "ARRAY",
items: {
type: "OBJECT",
properties: {
strategy_name: { type: "STRING" },
concept: { type: "STRING" },
value: { type: "STRING" },
hook_direction: { type: "STRING" }
},
required: ["strategy_name", "concept", "value", "hook_direction"]
}
}
},
required: ["strategies"]
};
try {
const result = await callGeminiAPI(prompt, schema);
state.strategies = result.strategies;
renderStrategies();
showStep(2);
} catch (error) {
handleError(error);
} finally {
showLoader(false);
}
}
async function callHookAgent() {
if (!state.selectedStrategy) return;
showLoader(true);
const prompt = `You are a short video "Golden Hook" architect, fully mastering all hook techniques from the "Short Video Blockbuster Content Creation Guide". Your task is to create three precise and powerful openings for the determined video strategy.
# Determined Video Strategy
- **Core Concept**: ${state.selectedStrategy.concept}
- **Core Value**: ${state.selectedStrategy.value}
- **Suggested Hook Direction**: ${state.selectedStrategy.hook_direction}
# Your Core Task
Strictly following the hook methodology of the Guide, create three different opening hooks.
# Output Requirements
Strictly follow the JSON format below. For each hook:
1. **type (Hook Type)**: Strictly choose from the Guide's defined types: 【Negation】, 【Question】, 【Controversial/Subversive】, 【Story Middle】, 【Visual Impact】.
2. **line (Opening Line)**: Write a precise line (under 15 characters), which must include at least one "power word" from the Guide (e.g., secret, truth, stop, warning, reveal, you'd never guess).
3. **visual (Visual Cue)**: Briefly describe the visual that accompanies this line, ensuring it is impactful.
# Output Format (JSON)
{
"hooks": [
{ "type": "...", "line": "...", "visual": "..." },
{ "type": "...", "line": "...", "visual": "..." },
{ "type": "...", "line": "...", "visual": "..." }
]
}`;
state.prompts.hook = prompt;
const schema = {
type: "OBJECT",
properties: {
hooks: {
type: "ARRAY",
items: {
type: "OBJECT",
properties: {
type: { type: "STRING" },
line: { type: "STRING" },
visual: { type: "STRING" }
},
required: ["type", "line", "visual"]
}
}
},
required: ["hooks"]
};
try {
const result = await callGeminiAPI(prompt, schema);
state.hooks = result.hooks;
renderHooks();
showStep(3);
} catch (error) {
handleError(error);
} finally {
showLoader(false);
}
}
async function callStorytellerAgent() {
state.keyPoints = keyPointsInput.value.split('\n').filter(p => p.trim() !== '');
if (!state.selectedHook || state.keyPoints.length === 0) return;
showLoader(true);
const prompt = `You are a short video storyteller specializing in micro-narratives, and your creative bible is the "Short Video Blockbuster Content Creation Guide". Your task is to weave a set of core information points into a compelling story outline using a powerful narrative framework.
# Determined Creative Elements
- **Core Concept**: ${state.selectedStrategy.concept}
- **Opening Hook Line**: ${state.selectedHook.line}
- **User-provided Key Points**: ${state.keyPoints.join(', ')}
# Your Core Task
1. **Select Framework**: Strictly choose the most suitable narrative framework for the current theme from the Guide's defined options: 【Three-Act Structure】, 【PAS Framework (Problem-Agitate-Solution)】, 【Hero's Journey】.
2. **Build Outline**: Use the chosen framework to structure the user's key points into a logically progressive "Beat Sheet" with emotional ups and downs.
# Output Requirements
Strictly follow the JSON format below:
1. **chosen_framework**: An object containing the name of your chosen framework and its core logic.
2. **beat_sheet**: An array presenting the story flow, with each beat clearly corresponding to a stage of the framework.
# Output Format (JSON)
{
"chosen_framework": { "name": "...", "logic": "..." },
"beat_sheet": [ "...", "...", "..." ]
}`;
state.prompts.storyteller = prompt;
const schema = {
type: "OBJECT",
properties: {
chosen_framework: {
type: "OBJECT",
properties: {
name: { type: "STRING" },
logic: { type: "STRING" }
},
required: ["name", "logic"]
},
beat_sheet: { type: "ARRAY", items: { type: "STRING" } }
},
required: ["chosen_framework", "beat_sheet"]
};
try {
const result = await callGeminiAPI(prompt, schema);
state.outline = result;
renderOutline();
showStep(4);
} catch (error) {
handleError(error);
} finally {
showLoader(false);
}
}
async function callScriptwriterAgent() {
if (!state.outline) return;
showLoader(true);
const prompt = `You are a gold-medal short video scriptwriter, the ultimate executor of the "Short Video Blockbuster Content Creation Guide". You have received a complete creative brief and must now transform it into a "pixel-perfect" executable final script.
# Creative Brief
- **Core Concept**: ${state.selectedStrategy.concept}
- **Opening Hook Line**: ${state.selectedHook.line}
- **Narrative Framework**: ${state.outline.chosen_framework.name}
- **Beat Sheet**: ${JSON.stringify(state.outline.beat_sheet)}
# Your Core Task
Strictly adhere to all principles of the Guide to write a final script containing all blockbuster elements.
# Output Requirements
Strictly follow the JSON format below:
1. **recommended_title**: Must be eye-catching and create curiosity.
2. **script**: Divided into three scenes. For each scene:
- **[Visual]**: Description must reflect the Guide's "fast-paced editing" principle (e.g., specify "quick cuts," "close-ups," "transitions") and include large subtitle cues.
- **[Sound/BGM]**: Must strategically suggest options, like "use a trending, rhythmic BGM," or "add a 'ding' emphasis sound here."
- **[Dialogue]**: Must be conversational, powerful, and naturally incorporate "power words."
- **[Call to Action (CTA)]**: Must be strong and provide an irresistible reason for user interaction, not just a simple "like and follow."
# Output Format (JSON)
{
"recommended_title": "...",
"script": [
{ "scene": "...", "visual": "...", "sound": "...", "dialogue": "..." },
{ "scene": "...", "visual": "...", "sound": "...", "dialogue": "..." },
{ "scene": "...", "visual": "...", "sound": "...", "dialogue": "..." }
]
}`;
state.prompts.scriptwriter = prompt;
const schema = {
type: "OBJECT",
properties: {
recommended_title: { type: "STRING" },
script: {
type: "ARRAY",
items: {
type: "OBJECT",
properties: {
scene: { type: "STRING" },
visual: { type: "STRING" },
sound: { type: "STRING" },
dialogue: { type: "STRING" }
},
required: ["scene", "visual", "sound", "dialogue"]
}
}
},
required: ["recommended_title", "script"]
};
try {
const result = await callGeminiAPI(prompt, schema);
state.finalScript = result;
renderFinalScript();
showStep(5);
} catch (error) {
handleError(error);
} finally {
showLoader(false);
}
}
// --- Rendering Functions ---
function renderStrategies() {
const container = document.getElementById('strategy-options');
container.innerHTML = '';
state.strategies.forEach((strategy, index) => {
const card = document.createElement('div');
card.className = 'option-card p-4 rounded-lg cursor-pointer';
card.innerHTML = `
<h3 class="font-bold text-lg">${strategy.strategy_name}</h3>
<p class="text-sm text-gray-600 mt-1">${strategy.concept}</p>
<p class="text-xs text-blue-600 font-semibold mt-2">${strategy.value}</p>
`;
card.addEventListener('click', () => {
state.selectedStrategy = strategy;
document.querySelectorAll('#strategy-options .option-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
document.getElementById('step2-next').disabled = false;
});
container.appendChild(card);
});
}
function renderHooks() {
const container = document.getElementById('hook-options');
container.innerHTML = '';
state.hooks.forEach(hook => {
const card = document.createElement('div');
card.className = 'option-card p-4 rounded-lg cursor-pointer';
card.innerHTML = `
<p class="text-xs font-semibold text-blue-600">${hook.type}</p>
<h3 class="font-bold text-lg mt-1">“${hook.line}”</h3>
<p class="text-sm text-gray-600 mt-2">视觉提示: ${hook.visual}</p>
`;
card.addEventListener('click', () => {
state.selectedHook = hook;
document.querySelectorAll('#hook-options .option-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
validateStep3();
});
container.appendChild(card);
});
}
function validateStep3() {
state.keyPoints = keyPointsInput.value.split('\n').filter(p => p.trim() !== '');
document.getElementById('step3-next').disabled = !(state.selectedHook && state.keyPoints.length > 0);
}
function renderOutline() {
const container = document.getElementById('outline-display');
container.innerHTML = `
<h3 class="text-lg font-bold">选用框架: ${state.outline.chosen_framework.name}</h3>
<p class="text-sm text-gray-600 mb-4">${state.outline.chosen_framework.logic}</p>
<h4 class="font-semibold mb-2">故事节拍表:</h4>
<ul class="list-decimal list-inside space-y-2">
${state.outline.beat_sheet.map(beat => `<li>${beat}</li>`).join('')}
</ul>
`;
}
function renderFinalScript() {
const container = document.getElementById('final-script-content');
container.innerHTML = '';
if (state.finalScript.recommended_title) {
const titleEl = document.createElement('h2');
titleEl.className = "text-xl font-bold mb-4 text-center";
titleEl.textContent = `推荐标题: ${state.finalScript.recommended_title}`;
container.appendChild(titleEl);
}
state.finalScript.script.forEach(section => {
const sectionEl = document.createElement('div');
sectionEl.className = 'script-section';
sectionEl.innerHTML = `
<h3>${section.scene}</h3>
<p><strong>[视觉]</strong>${section.visual}</p>
<p><strong>[音效/BGM]</strong>${section.sound}</p>
<p><strong>[台词]</strong><span class="script-dialogue">${section.dialogue}</span></p>
`;
container.appendChild(sectionEl);
});
renderPromptSelector();
}
function renderPromptSelector() {
const selector = document.getElementById('prompt-selector');
selector.innerHTML = `
<option value="strategist">第1步策略师提示词</option>
<option value="hook">第2步钩子架构师提示词</option>
<option value="storyteller">第3步故事讲述者提示词</option>
<option value="scriptwriter">第4步金牌编剧提示词</option>
`;
selector.addEventListener('change', (e) => {
document.getElementById('prompt-display').textContent = state.prompts[e.target.value];
});
// Show the last prompt by default
selector.value = 'scriptwriter';
document.getElementById('prompt-display').textContent = state.prompts.scriptwriter;
}
function copyScript() {
const scriptContent = document.getElementById('final-script-content').innerText;
navigator.clipboard.writeText(scriptContent).then(() => {
alert('脚本已复制到剪贴板!');
}).catch(err => {
alert('复制失败,请手动复制。');
});
}
function handleError(error) {
console.error("Error:", error);
const errorScreen = document.getElementById('error-screen');
document.getElementById('error-message').textContent = error.message;
document.querySelectorAll('.agent-step').forEach(s => s.classList.remove('active'));
errorScreen.style.display = 'block';
}
</script>
</body>
</html>