useDataInterpretation.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { ref } from 'vue';
  2. import { fetchTunnelInterpretation, fetchDeviceInterpretation } from './api';
  3. import { createTrigger, type TriggerHandle } from './useDataInterpretationTrigger';
  4. import type { TodoItem, ToolCallRecord } from './types';
  5. /** 数据解读 hook —— 调用后端 SSE 流式接口,实时追踪所有过程状态 */
  6. export function useDataInterpretation() {
  7. const loading = ref(false);
  8. const streamingText = ref('');
  9. const error = ref<string | null>(null);
  10. const threadId = ref('');
  11. const sessionId = ref('');
  12. // 过程状态
  13. const thinking = ref(false);
  14. const todos = ref<TodoItem[]>([]);
  15. const executingTools = ref<string[]>([]);
  16. const toolCalls = ref<ToolCallRecord[]>([]);
  17. /** 统一的事件处理 */
  18. function handleEvent(event: any) {
  19. switch (event.type) {
  20. case 'thinking':
  21. thinking.value = true;
  22. break;
  23. case 'executing':
  24. thinking.value = false;
  25. executingTools.value = event.tools || [];
  26. break;
  27. case 'tool_call':
  28. executingTools.value = [];
  29. toolCalls.value = [...toolCalls.value, { tool: event.tool || '', source: event.source || 'main', status: 'call' as const }];
  30. break;
  31. case 'tool_result':
  32. // 更新最近一条 tool_call 为 result 状态
  33. if (toolCalls.value.length > 0) {
  34. const last = toolCalls.value[toolCalls.value.length - 1];
  35. if (last.tool === event.tool && last.status === 'call') {
  36. last.status = 'result';
  37. }
  38. }
  39. break;
  40. case 'updated_todo_list':
  41. thinking.value = false;
  42. todos.value = event.todos || [];
  43. break;
  44. case 'token':
  45. thinking.value = false;
  46. if (event.content) {
  47. streamingText.value += event.content;
  48. }
  49. break;
  50. }
  51. }
  52. async function interpretTunnel(params: { tun_id: string; tun_name: string }) {
  53. loading.value = true;
  54. error.value = null;
  55. streamingText.value = '';
  56. thinking.value = false;
  57. todos.value = [];
  58. executingTools.value = [];
  59. toolCalls.value = [];
  60. try {
  61. const result = await fetchTunnelInterpretation(params, handleEvent);
  62. threadId.value = result.thread_id;
  63. sessionId.value = result.session_id;
  64. } catch (e: any) {
  65. const msg = e?.message || '数据解读请求失败';
  66. error.value = msg;
  67. throw e;
  68. } finally {
  69. loading.value = false;
  70. thinking.value = false;
  71. executingTools.value = [];
  72. }
  73. }
  74. async function interpretDevice(params: { device_id: string; device_name: string; device_type: string }) {
  75. loading.value = true;
  76. error.value = null;
  77. streamingText.value = '';
  78. thinking.value = false;
  79. todos.value = [];
  80. executingTools.value = [];
  81. toolCalls.value = [];
  82. try {
  83. const result = await fetchDeviceInterpretation(params, handleEvent);
  84. threadId.value = result.thread_id;
  85. sessionId.value = result.session_id;
  86. } catch (e: any) {
  87. const msg = e?.message || '数据解读请求失败';
  88. error.value = msg;
  89. throw e;
  90. } finally {
  91. loading.value = false;
  92. thinking.value = false;
  93. executingTools.value = [];
  94. }
  95. }
  96. function reset() {
  97. streamingText.value = '';
  98. error.value = null;
  99. threadId.value = '';
  100. sessionId.value = '';
  101. thinking.value = false;
  102. todos.value = [];
  103. executingTools.value = [];
  104. toolCalls.value = [];
  105. }
  106. return {
  107. loading,
  108. streamingText,
  109. error,
  110. threadId,
  111. sessionId,
  112. thinking,
  113. todos,
  114. executingTools,
  115. toolCalls,
  116. interpretTunnel,
  117. interpretDevice,
  118. reset,
  119. };
  120. }
  121. /**
  122. * 数据解读列表级便捷 Hook。
  123. * 整合了弹框状态 + 每行数据的悬浮触发器管理 + SSE 流式解读。
  124. *
  125. * @param mapParams - 将列表行数据映射为接口请求参数
  126. *
  127. * 使用:在列表/表格/卡片组等 v-for 场景的组件中调用一次即可。
  128. */
  129. export function useDataInterpretationList(
  130. mapParams: (item: any) => { tun_id: string; tun_name: string } | { device_id: string; device_name: string; device_type: string }
  131. ) {
  132. const { loading, streamingText, error, threadId, sessionId, thinking, todos, executingTools, toolCalls, interpretTunnel, interpretDevice, reset } =
  133. useDataInterpretation();
  134. const modalVisible = ref(false);
  135. const triggerMap = new Map<string, TriggerHandle>();
  136. function getTrigger(id: string): TriggerHandle {
  137. if (!triggerMap.has(id)) {
  138. triggerMap.set(id, createTrigger());
  139. }
  140. return triggerMap.get(id)!;
  141. }
  142. async function handleInterpret(item: any) {
  143. modalVisible.value = true;
  144. const params = mapParams(item);
  145. if ('tun_id' in params) {
  146. await interpretTunnel(params);
  147. } else {
  148. await interpretDevice(params);
  149. }
  150. }
  151. function handleClose() {
  152. modalVisible.value = false;
  153. reset();
  154. }
  155. return {
  156. modalVisible,
  157. loading,
  158. streamingText,
  159. error,
  160. threadId,
  161. sessionId,
  162. thinking,
  163. todos,
  164. executingTools,
  165. toolCalls,
  166. getTrigger,
  167. handleInterpret,
  168. handleClose,
  169. };
  170. }