From 642a85511342010ecc1fd5f25f7362a76a9f8f99 Mon Sep 17 00:00:00 2001 From: Floke Date: Sun, 4 Jan 2026 17:29:20 +0000 Subject: [PATCH] feat(gtm): add session history, database loading, and markdown file import --- gtm-architect/App.tsx | 323 +++++++++++++++++++++++---------- gtm-architect/geminiService.ts | 14 +- gtm-architect/types.ts | 10 +- gtm_architect_orchestrator.py | 43 ++++- 4 files changed, 292 insertions(+), 98 deletions(-) diff --git a/gtm-architect/App.tsx b/gtm-architect/App.tsx index 1dab050d..470c7836 100644 --- a/gtm-architect/App.tsx +++ b/gtm-architect/App.tsx @@ -1,13 +1,18 @@ import React, { useState, useEffect, useRef } from 'react'; import { Layout } from './components/Layout'; -import { Phase, AppState, Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Phase7Data, Phase8Data, Phase9Data, Language, Theme } from './types'; +import { Phase, AppState, Phase1Data, Phase2Data, Phase3Data, Phase4Data, Phase6Data, Phase7Data, Phase8Data, Phase9Data, Language, Theme, ProjectHistoryItem } from './types'; import * as Gemini from './geminiService'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; -import { AlertTriangle, ArrowRight, ArrowLeft, Check, Database, Globe, Search, ShieldAlert, Cpu, Building2, UserCircle, Briefcase, Zap, Terminal, Target, Crosshair, Loader2, Plus, X, Download, ShieldCheck, Image as ImageIcon, Copy, Sparkles, Upload, CheckCircle, PenTool, Eraser, Undo, Save, RefreshCw, Pencil, Trash2, LayoutTemplate, TrendingUp, Shield, Languages } from 'lucide-react'; +import { AlertTriangle, ArrowRight, ArrowLeft, Check, Database, Globe, Search, ShieldAlert, Cpu, Building2, UserCircle, Briefcase, Zap, Terminal, Target, Crosshair, Loader2, Plus, X, Download, ShieldCheck, Image as ImageIcon, Copy, Sparkles, Upload, CheckCircle, PenTool, Eraser, Undo, Save, RefreshCw, Pencil, Trash2, LayoutTemplate, TrendingUp, Shield, Languages, Clock, History, FileText } from 'lucide-react'; const TRANSLATIONS = { en: { + // ... existing ... + historyTitle: 'Recent Sessions', + loadBtn: 'Load', + noSessions: 'No history found.', + // ... existing ... phase1: 'Product & Constraints', phase2: 'ICP Discovery', phase3: 'Whale Hunting', @@ -92,6 +97,9 @@ const TRANSLATIONS = { ] }, de: { + historyTitle: 'Letzte Sitzungen', + loadBtn: 'Laden', + noSessions: 'Keine Historie gefunden.', phase1: 'Produkt & Constraints', phase2: 'ICP Entdeckung', phase3: 'Whale Hunting', @@ -196,6 +204,9 @@ const App: React.FC = () => { const [loadingMessage, setLoadingMessage] = useState(""); const [isTranslating, setIsTranslating] = useState(false); + // Session Management + const [sessions, setSessions] = useState([]); + // Local state for adding new items (Human in the Loop inputs) // Phase 1 const [newFeatureInput, setNewFeatureInput] = useState(""); @@ -235,6 +246,19 @@ 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; @@ -297,6 +321,65 @@ const App: React.FC = () => { 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, + })); + } catch (e: any) { + setError("Failed to load session: " + e.message); + setState(s => ({ ...s, isLoading: false })); + } + }; + + const handleDeleteSession = async (e: React.MouseEvent, projectId: string) => { + e.stopPropagation(); + 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) => { + 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 ""; @@ -515,7 +598,7 @@ const App: React.FC = () => { setError(null); try { const result = await Gemini.analyzeProduct(state.productInput, language); - setState(s => ({ ...s, currentPhase: Phase.ProductAnalysis, phase1Result: result, isLoading: false })); + setState(s => ({ ...s, currentPhase: Phase.ProductAnalysis, phase1Result: result, isLoading: false, projectId: result.projectId })); // Save projectId! } catch (e: any) { setError(e.message || "Analysis failed"); setState(s => ({ ...s, isLoading: false })); @@ -825,103 +908,153 @@ const App: React.FC = () => { const renderInputPhase = () => (
-
-

- - {labels.initTitle} -

+
-
-
- -