|
@@ -15,7 +15,10 @@
|
|
|
} from '@/api/ai'
|
|
|
import {
|
|
|
Top,
|
|
|
- Plus
|
|
|
+ Plus,
|
|
|
+ Check,
|
|
|
+ Loading,
|
|
|
+ Monitor
|
|
|
} from '@element-plus/icons-vue'
|
|
|
import {
|
|
|
useUserStore
|
|
@@ -99,7 +102,7 @@
|
|
|
if (flowData.state) {
|
|
|
versionIndex.value = 1;
|
|
|
chatHistory.value = flowData.data.data.map(node => {
|
|
|
- node['AIoutputs'] = renderMarkdown(node.answer);
|
|
|
+ node['AIoutputs'] = returnAIoutputs(node.answer);
|
|
|
return node
|
|
|
});
|
|
|
hljs.highlightAll();
|
|
@@ -114,6 +117,23 @@
|
|
|
if (!prompt.value.trim() || loading.value) return;
|
|
|
AIMessage(prompt.value)
|
|
|
}
|
|
|
+ const returnAIoutputs = (data) => {
|
|
|
+ let str = '';
|
|
|
+ if (data.indexOf('```markdown') > -1) {
|
|
|
+ str = data.replaceAll('\n', '</n>').replaceAll('```markdown', '').replaceAll('```', '');
|
|
|
+ } else {
|
|
|
+ str = data;
|
|
|
+ }
|
|
|
+ let obj = str.split('<workark>');
|
|
|
+ let newData = obj.map(node => {
|
|
|
+ try {
|
|
|
+ return JSON.parse(node)
|
|
|
+ } catch {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return deduplicateByTypeAndId(newData);
|
|
|
+ }
|
|
|
const AIMessage = async (userMessage) => {
|
|
|
try {
|
|
|
// 1. 添加用户消息到历史记录
|
|
@@ -127,6 +147,7 @@
|
|
|
currentAIResponse.value = ''
|
|
|
prompt.value = '' // 清空输入框
|
|
|
let result = '';
|
|
|
+ let setTime = null;
|
|
|
fetchEventSource(`${config.baseURL}/api/ai/chat/run/7`, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
@@ -146,11 +167,21 @@
|
|
|
}),
|
|
|
onmessage(event) {
|
|
|
// 处理接收到的消息
|
|
|
- if (!isStrictJSONString(event.data)) {
|
|
|
- result += event.data;
|
|
|
- console.log(result);
|
|
|
- chatHistory.value[chatHistory.value.length - 1].AIoutputs = renderMarkdown(result);
|
|
|
- scrollToBottom();
|
|
|
+ result += event.data;
|
|
|
+ if (!setTime) {
|
|
|
+ setTime = setTimeout(() => {
|
|
|
+ console.log(result);
|
|
|
+ if (result.indexOf('markdown') > -1) {
|
|
|
+ initChatList();
|
|
|
+ } else {
|
|
|
+ chatHistory.value[chatHistory.value.length - 1].AIoutputs =
|
|
|
+ returnAIoutputs(result);
|
|
|
+ scrollToBottom();
|
|
|
+ clearTimeout(setTime);
|
|
|
+ loading.value = false;
|
|
|
+ setTime = null;
|
|
|
+ }
|
|
|
+ }, 1000)
|
|
|
}
|
|
|
},
|
|
|
onopen(response) {
|
|
@@ -162,6 +193,7 @@
|
|
|
console.error('Error:', error);
|
|
|
},
|
|
|
onclose() {
|
|
|
+ console.log('close');
|
|
|
// 连接关闭时的回调
|
|
|
loading.value = false;
|
|
|
}
|
|
@@ -170,16 +202,6 @@
|
|
|
console.error("请求失败:", error);
|
|
|
}
|
|
|
}
|
|
|
- const isStrictJSONString = str => {
|
|
|
- console.log(str);
|
|
|
- try {
|
|
|
- const parsed = JSON.parse(str);
|
|
|
- return parsed !== null && typeof parsed === 'object';
|
|
|
- } catch (e) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
const scrollToBottom = () => {
|
|
|
nextTick(() => {
|
|
|
if (chatBody.value) {
|
|
@@ -191,6 +213,16 @@
|
|
|
if (!data) return '';
|
|
|
return JSON.parse(data).query;
|
|
|
}
|
|
|
+ const deduplicateByTypeAndId = (arr) => {
|
|
|
+ const map = new Map();
|
|
|
+
|
|
|
+ arr.forEach(item => {
|
|
|
+ const key = item.type === 'step' && item.id ? `${item.type}-${item.id}` : item.type;
|
|
|
+ map.set(key, item); // 后面覆盖前面的
|
|
|
+ });
|
|
|
+
|
|
|
+ return Array.from(map.values());
|
|
|
+ }
|
|
|
/**
|
|
|
* 使用 marked 解析 Markdown
|
|
|
* @param markdown 解析的文本
|
|
@@ -207,7 +239,7 @@
|
|
|
const highlighted = hljs.highlight(text, {
|
|
|
language
|
|
|
}).value;
|
|
|
- return `<div class="hljs-box"><div class="hljs-title">${language.toLocaleUpperCase()}</div><pre><code class="hljs language-${language}">${highlighted}</code></pre></div>`;
|
|
|
+ return `<div class="hljs-box"><div class="hljs-title">${language.toLocaleUpperCase()}</div><pre class="hljs-pre"><code class="hljs language-${language}">${highlighted}</code></pre></div>`;
|
|
|
};
|
|
|
renderer.link = (href) => {
|
|
|
let divBox = document.createElement('div');
|
|
@@ -220,7 +252,7 @@
|
|
|
previewUrl.value = href.href;
|
|
|
return divBox.innerHTML;
|
|
|
};
|
|
|
- return marked(markdown, {
|
|
|
+ return marked('```markdown' + markdown.replaceAll('</n>', '\n') + '```', {
|
|
|
renderer
|
|
|
});
|
|
|
};
|
|
@@ -241,6 +273,36 @@
|
|
|
onUnmounted(() => {
|
|
|
stopTimer()
|
|
|
})
|
|
|
+ const activities = ref([]);
|
|
|
+ // {
|
|
|
+ // title: '现在我将开始创建项目',
|
|
|
+ // type: 'success',
|
|
|
+ // icon: Check,
|
|
|
+ // content: '项目创建成功',
|
|
|
+ // describe: '项目名:text-project01'
|
|
|
+ // }, {
|
|
|
+ // title: '开始创建首页',
|
|
|
+ // type: 'success',
|
|
|
+ // icon: Check,
|
|
|
+ // content: '页面创建成功',
|
|
|
+ // describe: '/app/index/page.jsx'
|
|
|
+ // }, {
|
|
|
+ // title: '开始创建关于我们',
|
|
|
+ // type: 'success',
|
|
|
+ // icon: Check,
|
|
|
+ // content: '页面创建成功',
|
|
|
+ // describe: '/app/about/page.jsx'
|
|
|
+ // }, {
|
|
|
+ // title: '开始创建联系我们',
|
|
|
+ // type: 'success',
|
|
|
+ // icon: Check,
|
|
|
+ // content: '页面创建成功',
|
|
|
+ // describe: '/app/tell/page.jsx'
|
|
|
+ // }, {
|
|
|
+ // title: '创建项目',
|
|
|
+ // type: 'warning',
|
|
|
+ // icon: Loading,
|
|
|
+ // }
|
|
|
</script>
|
|
|
<template>
|
|
|
<div class="ai-chat-container">
|
|
@@ -259,7 +321,34 @@
|
|
|
<div class="message-header-label" style="width: 100px;">AI助手</div>
|
|
|
</div>
|
|
|
<div class="message-content">
|
|
|
- <div class="ai-website-boxs" @click="AIClick" v-html="message.AIoutputs"></div>
|
|
|
+ <div class="ai-website-boxs">
|
|
|
+ <div v-for="(item,index) in message.AIoutputs">
|
|
|
+ <div class="wk-query" v-if="item.type === 'query'">{{item.label}}</div>
|
|
|
+ <div class="wk-markdown wk-query" v-if="item.type === 'markdown'"
|
|
|
+ v-html="renderMarkdown(item.label)">
|
|
|
+ </div>
|
|
|
+ <div class="wk-step" v-if="item.type === 'step'">
|
|
|
+ <el-timeline class="wk-timeline">
|
|
|
+ <el-timeline-item :class="'timeline-'+item.type"
|
|
|
+ :icon="item.status === 'success' ? Check:Loading" :type="item.status"
|
|
|
+ size="large">
|
|
|
+ <div class="wk-content">
|
|
|
+ <div class="title">{{ item.label }}</div>
|
|
|
+ <div class="content">
|
|
|
+ <el-icon>
|
|
|
+ <Monitor />
|
|
|
+ </el-icon>
|
|
|
+ <span class="content-label">{{ item.label }}</span>
|
|
|
+ <span class="content-describe">{{ item.describe }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-timeline-item>
|
|
|
+ </el-timeline>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="ai-website-boxs" @click="AIClick" v-html="message.AIoutputs"></div> -->
|
|
|
+
|
|
|
<el-button size="small" @click="reloadMessage(message.query)" :disabled="loading"
|
|
|
style="margin-top: 10px;" v-if="message.AIoutputs.indexOf('系统繁忙,请重试')>-1">
|
|
|
点击重试
|
|
@@ -318,15 +407,16 @@
|
|
|
padding: 5px 10px;
|
|
|
color: #354052;
|
|
|
font-weight: bold;
|
|
|
- border-bottom: 1px solid #999999;
|
|
|
+ border-bottom: 1px solid #d0d3d9;
|
|
|
}
|
|
|
|
|
|
- .language-html {
|
|
|
+ .language-html,
|
|
|
+ .language-markdown {
|
|
|
background: #f1f2f5;
|
|
|
}
|
|
|
|
|
|
- pre {
|
|
|
- margin-top: 0;
|
|
|
+ .hljs-pre {
|
|
|
+ margin: 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -338,6 +428,65 @@
|
|
|
display: inline-block;
|
|
|
margin-top: 5px;
|
|
|
}
|
|
|
+
|
|
|
+ .timeline-warning {
|
|
|
+ .el-timeline-item__icon {
|
|
|
+ animation: rotating 2s linear infinite;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .wk-query {
|
|
|
+ padding: 12px 0;
|
|
|
+ font-size: 15px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ padding-bottom: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .wk-step {
|
|
|
+ padding-top: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .wk-timeline {
|
|
|
+ .el-timeline-item__icon {
|
|
|
+ font-size: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-timeline-item {
|
|
|
+ padding-bottom: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .wk-content {
|
|
|
+ .title {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #222;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content {
|
|
|
+ background: rgb(246, 247, 249);
|
|
|
+ border: 1px solid rgb(234, 234, 234);
|
|
|
+ height: 30px;
|
|
|
+ line-height: 28px;
|
|
|
+ border-radius: 30px;
|
|
|
+ padding: 0 15px;
|
|
|
+ font-size: 13px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ color: rgb(120, 121, 121);
|
|
|
+ margin-top: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-label {
|
|
|
+ margin: 0 10px 0 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-describe {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|
|
|
<style scoped lang="scss">
|
|
|
.ai-chat-container {
|
|
@@ -389,12 +538,12 @@
|
|
|
border: 1px solid #e9ecef;
|
|
|
margin-right: auto;
|
|
|
border-bottom-left-radius: 4px;
|
|
|
+ width: 100%;
|
|
|
}
|
|
|
|
|
|
.message-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- margin-bottom: 5px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|