"use client"; import { getAnnotationData, type Message } from "@llamaindex/chat-ui"; import { IconBrandGithub } from "@tabler/icons-react"; import { ExternalLink, FileText, Globe } from "lucide-react"; import { useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface Source { id: string; title: string; description: string; url: string; } interface SourceGroup { id: number; name: string; type: string; sources: Source[]; } // New interfaces for the updated data format interface NodeMetadata { title: string; source_type: string; group_name: string; } interface SourceNode { id: string; text: string; url: string; metadata: NodeMetadata; } function getSourceIcon(type: string) { switch (type) { case "USER_SELECTED_GITHUB_CONNECTOR": case "GITHUB_CONNECTOR": return ; case "USER_SELECTED_NOTION_CONNECTOR": case "NOTION_CONNECTOR": return ; case "USER_SELECTED_FILE": case "FILE": return ; default: return ; } } function SourceCard({ source }: { source: Source }) { const hasUrl = source.url && source.url.trim() !== ""; return ( {source.title} {hasUrl && ( window.open(source.url, "_blank")} > )} {source.description} ); } export default function ChatSourcesDisplay({ message }: { message: Message }) { const [open, setOpen] = useState(false); const annotations = getAnnotationData(message, "sources"); // Transform the new data format to the expected SourceGroup format const sourceGroups: SourceGroup[] = []; if (Array.isArray(annotations) && annotations.length > 0) { // Extract all nodes from the response const allNodes: SourceNode[] = []; annotations.forEach((item) => { if (item && typeof item === "object" && "nodes" in item && Array.isArray(item.nodes)) { allNodes.push(...item.nodes); } }); // Group nodes by source_type const groupedByType = allNodes.reduce( (acc, node) => { const sourceType = node.metadata.source_type; if (!acc[sourceType]) { acc[sourceType] = []; } acc[sourceType].push(node); return acc; }, {} as Record ); // Convert grouped nodes to SourceGroup format Object.entries(groupedByType).forEach(([sourceType, nodes], index) => { if (nodes.length > 0) { const firstNode = nodes[0]; sourceGroups.push({ id: index + 100, // Generate unique ID name: firstNode.metadata.group_name, type: sourceType, sources: nodes.map((node) => ({ id: node.id, title: node.metadata.title, description: node.text, url: node.url || "", })), }); } }); } if (sourceGroups.length === 0) { return null; } const totalSources = sourceGroups.reduce((acc, group) => acc + group.sources.length, 0); return ( View Sources ({totalSources}) Sources {sourceGroups.map((group) => ( {getSourceIcon(group.type)} {group.name} {group.sources.length} ))} {sourceGroups.map((group) => ( {group.sources.map((source) => ( ))} ))} ); }