UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

180 lines (160 loc) 5.86 kB
import { template } from 'lodash-es'; import { uuid } from '@/utils/uuid'; import { useUserStore } from '@/store/user'; import { userProfileSelectors } from '@/store/user/selectors'; const placeholderVariablesRegex = /{{(.*?)}}/g; /* eslint-disable sort-keys-fix/sort-keys-fix */ export const VARIABLE_GENERATORS = { /** * 时间类模板变量 * * | Value | Example | * |-------|---------| * | `{{date}}` | 12/25/2023 | * | `{{datetime}}` | 12/25/2023, 2:30:45 PM | * | `{{day}}` | 25 | * | `{{hour}}` | 14 | * | `{{iso}}` | 2023-12-25T14:30:45.123Z | * | `{{locale}}` | zh-CN | * | `{{minute}}` | 30 | * | `{{month}}` | 12 | * | `{{second}}` | 45 | * | `{{time}}` | 2:30:45 PM | * | `{{timestamp}}` | 1703538645123 | * | `{{timezone}}` | America/New_York | * | `{{weekday}}` | Monday | * | `{{year}}` | 2023 | * */ date: () => new Date().toLocaleDateString(), datetime: () => new Date().toLocaleString(), day: () => new Date().getDate().toString().padStart(2, '0'), hour: () => new Date().getHours().toString().padStart(2, '0'), iso: () => new Date().toISOString(), locale: () => Intl.DateTimeFormat().resolvedOptions().locale, minute: () => new Date().getMinutes().toString().padStart(2, '0'), month: () => (new Date().getMonth() + 1).toString().padStart(2, '0'), second: () => new Date().getSeconds().toString().padStart(2, '0'), time: () => new Date().toLocaleTimeString(), timestamp: () => Date.now().toString(), timezone: () => Intl.DateTimeFormat().resolvedOptions().timeZone, weekday: () => new Date().toLocaleDateString('en-US', { weekday: 'long' }), year: () => new Date().getFullYear().toString(), /** * 用户信息类模板变量 * * | Value | Example | * |-------|---------| * | `{{email}}` | demo@lobehub.com | * | `{{nickname}}` | 社区版用户 | * | `{{username}}` | LobeChat | * */ email: () => userProfileSelectors.email(useUserStore.getState()) ?? '', nickname: () => userProfileSelectors.nickName(useUserStore.getState()) ?? '', username: () => userProfileSelectors.displayUserName(useUserStore.getState()) ?? userProfileSelectors.fullName(useUserStore.getState()) ?? '', /** * 随机值类模板变量 * * | Value | Example | * |-------|---------| * | `{{random}}` | 100041 | * | `{{random_bool}}` | true | * | `{{random_float}}` | 76.02 | * | `{{random_hex}}` | de0dbd | * | `{{random_int}}` | 68 | * | `{{random_string}}` | wqn9zfrqe7h | * */ random: () => Math.floor(Math.random() * 1_000_000 + 1).toString(), random_bool: () => (Math.random() > 0.5 ? 'true' : 'false'), random_float: () => (Math.random() * 100).toFixed(2), random_hex: () => Math.floor(Math.random() * 16_777_215).toString(16).padStart(6, '0'), random_int: () => Math.floor(Math.random() * 100 + 1).toString(), random_string: () => Math.random().toString(36).slice(2, 15), random_digit: () => Math.floor(Math.random() * 10).toString(), /** * UUID 类模板变量 * * | Value | Example | * |-------|---------| * | `{{uuid}}` | dd90b35-669f-4e87-beb8-ac6877f6995d | * | `{{uuid_short}}` | dd90b35 | * */ uuid: () => uuid(), uuid_short: () => uuid().split('-')[0], /** * 平台类模板变量 * * | Value | Example | * |-------|---------| * | `{{language}}` | zh-CN | * | `{{platform}}` | MacIntel | * | `{{user_agent}}` | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0 | * */ language: () => typeof navigator !== 'undefined' ? navigator.language : '', platform: () => typeof navigator !== 'undefined' ? navigator.platform : '', user_agent: () => typeof navigator !== 'undefined' ? navigator.userAgent : '', } as Record<string, () => string>; /** * 从文本中提取所有 {{variable}} 占位符的变量名 * @param text 包含模板变量的字符串 * @returns 变量名数组,如 ['date', 'nickname'] */ const extractPlaceholderVariables = (text: string): string[] => { const matches = [...text.matchAll(placeholderVariablesRegex)]; return matches.map(m => m[1].trim()); }; /** * 将模板变量替换为实际值,并支持递归解析嵌套变量 * @param text - 含变量的原始文本 * @param depth - 递归深度,默认 1,设置更高可支持 {{text}} 中的 {{date}} 等 * @returns 替换后的文本 */ export const parsePlaceholderVariables = (text: string, depth = 2): string => { let result = text; // 递归解析,用于处理如 {{text}} 存在额外预设变量 for (let i = 0; i < depth; i++) { try { const variables = Object.fromEntries( extractPlaceholderVariables(result) .map((key) => [key, VARIABLE_GENERATORS[key]?.()]) .filter(([, value]) => value !== undefined) ); const replaced = template(result, { interpolate: placeholderVariablesRegex })(variables); if (replaced === result) break; result = replaced; } catch { break; } } return result; }; /** * 解析消息内容,替换占位符变量 * @param messages 原始消息数组 * @returns 处理后的消息数组 */ export const parsePlaceholderVariablesMessages = (messages: any[]): any[] => messages.map(message => { if (!message?.content) return message; const { content } = message; // 字符串类型直接处理 if (typeof content === 'string') { return { ...message, content: parsePlaceholderVariables(content) }; } // 数组类型处理其中的 text 元素 if (Array.isArray(content)) { return { ...message, content: content.map(item => item?.type === 'text' ? { ...item, text: parsePlaceholderVariables(item.text) } : item ) }; } return message; });