index.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import { currentHost } from '@/utils/location'
  2. /**
  3. * 转义处理响应值为 data: 的 json 字符串
  4. * 如: 科大讯飞星火、Kimi Moonshot 等大模型的 response
  5. */
  6. export const createParser = () => {
  7. let keepAliveShown = false
  8. const resetKeepAliveParser = () => {
  9. keepAliveShown = false
  10. }
  11. const parseJsonLikeData = (content) => {
  12. // 若是终止信号,则直接结束
  13. if (content === '[DONE]') {
  14. // 重置 keepAlive 标志
  15. keepAliveShown = false
  16. return {
  17. done: true
  18. }
  19. }
  20. if (content.startsWith('data: ')) {
  21. keepAliveShown = false
  22. const dataString = content.substring(6).trim()
  23. if (dataString === '[DONE]') {
  24. return {
  25. done: true
  26. }
  27. }
  28. try {
  29. return JSON.parse(dataString)
  30. } catch (error) {
  31. console.error('JSON 解析错误:', error)
  32. }
  33. }
  34. // 尝试直接解析 JSON 字符串
  35. try {
  36. const trimmedContent = content.trim()
  37. if (trimmedContent === ': keep-alive') {
  38. // 如果还没有显示过 keep-alive 提示,则显示
  39. if (!keepAliveShown) {
  40. keepAliveShown = true
  41. return {
  42. isWaitQueuing: true
  43. }
  44. } else {
  45. return null
  46. }
  47. }
  48. if (!trimmedContent) {
  49. return null
  50. }
  51. if (trimmedContent.startsWith('{') && trimmedContent.endsWith('}')) {
  52. return JSON.parse(trimmedContent)
  53. }
  54. if (trimmedContent.startsWith('[') && trimmedContent.endsWith(']')) {
  55. return JSON.parse(trimmedContent)
  56. }
  57. } catch (error) {
  58. console.error('尝试直接解析 JSON 失败:', error)
  59. }
  60. return null
  61. }
  62. return {
  63. resetKeepAliveParser,
  64. parseJsonLikeData
  65. }
  66. }
  67. export const createStreamThinkTransformer = () => {
  68. let isThinking = false
  69. const resetThinkTransformer = () => {
  70. isThinking = false
  71. }
  72. const transformStreamThinkData = (content) => {
  73. const stream = parseJsonLikeData(content)
  74. if (stream && stream.done) {
  75. return {
  76. done: true
  77. }
  78. }
  79. // DeepSeek 存在限速问题,这里做一个简单处理
  80. // https://api-docs.deepseek.com/zh-cn/quick_start/rate_limit
  81. if (stream && stream.isWaitQueuing) {
  82. return {
  83. isWaitQueuing: stream.isWaitQueuing
  84. }
  85. }
  86. if (!stream || !stream.choices || stream.choices.length === 0) {
  87. return {
  88. content: ''
  89. }
  90. }
  91. const delta = stream.choices[0].delta
  92. const contentText = delta.content || ''
  93. const reasoningText = delta.reasoning_content || ''
  94. let transformedContent = ''
  95. // 开始处理推理过程
  96. if (delta.content === null && delta.reasoning_content !== null) {
  97. if (!isThinking) {
  98. transformedContent += '<think>'
  99. isThinking = true
  100. }
  101. transformedContent += reasoningText
  102. }
  103. // 当 content 出现时,说明推理结束
  104. else if (delta.content !== null && delta.reasoning_content === null) {
  105. if (isThinking) {
  106. transformedContent += '</think>\n\n'
  107. isThinking = false
  108. }
  109. transformedContent += contentText
  110. }
  111. // 当为普通模型,即不包含推理字段时,直接追加 content
  112. else if (delta.content !== null && delta.reasoning_content === undefined) {
  113. isThinking = false
  114. transformedContent += contentText
  115. }
  116. return {
  117. content: transformedContent
  118. }
  119. }
  120. return {
  121. resetThinkTransformer,
  122. transformStreamThinkData
  123. }
  124. }
  125. const { resetKeepAliveParser, parseJsonLikeData } = createParser()
  126. const { resetThinkTransformer, transformStreamThinkData } = createStreamThinkTransformer()
  127. /**
  128. * 处理大模型调用暂停、异常或结束后触发的操作
  129. */
  130. export const triggerModelTermination = () => {
  131. resetKeepAliveParser()
  132. resetThinkTransformer()
  133. }
  134. type ContentResult = {
  135. content: any
  136. } | {
  137. done: boolean
  138. }
  139. type DoneResult = {
  140. content: any
  141. isWaitQueuing?: any
  142. } & {
  143. done: boolean
  144. }
  145. export type CrossTransformFunction = (readValue: Uint8Array | string, textDecoder: TextDecoder) => DoneResult
  146. export type TransformFunction = (readValue: Uint8Array | string, textDecoder: TextDecoder) => ContentResult
  147. interface TypesModelLLM {
  148. // 模型昵称
  149. label: string
  150. // 模型标识符
  151. modelName: string
  152. // Stream 结果转换器
  153. transformStreamValue: TransformFunction
  154. // 每个大模型调用的 API 请求
  155. chatFetch: (text: string) => Promise<Response>
  156. }
  157. /** ---------------- 大模型映射列表 & Response Transform 用于处理不同类型流的值转换器 ---------------- */
  158. /**
  159. * Mock 模拟模型的 name
  160. */
  161. export const defaultMockModelName = 'standard'
  162. /**
  163. * 项目默认使用模型,按需修改此字段即可
  164. */
  165. // export const defaultModelName = 'spark'
  166. export const defaultModelName = defaultMockModelName
  167. export const modelMappingList: TypesModelLLM[] = [
  168. {
  169. label: '🧪 模拟数据模型',
  170. modelName: 'standard',
  171. transformStreamValue(readValue, textDecoder) {
  172. let content = ''
  173. if (readValue instanceof Uint8Array) {
  174. content = textDecoder.decode(readValue, {
  175. stream: true
  176. })
  177. } else {
  178. content = readValue
  179. }
  180. return {
  181. content
  182. }
  183. },
  184. // Mock Event Stream 用于模拟读取大模型接口 Mock 数据
  185. async chatFetch(text): Promise<Response> {
  186. const formData = new FormData()
  187. const history = localStorage.getItem('kaConversationsChatList') || '[]'
  188. const payload = {
  189. client_id: `${ Date.now() + 10 }`,
  190. prompt: text,
  191. history
  192. }
  193. Object.entries(payload).forEach(([key, value]) => {
  194. formData.append(key, value)
  195. })
  196. const res = await fetch(`${ currentHost.baseApi }/chat`, {
  197. method: 'POST',
  198. body: formData
  199. })
  200. return res
  201. }
  202. }
  203. ]