Compare commits
2 Commits
main
...
9c5afcaa04
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c5afcaa04 | |||
| 8137a38060 |
6
global.d.ts
vendored
6
global.d.ts
vendored
@@ -134,6 +134,12 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "*.vue" {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
|
|
||||||
declare module "@store/*";
|
declare module "@store/*";
|
||||||
declare module "@service/*";
|
declare module "@service/*";
|
||||||
declare module "@utils/*";
|
declare module "@utils/*";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141; connect-src 'self' http://8.138.234.141 https://api.iconify.design"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -113,22 +113,46 @@ export class WebSocketManager {
|
|||||||
await this.connect()
|
await this.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 改进方案:让connect()真正等待连接
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
if (this.isConnecting || this.connectionState) return
|
console.log('[WebSocket] connect() called, isConnecting:', this.isConnecting, 'connectionState:', this.connectionState)
|
||||||
this.isConnecting = true
|
if (this.isConnecting || this.connectionState) {
|
||||||
|
console.log('[WebSocket] Already connecting or connected, returning early')
|
||||||
try {
|
return
|
||||||
this.ws = new WebSocket(this.wsUrl, this.protocols)
|
|
||||||
console.log('WebSocket connecting to:', this.wsUrl)
|
|
||||||
this.ws.onopen = this.handleOpen
|
|
||||||
this.ws.onmessage = this.handleMessage
|
|
||||||
this.ws.onclose = this.handleClose
|
|
||||||
this.ws.onerror = this.handleError
|
|
||||||
} catch (error) {
|
|
||||||
this.isConnecting = false
|
|
||||||
this.safeCall('onError', error)
|
|
||||||
this.scheduleReconnect()
|
|
||||||
}
|
}
|
||||||
|
this.isConnecting = true
|
||||||
|
console.log('[WebSocket] Starting connection...')
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
console.log('[WebSocket] About to create new WebSocket with URL:', this.wsUrl)
|
||||||
|
this.ws = new WebSocket(this.wsUrl, this.protocols)
|
||||||
|
console.log('[WebSocket] WebSocket object created, readyState:', this.ws?.readyState)
|
||||||
|
|
||||||
|
// 包装handleOpen以resolve Promise
|
||||||
|
this.ws.onopen = (event: Event) => {
|
||||||
|
console.log('[WebSocket] onopen event fired')
|
||||||
|
this.handleOpen(event)
|
||||||
|
resolve() // ← 真正的连接成功
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ws.onmessage = this.handleMessage
|
||||||
|
this.ws.onclose = (event: CloseEvent) => {
|
||||||
|
console.log('[WebSocket] onclose event fired, code:', event.code, 'reason:', event.reason)
|
||||||
|
this.handleClose(event)
|
||||||
|
}
|
||||||
|
this.ws.onerror = (error: Event) => {
|
||||||
|
console.log('[WebSocket] onerror event fired', error)
|
||||||
|
this.handleError(error)
|
||||||
|
reject(error) // ← Promise拒绝
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.isConnecting = false
|
||||||
|
this.safeCall('onError', error)
|
||||||
|
this.scheduleReconnect()
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleOpen = (event: Event): void => {
|
private handleOpen = (event: Event): void => {
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
:class="msg.messageRole === MessageRole.ME ? 'justify-end' : 'justify-start'">
|
:class="msg.messageRole === MessageRole.ME ? 'justify-end' : 'justify-start'">
|
||||||
|
|
||||||
<!-- AI avatar -->
|
<!-- AI avatar -->
|
||||||
<img v-if="msg.messageRole === MessageRole.AI" class="w-9 h-9 rounded-full shrink-0"
|
<ChatAvatar v-if="msg.messageRole === MessageRole.AI" :src="aiAvatar" />
|
||||||
src="@assets/images/login/blue_logo.png" />
|
|
||||||
|
|
||||||
<!-- 消息气泡 -->
|
<!-- 消息气泡 -->
|
||||||
<div class="max-w-[70%]">
|
<div class="max-w-[70%]">
|
||||||
@@ -18,33 +17,23 @@
|
|||||||
<span class="text-xs text-[#4E5969]"> ZHINIAN</span>
|
<span class="text-xs text-[#4E5969]"> ZHINIAN</span>
|
||||||
<span class="text-xs text-[#86909C]"> 20:30</span>
|
<span class="text-xs text-[#86909C]"> 20:30</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-sm text-gray-700"
|
<div class="text-sm text-gray-700"
|
||||||
:class="msg.messageRole === MessageRole.ME ? 'bg-[#f7f9fc] rounded-md px-2 py-2' : ''">
|
:class="msg.messageRole === MessageRole.ME ? 'bg-[#f7f9fc] rounded-md px-2 py-2' : ''">
|
||||||
{{ msg.messageContent }}
|
{{ msg.messageContent }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- AI 标识 -->
|
<!-- AI 标识 -->
|
||||||
<div v-if="msg.messageRole === MessageRole.AI" class="mt-2 text-xs text-gray-400 ">
|
<div v-if="msg.messageRole === MessageRole.AI && msg.finished" class="mt-2 text-xs text-gray-400 ">
|
||||||
本回答由 AI 生成
|
本回答由 AI 生成
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- AI 操作按钮 -->
|
<!-- AI 操作按钮 -->
|
||||||
<div v-if="msg.messageRole === MessageRole.AI"
|
<ChatOperation v-if="msg.messageRole === MessageRole.AI && msg.finished" :msg="msg" />
|
||||||
class="mt-4 text-gray-500 flex items-center justify-between gap-4 ">
|
|
||||||
<RiFileCopyLine size="16px" @click="copyFileClick(msg)" />
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<RiShareForwardLine size="16px" @click="shareForwardClick(msg)" />
|
|
||||||
<RiDownload2Line size="16px" @click="downloadClick(msg)" />
|
|
||||||
<RiThumbUpLine size="16px" @click="thumbUpClick(msg)" />
|
|
||||||
<RiThumbDownLine size="16px" @click="thumbDownClick(msg)" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User avatar -->
|
<!-- User avatar -->
|
||||||
<img v-if="msg.messageRole === MessageRole.ME" class="w-9 h-9 rounded-full shrink-0"
|
<ChatAvatar v-if="msg.messageRole === MessageRole.ME" :src="userAvatar" />
|
||||||
src="@assets/images/login/user_icon.png" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,9 +74,14 @@ import { onMounted, nextTick, onUnmounted } from "vue";
|
|||||||
import { WebSocketManager } from "@common/WebSocketManager";
|
import { WebSocketManager } from "@common/WebSocketManager";
|
||||||
import { MessageRole, ChatMessage } from "./model/ChatModel";
|
import { MessageRole, ChatMessage } from "./model/ChatModel";
|
||||||
import { IdUtils } from "@common/index";
|
import { IdUtils } from "@common/index";
|
||||||
|
import ChatAvatar from './components/ChatAvatar.vue';
|
||||||
|
import ChatOperation from './components/ChatOperation.vue';
|
||||||
|
|
||||||
import { Session } from '../../utils/storage';
|
import { Session } from '../../utils/storage';
|
||||||
|
|
||||||
|
import userAvatar from '@assets/images/login/user_icon.png';
|
||||||
|
import aiAvatar from '@assets/images/login/blue_logo.png';
|
||||||
|
|
||||||
///(控制滚动位置)
|
///(控制滚动位置)
|
||||||
const scrollTop = ref(99999);
|
const scrollTop = ref(99999);
|
||||||
|
|
||||||
@@ -162,24 +156,6 @@ const handleReplyInstruct = async (message: string, type: string) => {
|
|||||||
setTimeoutScrollToBottom();
|
setTimeoutScrollToBottom();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// actions 实现复制、分享、下载、点赞等功能
|
|
||||||
const copyFileClick = (msg: ChatMessage) => {
|
|
||||||
console.log('copy file', msg)
|
|
||||||
}
|
|
||||||
const shareForwardClick = (msg: ChatMessage) => {
|
|
||||||
console.log('share forward', msg)
|
|
||||||
}
|
|
||||||
const downloadClick = (msg: ChatMessage) => {
|
|
||||||
console.log('download', msg)
|
|
||||||
}
|
|
||||||
const thumbUpClick = (msg: ChatMessage) => {
|
|
||||||
console.log('thumb up', msg)
|
|
||||||
}
|
|
||||||
const thumbDownClick = (msg: ChatMessage) => {
|
|
||||||
console.log('thumb down', msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 添加附件按钮事件
|
/// 添加附件按钮事件
|
||||||
const addAttachmentAction = () => {
|
const addAttachmentAction = () => {
|
||||||
console.log("添加附件");
|
console.log("添加附件");
|
||||||
@@ -318,6 +294,7 @@ const initWebSocket = async () => {
|
|||||||
|
|
||||||
// 处理WebSocket消息
|
// 处理WebSocket消息
|
||||||
const handleWebSocketMessage = (data: any) => {
|
const handleWebSocketMessage = (data: any) => {
|
||||||
|
console.log("收到WebSocket消息:", data);
|
||||||
// 验证关键字段(若服务端传回 conversationId/agentId,则校验是否属于当前会话)
|
// 验证关键字段(若服务端传回 conversationId/agentId,则校验是否属于当前会话)
|
||||||
if (data.conversationId && data.conversationId !== conversationId.value) {
|
if (data.conversationId && data.conversationId !== conversationId.value) {
|
||||||
console.warn("收到不属于当前会话的消息,忽略", data.conversationId);
|
console.warn("收到不属于当前会话的消息,忽略", data.conversationId);
|
||||||
@@ -374,6 +351,7 @@ const handleWebSocketMessage = (data: any) => {
|
|||||||
|
|
||||||
// 处理完成状态
|
// 处理完成状态
|
||||||
if (data.finish) {
|
if (data.finish) {
|
||||||
|
chatMsgList.value[aiMsgIndex].finished = data.finish;
|
||||||
const msg = chatMsgList.value[aiMsgIndex].messageContent;
|
const msg = chatMsgList.value[aiMsgIndex].messageContent;
|
||||||
if (!msg || chatMsgList.value[aiMsgIndex].isLoading) {
|
if (!msg || chatMsgList.value[aiMsgIndex].isLoading) {
|
||||||
chatMsgList.value[aiMsgIndex].messageContent = "未获取到内容,请重试";
|
chatMsgList.value[aiMsgIndex].messageContent = "未获取到内容,请重试";
|
||||||
|
|||||||
13
src/renderer/views/home/components/ChatAvatar.vue
Normal file
13
src/renderer/views/home/components/ChatAvatar.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<img class="w-9 h-9 rounded-full shrink-0" :src="src" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
src?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
src: '@assets/images/login/blue_logo.png'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
43
src/renderer/views/home/components/ChatOperation.vue
Normal file
43
src/renderer/views/home/components/ChatOperation.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mt-4 text-gray-500 flex items-center justify-between gap-4 ">
|
||||||
|
<RiFileCopyLine size="16px" @click="copyFileClick()" />
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<RiShareForwardLine size="16px" @click="shareForwardClick()" />
|
||||||
|
<RiDownload2Line size="16px" @click="downloadClick()" />
|
||||||
|
<RiThumbUpLine size="16px" @click="thumbUpClick()" />
|
||||||
|
<RiThumbDownLine size="16px" @click="thumbDownClick()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { RiFileCopyLine, RiShareForwardLine, RiDownload2Line, RiThumbUpLine, RiThumbDownLine } from '@remixicon/vue'
|
||||||
|
import { ChatMessage } from '../model/ChatModel';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
msg: ChatMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
const { msg } = defineProps<Props>()
|
||||||
|
|
||||||
|
/// actions 实现复制、分享、下载、点赞等功能
|
||||||
|
const copyFileClick = () => {
|
||||||
|
console.log('copy file', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const shareForwardClick = () => {
|
||||||
|
console.log('share forward', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadClick = () => {
|
||||||
|
console.log('download', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbUpClick = () => {
|
||||||
|
console.log('thumb up', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbDownClick = () => {
|
||||||
|
console.log('thumb down', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
<div class="flex h-full w-full flex-col md:flex-row ">
|
<div class="flex h-full w-full flex-col md:flex-row ">
|
||||||
<chat-history class="flex-none w-64" />
|
<chat-history class="flex-none w-64" />
|
||||||
<div class="flex-1 mr-2 overflow-hidden bg-white rounded-xl">
|
<div class="flex-1 mr-2 overflow-hidden bg-white rounded-xl">
|
||||||
<chat-guide />
|
<!-- <chat-guide /> -->
|
||||||
<!-- <chat-box /> -->
|
<chat-box />
|
||||||
</div>
|
</div>
|
||||||
<TaskList />
|
<TaskList />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export class ChatMessage {
|
|||||||
messageContent: string;
|
messageContent: string;
|
||||||
// 是否加载中
|
// 是否加载中
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
// 是否完成
|
||||||
|
finished?: boolean;
|
||||||
// 工具调用信息
|
// 工具调用信息
|
||||||
toolCall?: any;
|
toolCall?: any;
|
||||||
// 问题信息
|
// 问题信息
|
||||||
@@ -28,13 +30,15 @@ export class ChatMessage {
|
|||||||
messageRole: MessageRole,
|
messageRole: MessageRole,
|
||||||
messageContent: string,
|
messageContent: string,
|
||||||
isLoading: boolean = false,
|
isLoading: boolean = false,
|
||||||
|
finished: boolean = false,
|
||||||
toolCall?: any,
|
toolCall?: any,
|
||||||
question?: any
|
question?: any
|
||||||
) {
|
) {
|
||||||
|
this.messageId = messageId;
|
||||||
this.messageRole = messageRole;
|
this.messageRole = messageRole;
|
||||||
this.messageContent = messageContent;
|
this.messageContent = messageContent;
|
||||||
this.isLoading = isLoading;
|
this.isLoading = isLoading;
|
||||||
this.messageId = messageId;
|
this.finished = finished;
|
||||||
this.toolCall = toolCall;
|
this.toolCall = toolCall;
|
||||||
this.question = question;
|
this.question = question;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user