import { Textarea } from "../../tremor/Textarea"; import { Button } from "../../tremor/Button"; import { Text } from "../../tremor/Text"; import { Card } from "../../tremor/Card"; import { Icon } from "../../tremor/Icon"; import { useEffect, useMemo, useRef, useState } from "react"; import React from "react"; import { useBackend } from "../../layouts/Wrapper"; import remarkGfm from "remark-gfm"; import ReactMarkdown from "react-markdown"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import { ErrorBoundary } from "react-error-boundary"; import { ArrowDownTrayIcon, ArrowRightIcon, DocumentChartBarIcon, HandThumbDownIcon as HandThumbDownOutlineIcon, HandThumbUpIcon as HandThumbUpOutlineIcon, PencilIcon, PhotoIcon, } from "@heroicons/react/24/outline"; import { HandThumbDownIcon as HandThumbDownSolidIcon, HandThumbUpIcon as HandThumbUpSolidIcon, } from "@heroicons/react/24/solid"; import ChartBase from "../Chart/ChartBase"; import { useDashboard } from "../../layouts/Dashboard/useDashboard"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuIconWrapper, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "../../tremor/DropdownMenu"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "../../tremor/Accordion"; import { Message } from "@onvo-ai/js"; import { useTheme } from "../../layouts/Dashboard/useTheme"; import { toast } from "sonner"; import { Popover, PopoverContent, PopoverTrigger } from "../../tremor/Popover"; import { SparklesIcon } from "@heroicons/react/20/solid"; import { ChartPlaceholder } from "../ChartLoader"; import { Widget } from "@onvo-ai/js"; dayjs.extend(relativeTime); const VotePopover: React.FC<{ children: React.ReactNode, questionId: string, messageId: string, type: "up" | "down", onSubmit: (reason: string) => void }> = ({ children, questionId, messageId, type, onSubmit }) => { const { backend } = useBackend(); const [reason, setReason] = useState(""); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const handleVote = async () => { if (!backend) return; setLoading(true); await backend.question(questionId).voteMessage(messageId, type, reason); onSubmit(reason); setLoading(false); setOpen(false); } return {children} Why did you {type === "up" ? "like" : "dislike"} this message? } export const QuestionMessage: React.FC<{ isLast?: boolean; questionLoading?: boolean; message: Message; onAdd: (widget: Widget, library?: boolean) => void; onEdit: (msg: string) => void; onReply: (msg: string) => void; }> = ({ isLast, questionLoading, message, onAdd, onEdit, onReply }) => { const { adminMode, containerRef, backend } = useBackend(); const { dashboard } = useDashboard(); const theme = useTheme(); const elementRef = useRef(null); const [widget, setWidget] = useState(); const [cache, setCache] = useState(); const [vote, setVote] = useState<"up" | "down">(); const [editing, setEditing] = useState(false); const [newMessage, setNewMessage] = useState(""); const [explainingCode, setExplainingCode] = useState(false); const [explainedCode, setExplainedCode] = useState(null); const { role, content, widget: widgetId } = message; const getWidget = async (id: string) => { let newWidget = await backend?.widgets.get(id); setWidget(newWidget); if (!newWidget || !newWidget.code || newWidget.code.trim() === "") return; let obj = await backend?.widget(id).cache(); setCache(obj?.data); } useEffect(() => { if (!widgetId) return; getWidget(widgetId); }, [widgetId, isLast, questionLoading]); const explainCode = async () => { if (!widget || !backend) return; setExplainingCode(true); setExplainedCode(null); try { let result = await backend.widget(widget.id).annotateCode(); setExplainedCode(result.insights); } catch (e: any) { toast.error("Failed to explain code: " + e.message); } setExplainingCode(false); }; useEffect(() => { if (!message) return; setVote(message.vote || undefined); }, [message]); const onDownload = (format: "svg" | "png" | "csv" | "xlsx" | "jpeg") => { if (!backend) return; if (!widgetId) return; toast.promise( () => { return backend.widget(widgetId).export(format, theme); }, { loading: `Exporting widget as ${format}...`, success: async (output: any) => { console.log("OUTPUT: ", output); try { // Get the file URL, replacing host.docker.internal with localhost if needed const fileUrl = output.url.replace("host.docker.internal", "localhost"); // Create a link element const link = document.createElement('a'); link.href = fileUrl; link.download = output.fileName || `export.${format}`; // Set the filename link.style.display = 'none'; // Add to document, click and remove document.body.appendChild(link); link.click(); // Clean up setTimeout(() => { document.body.removeChild(link); }, 100); } catch (error) { console.error('Download failed:', error); toast.error('Download failed. Please try again.'); } return "Widget exported"; }, error: (error) => "Error exporting widget: " + error.message, } ); }; if (role === "user") { let text = content as string; return (
{editing ? (