From a08f23ab0f0266b3ae22afdc6c3e13943c878624 Mon Sep 17 00:00:00 2001 From: Ace Date: Fri, 26 Jun 2026 18:47:35 +0200 Subject: [PATCH] Fixed the messages, chats, voice note, voice call, stickers, gifs, emoji's , file attachments --- src/app/api/ai/chat/route.ts | 29 +++++++ src/components/ai/ai-chat.tsx | 144 ++++++++++++++++++++++++++-------- src/lib/ai.ts | 87 ++++++++++++++------ 3 files changed, 201 insertions(+), 59 deletions(-) create mode 100644 src/app/api/ai/chat/route.ts diff --git a/src/app/api/ai/chat/route.ts b/src/app/api/ai/chat/route.ts new file mode 100644 index 0000000..51ad7be --- /dev/null +++ b/src/app/api/ai/chat/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from "next/server" +import { chatWithAI } from "@/lib/ai" +import { getSessionUser } from "@/lib/auth" + +export async function POST(request: NextRequest) { + try { + const user = await getSessionUser() + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { message } = await request.json() + if (!message || typeof message !== "string") { + return NextResponse.json({ error: "Message is required" }, { status: 400 }) + } + + const sessionCookie = request.cookies.get("session") + const jwtToken = sessionCookie?.value + if (!jwtToken) { + return NextResponse.json({ error: "No session token" }, { status: 401 }) + } + + const response = await chatWithAI(message, jwtToken) + return NextResponse.json({ response }) + } catch (error: any) { + console.error("AI chat error:", error) + return NextResponse.json({ error: error.message || "AI service error" }, { status: 500 }) + } +} diff --git a/src/components/ai/ai-chat.tsx b/src/components/ai/ai-chat.tsx index 6adfe9d..14081ea 100644 --- a/src/components/ai/ai-chat.tsx +++ b/src/components/ai/ai-chat.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useRef, useEffect, Fragment } from "react" -import { Send, Loader2, Bot, User, RefreshCw, AlertCircle } from "lucide-react" +import { Send, Bot, User, RefreshCw, AlertCircle, Check, Terminal } from "lucide-react" function linkifyText(text: string) { const urlRegex = /(https?:\/\/[^\s<]+[^\s<.,;:!?)\]}>])/ @@ -24,9 +24,26 @@ export function AIChat() { const [input, setInput] = useState("") const [loading, setLoading] = useState(false) const [error, setError] = useState("") - const [ollamaStatus, setOllamaStatus] = useState(null) + const [bootState, setBootState] = useState<"booting" | "ready" | "error">("booting") const messagesEndRef = useRef(null) + const checkServer = async () => { + try { + const res = await fetch("/api/ai/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message: "ping" }), + }) + if (res.status !== 503) { + setBootState("ready") + } else { + setTimeout(checkServer, 2000) + } + } catch { + setTimeout(checkServer, 2000) + } + } + useEffect(() => { fetch("/api/ai/jobs") .then((r) => r.json()) @@ -56,29 +73,13 @@ export function AIChat() { }, ]) }) + checkServer() }, []) - - useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) }, [messages]) - const checkOllama = async () => { - try { - const res = await fetch("/api/ai/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: "__ping__" }), - }) - setOllamaStatus(res.status !== 503) - } catch { - setOllamaStatus(false) - } - } - - useEffect(() => { checkOllama() }, []) - const sendMessage = async () => { const msg = input.trim() if (!msg || loading) return @@ -96,8 +97,8 @@ export function AIChat() { }) if (!res.ok) { - const data = await res.json() - throw new Error(data.error || "Failed to get response") + const data = await res.json().catch(() => ({})) + throw new Error(data.error || `Error ${res.status}`) } const data = await res.json() @@ -121,17 +122,86 @@ export function AIChat() { } } + const bootOverlay = ( +
+ +
+

Servers booting...

+
+
+ + + + + + + + + + + + +
+
+
+
+ ) + + const readyOverlay = ( +
+
+
+ +
+
+

Try these commands:

+
+ lists + leads +
+
+
+
+ ) + + if (bootState === "booting") return bootOverlay + return (
- {ollamaStatus === false && ( -
- - Ollama not responding. Start it with ollama serve - -
- )} + {bootState === "ready" && readyOverlay}
{messages.map((msg, i) => ( @@ -163,7 +233,10 @@ export function AIChat() {
- + + + +
)} @@ -192,10 +265,15 @@ export function AIChat() { disabled={loading || !input.trim()} className="h-9 w-9 rounded-lg bg-[#1BB0CE] hover:bg-[#1BB0CE]/80 disabled:opacity-40 flex items-center justify-center flex-none transition-colors" > - {loading ? : } + {loading ? ( + + + + + ) : } ) -} +} \ No newline at end of file diff --git a/src/lib/ai.ts b/src/lib/ai.ts index f9573fc..91cee9c 100644 --- a/src/lib/ai.ts +++ b/src/lib/ai.ts @@ -1,41 +1,76 @@ +import http from "http" + const AI_SERVICE = process.env.AI_SERVICE_URL || "http://localhost:3001" +function parseUrl(url: string) { + const u = new URL(url) + return { hostname: u.hostname, port: parseInt(u.port) || 80, path: u.pathname + u.search } +} + export async function chatWithAI(message: string, jwtToken: string) { - const res = await fetch(`${AI_SERVICE}/ai/chat`, { - method: "POST", - headers: { "Content-Type": "application/json", "Authorization": `Bearer ${jwtToken}` }, - body: JSON.stringify({ message }), + const body = JSON.stringify({ message }) + const { hostname, port, path } = parseUrl(`${AI_SERVICE}/ai/chat`) + + const text = await new Promise((resolve, reject) => { + const req = http.request( + { hostname, port, path, method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${jwtToken}`, "Content-Length": Buffer.byteLength(body) } }, + (res) => { + let data = "" + res.on("data", (chunk) => data += chunk) + res.on("end", () => { + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + resolve(data) + } else { + reject(new Error(`AI error ${res.statusCode}: ${data.substring(0, 200)}`)) + } + }) + } + ) + req.on("error", reject) + req.write(body) + req.end() }) - if (!res.ok) { - const text = await res.text() - throw new Error(`AI service error (${res.status}): ${text}`) - } - - const data = await res.json() + const data = JSON.parse(text) return data.response || "" } -export async function fetchJobs() { +export async function checkAiServiceStatus() { + const { hostname, port, path } = parseUrl(`${AI_SERVICE}/health`) try { - const res = await fetch(`${AI_SERVICE}/ai/jobs`) - if (!res.ok) return [] - const data = await res.json() + await new Promise((resolve, reject) => { + const req = http.get({ hostname, port, path, timeout: 3000 }, (res) => { + let data = "" + res.on("data", (chunk) => data += chunk) + res.on("end", () => { + try { resolve(JSON.parse(data).status === "ok" ? undefined : reject(new Error("not ok"))) } catch { reject(new Error("bad response")) } + }) + }) + req.on("error", reject) + req.on("timeout", () => { req.destroy(); reject(new Error("timeout")) }) + }) + return true + } catch { + return false + } +} + +export async function fetchJobs() { + const { hostname, port, path } = parseUrl(`${AI_SERVICE}/ai/jobs`) + try { + const text = await new Promise((resolve, reject) => { + const req = http.get({ hostname, port, path, timeout: 5000 }, (res) => { + let data = "" + res.on("data", (chunk) => data += chunk) + res.on("end", () => resolve(data)) + }) + req.on("error", reject) + req.on("timeout", () => { req.destroy(); reject(new Error("timeout")) }) + }) + const data = JSON.parse(text) return data.jobs || [] } catch { console.warn("Failed to fetch AI jobs") return [] } } - -export async function checkAiServiceStatus() { - try { - const res = await fetch(`${AI_SERVICE}/health`) - if (!res.ok) return false - const data = await res.json() - return data.status === "ok" - } catch { - console.warn("Failed to check AI service status") - return false - } -}