feat(3.15): AI Tools extindere si integrare
Prompt Generator: - Search bar cu cautare in name/description/tags/category - Filtru target type (text/image) cu toggle rapid 'Imagine' - 4 template-uri noi imagine: Midjourney Exterior, SD Interior, Midjourney Infographic, SD Material Texture (18 total) - Config v0.2.0 AI Chat Real API Integration: - /api/ai-chat route: multi-provider (OpenAI, Anthropic, Ollama, demo) - System prompt default in romana pt context arhitectura - GET: config status, POST: message routing - use-chat.ts: sendMessage() cu fetch real, sending state, providerConfig fetch, updateSession() pt project linking - UI: provider status badge (Wifi/WifiOff), Bot icon pe mesaje, loading spinner la generare, disable input while sending - Config banner cu detalii provider/model/stare AI Chat + Tag Manager: - Project selector dropdown in chat header (useTags project) - Session linking: projectTagId + projectName on ChatSession - Project name display in session sidebar - Project context injected in system prompt Docker: - AI env vars: AI_PROVIDER, AI_API_KEY, AI_MODEL, AI_BASE_URL, AI_MAX_TOKENS
This commit is contained in:
@@ -1,48 +1,73 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Plus, Send, Trash2, MessageSquare, Settings } from 'lucide-react';
|
||||
import { Button } from '@/shared/components/ui/button';
|
||||
import { Input } from '@/shared/components/ui/input';
|
||||
import { Card, CardContent } from '@/shared/components/ui/card';
|
||||
import { Badge } from '@/shared/components/ui/badge';
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
import { useChat } from '../hooks/use-chat';
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import {
|
||||
Plus,
|
||||
Send,
|
||||
Trash2,
|
||||
MessageSquare,
|
||||
Settings,
|
||||
Loader2,
|
||||
Bot,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
FolderOpen,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Input } from "@/shared/components/ui/input";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
import { useChat } from "../hooks/use-chat";
|
||||
import { useTags } from "@/core/tagging/use-tags";
|
||||
|
||||
const PROVIDER_LABELS: Record<string, string> = {
|
||||
openai: "OpenAI",
|
||||
anthropic: "Claude",
|
||||
ollama: "Ollama",
|
||||
demo: "Demo",
|
||||
};
|
||||
|
||||
export function AiChatModule() {
|
||||
const {
|
||||
sessions, activeSession, activeSessionId,
|
||||
createSession, addMessage, deleteSession, selectSession,
|
||||
sessions,
|
||||
activeSession,
|
||||
activeSessionId,
|
||||
sending,
|
||||
providerConfig,
|
||||
createSession,
|
||||
updateSession,
|
||||
sendMessage,
|
||||
deleteSession,
|
||||
selectSession,
|
||||
} = useChat();
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const { tags: projectTags } = useTags("project");
|
||||
|
||||
const [input, setInput] = useState("");
|
||||
const [showConfig, setShowConfig] = useState(false);
|
||||
const [showProjectPicker, setShowProjectPicker] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [activeSession?.messages.length]);
|
||||
|
||||
const providerLabel = providerConfig
|
||||
? (PROVIDER_LABELS[providerConfig.provider] ?? providerConfig.provider)
|
||||
: "Demo";
|
||||
const isConfigured = providerConfig?.isConfigured ?? false;
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!input.trim()) return;
|
||||
if (!input.trim() || sending) return;
|
||||
const text = input.trim();
|
||||
setInput('');
|
||||
setInput("");
|
||||
|
||||
if (!activeSessionId) {
|
||||
await createSession();
|
||||
}
|
||||
|
||||
await addMessage(text, 'user');
|
||||
|
||||
// Simulate AI response (no real API connected)
|
||||
setTimeout(async () => {
|
||||
await addMessage(
|
||||
'Acest modul necesită configurarea unei conexiuni API către un model AI (ex: Claude, GPT). ' +
|
||||
'Momentan funcționează în mod demonstrativ — mesajele sunt salvate local, dar răspunsurile AI nu sunt generate.\n\n' +
|
||||
'Pentru a activa răspunsurile AI, configurați cheia API în setările modulului.',
|
||||
'assistant'
|
||||
);
|
||||
}, 500);
|
||||
await sendMessage(text);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -58,20 +83,32 @@ export function AiChatModule() {
|
||||
<div
|
||||
key={session.id}
|
||||
className={cn(
|
||||
'group flex cursor-pointer items-center justify-between rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
session.id === activeSessionId ? 'bg-accent' : 'hover:bg-accent/50'
|
||||
"group flex cursor-pointer items-center justify-between rounded-md px-2 py-1.5 text-sm transition-colors",
|
||||
session.id === activeSessionId
|
||||
? "bg-accent"
|
||||
: "hover:bg-accent/50",
|
||||
)}
|
||||
onClick={() => selectSession(session.id)}
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<MessageSquare className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{session.title}</span>
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<MessageSquare className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{session.title}</span>
|
||||
</div>
|
||||
{session.projectName && (
|
||||
<span className="ml-5 truncate text-[10px] text-muted-foreground">
|
||||
{session.projectName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 opacity-0 group-hover:opacity-100"
|
||||
onClick={(e) => { e.stopPropagation(); deleteSession(session.id); }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteSession(session.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3 w-3 text-destructive" />
|
||||
</Button>
|
||||
@@ -86,11 +123,103 @@ export function AiChatModule() {
|
||||
<div className="flex items-center justify-between border-b px-4 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-sm font-medium">
|
||||
{activeSession?.title ?? 'Chat AI'}
|
||||
{activeSession?.title ?? "Chat AI"}
|
||||
</h3>
|
||||
<Badge variant="outline" className="text-[10px]">Demo</Badge>
|
||||
{/* Project link */}
|
||||
{activeSession && (
|
||||
<div className="relative">
|
||||
{activeSession.projectName ? (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="cursor-pointer gap-1 text-[10px]"
|
||||
onClick={() => setShowProjectPicker(!showProjectPicker)}
|
||||
>
|
||||
<FolderOpen className="h-2.5 w-2.5" />
|
||||
{activeSession.projectName}
|
||||
<X
|
||||
className="h-2.5 w-2.5 opacity-60 hover:opacity-100"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateSession(activeSession.id, {
|
||||
projectTagId: undefined,
|
||||
projectName: undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 gap-1 px-2 text-[10px] text-muted-foreground"
|
||||
onClick={() => setShowProjectPicker(!showProjectPicker)}
|
||||
>
|
||||
<FolderOpen className="h-3 w-3" />
|
||||
Proiect
|
||||
</Button>
|
||||
)}
|
||||
{showProjectPicker && projectTags.length > 0 && (
|
||||
<div className="absolute left-0 top-full z-50 mt-1 max-h-48 w-52 overflow-y-auto rounded-md border bg-popover p-1 shadow-md">
|
||||
{projectTags.map((tag) => (
|
||||
<button
|
||||
key={tag.id}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-xs hover:bg-accent",
|
||||
activeSession.projectTagId === tag.id &&
|
||||
"bg-accent font-medium",
|
||||
)}
|
||||
onClick={() => {
|
||||
updateSession(activeSession.id, {
|
||||
projectTagId: tag.id,
|
||||
projectName: tag.projectCode
|
||||
? `${tag.projectCode} — ${tag.label}`
|
||||
: tag.label,
|
||||
});
|
||||
setShowProjectPicker(false);
|
||||
}}
|
||||
>
|
||||
{tag.color && (
|
||||
<span
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: tag.color }}
|
||||
/>
|
||||
)}
|
||||
<span className="truncate">
|
||||
{tag.projectCode && (
|
||||
<span className="mr-1 font-mono text-muted-foreground">
|
||||
{tag.projectCode}
|
||||
</span>
|
||||
)}
|
||||
{tag.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Badge
|
||||
variant={isConfigured ? "default" : "outline"}
|
||||
className={cn(
|
||||
"text-[10px]",
|
||||
isConfigured && "bg-green-600 hover:bg-green-700",
|
||||
)}
|
||||
>
|
||||
{isConfigured ? (
|
||||
<Wifi className="mr-1 h-2.5 w-2.5" />
|
||||
) : (
|
||||
<WifiOff className="mr-1 h-2.5 w-2.5" />
|
||||
)}
|
||||
{providerLabel}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => setShowConfig(!showConfig)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => setShowConfig(!showConfig)}
|
||||
>
|
||||
<Settings className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -98,14 +227,42 @@ export function AiChatModule() {
|
||||
{/* Config banner */}
|
||||
{showConfig && (
|
||||
<div className="border-b bg-muted/30 px-4 py-3 text-xs text-muted-foreground">
|
||||
<p className="font-medium">Configurare API (viitor)</p>
|
||||
<p className="mt-1">
|
||||
Modulul va suporta conectarea la API-uri AI (Anthropic Claude, OpenAI, modele locale via Ollama).
|
||||
Cheia API și endpoint-ul se vor configura din setările aplicației sau variabile de mediu.
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
Momentan, conversațiile sunt salvate local, dar fără generare de răspunsuri AI reale.
|
||||
</p>
|
||||
<p className="font-medium">Configurare API</p>
|
||||
{providerConfig ? (
|
||||
<>
|
||||
<div className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<span>Provider:</span>
|
||||
<span className="font-medium text-foreground">
|
||||
{providerLabel}
|
||||
</span>
|
||||
<span>Model:</span>
|
||||
<span className="font-medium text-foreground">
|
||||
{providerConfig.model}
|
||||
</span>
|
||||
<span>Max tokens:</span>
|
||||
<span className="font-medium text-foreground">
|
||||
{providerConfig.maxTokens}
|
||||
</span>
|
||||
<span>Stare:</span>
|
||||
<span
|
||||
className={cn(
|
||||
"font-medium",
|
||||
isConfigured ? "text-green-600" : "text-amber-600",
|
||||
)}
|
||||
>
|
||||
{isConfigured ? "Configurat" : "Neconfigurat (mod demo)"}
|
||||
</span>
|
||||
</div>
|
||||
{!isConfigured && (
|
||||
<p className="mt-2 text-amber-600">
|
||||
Setați variabilele de mediu AI_PROVIDER, AI_API_KEY și
|
||||
AI_MODEL pentru a activa răspunsurile AI reale.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="mt-1">Se verifică conexiunea la API...</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -115,7 +272,9 @@ export function AiChatModule() {
|
||||
<div className="flex h-full flex-col items-center justify-center text-muted-foreground">
|
||||
<MessageSquare className="mb-3 h-10 w-10 opacity-30" />
|
||||
<p className="text-sm">Începe o conversație nouă</p>
|
||||
<p className="mt-1 text-xs">Scrie un mesaj sau creează o sesiune nouă din bara laterală.</p>
|
||||
<p className="mt-1 text-xs">
|
||||
Scrie un mesaj sau creează o sesiune nouă din bara laterală.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
@@ -123,21 +282,37 @@ export function AiChatModule() {
|
||||
<div
|
||||
key={msg.id}
|
||||
className={cn(
|
||||
'max-w-[80%] rounded-lg px-3 py-2 text-sm',
|
||||
msg.role === 'user'
|
||||
? 'ml-auto bg-primary text-primary-foreground'
|
||||
: 'bg-muted'
|
||||
"max-w-[80%] rounded-lg px-3 py-2 text-sm",
|
||||
msg.role === "user"
|
||||
? "ml-auto bg-primary text-primary-foreground"
|
||||
: "bg-muted",
|
||||
)}
|
||||
>
|
||||
{msg.role === "assistant" && (
|
||||
<Bot className="mb-1 inline h-3.5 w-3.5 text-muted-foreground" />
|
||||
)}
|
||||
<p className="whitespace-pre-wrap">{msg.content}</p>
|
||||
<p className={cn(
|
||||
'mt-1 text-[10px]',
|
||||
msg.role === 'user' ? 'text-primary-foreground/60' : 'text-muted-foreground'
|
||||
)}>
|
||||
{new Date(msg.timestamp).toLocaleTimeString('ro-RO', { hour: '2-digit', minute: '2-digit' })}
|
||||
<p
|
||||
className={cn(
|
||||
"mt-1 text-[10px]",
|
||||
msg.role === "user"
|
||||
? "text-primary-foreground/60"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{new Date(msg.timestamp).toLocaleTimeString("ro-RO", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
{sending && (
|
||||
<div className="flex max-w-[80%] items-center gap-2 rounded-lg bg-muted px-3 py-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
<span>Se generează răspunsul...</span>
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
)}
|
||||
@@ -149,12 +324,15 @@ export function AiChatModule() {
|
||||
<Input
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && !e.shiftKey && handleSend()}
|
||||
placeholder="Scrie un mesaj..."
|
||||
onKeyDown={(e) =>
|
||||
e.key === "Enter" && !e.shiftKey && handleSend()
|
||||
}
|
||||
placeholder={sending ? "Se generează..." : "Scrie un mesaj..."}
|
||||
disabled={sending}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button onClick={handleSend} disabled={!input.trim()}>
|
||||
<Send className="h-4 w-4" />
|
||||
<Button onClick={handleSend} disabled={!input.trim() || sending}>
|
||||
{sending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,15 @@ import type { ModuleConfig } from '@/core/module-registry/types';
|
||||
export const aiChatConfig: ModuleConfig = {
|
||||
id: 'ai-chat',
|
||||
name: 'Chat AI',
|
||||
description: 'Interfață de conversație cu modele AI pentru asistență profesională',
|
||||
description: 'Interfață de conversație cu modele AI (OpenAI, Claude, Ollama) pentru asistență profesională',
|
||||
icon: 'message-square',
|
||||
route: '/ai-chat',
|
||||
category: 'ai',
|
||||
featureFlag: 'module.ai-chat',
|
||||
visibility: 'all',
|
||||
version: '0.1.0',
|
||||
version: '0.2.0',
|
||||
dependencies: [],
|
||||
storageNamespace: 'ai-chat',
|
||||
navOrder: 51,
|
||||
tags: ['chat', 'ai', 'conversație'],
|
||||
tags: ['chat', 'ai', 'conversație', 'openai', 'claude', 'ollama'],
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useStorage } from "@/core/storage";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import type { ChatMessage, ChatSession } from "../types";
|
||||
import type { ChatMessage, ChatSession, AiProviderConfig } from "../types";
|
||||
|
||||
const SESSION_PREFIX = "session:";
|
||||
|
||||
@@ -12,6 +12,10 @@ export function useChat() {
|
||||
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [sending, setSending] = useState(false);
|
||||
const [providerConfig, setProviderConfig] = useState<AiProviderConfig | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const activeSession = sessions.find((s) => s.id === activeSessionId) ?? null;
|
||||
|
||||
@@ -29,18 +33,27 @@ export function useChat() {
|
||||
setLoading(false);
|
||||
}, [storage]);
|
||||
|
||||
// Load sessions + check provider config
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
fetch("/api/ai-chat")
|
||||
.then((r) => r.json())
|
||||
.then((data) => setProviderConfig(data as AiProviderConfig))
|
||||
.catch(() => {
|
||||
/* silent */
|
||||
});
|
||||
}, [refresh]);
|
||||
|
||||
const createSession = useCallback(
|
||||
async (title?: string) => {
|
||||
async (title?: string, projectTagId?: string, projectName?: string) => {
|
||||
const session: ChatSession = {
|
||||
id: uuid(),
|
||||
title: title || `Conversație ${new Date().toLocaleDateString("ro-RO")}`,
|
||||
messages: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
projectTagId,
|
||||
projectName,
|
||||
};
|
||||
await storage.set(`${SESSION_PREFIX}${session.id}`, session);
|
||||
setSessions((prev) => [session, ...prev]);
|
||||
@@ -50,6 +63,22 @@ export function useChat() {
|
||||
[storage],
|
||||
);
|
||||
|
||||
const updateSession = useCallback(
|
||||
async (
|
||||
id: string,
|
||||
updates: Partial<
|
||||
Pick<ChatSession, "title" | "projectTagId" | "projectName">
|
||||
>,
|
||||
) => {
|
||||
const current = sessions.find((s) => s.id === id);
|
||||
if (!current) return;
|
||||
const updated = { ...current, ...updates };
|
||||
await storage.set(`${SESSION_PREFIX}${id}`, updated);
|
||||
setSessions((prev) => prev.map((s) => (s.id === id ? updated : s)));
|
||||
},
|
||||
[storage, sessions],
|
||||
);
|
||||
|
||||
const addMessage = useCallback(
|
||||
async (content: string, role: ChatMessage["role"]) => {
|
||||
if (!activeSessionId) return;
|
||||
@@ -79,6 +108,66 @@ export function useChat() {
|
||||
[storage, activeSessionId, sessions],
|
||||
);
|
||||
|
||||
/**
|
||||
* Send a user message and get AI response from /api/ai-chat.
|
||||
* Falls back to demo mode if provider is not configured.
|
||||
*/
|
||||
const sendMessage = useCallback(
|
||||
async (content: string) => {
|
||||
if (!activeSessionId || sending) return;
|
||||
|
||||
// Add user message first
|
||||
await addMessage(content, "user");
|
||||
setSending(true);
|
||||
|
||||
try {
|
||||
// Build message history for API from current + the new message
|
||||
const current = sessions.find((s) => s.id === activeSessionId);
|
||||
const apiMessages = [
|
||||
...(current?.messages ?? []).map((m) => ({
|
||||
role: m.role as "user" | "assistant",
|
||||
content: m.content,
|
||||
})),
|
||||
{ role: "user" as const, content },
|
||||
];
|
||||
|
||||
// Add project context to system prompt if linked
|
||||
let systemPrompt: string | undefined;
|
||||
if (current?.projectName) {
|
||||
systemPrompt = `Ești un asistent AI pentru biroul de arhitectură. Contextul conversației: proiectul "${current.projectName}". Răspunzi în limba română cu terminologie profesională de arhitectură, urbanism și construcții.`;
|
||||
}
|
||||
|
||||
const res = await fetch("/api/ai-chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ messages: apiMessages, systemPrompt }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = (await res.json().catch(() => ({}))) as {
|
||||
message?: string;
|
||||
};
|
||||
await addMessage(
|
||||
`Eroare API: ${err.message ?? `HTTP ${res.status}`}`,
|
||||
"assistant",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await res.json()) as { content: string };
|
||||
await addMessage(data.content, "assistant");
|
||||
} catch (error) {
|
||||
await addMessage(
|
||||
`Eroare de conexiune: ${error instanceof Error ? error.message : "necunoscută"}`,
|
||||
"assistant",
|
||||
);
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
},
|
||||
[activeSessionId, sessions, sending, addMessage],
|
||||
);
|
||||
|
||||
const deleteSession = useCallback(
|
||||
async (id: string) => {
|
||||
await storage.delete(`${SESSION_PREFIX}${id}`);
|
||||
@@ -99,7 +188,11 @@ export function useChat() {
|
||||
activeSession,
|
||||
activeSessionId,
|
||||
loading,
|
||||
sending,
|
||||
providerConfig,
|
||||
createSession,
|
||||
updateSession,
|
||||
sendMessage,
|
||||
addMessage,
|
||||
deleteSession,
|
||||
selectSession,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { aiChatConfig } from './config';
|
||||
export { AiChatModule } from './components/ai-chat-module';
|
||||
export type { ChatMessage, ChatRole, ChatSession } from './types';
|
||||
export type { ChatMessage, ChatRole, ChatSession, AiProviderConfig } from './types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ChatRole = 'user' | 'assistant';
|
||||
export type ChatRole = "user" | "assistant";
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
@@ -12,4 +12,16 @@ export interface ChatSession {
|
||||
title: string;
|
||||
messages: ChatMessage[];
|
||||
createdAt: string;
|
||||
/** Linked project tag id from Tag Manager */
|
||||
projectTagId?: string;
|
||||
/** Project display name for quick reference */
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export interface AiProviderConfig {
|
||||
provider: string;
|
||||
model: string;
|
||||
baseUrl: string;
|
||||
maxTokens: number;
|
||||
isConfigured: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user