feat:增加历史消息模块
This commit is contained in:
203
src/App.vue
203
src/App.vue
@@ -1,145 +1,100 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 头部 -->
|
||||
<header class="header">食刻 AI</header>
|
||||
<div class="layout">
|
||||
<!-- 移动端抽屉 -->
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
direction="ltr"
|
||||
:size="250"
|
||||
class="mobile-drawer"
|
||||
>
|
||||
<div class="sidebar"><session-message /></div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 聊天区域 -->
|
||||
<main v-if="messages.length" class="chat-box">
|
||||
<div v-for="(item, index) in messages" :key="index" class="message" :class="item.role">
|
||||
<!-- 用户 -->
|
||||
<div v-if="item.role === 'user'" class="bubble">
|
||||
{{ item.text }}
|
||||
<el-aside v-if="!isMobile" width="250px" class="pc-aside">
|
||||
<div class="sidebar"><session-message /></div>
|
||||
</el-aside>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<el-container class="main-container">
|
||||
<el-header class="header">
|
||||
<el-icon v-if="isMobile" @click="drawerVisible = true"><Fold/></el-icon>
|
||||
<div class="website-title">
|
||||
网站标题
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<!-- AI -->
|
||||
<MdPreview v-else :modelValue="item.text" class="md"/>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
思考中…
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 输入区 -->
|
||||
<footer class="input-area">
|
||||
<el-input v-model="input" placeholder="请输入你的食材" @keyup.enter="send" clearable/>
|
||||
<el-button type="primary" :loading="loading" @click="send">发送</el-button>
|
||||
</footer>
|
||||
<el-main class="main-body">
|
||||
<agent />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
import { MdPreview } from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/preview.css';
|
||||
<script lang="ts" setup>
|
||||
import Agent from "./components/Agent.vue";
|
||||
import SessionMessage from "./components/SessionMessage.vue";
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { Fold } from '@element-plus/icons-vue'
|
||||
|
||||
interface Message {
|
||||
role: "user" | "ai";
|
||||
text: string;
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
const drawerVisible = ref(false)
|
||||
|
||||
const onResize = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
if (!isMobile.value) drawerVisible.value = false
|
||||
}
|
||||
|
||||
const input = ref("");
|
||||
const messages = ref<Message[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const send = async () => {
|
||||
if (!input.value.trim()) return;
|
||||
|
||||
const userMsg = input.value;
|
||||
input.value = "";
|
||||
|
||||
messages.value.push({ role: "user", text: userMsg });
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"/chief-agent-api/chat/stream",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: userMsg })
|
||||
}
|
||||
);
|
||||
|
||||
messages.value.push({ role: "ai", text: "" });
|
||||
const reader = response.body!.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let aiText = "";
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
aiText += decoder.decode(value, { stream: true });
|
||||
messages.value[messages.value.length - 1].text = aiText;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
onMounted(() => window.addEventListener('resize', onResize))
|
||||
onUnmounted(() => window.removeEventListener('resize', onResize))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app {
|
||||
<style lang="scss">
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 5px;
|
||||
height: 100%;
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
.pc-aside {
|
||||
background: gainsboro;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
background-color: #FBF9F5;
|
||||
.sidebar {
|
||||
padding: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
|
||||
.message {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ai {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 14px;
|
||||
max-width: 85%;
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.md {
|
||||
background: #f0f2f5;
|
||||
border-radius: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-area {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
flex-direction: column;
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
margin: 8px 0;
|
||||
.header {
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.el-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.website-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.main-body {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user