import { useRoute, useLocation } from "wouter"; import { useQuery, useMutation } from "@tanstack/react-query"; import { BrowserFrame } from "@/components/Browser/BrowserFrame"; import { Button } from "@/components/ui/button"; import { ChevronLeft, ExternalLink, RefreshCw, AlertCircle, Brain, Loader2, Check, Sparkles, LogIn } from "lucide-react"; import { LoginBridgePopup } from "@/components/LoginBridgePopup"; import { useState, useRef, useMemo, useEffect, useCallback } from "react"; import { useNavigationTracking } from "@/hooks/use-navigation-tracking"; import { useToast } from "@/hooks/use-toast"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; const CAPTURE_COOLDOWN_MS = 30 * 60 * 1000; const AUTO_CAPTURE_DELAY_MS = 3000; function getLastCaptureTime(url: string): number { try { const stored = localStorage.getItem('capture_timestamps'); if (!stored) return 0; const timestamps = JSON.parse(stored) as Record; return timestamps[url] || 0; } catch { return 0; } } function setLastCaptureTime(url: string): void { try { const stored = localStorage.getItem('capture_timestamps'); const timestamps = stored ? JSON.parse(stored) as Record : {}; timestamps[url] = Date.now(); const keys = Object.keys(timestamps); if (keys.length > 100) { const sorted = keys.sort((a, b) => timestamps[a] - timestamps[b]); for (let i = 0; i < 50; i++) { delete timestamps[sorted[i]]; } } localStorage.setItem('capture_timestamps', JSON.stringify(timestamps)); } catch {} } function canAutoCapture(url: string): boolean { const lastCapture = getLastCaptureTime(url); return Date.now() - lastCapture > CAPTURE_COOLDOWN_MS; } interface Application { id: string; name: string; category: string; icon: string; status: string; url?: string; description?: string; } function normalizeUrl(url: string | undefined): string | undefined { if (!url) return undefined; const trimmed = url.trim(); if (!trimmed) return undefined; if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { return trimmed; } return `https://${trimmed}`; } function isExternalUrl(url: string): boolean { if (url.startsWith("/")) return false; try { const parsed = new URL(url); return parsed.hostname !== window.location.hostname; } catch { return false; } } function getProxiedUrl(url: string): string { if (!isExternalUrl(url)) return url; return `/api/proxy/frame?url=${encodeURIComponent(url)}`; } export default function AppViewer() { const [, params] = useRoute("/app/:id"); const [, navigate] = useLocation(); const appId = params?.id; const iframeRef = useRef(null); const [isLoading, setIsLoading] = useState(true); const [hasError, setHasError] = useState(false); const [showCaptureDialog, setShowCaptureDialog] = useState(false); const [captureResult, setCaptureResult] = useState(null); const [extractRequirements, setExtractRequirements] = useState(true); const [showLoginBridge, setShowLoginBridge] = useState(false); const [loginPending, setLoginPending] = useState(false); const [useDirectMode, setUseDirectMode] = useState(false); const [externalWindow, setExternalWindow] = useState(null); const [externalMode, setExternalMode] = useState(false); const { trackAppOpen, trackSiteNavigation, trackIframeInteraction } = useNavigationTracking(); const { toast } = useToast(); const { data: app, isLoading: appLoading } = useQuery({ queryKey: ["/api/applications", appId], queryFn: async () => { const res = await fetch(`/api/applications/${appId}`, { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch application"); return res.json(); }, enabled: !!appId, }); const normalizedUrl = useMemo(() => normalizeUrl(app?.url), [app?.url]); const proxiedUrl = useMemo(() => normalizedUrl ? getProxiedUrl(normalizedUrl) : undefined, [normalizedUrl]); const isExternal = useMemo(() => normalizedUrl ? isExternalUrl(normalizedUrl) : false, [normalizedUrl]); const captureMutation = useMutation({ mutationFn: async () => { const res = await fetch("/api/learning/capture", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ url: normalizedUrl, appName: app?.name, extractRequirements, context: `Aplicativo: ${app?.name}, Categoria: ${app?.category}` }) }); if (!res.ok) throw new Error("Falha na captura"); return res.json(); }, onSuccess: (data) => { setCaptureResult(data); setShowCaptureDialog(true); toast({ title: "Conhecimento capturado!", description: `${data.word_count} palavras extraídas de ${data.title || app?.name}`, }); }, onError: () => { toast({ title: "Erro na captura", description: "Não foi possível capturar o conteúdo da página", variant: "destructive" }); } }); useEffect(() => { if (app && normalizedUrl) { trackAppOpen(app.name, normalizedUrl, app.category); trackSiteNavigation(app.name, normalizedUrl); } }, [app, normalizedUrl, trackAppOpen, trackSiteNavigation]); const autoCaptureSilent = useCallback(async () => { if (!normalizedUrl || !app) return; if (!canAutoCapture(normalizedUrl)) { console.debug('[AutoCapture] Skipped - within cooldown for', normalizedUrl); return; } try { const res = await fetch("/api/learning/capture", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ url: normalizedUrl, appName: app.name, extractRequirements: false, context: `Captura automática: ${app.name}` }) }); if (res.ok) { setLastCaptureTime(normalizedUrl); const data = await res.json(); console.debug('[AutoCapture] Success:', data.word_count, 'words from', data.title); } } catch (err) { console.debug('[AutoCapture] Failed silently:', err); } }, [normalizedUrl, app]); const handleIframeLoad = () => { setIsLoading(false); if (app && normalizedUrl) { trackIframeInteraction(app.name, normalizedUrl, "iframe_loaded"); setTimeout(() => { autoCaptureSilent(); }, AUTO_CAPTURE_DELAY_MS); } }; const handleRefresh = () => { if (iframeRef.current && proxiedUrl) { setIsLoading(true); setHasError(false); iframeRef.current.src = proxiedUrl; } }; const handleOpenExternal = () => { if (normalizedUrl) { window.open(normalizedUrl, "_blank"); } }; const handleOpenExternalWithTracking = () => { if (normalizedUrl) { const win = window.open(normalizedUrl, `arcadia_${app?.name || 'app'}`); if (win) { setExternalWindow(win); setExternalMode(true); toast({ title: "Navegador externo aberto", description: "Use o botão 'Capturar' para salvar o conhecimento da página atual.", }); } } }; const handleCaptureExternal = async () => { if (!normalizedUrl || !app) return; captureMutation.mutate(); }; // Auto-capture em modo externo - captura a cada 30 segundos const [autoCapturing, setAutoCapturing] = useState(false); const [captureCount, setCaptureCount] = useState(0); const autoCaptureIntervalRef = useRef(null); const startAutoCapture = () => { if (autoCaptureIntervalRef.current) return; setAutoCapturing(true); toast({ title: "Monitoramento ativo", description: "Capturando conhecimento automaticamente a cada 30 segundos.", }); // Captura inicial handleCaptureExternal(); setCaptureCount(1); // Intervalo de captura autoCaptureIntervalRef.current = setInterval(() => { handleCaptureExternal(); setCaptureCount(c => c + 1); }, 30000); // 30 segundos }; const stopAutoCapture = () => { if (autoCaptureIntervalRef.current) { clearInterval(autoCaptureIntervalRef.current); autoCaptureIntervalRef.current = null; } setAutoCapturing(false); toast({ title: "Monitoramento pausado", description: `${captureCount} capturas realizadas.`, }); }; // Limpar intervalo ao desmontar ou sair do modo externo useEffect(() => { return () => { if (autoCaptureIntervalRef.current) { clearInterval(autoCaptureIntervalRef.current); } }; }, []); useEffect(() => { if (!externalMode && autoCaptureIntervalRef.current) { clearInterval(autoCaptureIntervalRef.current); autoCaptureIntervalRef.current = null; setAutoCapturing(false); } }, [externalMode]); if (appLoading) { return (

Carregando aplicativo...

); } if (!app) { return (

Aplicativo não encontrado

); } if (!normalizedUrl) { return (
{app.name[0]}

{app.name}

{app.description || "Este aplicativo não possui uma URL configurada."}

); } return (
{app.name[0]}
{app.name} {normalizedUrl}
{isExternal && ( <> )}
{isLoading && (

Carregando {app.name}...

)} {(hasError || externalMode) && (
{app.name[0]}

{app.name}

{externalMode ? "Navegando em janela externa. O Arcadia está monitorando e pode capturar conhecimento." : "Este site funciona melhor em janela externa."}

{!externalMode ? ( <>

O Arcadia continua ativo e pode capturar o que você está vendo

) : ( <>
{autoCapturing ? `Monitorando... (${captureCount} capturas)` : 'Janela externa ativa'}
{!autoCapturing ? ( ) : ( )}
{autoCapturing ? "Capturando automaticamente a cada 30 segundos" : "O motor vai capturar conhecimento enquanto você navega"}
)}
{externalMode && (

Navegue normalmente na janela externa. Clique em "Capturar" a qualquer momento para salvar o conteúdo na base de conhecimento.

)}
)}