Story Page Builder v6 FINAL
Workflow v6: najprv vytvor osnovu celej stránky a rozmiestni custom médiá podľa ich textových popisov. Až potom sa každý headline, subheadline, body, tabuľka, quote, transition a ďalší element generuje vlastným phase/section/element promptom.
Vygeneruj sekcie a klikni Preview.
Zatiaľ bez custom assetov.
`;
return;
}
for(const asset of state.mediaAssets){
const item=document.createElement("div");
item.className="media-item";
const preview=asset.type.startsWith("video/")
? ``
: `${escapeHtml(asset.name)}
${escapeHtml(asset.type)}
${escapeHtml(asset.type)}
Do AI requestu pôjde iba tento textový popis + ID/názov/typ. Base64 obsah obrázka/videa sa neposiela.
`;
root.appendChild(item);
}
root.querySelectorAll("[data-media-desc]").forEach(n=>n.addEventListener("input",()=>{
const a=mediaAssetById(n.dataset.mediaDesc); if(a) a.description=n.value;
}));
root.querySelectorAll("[data-media-section]").forEach(n=>n.addEventListener("change",()=>{
const a=mediaAssetById(n.dataset.mediaSection); if(a) a.preferredSection=n.value;
}));
root.querySelectorAll("[data-media-reuse]").forEach(n=>n.addEventListener("change",()=>{
const a=mediaAssetById(n.dataset.mediaReuse); if(a) a.allowReuse=n.checked;
}));
root.querySelectorAll("[data-media-delete]").forEach(n=>n.addEventListener("click",()=>{
state.mediaAssets=state.mediaAssets.filter(a=>a.id!==n.dataset.mediaDelete);
renderMediaLibrary();
}));
}function fileToDataUrl(file){
return new Promise((resolve,reject)=>{
const r=new FileReader();
r.onload=()=>resolve(String(r.result||""));
r.onerror=()=>reject(r.error||new Error("FileReader zlyhal"));
r.readAsDataURL(file);
});
}async function addMediaFiles(files){
for(const file of files){
const dataUrl=await fileToDataUrl(file);
state.mediaAssets.push({
id:`media_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,
name:file.name,
type:file.type||"application/octet-stream",
size:file.size||0,
description:"",
preferredSection:"auto",
allowReuse:false,
dataUrl
});
}
renderMediaLibrary();
}function saveProject(){
const project={
schema_version:"1.0",
saved_at:new Date().toISOString(),
settings:{
model:$("model").value,
publicationName:$("publicationName").value,
pageLanguage:$("pageLanguage").value,
ctaUrl:$("ctaUrl").value,
imageModel:$("imageModel").value,
autoGenerateImages:$("autoGenerateImages").checked,
globalInstruction:$("globalInstruction").value
},
contexts:{
product:$("productContext").value,
persona:$("personaContext").value,
angle:$("angleContext").value,
story:$("storyContext").value
},
page_state:state.sections,
histories:state.histories,
prompt_logs:state.promptLogs,
media_assets:state.mediaAssets,
section_plans:state.sectionPlans,
outline_meta:state.outlineMeta
};
downloadText("story-page-project.json",JSON.stringify(project,null,2),"application/json");
}function loadDetectedObject(obj){
const s=obj||{};
if(s.settings){
if(s.settings.model) $("model").value=s.settings.model;
if(s.settings.publicationName) $("publicationName").value=s.settings.publicationName;
if(s.settings.pageLanguage) $("pageLanguage").value=s.settings.pageLanguage;
if(s.settings.ctaUrl) $("ctaUrl").value=s.settings.ctaUrl;
if(s.settings.imageModel) $("imageModel").value=s.settings.imageModel;
if(s.settings.autoGenerateImages!==undefined) $("autoGenerateImages").checked=!!s.settings.autoGenerateImages;
if(s.settings.globalInstruction!==undefined) $("globalInstruction").value=s.settings.globalInstruction;
}
if(s.contexts){
if(s.contexts.product!==undefined) $("productContext").value=typeof s.contexts.product==="string"?s.contexts.product:JSON.stringify(s.contexts.product,null,2);
if(s.contexts.persona!==undefined) $("personaContext").value=typeof s.contexts.persona==="string"?s.contexts.persona:JSON.stringify(s.contexts.persona,null,2);
if(s.contexts.angle!==undefined) $("angleContext").value=typeof s.contexts.angle==="string"?s.contexts.angle:JSON.stringify(s.contexts.angle,null,2);
if(s.contexts.story!==undefined) $("storyContext").value=typeof s.contexts.story==="string"?s.contexts.story:JSON.stringify(s.contexts.story,null,2);
}// auto-detect common combined keys
const product=s.PRODUCT_CONTEXT||s.product_context||s.productContext;
const persona=s.PERSONA_CONTEXT||s.persona_context||s.personaContext;
const angle=s.ANGLE_CONTEXT||s.angle_context||s.angleContext;
const story=s.STORY_CONTEXT||s.story_context||s.storyContext;if(product) $("productContext").value=JSON.stringify(product,null,2);
if(persona) $("personaContext").value=JSON.stringify(persona,null,2);
if(angle) $("angleContext").value=JSON.stringify(angle,null,2);
if(story) $("storyContext").value=JSON.stringify(story,null,2);// If the uploaded JSON itself looks like a Story Context
if(!story && s.story_id && (s.present_day_hook||s.story_strategy||s.first_person_story)){
$("storyContext").value=JSON.stringify(s,null,2);
}
// If itself looks like Persona
if(!persona && s.persona && s.problem && s.funnel_variables){
$("personaContext").value=JSON.stringify(s,null,2);
}
// If itself looks like Angle
if(!angle && s.angle_name && s.angle_type){
$("angleContext").value=JSON.stringify(s,null,2);
}if(s.page_state) state.sections=clone(s.page_state);
if(s.histories) state.histories=clone(s.histories);
if(s.prompt_logs) state.promptLogs=clone(s.prompt_logs);
if(s.media_assets) state.mediaAssets=clone(s.media_assets);
if(s.section_plans) state.sectionPlans=clone(s.section_plans);
if(s.outline_meta) state.outlineMeta=clone(s.outline_meta);
normalizeOutlineAssetAssignments();
applyOutlineMediaAssignments();
buildBuilder();
renderMediaLibrary();
updateOutlineStatus();
}$("mediaFiles").addEventListener("change",async ev=>{
const files=Array.from(ev.target.files||[]);
if(!files.length) return;
try{
await addMediaFiles(files);
ev.target.value="";
setStatus($("globalStatus"),`${files.length} médií pridaných.`,"ok");
}catch(err){
setStatus($("globalStatus"),`Media upload: ${err.message}`,"err");
}
});$("contextFile").addEventListener("change",async ev=>{
const file=ev.target.files?.[0];
if(!file) return;
try{
const txt=await file.text();
const obj=JSON.parse(txt);
loadDetectedObject(obj);
setStatus($("contextStatus"),"Súbor načítaný.","ok");
}catch(err){
setStatus($("contextStatus"),`Súbor: ${err.message}`,"err");
}
});$("detectContext").addEventListener("click",()=>{
try{
const candidates=[$("storyContext").value,$("personaContext").value,$("angleContext").value,$("productContext").value].filter(v=>v.trim());
if(!candidates.length) throw new Error("Nie je čo detegovať.");
for(const raw of candidates){
try{loadDetectedObject(JSON.parse(raw));}catch{}
}
setStatus($("contextStatus"),"Auto-detect dokončený.","ok");
}catch(err){setStatus($("contextStatus"),err.message,"err")}
});$("validateContext").addEventListener("click",()=>{
try{
const c=getContexts();
if(!c.story) throw new Error("STORY_CONTEXT je povinný.");
setStatus($("contextStatus"),"Context je validný. STORY_CONTEXT nájdený.","ok");
}catch(err){setStatus($("contextStatus"),err.message,"err")}
});$("clearContext").addEventListener("click",()=>{
["productContext","personaContext","angleContext","storyContext"].forEach(id=>$(id).value="");
setStatus($("contextStatus"),"");
});$("resetPage").addEventListener("click",()=>{
if(!confirm("Vymazať vygenerovanú page state? Context ostane.")) return;
state.sections={};state.histories={};
buildBuilder();
setStatus($("globalStatus"),"Page resetnutá.","ok");
});$("generateOutline").addEventListener("click",generateOutline);
$("generateOutlineSide").addEventListener("click",generateOutline);
$("clearOutline").addEventListener("click",()=>{
state.sectionPlans={};
state.outlineMeta=null;
for(const section of SECTION_SCHEMA.flatMap(p=>p.sections)){
if(state.sections[section.id] && String(state.sections[section.id].image_url||"").startsWith("asset:")){
state.sections[section.id].image_url="";
}
}
updateOutlineStatus();
buildBuilder();
setStatus($("globalStatus"),"Osnova vymazaná.");
});
$("generateAll").addEventListener("click",generateAll);
$("generateAllImages").addEventListener("click",generateAllImages);
$("previewBtn").addEventListener("click",()=>switchTab("preview"));
$("exportBtn").addEventListener("click",exportHtml);
$("saveProjectBtn").addEventListener("click",saveProject);
document.querySelectorAll(".tab").forEach(btn=>btn.addEventListener("click",()=>switchTab(btn.dataset.tab)));buildBuilder();
