feat:更新对话框UI
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>食刻 AI</title>
|
<title>智能家庭助手</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<el-header class="header">
|
<el-header class="header">
|
||||||
<el-icon v-if="isMobile" @click="drawerVisible = true"><Fold/></el-icon>
|
<el-icon v-if="isMobile" @click="drawerVisible = true"><Fold/></el-icon>
|
||||||
<div class="website-title">
|
<div class="website-title">
|
||||||
网站标题
|
智能家庭助手
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
|
|
||||||
|
|||||||
1
src/assets/agent.svg
Normal file
1
src/assets/agent.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.0 KiB |
1
src/assets/user.svg
Normal file
1
src/assets/user.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1783005197096" class="icon" viewBox="0 0 1820 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27777" xmlns:xlink="http://www.w3.org/1999/xlink" width="113.75" height="64"><path d="M735.573333 475.685926C750.648889 558.459259 823.087407 621.226667 910.222222 621.226667c87.134815 0 159.573333-62.672593 174.648889-145.540741 26.927407-24.936296 43.899259-60.681481 43.899259-100.219259 0-23.22963-5.783704-45.131852-16.023703-64.284445 1.611852-10.24 2.37037-20.66963 2.37037-31.288889 0-113.114074-91.685926-204.8-204.8-204.8s-204.8 91.685926-204.8 204.8c0 10.619259 0.853333 21.143704 2.37037 31.288889-10.24 19.152593-16.023704 41.054815-16.023703 64.284445-0.094815 39.632593 16.782222 75.282963 43.709629 100.219259z" p-id="27778" fill="#1296db"></path><path d="M1181.487407 717.653333c-111.881481-55.751111-61.724444-60.207407-130.18074-103.158518-2.180741-1.327407-5.025185-2.085926-7.585186-2.085926-24.367407 8.628148-18.014815 19.437037-29.961481 38.494815-14.506667 33.943704-33.28 87.703704-45.795556 124.397037l-23.04-72.343704 12.515556-43.804444c1.137778-4.077037 0.379259-8.533333-2.180741-11.946667-2.56-3.413333-6.637037-5.404444-10.903703-5.404445h-66.275556c-4.171852 0-8.154074 1.896296-10.714074 5.214815-2.56 3.318519-3.508148 7.585185-2.56 11.662223l10.714074 43.804444-25.505185 72.343704c-5.025185-16.782222-11.188148-37.072593-17.635556-57.268148-7.774815-24.557037-16.023704-49.114074-23.134815-67.887408-11.377778-19.342222-3.792593-29.961481-28.823703-39.253333-2.465185 0-5.214815 0.758519-7.395556 1.991111-68.077037 40.485926-21.522963 39.253333-133.878518 104.485926-102.968889 60.112593-111.597037 155.97037-111.407408 183.940741 0 5.12 0.284444 8.059259 0.284445 8.438518 0.663704 7.016296 6.542222 12.420741 13.558518 12.420741h737.28c7.016296 0 12.98963-5.404444 13.558519-12.420741 0-0.379259 0.379259-3.982222 0.379259-10.145185 0.18963-30.72-8.533333-129.896296-111.312593-181.475556z" p-id="27779" fill="#1296db"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,79 +1,96 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
<!-- 聊天区域 -->
|
<main class="message-container" ref="msgContainer">
|
||||||
<main class="chat-container">
|
<div v-if="agentStore.chatMessages.length === 0" class="empty-state">
|
||||||
<div v-for="(item, index) in agentStore.chatMessages" :key="index" class="message" :class="item.role">
|
<el-avatar :size="72" :src="aiAvatar" class="empty-avatar" />
|
||||||
<!-- 用户 -->
|
<h2>你好,我是智能家庭助手</h2>
|
||||||
<div v-if="item.role === 'user'" class="bubble">
|
<p>有什么可以帮你的?</p>
|
||||||
{{ item.content }}
|
<div class="quick-actions">
|
||||||
|
<el-button v-for="q in quickQuestions" :key="q" type="primary" round @click="askQuick(q)">{{ q }}</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- AI -->
|
<div v-for="(item, index) in agentStore.chatMessages" :key="index" class="msg-row" :class="item.role">
|
||||||
<MdPreview v-else :modelValue="item.content" class="md"/>
|
<el-avatar :size="36" :src="item.role === 'ai' ? aiAvatar : userAvatar" class="msg-avatar"/>
|
||||||
|
|
||||||
|
<div class="msg-body">
|
||||||
|
<div class="msg-bubble" :class="item.role">
|
||||||
|
<MdPreview v-if="item.role === 'ai'" :modelValue="item.content" class="md-preview"/>
|
||||||
|
<template v-else><span class="user-text">{{ item.content }}</span></template>
|
||||||
|
</div>
|
||||||
|
<span class="msg-time">{{ item.role === 'ai' ? 'AI' : '你' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="agentStore.loading" class="loading">
|
<!-- 加载动画 -->
|
||||||
<el-icon class="is-loading"><Loading /></el-icon>
|
<div v-if="agentStore.loading" class="msg-row ai">
|
||||||
思考中…
|
<el-avatar :size="36" :src="aiAvatar" class="msg-avatar" />
|
||||||
|
<div class="msg-body">
|
||||||
|
<div class="msg-bubble ai typing">
|
||||||
|
<span class="dot" />
|
||||||
|
<span class="dot" />
|
||||||
|
<span class="dot" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 输入区 -->
|
<!-- 底部输入区 -->
|
||||||
<footer class="input-container">
|
<footer class="input-bar">
|
||||||
<el-input v-model="agentStore.input" placeholder="请输入你的食材" @keyup.enter="sendMessage" clearable/>
|
<div class="input-wrap">
|
||||||
<el-button type="primary" :loading="agentStore.loading" @click="sendMessage">发送</el-button>
|
<el-input v-model="agentStore.inputMessage" placeholder="输入你的问题…"
|
||||||
|
@keyup.enter="sendMessage" clearable :disabled="agentStore.loading"/>
|
||||||
|
<el-button type="primary" circle :loading="agentStore.loading"
|
||||||
|
:disabled="!agentStore.inputMessage.trim()" @click="sendMessage">
|
||||||
|
<template #icon>
|
||||||
|
<el-icon><Promotion /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
import { Promotion } from "@element-plus/icons-vue";
|
||||||
import { Loading } from "@element-plus/icons-vue";
|
import { MdPreview } from "md-editor-v3";
|
||||||
import { MdPreview } from 'md-editor-v3';
|
import "md-editor-v3/lib/preview.css";
|
||||||
import 'md-editor-v3/lib/preview.css';
|
|
||||||
import { useAgentStore } from "../stores/agent.ts";
|
import { useAgentStore } from "../stores/agent.ts";
|
||||||
|
import { nextTick, ref } from "vue";
|
||||||
|
import aiAvatar from '../assets/agent.svg'
|
||||||
|
import userAvatar from '../assets/user.svg'
|
||||||
|
|
||||||
const agentStore = useAgentStore();
|
const agentStore = useAgentStore();
|
||||||
|
const msgContainer = ref<HTMLElement>();
|
||||||
|
|
||||||
onMounted(() => {
|
const quickQuestions = [
|
||||||
getSessionMessage()
|
"帮我分析下这个月的账单",
|
||||||
})
|
"帮我查询下最近花费最大的一笔是什么?",
|
||||||
|
"帮我查询下今年存了多少钱?"
|
||||||
|
];
|
||||||
|
|
||||||
const getSessionMessage = async () => {
|
const scrollToBottom = () => {
|
||||||
const response = await fetch(
|
nextTick(() => {
|
||||||
"/chief-agent-api/chat/messages/session?username=Cxx0822",
|
const el = msgContainer.value;
|
||||||
{
|
if (el) el.scrollTop = el.scrollHeight;
|
||||||
method: "GET",
|
});
|
||||||
headers: { "Content-Type": "application/json" },
|
};
|
||||||
}
|
|
||||||
);
|
|
||||||
agentStore.sessionMessages = await response.json();
|
|
||||||
agentStore.threadId = crypto.randomUUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendMessage = async () => {
|
const sendMessage = async () => {
|
||||||
if (!agentStore.input.trim()) return;
|
if (!agentStore.inputMessage.trim() || agentStore.loading) return;
|
||||||
|
|
||||||
const userMsg = agentStore.input;
|
|
||||||
agentStore.input = "";
|
|
||||||
|
|
||||||
|
const userMsg = agentStore.inputMessage;
|
||||||
|
agentStore.inputMessage = "";
|
||||||
agentStore.chatMessages.push({ role: "user", content: userMsg });
|
agentStore.chatMessages.push({ role: "user", content: userMsg });
|
||||||
agentStore.loading = true;
|
agentStore.loading = true;
|
||||||
|
scrollToBottom();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
agentStore.agentMessage = {
|
const response = await fetch("/agent-api/query/bill", {
|
||||||
username: 'Cxx0822',
|
|
||||||
message: userMsg,
|
|
||||||
thread_id: agentStore.threadId,
|
|
||||||
}
|
|
||||||
const response = await fetch(
|
|
||||||
"/chief-agent-api/chat/stream",
|
|
||||||
{
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(agentStore.agentMessage)
|
body: JSON.stringify({ message: userMsg }),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
agentStore.chatMessages.push({ role: "ai", content: "" });
|
agentStore.chatMessages.push({ role: "ai", content: "" });
|
||||||
const reader = response.body!.getReader();
|
const reader = response.body!.getReader();
|
||||||
@@ -83,77 +100,169 @@ const sendMessage = async () => {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
|
||||||
aiText += decoder.decode(value, { stream: true });
|
aiText += decoder.decode(value, { stream: true });
|
||||||
agentStore.chatMessages[agentStore.chatMessages.length - 1].content = aiText;
|
agentStore.chatMessages[agentStore.chatMessages.length - 1].content = aiText;
|
||||||
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
agentStore.loading = false;
|
agentStore.loading = false;
|
||||||
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const askQuick = (q: string) => {
|
||||||
|
agentStore.inputMessage = q;
|
||||||
|
sendMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearChat = () => {
|
||||||
|
agentStore.chatMessages = [];
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.agent-container {
|
.agent-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: whitesmoke;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 5px;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.message-container {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
.chat-container {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px;
|
padding: 20px 16px;
|
||||||
|
|
||||||
.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;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.quick-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.msg-row {
|
||||||
text-align: center;
|
display: flex;
|
||||||
color: #909399;
|
gap: 10px;
|
||||||
|
align-items: flex-start;
|
||||||
|
max-width: 85%;
|
||||||
|
|
||||||
|
.msg-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.msg-bubble {
|
||||||
|
padding: 10px 14px;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-word;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
.md-preview {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 8px 0;
|
}
|
||||||
|
|
||||||
|
.user-text {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ai {
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
&.user {
|
||||||
|
background: linear-gradient(135deg, #409eff 0%, #337ecc 100%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #bbb;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #bbb;
|
||||||
|
animation: blink 1.2s infinite both;
|
||||||
|
&:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
&:nth-child(3) { animation-delay: 0.4s; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||||
|
40% { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.user {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-self: flex-end;
|
||||||
|
.msg-bubble {
|
||||||
|
border-radius: 18px 18px 4px 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ai {
|
||||||
|
align-self: flex-start;
|
||||||
|
.msg-bubble {
|
||||||
|
border-radius: 18px 18px 18px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-bar {
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
.input-wrap {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// src/stores/chat.ts
|
|
||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
import type {IAgentMessage, IChatMessage, ISessionMessage} from "../types/agent.ts";
|
import type {IAgentMessage, IChatMessage, ISessionMessage} from "../types/agent.ts";
|
||||||
|
|
||||||
@@ -8,8 +7,10 @@ export const useAgentStore = defineStore('agent', {
|
|||||||
chatMessages: [] as IChatMessage[],
|
chatMessages: [] as IChatMessage[],
|
||||||
sessionMessages: [] as ISessionMessage[],
|
sessionMessages: [] as ISessionMessage[],
|
||||||
agentMessage: {} as IAgentMessage,
|
agentMessage: {} as IAgentMessage,
|
||||||
input: '',
|
inputMessage: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
}),
|
}),
|
||||||
actions: {}
|
actions: {
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
@@ -8,10 +8,10 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
// 服务器代理 服务器没有部署Nginx的情况下使用
|
// 服务器代理 服务器没有部署Nginx的情况下使用
|
||||||
proxy: {
|
proxy: {
|
||||||
'/chief-agent-api': {
|
'/agent-api': {
|
||||||
target: `http://60.247.145.200:8300`,
|
target: `http://192.168.1.7:8000`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (item) => item.replace(/^\/chief-agent-api/, '')
|
rewrite: (item) => item.replace(/^\/agent-api/, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user