mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-25 07:57:18 +08:00
feat: complete the qa/chat interface
This commit is contained in:
parent
a3887dde7a
commit
8c26178240
BIN
public/chat_background-removebg-preview.png
Normal file
BIN
public/chat_background-removebg-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
public/chat_background.png
Normal file
BIN
public/chat_background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -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"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
path: "/qa",
|
path: "/qa",
|
||||||
redirect: "/qa/chat",
|
redirect: "/qa/chat",
|
||||||
component: () => import("@/views/qa/index.vue"),
|
component: () => import("@/views/qa/chat/index.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
icon: "lollipop",
|
icon: "lollipop",
|
||||||
title: "问答系统",
|
title: "问答系统",
|
||||||
|
@ -17,7 +17,7 @@ import { useUserStoreHook } from "@/store/modules/user";
|
|||||||
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
||||||
const defaultConfig: AxiosRequestConfig = {
|
const defaultConfig: AxiosRequestConfig = {
|
||||||
// 请求超时时间
|
// 请求超时时间
|
||||||
timeout: 10000,
|
timeout: 60000,
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json, text/plain, */*",
|
Accept: "application/json, text/plain, */*",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -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">
|
<script setup lang="ts">
|
||||||
defineOptions({
|
import { ref, nextTick } from "vue";
|
||||||
name: "QAchat"
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<style scoped>
|
||||||
<h1>问答系统</h1>
|
@keyframes spin {
|
||||||
</template>
|
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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user