280 lines
6.7 KiB
Vue
280 lines
6.7 KiB
Vue
|
|
<template>
|
|||
|
|
<d-modal
|
|||
|
|
v-model="visible"
|
|||
|
|
title="AI问答"
|
|||
|
|
@ok="handleClose"
|
|||
|
|
:cancel="handleClose"
|
|||
|
|
style="width: 900px;"
|
|||
|
|
>
|
|||
|
|
<div class="log-content">
|
|||
|
|
<McLayout>
|
|||
|
|
<!-- <McLayoutHeader>
|
|||
|
|
<McHeader :logoImg="'/logo.svg'" :title="'MateChat'"></McHeader>
|
|||
|
|
</McLayoutHeader>-->
|
|||
|
|
<McLayoutContent v-if="visible" style="margin: 16px 0;">
|
|||
|
|
<div ref="conversationRef" class="conversation-area">
|
|||
|
|
<!-- 欢迎提示 -->
|
|||
|
|
<div v-if="messages.length === 0" class="welcome-message">
|
|||
|
|
|
|||
|
|
<div class="welcome-page">
|
|||
|
|
<div class="home-title">
|
|||
|
|
<img src="@/assets/imgs/jyh/aiLogo2.png" alt="logo" />
|
|||
|
|
<h1>可信开源代码库AI助手</h1>
|
|||
|
|
</div>
|
|||
|
|
<div class="home-detail">
|
|||
|
|
<div class="home-detail-1">Hi,欢迎使用可信开源代码库AI助手</div>
|
|||
|
|
<div class="home-detail-2">可信开源代码库AI助手 可以辅助研发人员进行软件查询、批量校验、查看软件详情等。</div>
|
|||
|
|
<div class="home-detail-2">作为AI模型,可信开源代码库AI助手 提供的答案可能不总是确定或准确的,但您的反馈可以帮助 可信开源代码库AI助手 做得更好。</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<!-- <div class="welcome-icon">-->
|
|||
|
|
<!-- <i class="fa fa-comment-dots"></i>-->
|
|||
|
|
<!-- </div>-->
|
|||
|
|
<!-- <div class="welcome-text">-->
|
|||
|
|
<!-- <h3>请输入你想问的内容</h3>-->
|
|||
|
|
<!-- <p>与AI助手开始对话,获取智能回答</p>-->
|
|||
|
|
<!-- </div>-->
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<template v-for="(msg, idx) in messages" :key="idx">
|
|||
|
|
<McBubble v-if="msg.role === 'user'" :content="msg.content" :align="'right'"></McBubble>
|
|||
|
|
<McBubble v-else :loading="msg.loading ?? false">
|
|||
|
|
<McMarkdownCard :content="msg.content" :theme="theme"></McMarkdownCard>
|
|||
|
|
</McBubble>
|
|||
|
|
</template>
|
|||
|
|
</div>
|
|||
|
|
</McLayoutContent>
|
|||
|
|
<McLayoutSender>
|
|||
|
|
<McInput :value="inputValue" :maxLength="2000" @submit="onSubmit" showCount></McInput>
|
|||
|
|
</McLayoutSender>
|
|||
|
|
</McLayout>
|
|||
|
|
</div>
|
|||
|
|
</d-modal>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, watch,nextTick } from 'vue';
|
|||
|
|
import { Message } from 'vue-devui';
|
|||
|
|
import axios from 'axios';
|
|||
|
|
import {uvdV1Chat} from "@/api/inboundTask/inboundTaskApi";
|
|||
|
|
|
|||
|
|
const messages = ref([]);
|
|||
|
|
const history = ref([]);
|
|||
|
|
const resultInfo = ref(null);
|
|||
|
|
const inputValue = ref('');
|
|||
|
|
const conversationRef = ref(null);
|
|||
|
|
|
|||
|
|
// 组件状态
|
|||
|
|
const visible = ref(false);
|
|||
|
|
const VITE_AI_API_HOST = import.meta.env.VITE_AI_API_HOST;
|
|||
|
|
// 打开弹框时调用接口
|
|||
|
|
const open = async (id) => {
|
|||
|
|
if (!id) return Message.error('缺少result_id参数');
|
|||
|
|
visible.value = true;
|
|||
|
|
resultInfo.value = id
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onSubmit = (e) => {
|
|||
|
|
if(e === '') {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if(messages.value[messages.value.length-1]?.loading) return;
|
|||
|
|
inputValue.value = '';
|
|||
|
|
messages.value.push({
|
|||
|
|
role: 'user',
|
|||
|
|
content: e,
|
|||
|
|
});
|
|||
|
|
nextTick(() => {
|
|||
|
|
conversationRef.value?.scrollTo({
|
|||
|
|
top: conversationRef.value.scrollHeight,
|
|||
|
|
behavior: 'smooth',
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
getAIAnswer(e);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getAIAnswer = async (content) => {
|
|||
|
|
messages.value.push({
|
|||
|
|
role: 'assistant',
|
|||
|
|
content: '',
|
|||
|
|
loading: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const res = await uvdV1Chat({
|
|||
|
|
"resultInfo": resultInfo.value,
|
|||
|
|
"query": content,
|
|||
|
|
"history": history.value || []
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let reply = res?.data?.data?.reply || '';
|
|||
|
|
history.value.push({role: "user", content: content});
|
|||
|
|
history.value.push({role: "assistant", content: reply});
|
|||
|
|
messages.value[messages.value.length - 1].content = reply;
|
|||
|
|
messages.value[messages.value.length - 1].loading = false;
|
|||
|
|
nextTick(() => {
|
|||
|
|
conversationRef.value?.scrollTo({
|
|||
|
|
top: conversationRef.value.scrollHeight
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
/* 模拟流式数据返回 */
|
|||
|
|
// setTimeout(async () => {
|
|||
|
|
// messages.value.at(-1).loading = false;
|
|||
|
|
// for (let i = 0; i < reply.length;) {
|
|||
|
|
// await new Promise(r => setTimeout(r, 300 * Math.random()));
|
|||
|
|
// messages.value[messages.value.length - 1].content = reply.slice(0, i += Math.random() * 10);
|
|||
|
|
// nextTick(() => {
|
|||
|
|
// conversationRef.value?.scrollTo({
|
|||
|
|
// top: conversationRef.value.scrollHeight
|
|||
|
|
// });
|
|||
|
|
// });
|
|||
|
|
// }
|
|||
|
|
// }, 1000);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 暴露给父组件的方法
|
|||
|
|
defineExpose({
|
|||
|
|
open // 通过ref调用open方法传递resultId
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 关闭弹框
|
|||
|
|
const handleClose = () => {
|
|||
|
|
messages.value = [];
|
|||
|
|
history.value = [];
|
|||
|
|
result_id.value = null;
|
|||
|
|
inputValue.value = '';
|
|||
|
|
visible.value = false;
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
.log-content {
|
|||
|
|
padding: 24px;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
white-space: pre-wrap; // 保留日志换行
|
|||
|
|
word-break: break-all; // 自动换行长单词
|
|||
|
|
min-height: 200px;
|
|||
|
|
|
|||
|
|
p {
|
|||
|
|
margin: 0;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.conversation-area,
|
|||
|
|
.welcome-page {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
overflow: auto;
|
|||
|
|
padding: 0 12px;
|
|||
|
|
min-height: 400px;
|
|||
|
|
max-height: 600px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.conversation-area {
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.welcome-message {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
height: 100%;
|
|||
|
|
padding: 32px;
|
|||
|
|
text-align: center;
|
|||
|
|
|
|||
|
|
.welcome-icon {
|
|||
|
|
width: 64px;
|
|||
|
|
height: 64px;
|
|||
|
|
background-color: #e6f4ff;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
|
|||
|
|
i {
|
|||
|
|
font-size: 28px;
|
|||
|
|
color: #1677ff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.welcome-text {
|
|||
|
|
h3 {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #333;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
p {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.home-title {
|
|||
|
|
display: flex;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 20px;
|
|||
|
|
h1 {
|
|||
|
|
color: #191919;
|
|||
|
|
font-family: Huawei Sans;
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 32px;
|
|||
|
|
line-height: 48px;
|
|||
|
|
letter-spacing: 0px;
|
|||
|
|
text-align: center;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
img {
|
|||
|
|
width: 64px;
|
|||
|
|
//filter: brightness(0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.home-detail {
|
|||
|
|
.home-detail-1 {
|
|||
|
|
color: #191919;
|
|||
|
|
font-family: HarmonyOS Sans SC;
|
|||
|
|
font-weight: medium;
|
|||
|
|
font-size: 24px;
|
|||
|
|
line-height: 32px;
|
|||
|
|
letter-spacing: 0px;
|
|||
|
|
text-align: center;
|
|||
|
|
margin-bottom: 24px;
|
|||
|
|
}
|
|||
|
|
.home-detail-2 {
|
|||
|
|
color: #777777;
|
|||
|
|
font-family: HarmonyOS Sans SC;
|
|||
|
|
font-weight: regular;
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 22px;
|
|||
|
|
letter-spacing: 0px;
|
|||
|
|
text-align: center;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.welcome-page {
|
|||
|
|
//height: 90%;
|
|||
|
|
width: 100%;
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
overflow: auto;
|
|||
|
|
padding: 0 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.welcome-page {
|
|||
|
|
gap: 16px;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
</style>
|