feat(chatbot): per-user history via X-User-Id + agentic tools calling
Backend: - New chatbot.tools.ts: 4 tools (get_server_stats, get_top_channels, get_recent_activity, get_top_flagged) with real DB executors - chatbot.service: agentic loop — stream:true, parse SSE, execute tool_calls, feed results back, up to 4 rounds - controller: resolve userId from X-User-Id header (no-login device uuid) with auth middleware precedence; history/clear scoped per user Frontend: - use-chatbot-user: mint UUID in localStorage, send as X-User-Id - chatbotApi.send/getHistory/clearHistory accept userId header - client.ts: apiRequest supports custom headers per call - provider: history load + send + clear keyed to device user id
This commit is contained in:
@@ -1,17 +1,27 @@
|
||||
import type { ChatbotHistoryRow, ChatbotResponse } from "@/lib/types";
|
||||
import { api } from "./client";
|
||||
|
||||
export const chatbotApi = {
|
||||
send: (message: string, guildId?: string) =>
|
||||
api.post<ChatbotResponse>("/api/chat", {
|
||||
message,
|
||||
context: guildId ? { guildId } : undefined,
|
||||
}),
|
||||
function userHeader(userId?: string): Record<string, string> {
|
||||
return userId && userId !== "anonymous" ? { "X-User-Id": userId } : {};
|
||||
}
|
||||
|
||||
getHistory: () =>
|
||||
api.get<{ history: ChatbotHistoryRow[]; total: number }>(
|
||||
"/api/chat/history",
|
||||
export const chatbotApi = {
|
||||
send: (message: string, guildId?: string, userId?: string) =>
|
||||
api.post<ChatbotResponse>(
|
||||
"/api/chat",
|
||||
{
|
||||
message,
|
||||
context: guildId ? { guildId } : undefined,
|
||||
},
|
||||
userHeader(userId),
|
||||
),
|
||||
|
||||
clearHistory: () => api.delete<{ ok: boolean }>("/api/chat/history"),
|
||||
getHistory: (userId?: string) =>
|
||||
api.get<{ history: ChatbotHistoryRow[]; total: number }>(
|
||||
"/api/chat/history",
|
||||
userHeader(userId),
|
||||
),
|
||||
|
||||
clearHistory: (userId?: string) =>
|
||||
api.delete<{ ok: boolean }>("/api/chat/history", userHeader(userId)),
|
||||
};
|
||||
|
||||
@@ -31,17 +31,18 @@ export async function apiRequest<T>(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: unknown,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<T> {
|
||||
const url = `${getBaseUrl()}${path}`;
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
const finalHeaders: Record<string, string> = { ...(headers ?? {}) };
|
||||
if (body !== undefined) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
finalHeaders["Content-Type"] ??= "application/json";
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
headers: finalHeaders,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
|
||||
@@ -59,7 +60,10 @@ export async function apiRequest<T>(
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => apiRequest<T>("GET", path),
|
||||
post: <T>(path: string, body?: unknown) => apiRequest<T>("POST", path, body),
|
||||
delete: <T>(path: string) => apiRequest<T>("DELETE", path),
|
||||
get: <T>(path: string, headers?: Record<string, string>) =>
|
||||
apiRequest<T>("GET", path, undefined, headers),
|
||||
post: <T>(path: string, body?: unknown, headers?: Record<string, string>) =>
|
||||
apiRequest<T>("POST", path, body, headers),
|
||||
delete: <T>(path: string, headers?: Record<string, string>) =>
|
||||
apiRequest<T>("DELETE", path, undefined, headers),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user