import { apiUrl, getAgentHeaders } from "@/lib/config"; import { ConversationDetail, ConversationSummary, } from "../types"; export type ConversationListType = "visitor" | "internal"; export type ConversationStatus = "open" | "closed"; export interface ConversationListResponse { items: ConversationSummary[]; total: number; page: number; page_size: number; has_more: boolean; total_unread: number; } const DEFAULT_PAGE_SIZE = 50; function normalizeConversationItem(item: ConversationSummary): ConversationSummary { return { ...item, unread_count: item.unread_count ?? 0, has_participated: item.has_participated ?? false, }; } function parseConversationListPayload(data: unknown): ConversationListResponse { if (Array.isArray(data)) { const items = data.map((item) => normalizeConversationItem(item as ConversationSummary) ); return { items, total: items.length, page: 1, page_size: items.length, has_more: false, total_unread: items.reduce((sum, c) => sum + (c.unread_count ?? 0), 0), }; } if (data && typeof data === "object") { const raw = data as Record; const items = Array.isArray(raw.items) ? raw.items.map((item) => normalizeConversationItem(item as ConversationSummary) ) : []; return { items, total: Number(raw.total ?? items.length), page: Number(raw.page ?? 1), page_size: Number(raw.page_size ?? items.length), has_more: Boolean(raw.has_more), total_unread: Number(raw.total_unread ?? 0), }; } return { items: [], total: 0, page: 1, page_size: DEFAULT_PAGE_SIZE, has_more: false, total_unread: 0, }; } export async function fetchConversations( userId?: number, opts?: { type?: ConversationListType; status?: ConversationStatus; page?: number; page_size?: number; } ): Promise { const params = new URLSearchParams(); if (userId) params.set("user_id", String(userId)); if (opts?.type) params.set("type", opts.type); if (opts?.status) params.set("status", opts.status); params.set("page", String(opts?.page ?? 1)); params.set("page_size", String(opts?.page_size ?? DEFAULT_PAGE_SIZE)); const url = `${apiUrl("/conversations")}?${params.toString()}`; const res = await fetch(url, { cache: "no-store", headers: getAgentHeaders() }); if (!res.ok) { throw new Error("获取对话列表失败"); } const data = await res.json(); return parseConversationListPayload(data); } /** 创建一条内部对话(知识库测试),返回新对话 ID */ export async function initInternalConversation(userId: number): Promise<{ conversation_id: number }> { const res = await fetch(`${apiUrl("/conversations/internal")}?user_id=${userId}`, { method: "POST", headers: { "Content-Type": "application/json", ...getAgentHeaders() }, }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as { error?: string }).error || "创建内部对话失败"); } const data = await res.json(); return { conversation_id: data.conversation_id }; } export async function searchConversations( query: string, userId?: number, opts?: { status?: ConversationStatus; type?: ConversationListType } ): Promise { const status = opts?.status ?? "open"; const listType = opts?.type ?? "visitor"; const params = new URLSearchParams({ q: query, status, type: listType, }); if (userId) { params.set("user_id", String(userId)); } const url = `${apiUrl("/conversations/search")}?${params.toString()}`; const res = await fetch(url, { cache: "no-store", headers: getAgentHeaders(), }); if (!res.ok) { throw new Error("搜索对话失败"); } const data = await res.json(); if (!Array.isArray(data)) { return []; } return data.map((item) => ({ ...item, unread_count: item.unread_count ?? 0, has_participated: item.has_participated ?? false, })); } export async function fetchConversationDetail( conversationId: number, userId?: number ): Promise { const url = userId ? `${apiUrl(`/conversations/${conversationId}`)}?user_id=${userId}` : apiUrl(`/conversations/${conversationId}`); const res = await fetch(url, { cache: "no-store", headers: getAgentHeaders() }); if (!res.ok) { return null; } const data = await res.json(); return { ...data, unread_count: data.unread_count ?? 0, }; } /** 关闭会话(进入历史/归档)。访客再次发消息会自动 reopen(B 方案)。 */ export async function closeConversation(conversationId: number): Promise { const res = await fetch(apiUrl(`/conversations/${conversationId}/close`), { method: "POST", headers: getAgentHeaders(), }); if (!res.ok) { const j = await res.json().catch(() => ({})); throw new Error((j as { error?: string }).error || `关闭会话失败(${res.status})`); } } export interface UpdateConversationContactPayload { email?: string; phone?: string; notes?: string; } export interface UpdateConversationContactResult { email: string; phone: string; notes: string; } export async function updateConversationContact( conversationId: number, payload: UpdateConversationContactPayload ): Promise { const res = await fetch( apiUrl(`/conversations/${conversationId}/contact`), { method: "PUT", headers: { "Content-Type": "application/json", ...getAgentHeaders() }, body: JSON.stringify(payload), } ); if (!res.ok) { throw new Error("更新访客联系信息失败"); } const data = await res.json(); return { email: data.email ?? "", phone: data.phone ?? "", notes: data.notes ?? "", }; } export interface AutoCloseConversationDaysPolicy { effective_days: number; env_days: number; persisted_in_database: boolean; } export async function fetchAutoCloseConversationDaysPolicy(): Promise { const res = await fetch(apiUrl("/conversations/maintenance/auto-close-days"), { cache: "no-store", headers: getAgentHeaders(), }); if (!res.ok) { throw new Error("获取会话维护配置失败"); } return res.json(); } export async function putAutoCloseConversationDaysPolicy( inactiveDays: number ): Promise<{ effective_days: number }> { const res = await fetch(apiUrl("/conversations/maintenance/auto-close-days"), { method: "PUT", headers: { "Content-Type": "application/json", ...getAgentHeaders() }, body: JSON.stringify({ inactive_days: inactiveDays }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as { error?: string }).error || "保存会话维护配置失败"); } return res.json(); } export async function deleteAutoCloseConversationDaysPolicy(): Promise<{ effective_days: number }> { const res = await fetch(apiUrl("/conversations/maintenance/auto-close-days"), { method: "DELETE", headers: getAgentHeaders(), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as { error?: string }).error || "恢复默认配置失败"); } return res.json(); }