conversationApi.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { apiUrl } from "@/lib/config";
  2. import {
  3. getVisitorConversationHeaders,
  4. saveVisitorAccessToken,
  5. } from "@/lib/visitor-session";
  6. import { reportFrontendLog } from "@/features/agent/services/systemLogApi";
  7. export interface InitVisitorConversationPayload {
  8. visitorId: number;
  9. website?: string;
  10. referrer?: string;
  11. browser?: string;
  12. os?: string;
  13. language?: string;
  14. ipAddress?: string;
  15. chatMode?: string; // 对话模式:human(人工客服)、ai(AI客服)
  16. aiConfigId?: number; // AI 配置 ID(访客选择的模型配置,AI 模式时必需)
  17. }
  18. export interface InitVisitorConversationResult {
  19. conversation_id: number;
  20. status: string;
  21. access_token: string;
  22. }
  23. export async function initVisitorConversation(
  24. payload: InitVisitorConversationPayload
  25. ): Promise<InitVisitorConversationResult> {
  26. const res = await fetch(apiUrl("/conversation/init"), {
  27. method: "POST",
  28. headers: { "Content-Type": "application/json" },
  29. body: JSON.stringify({
  30. visitor_id: payload.visitorId,
  31. website: payload.website,
  32. referrer: payload.referrer,
  33. browser: payload.browser,
  34. os: payload.os,
  35. language: payload.language,
  36. ip_address: payload.ipAddress,
  37. chat_mode: payload.chatMode,
  38. ai_config_id: payload.aiConfigId,
  39. }),
  40. });
  41. if (!res.ok) {
  42. void reportFrontendLog({
  43. level: "warn",
  44. category: "frontend",
  45. event: "visitor_init_conversation_failed",
  46. message: "访客初始化对话失败",
  47. visitorId: payload.visitorId,
  48. meta: { status: res.status, chatMode: payload.chatMode, aiConfigId: payload.aiConfigId },
  49. });
  50. throw new Error("初始化对话失败");
  51. }
  52. const data = await res.json();
  53. const conversationId = data.conversation_id ?? 0;
  54. const accessToken = typeof data.access_token === "string" ? data.access_token : "";
  55. if (conversationId && accessToken) {
  56. saveVisitorAccessToken(conversationId, accessToken);
  57. }
  58. return {
  59. conversation_id: conversationId,
  60. status: data.status ?? "open",
  61. access_token: accessToken,
  62. };
  63. }
  64. /** 访客更新本会话的联系邮箱(可选,用于离线收消息) */
  65. export async function updateVisitorContactEmail(
  66. conversationId: number,
  67. email: string,
  68. accessToken?: string | null
  69. ): Promise<{ email: string }> {
  70. const res = await fetch(apiUrl(`/conversations/${conversationId}/contact`), {
  71. method: "PUT",
  72. headers: {
  73. "Content-Type": "application/json",
  74. ...getVisitorConversationHeaders(conversationId, accessToken),
  75. },
  76. body: JSON.stringify({ email: email.trim() }),
  77. });
  78. if (!res.ok) {
  79. const err = await res.json().catch(() => ({}));
  80. throw new Error(err.error || "保存邮箱失败");
  81. }
  82. const data = await res.json();
  83. return { email: data.email ?? email.trim() };
  84. }