import { BrowserFrame } from "@/components/Browser/BrowserFrame"; import { useState, useRef, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { MessageSquare, Send, Plus, Trash2, Bot, User, Loader2, Paperclip, FileText, X, Zap, Play, CheckCircle, XCircle, Brain, Wrench, Search, Database, Calculator, Calendar, Globe, Sparkles, MessageCircle, ChevronDown, ChevronRight, Download, FileDown, Table, Compass, FolderKanban, Pencil, Check, HelpCircle, ArrowRight, Network, Mail, BarChart3, Settings2, ThumbsUp, ThumbsDown, Eye, TrendingUp, Clock } from "lucide-react"; import { CardDescription } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { jsPDF } from "jspdf"; import { Document, Packer, Paragraph, TextRun } from "docx"; import { saveAs } from "file-saver"; import html2canvas from "html2canvas"; import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell } from "recharts"; import { ResultViewer } from "@/components/ResultViewer"; import DevHistory from "@/components/DevHistory"; interface ChartData { type: 'bar' | 'line' | 'pie' | 'area'; title: string; data: Record[]; xKey: string; yKeys: string[]; colors: string[]; } function parseChartData(text: string): ChartData | null { const match = text.match(/__CHART_DATA__([\s\S]*?)__END_CHART_DATA__/); if (match) { try { return JSON.parse(match[1]); } catch { return null; } } return null; } function MarkdownTable({ content }: { content: string }) { const lines = content.split('\n').filter(line => line.includes('|')); if (lines.length < 2) return null; const parseRow = (line: string) => line.split('|').map(cell => cell.trim()).filter(cell => cell && !cell.match(/^-+$/)); const headers = parseRow(lines[0]); const dataRows = lines.slice(2).map(parseRow).filter(row => row.length > 0); if (headers.length === 0) return null; return (
{headers.map((header, idx) => ( ))} {dataRows.map((row, rowIdx) => ( {row.map((cell, cellIdx) => ( ))} ))}
{header}
{cell}
); } function RichTextRenderer({ content }: { content: string }) { const hasTable = content.includes('|') && content.split('\n').filter(l => l.includes('|')).length >= 2; if (!hasTable) { return

{content}

; } const parts: React.ReactNode[] = []; const lines = content.split('\n'); let currentText: string[] = []; let inTable = false; let tableLines: string[] = []; lines.forEach((line, idx) => { const isTableLine = line.includes('|') && line.trim().startsWith('|'); if (isTableLine && !inTable) { if (currentText.length > 0) { parts.push(

{currentText.join('\n')}

); currentText = []; } inTable = true; tableLines = [line]; } else if (isTableLine && inTable) { tableLines.push(line); } else if (!isTableLine && inTable) { parts.push(); tableLines = []; inTable = false; if (line.trim()) currentText.push(line); } else { currentText.push(line); } }); if (inTable && tableLines.length > 0) { parts.push(); } if (currentText.length > 0) { parts.push(

{currentText.join('\n')}

); } return <>{parts}; } function ChartRenderer({ chart }: { chart: ChartData }) { const formatValue = (value: number) => { if (value >= 1000000) return `R$ ${(value / 1000000).toFixed(1)}M`; if (value >= 1000) return `R$ ${(value / 1000).toFixed(0)}K`; return `R$ ${value.toFixed(0)}`; }; const renderChart = () => { switch (chart.type) { case 'bar': return ( formatValue(value)} /> {chart.yKeys.map((key, idx) => ( ))} ); case 'line': return ( formatValue(value)} /> {chart.yKeys.map((key, idx) => ( ))} ); case 'area': return ( formatValue(value)} /> {chart.yKeys.map((key, idx) => ( ))} ); case 'pie': return ( `${name}: ${formatValue(value)}`} > {chart.data.map((_, idx) => ( ))} formatValue(value)} /> ); default: return
Tipo de gráfico não suportado
; } }; return (

{chart.title}

{renderChart()}
); } interface Message { id: number; role: "user" | "assistant"; content: string; createdAt: string; interactionId?: number; feedback?: 'positive' | 'negative' | 'neutral' | null; } interface Conversation { id: number; title: string; userId: string; createdAt: string; messages?: Message[]; } interface AttachedFile { name: string; content: string; base64?: string; isImage?: boolean; preview?: string; } interface ManusStep { id: number; stepNumber: number; thought: string | null; tool: string | null; toolInput: string | null; toolOutput: string | null; status: string | null; createdAt: string; } interface ManusRun { id: number; userId: string; prompt: string; status: string | null; result: string | null; createdAt: string; completedAt: string | null; steps?: ManusStep[]; } async function fetchConversations(): Promise { const response = await fetch("/api/conversations", { credentials: "include" }); if (!response.ok) throw new Error("Failed to fetch conversations"); return response.json(); } async function fetchConversation(id: number): Promise { const response = await fetch(`/api/conversations/${id}`, { credentials: "include" }); if (!response.ok) throw new Error("Failed to fetch conversation"); return response.json(); } async function createConversation(title: string = "Nova Conversa"): Promise { const response = await fetch("/api/conversations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title }), credentials: "include", }); if (!response.ok) throw new Error("Failed to create conversation"); return response.json(); } function generateConversationTitle(message: string): string { const cleanMessage = message.replace(/\[📎.*?\]\s*/g, "").trim(); const words = cleanMessage.split(/\s+/).slice(0, 6).join(" "); if (words.length > 40) { return words.substring(0, 40) + "..."; } return words || "Nova Conversa"; } async function deleteConversation(id: number): Promise { const response = await fetch(`/api/conversations/${id}`, { method: "DELETE", credentials: "include", }); if (!response.ok) throw new Error("Failed to delete conversation"); } async function updateConversation(id: number, title: string): Promise { const response = await fetch(`/api/conversations/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title }), credentials: "include", }); if (!response.ok) throw new Error("Failed to update conversation"); return response.json(); } async function fetchManusRuns(): Promise { const response = await fetch("/api/manus/runs", { credentials: "include" }); if (!response.ok) throw new Error("Failed to fetch runs"); return response.json(); } async function fetchManusRun(id: number): Promise { const response = await fetch(`/api/manus/runs/${id}`, { credentials: "include" }); if (!response.ok) throw new Error("Failed to fetch run"); return response.json(); } async function startManusRun(data: { prompt: string; attachedFiles?: AttachedFile[]; conversationHistory?: Array<{role: string; content: string}> }): Promise<{ runId: number }> { const response = await fetch("/api/manus/run", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: data.prompt, attachedFiles: data.attachedFiles, conversationHistory: data.conversationHistory }), credentials: "include", }); if (!response.ok) throw new Error("Failed to start run"); return response.json(); } const TOOL_NAMES: Record = { web_search: "Pesquisa Web", web_browse: "Navegar Site", knowledge_query: "Base de Conhecimento", erp_query: "Consulta ERP", calculate: "Cálculo", send_message: "Enviar Mensagem", generate_report: "Gerar Relatório", schedule_task: "Agendar Tarefa", analyze_file: "Analisar Arquivo", finish: "Finalizar", }; const TOOL_ICONS: Record = { web_search: , web_browse: , knowledge_query: , erp_query: , calculate: , send_message: , generate_report: , schedule_task: , analyze_file: , finish: , }; type ProcessingMode = "idle" | "thinking" | "using_tools"; type AgentMode = "chat" | "autonomous"; function StepCard({ step, isFinish, prompt }: { step: ManusStep; isFinish: boolean; prompt?: string }) { const [expanded, setExpanded] = useState(false); const [showViewer, setShowViewer] = useState(false); const exportToPDF = async () => { if (!step.toolOutput) return; const doc = new jsPDF(); const pageWidth = doc.internal.pageSize.getWidth(); const margin = 20; const maxWidth = pageWidth - margin * 2; doc.setFontSize(16); doc.setTextColor(31, 51, 77); doc.text("Arcádia Agent - Resultado", margin, 20); let currentY = 30; if (prompt) { doc.setFontSize(10); doc.setTextColor(90, 108, 125); doc.text("Tarefa:", margin, currentY); currentY += 7; const promptLines = doc.splitTextToSize(prompt, maxWidth); doc.text(promptLines, margin, currentY); currentY += promptLines.length * 5 + 10; } doc.setFontSize(12); doc.setTextColor(31, 51, 77); doc.text("Resposta:", margin, currentY); currentY += 8; const textContent = step.toolOutput.replace(/__CHART_DATA__[\s\S]*?__END_CHART_DATA__/g, '').trim(); doc.setFontSize(11); const lines = doc.splitTextToSize(textContent, maxWidth); doc.text(lines, margin, currentY); currentY += lines.length * 5 + 10; const chartData = parseChartData(step.toolOutput); if (chartData) { const chartContainer = document.querySelector('[data-testid="step-final-response"] .recharts-wrapper'); if (chartContainer) { try { const canvas = await html2canvas(chartContainer as HTMLElement, { backgroundColor: '#ffffff', scale: 2 }); const imgData = canvas.toDataURL('image/png'); const imgWidth = maxWidth; const imgHeight = (canvas.height * imgWidth) / canvas.width; if (currentY + imgHeight > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); currentY = margin; } doc.addImage(imgData, 'PNG', margin, currentY, imgWidth, imgHeight); } catch (e) { console.error('Error capturing chart:', e); } } } doc.save("arcadia-resultado.pdf"); }; const exportToWord = async () => { if (!step.toolOutput) return; const paragraphs = []; paragraphs.push( new Paragraph({ children: [new TextRun({ text: "Arcádia Agent - Resultado", bold: true, size: 32 })], }) ); if (prompt) { paragraphs.push( new Paragraph({ children: [] }), new Paragraph({ children: [new TextRun({ text: "Tarefa: ", bold: true }), new TextRun({ text: prompt })], }) ); } paragraphs.push( new Paragraph({ children: [] }), new Paragraph({ children: [new TextRun({ text: "Resposta:", bold: true })], }) ); step.toolOutput.split("\n").forEach((line) => { paragraphs.push(new Paragraph({ children: [new TextRun({ text: line })] })); }); const doc = new Document({ sections: [{ properties: {}, children: paragraphs }], }); const blob = await Packer.toBlob(doc); saveAs(blob, "arcadia-resultado.docx"); }; const exportToText = () => { if (!step.toolOutput) return; const content = prompt ? `Tarefa: ${prompt}\n\nResposta:\n${step.toolOutput}` : step.toolOutput; const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); saveAs(blob, "arcadia-resultado.txt"); }; const exportToCSV = () => { if (!step.toolOutput) return; const lines = step.toolOutput.split("\n"); let csvContent = ""; for (const line of lines) { if (line.includes("|")) { const cells = line.split("|").map(cell => cell.trim()).filter(cell => cell && !cell.match(/^[-:]+$/)); if (cells.length > 0) { csvContent += cells.map(cell => `"${cell.replace(/"/g, '""')}"`).join(",") + "\n"; } } else if (line.includes("\t")) { csvContent += line.split("\t").map(cell => `"${cell.trim().replace(/"/g, '""')}"`).join(",") + "\n"; } else if (line.trim()) { csvContent += `"${line.replace(/"/g, '""')}"\n`; } } const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8" }); saveAs(blob, "arcadia-resultado.csv"); }; const detectTableContent = () => { if (!step.toolOutput) return false; const lines = step.toolOutput.split("\n"); let tableLines = 0; for (const line of lines) { if ((line.includes("|") && line.split("|").length >= 3) || (line.includes("\t") && line.split("\t").length >= 2)) { tableLines++; } } return tableLines >= 2; }; const hasTableContent = detectTableContent(); if (isFinish) { return (
Resposta Final
{hasTableContent && (