api.ts 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { consumeSse } from '/@/views/ventAI/manageAssistent/api/sse';
  2. // [审计-第四轮 M2] 重放自我方 fix/base-agent-token(M1/M2/M3):复用对话框模块既有的结构化错误解析器,避免两套实现各自演化
  3. import { throwStreamHttpError } from '/@/views/ventAI/manageAssistent/api/client';
  4. import type { SSERawEvent } from './types';
  5. // [审计-第四轮 M1] 重放自我方 fix/base-agent-token(M1/M2/M3):取基座登录态的令牌(与对话链路 manageAssistent/api/client.ts 同源)
  6. import { getToken } from '/@/utils/auth';
  7. enum Api {
  8. clickTun = '/ventAI/api/interpret/click/tun',
  9. clickDevice = '/ventAI/api/interpret/click/device',
  10. }
  11. /**
  12. * 巷道数据解读(SSE 流式)
  13. * @param params - { tun_id, tun_name }
  14. * @param onEvent - 每个 SSE 事件的回调
  15. * @returns 流完成后的 thread_id、session_id
  16. */
  17. export async function fetchTunnelInterpretation(
  18. params: { tun_id: string; tun_name: string; mode?: string },
  19. onEvent: (event: SSERawEvent) => void
  20. ): Promise<{ thread_id: string; session_id: string }> {
  21. return ssePost(Api.clickTun, params, onEvent);
  22. }
  23. /**
  24. * 设备数据解读(SSE 流式)
  25. * @param params - { device_id, device_name, device_type }
  26. * @param onEvent - 每个 SSE 事件的回调
  27. * @returns 流完成后的 thread_id、session_id
  28. */
  29. export async function fetchDeviceInterpretation(
  30. params: { device_id: string; device_name: string; device_type: string; mode?: string },
  31. onEvent: (event: SSERawEvent) => void
  32. ): Promise<{ thread_id: string; session_id: string }> {
  33. return ssePost(Api.clickDevice, params, onEvent);
  34. }
  35. /** SSE 流式 POST 请求的通用实现 */
  36. async function ssePost(
  37. url: string,
  38. body: Record<string, string>,
  39. onEvent: (event: SSERawEvent) => void
  40. ): Promise<{ thread_id: string; session_id: string }> {
  41. const response = await fetch(url, {
  42. method: 'POST',
  43. headers: {
  44. 'Content-Type': 'application/json',
  45. // [审计-第四轮 M1] 重放自我方 fix/base-agent-token(M1/M2/M3)
  46. // 原行为:点选解读不带令牌,是基座里唯一一条「匿名」调 TfAgents 的路径,
  47. // 消耗只能记到兜底账号 click-anonymous,无法按登录人记账。
  48. // 现行为:补上与对话链路同名的请求头(TfAgents 侧读小写 x-access-token,HTTP 头不区分大小写)。
  49. // 为什么:恢复按登录人记账归因。
  50. // ⚠️ 令牌是「可选」语义:getToken() 为空时仍照常发请求(TfAgents 侧有 click-anonymous 兜底),
  51. // 禁止加「未登录就拦截/跳登录」的判断(我方 D5 决策)——那会把未登录场景直接变成不可用。
  52. 'X-Access-Token': getToken(),
  53. },
  54. body: JSON.stringify(body),
  55. });
  56. if (!response.ok) {
  57. // [审计-第四轮 M2] 重放自我方 fix/base-agent-token(M1/M2/M3)
  58. // 原行为:throw new Error(`HTTP错误: ${response.status}`) —— 用户只看到「HTTP错误: 429」。
  59. // 现行为:复用 manageAssistent/api/client.ts 的 throwStreamHttpError,它会解析响应体的
  60. // detail(字符串或 { code, message, retry_after, limit_tokens, spent_tokens }),
  61. // 抛出携带 status/retryAfter/limitTokens/spentTokens 的 StreamError;
  62. // 最终文案由 useDataPicker.ts 的 formatClickError() 统一渲染。
  63. // 为什么:429/413/404 等错误需要可读文案,且与对话链路共用一套解析器不重复造轮子。
  64. await throwStreamHttpError(response);
  65. }
  66. // 该接口只把 done / error 当终止事件(与统一对话不同,不含 interrupt)
  67. const { sessionId, threadId } = await consumeSse<SSERawEvent>(response, onEvent, { terminateOn: ['done', 'error'] });
  68. return { thread_id: threadId, session_id: sessionId };
  69. }