import { useEffect, useRef, useState } from "react"; import { Archive, ArchiveRestore, BookOpen, Bot, Check, Copy, Cpu, FileText, Flag, Menu, PanelLeftOpen, Paperclip, RotateCcw, ScanText, Search, SendHorizontal, Share2, User as UserIcon, X } from "lucide-react"; import type { Attachment, ChatMessage, Conversation, DriverModel, User } from "../lib/types.ts"; import { api, ApiError, type ReportReason } from "../lib/api.ts"; import { AssistantAvatar, Avatar, Button, Field, FlowerMark, IconButton, Modal, Picker, type PickerGroup, Spinner } from "./ui.tsx"; import { Markdown } from "./Markdown.tsx"; import { UserMessageContent } from "./MessageContent.tsx"; import { cn } from "../lib/cn.ts"; import { useFeatures } from "../lib/features.ts"; import { useT, type StringKey } from "../lib/i18n.ts"; const REPORT_REASONS: ReportReason[] = ["copyright", "gore", "falseinfo", "bug"]; function MessageBubble({ msg, index, authorAvatar, authorName, onReport, isFailed, retryCount, maxRetries, retryDisabled, onRetry }: { msg: ChatMessage; index: number; authorAvatar?: string; authorName: string; onReport?: (index: number) => void; isFailed?: boolean; retryCount?: number; maxRetries?: number; retryDisabled?: boolean; onRetry?: () => void }) { const { t } = useT(); const isUser = msg.role === "user"; const [copied, setCopied] = useState(false); function copy() { navigator.clipboard.writeText(msg.content).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }).catch(() => {}); } function searchWeb() { window.open(`https://duckduckgo.com/?q=${encodeURIComponent(msg.content.slice(0, 400))}`, "_blank", "noopener,noreferrer"); } return (
{isUser ? : }
{isUser ? : } {isUser ? t("chat.you") : "KisAssistant"}
{isUser ? (
) : (
)} {!isUser && (onReport || (isFailed && onRetry)) && (
{onReport && ( <> {copied ? : } onReport(index)}> )} {isFailed && onRetry && ( )}
)}
); } function ReportDialog({ open, onClose, conversationId, messageIndex, content, prompt, model }: { open: boolean; onClose: () => void; conversationId: string | null; messageIndex: number; content: string; prompt: string; model: string; }) { const { t } = useT(); const [reason, setReason] = useState(""); const [details, setDetails] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [sent, setSent] = useState(false); const [duplicate, setDuplicate] = useState(false); useEffect(() => { if (open) { setReason(""); setDetails(""); setError(""); setSent(false); setDuplicate(false); } }, [open, messageIndex]); async function submit(e: React.FormEvent) { e.preventDefault(); if (!reason || busy) return; setBusy(true); setError(""); try { await api.createReport({ conversationId: conversationId ?? undefined, messageIndex, reason: reason as ReportReason, details: details.trim(), content, prompt, model, }); setSent(true); } catch (err) { if (err instanceof ApiError && err.status === 409) { setDuplicate(true); } else { setError(err instanceof Error ? err.message : t("report.failed")); } } finally { setBusy(false); } } return ( {sent || duplicate ? (

{duplicate ? t("report.alreadyReported") : t("report.sent")}

) : (
({ value: r, label: t(`report.reason.${r}` as StringKey) }))} placeholder={t("report.reasonPh")} ariaLabel={t("report.reason")} align="left" />