UNPKG

@liuyuzhao/ai-chat

Version:

A Vue 3 AI chat component with TypeScript support for Web, H5, and UniApp

1,069 lines 51.4 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; import { ref, onMounted, defineComponent, watch, createElementBlock, openBlock, createCommentVNode, createElementVNode, unref, toDisplayString, Fragment, renderList, normalizeClass, createTextVNode, withDirectives, withKeys, isRef, vModelText } from "vue"; import { marked } from "marked"; import DOMPurify from "dompurify"; const detectPlatform = () => { if (typeof uni !== "undefined") { return "uniapp"; } if (typeof window !== "undefined") { return "web"; } return "node"; }; class AIService { constructor(config) { __publicField(this, "config"); this.config = config; } connect(messages, sessionId, requestConfig, onMessage, onError, onComplete) { return __async(this, null, function* () { try { const params = new URLSearchParams(); const prompt = `${messages[messages.length - 1][0].content}`; params.append("prompt", prompt); if (sessionId) { params.append("sessionId", sessionId); } if (requestConfig.requestParams) { Object.entries(requestConfig.requestParams).forEach(([key, value]) => { params.append(key, String(value)); }); } if (!params.has("lawyerName")) { params.append("lawyerName", requestConfig.lawyerName || ""); } if (requestConfig.lawyerAllowedPort) { params.append("lawyerAllowedPort", requestConfig.lawyerAllowedPort || ""); } if (!params.has("knowledgeCode")) { params.append("knowledgeCode", requestConfig.knowledgeCode || ""); } if (!params.has("hotIssues")) { params.append("hotIssues", requestConfig.hotIssues || ""); } const url = `${requestConfig.apiUrl}/chat?${params.toString()}`; const platform = detectPlatform(); return yield this.connectWeb(url, requestConfig.headers, onMessage, onError, onComplete); if (platform === "uniapp") { return this.connectUniapp(url, requestConfig.headers, onMessage, onError, onComplete); } else { return yield this.connectWeb(url, requestConfig.headers, onMessage, onError, onComplete); } } catch (error) { onError(new Error(`Failed to establish connection: ${error}`)); return { close: () => { } }; } }); } connectWeb(url, headers, onMessage, onError, onComplete) { return __async(this, null, function* () { const { EventSourcePolyfill } = yield import("./eventsource-_QmQq4NR.mjs").then((n) => n.e); const source = new EventSourcePolyfill(url, __spreadValues({ withCredentials: false }, headers && { headers })); source.addEventListener("message", (event) => { try { onMessage(event.data); } catch (error) { onError(new Error(`Message parsing error: ${error}`)); } }); source.addEventListener("error", (err) => { onError(new Error("Connection error occurred")); source.close(); }); source.addEventListener("end", () => { source.close(); onComplete == null ? void 0 : onComplete(); }); return source; }); } connectUniapp(url, headers, onMessage, onError, onComplete) { const requestTask = uni.request({ url, method: "GET", header: headers || {}, responseType: "text", enableChunked: true, success: (res) => { try { onMessage(res.data); onComplete == null ? void 0 : onComplete(); } catch (error) { onError(new Error(`Message parsing error: ${error}`)); } }, fail: (err) => { onError(new Error("Connection error occurred")); } }); return { close: () => requestTask.abort() }; } } let chunkBuffer = ""; function useAiChat(config) { const messages = ref([]); const input = ref(""); const loading = ref(false); const error = ref(null); const currentSessionId = ref(null); const currentConnection = ref(null); const currentHotIssueId = ref(null); const hotQuestions = ref([]); const systemQuestions = ref([]); const lawyerQuestions = ref([]); const hotQuestionsLoading = ref(false); const systemQuestionsLoading = ref(false); const lawyerQuestionsLoading = ref(false); const questions = ref(["hotQuestions", "systemQuestions", "lawyerQuestions"]); const hotIssuesCache = /* @__PURE__ */ new Map(); const CACHE_DURATION = 5 * 60 * 1e3; let fetchDebounceTimer = null; const service = new AIService(config); const getHotIssueConfig = (type) => { const configs = { hotQuestions: { category: "0", ref: hotQuestions, loading: hotQuestionsLoading }, systemQuestions: { category: "1", ref: systemQuestions, loading: systemQuestionsLoading }, lawyerQuestions: { category: "2", ref: lawyerQuestions, loading: lawyerQuestionsLoading } }; return configs[type] || configs.lawyerQuestions; }; const setLoadingState = (type, isLoading) => { const config2 = getHotIssueConfig(type); config2.loading.value = isLoading; }; const setHotIssuesData = (type, data) => { const config2 = getHotIssueConfig(type); config2.ref.value = data; }; const fetchHotIssues = (..._0) => __async(this, [..._0], function* (excludeIds = [], type = "lawyerQuestions", immediate = false) { if (immediate) { return yield fetchHotIssuesInternal(excludeIds, type); } if (fetchDebounceTimer) { clearTimeout(fetchDebounceTimer); } return new Promise((resolve) => { fetchDebounceTimer = setTimeout(() => __async(this, null, function* () { yield fetchHotIssuesInternal(excludeIds, type); resolve(); }), 300); }); }); const fetchHotIssuesInternal = (..._0) => __async(this, [..._0], function* (excludeIds = [], type = "lawyerQuestions") { const cacheKey = `${type}-${excludeIds.join(",")}`; const cached = hotIssuesCache.get(cacheKey); const now = Date.now(); if (cached && now - cached.timestamp < CACHE_DURATION) { setHotIssuesData(type, cached.data); return; } setLoadingState(type, true); try { const typeConfig = getHotIssueConfig(type); const url = `${config.apiUrl}/hot-issues?category=${typeConfig.category}`; const response = yield fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(excludeIds) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = yield response.json(); const hotIssues = data.data || []; setHotIssuesData(type, hotIssues); hotIssuesCache.set(cacheKey, { data: hotIssues, timestamp: now }); } catch (err) { console.error(`获取${type}热点问题失败:`, err); setHotIssuesData(type, []); error.value = err instanceof Error ? err : new Error("获取热点问题失败"); } finally { setLoadingState(type, false); } }); const refreshHotIssues = (type = "lawyerQuestions") => __async(this, null, function* () { console.log("刷新热点问题", type); const keysToDelete = Array.from(hotIssuesCache.keys()).filter((key) => key.startsWith(type)); keysToDelete.forEach((key) => hotIssuesCache.delete(key)); const getCurrentIds = () => { switch (type) { case "hotQuestions": return hotQuestions.value.map((item) => item.id); case "systemQuestions": return systemQuestions.value.map((item) => item.id); case "lawyerQuestions": return lawyerQuestions.value.map((item) => item.id); default: return []; } }; try { yield fetchHotIssues(getCurrentIds(), type); } catch (err) { console.error("刷新热点问题失败:", err); error.value = err instanceof Error ? err : new Error("刷新热点问题失败"); } }); const selectHotIssue = (hotIssue) => { if (!hotIssue || !hotIssue.question.trim()) { console.warn("无效的热点问题:", hotIssue); return; } if (loading.value) { console.warn("正在发送消息,请稍后再试"); return; } currentHotIssueId.value = hotIssue.id; input.value = hotIssue.question; sendMessage(); }; onMounted(() => __async(this, null, function* () { if (config.knowledgeCode === "lsyy") { const fetchPromises = questions.value.map( (item) => fetchHotIssues([], item, true).catch((err) => { console.error(`初始化${item}失败:`, err); }) ); yield Promise.allSettled(fetchPromises); } })); let contentBuffer = ""; let isProcessingJson = false; let currentMessageId = ""; let activeJsonContainers = /* @__PURE__ */ new Map(); let buttonBuffers = /* @__PURE__ */ new Map(); const sendMessage = () => __async(this, null, function* () { if (!input.value.trim() || loading.value) return; const userMsg = { role: "user", content: input.value }; const assistantMsg = { role: "assistant", content: "" }; messages.value.push([userMsg, assistantMsg]); input.value = ""; loading.value = true; chunkBuffer = ""; contentBuffer = ""; isProcessingJson = false; currentMessageId = ""; activeJsonContainers.clear(); const requestConfig = __spreadValues({}, config); if (currentHotIssueId.value) { requestConfig.requestParams = __spreadProps(__spreadValues({}, config.requestParams), { hotIssues: currentHotIssueId.value }); currentHotIssueId.value = null; } try { const connection = yield service.connect( messages.value, currentSessionId.value, requestConfig, (chunk) => { var _a, _b; chunkBuffer += chunk; let remainingBuffer = chunkBuffer; while (remainingBuffer.length > 0) { try { let braceCount = 0; let inString = false; let escapeNext = false; let jsonEnd = -1; for (let i = 0; i < remainingBuffer.length; i++) { const char = remainingBuffer[i]; if (escapeNext) { escapeNext = false; continue; } if (char === "\\") { escapeNext = true; continue; } if (char === '"') { inString = !inString; continue; } if (!inString) { if (char === "{") { braceCount++; } else if (char === "}") { braceCount--; if (braceCount === 0) { jsonEnd = i + 1; break; } } } } if (jsonEnd > 0) { const jsonStr = remainingBuffer.substring(0, jsonEnd); const tempData = JSON.parse(jsonStr); const data = tempData.choices[0].delta; let content = data.content; if (content === "[DONE]") { loading.value = false; currentConnection.value = null; chunkBuffer = ""; contentBuffer = ""; activeJsonContainers.clear(); return; } currentSessionId.value = (_a = tempData.id) != null ? _a : ""; if (content) { const messageId = (_b = tempData.id) != null ? _b : ""; if (currentMessageId !== messageId) { contentBuffer = ""; isProcessingJson = false; currentMessageId = messageId; activeJsonContainers.clear(); buttonBuffers.clear(); } const generateId = () => "json_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9); const createButtonContainer = (containerId) => { return `<div id="${containerId}" class="button-group" style="margin: 10px 0; display: flex; flex-wrap: wrap; gap: 8px; min-height: 40px; border: 1px dashed #ccc; padding: 8px; border-radius: 4px;"><span class="loading-text" style="color: #666; font-size: 12px;">正在加载按钮...</span></div>`; }; const createTableContainer = (containerId) => { return `<div id="${containerId}" style="margin: 10px 0;"><table class="json-table" style="border-collapse: collapse; width: 100%; border: 1px solid #ddd;"><thead><tr><th style="border: 1px solid #ddd; padding: 12px; background: #f5f5f5;">正在加载表格...</th></tr></thead><tbody></tbody></table></div>`; }; const fillButtonGroup = (containerId, data2) => { if (!Array.isArray(data2)) { console.error("按钮数据格式错误:", data2); return; } let html = ""; data2.forEach((item) => { html += `<button class="msgButton" onclick="selectPerson('${item}')">${item}</button>`; }); setTimeout(() => { const container = document.getElementById(containerId); if (container) { container.innerHTML = html; container.style.border = "none"; } }, 0); }; const fillTable = (containerId, data2) => { let html = ""; if (Array.isArray(data2)) { if (data2.length === 0) return; const headers = Object.keys(data2[0]); html = '<table class="json-table" style="border-collapse: collapse; width: 100%; border: 1px solid #ddd;">'; html += "<thead><tr>"; headers.forEach((header) => { html += `<th style="border: 1px solid #ddd; padding: 12px; background: #f5f5f5; font-weight: bold; text-align: left;">${header}</th>`; }); html += "</tr></thead>"; html += "<tbody>"; data2.forEach((row, rowIndex) => { const bgColor = rowIndex % 2 === 0 ? "#ffffff" : "#f9f9f9"; html += `<tr style="background-color: ${bgColor};">`; headers.forEach((header) => { html += `<td style="border: 1px solid #ddd; padding: 12px;">${row[header] || ""}</td>`; }); html += "</tr>"; }); html += "</tbody></table>"; } else if (data2.headers && data2.rows) { html = '<table class="json-table" style="border-collapse: collapse; width: 100%; border: 1px solid #ddd;">'; html += "<thead><tr>"; data2.headers.forEach((header) => { html += `<th style="border: 1px solid #ddd; padding: 12px; background: #f5f5f5; font-weight: bold; text-align: left;">${header}</th>`; }); html += "</tr></thead>"; html += "<tbody>"; data2.rows.forEach((row, rowIndex) => { const bgColor = rowIndex % 2 === 0 ? "#ffffff" : "#f9f9f9"; html += `<tr style="background-color: ${bgColor};">`; row.forEach((cell) => { html += `<td style="border: 1px solid #ddd; padding: 12px;">${cell}</td>`; }); html += "</tr>"; }); html += "</tbody></table>"; } setTimeout(() => { const container = document.getElementById(containerId); if (container) { container.innerHTML = html; } }, 0); }; const progressiveButtonDisplay = (containerId, newButtons) => { const container = document.getElementById(containerId); if (!container) return; const existingButtons = container.querySelectorAll("button").length; if (existingButtons === 0) { const loadingText = container.querySelector(".loading-text"); if (loadingText) { loadingText.remove(); } } const buttonsToAdd = newButtons.slice(existingButtons); buttonsToAdd.forEach((buttonText, index) => { if (buttonText && buttonText.trim()) { setTimeout(() => { const button = document.createElement("button"); button.className = "msgButton"; button.textContent = buttonText.trim(); button.onclick = () => { input.value = buttonText.trim(); sendMessage(); }; button.style.opacity = "0"; button.style.transform = "translateY(10px)"; button.style.transition = "all 0.3s ease"; container.appendChild(button); setTimeout(() => { button.style.opacity = "1"; button.style.transform = "translateY(0)"; }, 10); }, index * 150); } }); }; const parsePartialButtons = (partialJson) => { const buttons = []; try { const arrayMatch = partialJson.match(new RegExp('"data"\\s*:\\s*\\[(.*?)\\]', "s")); if (arrayMatch) { const arrayContent = arrayMatch[1]; const stringMatches = arrayContent.match(/"([^"]+)"/g); if (stringMatches) { stringMatches.forEach((match) => { const buttonText = match.replace(/"/g, "").trim(); if (buttonText) { buttons.push(buttonText); } }); } } } catch (error2) { console.log("解析部分按钮数据失败:", error2); } return buttons; }; contentBuffer += content; const hasBeginTag = contentBuffer.includes("begin"); if (!hasBeginTag) { const currentMessage = messages.value[messages.value.length - 1][1]; currentMessage.content += content; contentBuffer = ""; } else { const beginMatches = [...contentBuffer.matchAll(/begin/g)]; const endMatches = [...contentBuffer.matchAll(/end/g)]; if (beginMatches.length > endMatches.length) { const newBeginCount = beginMatches.length - activeJsonContainers.size; for (let i = 0; i < newBeginCount; i++) { const containerId = generateId(); const partialRegex = /begin([\s\S]*?)$/; const partialMatch = contentBuffer.match(partialRegex); if (partialMatch) { const partialJson = partialMatch[1].trim(); let containerHtml = ""; if (partialJson.includes('"type"') && partialJson.includes('"button"')) { containerHtml = createButtonContainer(containerId); activeJsonContainers.set(containerId, "button"); buttonBuffers.set(containerId, []); const currentMessage = messages.value[messages.value.length - 1][1]; currentMessage.content += containerHtml; setTimeout(() => { const partialButtons = parsePartialButtons(partialJson); if (partialButtons.length > 0) { buttonBuffers.set(containerId, partialButtons); progressiveButtonDisplay(containerId, partialButtons); } }, 100); } else if (partialJson.includes('"type"') && partialJson.includes('"table"')) { containerHtml = createTableContainer(containerId); activeJsonContainers.set(containerId, "table"); const currentMessage = messages.value[messages.value.length - 1][1]; currentMessage.content += containerHtml; } } } } activeJsonContainers.forEach((type, containerId) => { if (type === "button") { const partialRegex = /begin([\s\S]*?)(?:end|$)/; const partialMatch = contentBuffer.match(partialRegex); if (partialMatch) { const partialJson = partialMatch[1].trim(); const currentButtons = parsePartialButtons(partialJson); const existingButtons = buttonBuffers.get(containerId) || []; if (currentButtons.length > existingButtons.length) { buttonBuffers.set(containerId, currentButtons); progressiveButtonDisplay(containerId, currentButtons); } } } }); const completeRegex = /begin([\s\S]*?)end/g; const completeMatches = [...contentBuffer.matchAll(completeRegex)]; completeMatches.forEach((match, index) => { const jsonContent = match[1].trim(); try { const jsonData = JSON.parse(jsonContent); if (jsonData.type && jsonData.data) { const containerIds = Array.from(activeJsonContainers.keys()); const containerId = containerIds[index]; if (containerId) { switch (jsonData.type) { case "button": const finalButtons = Array.isArray(jsonData.data) ? jsonData.data : []; const currentButtons = buttonBuffers.get(containerId) || []; if (finalButtons.length > currentButtons.length) { buttonBuffers.set(containerId, finalButtons); progressiveButtonDisplay(containerId, finalButtons); } setTimeout(() => { const container = document.getElementById(containerId); if (container) { container.style.border = "none"; } }, finalButtons.length * 150 + 200); break; case "table": fillTable(containerId, jsonData.data); break; default: console.warn("未知的JSON类型:", jsonData.type); } activeJsonContainers.delete(containerId); buttonBuffers.delete(containerId); } } } catch (error2) { console.log("JSON解析失败:", error2); } }); if (completeMatches.length > 0) { let cleanContent = contentBuffer; completeMatches.forEach((match) => { cleanContent = cleanContent.replace(match[0], ""); }); if (cleanContent.trim() && !cleanContent.includes("begin")) { const currentMessage = messages.value[messages.value.length - 1][1]; currentMessage.content += cleanContent; } contentBuffer = cleanContent.includes("begin") ? cleanContent : ""; } } } } remainingBuffer = remainingBuffer.substring(jsonEnd); chunkBuffer = remainingBuffer; } catch (e) { break; } } }, (err) => { error.value = err; loading.value = false; currentConnection.value = null; chunkBuffer = ""; contentBuffer = ""; activeJsonContainers.clear(); } ); currentConnection.value = connection; } catch (err) { error.value = err instanceof Error ? err : new Error("连接失败"); loading.value = false; currentConnection.value = null; } }); const stopGeneration = () => { if (currentConnection.value && typeof currentConnection.value.close === "function") { try { currentConnection.value.close(); } catch (error2) { console.warn("关闭连接时出错:", error2); } currentConnection.value = null; loading.value = false; messages.value[messages.value.length - 1][1].stopMessage = true; chunkBuffer = ""; contentBuffer = ""; activeJsonContainers.clear(); } }; return { messages, input, loading, error, sendMessage, stopGeneration, hotQuestions, systemQuestions, lawyerQuestions, hotQuestionsLoading, systemQuestionsLoading, lawyerQuestionsLoading, refreshHotIssues, selectHotIssue }; } function parseStructuredData(content) { return content; } function parseMarkdown(content) { if (!content) return ""; let processedContent = parseStructuredData(content); if (processedContent !== content) { return DOMPurify.sanitize(processedContent, { ALLOWED_TAGS: [ "div", "span", "p", "br", "strong", "em", "code", "pre", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "a", "button", "table", "thead", "tbody", "tr", "th", "td" ], ALLOWED_ATTR: ["href", "class", "onclick", "data-*", "id", "colspan", "rowspan"] }); } const hasButton = content.includes("<button"); const hasTable = content.includes("<table") || content.includes("<tr") || content.includes("<td") || content.includes("<th"); if (hasButton || hasTable) { const result2 = content.replace(/\n/g, "\n"); return DOMPurify.sanitize(result2, { ALLOWED_TAGS: [ "p", "br", "strong", "em", "code", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "a", "button", "div", "span", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption" ], ALLOWED_ATTR: [ "href", "class", "onclick", "data-*", "id", "target", "title", "alt", "src", "width", "height", "colspan", "rowspan", "scope", "style" ] }); } const needsMarkdownParsing = content.includes("**") || content.includes("*") || content.includes("`") || content.includes("#") || content.includes("[") || content.includes("\n- ") || content.includes("\n1. ") || content.includes("|"); let result = content; if (needsMarkdownParsing) { result = marked.parse(content); } else { result = content.replace(/\n/g, "<br>"); } return DOMPurify.sanitize(result, { ALLOWED_TAGS: [ "p", "br", "strong", "em", "code", "pre", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "a", "button", "div", "span", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption" ], ALLOWED_ATTR: [ "href", "class", "onclick", "data-*", "id", "target", "title", "alt", "src", "width", "height", "colspan", "rowspan", "scope", "style" ] }); } const aiIcon = "data:image/png;base64,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"; const _hoisted_1 = { class: "ai-chat-container" }; const _hoisted_2 = { key: 0, class: "chat-header" }; const _hoisted_3 = { class: "ai-avatar" }; const _hoisted_4 = ["src"]; const _hoisted_5 = { class: "welcome-message" }; const _hoisted_6 = { key: 1, class: "hot-questions-container" }; const _hoisted_7 = { class: "hot-questions" }; const _hoisted_8 = { class: "section-title" }; const _hoisted_9 = ["disabled"]; const _hoisted_10 = { class: "questions-list" }; const _hoisted_11 = ["onClick"]; const _hoisted_12 = { key: 0, class: "empty-questions" }; const _hoisted_13 = { class: "hot-questions" }; const _hoisted_14 = { class: "section-title" }; const _hoisted_15 = ["disabled"]; const _hoisted_16 = { class: "questions-list" }; const _hoisted_17 = ["onClick"]; const _hoisted_18 = { key: 0, class: "empty-questions" }; const _hoisted_19 = { class: "message-content user" }; const _hoisted_20 = ["innerHTML"]; const _hoisted_21 = { class: "message-content assistant" }; const _hoisted_22 = { class: "message-label" }; const _hoisted_23 = { key: 0, class: "assistant-loading" }; const _hoisted_24 = { key: 1, class: "assistant-stop" }; const _hoisted_25 = ["innerHTML"]; const _hoisted_26 = { class: "chat-input" }; const _hoisted_27 = { class: "input-container" }; const _hoisted_28 = ["disabled"]; const _hoisted_29 = ["disabled"]; const inputPlaceholder = "请输入想要咨询的问题"; const _sfc_main = /* @__PURE__ */ defineComponent({ __name: "AiChat", props: { config: {} }, setup(__props) { const props = __props; const { messages, input, loading, sendMessage, stopGeneration, // stopMessage, hotQuestions, systemQuestions, lawyerQuestions, hotQuestionsLoading, systemQuestionsLoading, lawyerQuestionsLoading, refreshHotIssues, selectHotIssue } = useAiChat(props.config); const tagList = ref(["查看被监管人", "系统问题"]); const scrollToBottom = () => { setTimeout(() => { window.scrollTo({ top: document.documentElement.scrollHeight, behavior: "smooth" }); }, 100); }; watch( () => messages.value.length, () => { scrollToBottom(); } ); watch( () => { if (messages.value.length > 0) { return messages.value[messages.value.length - 1][1].content; } return ""; }, () => { scrollToBottom(); } ); watch(loading, (newLoading) => { if (newLoading) { scrollToBottom(); } }); const parseMessageContent = (content) => { if (!content) return ""; const messageIndex = messages.value.findIndex( (msgPair) => msgPair[1].content === content ); const currentIndex = messageIndex !== -1 ? messageIndex : messages.value.length - 1; let processedContent = content.replace( /onclick="selectPerson\('([^']+)',\s*(true|false)\)"/g, `onclick="selectPerson('$1', $2, ${currentIndex})"` ); processedContent = processedContent.replace( /onclick="selectPerson\('([^']+)'\)"/g, `onclick="selectPerson('$1', false, ${currentIndex})"` ); const phoneRegex = new RegExp("(?<!\\w)((?:1[3-9]\\d{9})|(?:400-?\\d{3}-?\\d{4})|(?:0\\d{2,3}-?\\d{7,8})|(?:\\d{3,4}-\\d{7,8})|(?:\\(\\d{3,4}\\)\\d{7,8})|(?:\\d{6}))(?!\\w)", "g"); processedContent = processedContent.replace(phoneRegex, (match) => { const cleanPhone = match.replace(/[-()]/g, ""); return `<a href="tel:${cleanPhone}" style="color: #007bff; text-decoration: underline; cursor: pointer;">${match}</a>`; }); return parseMarkdown(processedContent); }; const selectPerson = (personId, isJoin = false, messageIndex) => { const targetIndex = messageIndex !== void 0 ? messageIndex : messages.value.length - 1; if (props.config.knowledgeCode === "lsyy") { input.value = `请提供关于 ${personId} 的详细信息`; } else { if (targetIndex >= 0 && targetIndex < messages.value.length) { const targetMessage = messages.value[targetIndex][0].content; input.value = `${targetMessage}${targetMessage.includes(":") ? `-${personId}` : `:${personId}`}`; } else { const lastMessage = messages.value[messages.value.length - 1][0].content; input.value = `${lastMessage}${lastMessage.includes(":") ? `-${personId}` : `:${personId}`}`; } return; } sendMessage(); }; if (typeof window !== "undefined") { window.selectPerson = selectPerson; } return (_ctx, _cache) => { return openBlock(), createElementBlock("div", _hoisted_1, [ _ctx.config.knowledgeCode === "lsyy" ? (openBlock(), createElementBlock("div", _hoisted_2, [ createElementVNode("div", _hoisted_3, [ createElementVNode("img", { width: "100%", mode: "widthFix", src: unref(aiIcon), alt: "AI头像" }, null, 8, _hoisted_4) ]), createElementVNode("div", _hoisted_5, " 你好," + toDisplayString(_ctx.config.lawyerName) + "律师,我是智能客服,很高兴为您服务。请选择您想询问的问题,或输入想要咨询的内容: ", 1) ])) : createCommentVNode("", true), _ctx.config.knowledgeCode === "lsyy" ? (openBlock(), createElementBlock("div", _hoisted_6, [ createElementVNode("div", _hoisted_7, [ createElementVNode("div", _hoisted_8, [ _cache[6] || (_cache[6] = createElementVNode("span", null, "热门问题", -1)), createElementVNode("button", { class: "refresh-btn", onClick: _cache[0] || (_cache[0] = ($event) => unref(refreshHotIssues)("hotQuestions")), disabled: unref(hotQuestionsLoading) }, toDisplayString(unref(hotQuestionsLoading) ? "加载中..." : "换一换"), 9, _hoisted_9) ]), createElementVNode("div", _hoisted_10, [ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(hotQuestions), (question, index) => { return openBlock(), createElementBlock("div", { class: "question-item", key: question.id, onClick: ($event) => unref(selectHotIssue)(question) }, toDisplayString(index + 1) + ". " + toDisplayString(question.question), 9, _hoisted_11); }), 128)), unref(hotQuestions).length === 0 && !unref(hotQuestionsLoading) ? (openBlock(), createElementBlock("div", _hoisted_12, " 暂无热门问题 ")) : createCommentVNode("", true) ]) ]), createElementVNode("div", _hoisted_13, [ createElementVNode("div", _hoisted_14, [ _cache[7] || (_cache[7] = createElementVNode("span", null, "系统问题", -1)), createElementVNode("button", { class: "refresh-btn", onClick: _cache[1] || (_cache[1] = ($event) => unref(refreshHotIssues)("systemQuestions")), disabled: unref(systemQuestionsLoading) }, toDisplayString(unref(systemQuestionsLoading) ? "加载中..." : "换一换"), 9, _hoisted_15) ]), createElementVNode("div", _hoisted_16, [ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(systemQuestions), (question, index) => { return openBlock(), createElementBlock("div", { class: "question-item", key: question.id, onClick: ($event) => unref(selectHotIssue)(question) }, toDisplayString(index + 1) + ". " + toDisplayString(question.question), 9, _hoisted_17); }), 128)), unref(systemQuestions).length === 0 && !unref(systemQuestionsLoading) ? (openBlock(), createElementBlock("div", _hoisted_18, " 暂无热门问题 ")) : createCommentVNode("", true) ]) ]) ])) : createCommentVNode("", true), unref(messages).length > 0 ? (openBlock(), createElementBlock("div", { key: 2, class: normalizeClass(["chat-messages", { "tag": tagList.value.length > 0 }]) }, [ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(messages), (message, index) => { return openBlock(), createElementBlock("div", { class: "message-item", key: index }, [ createElementVNode("div", _hoisted_19, [ _cache[8] || (_cache[8] = createElementVNode("div", { class: "message-label" }, "问:", -1)), createElementVNode("div", { class: "user-message-text", innerHTML: parseMessageContent(message[0].content) }, null, 8, _hoisted_20) ]), createElementVNode("div", _hoisted_21, [ createElementVNode("div", _hoisted_22, [ _cache[9] || (_cache[9] = createTextVNode(" 答: ", -1)), unref(loading) && index === unref(messages).length - 1 ? (openBlock(), createElementBlock("span", _hoisted_23)) : createCommentVNode("", true), message[1].stopMessage ? (openBlock(), createElementBlock("span", _hoisted_24, "此问题用户停止咨询")) : createCommentVNode("", true) ]), createElementVNode("div", { class: "message-text", innerHTML: parseMessageContent(message[1].content) }, null, 8, _hoisted_25) ]) ]); }), 128)) ], 2)) : createCommentVNode("", true), createElementVNode("div", _hoisted_26, [ createElementVNode("div", _hoisted_27, [ withDirectives(createElementVNode("input", { "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => isRef(input) ? input.value = $event : null), onKeyup: _cache[3] || (_cache[3] = withKeys( //@ts-ignore (...args) => unref(sendMessage) && unref(sendMessage)(...args), ["enter"] )), placeholder: inputPlaceholder, disabled: unref(loading), class: "message-input" }, null, 40, _hoisted_28), [ [vModelText, unref(input)] ]), !unref(loading) ? (openBlock(), createElementBlock("button", { key: 0, onClick: _cache[4] || (_cache[4] = //@ts-ignore (...args) => unref(sendMessage) && unref(sendMessage)(...args)), disabled: !unref(input).trim(), class: "send-button" }, _cache[10] || (_cache[10] = [ createElementVNode("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none" }, [ createElementVNode("path", { d: "M2 21L23 12L2 3V10L17 12L2 14V21Z", fill: "currentColor" }) ], -1) ]), 8, _hoisted_29)) : createCommentVNode("", true), unref(loading) ? (openBlock(), createElementBlock("button", { key: 1, onClick: _cache[5] || (_cache[5] = //@ts-ignore (...args) => unref(stopGeneration) && unref(stopGeneration)(...args)), class: "stop-butto