From ffa0d2bd91e305e2d9e577d09f5ea8d0b3680ece Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Mon, 29 Dec 2025 03:00:42 +0400 Subject: [PATCH] fix: agent selection persistence + premium SEO report design - Fixed agent selection to persist when switching tabs (saves to tab state) - Added CanvasErrorBoundary to gracefully catch render crashes - Added defensive null checks for tab state access - Overhauled SEO agent prompt with detailed Google-style dashboard requirements: - Dark theme (bg-slate-900) - Animated SVG progress rings with stroke-dasharray - Color-coded scoring (green/amber/red) - Tailwind CDN + Google Fonts (Inter/Roboto) - Card layouts with backdrop-blur and subtle borders - Key Recommendations section with icons - Responsive max-w-4xl layout --- components/AIAssist.tsx | 56 +++++++++++++++++++++++++++++------- lib/services/ollama-cloud.ts | 14 ++++++++- lib/services/qwen-oauth.ts | 14 ++++++++- lib/services/zai-plan.ts | 14 ++++++++- 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/components/AIAssist.tsx b/components/AIAssist.tsx index c1137aa..f1f9d02 100644 --- a/components/AIAssist.tsx +++ b/components/AIAssist.tsx @@ -34,6 +34,37 @@ interface PreviewData { * A ultra-stable iframe wrapper that avoids hydration issues * and provides a WOW visual experience. */ + +// Error Boundary for Canvas crashes +class CanvasErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean, error: string | null }> { + constructor(props: { children: React.ReactNode }) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, error: error.message }; + } + + render() { + if (this.state.hasError) { + return ( +
+ +

Canvas Crashed

+

{this.state.error}

+ +
+ ); + } + return this.props.children; + } +} const BuildingArtifact = ({ type }: { type: string }) => { const [progress, setProgress] = useState(0); const steps = [ @@ -393,7 +424,7 @@ export default function AIAssist() { } = useStore(); const t = translations[language].aiAssist; - const activeTab = aiAssistTabs.find(t => t.id === activeTabId) || aiAssistTabs[0]; + const activeTab = aiAssistTabs?.find(t => t.id === activeTabId) || aiAssistTabs?.[0] || { id: 'default', title: 'New Chat', history: [], currentAgent: 'general' }; const aiAssistHistory = activeTab?.history || []; const [input, setInput] = useState(""); @@ -404,10 +435,10 @@ export default function AIAssist() { // Sync local state when tab changes useEffect(() => { if (activeTab) { - setCurrentAgent(activeTab.currentAgent); - setPreviewData(activeTab.previewData); + setCurrentAgent(activeTab.currentAgent || "general"); + setPreviewData(activeTab.previewData || null); } - }, [activeTabId]); + }, [activeTabId, activeTab]); const [availableModels, setAvailableModels] = useState([]); const [showCanvas, setShowCanvas] = useState(false); const [viewMode, setViewMode] = useState<"preview" | "code">("preview"); @@ -716,7 +747,10 @@ export default function AIAssist() { ].map(({ label, agent, icon }) => (