messageApi.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { apiUrl } from "@/lib/config";
  2. import { MessageItem } from "../types";
  3. import { reportFrontendLog } from "./systemLogApi";
  4. interface SendMessagePayload {
  5. conversationId: number;
  6. content: string;
  7. senderId?: number;
  8. senderIsAgent?: boolean;
  9. fileUrl?: string;
  10. fileType?: "image" | "document";
  11. fileName?: string;
  12. fileSize?: number;
  13. mimeType?: string;
  14. useKnowledgeBase?: boolean;
  15. useLLM?: boolean;
  16. useWebSearch?: boolean;
  17. needWebSearch?: boolean;
  18. }
  19. // 文件上传结果
  20. export interface UploadFileResult {
  21. file_url: string;
  22. file_type: "image" | "document";
  23. file_name: string;
  24. file_size: number;
  25. mime_type: string;
  26. }
  27. export async function fetchMessages(
  28. conversationId: number,
  29. includeAIMessages: boolean = false
  30. ): Promise<MessageItem[]> {
  31. const res = await fetch(
  32. `${apiUrl("/messages")}?conversation_id=${conversationId}&include_ai_messages=${includeAIMessages}`,
  33. {
  34. cache: "no-store",
  35. }
  36. );
  37. if (!res.ok) {
  38. void reportFrontendLog({
  39. level: "warn",
  40. category: "frontend",
  41. event: "fetch_messages_failed",
  42. message: "获取消息失败",
  43. conversationId,
  44. meta: { status: res.status, includeAIMessages },
  45. });
  46. throw new Error("获取消息失败");
  47. }
  48. const data = await res.json();
  49. if (!Array.isArray(data)) {
  50. return [];
  51. }
  52. return data;
  53. }
  54. // 上传文件
  55. export async function uploadFile(
  56. file: File,
  57. conversationId?: number
  58. ): Promise<UploadFileResult> {
  59. const formData = new FormData();
  60. formData.append("file", file);
  61. if (conversationId) {
  62. formData.append("conversation_id", conversationId.toString());
  63. }
  64. const res = await fetch(apiUrl("/messages/upload"), {
  65. method: "POST",
  66. body: formData,
  67. });
  68. if (!res.ok) {
  69. const error = await res.json().catch(() => ({}));
  70. void reportFrontendLog({
  71. level: "warn",
  72. category: "frontend",
  73. event: "upload_file_failed",
  74. message: "上传文件失败",
  75. conversationId,
  76. meta: { status: res.status, error },
  77. });
  78. throw new Error(error.error || "文件上传失败");
  79. }
  80. const data = await res.json();
  81. if (!data.success) {
  82. throw new Error(data.error || "文件上传失败");
  83. }
  84. return data.data;
  85. }
  86. export async function sendMessage({
  87. conversationId,
  88. content,
  89. senderId,
  90. senderIsAgent = true,
  91. fileUrl,
  92. fileType,
  93. fileName,
  94. fileSize,
  95. mimeType,
  96. useKnowledgeBase,
  97. useLLM,
  98. useWebSearch,
  99. needWebSearch,
  100. }: SendMessagePayload): Promise<void> {
  101. const payload: Record<string, unknown> = {
  102. conversation_id: conversationId,
  103. content,
  104. sender_is_agent: senderIsAgent,
  105. sender_id: typeof senderId === "number" ? senderId : 0,
  106. };
  107. if (fileUrl) {
  108. payload.file_url = fileUrl;
  109. if (fileType) payload.file_type = fileType;
  110. if (fileName) payload.file_name = fileName;
  111. if (fileSize) payload.file_size = fileSize;
  112. if (mimeType) payload.mime_type = mimeType;
  113. }
  114. if (useKnowledgeBase !== undefined) payload.use_knowledge_base = useKnowledgeBase;
  115. if (useLLM !== undefined) payload.use_llm = useLLM;
  116. if (useWebSearch !== undefined) payload.use_web_search = useWebSearch;
  117. if (needWebSearch !== undefined) payload.need_web_search = needWebSearch;
  118. const res = await fetch(apiUrl("/messages"), {
  119. method: "POST",
  120. headers: { "Content-Type": "application/json" },
  121. body: JSON.stringify(payload),
  122. });
  123. if (!res.ok) {
  124. const error = await res.json().catch(() => ({}));
  125. console.error(
  126. `❌ 发送消息失败: 对话ID=${conversationId}, 状态=${res.status}, 错误=${JSON.stringify(error)}`
  127. );
  128. void reportFrontendLog({
  129. level: "error",
  130. category: "frontend",
  131. event: "send_message_failed",
  132. message: "发送消息失败",
  133. conversationId,
  134. meta: { status: res.status, error },
  135. });
  136. throw new Error(error.error || "发送消息失败");
  137. }
  138. }
  139. export interface MarkMessagesReadResult {
  140. message_ids: number[];
  141. unread_count: number;
  142. read_at?: string;
  143. }
  144. export async function markMessagesRead(
  145. conversationId: number,
  146. readerIsAgent: boolean
  147. ): Promise<MarkMessagesReadResult | null> {
  148. const res = await fetch(apiUrl("/messages/read"), {
  149. method: "PUT",
  150. headers: { "Content-Type": "application/json" },
  151. body: JSON.stringify({
  152. conversation_id: conversationId,
  153. reader_is_agent: readerIsAgent,
  154. }),
  155. });
  156. if (!res.ok) {
  157. return null;
  158. }
  159. const data = await res.json();
  160. return {
  161. message_ids: Array.isArray(data.message_ids) ? data.message_ids : [],
  162. unread_count:
  163. typeof data.unread_count === "number" ? data.unread_count : 0,
  164. read_at: typeof data.read_at === "string" ? data.read_at : undefined,
  165. };
  166. }