feat: complete the qa/chat interface

This commit is contained in:
cheng 2024-03-15 21:19:57 +08:00
parent a3887dde7a
commit 8c26178240
6 changed files with 272 additions and 9 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/chat_background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1 +1,38 @@
// import { http } from "@/utils/http";
import { http } from "@/utils/http";
export type ChatResult = {
status: string;
chat_history: Array<{
role: string;
content: string;
}>;
content: string;
};
export type ModelActionResult = {
status: string;
message: string;
};
/** 发起聊天请求 */
export const chat = (data?: object) => {
return http.request<ChatResult>("post", "http://127.0.0.1:5005/qa/chat", {
data
});
};
/** 创建模型 */
export const createModel = () => {
return http.request<ModelActionResult>(
"post",
"http://127.0.0.1:5005/qa/create_model"
);
};
/** 终止模型 */
export const terminateModel = () => {
return http.request<ModelActionResult>(
"post",
"http://127.0.0.1:5005/qa/terminate_model"
);
};

View File

@ -1,7 +1,7 @@
export default {
path: "/qa",
redirect: "/qa/chat",
component: () => import("@/views/qa/index.vue"),
component: () => import("@/views/qa/chat/index.vue"),
meta: {
icon: "lollipop",
title: "问答系统",

View File

@ -17,7 +17,7 @@ import { useUserStoreHook } from "@/store/modules/user";
// 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
// 请求超时时间
timeout: 10000,
timeout: 60000,
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",

View File

@ -1,9 +1,235 @@
<template>
<div class="qa-chat">
<div class="chat-container">
<div
class="messages"
ref="messagesContainer"
:style="
chatHistory.length === 0
? { backgroundImage: 'url(/chat_background-removebg-preview.png)' }
: {}
"
>
<div
v-for="(message, index) in chatHistory"
:key="index"
class="message"
:class="{
'user-message': message.type === 'user',
'system-message': message.type === 'system'
}"
>
<strong>{{ message.type.toUpperCase() }}:</strong>
{{ message.content }}
</div>
</div>
<div class="input-container">
<button
:disabled="modelLoading"
:class="{ 'model-button': true, 'model-active': modelActive }"
@click="toggleModel"
>
<span v-if="modelLoading" class="loading-spinner" />
<span v-else>{{ modelActive ? "关闭模型" : "创建模型" }}</span>
</button>
<textarea
ref="textareaRef"
v-model="userInput"
@input="adjustTextareaHeight"
@keyup.enter="sendMessage"
placeholder="输入问题..."
/>
<div class="button-container">
<button @click="sendMessage">上传</button>
<div v-if="isLoading" class="loading-spinner" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "QAchat"
});
import { ref, nextTick } from "vue";
import { chat, createModel, terminateModel } from "@/api/qa";
const userInput = ref("");
const chatHistory = ref([]);
const messagesContainer = ref(null);
const textareaRef = ref(null);
const isLoading = ref(false);
const modelActive = ref(false);
const modelLoading = ref(false);
const sendMessage = async () => {
if (userInput.value.trim() === "") return;
chatHistory.value.push({ content: userInput.value, type: "user" });
isLoading.value = true;
try {
const response = await chat({ prompt: userInput.value });
chatHistory.value.push({ content: response.content, type: "system" });
await nextTick();
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
} catch (error) {
console.error("Error sending message:", error);
} finally {
isLoading.value = false;
}
userInput.value = "";
textareaRef.value.style.height = "auto";
};
const adjustTextareaHeight = () => {
textareaRef.value.style.height = "auto";
textareaRef.value.style.height =
Math.min(textareaRef.value.scrollHeight, 3 * 24) + "px";
};
const toggleModel = async () => {
modelLoading.value = true;
try {
if (modelActive.value) {
await terminateModel();
modelActive.value = false;
} else {
await createModel();
modelActive.value = true;
}
} catch (error) {
alert(`操作失败: ${error.message}`);
} finally {
modelLoading.value = false;
}
};
</script>
<template>
<h1>问答系统</h1>
</template>
<style scoped>
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.qa-chat {
display: flex;
flex-direction: column;
height: 100%;
}
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
padding-bottom: 60px; /* 调整这个值以适应输入框的高度 */
}
/* .messages {
flex-grow: 1;
overflow-y: auto;
padding: 10px;
} */
.messages {
flex-grow: 1;
height: calc(100vh - 200px); /* 根据实际情况调整 */
padding: 10px;
overflow-y: auto;
background-repeat: no-repeat; /* 防止背景图片重复 */
background-position: center; /* 将背景图片居中 */
background-size: 36%; /* 将背景图片缩小到原来的50% */
}
.message {
padding: 5px;
margin-bottom: 10px;
word-wrap: break-word;
border-radius: 5px;
}
.user-message {
align-self: flex-end;
background-color: #f0f0f0;
}
.system-message {
align-self: flex-start;
background-color: #e0e0e0;
}
.input-container {
position: absolute;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
width: 100%;
padding: 10px;
background-color: white;
box-shadow: 0 -2px 5px rgb(0 0 0 / 10%);
}
textarea {
width: 60%;
padding: 5px 10px;
margin-right: 10px;
overflow-y: auto;
resize: none;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 5px 10px;
color: white;
cursor: pointer;
background-color: #007bff;
border: none;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
}
.button-container {
display: flex;
align-items: center;
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
margin-right: 5px;
border: 2px solid #f3f3f3; /* Light grey */
border-top: 2px solid #3498db; /* Blue */
border-radius: 50%;
animation: spin 2s linear infinite;
}
.model-button {
display: flex;
align-items: center;
justify-content: center;
padding: 5px 10px;
margin-right: 10px;
color: white;
cursor: pointer;
background-color: #ccc;
border: none;
border-radius: 5px;
}
.model-active {
background-color: #007bff;
}
</style>