api.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. const baseURL =
  2. import.meta.env.VITE_API_BASE_URL || "http://localhost:8000";
  3. export interface ResearchRequest {
  4. topic: string;
  5. search_api?: string;
  6. }
  7. export interface ResearchStreamEvent {
  8. type: string;
  9. [key: string]: unknown;
  10. }
  11. export interface StreamOptions {
  12. signal?: AbortSignal;
  13. }
  14. export async function runResearchStream(
  15. payload: ResearchRequest,
  16. onEvent: (event: ResearchStreamEvent) => void,
  17. options: StreamOptions = {}
  18. ): Promise<void> {
  19. const response = await fetch(`${baseURL}/research/stream`, {
  20. method: "POST",
  21. headers: {
  22. "Content-Type": "application/json",
  23. Accept: "text/event-stream"
  24. },
  25. body: JSON.stringify(payload),
  26. signal: options.signal
  27. });
  28. if (!response.ok) {
  29. const errorText = await response.text().catch(() => "");
  30. throw new Error(
  31. errorText || `研究请求失败,状态码:${response.status}`
  32. );
  33. }
  34. const body = response.body;
  35. if (!body) {
  36. throw new Error("浏览器不支持流式响应,无法获取研究进度");
  37. }
  38. const reader = body.getReader();
  39. const decoder = new TextDecoder("utf-8");
  40. let buffer = "";
  41. while (true) {
  42. const { value, done } = await reader.read();
  43. buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
  44. let boundary = buffer.indexOf("\n\n");
  45. while (boundary !== -1) {
  46. const rawEvent = buffer.slice(0, boundary).trim();
  47. buffer = buffer.slice(boundary + 2);
  48. if (rawEvent.startsWith("data:")) {
  49. const dataPayload = rawEvent.slice(5).trim();
  50. if (dataPayload) {
  51. try {
  52. const event = JSON.parse(dataPayload) as ResearchStreamEvent;
  53. onEvent(event);
  54. if (event.type === "error" || event.type === "done") {
  55. return;
  56. }
  57. } catch (error) {
  58. console.error("解析流式事件失败:", error, dataPayload);
  59. }
  60. }
  61. }
  62. boundary = buffer.indexOf("\n\n");
  63. }
  64. if (done) {
  65. // 处理可能的尾巴事件
  66. if (buffer.trim()) {
  67. const rawEvent = buffer.trim();
  68. if (rawEvent.startsWith("data:")) {
  69. const dataPayload = rawEvent.slice(5).trim();
  70. if (dataPayload) {
  71. try {
  72. const event = JSON.parse(dataPayload) as ResearchStreamEvent;
  73. onEvent(event);
  74. } catch (error) {
  75. console.error("解析流式事件失败:", error, dataPayload);
  76. }
  77. }
  78. }
  79. }
  80. break;
  81. }
  82. }
  83. }