messageUtils.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { THINK_TAG_REGEX, MESSAGE_ROLES } from './constants';
  2. // 生成唯一ID
  3. let messageId = 4;
  4. export const generateMessageId = () => `${messageId++}`;
  5. // 提取消息中的文本内容
  6. export const getTextContent = (message) => {
  7. if (!message || !message.content) return '';
  8. if (Array.isArray(message.content)) {
  9. const textContent = message.content.find(item => item.type === 'text');
  10. return textContent?.text || '';
  11. }
  12. return typeof message.content === 'string' ? message.content : '';
  13. };
  14. // 处理 think 标签
  15. export const processThinkTags = (content, reasoningContent = '') => {
  16. if (!content || !content.includes('<think>')) {
  17. return { content, reasoningContent };
  18. }
  19. const thoughts = [];
  20. const replyParts = [];
  21. let lastIndex = 0;
  22. let match;
  23. THINK_TAG_REGEX.lastIndex = 0;
  24. while ((match = THINK_TAG_REGEX.exec(content)) !== null) {
  25. replyParts.push(content.substring(lastIndex, match.index));
  26. thoughts.push(match[1]);
  27. lastIndex = match.index + match[0].length;
  28. }
  29. replyParts.push(content.substring(lastIndex));
  30. const processedContent = replyParts.join('').replace(/<\/?think>/g, '').trim();
  31. const thoughtsStr = thoughts.join('\n\n---\n\n');
  32. const processedReasoningContent = reasoningContent && thoughtsStr
  33. ? `${reasoningContent}\n\n---\n\n${thoughtsStr}`
  34. : reasoningContent || thoughtsStr;
  35. return {
  36. content: processedContent,
  37. reasoningContent: processedReasoningContent
  38. };
  39. };
  40. // 处理未完成的 think 标签
  41. export const processIncompleteThinkTags = (content, reasoningContent = '') => {
  42. if (!content) return { content: '', reasoningContent };
  43. const lastOpenThinkIndex = content.lastIndexOf('<think>');
  44. if (lastOpenThinkIndex === -1) {
  45. return processThinkTags(content, reasoningContent);
  46. }
  47. const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex);
  48. if (!fragmentAfterLastOpen.includes('</think>')) {
  49. const unclosedThought = fragmentAfterLastOpen.substring('<think>'.length).trim();
  50. const cleanContent = content.substring(0, lastOpenThinkIndex);
  51. const processedReasoningContent = unclosedThought
  52. ? reasoningContent ? `${reasoningContent}\n\n---\n\n${unclosedThought}` : unclosedThought
  53. : reasoningContent;
  54. return processThinkTags(cleanContent, processedReasoningContent);
  55. }
  56. return processThinkTags(content, reasoningContent);
  57. };
  58. // 构建消息内容(包含图片)
  59. export const buildMessageContent = (textContent, imageUrls = [], imageEnabled = false) => {
  60. if (!textContent && (!imageUrls || imageUrls.length === 0)) {
  61. return '';
  62. }
  63. const validImageUrls = imageUrls.filter(url => url && url.trim() !== '');
  64. if (imageEnabled && validImageUrls.length > 0) {
  65. return [
  66. { type: 'text', text: textContent || '' },
  67. ...validImageUrls.map(url => ({
  68. type: 'image_url',
  69. image_url: { url: url.trim() }
  70. }))
  71. ];
  72. }
  73. return textContent || '';
  74. };
  75. // 创建新消息
  76. export const createMessage = (role, content, options = {}) => ({
  77. role,
  78. content,
  79. createAt: Date.now(),
  80. id: generateMessageId(),
  81. ...options
  82. });
  83. // 创建加载中的助手消息
  84. export const createLoadingAssistantMessage = () => createMessage(
  85. MESSAGE_ROLES.ASSISTANT,
  86. '',
  87. {
  88. reasoningContent: '',
  89. isReasoningExpanded: true,
  90. isThinkingComplete: false,
  91. hasAutoCollapsed: false,
  92. status: 'loading'
  93. }
  94. );
  95. // 检查消息是否包含图片
  96. export const hasImageContent = (message) => {
  97. return message &&
  98. Array.isArray(message.content) &&
  99. message.content.some(item => item.type === 'image_url');
  100. };
  101. // 格式化消息用于API请求
  102. export const formatMessageForAPI = (message) => {
  103. if (!message) return null;
  104. return {
  105. role: message.role,
  106. content: message.content
  107. };
  108. };
  109. // 验证消息是否有效
  110. export const isValidMessage = (message) => {
  111. return message &&
  112. message.role &&
  113. (message.content || message.content === '');
  114. };
  115. // 获取最后一条用户消息
  116. export const getLastUserMessage = (messages) => {
  117. if (!Array.isArray(messages)) return null;
  118. for (let i = messages.length - 1; i >= 0; i--) {
  119. if (messages[i].role === MESSAGE_ROLES.USER) {
  120. return messages[i];
  121. }
  122. }
  123. return null;
  124. };
  125. // 获取最后一条助手消息
  126. export const getLastAssistantMessage = (messages) => {
  127. if (!Array.isArray(messages)) return null;
  128. for (let i = messages.length - 1; i >= 0; i--) {
  129. if (messages[i].role === MESSAGE_ROLES.ASSISTANT) {
  130. return messages[i];
  131. }
  132. }
  133. return null;
  134. };
  135. // 构建API请求负载(从apiUtils移动过来)
  136. export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => {
  137. const processedMessages = messages
  138. .filter(isValidMessage)
  139. .map(formatMessageForAPI)
  140. .filter(Boolean);
  141. // 如果有系统提示,插入到消息开头
  142. if (systemPrompt && systemPrompt.trim()) {
  143. processedMessages.unshift({
  144. role: MESSAGE_ROLES.SYSTEM,
  145. content: systemPrompt.trim()
  146. });
  147. }
  148. const payload = {
  149. model: inputs.model,
  150. messages: processedMessages,
  151. stream: inputs.stream,
  152. };
  153. // 添加启用的参数
  154. const parameterMappings = {
  155. temperature: 'temperature',
  156. top_p: 'top_p',
  157. max_tokens: 'max_tokens',
  158. frequency_penalty: 'frequency_penalty',
  159. presence_penalty: 'presence_penalty',
  160. seed: 'seed'
  161. };
  162. Object.entries(parameterMappings).forEach(([key, param]) => {
  163. if (parameterEnabled[key] && inputs[param] !== undefined && inputs[param] !== null) {
  164. payload[param] = inputs[param];
  165. }
  166. });
  167. return payload;
  168. };