|
@@ -30,7 +30,7 @@
|
|
|
</transition>
|
|
|
</div>
|
|
|
<div class="user-info" v-if="!isMobile" @click="toggleDropdown">
|
|
|
- <div class="user-name">商户名称:{{ userInfo.merchant?.merchant_name || '未登录用户' }}</div>
|
|
|
+ <div class="user-name">商户名称:{{ userInfo.merchant_name || '未登录用户' }}</div>
|
|
|
<div class="user-name">当前用户:{{ userInfo.username || '未登录用户' }}</div>
|
|
|
<!-- <div class="user-role">{{ userInfo.role || '未设置角色' }}</div> -->
|
|
|
</div>
|
|
@@ -186,6 +186,21 @@
|
|
|
</div>
|
|
|
<!-- 修改消息列表部分 -->
|
|
|
<div v-else class="message-list" ref="messageList">
|
|
|
+ <!-- 添加流式思考步骤显示 -->
|
|
|
+ <div v-if="currentThinkingSteps && currentThinkingSteps.length > 0" class="thinking-steps-stream">
|
|
|
+ <div v-for="(step, index) in currentThinkingSteps"
|
|
|
+ :key="index"
|
|
|
+ class="thinking-step-stream"
|
|
|
+ :class="[step.type, {'fade-in': true}]">
|
|
|
+ {{ step.content }}
|
|
|
+ <div v-if="index === currentThinkingSteps.length - 1" class="typing-indicator">
|
|
|
+ <span></span>
|
|
|
+ <span></span>
|
|
|
+ <span></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div
|
|
|
v-for="message in messages"
|
|
|
:key="message.id"
|
|
@@ -196,6 +211,15 @@
|
|
|
>
|
|
|
<div class="message-bubble">
|
|
|
<div class="message-content">
|
|
|
+ <!-- 添加thinking steps显示 -->
|
|
|
+ <div v-if="message.thinkingSteps && message.thinkingSteps.length > 0" class="thinking-steps">
|
|
|
+ <div v-for="(step, index) in message.thinkingSteps"
|
|
|
+ :key="index"
|
|
|
+ class="thinking-step"
|
|
|
+ :class="step.type">
|
|
|
+ {{ step.content }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<template v-if="message.role === 'user' && message.files">
|
|
|
<div
|
|
|
v-for="file in message.files"
|
|
@@ -320,6 +344,27 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div class="search-toggle-item">
|
|
|
+ <div class="toggle-content">
|
|
|
+ <DatabaseOutlined />
|
|
|
+ <span class="toggle-text" >推理模式</span><!-- v-show="!isCompact" -->
|
|
|
+ <div class="toggle-switch">
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ id="inferenceModelId"
|
|
|
+ v-model="inferenceModel"
|
|
|
+ @change="handleInferenceModel"
|
|
|
+ class="toggle-input"
|
|
|
+ />
|
|
|
+ <label
|
|
|
+ for="inferenceModelId"
|
|
|
+ class="toggle-label"
|
|
|
+ >
|
|
|
+ <span class="toggle-slider"></span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
|
|
@@ -859,6 +904,9 @@ const knowledgeResults = ref([]);
|
|
|
const knowledgeTotal = ref(0);
|
|
|
const isKnowledgeLoading = ref(false);
|
|
|
|
|
|
+// 推理模式
|
|
|
+const inferenceModel = ref(true);
|
|
|
+
|
|
|
// 检测设备类型
|
|
|
const checkMobile = () => {
|
|
|
isMobile.value = window.innerWidth <= 768;
|
|
@@ -946,7 +994,7 @@ const fetchDocumentSummary = async () => {
|
|
|
isLoadingSummary.value = true;
|
|
|
try {
|
|
|
const response = await axios.get(
|
|
|
- `${import.meta.env.VITE_BASE_AI_API}/api/document/summary`,
|
|
|
+ `${import.meta.env.VITE_BASE_AI_API}/api/document/summary?limit=2`,
|
|
|
{
|
|
|
headers: {
|
|
|
'Authorization': `JWT ${localStorage.getItem('token')}`
|
|
@@ -955,8 +1003,8 @@ const fetchDocumentSummary = async () => {
|
|
|
);
|
|
|
console.log("API Response:", response.data);
|
|
|
|
|
|
- if (response.data.code === 200) {
|
|
|
- documentSummary.value = response.data.data.items;
|
|
|
+ if (response.data.status === 200) {
|
|
|
+ documentSummary.value = response.data.data;
|
|
|
console.log("Document Summary:", documentSummary.value);
|
|
|
startRotation();
|
|
|
}
|
|
@@ -975,40 +1023,40 @@ const startRotation = () => {
|
|
|
|
|
|
const updateItems = () => {
|
|
|
if (!documentSummary.value?.length) {
|
|
|
- console.log("No document summary data available");
|
|
|
+ console.log("没有可用的文档摘要数据");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 处理第一项数据(Hot Topics)
|
|
|
+ // 处理第一篇文档的问题(如:热门话题)
|
|
|
try {
|
|
|
- const hotTopicsData = documentSummary.value[0];
|
|
|
- if (hotTopicsData?.questions) {
|
|
|
- const parsedQaPairs =
|
|
|
- /* JSON.parse( */ hotTopicsData.questions; /* .replace(/'/g, '"')); */
|
|
|
- const formattedQaPairs = parsedQaPairs.map((question) => ({
|
|
|
+ const firstDocData = documentSummary.value[0];
|
|
|
+ if (firstDocData?.questions) {
|
|
|
+ // 直接使用questions数组中的问题
|
|
|
+ const formattedQaPairs = firstDocData.questions.map((question) => ({
|
|
|
question,
|
|
|
}));
|
|
|
- // 随机选择3个问题
|
|
|
+ // 随机抽取3个问题显示
|
|
|
const shuffled = [...formattedQaPairs].sort(() => 0.5 - Math.random());
|
|
|
currentQaPairs.value = shuffled.slice(0, 3);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error("解析 Hot Topics 失败:", error);
|
|
|
+ console.error("处理第一篇文档问题失败:", error);
|
|
|
}
|
|
|
|
|
|
- // 处理第二项数据(Design Guide)
|
|
|
+ // 处理第二篇文档的问题(如:设计指南)
|
|
|
try {
|
|
|
- const designGuideData = documentSummary.value[1];
|
|
|
- if (designGuideData?.questions) {
|
|
|
- const parsedGuides =
|
|
|
- /* JSON.parse( */ designGuideData.questions; /* .replace(/'/g, '"')); */
|
|
|
- const formattedGuides = parsedGuides.map((question) => ({ question }));
|
|
|
- // 随机选择3个指南
|
|
|
+ const secondDocData = documentSummary.value[1];
|
|
|
+ if (secondDocData?.questions) {
|
|
|
+ // 直接使用questions数组中的问题
|
|
|
+ const formattedGuides = secondDocData.questions.map((question) => ({
|
|
|
+ question,
|
|
|
+ }));
|
|
|
+ // 随机抽取3个问题显示
|
|
|
const shuffled = [...formattedGuides].sort(() => 0.5 - Math.random());
|
|
|
currentDesignGuides.value = shuffled.slice(0, 3);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error("解析 Design Guide 失败:", error);
|
|
|
+ console.error("处理第二篇文档问题失败:", error);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -1250,13 +1298,15 @@ const sendMessage = async () => {
|
|
|
isTyping.value
|
|
|
)
|
|
|
return;
|
|
|
- isTyping.value = true;
|
|
|
+ isTyping.value = true;
|
|
|
+
|
|
|
// 保存当前输入内容
|
|
|
const currentMessage = inputContent.value.trim();
|
|
|
|
|
|
// 立即清空输入框
|
|
|
inputContent.value = '';
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
// 根据开启的搜索类型执行相应的搜索
|
|
|
if (enableWebSearch.value) {
|
|
|
currentSearchQuery.value = currentMessage;
|
|
@@ -1292,13 +1342,13 @@ const sendMessage = async () => {
|
|
|
documentUrls.push(file.url);
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 添加用户消息
|
|
|
const userMessage = {
|
|
|
id: Date.now(),
|
|
|
role: "user",
|
|
|
- content: messageContent,
|
|
|
- displayContent: messageContent,
|
|
|
+ content: currentMessage,
|
|
|
+ displayContent: currentMessage,
|
|
|
files: attachedFiles.value.map((file) => ({
|
|
|
name: file.name,
|
|
|
size: file.sizeFormatted,
|
|
@@ -1307,29 +1357,39 @@ const sendMessage = async () => {
|
|
|
})),
|
|
|
};
|
|
|
messages.value.push(userMessage);
|
|
|
-/* scrollToBottom(); */
|
|
|
|
|
|
// 添加临时AI消息
|
|
|
const tempAiMessage = {
|
|
|
id: Date.now() + 1,
|
|
|
role: "assistant",
|
|
|
- content: "Thinking...",
|
|
|
- displayContent: "Thinking...",
|
|
|
+ content: "思考中...",
|
|
|
+ displayContent: "思考中...",
|
|
|
isLoading: true,
|
|
|
};
|
|
|
messages.value.push(tempAiMessage);
|
|
|
- /* scrollToBottom(); */
|
|
|
|
|
|
try {
|
|
|
// 创建新的 AbortController
|
|
|
abortController.value = new AbortController();
|
|
|
-
|
|
|
- // 根据开关状态选择不同的 API 端点
|
|
|
+ if(!inferenceModel.value){
|
|
|
+ // 构建请求参数
|
|
|
+ /* const requestData = {
|
|
|
+ message: currentMessage,
|
|
|
+ chat_config_id: "2",
|
|
|
+ user_id: "13365429324",
|
|
|
+ session_id: session_id.value || "",
|
|
|
+ source: "pc",
|
|
|
+ image_urls: attachedFiles.value.filter(f => isImageFile(f)).map(f => f.url),
|
|
|
+ video_urls: attachedFiles.value.filter(f => isVideoFile(f)).map(f => f.url),
|
|
|
+ documents: attachedFiles.value.filter(f => !isImageFile(f) && !isVideoFile(f)).map(f => f.url),
|
|
|
+ merchant_id: JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id||1,
|
|
|
+ model_type: selectedModelInfo.value.type,
|
|
|
+ model_name: selectedModelInfo.value.name
|
|
|
+ }; */
|
|
|
const apiEndpoint = enableWebSearch.value
|
|
|
? `${import.meta.env.VITE_BASE_AI_API}/api/chat/web-search-llm/`
|
|
|
: `${import.meta.env.VITE_BASE_AI_API}/api/chat/online/multimodal`;
|
|
|
-
|
|
|
- const response = await axios.post(apiEndpoint, {
|
|
|
+ const response = await axios.post(apiEndpoint, {
|
|
|
message: currentMessage,
|
|
|
chat_config_id: "2",
|
|
|
user_id: "13365429324",
|
|
@@ -1383,36 +1443,238 @@ const sendMessage = async () => {
|
|
|
|
|
|
// 显示打字效果
|
|
|
await typeMessage(aiMessage);
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- if (error.name === 'CanceledError') {
|
|
|
- console.log('请求已被取消');
|
|
|
- messages.value = messages.value.filter(
|
|
|
- (msg) => msg.id !== tempAiMessage.id
|
|
|
- );
|
|
|
- messages.value.push({
|
|
|
- id: Date.now() + 2,
|
|
|
- role: "assistant",
|
|
|
- content: "生成已取消",
|
|
|
- displayContent: "生成已取消",
|
|
|
- });
|
|
|
- } else {
|
|
|
- console.error("发送消息失败:", error);
|
|
|
- messages.value = messages.value.filter(
|
|
|
- (msg) => msg.id !== tempAiMessage.id
|
|
|
- );
|
|
|
- messages.value.push({
|
|
|
- id: Date.now() + 2,
|
|
|
- role: "assistant",
|
|
|
- content: "Sorry, there was an error processing your request.",
|
|
|
- displayContent: "Sorry, there was an error processing your request.",
|
|
|
+
|
|
|
+ }else{
|
|
|
+ // 构建请求参数
|
|
|
+ const requestData = {
|
|
|
+ message: currentMessage,
|
|
|
+ user_id: "13365429324",
|
|
|
+ session_id: session_id.value || "",
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ // 发送初始请求获取SSE URL
|
|
|
+ const response = await fetch(`${import.meta.env.VITE_BASE_AI_API}/api/agents/chat/multimodal/`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `JWT ${token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ },
|
|
|
+ body: JSON.stringify(requestData)
|
|
|
});
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('请求失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取SSE URL
|
|
|
+ const responseData = await response.json();
|
|
|
+ console.log(responseData)
|
|
|
+ const sseUrl = responseData.data.sse_url;
|
|
|
+
|
|
|
+ if (!sseUrl) {
|
|
|
+ throw new Error('未获取到SSE URL');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 建立SSE连接
|
|
|
+ const sseUrlWithToken = `${import.meta.env.VITE_BASE_AI_API}${sseUrl}${sseUrl.includes('?') ? '&' : '?'}token=JWT ${encodeURIComponent(token)}`;
|
|
|
+ console.log('SSE连接URL:', sseUrlWithToken);
|
|
|
+
|
|
|
+ const eventSource = new EventSource(sseUrlWithToken, {
|
|
|
+ withCredentials: true // 启用跨域认证
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理SSE消息
|
|
|
+ eventSource.onmessage = (event) => {
|
|
|
+ try {
|
|
|
+ console.log('收到SSE消息:', event.data);
|
|
|
+ const data = JSON.parse(event.data);
|
|
|
+
|
|
|
+ switch(data.type) {
|
|
|
+ case 'connected':
|
|
|
+ // 更新session_id
|
|
|
+ if (data.session_id) {
|
|
|
+ session_id.value = data.session_id;
|
|
|
+ console.log('已连接,会话ID:', data.session_id);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'thinking_step':
|
|
|
+ // 更新临时消息的内容
|
|
|
+ const lastMessage = messages.value[messages.value.length - 1];
|
|
|
+ if (lastMessage && lastMessage.id === tempAiMessage.id) {
|
|
|
+ // 初始化thinking steps数组(如果不存在)
|
|
|
+ if (!lastMessage.thinkingSteps) {
|
|
|
+ lastMessage.thinkingSteps = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ let stepContent = '';
|
|
|
+ let stepType = data.data.step_type;
|
|
|
+
|
|
|
+ // 根据step_type处理不同类型的思考步骤
|
|
|
+ switch(data.data.step_type) {
|
|
|
+ case 'start':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'request_analysis':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'knowledge_search':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'search_error':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'intent_analysis':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'intent_result':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'model_call':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'response_generation':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'text_analysis':
|
|
|
+ stepContent = ` ${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'planning':
|
|
|
+ stepContent = ` ${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'execution':
|
|
|
+ stepContent = `${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'result':
|
|
|
+ stepContent = ` ${data.data.content}`;
|
|
|
+ break;
|
|
|
+ case 'conclusion':
|
|
|
+ if (data.data.metadata && data.data.metadata.final_content) {
|
|
|
+ // 添加最后一个thinking step
|
|
|
+ lastMessage.thinkingSteps.push({
|
|
|
+ type: 'conclusion',
|
|
|
+ content: `✨ 完成: ${data.data.content}`
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建最终的AI消息
|
|
|
+ const aiMessage = reactive({
|
|
|
+ id: Date.now() + 2,
|
|
|
+ role: "assistant",
|
|
|
+ content: data.data.metadata.final_content,
|
|
|
+ displayContent: "",
|
|
|
+ audioData: null,
|
|
|
+ thinkingSteps: lastMessage.thinkingSteps // 保留thinking steps
|
|
|
+ });
|
|
|
+
|
|
|
+ // 替换临时消息
|
|
|
+ const msgIndex = messages.value.findIndex(msg => msg.id === tempAiMessage.id);
|
|
|
+ if (msgIndex !== -1) {
|
|
|
+ messages.value.splice(msgIndex, 1, aiMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示打字效果
|
|
|
+ typeMessage(aiMessage);
|
|
|
+ return;
|
|
|
+ } else if (data.data.content) {
|
|
|
+ // 根据内容是否包含某些关键词来决定显示的emoji
|
|
|
+ let emoji = '💭'; // 默认emoji
|
|
|
+ if (data.data.content.includes('分析')) {
|
|
|
+ emoji = '🔍';
|
|
|
+ } else if (data.data.content.includes('规划')) {
|
|
|
+ emoji = '📋';
|
|
|
+ } else if (data.data.content.includes('执行')) {
|
|
|
+ emoji = '⚙️';
|
|
|
+ } else if (data.data.content.includes('完成')) {
|
|
|
+ emoji = '✨';
|
|
|
+ }
|
|
|
+ stepContent = `${emoji} ${data.data.content}`;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加新的thinking step
|
|
|
+ if (stepContent) {
|
|
|
+ lastMessage.thinkingSteps.push({
|
|
|
+ type: stepType,
|
|
|
+ content: stepContent
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新显示内容
|
|
|
+ lastMessage.content = "AI正在思考中...";
|
|
|
+ lastMessage.displayContent = "AI正在思考中...";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* case 'idle_timeout':
|
|
|
+ console.log('连接空闲超时,关闭连接');
|
|
|
+ eventSource.close();
|
|
|
+ // 移除临时消息
|
|
|
+ messages.value = messages.value.filter(msg => msg.id !== tempAiMessage.id);
|
|
|
+ // 添加提示消息
|
|
|
+ messages.value.push({
|
|
|
+ id: Date.now() + 2,
|
|
|
+ role: "assistant",
|
|
|
+ content: "连接超时,请重试",
|
|
|
+ displayContent: "连接超时,请重试",
|
|
|
+ });
|
|
|
+ break; */
|
|
|
+
|
|
|
+ case 'session_ended':
|
|
|
+ console.log('会话结束');
|
|
|
+ eventSource.close();
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'heartbeat':
|
|
|
+ // 忽略心跳消息
|
|
|
+ console.log('收到心跳消息');
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ console.log('未处理的消息类型:', data.type);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('处理SSE消息失败:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理SSE错误
|
|
|
+ eventSource.onerror = (error) => {
|
|
|
+ console.error('SSE连接错误:', error);
|
|
|
+ eventSource.close();
|
|
|
+
|
|
|
+ // 移除临时消息
|
|
|
+ messages.value = messages.value.filter(msg => msg.id !== tempAiMessage.id);
|
|
|
+
|
|
|
+ // 添加错误消息
|
|
|
+ messages.value.push({
|
|
|
+ id: Date.now() + 2,
|
|
|
+ role: "assistant",
|
|
|
+ content: "连接出错,请重试",
|
|
|
+ displayContent: "连接出错,请重试",
|
|
|
+ });
|
|
|
+ };
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error("发送消息失败:", error);
|
|
|
+ // 移除临时消息
|
|
|
+ messages.value = messages.value.filter(msg => msg.id !== tempAiMessage.id);
|
|
|
+
|
|
|
+ // 添加错误消息
|
|
|
+ messages.value.push({
|
|
|
+ id: Date.now() + 2,
|
|
|
+ role: "assistant",
|
|
|
+ content: "发送消息失败,请重试",
|
|
|
+ displayContent: "发送消息失败,请重试",
|
|
|
+ });
|
|
|
} finally {
|
|
|
isTyping.value = false;
|
|
|
abortController.value = null;
|
|
|
- await nextTick();
|
|
|
- /* scrollToBottom(); */
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -1453,14 +1715,43 @@ const typeMessage = async (message) => {
|
|
|
}
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
- const delay = 30; // 打字延迟时间
|
|
|
+ const minDelay = 20; // 最小打字延迟
|
|
|
+ const maxDelay = 50; // 最大打字延迟
|
|
|
let i = 0;
|
|
|
message.displayContent = ""; // 确保开始时是空的
|
|
|
|
|
|
const typeChar = () => {
|
|
|
if (i < message.content.length && isTyping.value) {
|
|
|
- message.displayContent = message.content.substring(0, i + 1);
|
|
|
- i++;
|
|
|
+ // 处理特殊字符和标点符号
|
|
|
+ let chunk = "";
|
|
|
+ const char = message.content[i];
|
|
|
+
|
|
|
+ // 如果是中文字符,整个字符一起显示
|
|
|
+ if (/[\u4e00-\u9fa5]/.test(char)) {
|
|
|
+ chunk = char;
|
|
|
+ }
|
|
|
+ // 如果是标点符号,与前面的字符一起显示
|
|
|
+ else if (/[.,!?;,。!?;]/.test(char)) {
|
|
|
+ chunk = char;
|
|
|
+ }
|
|
|
+ // 如果是空格,与下一个单词一起显示
|
|
|
+ else if (char === " " && i + 1 < message.content.length) {
|
|
|
+ let j = i + 1;
|
|
|
+ while (j < message.content.length && !/[\s\n]/.test(message.content[j])) {
|
|
|
+ j++;
|
|
|
+ }
|
|
|
+ chunk = message.content.substring(i, j);
|
|
|
+ }
|
|
|
+ // 其他情况,单个字符显示
|
|
|
+ else {
|
|
|
+ chunk = char;
|
|
|
+ }
|
|
|
+
|
|
|
+ message.displayContent = message.content.substring(0, i) + chunk;
|
|
|
+ i += chunk.length;
|
|
|
+
|
|
|
+ // 根据字符类型动态调整延迟
|
|
|
+ const delay = chunk.length > 1 ? maxDelay : minDelay;
|
|
|
typingTimeout.value = setTimeout(typeChar, delay);
|
|
|
} else {
|
|
|
if (!isTyping.value) {
|
|
@@ -1673,6 +1964,21 @@ onUnmounted(() => {
|
|
|
// 添加 renderMarkdown 方法
|
|
|
const renderMarkdown = (content) => {
|
|
|
if (!content) return "";
|
|
|
+
|
|
|
+ /* // 处理代码块
|
|
|
+ content = content.replace(/```([\s\S]*?)```/g, (match, code) => {
|
|
|
+ return `<pre class="code-block"><code>${code}</code></pre>`;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理行内代码
|
|
|
+ content = content.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
|
+
|
|
|
+ // 处理标题
|
|
|
+ content = content.replace(/^(#{1,6})\s+(.*)$/gm, (match, hashes, title) => {
|
|
|
+ const level = hashes.length;
|
|
|
+ return `<h${level}>${title}</h${level}>`;
|
|
|
+ }); */
|
|
|
+
|
|
|
return md.render(content);
|
|
|
};
|
|
|
|
|
@@ -1825,6 +2131,10 @@ const updateCollapsedState = () => {
|
|
|
(activeTab.value === 'preview' && attachedFiles.value.length > 0)
|
|
|
)); */
|
|
|
});
|
|
|
+ // 推理模式
|
|
|
+ const handleInferenceModel = () => {
|
|
|
+ inferenceModel.value = !inferenceModel.value;
|
|
|
+ };
|
|
|
|
|
|
// 修改知识库搜索开关处理方法
|
|
|
const handleKnowledgeToggle = (event) => {
|
|
@@ -1936,7 +2246,7 @@ const updateCollapsedState = () => {
|
|
|
const fetchModels = async () => {
|
|
|
isLoadingModels.value = true;
|
|
|
try {
|
|
|
- const response = await fetch(`${API_BASE_URL}/api/models`);
|
|
|
+ const response = await fetch(`${API_BASE_URL}/api/models?merchant_id=${JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id}`);
|
|
|
const data = await response.json();
|
|
|
console.log(data);
|
|
|
if (data.code === 2000) {
|
|
@@ -2271,6 +2581,36 @@ const updateCollapsedState = () => {
|
|
|
const handleDocClick = (doc) => {
|
|
|
currentDoc.value = doc;
|
|
|
};
|
|
|
+
|
|
|
+ /*
|
|
|
+
|
|
|
+ */
|
|
|
+
|
|
|
+ // 添加流式思考步骤
|
|
|
+ const currentThinkingSteps = ref([]);
|
|
|
+
|
|
|
+ // 添加流式思考步骤
|
|
|
+ const addThinkingStep = (step) => {
|
|
|
+ currentThinkingSteps.value.push({
|
|
|
+ type: step.type || 'default',
|
|
|
+ content: step.content,
|
|
|
+ timestamp: Date.now()
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 清除当前思考步骤
|
|
|
+ const clearThinkingSteps = () => {
|
|
|
+ currentThinkingSteps.value = [];
|
|
|
+ };
|
|
|
+
|
|
|
+ // 在消息发送完成后将思考步骤添加到消息中
|
|
|
+ const finalizeThinkingSteps = (messageId) => {
|
|
|
+ const message = messages.value.find(m => m.id === messageId);
|
|
|
+ if (message) {
|
|
|
+ message.thinkingSteps = [...currentThinkingSteps.value];
|
|
|
+ clearThinkingSteps();
|
|
|
+ }
|
|
|
+ };
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
@@ -5454,4 +5794,107 @@ button,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+.thinking-steps {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step {
|
|
|
+ padding: 4px 8px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #f5f5f5;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step.start {
|
|
|
+ background: #e8f5e9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step.text_analysis {
|
|
|
+ background: #e3f2fd;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step.planning {
|
|
|
+ background: #fff3e0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step.execution {
|
|
|
+ background: #f3e5f5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step.result {
|
|
|
+ background: #e8eaf6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-step.conclusion {
|
|
|
+ background: #e0f7fa;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+</style>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.thinking-steps-stream {
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 100;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ padding: 10px;
|
|
|
+ margin: 10px 0;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+
|
|
|
+ .thinking-step-stream {
|
|
|
+ padding: 8px 12px;
|
|
|
+ margin: 4px 0;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #f5f5f5;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.5;
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-10px);
|
|
|
+
|
|
|
+ &.fade-in {
|
|
|
+ animation: fadeInDown 0.3s ease forwards;
|
|
|
+ }
|
|
|
+
|
|
|
+ .typing-indicator {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 8px;
|
|
|
+
|
|
|
+ span {
|
|
|
+ width: 4px;
|
|
|
+ height: 4px;
|
|
|
+ margin: 0 2px;
|
|
|
+ background: #666;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: typing 1s infinite ease-in-out;
|
|
|
+
|
|
|
+ &:nth-child(1) { animation-delay: 0.2s; }
|
|
|
+ &:nth-child(2) { animation-delay: 0.3s; }
|
|
|
+ &:nth-child(3) { animation-delay: 0.4s; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeInDown {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-10px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes typing {
|
|
|
+ 0%, 60%, 100% { transform: translateY(0); }
|
|
|
+ 30% { transform: translateY(-4px); }
|
|
|
+}
|
|
|
+
|
|
|
</style>
|