feat(gtm): add responsive collapsible sidebar for mobile
This commit is contained in:
@@ -8,9 +8,11 @@ import { AlertTriangle, ArrowRight, ArrowLeft, Check, Database, Globe, Search, S
|
|||||||
|
|
||||||
const TRANSLATIONS = {
|
const TRANSLATIONS = {
|
||||||
en: {
|
en: {
|
||||||
|
// ... existing ...
|
||||||
historyTitle: 'Recent Sessions',
|
historyTitle: 'Recent Sessions',
|
||||||
loadBtn: 'Load',
|
loadBtn: 'Load',
|
||||||
noSessions: 'No history found.',
|
noSessions: 'No history found.',
|
||||||
|
// ... existing ...
|
||||||
phase1: 'Product & Constraints',
|
phase1: 'Product & Constraints',
|
||||||
phase2: 'ICP Discovery',
|
phase2: 'ICP Discovery',
|
||||||
phase3: 'Whale Hunting',
|
phase3: 'Whale Hunting',
|
||||||
@@ -78,6 +80,7 @@ const TRANSLATIONS = {
|
|||||||
imageUploaded: 'Reference Images',
|
imageUploaded: 'Reference Images',
|
||||||
canvasClear: 'Clear',
|
canvasClear: 'Clear',
|
||||||
canvasMode: 'Sketch Mode',
|
canvasMode: 'Sketch Mode',
|
||||||
|
// New placeholders
|
||||||
addName: 'Name...',
|
addName: 'Name...',
|
||||||
addRationale: 'Rationale...',
|
addRationale: 'Rationale...',
|
||||||
addTarget: 'Target criteria...',
|
addTarget: 'Target criteria...',
|
||||||
@@ -164,6 +167,7 @@ const TRANSLATIONS = {
|
|||||||
imageUploaded: 'Referenzbilder',
|
imageUploaded: 'Referenzbilder',
|
||||||
canvasClear: 'Leeren',
|
canvasClear: 'Leeren',
|
||||||
canvasMode: 'Zeichenmodus',
|
canvasMode: 'Zeichenmodus',
|
||||||
|
// New placeholders
|
||||||
addName: 'Name...',
|
addName: 'Name...',
|
||||||
addRationale: 'Begründung...',
|
addRationale: 'Begründung...',
|
||||||
addTarget: 'Zielkriterium...',
|
addTarget: 'Zielkriterium...',
|
||||||
@@ -187,7 +191,7 @@ const App: React.FC = () => {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
history: [],
|
history: [],
|
||||||
productInput: '',
|
productInput: '',
|
||||||
productImages: [],
|
productImages: [], // Now an array
|
||||||
language: 'light', // temporary init, fix in useEffect
|
language: 'light', // temporary init, fix in useEffect
|
||||||
theme: 'light' // Default to light
|
theme: 'light' // Default to light
|
||||||
});
|
});
|
||||||
@@ -324,64 +328,21 @@ const App: React.FC = () => {
|
|||||||
const data = await Gemini.loadSession(projectId);
|
const data = await Gemini.loadSession(projectId);
|
||||||
const phases = data.phases || {};
|
const phases = data.phases || {};
|
||||||
|
|
||||||
console.log("Raw Loaded Phases from DB:", phases);
|
|
||||||
|
|
||||||
// Helper to safely parse potentially MULTIPLE times stringified JSON
|
|
||||||
const parsePhaseData = (phaseData: any) => {
|
|
||||||
if (!phaseData) return undefined;
|
|
||||||
let parsed = phaseData;
|
|
||||||
try {
|
|
||||||
// Versuch 1
|
|
||||||
if (typeof parsed === 'string') parsed = JSON.parse(parsed);
|
|
||||||
// Versuch 2 (falls double encoded)
|
|
||||||
if (typeof parsed === 'string') parsed = JSON.parse(parsed);
|
|
||||||
// Versuch 3 (extrem)
|
|
||||||
if (typeof parsed === 'string') parsed = JSON.parse(parsed);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Failed to parse phase data", e, phaseData);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
};
|
|
||||||
|
|
||||||
const p1 = parsePhaseData(phases.phase1_result);
|
|
||||||
const p2 = parsePhaseData(phases.phase2_result);
|
|
||||||
const p3 = parsePhaseData(phases.phase3_result);
|
|
||||||
const p4 = parsePhaseData(phases.phase4_result);
|
|
||||||
const p5 = parsePhaseData(phases.phase5_result);
|
|
||||||
const p6 = parsePhaseData(phases.phase6_result);
|
|
||||||
const p7 = parsePhaseData(phases.phase7_result);
|
|
||||||
const p8 = parsePhaseData(phases.phase8_result);
|
|
||||||
const p9 = parsePhaseData(phases.phase9_result);
|
|
||||||
|
|
||||||
console.log("Parsed Phase 2:", p2); // Debug
|
|
||||||
|
|
||||||
// Determine the highest available phase to jump to
|
|
||||||
let targetPhase = Phase.ProductAnalysis;
|
|
||||||
if (p9) targetPhase = Phase.TechTranslator;
|
|
||||||
else if (p8) targetPhase = Phase.BusinessCase;
|
|
||||||
else if (p7) targetPhase = Phase.LandingPage;
|
|
||||||
else if (p6) targetPhase = Phase.SalesEnablement;
|
|
||||||
else if (p5) targetPhase = Phase.AssetGeneration;
|
|
||||||
else if (p4) targetPhase = Phase.Strategy;
|
|
||||||
else if (p3) targetPhase = Phase.WhaleHunting;
|
|
||||||
else if (p2) targetPhase = Phase.ICPDiscovery;
|
|
||||||
|
|
||||||
setState(s => ({
|
setState(s => ({
|
||||||
...s,
|
...s,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
currentPhase: targetPhase,
|
currentPhase: Phase.ProductAnalysis,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
productInput: p1?.rawAnalysis || "",
|
productInput: phases.phase1_result?.rawAnalysis || "",
|
||||||
phase1Result: p1,
|
phase1Result: phases.phase1_result,
|
||||||
phase2Result: p2,
|
phase2Result: phases.phase2_result,
|
||||||
phase3Result: p3,
|
phase3Result: phases.phase3_result,
|
||||||
phase4Result: p4,
|
phase4Result: phases.phase4_result,
|
||||||
phase5Result: p5,
|
phase5Result: phases.phase5_result,
|
||||||
phase6Result: p6,
|
phase6Result: phases.phase6_result,
|
||||||
phase7Result: p7,
|
phase7Result: phases.phase7_result,
|
||||||
phase8Result: p8,
|
phase8Result: phases.phase8_result,
|
||||||
phase9Result: p9,
|
phase9Result: phases.phase9_result,
|
||||||
}));
|
}));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError("Failed to load session: " + e.message);
|
setError("Failed to load session: " + e.message);
|
||||||
@@ -419,8 +380,6 @@ const App: React.FC = () => {
|
|||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Asset Generation Helpers ---
|
|
||||||
|
|
||||||
const generateFullReportMarkdown = (): string => {
|
const generateFullReportMarkdown = (): string => {
|
||||||
if (!state.phase5Result || !state.phase5Result.report) return "";
|
if (!state.phase5Result || !state.phase5Result.report) return "";
|
||||||
|
|
||||||
@@ -438,11 +397,7 @@ const App: React.FC = () => {
|
|||||||
state.phase6Result.visualPrompts.forEach(prompt => {
|
state.phase6Result.visualPrompts.forEach(prompt => {
|
||||||
fullReport += `### ${prompt.title}\n`;
|
fullReport += `### ${prompt.title}\n`;
|
||||||
fullReport += `*Context: ${prompt.context}*\n\n`;
|
fullReport += `*Context: ${prompt.context}*\n\n`;
|
||||||
fullReport += `\
|
fullReport += `\`\`\`\n${prompt.prompt}\n\`\`\`\n\n`;
|
||||||
\
|
|
||||||
${prompt.prompt}\n\
|
|
||||||
\
|
|
||||||
`;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +487,7 @@ ${prompt.prompt}\n\
|
|||||||
// If not sketching, use the array of uploaded product images
|
// If not sketching, use the array of uploaded product images
|
||||||
const refImages = overrideReference ? [overrideReference] : state.productImages;
|
const refImages = overrideReference ? [overrideReference] : state.productImages;
|
||||||
|
|
||||||
const imageUrl = await Gemini.generateConceptImage(prompt, refImages, state.projectId);
|
const imageUrl = await Gemini.generateConceptImage(prompt, refImages);
|
||||||
setGeneratedImages(prev => ({ ...prev, [index]: imageUrl }));
|
setGeneratedImages(prev => ({ ...prev, [index]: imageUrl }));
|
||||||
|
|
||||||
// If we were editing, close edit mode
|
// If we were editing, close edit mode
|
||||||
@@ -547,7 +502,93 @@ ${prompt.prompt}\n\
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ... (Upload handlers remain same) ...
|
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const files = event.target.files;
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
Array.from(files).forEach((file: File) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
if (reader.result) {
|
||||||
|
setState(s => ({ ...s, productImages: [...s.productImages, reader.result as string] }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeUploadedImage = (index: number) => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
productImages: s.productImages.filter((_, i) => i !== index)
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Drawing / Editing Handlers ---
|
||||||
|
|
||||||
|
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||||
|
setIsDrawing(true);
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const scaleX = canvas.width / rect.width;
|
||||||
|
const scaleY = canvas.height / rect.height;
|
||||||
|
const x = (('touches' in e ? e.touches[0].clientX : e.clientX) - rect.left) * scaleX;
|
||||||
|
const y = (('touches' in e ? e.touches[0].clientY : e.clientY) - rect.top) * scaleY;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
const draw = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||||
|
if (!isDrawing) return;
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const scaleX = canvas.width / rect.width;
|
||||||
|
const scaleY = canvas.height / rect.height;
|
||||||
|
const x = (('touches' in e ? e.touches[0].clientX : e.clientX) - rect.left) * scaleX;
|
||||||
|
const y = (('touches' in e ? e.touches[0].clientY : e.clientY) - rect.top) * scaleY;
|
||||||
|
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
ctx.strokeStyle = brushColor;
|
||||||
|
ctx.lineWidth = brushSize;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopDrawing = () => {
|
||||||
|
setIsDrawing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearCanvas = () => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (canvas && editingIndex !== null) {
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
ctx.clearRect(0,0, canvas.width, canvas.height);
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
}
|
||||||
|
img.src = generatedImages[editingIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRegenerateWithSketch = (index: number, prompt: string) => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (canvas) {
|
||||||
|
const sketchData = canvas.toDataURL('image/png');
|
||||||
|
handleGenerateImage(prompt, index, sketchData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- Handlers ---
|
// --- Handlers ---
|
||||||
|
|
||||||
@@ -557,7 +598,7 @@ ${prompt.prompt}\n\
|
|||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const result = await Gemini.analyzeProduct(state.productInput, language);
|
const result = await Gemini.analyzeProduct(state.productInput, language);
|
||||||
setState(s => ({ ...s, currentPhase: Phase.ProductAnalysis, phase1Result: result, isLoading: false, projectId: result.projectId }));
|
setState(s => ({ ...s, currentPhase: Phase.ProductAnalysis, phase1Result: result, isLoading: false, projectId: result.projectId })); // Save projectId!
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError(e.message || "Analysis failed");
|
setError(e.message || "Analysis failed");
|
||||||
setState(s => ({ ...s, isLoading: false }));
|
setState(s => ({ ...s, isLoading: false }));
|
||||||
@@ -1160,9 +1201,7 @@ ${prompt.prompt}\n\
|
|||||||
{state.isLoading ? (
|
{state.isLoading ? (
|
||||||
<span className="flex items-center gap-2"><Loader2 className="animate-spin"/> {labels.processing}</span>
|
<span className="flex items-center gap-2"><Loader2 className="animate-spin"/> {labels.processing}</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<> {labels.confirmICP} <ArrowRight size={18} /></>
|
||||||
{labels.confirmICP} <ArrowRight size={18} />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Phase, Language, Theme } from '../types';
|
import { Phase, Language, Theme } from '../types';
|
||||||
import { Activity, Target, Crosshair, Map, FileText, CheckCircle, Lock, Moon, Sun, Languages, ShieldCheck, Terminal, LayoutTemplate, TrendingUp, Shield } from 'lucide-react';
|
import { Activity, Target, Crosshair, Map, FileText, CheckCircle, Lock, Moon, Sun, Languages, ShieldCheck, Terminal, LayoutTemplate, TrendingUp, Shield, Menu, X } from 'lucide-react';
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
currentPhase: Phase;
|
currentPhase: Phase;
|
||||||
@@ -41,6 +41,7 @@ export const Layout: React.FC<LayoutProps> = ({
|
|||||||
setLanguage,
|
setLanguage,
|
||||||
labels
|
labels
|
||||||
}) => {
|
}) => {
|
||||||
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{ id: Phase.Input, label: labels.initTitle ? 'Input' : 'Input' },
|
{ id: Phase.Input, label: labels.initTitle ? 'Input' : 'Input' },
|
||||||
@@ -57,18 +58,34 @@ export const Layout: React.FC<LayoutProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen flex font-sans transition-colors duration-300 ${theme === 'dark' ? 'dark bg-robo-900 text-slate-200' : 'bg-slate-50 text-slate-900'}`}>
|
<div className={`min-h-screen flex font-sans transition-colors duration-300 ${theme === 'dark' ? 'dark bg-robo-900 text-slate-200' : 'bg-slate-50 text-slate-900'}`}>
|
||||||
|
|
||||||
|
{/* Mobile Backdrop */}
|
||||||
|
{isSidebarOpen && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/50 z-40 md:hidden backdrop-blur-sm"
|
||||||
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="w-80 border-r flex-shrink-0 flex flex-col transition-colors duration-300
|
<div className={`
|
||||||
bg-white border-slate-200
|
fixed inset-y-0 left-0 z-50 w-80 border-r flex-shrink-0 flex flex-col transition-all duration-300 transform
|
||||||
dark:bg-robo-800 dark:border-robo-700
|
bg-white border-slate-200 dark:bg-robo-800 dark:border-robo-700
|
||||||
">
|
${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||||
<div className="p-6 border-b transition-colors duration-300 border-slate-200 dark:border-robo-700">
|
md:relative md:translate-x-0
|
||||||
|
`}>
|
||||||
|
<div className="p-6 border-b transition-colors duration-300 border-slate-200 dark:border-robo-700 flex justify-between items-center">
|
||||||
|
<div>
|
||||||
<h1 className="text-xl font-bold font-mono tracking-tighter flex items-center gap-2 text-slate-900 dark:text-white">
|
<h1 className="text-xl font-bold font-mono tracking-tighter flex items-center gap-2 text-slate-900 dark:text-white">
|
||||||
<div className="w-3 h-3 bg-robo-500 dark:bg-robo-accent rounded-full animate-pulse"></div>
|
<div className="w-3 h-3 bg-robo-500 dark:bg-robo-accent rounded-full animate-pulse"></div>
|
||||||
ROBOPLANET
|
ROBOPLANET
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs mt-1 uppercase tracking-widest text-slate-500 dark:text-robo-400">GTM Architect Engine</p>
|
<p className="text-xs mt-1 uppercase tracking-widest text-slate-500 dark:text-robo-400">GTM Architect Engine</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button onClick={() => setIsSidebarOpen(false)} className="md:hidden p-2 text-slate-500">
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="px-4 py-4 flex gap-2">
|
<div className="px-4 py-4 flex gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -101,7 +118,12 @@ export const Layout: React.FC<LayoutProps> = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={step.id}
|
key={step.id}
|
||||||
onClick={() => isUnlocked && onPhaseSelect(step.id)}
|
onClick={() => {
|
||||||
|
if (isUnlocked) {
|
||||||
|
onPhaseSelect(step.id);
|
||||||
|
setIsSidebarOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={!isUnlocked}
|
disabled={!isUnlocked}
|
||||||
className={`w-full flex items-center gap-3 p-3 rounded-lg transition-all border text-left
|
className={`w-full flex items-center gap-3 p-3 rounded-lg transition-all border text-left
|
||||||
${isActive
|
${isActive
|
||||||
@@ -149,6 +171,14 @@ export const Layout: React.FC<LayoutProps> = ({
|
|||||||
bg-slate-50
|
bg-slate-50
|
||||||
dark:bg-gradient-to-br dark:from-robo-900 dark:to-[#0b1120]
|
dark:bg-gradient-to-br dark:from-robo-900 dark:to-[#0b1120]
|
||||||
">
|
">
|
||||||
|
{/* Mobile Header Toggle */}
|
||||||
|
<header className="md:hidden flex items-center p-4 border-b bg-white dark:bg-robo-800 border-slate-200 dark:border-robo-700">
|
||||||
|
<button onClick={() => setIsSidebarOpen(true)} className="p-2 text-slate-600 dark:text-slate-300">
|
||||||
|
<Menu size={24} />
|
||||||
|
</button>
|
||||||
|
<h1 className="ml-2 font-bold font-mono tracking-tighter text-slate-900 dark:text-white">ROBOPLANET</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div className="max-w-5xl mx-auto p-8 pb-32">
|
<div className="max-w-5xl mx-auto p-8 pb-32">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,8 +102,9 @@ export const translateReportToEnglish = async (reportMarkdown: string): Promise<
|
|||||||
return callApi<{ report: string }>('run', 'translate', { reportMarkdown });
|
return callApi<{ report: string }>('run', 'translate', { reportMarkdown });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[], projectId?: string): Promise<string> => {
|
export const generateConceptImage = async (prompt: string, referenceImagesBase64?: string[]): Promise<string> => {
|
||||||
return (await callApi<{ imageBase64: string }>('run', 'image', { prompt, referenceImagesBase64, projectId })).imageBase64;
|
const result = await callApi<{ imageBase64: string }>('run', 'image', { prompt, referenceImagesBase64 });
|
||||||
|
return result.imageBase64;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSessions = async (): Promise<{ projects: ProjectHistoryItem[] }> => {
|
export const listSessions = async (): Promise<{ projects: ProjectHistoryItem[] }> => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
@@ -202,9 +203,7 @@ def phase1(payload):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase1_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary, not stringified JSON, to avoid double encoding
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase1_result', data)
|
|
||||||
|
|
||||||
# WICHTIG: ID zurückgeben, damit Frontend sie speichert
|
# WICHTIG: ID zurückgeben, damit Frontend sie speichert
|
||||||
data['projectId'] = project_id
|
data['projectId'] = project_id
|
||||||
@@ -214,7 +213,7 @@ def phase1(payload):
|
|||||||
error_response = {
|
error_response = {
|
||||||
"error": "Die Antwort des KI-Modells war kein gültiges JSON. Das passiert manchmal bei hoher Auslastung. Bitte versuchen Sie es in Kürze erneut.",
|
"error": "Die Antwort des KI-Modells war kein gültiges JSON. Das passiert manchmal bei hoher Auslastung. Bitte versuchen Sie es in Kürze erneut.",
|
||||||
"details": response,
|
"details": response,
|
||||||
"projectId": project_id
|
"projectId": project_id # Auch bei Fehler ID zurückgeben? Besser nicht, da noch nichts gespeichert.
|
||||||
}
|
}
|
||||||
return error_response
|
return error_response
|
||||||
|
|
||||||
@@ -240,9 +239,7 @@ def phase2(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase2", "response", response)
|
log_and_save(project_id, "phase2", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase2_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase2_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def phase3(payload):
|
def phase3(payload):
|
||||||
@@ -266,9 +263,7 @@ def phase3(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase3", "response", response)
|
log_and_save(project_id, "phase3", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase3_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase3_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def phase4(payload):
|
def phase4(payload):
|
||||||
@@ -299,9 +294,7 @@ def phase4(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase4", "response", response)
|
log_and_save(project_id, "phase4", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase4_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase4_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def phase5(payload):
|
def phase5(payload):
|
||||||
@@ -372,9 +365,7 @@ def phase5(payload):
|
|||||||
report = report.strip()
|
report = report.strip()
|
||||||
|
|
||||||
log_and_save(project_id, "phase5", "response", report)
|
log_and_save(project_id, "phase5", "response", report)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase5_result', json.dumps({"report": report}))
|
||||||
# FIX: Save raw dictionary (no double JSON stringification)
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase5_result', {"report": report})
|
|
||||||
return {"report": report}
|
return {"report": report}
|
||||||
|
|
||||||
def phase6(payload):
|
def phase6(payload):
|
||||||
@@ -400,9 +391,7 @@ def phase6(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase6", "response", response)
|
log_and_save(project_id, "phase6", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase6_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase6_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def phase7(payload):
|
def phase7(payload):
|
||||||
@@ -428,9 +417,7 @@ def phase7(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase7", "response", response)
|
log_and_save(project_id, "phase7", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase7_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase7_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def phase8(payload):
|
def phase8(payload):
|
||||||
@@ -455,9 +442,7 @@ def phase8(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase8", "response", response)
|
log_and_save(project_id, "phase8", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase8_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase8_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def phase9(payload):
|
def phase9(payload):
|
||||||
@@ -483,9 +468,7 @@ def phase9(payload):
|
|||||||
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
response = call_gemini_flash(prompt, system_instruction=sys_instr, json_mode=True)
|
||||||
log_and_save(project_id, "phase9", "response", response)
|
log_and_save(project_id, "phase9", "response", response)
|
||||||
data = json.loads(response)
|
data = json.loads(response)
|
||||||
|
db_manager.save_gtm_result(project_id, 'phase9_result', json.dumps(data))
|
||||||
# FIX: Save raw dictionary
|
|
||||||
db_manager.save_gtm_result(project_id, 'phase9_result', data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def translate(payload):
|
def translate(payload):
|
||||||
|
|||||||
Reference in New Issue
Block a user