Files
homelab-optimized/dashboard/ui/components/ollama-chat.tsx
Gitea Mirror Bot e4159258e8
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m7s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-04-05 06:44:51 UTC
2026-04-05 06:44:51 +00:00

80 lines
3.4 KiB
TypeScript

"use client";
import { useState, useRef } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
interface Message { role: "user" | "assistant"; content: string }
export function OllamaChat() {
const [open, setOpen] = useState(false);
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
async function send() {
if (!input.trim() || loading) return;
const userMsg = input.trim();
setInput("");
setMessages(prev => [...prev, { role: "user", content: userMsg }]);
setLoading(true);
try {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: userMsg }),
});
const data = await res.json();
setMessages(prev => [...prev, { role: "assistant", content: data.response || data.error || "No response" }]);
} catch (e) {
setMessages(prev => [...prev, { role: "assistant", content: `Error: ${e}` }]);
}
setLoading(false);
setTimeout(() => scrollRef.current?.scrollTo(0, scrollRef.current.scrollHeight), 100);
}
if (!open) {
return (
<button
onClick={() => setOpen(true)}
className="fixed bottom-4 left-4 z-50 w-10 h-10 rounded-full bg-gradient-to-br from-violet-500 to-blue-500 text-white flex items-center justify-center shadow-lg hover:scale-110 transition-transform"
title="Chat with Ollama"
>
<span className="text-sm font-bold">AI</span>
</button>
);
}
return (
<div className="fixed bottom-4 left-4 z-50 w-80">
<Card className="shadow-2xl border-violet-500/20">
<CardHeader className="pb-2 flex flex-row items-center justify-between">
<CardTitle className="text-sm font-medium">Ollama Chat</CardTitle>
<Button variant="ghost" size="sm" className="h-6 w-6 p-0 text-xs" onClick={() => setOpen(false)}>x</Button>
</CardHeader>
<CardContent className="space-y-2">
<div ref={scrollRef} className="h-48 overflow-y-auto space-y-2 text-xs">
{messages.length === 0 && <p className="text-muted-foreground text-center py-4">Ask anything about your homelab...</p>}
{messages.map((m, i) => (
<div key={i} className={`rounded-md px-2 py-1.5 ${m.role === "user" ? "bg-primary/10 ml-8" : "bg-secondary mr-8"}`}>
<p className="whitespace-pre-wrap">{m.content}</p>
</div>
))}
{loading && <div className="bg-secondary rounded-md px-2 py-1.5 mr-8 animate-pulse"><p className="text-muted-foreground">Thinking...</p></div>}
</div>
<div className="flex gap-1.5">
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === "Enter" && send()}
placeholder="Ask Ollama..."
className="flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-primary"
/>
<Button size="sm" className="h-7 text-xs px-2" onClick={send} disabled={loading}>Send</Button>
</div>
</CardContent>
</Card>
</div>
);
}