[全量] 初始化项目代码、配置、文档及Agent协同harness

This commit is contained in:
2026-04-02 11:36:05 +08:00
parent 0553309cdf
commit 87e571d9ec
1133 changed files with 221948 additions and 0 deletions

View File

@@ -0,0 +1,571 @@
<template>
<div style="height: 100%">
<!-- 对话内容-->
<div class="message-content" ref="messageContent">
<ReadingCreateMessage id="readingMessageContent" v-for="(item, index) in chatInfo" :key="index" :chatInfo="item" :index="index"
@removeIndex="removeIndex(index)" @refresh="refresh"/>
</div>
<!-- 文字窗口-->
<div>
<div class="tool-bar">
<div class="label">
<!-- <img src="../assets/images/writing/start.png">
<div>AI写作助手</div>-->
</div>
<div class="clean" @click="cleanChat">
<img src="../assets/images/writing/brush.png">
<div>清除对话</div>
</div>
</div>
<div class="text-box">
<div class="quote-box" v-if="quoteMsg">
<div class="vertical-line"></div>
<div class="quote-info" :title="quoteMsg">{{ quoteMsg }}</div>
<div class="quote-close" @click="clearQuote"><img src="@/assets/images/close.png" alt=""></div>
</div>
<textarea
v-model.trim="textarea"
class="box-textarea"
:class="quoteMsg ? 'quote-textarea' : ''"
type="textarea"
maxlength="1000"
@input="handleInput"
@keydown.enter="keyDown"
placeholder="请输入你想提的问题字数不能超过1000字"/>
<div>
<img v-if="textarea&&!sendStatus" style="width: 38px" src="../assets/images/writing/send-blue.png" @click="send('','0')">
<img v-if="!textarea&&!sendStatus" src="../assets/images/writing/send-gray.png">
<img v-if="sendStatus" src="../assets/images/chat/stopChat.png" @click="handleStop"></img>
</div>
</div>
<div class="bottom">以上内容均由AI生成, 仅供参考和借鉴</div>
</div>
</div>
</template>
<script setup lang='ts'>
import {inject, onMounted, reactive, ref} from "vue";
import {
deleteChatMessageById,
delHistoryChatById,
fetchChatAPI,
fetchConversationId,
interrupt,
listChatMessage,
listFileChatHistory
} from "@/api";
import {generateAuthInfo} from "@/utils/request/headerUtils";
import ReadingCreateMessage from "@/components/ReadingCreateMessage.vue";
import {withLoading} from "@/utils/loading";
import {ElMessage} from "element-plus";
import {debounce} from "@/utils";
// 引用内容
const quoteMsg = inject("quoteMsg");
const updateQuoteMsg = inject("updateQuoteMsg");
/**
* 清除引用信息
*/
const clearQuote = () => {
updateQuoteMsg('');
}
//const title = inject('aiboxTitle');
const textarea = ref("");
const firstChat = ref(true);
const sendStatus = ref(false);
const chatStatus = ref(false);
const chatNumber = ref(0);
const chatId = ref('');
const historyParam = history.state;
const conversationId = ref('');
const chatInfo = reactive([]);
let controller = null;
const baseURL = import.meta.env.VITE_GLOB_API_CTX;
const cleanChat = async () => {
if(chatId.value){
try {
withLoading(await delHistoryChatById(chatId.value));
chatInfo.length = 0;
chatId.value = '';
firstChat.value = true;
} catch (error: any) {
ElMessage.error(error && error.message ? error.message : '未知错误');
}
}
}
const removeIndex = async (index) => {
try {
await deleteChatMessageById(chatInfo[index].user.conversationId);
chatInfo.splice(index, 1);
} catch (error: any) {
ElMessage.error(error && error.message ? error.message : '未知错误');
}
};
const readingBoxSend = (quote: string, type: string) => {
send(quote, type);
}
defineExpose({
readingBoxSend
})
let send = async (quote: string, type: string) => {
if (quote && quote.trim()) {
textarea.value = quote;
}
if (textarea.value === "" || sendStatus.value) {
return;
}
sendStatus.value = true;
//如果是第一次创建对话先获取对话的idchatNumber
if (firstChat.value) {
await getFetchChatAPI(textarea.value);
}
//获取conversationId
await getFetchConversationId(textarea.value);
chatStatus.value = true;
//流式会话
await getFetchChatAPIProcess(type);
firstChat.value ? (firstChat.value = false) : "";
sendStatus.value = false;
};
// 获取对话 chatNumber chatIdpromt是对话
const getFetchChatAPI = async (prompt) => {
const chatData = {
prompt,
type: 99,
fileId: historyParam.fileId
};
try {
const fetchChatAPIData = await fetchChatAPI(chatData);
chatNumber.value = fetchChatAPIData.data.chatNumber;
chatId.value = fetchChatAPIData.data.id;
} catch (error: any) {
ElMessage.error(error && error.message ? error.message : '未知错误');
}
};
//根据所写的问题获取conversationId
const getFetchConversationId = async (prompt) => {
const messageData = {
chatNumber: chatNumber.value,
prompt,
model: "QIANWEN",
promptName: "default",
promptTitle: "default",
promptPrompt: '请输入你想提的问题字数不能超过1000字',
chatId: chatId.value,
};
try {
const fetchConversationIdData = await fetchConversationId(messageData);
conversationId.value = fetchConversationIdData.data;
} catch (error: any) {
ElMessage.error(error && error.message ? error.message : '未知错误');
}
};
// 对话,流式对话
const getFetchChatAPIProcess = async (type: string) => {
const info = reactive({
user: {content: textarea.value, conversationId: "", quote: quoteMsg.value},
ai: {content: "", question: [], contentStatus: false},
});
textarea.value = "";
chatInfo.push(info);
autoScroll();
let aiQuestion = ``;
if (!controller) {
controller = new AbortController();
}
let headers = generateAuthInfo();
headers["Content-Type"] = "application/json";
const response = await fetch(
`${baseURL}/knowledgeChat/chatSelf?conversationId=${conversationId.value}&promptName=default?type=` + type,
{
method: "POST",
headers: headers,
signal: controller.signal,
body: JSON.stringify({
fileNames: [historyParam.embeddingId],
conversationId: conversationId.value,
promptName: "default",
knowledgeBaseIdList: [historyParam.folderId],
chatType: type,
quote: quoteMsg.value
}),
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let done = false;
let result = "";
let picTemp = false;
// 逐块读取流数据
while (!done) {
const {value, done: doneReading} = await reader.read();
done = doneReading;
result += decoder.decode(value, {stream: true});
// 分割数据并逐条展示
const lines = result.split("\n");
for (let i = 0; i < lines.length - 1; i++) {
let lineObj = JSON.parse(lines[i]);
if (
!info.user.conversationId ||
info.user.conversationId != lineObj.conversationId
) {
info.user.conversationId = lineObj.conversationId;
}
if (lineObj.text) {
let index = info.ai.content.indexOf("思考中...");
if (index != -1) {
let text = info.ai.content.substring(0, index);
info.ai.content = text;
}
if (info.ai.content.indexOf(".png)") != -1 && info.ai.content.indexOf("http") != -1) {
info.ai.content = info.ai.content.replace("(http", "http");
info.ai.content = info.ai.content.replace(".png)", ".png");
picTemp = true;
}
info.ai.content += lineObj.text;
} else if (lineObj.docs) {
info.ai.content += `\n**参考文献:**`;
for(let i=0;i<lineObj.docs.length;i++){
lineObj.docs[i]=lineObj.docs[i].replaceAll(/\(\.files.*?\)/g, "");
lineObj.docs[i]=lineObj.docs[i].replaceAll(/\(\.\/files.*?\)/g, "");
}
if(lineObj.docs.length>0){
let lineObjDocs=lineObj.docs.join("\n\n\n");
info.ai.content += '<div class="docs">'+lineObjDocs+'</div>';
}else{
info.ai.content += `<div class="docs">${lineObj.docs}</div>`;
}
}
}
// 保留最后一块数据,等待下一次读取
result = lines[lines.length - 1];
// 自动滚动到最新位置
autoPositionToBottom();
}
};
const refresh=async (content:string )=>{
//获取conversationId
await getFetchConversationId(content);
sendStatus.value = true;
chatStatus.value = true;
textarea.value=content;
//流式会话
await getFetchChatAPIProcess('0');
firstChat.value ? (firstChat.value = false) : "";
sendStatus.value = false;
}
const messageContent=ref(null);
const autoScroll=()=>{
if(messageContent.value){
setTimeout(()=>{
messageContent.value.scrollTop = messageContent.value.scrollHeight;
})
}
}
/**
* 监听滚动条
*/
// 标志位:用户是否手动滚动
const isUserScrolled = ref(false);
const handleReadingMessageContentScroll = () => {
const element = document.getElementById("readingMessageContent");
if (!element) return;
element.addEventListener("scroll", () => {
const scrollTop = element.scrollTop;
const scrollHeight = element.scrollHeight;
const clientHeight = element.clientHeight;
if (scrollTop + clientHeight < scrollHeight - 55) {
isUserScrolled.value = true; // 用户手动滚动
} else {
isUserScrolled.value = false; // 用户滚动到最下方
}
});
}
// 滚动条自定位
const autoPositionToBottom = () => {
const element = document.getElementById("readingMessageContent");
if (!element || isUserScrolled.value) return;
element.scrollTop = element.scrollHeight + 55;
}
onMounted(async () => {
//请求文件对话的dataId
try {
let hisResponse = await listFileChatHistory(historyParam.fileId);
//根据dataId请求获取历史记录
if (hisResponse.code == 200 && hisResponse.data) {
chatNumber.value = hisResponse.data;
firstChat.value = false;
//请求文件对话的dataId
let historyList = await listChatMessage({
chatNumber: chatNumber.value,
fileType: 1
});
let chatInfoBack = historyList.data;
chatId.value = chatInfoBack[0].chatId;
for (let i = 0; i < chatInfoBack.length;) {
let index = chatInfoBack[i + 1].content.indexOf("**参考文献:**");
let aiStrDoc = "";
let aisStr = "";
if (
chatInfoBack[i + 1].content &&
chatInfoBack[i + 1].content.indexOf("**参考文献:**") != -1
) {
aiStrDoc = chatInfoBack[i + 1].content.substring(index + 9);
aiStrDoc=aiStrDoc.replaceAll(/\(\.files.*?\)/g, "");
aiStrDoc=aiStrDoc.replaceAll(/\(\.\/files.*?\)/g, "");
aisStr =
chatInfoBack[i + 1].content.substring(0, index + 9) +
`<div class="docs">\n${aiStrDoc}</div>`;
} else {
aisStr = chatInfoBack[i + 1].content;
}
let info = {
user: {
content: chatInfoBack[i].content,
conversationId: chatInfoBack[i].messageId,
},
ai: {content: aisStr},
};
chatInfo.push(info);
i = i + 2;
}
}
} catch (error: any) {
ElMessage.error(error && error.message ? error.message : '未知错误');
}
handleReadingMessageContentScroll();
})
let messageInstance = null;
// 自定义的错误消息显示函数
const customErrorMessage = (options) => {
if (messageInstance) {
messageInstance.close(); // 如果已有消息实例,则关闭它
}
messageInstance = ElMessage.error(options); // 创建新的错误消息实例
};
const handleInput = (event) => {
if (textarea.value.length >= 1000 ) {
// 显示警告框
customErrorMessage('您已达到最大字数限制,无法继续输入更多内容。');
// 强制设置textarea的内容为截断后的值
textarea.value = textarea.value.substring(0, 1000);
}
};
/**
* 输入框的回车事件
* @param e
*/
const keyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (!sendStatus.value) {
debounce(() => {
send('','0')
});
}
}
}
const handleStop = async () => {
controller.abort();
controller = null;
let messageData = {
messageId: conversationId.value
};
await interrupt(messageData);
if (
chatInfo[chatInfo.length - 1].ai.content == "" ||
chatInfo[chatInfo.length - 1].ai.content == "思考中..."
) {
chatInfo[chatInfo.length - 1].ai.content = "已停止响应";
}
sendStatus.value = false;
};
</script>
<style lang="less" scoped>
.message-content {
height: calc(100% - 255px);
overflow-y: auto;
padding: 20px;
&::-webkit-scrollbar {
width: 0;
}
}
.background-color {
width: 490px;
height: 30px;
margin-left: 20px;
background: linear-gradient(360deg, #C0D4FD 0%, rgba(199, 219, 255, 0) 100%);
border-radius: 0px 0px 0px 0px;
position: fixed;
bottom: 300px;
}
.tool-bar {
display: flex;
justify-content: space-between;
width: 505px;
padding-top: 30px;
height: 65px;
position:absolute;
bottom:275px;
background: linear-gradient( 360deg, #C0D4FD 0%, rgba(199,219,255,0) 100%);
.label {
display: flex;
justify-content: space-around;
margin-left: 20px;
img {
width: 16px;
height: 16px;
}
div {
font-family: PingFang SC, PingFang SC;
font-weight: bold;
font-size: 16px;
color: #000000;
line-height: 16px;
margin-left: 4px;
}
}
.clean {
width: 84px;
height: 24px;
background: #004EA0;
border-radius: 4px 4px 4px 4px;
display: flex;
justify-content: space-around;
cursor: pointer !important;
margin-right: 20px;
img {
width: 12px;
height: 12px;
margin-top: 6px;
}
div {
font-family: Microsoft YaHei, Microsoft YaHei;
font-weight: 400;
font-size: 12px;
color: #FAFBFF;
line-height: 24px;
}
}
}
.text-box {
//width: 490px;
height: 190px;
background: #FFFFFF;
border-radius: 8px;
border: 1px solid #D5DDFF;
margin: 12px 20px 12px 20px;
.box-textarea {
outline: none;
border: none;
resize: none;
width: 100%;
height: calc(100% - 54px);
padding: 16px;
line-height: 24px;
border-radius: 8px;
}
img {
cursor:pointer;
float: right;
margin-right: 16px;
}
.quote-box {
display: flex;
padding: 16px;
align-items: center;
.vertical-line {
width: 2px;
height: 16px;
background: #004EA0;
border-radius: 1px 1px 1px 1px;
opacity: 0.4;
}
.quote-info {
width: calc(100% - 14px);
height: 20px;
font-size: 14px;
color: #004EA0;
line-height: 20px;
margin-left: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.quote-close {
height: 16px;
img {
width: 14px;
margin: auto;
float: none;
cursor: pointer;
}
}
}
.quote-textarea {
padding: 0 16px 16px 16px;
height: calc(100% - 106px);
}
}
.bottom {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #858A94;
line-height: 30px;
float: right;
margin-right: 16px;
}
</style>