6 Commits

Author SHA1 Message Date
zoujing
011c28d945 feat: 样式调整 2026-02-04 09:59:05 +08:00
zoujing
ff5355855f feat: 历史消息的加载 2026-02-03 16:34:16 +08:00
zoujing
aff9233ce2 feat: 对话的调试调整 2026-02-03 14:38:36 +08:00
d566344eb8 feat: 样式调整 2026-01-22 18:00:15 +08:00
236abba8d0 feat: 聊天界面的调整与交互处理 2026-01-22 01:04:06 +08:00
47a361e78b feat: 调整 2026-01-22 00:08:44 +08:00
12 changed files with 403 additions and 263 deletions

View File

@@ -0,0 +1,64 @@
/* eslint-disable */
// @ts-ignore
import { getRequest, postRequest, ResponseModel } from '@utils/request'
/** 创建会话 创建会话创建会话 GET /agent/assistant/createConversation */
export interface CreateConversationResponse {
conversationId: string
}
export const createConversation = async () => {
const res: ResponseModel = await getRequest('/agent/assistant/createConversation')
return res.data as CreateConversationResponse
}
/** 获取会话列表 获取会话列表获取会话列表 POST /agent/assistant/conversationList */
export interface ConversationListRequest {
pageSize: number
pageNum: number
conversationId?: string
}
export interface ConversationListResponse {
records: Array<ConversationListRecords>
total: number
size: number
current: number
optimizeCountSql: boolean
searchCount: boolean
}
export interface ConversationListRecords {
conversationTitle: string
conversationId: string
}
export interface ConversationMessageListResponse {
records: Array<ConversationMessageListRecords>
total: number
size: number
current: number
optimizeCountSql: boolean
searchCount: boolean
}
export interface ConversationMessageListRecords {
messageId: string
conversationId: string
messageType: string
messageContent: string
messageDisplay: string
messageSenderId: string
messageSenderRole: string
messageTime: string
}
export const getConversationList = async (params: ConversationListRequest) => {
const res: ResponseModel = await postRequest('/agent/assistant/conversationList', params)
return res.data as ConversationListResponse
}
export const conversationMessageList = async (params: ConversationListRequest) => {
const res: ResponseModel = await postRequest('/agent/assistant/conversationMessageList', params)
return res.data as ConversationMessageListResponse
}

View File

@@ -1,5 +1,5 @@
<template>
<header class="title-bar flex items-start justify-between h-[40px]">
<header class="flex items-start justify-between h-[40px]">
<div class="title-bar-main flex-auto">
<slot>{{ title ?? '' }}</slot>
</div>
@@ -69,8 +69,4 @@ function handleClose() {
}
</script>
<style scoped>
.title-bar {
background-color: rgba(239, 246, 255, 0.8);
}
</style>
<style scoped></style>

View File

@@ -1,10 +1,10 @@
<template>
<div class="bg h-screen flex flex-col">
<div class="bg-color h-screen flex flex-col">
<header-bar>
<drag-region class="w-full" />
</header-bar>
<main class="bg-[#f7f9fc] box-border w-full h-[calc(100vh-40px)] flex pt-[8px] pb-[8px] pl-[8px] ">
<main class="box-border w-full h-[calc(100vh-40px)] flex pt-[8px] pb-[8px] pl-[8px] ">
<div class="flex-1 flex">
<slot />
</div>
@@ -17,3 +17,8 @@
<script setup lang="ts" name="Layout">
import SideMenus from '@renderer/components/SideMenus/index.vue'
</script>
<style scoped>
.bg-color {
background: linear-gradient(180deg, #EFF6FF 0%, #F5F7FA 40%);
}
</style>

View File

@@ -1,7 +1,7 @@
import axios from 'axios'
import cache from '@utils/cache'
import errorCode from '@constant/errorCode'
import { ElNotification , ElMessageBox, ElMessage } from 'element-plus'
import { ElNotification, ElMessageBox, ElMessage } from 'element-plus'
import { Session } from '@renderer/utils/storage'
import { tansParams } from '@utils/tansParams'
@@ -87,6 +87,7 @@ instance.interceptors.request.use(
// 添加响应拦截器
instance.interceptors.response.use(
(res) => {
console.log(`🚀 ~ response: \n url:${res.config.url} \n params:${JSON.stringify(res.config.params)} \n data:\n ${JSON.stringify(res.data)}`)
// 未设置状态码则默认成功状态
const code = res.data.code || 200
// 获取错误信息
@@ -138,4 +139,34 @@ instance.interceptors.response.use(
}
)
// 封装基于 request 的 POST 请求(
export const postRequest = <ResponseModel>(url: string, data?: any, options?: any): Promise<ResponseModel> => {
return instance.request({
url,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data,
...(options || {}),
}) as Promise<ResponseModel>
}
// 封装基于 request 的 GET 请求
export const getRequest = <ResponseModel>(url: string, params?: any, options?: any): Promise<ResponseModel> => {
return instance.request({
url,
method: 'GET',
params,
...(options || {}),
}) as Promise<ResponseModel>
}
export default instance
/// 响应模型
export interface ResponseModel {
code: number;
msg: string | null;
data: any | null;
};

View File

@@ -1,9 +1,18 @@
<template>
<!-- 页面根 -->
<div class="h-full overflow-hidden flex flex-col">
<div class="flex flex-col h-full py-6 px-6" :class="isGuidePage ? 'overflow-auto' : 'overflow-hidden'">
<!-- 消息列表唯一滚动区 -->
<div ref="listRef" class="flex-1 overflow-y-auto px-6 py-6 space-y-6">
<!-- 引导页顶部 welcome仅在引导页显示 -->
<div v-if="isGuidePage" class="border-box pt-30">
<h1 class="text-[28px] font-bold mb-7 leading-tight">
你好<br />
我今天能帮你什么
</h1>
</div>
<!-- 主体滚动区聊天或引导页内容 -->
<div v-if="!isGuidePage" ref="listRef" class="flex-1 overflow-y-auto py-6 space-y-6">
<!-- 聊天消息列表 -->
<div v-for="msg in chatMsgList" :key="msg.messageId" class="flex items-start gap-3"
:class="msg.messageRole === MessageRole.ME ? 'justify-end' : 'justify-start'">
@@ -11,7 +20,7 @@
<ChatAvatar v-if="msg.messageRole === MessageRole.AI" :src="aiAvatar" />
<!-- 自己 发的消息 -->
<ChatRoleMe v-if="msg.messageRole === MessageRole.ME" :msg="msg" >
<ChatRoleMe v-if="msg.messageRole === MessageRole.ME" :msg="msg">
<template #header>
<!-- 名字和时间 -->
<ChatNameTime :showReverse="true" />
@@ -42,40 +51,26 @@
</div>
</div>
<!-- 输入区固定底部不滚 -->
<div class="shrink-0 px-6 py-4 gap-3">
<!-- 输入区 -->
<div class="flex flex-col gap-3" :class="isGuidePage ? 'mt-16' : 'mt-4'">
<div class="inline-flex items-center justify-center w-[108px]
px-3 py-1.5 rounded-2xl border border-[#E5E8EE]
text-[13px] text-[#333]">
智能问数
</div>
<div class="h-[174px] bg-white rounded-lg border border-[#eef2f6]
shadow-[0_1px_0_rgba(0,0,0,0.03)]
p-[18px] mt-[8px] flex flex-col justify-between">
<textarea rows="2" placeholder="给我发布或者布置任务" class="flex-1 resize-none outline-none text-sm"
v-model="inputMessage" @keydown.enter="handleKeydownEnter" />
<div class="flex justify-between items-end">
<button @click="addAttachmentAction()">
<RiLink />
</button>
<button class="w-[48px] h-[48px] bg-[#F5F7FA] px-2.5 py-1.5 rounded-md flex items-center justify-center"
@click="sendMessageAction()">
<RiStopFill v-if="isSendingMessage" />
<RiSendPlaneFill v-else />
</button>
</div>
</div>
<ChatInputArea v-model="inputMessage" :isSendingMessage="isSendingMessage" @send="onGuideSend"
@attach="addAttachmentAction" />
</div>
<!-- 任务中心仅在引导页显示 -->
<TaskCenter v-if="isGuidePage" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { RiLink, RiSendPlaneFill, RiStopFill } from '@remixicon/vue'
import { onMounted, nextTick, onUnmounted } from "vue";
import { ref, defineProps, defineEmits, watch, nextTick } from 'vue'
import { onMounted, onUnmounted } from "vue";
import { WebSocketManager } from "@common/WebSocketManager";
import { MessageRole, ChatMessage } from "./model/ChatModel";
import { IdUtils } from "@common/index";
@@ -86,11 +81,39 @@ import ChatRoleMe from './components/ChatRoleMe.vue';
import ChatAIMark from './components/ChatAIMark.vue';
import ChatNameTime from './components/ChatNameTime.vue';
import ChatAttach from './components/ChatAttach.vue';
import ChatInputArea from './components/ChatInputArea.vue';
import TaskCenter from './TaskCenter.vue';
import { Session } from '../../utils/storage';
import userAvatar from '@assets/images/login/user_icon.png';
import aiAvatar from '@assets/images/login/blue_logo.png';
import { createConversation, conversationMessageList } from '../../api/ConversationApi';
import { ElMessage, ElLoading } from 'element-plus'
// 支持外部通过 prop 控制是否为引导页
const props = defineProps({
guide: { type: Boolean, default: true },
conversationId: { type: String, default: '' }
});
const emit = defineEmits(['update:guide']);
/// 是否是引导页(内部响应式)
const isGuidePage = ref(props.guide);
// 同步外部变化到内部
watch(() => props.guide, (v) => {
isGuidePage.value = v;
});
// 将内部变化通知父组件
watch(isGuidePage, (v) => {
emit('update:guide', v);
if (v) {
// 当切换到引导页时,重置/清理会话状态
resetConversation();
createConversationRequest();
}
});
// 列表滚动容器引用
const listRef = ref<HTMLElement | null>(null);
@@ -102,10 +125,19 @@ const inputMessage = ref("");
/// 发送消息中标志
const isSendingMessage = ref(false);
/// agentId 首页接口中获取 1953462165250859010
const agentId = ref("1");
/// agentId 首页接口中获取 1953462165250859011
const agentId = ref("1953462165250859011");
/// 会话ID 历史数据接口中获取
const conversationId = ref("");
const conversationId = ref(props.conversationId);
// 监听 conversationId prop 变化,只有当有值时(选择历史消息)才请求消息列表
watch(() => props.conversationId, (newId) => {
if (newId) {
conversationId.value = newId;
loadConversationMessages(newId);
}
});
// 会话进行中标志
const isSessionActive = ref(false);
/// 指令通用消息类型
@@ -189,9 +221,16 @@ const handleReplyInstruct = async (message: string, type: string) => {
setTimeoutScrollToBottom();
};
/// 选择标签事件
/// 选择标签事件:切换到聊天页并发送
const onTagSelect = (text: string) => {
handleReplyText(text);
isGuidePage.value = false;
nextTick(() => handleReplyText(text));
};
// 在引导页中按发送:切换到聊天页再发送
const onGuideSend = () => {
isGuidePage.value = false;
nextTick(() => sendMessageAction());
};
/// 添加附件按钮事件
@@ -217,16 +256,6 @@ const sendMessageAction = () => {
setTimeoutScrollToBottom();
};
// 处理在 textarea 中按 EnterShift+Enter 保留换行,单按 Enter 发送并阻止默认换行行为
const handleKeydownEnter = (e: KeyboardEvent) => {
if ((e as KeyboardEvent).shiftKey) {
// 允许插入换行
return;
}
e.preventDefault();
sendMessageAction();
};
// 停止发送消息事件
const sendStopAction = () => {
console.log("停止发送消息");
@@ -245,11 +274,12 @@ onMounted(() => {
});
// token存在初始化数据
const initHandler = () => {
const initHandler = async () => {
console.log("initHandler");
const token = getAccessToken();
if (!token) return;
initWebSocket();
await createConversationRequest();
await initWebSocket();
};
const getAccessToken = () => {
@@ -264,6 +294,38 @@ const checkToken = async () => {
}
};
// 调用接口创建新会话
const createConversationRequest = async (): Promise<string | null> => {
const res = await createConversation();
if (res && res.conversationId) {
conversationId.value = res.conversationId;
console.log("创建新会话ID:", conversationId.value);
return res.conversationId;
} else {
console.log("创建会话失败,接口返回异常");
return null;
}
};
// 加载历史会话消息
const loadConversationMessages = async (convId: string) => {
try {
const res = await conversationMessageList({ conversationId: convId, pageSize: 50, pageNum: 1 });
// 将消息转换为 ChatMessage 格式
chatMsgList.value = res.records.map((msg: any) => ({
messageId: msg.messageId,
messageRole: msg.messageSenderRole === 'user' ? MessageRole.ME : MessageRole.AI,
messageContent: msg.messageContent,
finished: true, // 历史消息已完成
}));
console.log("加载历史消息:", chatMsgList.value);
// 加载历史消息后滚动到底部
nextTick(() => scrollToBottom());
} catch (error) {
console.error("加载历史消息失败:", error);
}
};
/// =============对话↓================
// 初始化WebSocket
const initWebSocket = async () => {
@@ -459,10 +521,7 @@ const sendMessage = async (message: string, isInstruct: boolean = false) => {
if (!isWsConnected()) {
console.log("WebSocket未连接尝试重新连接...");
// 显示加载提示
// uni.showLoading({
// title: "正在连接服务器...",
// });
const loadingInstance = ElLoading.service({ fullscreen: true, text: '正在连接服务器...' });
// 尝试重新初始化WebSocket连接
try {
await initWebSocket();
@@ -471,31 +530,22 @@ const sendMessage = async (message: string, isInstruct: boolean = false) => {
// 检查连接是否成功建立
if (!isWsConnected()) {
// uni.hideLoading();
// uni.showToast({
// title: "连接服务器失败,请稍后重试",
// icon: "none",
// });
loadingInstance.close();
ElMessage({ message: '连接服务器失败,请稍后重试', type: 'error' })
console.error("重新连接WebSocket后仍未连接成功");
return;
}
// uni.hideLoading();
loadingInstance.close();
} catch (error) {
loadingInstance.close();
console.error("重新连接WebSocket失败:", error);
// uni.hideLoading();
// uni.showToast({
// title: "连接服务器失败,请稍后重试",
// icon: "none",
// });
ElMessage({ message: '连接服务器失败,请稍后重试', type: 'error' })
return;
}
}
if (isSessionActive.value) {
// uni.showToast({
// title: "请等待当前回复完成",
// icon: "none",
// });
ElMessage({ message: '当前会话正在进行中,请等待回复完成', type: 'warning' })
console.warn("当前会话正在进行中,请等待回复完成");
return;
}
@@ -757,5 +807,32 @@ const resetConfig = () => {
pendingMap.clear();
};
// 清空会话并停止相关活动(保留 websocket 连接以便继续使用)
const resetConversation = () => {
try {
// 如果正在发送,尝试发送停止请求
try {
if (isSendingMessage.value) sendStopAction();
} catch (e) {
// ignore
}
// 清理 pendingTimeouts
for (const t of pendingTimeouts.values()) {
clearTimeout(t);
}
pendingTimeouts.clear();
pendingMap.clear();
// 清理消息与状态
chatMsgList.value = [];
inputMessage.value = '';
isSendingMessage.value = false;
isSessionActive.value = false;
currentSessionMessageId = null;
} catch (e) {
console.warn('resetConversation failed', e);
}
};
</script>

View File

@@ -1,73 +0,0 @@
<template>
<!-- 唯一滚动容器 -->
<div class="h-full overflow-y-auto">
<!-- Hero吸顶 -->
<div class="bg-white border-box px-12 pt-10 pb-6 max-[800px]:px-5">
<h1 class="text-[28px] font-bold mb-7 leading-tight">
你好<br />
我今天能帮你什么
</h1>
<!-- input -->
<div class="flex flex-col gap-3">
<div class="inline-flex items-center justify-center w-[108px]
px-3 py-1.5 rounded-2xl border border-[#E5E8EE]
text-[13px] text-[#333]">
智能问数
</div>
<div class="h-[174px] bg-white rounded-lg border border-[#eef2f6]
shadow-[0_1px_0_rgba(0,0,0,0.03)]
p-[18px] flex flex-col justify-between">
<div class="text-[#bfc9d4]">
给我发布或者布置任务
</div>
<div class="flex justify-between items-center">
<button class="text-[#9fb0c4]">🔗</button>
<button class="bg-[#f1f6fb] px-2.5 py-1.5 rounded-md">
</button>
</div>
</div>
<!-- header -->
<div class="flex justify-between items-center mt-4">
<h3 class="text-base font-semibold">任务中心</h3>
<a class="text-[#3b82f6] text-[13px] cursor-pointer">
编辑
</a>
</div>
</div>
</div>
<!-- 内容区 -->
<div class="flex-1 px-12 pb-10 pt-4 max-[800px]:px-5">
<div class="grid grid-cols-2 gap-4 max-[800px]:grid-cols-1">
<div v-for="n in 14" :key="n" class="flex gap-3 items-start p-3.5
rounded-[10px] border border-[#dfeaf6] bg-white">
<div class="w-11 h-11 bg-[#EFF6FF] rounded-lg
border border-dashed border-[#9fc0e8]
flex items-center justify-center
text-[#3b82f6] text-[23px]">
</div>
<div>
<div class="font-semibold">
每日销售数据
</div>
<div class="text-[#9aa5b1] text-[13px] mt-1.5">
分析用于销售渠道每日数据汇总及简要展示
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -5,127 +5,79 @@
<div class="font-bold text-gray-80">YINIAN</div>
</div>
<div class="flex justify-center m-2 bg-white rounded-lg p-2.5 border-[##E5E8EE] shadow-sm text-center" @click="addNewChat">
<div class="flex justify-center m-2 bg-white rounded-lg p-2.5 border-[#E5E8EE] shadow-sm text-center"
@click="addNewChat">
<RiAddLine /> 新对话
</div>
<div class="overflow-y-auto p-2 ">
<section v-for="(group, index) in groups" :key="group.title" class="mb-3">
<div class="flex items-center justify-between text-sm text-gray-500" @click="selectGroupKey(index)">
<span>{{ group.title }}</span>
<RiArrowDownSLine v-show="group.selected" color="rgba(153,160,174,1)" />
<RiArrowRightSLine v-show="!group.selected" color="rgba(153,160,174,1)" />
</div>
<ul class="list-none mt-1.5" v-if="group.selected">
<li v-for="item in group.items" :key="item.id" @click="selectedHistoryMessage(item.id)" :class="[
<ul class="list-none">
<li v-for="item in groups" :key="item.conversationId" @click="selectedHistoryMessage(item.conversationId)"
:class="[
'flex items-center gap-2 p-2 text-gray-600 rounded-lg cursor-pointer transition-colors',
item.id === selectedId ? 'bg-white shadow-sm border-[##E5E8EE]' : 'hover:bg-gray-50'
item.conversationId === selectedConversationId ? 'bg-white shadow-sm border-[#E5E8EE] py-1.5 relative z-10' : 'hover:bg-gray-200'
]">
<span class="w-2 h-2 rounded-full bg-[#BEDBFF] flex-none"></span>
<div class="flex-1 min-w-0">
<div class="truncate text-sm">{{ item.title }}</div>
<div class="truncate text-sm">{{ item.conversationId }}</div>
</div>
<button v-if="item.id === selectedId"
<button v-if="item.conversationId === selectedConversationId"
class="bg-transparent border-0 text-gray-500 text-lg px-1 py-0"></button>
</li>
</ul>
</section>
</div>
</aside>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted, defineEmits } from 'vue'
import { RiAddLine, RiArrowRightSLine, RiArrowDownSLine } from '@remixicon/vue'
import { getConversationList } from '../../api/ConversationApi';
interface HistoryMessage {
conversationId: string;
conversationTitle: string;
}
/// 记录选择的历史消息ID
const selectedId = ref<number | null>(2)
const selectedConversationId = ref<string>('')
/// 历史消息分组数据
const groups = ref([
{
title: '近3天',
selected: false,
items: [
{ id: 1, title: '这是一段对话' },
{ id: 2, title: '这是一段对话' },
{ id: 3, title: '这是一段对话这是一段对话这是一段对话' },
{ id: 4, title: '这是一段对话这是一段对话' },
{ id: 5, title: '这是一段对话这是一段对话' }
]
},
{
title: '近7天',
selected: false,
items: [
{ id: 6, title: '这是一段对话' },
{ id: 7, title: '这是一段对话' },
{ id: 8, title: '这是一段对话这是一段对话' },
{ id: 9, title: '这是一段对话这是一段对话' }
]
},
{
title: '近15天',
selected: false,
items: [
{ id: 10, title: '这是一段对话' },
{ id: 11, title: '这是一段对话' },
{ id: 12, title: '这是一段对话这是一段对话这是一段对话' },
{ id: 13, title: '这是一段对话这是一段对话' },
{ id: 14, title: '这是一段对话这是一段对话' }
]
},
{
title: '近30天',
selected: false,
items: [
{ id: 15, title: '这是一段对话' },
{ id: 16, title: '这是一段对话' },
{ id: 17, title: '这是一段对话这是一段对话' },
{ id: 18, title: '这是一段对话这是一段对话' }
]
},
{
title: '近60天',
selected: false,
items: [
{ id: 19, title: '这是一段对话' },
{ id: 20, title: '这是一段对话' },
{ id: 21, title: '这是一段对话这是一段对话这是一段对话' },
{ id: 22, title: '这是一段对话这是一段对话' },
{ id: 23, title: '这是一段对话这是一段对话' }
]
},
{
title: '近90天',
selected: false,
items: [
{ id: 24, title: '这是一段对话' },
{ id: 25, title: '这是一段对话' },
{ id: 26, title: '这是一段对话这是一段对话' },
{ id: 27, title: '这是一段对话这是一段对话' }
]
}
])
const groups = ref<Array<HistoryMessage>>([])
/// 选择历史消息
const selectedHistoryMessage = (id: number) => {
selectedId.value = id
}
/// 定义事件
const emit = defineEmits(['new-chat', 'select-chat'])
/// 选择分组展开/收起
const selectGroupKey = (index: number) => {
groups.value.forEach((group, i) => {
if (i === index) {
group.selected = !group.selected
}
})
}
/// TODO: 添加新对话
/// 添加新对话
const addNewChat = () => {
console.log('add new chat')
// 触发新对话事件
emit('new-chat')
// 清空选择的历史消息ID
selectedConversationId.value = ''
// 获取最新的历史会话列表
getHistoryConversationList()
}
/// 选择历史消息
const selectedHistoryMessage = (conversationId: string) => {
selectedConversationId.value = conversationId
emit('select-chat', conversationId)
}
/// 页面加载时获取历史会话列表
onMounted(() => {
getHistoryConversationList()
})
/// 获取历史会话列表
const getHistoryConversationList = async () => {
const list = await getConversationList({ pageSize: 20, pageNum: 1 })
if (!list || !list.records) return;
groups.value.push(...list.records.map((item: any) => ({
conversationId: item.conversationId,
conversationTitle: item.conversationTitle
})))
}
</script>

View File

@@ -0,0 +1,31 @@
<template>
<div class="flex-1 pb-6">
<div class="flex justify-between items-center py-4">
<h3 class="text-base font-semibold">任务中心</h3>
<a class="text-[#3b82f6] text-[13px] cursor-pointer">
编辑
</a>
</div>
<div class="grid grid-cols-2 gap-4 max-[800px]:grid-cols-1">
<div v-for="n in 14" :key="n" class="flex gap-3 items-start p-3.5
rounded-[10px] border border-[#dfeaf6] bg-white">
<div class="w-11 h-11 bg-[#EFF6FF] rounded-lg
border border-dashed border-[#9fc0e8]
flex items-center justify-center
text-[#3b82f6] text-[23px]">
</div>
<div>
<div class="font-semibold">
每日销售数据
</div>
<div class="text-[#9aa5b1] text-[13px] mt-1.5">
分析用于销售渠道每日数据汇总及简要展示
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,48 @@
<template>
<div class="h-[174px] bg-white rounded-lg border border-[#eef2f6] shadow-[0_1px_0_rgba(0,0,0,0.03)] p-4 mt-2 flex flex-col justify-between">
<textarea
rows="2"
placeholder="给我发布或者布置任务"
class="flex-1 resize-none outline-none text-sm"
:value="modelValue"
@input="onInput"
@keydown.enter="onKeydownEnter"
/>
<div class="flex justify-between items-end">
<button @click="onAttach">
<RiLink />
</button>
<button class="w-12 h-12 bg-[#F5F7FA] px-2.5 py-1.5 rounded-md flex items-center justify-center" @click="onSend">
<RiStopFill v-if="isSendingMessage" />
<RiSendPlaneFill v-else />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
import { RiLink, RiSendPlaneFill, RiStopFill } from '@remixicon/vue'
const props = defineProps({
modelValue: { type: String, default: '' },
isSendingMessage: { type: Boolean, default: false },
})
const emit = defineEmits(['update:modelValue', 'send', 'attach'])
const onInput = (e: Event) => {
const v = (e.target as HTMLTextAreaElement).value
emit('update:modelValue', v)
}
const onKeydownEnter = (e: KeyboardEvent) => {
if ((e as KeyboardEvent).shiftKey) return
e.preventDefault()
emit('send')
}
const onAttach = () => emit('attach')
const onSend = () => emit('send')
</script>

View File

@@ -16,7 +16,6 @@ import MarkdownIt from 'markdown-it'
import hljs from 'highlight.js'
import 'highlight.js/styles/github.css'
import ChatLoading from './ChatLoading.vue';
import { sl } from 'element-plus/es/locale/index.mjs';
interface Props {
msg: ChatMessage

View File

@@ -1,10 +1,9 @@
<template>
<layout>
<div class="flex h-full w-full flex-col md:flex-row ">
<chat-history class="flex-none w-64" />
<div class="flex h-full w-full flex-col md:flex-row">
<ChatHistory class="flex-none w-50" @new-chat="guide = true" @select-chat="handleSelectChat" />
<div class="flex-1 mr-2 overflow-hidden bg-white rounded-xl">
<!-- <chat-guide /> -->
<chat-box />
<ChatBox v-model:guide="guide" :conversationId="selectedConversationId" />
</div>
<TaskList />
</div>
@@ -14,6 +13,17 @@
<script setup lang="ts">
import TaskList from '@renderer/components/TaskList/index.vue'
import ChatHistory from './ChatHistory.vue'
import ChatGuide from './ChatGuide.vue'
import ChatBox from './ChatBox.vue'
import { ref } from 'vue'
/// 是否显示引导页
const guide = ref(true)
/// 选择的历史会话ID
const selectedConversationId = ref('')
/// 选择历史会话
const handleSelectChat = (conversationId: string) => {
guide.value = false;
selectedConversationId.value = conversationId;
};
</script>

View File

@@ -14,7 +14,7 @@
class="bg-sky-50 rounded-[8px] text-[14px] text-sky-600 px-[12px] py-[6px] focus-visible:outline-none cursor-pointer">注册</button> -->
</div>
<div class="flex flex-col items-center justify-center mb-[24px] box-border pt-[108px]">
<div class="flex flex-col items-center justify-center mb-[24px] box-border pt-[40px]">
<img class="w-[80px] h-[80px] mb-[12px]" src="@assets/images/login/user_icon.png" />
<div class="text-[24px] font-500 text-gray-800 line-height-[32px] mb-[4px]">登录</div>
<div class="text-[16px] text-gray-500 line-height-[24px]">24小时在岗从不打烊的数字员工</div>