feat: tenant AI chat agent with function calling

Actionable AI assistant that uses per-tenant Azure OpenAI credentials
to execute natural language commands against the render pipeline.

Backend:
- ChatMessage model + migration (session-based conversations)
- Chat service with 10 OpenAI function-calling tools:
  list_orders, search_products, create_order, dispatch_renders,
  get_order_status, set_material_override, set_render_overrides,
  get_render_stats, check_materials, query_database
- All tools tenant-scoped (queries filtered by tenant_id)
- Write operations use httpx to call backend API internally
- Chat API: POST /chat/messages, GET /chat/sessions, DELETE session
- Conversation history preserved in DB (last 50 messages per session)

Frontend:
- Slide-out ChatPanel (right side, w-96, animated)
- User/assistant message styling with avatars and timestamps
- Session management (new chat, session history, delete)
- Typing indicator while waiting for AI response
- Floating chat button in bottom-right corner
- Error state for unconfigured AI tenants

Example: "Render all Kugellager products as WebP at 1024x1024"
→ Agent calls search_products + create_order + dispatch_renders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 12:46:21 +01:00
parent daad2c64f3
commit 59ce61098c
10 changed files with 1627 additions and 66 deletions
+50
View File
@@ -0,0 +1,50 @@
import api from './client'
export interface ChatMessage {
id: string
role: 'user' | 'assistant' | 'system'
content: string
created_at: string
}
export interface ChatSession {
session_id: string
last_message: string
message_count: number
created_at: string
}
export interface ChatResponse {
session_id: string
message: ChatMessage
response: ChatMessage
}
export async function sendChatMessage(
message: string,
sessionId?: string,
contextType?: string,
contextId?: string,
): Promise<ChatResponse> {
const res = await api.post<ChatResponse>('/chat/messages', {
message,
session_id: sessionId || undefined,
context_type: contextType || undefined,
context_id: contextId || undefined,
})
return res.data
}
export async function getChatSessions(): Promise<ChatSession[]> {
const res = await api.get<ChatSession[]>('/chat/sessions')
return res.data
}
export async function getSessionMessages(sessionId: string): Promise<ChatMessage[]> {
const res = await api.get<ChatMessage[]>(`/chat/sessions/${sessionId}/messages`)
return res.data
}
export async function deleteChatSession(sessionId: string): Promise<void> {
await api.delete(`/chat/sessions/${sessionId}`)
}