riseup-squad20/susconecta/components/ZoeIA/ai-assistant-interface.tsx

318 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { SimpleThemeToggle } from "@/components/ui/simple-theme-toggle";
import {
Clock,
Info,
Lock,
MessageCircle,
Plus,
Upload,
} from "lucide-react";
interface HistoryEntry {
id: string;
text: string;
createdAt: string;
}
interface AIAssistantInterfaceProps {
onOpenDocuments?: () => void;
onOpenChat?: () => void;
history?: HistoryEntry[];
onAddHistory?: (entry: HistoryEntry) => void;
onClearHistory?: () => void;
}
export function AIAssistantInterface({
onOpenDocuments,
onOpenChat,
history: externalHistory,
onAddHistory,
onClearHistory,
}: AIAssistantInterfaceProps) {
const [question, setQuestion] = useState("");
const [drawerOpen, setDrawerOpen] = useState(false);
const [internalHistory, setInternalHistory] = useState<HistoryEntry[]>([]);
const history = externalHistory ?? internalHistory;
const showHistoryBadge = useMemo(() => history.length > 0, [history.length]);
const handleDocuments = () => {
if (onOpenDocuments) {
onOpenDocuments();
return;
}
console.log("[ZoeIA] Abrir fluxo de documentos");
};
const handleOpenRealtimeChat = () => {
if (onOpenChat) {
onOpenChat();
return;
}
console.log("[ZoeIA] Abrir chat em tempo real");
};
const handleSendMessage = () => {
const trimmed = question.trim();
if (!trimmed) return;
handlePersistHistory(trimmed);
console.log("[ZoeIA] Mensagem enviada para Zoe", trimmed);
setQuestion("");
};
const RealtimeTriggerButton = () => (
<button
type="button"
onClick={handleOpenRealtimeChat}
className="flex h-12 w-12 items-center justify-center rounded-full bg-white text-foreground shadow-sm transition hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:bg-zinc-900 dark:text-white"
aria-label="Abrir chat Zoe em tempo real"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="h-5 w-5"
fill="currentColor"
aria-hidden
>
<rect x="4" y="7" width="2" height="10" rx="1" />
<rect x="8" y="5" width="2" height="14" rx="1" />
<rect x="12" y="7" width="2" height="10" rx="1" />
<rect x="16" y="9" width="2" height="6" rx="1" />
<rect x="20" y="8" width="2" height="8" rx="1" />
</svg>
</button>
);
const handlePersistHistory = (text: string) => {
const entry: HistoryEntry = {
id: `hist-${Date.now()}`,
text,
createdAt: new Date().toISOString(),
};
if (onAddHistory) {
onAddHistory(entry);
} else {
setInternalHistory((prev) => [...prev, entry]);
}
setDrawerOpen(true);
};
const handleClearHistory = () => {
if (onClearHistory) {
onClearHistory();
} else {
setInternalHistory([]);
}
};
const HistoryGlyph = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="h-5 w-5"
fill="none"
>
<circle cx="16" cy="8" r="3" className="stroke-current" strokeWidth="1.6" />
<line x1="5" y1="8" x2="12" y2="8" className="stroke-current" strokeWidth="1.6" strokeLinecap="round" />
<line x1="5" y1="16" x2="19" y2="16" className="stroke-current" strokeWidth="1.6" strokeLinecap="round" />
<circle cx="9" cy="16" r="1" className="fill-current" />
</svg>
);
return (
<div className="min-h-screen bg-background text-foreground">
<div className="mx-auto flex w-full max-w-3xl flex-col gap-8 px-4 py-10 sm:px-6 sm:py-12">
<header className="flex flex-col gap-6 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-3">
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => setDrawerOpen(true)}
className="relative rounded-2xl border border-border/60 bg-card text-muted-foreground shadow-sm transition hover:text-primary"
>
<HistoryGlyph />
<span className="sr-only">Abrir histórico de interações</span>
{showHistoryBadge && (
<span className="absolute -top-1 -right-1 h-2.5 w-2.5 rounded-full bg-primary" />
)}
</Button>
<span className="flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br from-primary via-sky-500 to-emerald-400 text-base font-semibold text-white shadow-lg">
Zoe
</span>
<div>
<p className="text-sm font-semibold text-primary">Assistente Clínica Zoe</p>
<h1 className="text-2xl font-bold tracking-tight">Olá, eu sou Zoe. Como posso ajudar hoje?</h1>
</div>
</div>
<SimpleThemeToggle />
</header>
<div className="flex items-center gap-2 rounded-full border border-border px-4 py-2 text-xs text-muted-foreground shadow-sm">
<Lock className="h-4 w-4" />
<span>Suas informações permanecem criptografadas e seguras com a equipe Zoe.</span>
</div>
<section className="space-y-6 rounded-3xl border border-primary/15 bg-card/60 p-6 shadow-lg backdrop-blur">
<div className="rounded-3xl border border-primary/20 bg-primary/5 p-6 text-sm leading-relaxed text-muted-foreground">
<div className="mb-4 flex items-center gap-3 text-primary">
<Info className="h-5 w-5" />
<span className="font-semibold">Informativo importante</span>
</div>
<p>
A Zoe é a assistente virtual da Clínica Zoe. Ela reúne informações sobre seus cuidados e orienta os próximos passos.
O atendimento é informativo e não substitui a avaliação de um profissional de saúde qualificado.
</p>
<p className="mt-4">
Em situações de urgência, procure imediatamente o suporte médico presencial ou ligue para os serviços de emergência.
</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<Button
onClick={handleDocuments}
size="lg"
className="justify-start gap-3 rounded-2xl bg-primary text-primary-foreground shadow-md transition hover:shadow-xl"
>
<Upload className="h-5 w-5" />
Enviar documentos clínicos
</Button>
<Button
onClick={handleOpenRealtimeChat}
size="lg"
variant="outline"
className="justify-start gap-3 rounded-2xl border-primary/40 bg-background shadow-md transition hover:border-primary hover:text-primary"
>
<MessageCircle className="h-5 w-5" />
Conversar com a equipe Zoe
</Button>
</div>
<div className="rounded-2xl border border-border bg-background/80 p-4 shadow-inner">
<p className="text-sm text-muted-foreground">
Estamos reunindo o histórico da sua jornada. Enquanto isso, você pode anexar exames, enviar dúvidas ou solicitar contato com a equipe Zoe.
</p>
</div>
</section>
<div className="flex items-center gap-3 rounded-full border border-border bg-card/70 px-3 py-2 shadow-xl">
<Button
type="button"
variant="ghost"
size="icon"
className="rounded-full border border-border/40 bg-background/60 text-muted-foreground transition hover:text-primary"
onClick={handleDocuments}
>
<Plus className="h-5 w-5" />
</Button>
<Input
value={question}
onChange={(event) => setQuestion(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
handleSendMessage();
}
}}
placeholder="Pergunte qualquer coisa para a Zoe"
className="border-none bg-transparent text-sm shadow-none focus-visible:ring-0"
/>
<div className="flex items-center gap-2">
<Button
type="button"
className="rounded-full bg-primary px-5 text-primary-foreground shadow-md transition hover:bg-primary/90"
onClick={handleSendMessage}
>
Enviar
</Button>
<RealtimeTriggerButton />
</div>
</div>
{drawerOpen && (
<aside className="fixed inset-y-0 left-0 z-[120] w-[min(22rem,80vw)] bg-card shadow-2xl">
<div className="flex h-full flex-col">
<div className="flex items-center justify-between border-b border-border px-4 py-4">
<div className="flex items-center gap-3">
<span className="flex h-9 w-9 items-center justify-center rounded-2xl bg-gradient-to-br from-primary via-sky-500 to-emerald-400 text-sm font-semibold text-white shadow-md">
Zoe
</span>
<div>
<h2 className="text-sm font-semibold text-foreground">Histórico da Zoe</h2>
<p className="text-xs text-muted-foreground">{history.length} registro{history.length === 1 ? "" : "s"}</p>
</div>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => setDrawerOpen(false)}
className="rounded-full"
>
<span className="sr-only">Fechar histórico</span>
×
</Button>
</div>
<div className="flex items-center gap-2 border-b border-border px-4 py-3">
<Button
type="button"
className="flex-1 justify-start gap-2 rounded-xl bg-primary text-primary-foreground shadow-md transition hover:shadow-lg"
onClick={() => {
handleClearHistory();
setDrawerOpen(false);
}}
>
<Plus className="h-4 w-4" />
Novo atendimento
</Button>
</div>
<div className="flex-1 overflow-y-auto px-4 py-4">
{history.length === 0 ? (
<p className="text-sm text-muted-foreground">
Nenhuma conversa registrada ainda. Envie uma mensagem para começar um novo acompanhamento com a Zoe.
</p>
) : (
<ul className="flex flex-col gap-3 text-sm">
{[...history]
.reverse()
.map((entry) => (
<li
key={entry.id}
className="flex items-start gap-3 rounded-xl border border-border/60 bg-background/90 p-3 shadow-sm"
>
<span className="mt-0.5 text-primary">
<Clock className="h-4 w-4" />
</span>
<div className="flex-1">
<p className="font-medium text-foreground line-clamp-2">{entry.text}</p>
<p className="text-xs text-muted-foreground">
{new Date(entry.createdAt).toLocaleString("pt-BR", {
day: "2-digit",
month: "2-digit",
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
</li>
))}
</ul>
)}
</div>
</div>
</aside>
)}
</div>
</div>
);
}