Commit b1f619b0 authored by 水玉婷's avatar 水玉婷
Browse files

feat:添加option模版数据

parent 684caa69
......@@ -6,10 +6,10 @@
<img :src="props.logoUrl || defaultAvatar" alt="avatar" class="avatar-image" />
</div>
<div class="header-info">
<h2>{{ props.dialogSessionId ? props?.detailData?.title || '继续对话' : '新建对话' }}</h2>
<h2>{{ props.dialogSessionId ? props?.detailData?.title || '继续对话' : '新建对333' }}</h2>
</div>
</div>
<!-- 当没有dialogSessionId且未开始对话时显示介绍页面 -->
<div class="chat-intro-center" v-if="!props.dialogSessionId && !hasStartedConversation">
<div class="intro-content">
......@@ -18,13 +18,13 @@
<p>可以问我公司经营情况、制度等相关问题,<br>我还在成长中,会不断强大</p>
</div>
</div>
<!-- 消息区域 -->
<div class="chat-messages" ref="messagesContainer" v-if="props.dialogSessionId || hasStartedConversation">
<div v-for="(msg, index) in messages" :key="index" :class="['message', msg.type]">
<div v-for="(msg, index) in messages" :key="index" :class="['message', msg.messageType]">
<div class="avatar-container">
<div class="avatar">
<template v-if="msg.type === 'received'">
<template v-if="msg.messageType === 'received'">
<img :src="props.logoUrl || defaultAvatar" alt="avatar" class="avatar-image" />
</template>
<template v-else>
......@@ -65,7 +65,7 @@
</div>
</div>
</div>
<!-- 输入区域 - 始终显示 -->
<div class="chat-input-container">
<div class="chat-input">
......@@ -81,7 +81,7 @@
<script setup lang="ts">
import { EventSourcePolyfill } from 'event-source-polyfill';
import { ref, reactive, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import dayjs from 'dayjs';
import { post } from '@/utils/axios.js'; // 导入axios的post方法
import tableTemplate from './tableTemplate';
......@@ -137,9 +137,36 @@ const contentTemplates = {
table: (tableData: any) => {
return tableTemplate(tableData);
},
// 选项数据模板 - 纯渲染,不允许点击
option: (optionData: any) => {
const { tips, options } = optionData;
// 生成选项列表HTML - 序号和标题直接放在一行
const optionsHtml = options?.map((item: any, index: number) => {
const { title, url } = item;
return `
<div class="option-item">
<div class="option-content">
<span class="option-number-title">${index + 1}. ${title}</span>
<span class="option-url">(${url})</span>
</div>
</div>
`;
}).join('') || '';
return `
<div class="message-options">
${tips ? `<div class="options-tips">${tips}</div>` : ''}
<div class="options-list">
${optionsHtml}
</div>
</div>
`;
},
// 简化的iframe模板 - 移除全屏功能,设置宽高100%固定
iframe: (iframeData: any) => {
const src = iframeData.src;
const { tips,title,url } = iframeData || {};
console.log('iframeData', iframeData);
return `<div class="message-iframe iframe-loading">
<!-- 加载状态 -->
<div class="iframe-loading">
......@@ -151,8 +178,10 @@ const contentTemplates = {
</div>
<!-- iframe容器 -->
<div class="iframe-tips">${tips || ''}</div>
<div class="iframe-title">${title || ''}</div>
<iframe
src="${src}"
src="${url}"
width="100%"
height="100%"
frameborder="0"
......@@ -168,7 +197,8 @@ const contentTemplates = {
// 定义消息类型 - 更新接口添加图表相关字段
interface Message {
type: 'received' | 'sent';
messageType: 'received' | 'sent';
type?: number | string;
avatar: string;
recordId: string;
promptTokens: number;
......@@ -186,14 +216,10 @@ interface Message {
}[];
}
interface Token {
value: string;
expire: number;
}
interface SSEData {
message: any;
status: number | string;
type: number | string;
}
interface ChatParams {
......@@ -222,6 +248,76 @@ const startConversation = () => {
hasStartedConversation.value = true;
};
// 模拟status为3、type为options的数据返回
const simulateOptionData = () => {
// 第一个消息:展示三个options
const firstOptionData = {
status: 3,
type: 'option',
message: {
tips: "以下是三个可选的报表链接:",
options: [
{ title: "月度销售报表", url: "https://example.com/report/monthly" },
{ title: "季度业绩分析", url: "https://example.com/report/quarterly" },
{ title: "年度总结报告", url: "https://example.com/report/annual" }
]
}
};
// 第二个消息:展示一个options(会走iframe逻辑)
const secondOptionData = {
status: 3,
type: 'option',
message: {
tips: "这是单个报表的预览:",
options: [
{
title: "销售指标看板",
url: "/WeChatOauth2/MobileReport_Monthly/MonthlyReport_index.aspx?postage=384b67414b334f2f693177644246313756704a724d513d3d&company=器械整体&typename=整体指标"
}
]
}
};
// 创建AI响应消息
const aiMessage = {
messageType: 'received',
avatar: 'AI',
recordId: 'simulate-001',
promptTokens: 100,
completionTokens: 50,
totalTokens: 150,
date: dayjs().format('HH:mm'),
contentBlocks: []
};
// 处理第一个选项数据(三个options)
const firstResult = processSSEMessage(
firstOptionData,
aiMessage,
false,
-1,
false
);
// 处理第二个选项数据(一个options)
const secondResult = processSSEMessage(
secondOptionData,
firstResult.updatedResponse,
false,
-1,
false
);
// 将处理后的消息添加到消息列表
if (secondResult.updatedResponse) {
messages.value.push(secondResult.updatedResponse);
nextTick(() => {
scrollToBottom();
});
}
};
// 发送消息
const sendMessage = async () => {
loading.value = true;
......@@ -230,13 +326,13 @@ const sendMessage = async () => {
if (message) {
// 开始对话
startConversation();
isAIResponding.value = false;
isInThinkingMode.value = false;
currentAIResponse.value = null;
messages.value.push({
type: 'sent',
messageType: 'sent',
avatar: '',
recordId: '',
promptTokens: 0,
......@@ -262,41 +358,13 @@ const sendMessage = async () => {
try {
messageText.value = '';
// 模拟AI返回iframe数据
// 可选:在发送消息后模拟选项数据返回
// 取消注释以下代码来启用模拟
setTimeout(() => {
// 创建AI响应消息
const aiMessage = {
type: 'received',
avatar: 'AI',
recordId: '111',
promptTokens: 1110,
completionTokens: 11,
totalTokens: 1110,
date: dayjs().format('HH:mm'),
contentBlocks: [
{
content: contentTemplates.text('这是模拟的AI响应,包含一个嵌入的iframe:涵盖了销售指标的展示'),
thinkContent: '',
hasThinkBox: false,
thinkBoxExpanded: false,
},
{
content: contentTemplates.iframe({
src:'/WeChatOauth2/MobileReport_Monthly/MonthlyReport_index.aspx?postage=384b67414b334f2f693177644246313756704a724d513d3d&company=器械整体&typename=整体指标',
}),
thinkContent: '',
hasThinkBox: false,
thinkBoxExpanded: false,
}
],
};
messages.value.push(aiMessage);
nextTick(() => {
scrollToBottom();
});
simulateOptionData();
loading.value = false;
}, 1000); // 1秒后模拟AI响应
}, 1000);
// 调用外部传入的消息发送函数
if (props.onMessageSend) {
console.log('调用外部消息发送函数', message);
......@@ -314,7 +382,7 @@ const sendMessage = async () => {
'x-app-code': props.appCode || '',
}
});
const data = response.data;
if (data.code === 0) {
loading.value = false;
......@@ -337,7 +405,8 @@ const processSSEMessage = (
isHistoryData = false,
) => {
let messageContent = data.message || '';
const contentType = data.status;
const contentStatus = data.status;
const contentType = data.type
let updatedResponse = currentResponse;
let updatedIsThinking = isThinking;
let updatedBlockIndex = currentBlockIndex;
......@@ -350,7 +419,7 @@ const processSSEMessage = (
// 根据是否为历史数据设置默认展开状态
const defaultThinkBoxExpanded = !isHistoryData;
switch (contentType) {
switch (contentStatus) {
case -1: // 错误信息
if (updatedResponse) {
updatedResponse.contentBlocks.push({
......@@ -363,33 +432,60 @@ const processSSEMessage = (
break;
case 3: // 图表数据
if (updatedResponse) {
const { rows } = messageContent;
// 表格数据处理
updatedResponse.contentBlocks.push({
content: contentTemplates.table(rows),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
// 图表数据处理
updatedResponse.contentBlocks.push({
content: '',
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
chartData: messageContent, // 添加图表数据
chartType: 3,
});
}
break;
case 4: // iframe数据
if (updatedResponse) {
updatedResponse.contentBlocks.push({
content: contentTemplates.iframe(messageContent),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
switch (contentType) {
case 'table': // 表格数据
const { rows } = messageContent;
// 表格数据处理
updatedResponse.contentBlocks.push({
content: contentTemplates.table(rows),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
// 图表数据处理
updatedResponse.contentBlocks.push({
content: '',
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
chartData: messageContent, // 添加图表数据
chartType: 3,
});
break;
case 'option': // 选项数据
const { tips,options } = messageContent;
if (options?.length) {
if (options?.length === 1) {
// 走iframe
updatedResponse.contentBlocks.push({
content: contentTemplates.iframe({
...options[0],
tips: tips || '',
}),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
} else {
updatedResponse.contentBlocks.push({
content: contentTemplates.option(messageContent),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
}
}
break;
default: // 默认处理
updatedResponse.contentBlocks.push({
content: contentTemplates.text(messageContent || ''),
hasThinkBox: false,
thinkContent: '',
thinkBoxExpanded: false,
});
break;
}
}
break;
case 10: // 思考开始
......@@ -567,7 +663,7 @@ const initSSE = () => {
if (!isAIResponding.value && hasContent) {
isAIResponding.value = true;
currentAIResponse.value = {
type: 'received',
messageType: 'received',
avatar: 'AI',
recordId: '',
promptTokens: 0,
......@@ -583,7 +679,7 @@ const initSSE = () => {
if (!isAIResponding.value) {
isAIResponding.value = true;
currentAIResponse.value = {
type: 'received',
messageType: 'received',
avatar: 'AI',
recordId: '',
promptTokens: 0,
......@@ -675,7 +771,7 @@ const processHistoryData = (dataArray: any[]) => {
let date = dayjs(data.startTime).format('YYYY-MM-DD HH:mm:ss');
if (data.question) {
result.push({
type: 'sent',
messageType: 'sent',
avatar: '',
recordId: '',
promptTokens: 0,
......@@ -695,7 +791,7 @@ const processHistoryData = (dataArray: any[]) => {
if (data.answerInfoList && Array.isArray(data.answerInfoList)) {
const aiMessage: Message = {
type: 'received',
messageType: 'received',
avatar: 'AI',
recordId: '',
promptTokens: 0,
......@@ -713,6 +809,7 @@ const processHistoryData = (dataArray: any[]) => {
const sseData: SSEData = {
message: answer.message || '',
status: answer.status || 0,
type: answer.type || 0,
};
const processResult = processSSEMessage(
......@@ -763,7 +860,7 @@ const getChatRecord = async (dialogSessionId: string) => {
'x-app-code': props.appCode || '',
}
});
const data = response.data;
if (data.code === 0) {
const recordList = processHistoryData(data.data || []);
......
......@@ -560,9 +560,34 @@ p, h1, h2, h3, h4, h5, h6, ul, ol, li {
max-width: 100%;
margin: 8px 0;
border-radius: 8px;
overflow: hidden;
background-color: @white;
border: 1px solid @blue-light-3;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
min-height: 300px;
// iframe提示信息样式
.iframe-tips {
padding: 12px 16px;
background-color: @blue-light-2;
border-bottom: 1px solid @blue-light-3;
font-size: 14px;
color: @gray-7;
font-weight: 500;
line-height: 1.4;
}
// iframe标题样式
.iframe-title {
padding: 8px 16px;
background-color: @white;
border-bottom: 1px solid @gray-2;
font-size: 14px;
color: @gray-7;
font-weight: 600;
line-height: 1.4;
}
iframe {
width: 100%;
......@@ -726,4 +751,95 @@ p, h1, h2, h3, h4, h5, h6, ul, ol, li {
width: 40px;
height: 40px;
}
}
// =============================================
// 选项消息样式 - 纯渲染版本
// =============================================
:deep(.message-options) {
width: 100%;
max-width: 100%;
margin: 8px 0;
border-radius: 8px;
background-color: @white;
border: 1px solid @blue-light-3;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
.options-tips {
padding: 12px 16px;
background-color: @blue-light-2;
border-bottom: 1px solid @blue-light-3;
font-size: 14px;
color: @gray-7;
font-weight: 500;
line-height: 1.4;
}
.options-list {
padding: 8px 0;
}
.option-item {
padding: 8px 16px;
border-bottom: 1px solid @gray-2;
transition: background-color 0.2s ease;
&:last-child {
border-bottom: none;
}
&:hover {
background-color: @blue-light-1;
}
.option-content {
line-height: 1.4;
.option-number-title {
font-size: 14px;
color: @gray-7;
font-weight: 500;
// 序号部分特殊样式
&::before {
content: '';
display: inline;
}
}
.option-number-title:first-letter {
color: @primary-color;
font-weight: 600;
}
.option-url {
font-size: 12px;
color: @gray-5;
font-family: 'Courier New', monospace;
word-break: break-all;
display: block;
margin-top: 4px;
}
}
}
}
// 响应式调整
@media (max-width: 768px) {
:deep(.message-options) {
.option-item {
.option-content {
.option-number-title {
font-size: 14px;
}
.option-url {
font-size: 11px;
max-width: 100%;
}
}
}
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment