feat:增加历史消息模块
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"element-plus": "^2.9.7",
|
||||
"md-editor-v3": "^6.4.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
99
pnpm-lock.yaml
generated
99
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
||||
md-editor-v3:
|
||||
specifier: ^6.4.0
|
||||
version: 6.5.1(vue@3.5.35(typescript@6.0.3))
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
|
||||
vue:
|
||||
specifier: ^3.5.34
|
||||
version: 3.5.35(typescript@6.0.3)
|
||||
@@ -494,6 +497,15 @@ packages:
|
||||
'@vue/compiler-ssr@3.5.35':
|
||||
resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==}
|
||||
|
||||
'@vue/devtools-api@7.7.9':
|
||||
resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
|
||||
|
||||
'@vue/devtools-kit@7.7.9':
|
||||
resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
|
||||
|
||||
'@vue/devtools-shared@7.7.9':
|
||||
resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
|
||||
|
||||
'@vue/language-core@3.3.2':
|
||||
resolution: {integrity: sha512-CLwjSfHlPLhjd2qhuS3tTFtnOIWHXAM5u4X1DxmzlQ8j5bmOYlKCsSusOP7jCRJnlVg0mCTQtHU3vwFvopZGoQ==}
|
||||
|
||||
@@ -547,6 +559,9 @@ packages:
|
||||
async-validator@4.2.5:
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||
|
||||
birpc@2.9.0:
|
||||
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
@@ -557,6 +572,10 @@ packages:
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
copy-anything@4.0.5:
|
||||
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
@@ -603,6 +622,9 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
immutable@5.1.5:
|
||||
resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==}
|
||||
|
||||
@@ -614,6 +636,10 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-what@5.5.0:
|
||||
resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -737,6 +763,9 @@ packages:
|
||||
memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
muggle-string@0.4.1:
|
||||
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||
|
||||
@@ -754,6 +783,9 @@ packages:
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
perfect-debounce@1.0.0:
|
||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -761,6 +793,15 @@ packages:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pinia@3.0.4:
|
||||
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
|
||||
peerDependencies:
|
||||
typescript: '>=4.5.0'
|
||||
vue: ^3.5.11
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
postcss@8.5.15:
|
||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -773,6 +814,9 @@ packages:
|
||||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rolldown@1.0.2:
|
||||
resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -787,9 +831,17 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
speakingurl@14.0.1:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
style-mod@4.1.3:
|
||||
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
|
||||
|
||||
superjson@2.2.6:
|
||||
resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
tinyglobby@0.2.16:
|
||||
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1480,6 +1532,24 @@ snapshots:
|
||||
'@vue/compiler-dom': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
|
||||
'@vue/devtools-api@7.7.9':
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.9
|
||||
|
||||
'@vue/devtools-kit@7.7.9':
|
||||
dependencies:
|
||||
'@vue/devtools-shared': 7.7.9
|
||||
birpc: 2.9.0
|
||||
hookable: 5.5.3
|
||||
mitt: 3.0.1
|
||||
perfect-debounce: 1.0.0
|
||||
speakingurl: 14.0.1
|
||||
superjson: 2.2.6
|
||||
|
||||
'@vue/devtools-shared@7.7.9':
|
||||
dependencies:
|
||||
rfdc: 1.4.1
|
||||
|
||||
'@vue/language-core@3.3.2':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.28
|
||||
@@ -1538,6 +1608,8 @@ snapshots:
|
||||
|
||||
async-validator@4.2.5: {}
|
||||
|
||||
birpc@2.9.0: {}
|
||||
|
||||
chokidar@5.0.0:
|
||||
dependencies:
|
||||
readdirp: 5.0.0
|
||||
@@ -1554,6 +1626,10 @@ snapshots:
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
copy-anything@4.0.5:
|
||||
dependencies:
|
||||
is-what: 5.5.0
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cssfilter@0.0.10: {}
|
||||
@@ -1596,6 +1672,8 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
immutable@5.1.5: {}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
@@ -1606,6 +1684,8 @@ snapshots:
|
||||
is-extglob: 2.1.1
|
||||
optional: true
|
||||
|
||||
is-what@5.5.0: {}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
@@ -1720,6 +1800,8 @@ snapshots:
|
||||
|
||||
memoize-one@6.0.0: {}
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
muggle-string@0.4.1: {}
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
@@ -1731,10 +1813,19 @@ snapshots:
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
perfect-debounce@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 7.7.9
|
||||
vue: 3.5.35(typescript@6.0.3)
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
postcss@8.5.15:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
@@ -1745,6 +1836,8 @@ snapshots:
|
||||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rolldown@1.0.2:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.132.0
|
||||
@@ -1776,8 +1869,14 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
style-mod@4.1.3: {}
|
||||
|
||||
superjson@2.2.6:
|
||||
dependencies:
|
||||
copy-anything: 4.0.5
|
||||
|
||||
tinyglobby@0.2.16:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
|
||||
195
src/App.vue
195
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;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
background-color: #FBF9F5;
|
||||
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;
|
||||
.pc-aside {
|
||||
background: gainsboro;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.md {
|
||||
background: #f0f2f5;
|
||||
border-radius: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
.sidebar {
|
||||
padding: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
.main-container {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
margin: 8px 0;
|
||||
.website-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.main-body {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
159
src/components/Agent.vue
Normal file
159
src/components/Agent.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="agent-container">
|
||||
<!-- 聊天区域 -->
|
||||
<main class="chat-container">
|
||||
<div v-for="(item, index) in agentStore.chatMessages" :key="index" class="message" :class="item.role">
|
||||
<!-- 用户 -->
|
||||
<div v-if="item.role === 'user'" class="bubble">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
|
||||
<!-- AI -->
|
||||
<MdPreview v-else :modelValue="item.content" class="md"/>
|
||||
</div>
|
||||
|
||||
<div v-if="agentStore.loading" class="loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
思考中…
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 输入区 -->
|
||||
<footer class="input-container">
|
||||
<el-input v-model="agentStore.input" placeholder="请输入你的食材" @keyup.enter="sendMessage" clearable/>
|
||||
<el-button type="primary" :loading="agentStore.loading" @click="sendMessage">发送</el-button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
import { MdPreview } from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/preview.css';
|
||||
import { useAgentStore } from "../stores/agent.ts";
|
||||
|
||||
const agentStore = useAgentStore();
|
||||
|
||||
onMounted(() => {
|
||||
getSessionMessage()
|
||||
})
|
||||
|
||||
const getSessionMessage = async () => {
|
||||
const response = await fetch(
|
||||
"/chief-agent-api/chat/messages/session?username=Cxx0822",
|
||||
{
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
agentStore.sessionMessages = await response.json();
|
||||
agentStore.threadId = crypto.randomUUID()
|
||||
}
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!agentStore.input.trim()) return;
|
||||
|
||||
const userMsg = agentStore.input;
|
||||
agentStore.input = "";
|
||||
|
||||
agentStore.chatMessages.push({ role: "user", content: userMsg });
|
||||
agentStore.loading = true;
|
||||
|
||||
try {
|
||||
agentStore.agentMessage = {
|
||||
username: 'Cxx0822',
|
||||
message: userMsg,
|
||||
thread_id: agentStore.threadId,
|
||||
}
|
||||
const response = await fetch(
|
||||
"/chief-agent-api/chat/stream",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(agentStore.agentMessage)
|
||||
}
|
||||
);
|
||||
|
||||
agentStore.chatMessages.push({ role: "ai", content: "" });
|
||||
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 });
|
||||
agentStore.chatMessages[agentStore.chatMessages.length - 1].content = aiText;
|
||||
}
|
||||
} finally {
|
||||
agentStore.loading = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.agent-container {
|
||||
height: 100%;
|
||||
background-color: whitesmoke;
|
||||
border-radius: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 5px;
|
||||
|
||||
.chat-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-container {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
src/components/SessionMessage.vue
Normal file
49
src/components/SessionMessage.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="session-message-container">
|
||||
<el-scrollbar>
|
||||
<div
|
||||
v-for="item in agentStore.sessionMessages"
|
||||
:key="item.thread_id"
|
||||
class="scrollbar-demo-item"
|
||||
@click="viewMessage(item)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAgentStore } from "../stores/agent.ts";
|
||||
import type {ISessionMessage} from "../types/agent.ts";
|
||||
|
||||
const agentStore = useAgentStore();
|
||||
|
||||
const viewMessage = async (session: ISessionMessage) => {
|
||||
const response = await fetch(
|
||||
`/chief-agent-api/chat/messages?thread_id=${session.thread_id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
}
|
||||
);
|
||||
agentStore.chatMessages = await response.json();
|
||||
agentStore.threadId = session.thread_id
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.scrollbar-demo-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-bottom: 10px;
|
||||
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
background: darkgray;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
|
||||
// 引入element-ui组件
|
||||
@@ -8,8 +9,11 @@ import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
|
||||
import './style.css'
|
||||
|
||||
// 创建Vue3实例
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
// 使用Element UI Plus
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
// 挂载到根组件上
|
||||
|
||||
15
src/stores/agent.ts
Normal file
15
src/stores/agent.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// src/stores/chat.ts
|
||||
import {defineStore} from 'pinia'
|
||||
import type {IAgentMessage, IChatMessage, ISessionMessage} from "../types/agent.ts";
|
||||
|
||||
export const useAgentStore = defineStore('agent', {
|
||||
state: () => ({
|
||||
threadId: '0',
|
||||
chatMessages: [] as IChatMessage[],
|
||||
sessionMessages: [] as ISessionMessage[],
|
||||
agentMessage: {} as IAgentMessage,
|
||||
input: '',
|
||||
loading: false,
|
||||
}),
|
||||
actions: {}
|
||||
})
|
||||
@@ -28,18 +28,7 @@ a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
html {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
16
src/types/agent.ts
Normal file
16
src/types/agent.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface IChatMessage {
|
||||
role: "user" | "ai";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface IAgentMessage {
|
||||
username: string;
|
||||
message: string;
|
||||
thread_id: string;
|
||||
}
|
||||
|
||||
export interface ISessionMessage {
|
||||
username: string;
|
||||
title: string;
|
||||
thread_id: string;
|
||||
}
|
||||
Reference in New Issue
Block a user