- {getSeverityIcon(finding.severity)}
-
-
{finding.category}
-
- #{finding.check_id}
+ {/* Sections Overview */}
+ {sections.length > 0 && (
+
+
+
+ {expandedSections.has("sections") && (
+
+ {sections.map((section, index) => (
+
+
+
+ {section.section}
+
+ {getSeverityIcon(section.severity)}
+
+ {Math.round(section.confidence * 100)}%
+
+
+
{section.summary}
-
-
-
+ ))}
+
+ )}
+
+ )}
+
+ {/* All Findings */}
+
+
+
+ {expandedSections.has("findings") && (
+
+ {findings.map((finding, index) => (
+
+
+
+ {getSeverityIcon(finding.severity)}
+
+
+ {finding.category}
+
+
+ #{finding.check_id}
+
+
+
+
{Math.round(finding.confidence * 100)}% confidence
+
+
+ {finding.message}
+
+
+ {finding.mitigation && (
+
+
+ Recommended:
+
+
+ {finding.mitigation}
+
+
+ )}
-
-
- {finding.message}
-
-
- {finding.mitigation && (
-
-
- Recommended Action:
-
-
{finding.mitigation}
-
- )}
-
- ))}
-
+ ))}
+
+ )}
{/* Notes */}
{notes && (
-
-
- Additional Notes
-
-
{notes}
+
)}
diff --git a/frontend/src/components/ChatExample.tsx b/frontend/src/components/ChatExample.tsx
deleted file mode 100644
index 221fd82..0000000
--- a/frontend/src/components/ChatExample.tsx
+++ /dev/null
@@ -1,241 +0,0 @@
-import {
- Conversation,
- ConversationContent,
- ConversationScrollButton,
-} from "@/components/ai-elements/conversation";
-import { Message, MessageContent } from "@/components/ai-elements/message";
-import {
- PromptInput,
- PromptInputActionAddAttachments,
- PromptInputActionMenu,
- PromptInputActionMenuContent,
- PromptInputActionMenuTrigger,
- PromptInputAttachment,
- PromptInputAttachments,
- PromptInputBody,
- PromptInputButton,
- PromptInputHeader,
- type PromptInputMessage,
- PromptInputSelect,
- PromptInputSelectContent,
- PromptInputSelectItem,
- PromptInputSelectTrigger,
- PromptInputSelectValue,
- PromptInputSubmit,
- PromptInputTextarea,
- PromptInputFooter,
- PromptInputTools,
-} from "@/components/ai-elements/prompt-input";
-import { Action, Actions } from "@/components/ai-elements/actions";
-import { Fragment, useState } from "react";
-import { useChat } from "@ai-sdk/react";
-import { Response } from "@/components/ai-elements/response";
-import { CopyIcon, GlobeIcon, RefreshCcwIcon } from "lucide-react";
-import {
- Source,
- Sources,
- SourcesContent,
- SourcesTrigger,
-} from "@/components/ai-elements/sources";
-import {
- Reasoning,
- ReasoningContent,
- ReasoningTrigger,
-} from "@/components/ai-elements/reasoning";
-import { Loader } from "@/components/ai-elements/loader";
-import { DefaultChatTransport } from "ai";
-
-const models = [
- {
- name: "GPT 4o",
- value: "openai/gpt-4o",
- },
- {
- name: "Deepseek R1",
- value: "deepseek/deepseek-r1",
- },
-];
-
-const ChatBotDemo = () => {
- const [input, setInput] = useState("");
- const [model, setModel] = useState
(models[0].value);
- const [webSearch, setWebSearch] = useState(false);
- const { messages, sendMessage, status, regenerate } = useChat({
- transport: new DefaultChatTransport({
- api: "/api/v1/chat",
- }),
- });
-
- const handleSubmit = (message: PromptInputMessage) => {
- const hasText = Boolean(message.text);
- const hasAttachments = Boolean(message.files?.length);
-
- if (!(hasText || hasAttachments)) {
- return;
- }
-
- sendMessage(
- {
- text: message.text || "Sent with attachments",
- files: message.files,
- },
- {
- body: {
- model: model,
- webSearch: webSearch,
- },
- },
- );
- setInput("");
- };
-
- return (
-
-
-
-
- {messages.map((message) => (
-
- {message.role === "assistant" &&
- message.parts.filter((part) => part.type === "source-url")
- .length > 0 && (
-
- part.type === "source-url",
- ).length
- }
- />
- {message.parts
- .filter((part) => part.type === "source-url")
- .map((part, i) => (
-
-
-
- ))}
-
- )}
- {message.parts.map((part, i) => {
- switch (part.type) {
- case "text":
- return (
-
-
-
- {part.text}
-
-
- {message.role === "assistant" &&
- i === messages.length - 1 && (
-
- regenerate()}
- label="Retry"
- >
-
-
-
- navigator.clipboard.writeText(part.text)
- }
- label="Copy"
- >
-
-
-
- )}
-
- );
- case "reasoning":
- return (
-
-
- {part.text}
-
- );
- default:
- return null;
- }
- })}
-
- ))}
- {status === "submitted" && }
-
-
-
-
-
-
-
- {(attachment) => }
-
-
-
- setInput(e.target.value)}
- value={input}
- />
-
-
-
-
-
-
-
-
-
- setWebSearch(!webSearch)}
- >
-
- Search
-
- {
- setModel(value);
- }}
- value={model}
- >
-
-
-
-
- {models.map((model) => (
-
- {model.name}
-
- ))}
-
-
-
-
-
-
-
-
- );
-};
-
-export default ChatBotDemo;
diff --git a/frontend/src/components/ChatTab.tsx b/frontend/src/components/ChatTab.tsx
index 0cec45a..60cfd12 100644
--- a/frontend/src/components/ChatTab.tsx
+++ b/frontend/src/components/ChatTab.tsx
@@ -1,8 +1,3 @@
-import {
- Conversation,
- ConversationContent,
- ConversationScrollButton,
-} from "@/components/ai-elements/conversation";
import { Message, MessageContent } from "@/components/ai-elements/message";
import {
PromptInput,
@@ -31,8 +26,10 @@ import {
Bot,
AlertCircle,
PaperclipIcon,
+ User,
} from "lucide-react";
import { AuditReport } from "./AuditReport";
+import { WebSearchResults } from "./WebSearchResults";
import { Loader } from "@/components/ai-elements/loader";
import { DefaultChatTransport } from "ai";
@@ -53,6 +50,9 @@ export function ChatTab({ selectedTema }: ChatTabProps) {
} = useChat({
transport: new DefaultChatTransport({
api: "/api/v1/agent/chat",
+ headers: {
+ tema: selectedTema || "",
+ },
}),
onError: (error) => {
setError(`Error en el chat: ${error.message}`);
@@ -103,23 +103,6 @@ export function ChatTab({ selectedTema }: ChatTabProps) {
return (
- {/* Chat Header */}
-
-
-
-
-
-
-
- Chat con {selectedTema}
-
-
- Haz preguntas sobre los documentos de este dataroom
-
-
-
-
-
{/* Chat Content */}
@@ -159,30 +142,60 @@ export function ChatTab({ selectedTema }: ChatTabProps) {
case "text":
return (
-
-
- {part.text}
-
-
+ {message.role === "user" ? (
+
+
+
+
+ {part.text}
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ {part.text}
+
+
+
+
+ )}
{message.role === "assistant" &&
i === message.parts.length - 1 && (
-
- regenerate()}
- label="Regenerar"
- disabled={status === "streaming"}
- >
-
-
-
- navigator.clipboard.writeText(part.text)
- }
- label="Copiar"
- >
-
-
-
+
+
+ regenerate()}
+ label="Regenerar"
+ disabled={status === "streaming"}
+ >
+
+
+
+ navigator.clipboard.writeText(part.text)
+ }
+ label="Copiar"
+ >
+
+
+
+
)}
);
@@ -231,6 +244,51 @@ export function ChatTab({ selectedTema }: ChatTabProps) {
default:
return null;
}
+ case "tool-search_web_information":
+ switch (part.state) {
+ case "input-available":
+ return (
+
+
+
+ Searching the web...
+
+
+ );
+ case "output-available":
+ return (
+
+ );
+ case "output-error":
+ return (
+
+
+
+
+ Error searching the web
+
+
+
+ {part.errorText}
+
+
+ );
+ default:
+ return null;
+ }
default:
return null;
}
diff --git a/frontend/src/components/DashboardTab.tsx b/frontend/src/components/DashboardTab.tsx
index a8507d7..38075eb 100644
--- a/frontend/src/components/DashboardTab.tsx
+++ b/frontend/src/components/DashboardTab.tsx
@@ -281,47 +281,6 @@ export function DashboardTab({ selectedTema }: DashboardTabProps) {
)}
-
- {/* Collection Details */}
- {dataroomInfo.collection_info && (
-
-
- Detalles de la Colección
-
-
-
-
-
- Total Vectores
-
-
- {dataroomInfo.collection_info.vectors_count}
-
-
-
-
- Vectores Indexados
-
-
- {dataroomInfo.collection_info.indexed_vectors_count}
-
-
-
-
Puntos
-
- {dataroomInfo.collection_info.points_count}
-
-
-
-
Segmentos
-
- {dataroomInfo.collection_info.segments_count}
-
-
-
-
-
- )}
);
}
diff --git a/frontend/src/components/DataroomView.tsx b/frontend/src/components/DataroomView.tsx
index 4792ddf..df73bd7 100644
--- a/frontend/src/components/DataroomView.tsx
+++ b/frontend/src/components/DataroomView.tsx
@@ -1,7 +1,14 @@
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { useFileStore } from "@/stores/fileStore";
-import { api } from "@/services/api";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Expand, Minimize2 } from "lucide-react";
import { FilesTab } from "./FilesTab";
import { DashboardTab } from "./DashboardTab";
import { ChatTab } from "./ChatTab";
@@ -11,15 +18,69 @@ interface DataroomViewProps {
}
export function DataroomView({ onProcessingChange }: DataroomViewProps = {}) {
- const { selectedTema, files } = useFileStore();
+ const { selectedTema } = useFileStore();
const [processing, setProcessing] = useState(false);
+ const [fullscreenTab, setFullscreenTab] = useState(null);
+ const [currentTab, setCurrentTab] = useState("overview");
const handleProcessingChange = (isProcessing: boolean) => {
setProcessing(isProcessing);
onProcessingChange?.(isProcessing);
};
+ const openFullscreen = (tabValue: string) => {
+ setFullscreenTab(tabValue);
+ };
+
+ const closeFullscreen = () => {
+ setFullscreenTab(null);
+ };
+
+ const renderTabContent = (tabValue: string, isFullscreen = false) => {
+ const className = isFullscreen ? "h-[calc(100vh-8rem)] flex flex-col" : "";
+
+ switch (tabValue) {
+ case "overview":
+ return (
+
+
+
+ );
+ case "files":
+ return (
+
+
+
+ );
+ case "chat":
+ return (
+
+
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ const getTabTitle = (tabValue: string) => {
+ switch (tabValue) {
+ case "overview":
+ return "Overview";
+ case "files":
+ return "Files";
+ case "chat":
+ return "Chat";
+ default:
+ return "";
+ }
+ };
+
return (
@@ -39,46 +100,86 @@ export function DataroomView({ onProcessingChange }: DataroomViewProps = {}) {
-
+
-
-
+
+
+ Overview
+
+
+ Files
+
+
+ Chat
+
+
+
+
-
- Files
-
-
- Chat
-
+
+ Open fullscreen
+
-
+ {renderTabContent("overview")}
-
+ {renderTabContent("files")}
-
+ {renderTabContent("chat")}
+
+
);
}
diff --git a/frontend/src/components/WebSearchResults.tsx b/frontend/src/components/WebSearchResults.tsx
new file mode 100644
index 0000000..bc297d7
--- /dev/null
+++ b/frontend/src/components/WebSearchResults.tsx
@@ -0,0 +1,206 @@
+import React, { useState } from "react";
+import {
+ Globe,
+ ExternalLink,
+ Search,
+ ChevronDown,
+ ChevronRight,
+ Info,
+ Star,
+} from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface SearchResult {
+ title: string;
+ url: string;
+ content: string;
+ score?: number;
+}
+
+interface WebSearchData {
+ query: string;
+ results: SearchResult[];
+ summary: string;
+ total_results: number;
+}
+
+interface WebSearchResultsProps {
+ data: WebSearchData;
+}
+
+const getScoreColor = (score?: number) => {
+ if (!score) return "text-gray-500";
+ if (score >= 0.8) return "text-green-600";
+ if (score >= 0.6) return "text-yellow-600";
+ return "text-gray-500";
+};
+
+const getScoreStars = (score?: number) => {
+ if (!score) return 0;
+ return Math.round(score * 5);
+};
+
+const truncateContent = (content: string, maxLength: number = 200) => {
+ if (content.length <= maxLength) return content;
+ return content.slice(0, maxLength) + "...";
+};
+
+export const WebSearchResults: React.FC