| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- 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<string, unknown>;
- 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<ConversationListResponse> {
- 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<ConversationSummary[]> {
- 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<ConversationDetail | null> {
- 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<void> {
- 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<UpdateConversationContactResult> {
- 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<AutoCloseConversationDaysPolicy> {
- 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();
- }
|