fix(gtm): Fix white screen and implement URL persistence v2.6.1

- Fixed TypeError in SessionBrowser by adding defensive checks for the sessions array.
- Implemented mandatory URL persistence: The research URL is now saved in DB, shown in UI, and included in reports.
- Added 'Start New Analysis' button to the session browser for better UX flow.
- Updated documentation to reflect v2.6.1 changes.
This commit is contained in:
2026-01-08 21:36:42 +00:00
parent 9f65a1b01b
commit 84fc0b91b0
7 changed files with 231 additions and 281 deletions

View File

@@ -248,145 +248,152 @@ const App: React.FC = () => {
}
}, [theme]);
// Load Sessions on Mount
useEffect(() => {
const fetchSessions = async () => {
try {
const res = await Gemini.listSessions();
setSessions(res.projects);
} catch (e) {
console.error("Failed to load sessions", e);
}
};
fetchSessions();
}, []);
useEffect(() => {
if (!state.isLoading && !isTranslating) return;
const messages = labels.loading;
let i = 0;
setLoadingMessage(messages[0]);
const interval = setInterval(() => {
i = (i + 1) % messages.length;
setLoadingMessage(messages[i]);
}, 2500);
return () => clearInterval(interval);
}, [state.isLoading, isTranslating, labels.loading]);
// Canvas Initialization for Editing
useEffect(() => {
if (editingIndex !== null && canvasRef.current && generatedImages[editingIndex]) {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
};
img.src = generatedImages[editingIndex];
}
}, [editingIndex, generatedImages]);
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const goBack = () => {
if (state.currentPhase > Phase.Input) {
setState(s => ({ ...s, currentPhase: s.currentPhase - 1 }));
}
};
// Determine the highest phase the user has completed data for.
// This allows navigation back and forth.
const getMaxAllowedPhase = (): Phase => {
if (state.phase9Result) return Phase.TechTranslator;
if (state.phase8Result) return Phase.BusinessCase;
if (state.phase7Result) return Phase.LandingPage;
if (state.phase6Result) return Phase.SalesEnablement;
if (state.phase5Result) return Phase.AssetGeneration;
if (state.phase4Result) return Phase.Strategy;
if (state.phase3Result) return Phase.WhaleHunting;
if (state.phase2Result) return Phase.ICPDiscovery;
if (state.phase1Result) return Phase.ProductAnalysis;
return Phase.Input;
};
const handlePhaseSelect = (phase: Phase) => {
if (state.isLoading) return; // Prevent navigation while generating
setState(s => ({ ...s, currentPhase: phase }));
};
const handleLoadSession = async (projectId: string) => {
setLoadingMessage("Loading Session...");
setState(s => ({ ...s, isLoading: true }));
try {
const data = await Gemini.loadSession(projectId);
const phases = data.phases || {};
setState(s => ({
...s,
isLoading: false,
currentPhase: Phase.ProductAnalysis,
projectId: projectId,
productInput: phases.phase1_result?.rawAnalysis || "",
phase1Result: phases.phase1_result,
phase2Result: phases.phase2_result,
phase3Result: phases.phase3_result,
phase4Result: phases.phase4_result,
phase5Result: phases.phase5_result,
phase6Result: phases.phase6_result,
phase7Result: phases.phase7_result,
phase8Result: phases.phase8_result,
phase9Result: phases.phase9_result,
}));
setViewingSessions(false); // Switch back to the main view
} catch (e: any) {
setError("Failed to load session: " + e.message);
setState(s => ({ ...s, isLoading: false }));
}
};
const handleDeleteSession = async (projectId: string) => {
if (!window.confirm("Delete this session permanently?")) return;
// Load Sessions on Mount
useEffect(() => {
const fetchSessions = async () => {
try {
const res = await Gemini.listSessions();
if (res && Array.isArray(res.projects)) {
setSessions(res.projects);
} else {
setSessions([]);
}
} catch (e) {
console.error("Failed to load sessions", e);
setSessions([]);
}
};
fetchSessions();
}, []);
useEffect(() => {
if (!state.isLoading && !isTranslating) return;
const messages = labels.loading;
try {
await Gemini.deleteSession(projectId);
setSessions(prev => prev.filter(s => s.id !== projectId));
} catch (e: any) {
setError("Failed to delete session: " + e.message);
}
};
const handleLoadMarkdown = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const content = event.target?.result as string;
if (content) {
setState(s => ({
...s,
currentPhase: Phase.AssetGeneration,
phase5Result: { report: content }
}));
let i = 0;
setLoadingMessage(messages[0]);
const interval = setInterval(() => {
i = (i + 1) % messages.length;
setLoadingMessage(messages[i]);
}, 2500);
return () => clearInterval(interval);
}, [state.isLoading, isTranslating, labels.loading]);
// Canvas Initialization for Editing
useEffect(() => {
if (editingIndex !== null && canvasRef.current && generatedImages[editingIndex]) {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
};
img.src = generatedImages[editingIndex];
}
}, [editingIndex, generatedImages]);
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const goBack = () => {
if (state.currentPhase > Phase.Input) {
setState(s => ({ ...s, currentPhase: s.currentPhase - 1 }));
}
};
reader.readAsText(file);
};
const generateFullReportMarkdown = (): string => {
if (!state.phase5Result || !state.phase5Result.report) return "";
let fullReport = state.phase5Result.report;
// Determine the highest phase the user has completed data for.
// This allows navigation back and forth.
const getMaxAllowedPhase = (): Phase => {
if (state.phase9Result) return Phase.TechTranslator;
if (state.phase8Result) return Phase.BusinessCase;
if (state.phase7Result) return Phase.LandingPage;
if (state.phase6Result) return Phase.SalesEnablement;
if (state.phase5Result) return Phase.AssetGeneration;
if (state.phase4Result) return Phase.Strategy;
if (state.phase3Result) return Phase.WhaleHunting;
if (state.phase2Result) return Phase.ICPDiscovery;
if (state.phase1Result) return Phase.ProductAnalysis;
return Phase.Input;
};
const handlePhaseSelect = (phase: Phase) => {
if (state.isLoading) return; // Prevent navigation while generating
setState(s => ({ ...s, currentPhase: phase }));
};
const handleLoadSession = async (projectId: string) => {
setLoadingMessage("Loading Session...");
setState(s => ({ ...s, isLoading: true }));
try {
const data = await Gemini.loadSession(projectId);
const phases = data.phases || {};
setState(s => ({
...s,
isLoading: false,
currentPhase: Phase.ProductAnalysis,
projectId: projectId,
productInput: phases.phase1_result?.rawAnalysis || "",
phase1Result: phases.phase1_result,
phase2Result: phases.phase2_result,
phase3Result: phases.phase3_result,
phase4Result: phases.phase4_result,
phase5Result: phases.phase5_result,
phase6Result: phases.phase6_result,
phase7Result: phases.phase7_result,
phase8Result: phases.phase8_result,
phase9Result: phases.phase9_result,
}));
setViewingSessions(false); // Switch back to the main view
} catch (e: any) {
setError("Failed to load session: " + e.message);
setState(s => ({ ...s, isLoading: false }));
}
};
const handleDeleteSession = async (projectId: string) => {
if (!window.confirm("Delete this session permanently?")) return;
try {
await Gemini.deleteSession(projectId);
setSessions(prev => prev.filter(s => s.id !== projectId));
} catch (e: any) {
setError("Failed to delete session: " + e.message);
}
};
const handleLoadMarkdown = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const content = event.target?.result as string;
if (content) {
setState(s => ({
...s,
currentPhase: Phase.AssetGeneration,
phase5Result: { report: content }
}));
}
};
reader.readAsText(file);
};
const generateFullReportMarkdown = (): string => {
if (!state.phase5Result || !state.phase5Result.report) return "";
const sourceUrl = state.phase1Result?.specs?.metadata?.manufacturer_url;
let fullReport = sourceUrl ? `# GTM Strategy\n\n**Recherche-URL:** ${sourceUrl}\n\n---\n\n` : '# GTM Strategy\n\n';
fullReport += state.phase5Result.report;
if (state.phase6Result) {
fullReport += `\n\n# SALES ENABLEMENT & VISUALS (PHASE 6)\n\n`;
fullReport += `## Kill-Critique Battlecards\n\n`;
@@ -920,6 +927,7 @@ const App: React.FC = () => {
sessions={sessions}
onLoadSession={handleLoadSession}
onDeleteSession={handleDeleteSession}
onStartNew={() => setViewingSessions(false)}
/>
) : (
<div className="w-full max-w-4xl grid grid-cols-1 md:grid-cols-3 gap-6">