- 相关案例
+ 引用案例
+
+ {{ showAllCaseRefers ? '收起' : '查看更多' }}
+
-
+
+
{{ item.authorName }}
+ {{ item.orgInfo }}
{{ keyword }}
+
+
{{item.content}}
@@ -160,6 +186,25 @@ export default {
.case-refers-title {
font-weight: bold;
margin-bottom: 5px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ .icon-think {
+ background-image: url("./map.svg") ;
+ width: 15px;
+ height: 13px;
+ display: inline-block;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ }
+ .more{
+ font-size: 10px;
+ padding: 2px 6px;
+ background-color: #F4F7FD;
+ border-radius: 5px;
+ color:#577EE1;font-weight: unset;
+ cursor: pointer;
+ }
}
.case-refers-list {
@@ -177,12 +222,49 @@ export default {
.case-refers-item-title {
font-size: 14px;
margin-bottom: 5px;
- }
+ font-weight: 600;
+ color: #000;
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ .title{
+ max-width: 70% ;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ }
+ .case-refers-item-timer{
+ font-size: 10px;
+ margin-right: 20px;
+ color:#cecece;
+ font-weight: unset!important;
+ }
+ }
+.case-refers-item-author{
+ display: flex;
+ align-items: center;
+ .user{
+ background-image: url("./user.svg");
+ width: 15px;
+ height: 15px;
+ display: inline-block;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ margin-right: 5px;
+ }
+ .case-inter-orginInfo{
+ font-size: 10px;
+ color: rgba(144, 147, 153, 0.44);margin-left: 5px;
+
+ }
+}
.case-refers-item-author,
.case-refers-item-keywords {
font-size: 12px;
- color: #909399;
+ font-weight: 600;
+ color: #000;
}
}
}
@@ -197,5 +279,22 @@ export default {
border-radius: 5px;
margin-bottom: 10px;
}
+ .case-refers-item-keywords{
+ margin-top: 5px;
+ span{
+ padding: 1px 4px;
+ background-color: #F4F7FD;
+ border-radius: 5px;
+ font-size: 10px!important;
+ color:#577EE1
+ }
+ span + span{
+ margin-left: 8px;
+ }
+ }
+ .message-content{
+ font-size: 12px!important;
+ margin-top: 5px;
+ }
}
diff --git a/src/views/portal/case/components/sendMessage.vue b/src/views/portal/case/components/sendMessage.vue
index 55be4afd..6c3c7c52 100644
--- a/src/views/portal/case/components/sendMessage.vue
+++ b/src/views/portal/case/components/sendMessage.vue
@@ -55,7 +55,6 @@ export default {
methods: {
handleSend() {
if (!this.inputContent.trim() || this.disabled) return
- console.log(1);
// 添加用户消息到列表
const userMessage = {
isBot: false,
@@ -80,6 +79,7 @@ export default {
const aiMessage = {
isBot: true,
text: '',
+ status:null,
thinkText: '',
caseRefers: [], // 添加caseRefers字段
textCompleted: false // 添加文字处理完成状态,默认为false
@@ -93,7 +93,15 @@ export default {
};
// 创建POST请求
- aiChat(requestData).then(response => {
+ fetch('/systemapi/xboe/m/boe/case/ai/chat',{
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(requestData)
+ }).then(r=>{
+ return r
+ }).then(response => {
// 处理流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
@@ -101,15 +109,101 @@ export default {
let accumulatedContent = ''; // 累积的内容用于打字机效果
let accumulatedThinkContent = ''; // 累积的思考内容
let inThinkSection = false; // 是否在思考部分
+ let typingTimer = null; // 打字机定时器
+ let thinkTypingTimer = null; // 思考内容打字机定时器
+
+ // 逐字显示文本的函数
+ const typeText = (message, fullContent) => {
+ // 如果已有定时器在运行,先清除它
+ if (typingTimer) {
+ clearInterval(typingTimer);
+ }
+
+ // 获取当前已显示的文本长度
+ const currentLength = message.text.length;
+ // 获取完整文本
+ const targetLength = fullContent.length;
+
+ // 如果已经显示完整文本,不需要继续
+ if (currentLength >= targetLength) {
+ return;
+ }
+
+ const typingSpeed = 50; // 每个字符的间隔时间(毫秒)
+
+ typingTimer = setInterval(() => {
+ // 计算下一个要显示的字符索引
+ const nextIndex = message.text.length + 1;
+ if (nextIndex <= targetLength) {
+ message.text = fullContent.substring(0, nextIndex);
+ this.$emit('update-message', message);
+ } else {
+ clearInterval(typingTimer);
+ typingTimer = null;
+ // 当打字机效果完成时,检查是否应该设置textCompleted为true
+ // 这应该在status 4(交互完成)时才设置
+ if (message.status === 4) {
+ if (nextIndex >= targetLength) {
+ message.textCompleted = true;
+ }
+ }
+ }
+ }, typingSpeed);
+ };
+
+ // 逐字显示思考内容的函数
+ const typeThinkText = (message, fullThinkContent) => {
+ // 如果已有定时器在运行,先清除它
+ if (thinkTypingTimer) {
+ clearInterval(thinkTypingTimer);
+ }
+
+ // 获取当前已显示的文本长度
+ const currentLength = message.thinkText.length;
+ // 获取完整文本
+ const targetLength = fullThinkContent.length;
+
+ // 如果已经显示完整文本,不需要继续
+ if (currentLength >= targetLength) {
+ return;
+ }
+
+ // 从当前显示位置开始继续显示(避免清空重显)
+ const startIndex = currentLength;
+
+ const typingSpeed = 20; // 每个字符的间隔时间(毫秒)
+
+ thinkTypingTimer = setInterval(() => {
+ // 计算下一个要显示的字符索引
+ const nextIndex = message.thinkText.length + 1;
+ if (nextIndex <= targetLength) {
+ message.thinkText = fullThinkContent.substring(0, nextIndex);
+ this.$emit('update-message', message);
+ } else {
+ clearInterval(thinkTypingTimer);
+ thinkTypingTimer = null;
+ }
+ }, typingSpeed);
+ };
+
+ // 添加一个检查是否所有文本都已完成显示的函数
+ const isTextDisplayCompleted = (message, fullContent) => {
+ return message.text.length >= fullContent.length;
+ };
// 读取流数据
const read = () => {
reader.read().then(({ done, value }) => {
if (done) {
- // 当流结束时,设置文字处理完成状态为true
- aiMessage.textCompleted = true;
- this.$emit('update-message', aiMessage);
- this.$emit('loading', false);
+ // 当流结束时,等待打字机效果完成
+ const waitForTyping = () => {
+ if (!typingTimer) {
+ this.$emit('loading', false);
+ } else {
+ setTimeout(waitForTyping, 100);
+ }
+ };
+ waitForTyping();
return;
}
@@ -121,71 +215,82 @@ export default {
buffer = lines.pop(); // 保留不完整的行
for (const line of lines) {
- if (line.startsWith('data: ')) {
+ if (line.startsWith('data:')) {
try {
- const jsonData = JSON.parse(line.substring(6));
-
+ const jsonData = JSON.parse(line.substring(5));
// 根据status处理不同类型的数据
- switch (jsonData.data.status) {
+ switch (jsonData.status) {
case 0: // 引用文件
// 处理引用文件信息
- if (jsonData.data.fileRefer && jsonData.data.fileRefer.caseRefers) {
- aiMessage.caseRefers = jsonData.data.fileRefer.caseRefers;
+ if (jsonData.fileRefer && jsonData.fileRefer.caseRefers) {
+ aiMessage.caseRefers = jsonData.fileRefer.caseRefers;
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
}
// 从响应中获取并保存conversationId
- if (jsonData.data.conversationId) {
- this.conversationId = jsonData.data.conversationId;
+ if (jsonData.conversationId) {
+ this.conversationId = jsonData.conversationId;
}
break;
case 1: // 流式对话内容
- // 处理<think>标签内容
- const content = jsonData.data.content;
+ // 处理
+ const content = jsonData.content;
+ aiMessage.hasThink = false;
if (content.startsWith('
')) {
+ aiMessage.hasThink = true
inThinkSection = true;
- accumulatedThinkContent = content.replace('<think>', '');
- aiMessage.thinkText = accumulatedThinkContent;
- } else if (content.startsWith('</think>')) {
+ accumulatedThinkContent = content.replace('', '');
+ // 使用打字机效果显示think内容
+ typeThinkText(aiMessage, accumulatedThinkContent);
+ } else if (content.startsWith('')) {
inThinkSection = false;
accumulatedThinkContent += content.replace('', '');
- aiMessage.thinkText = accumulatedThinkContent;
+ // 使用打字机效果显示think内容
+ typeThinkText(aiMessage, accumulatedThinkContent);
} else if (inThinkSection) {
accumulatedThinkContent += content;
- aiMessage.thinkText = accumulatedThinkContent;
+ // 使用打字机效果显示think内容
+ typeThinkText(aiMessage, accumulatedThinkContent);
} else {
- // 累积内容并更新显示
+ // 累积内容并使用打字机效果更新显示
accumulatedContent += content;
- aiMessage.text = accumulatedContent;
+ // 如果thinkText已经显示完整,则继续使用打字机效果显示内容
+ if( aiMessage.hasThink){
+ if(aiMessage.thinkText.length >=accumulatedThinkContent.length){
+ typeText(aiMessage, accumulatedContent);
+ }
+ } else {
+ typeText(aiMessage, accumulatedContent);
+ }
+
}
- // 更新父组件的messageList
- this.$emit('update-message', aiMessage);
+ // 不在这里直接更新,让打字机效果处理更新
break;
case 2: // 回答完成
- // 设置文字处理完成状态为true
- aiMessage.textCompleted = true;
+ // 不再在这里设置textCompleted状态
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
// 从响应中获取并保存conversationId
- if (jsonData.data.conversationId) {
- this.conversationId = jsonData.data.conversationId;
- }
+
break;
case 3: // 返回建议
// 这里可以处理建议问题
- console.log('建议问题:', jsonData.data.suggestions);
- this.$emit('update-suggestions', []);
+ this.$emit('update-suggestions', jsonData.suggestions);
break;
case 4: // 交互完成
+ aiMessage.status = 4
+
// 从响应中获取并保存conversationId
- if (jsonData.data.conversationId) {
- this.conversationId = jsonData.data.conversationId;
- }
this.$emit('loading', false);
+ // 检查文本是否已经完全显示,如果是则设置textCompleted为true
+ if (isTextDisplayCompleted(aiMessage, accumulatedContent)) {
+ // aiMessage.textCompleted = true;
+ this.$emit('update-message', aiMessage);
+ }
break;
}
} catch (error) {
@@ -199,6 +304,10 @@ export default {
}).catch(error => {
console.error('SSE连接错误:', error);
// 出错时也设置文字处理完成状态
+ if (typingTimer) {
+ clearInterval(typingTimer);
+ typingTimer = null;
+ }
aiMessage.textCompleted = true;
this.$emit('loading', false);
aiMessage.text = '抱歉,网络连接出现问题,请稍后重试。';
diff --git a/src/views/portal/case/components/user.svg b/src/views/portal/case/components/user.svg
new file mode 100644
index 00000000..3ffc87bc
--- /dev/null
+++ b/src/views/portal/case/components/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file