综合办公系统
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /*
  2. * @Author: ysh
  3. * @Date: 2025-07-08 16:43:22
  4. * @LastEditors: Please set LastEditors
  5. * @LastEditTime: 2025-07-16 15:49:45
  6. */
  7. import request from '@/utils/request'
  8. import { getToken } from '@/utils/auth'
  9. // 查询cmc聊天记录列表
  10. export function getAnswer(question, collectionName) {
  11. return request({
  12. url: '/llm/rag/answer',
  13. method: 'get',
  14. params: { question, collectionName }
  15. })
  16. }
  17. // 查询上下文引用文件
  18. export function getContextFile(question, collectionName) {
  19. return request({
  20. url: '/llm/rag/context',
  21. method: 'get',
  22. params: { question, collectionName }
  23. })
  24. }
  25. function parseSseEvents(buffer) {
  26. // SSE 事件以空行分隔:\n\n(兼容 \r\n)
  27. const events = []
  28. const normalized = buffer.replace(/\r\n/g, '\n')
  29. const parts = normalized.split('\n\n')
  30. // 最后一段可能是不完整事件,留给下次拼接
  31. const rest = parts.pop() ?? ''
  32. for (const part of parts) {
  33. if (!part.trim()) continue
  34. const lines = part.split('\n')
  35. const dataLines = []
  36. for (const line of lines) {
  37. if (line.startsWith('data:')) {
  38. dataLines.push(line.slice(5).trimStart())
  39. }
  40. }
  41. const data = dataLines.join('\n')
  42. if (data !== '') events.push(data)
  43. }
  44. return { events, rest }
  45. }
  46. function normalizeSseData(data) {
  47. if (!data) return ''
  48. const trimmed = data.trim()
  49. // 移除可能的JSON包装(如果有的话)
  50. if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
  51. try {
  52. const parsed = JSON.parse(trimmed)
  53. if (parsed.content) return parsed.content
  54. if (parsed.resultContent) return parsed.resultContent
  55. } catch (e) {
  56. // 不是JSON,直接返回
  57. }
  58. }
  59. return trimmed
  60. }
  61. function stripToolCallStream(text, state) {
  62. if (!text) return ''
  63. const s = String(text)
  64. if (!state.inToolCall) {
  65. const toolStart = s.indexOf('<tool_call>')
  66. if (toolStart === -1) {
  67. return s
  68. } else {
  69. state.inToolCall = true
  70. return s.slice(0, toolStart)
  71. }
  72. } else {
  73. const toolEnd = s.indexOf('</tool_call>')
  74. if (toolEnd === -1) {
  75. return ''
  76. } else {
  77. state.inToolCall = false
  78. return s.slice(toolEnd + 11)
  79. }
  80. }
  81. }
  82. function streamFetchSse(url, onMessage, onError, onComplete) {
  83. const controller = new AbortController()
  84. const toolCallState = { inToolCall: false }
  85. fetch(url, {
  86. method: 'GET',
  87. headers: {
  88. 'Authorization': 'Bearer ' + getToken(),
  89. 'Accept': 'text/event-stream',
  90. 'Cache-Control': 'no-cache'
  91. },
  92. signal: controller.signal
  93. })
  94. .then(async (response) => {
  95. if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
  96. if (!response.body) throw new Error('ReadableStream not supported')
  97. const reader = response.body.getReader()
  98. const decoder = new TextDecoder('utf-8')
  99. let buffer = ''
  100. while (true) {
  101. const { done, value } = await reader.read()
  102. if (done) break
  103. buffer += decoder.decode(value, { stream: true })
  104. const parsed = parseSseEvents(buffer)
  105. buffer = parsed.rest
  106. for (const data of parsed.events) {
  107. const normalized = normalizeSseData(data)
  108. if (normalized === '[DONE]') {
  109. onComplete()
  110. controller.abort()
  111. return
  112. }
  113. const visible = stripToolCallStream(normalized, toolCallState)
  114. if (visible !== '') onMessage(visible)
  115. }
  116. }
  117. // 兜底:流结束但没收到 [DONE]
  118. if (buffer.trim()) {
  119. const parsed = parseSseEvents(buffer + '\n\n')
  120. for (const data of parsed.events) {
  121. const normalized = normalizeSseData(data)
  122. if (normalized !== '' && normalized !== '[DONE]') {
  123. const visible = stripToolCallStream(normalized, toolCallState)
  124. if (visible !== '') onMessage(visible)
  125. }
  126. }
  127. }
  128. onComplete()
  129. })
  130. .catch((error) => {
  131. if (error.name === 'AbortError') return
  132. console.error('流式请求错误:', error)
  133. onError(error)
  134. })
  135. return controller
  136. }
  137. // 流式回答API(SSE)
  138. export function getAnswerStream(question, collectionName, onMessage, onError, onComplete) {
  139. const baseURL = process.env.VUE_APP_BASE_API
  140. const url = `${baseURL}/llm/rag/answer?question=${encodeURIComponent(question)}&collectionName=${encodeURIComponent(collectionName)}`
  141. return streamFetchSse(url, onMessage, onError, onComplete)
  142. }