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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user