From ec5bd7d7dc025071d0427985391990fba11894dc Mon Sep 17 00:00:00 2001 From: admin-valentin Date: Wed, 26 Aug 2026 15:41:08 +0000 Subject: [PATCH] =?UTF-8?q?feat(FE):=20AiAskPanel=20floating=20component?= =?UTF-8?q?=20=E2=80=94=20chat=20with=20Hermes=20from=20any=20dashboard=20?= =?UTF-8?q?page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ai-ask-panel.tsx | 146 ++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/components/ai-ask-panel.tsx diff --git a/src/components/ai-ask-panel.tsx b/src/components/ai-ask-panel.tsx new file mode 100644 index 0000000..2a2e5dc --- /dev/null +++ b/src/components/ai-ask-panel.tsx @@ -0,0 +1,146 @@ +'use client'; + +import { useRef, useState, useEffect } from 'react'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { apiFetch } from '../lib/api'; +import { useSession } from './session-provider'; + +interface Message { role: 'user' | 'assistant'; content: string; } + +export function AiAskPanel() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const [open, setOpen] = useState(false); + const [input, setInput] = useState(''); + const [messages, setMessages] = useState([]); + const bottomRef = useRef(null); + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + const askMut = useMutation({ + mutationFn: (userMsg: string) => + apiFetch<{ reply: string }>('/v1/ai/ask', { + tenantId, + method: 'POST', + body: { + messages: [...messages, { role: 'user', content: userMsg }], + context: JSON.stringify({ tenantId, ts: new Date().toISOString() }), + }, + }), + onSuccess: (data, userMsg) => { + setMessages((prev) => [ + ...prev, + { role: 'user', content: userMsg }, + { role: 'assistant', content: data.reply }, + ]); + }, + }); + + const { data: brief } = useQuery({ + queryKey: ['daily-brief', tenantId], + queryFn: () => apiFetch<{ brief: string }>('/v1/ai/daily-brief', { tenantId }), + enabled: Boolean(tenantId) && open && messages.length === 0, + staleTime: 600_000, // 10 min + }); + + function send() { + const msg = input.trim(); + if (!msg || askMut.isPending) return; + setInput(''); + askMut.mutate(msg); + } + + const SUGGESTIONS = [ + 'Ce ar trebui să prioritizez azi?', + 'Rezumă săptămâna trecută.', + 'Care sunt obiectivele cu risc de nefinalizare?', + 'Generează un brief pentru echipă.', + ]; + + if (!tenantId) return null; + + return ( + <> + {/* Floating button */} + + + {/* Panel */} + {open && ( +
+ {/* Header */} +
+ +

CEO OS AI

+ via Hermes +
+ + {/* Messages */} +
+ {messages.length === 0 && brief?.brief && ( +
+

Brief zilnic

+

{brief.brief}

+
+ )} + {messages.length === 0 && !brief && ( +
+

Întreabă-mă orice despre CEO OS:

+ {SUGGESTIONS.map((s) => ( + + ))} +
+ )} + {messages.map((m, i) => ( +
+
+ {m.content} +
+
+ ))} + {askMut.isPending && ( +
+
+ Hermes gândește… +
+
+ )} + {askMut.isError && ( +

+ Hermes indisponibil. Verifică HERMES_BASE_URL în Coolify. +

+ )} +
+
+ + {/* Input */} +
+ setInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && !e.shiftKey && send()} + placeholder="Întreabă ceva… (Enter)" + className="flex-1 rounded-xl border bg-background px-3 py-2 text-xs text-ink focus:outline-none focus:ring-2 focus:ring-ring" + /> + +
+
+ )} + + ); +}