Enhance file upload process in devlop.py with progress tracking and remote file validation. Add aiType field to weChatGroup and ContractData interfaces for AI type differentiation. Update ChatWindow component to handle AI configuration changes and ensure proper database updates for contact and message management. Implement database versioning to include aiType in chatSessions and contactsUnified tables, with migration logic for existing data. Improve error handling and user feedback during configuration updates.
This commit is contained in:
@@ -55,13 +55,52 @@ success('本地打包完成')
|
||||
# 3. 上传
|
||||
step('Step 3: 上传 zip 包到服务器')
|
||||
info('开始上传 zip 包...')
|
||||
|
||||
# 获取本地文件大小
|
||||
local_size = os.path.getsize(zip_name)
|
||||
info(f'本地文件大小: {local_size / 1024 / 1024:.2f} MB')
|
||||
|
||||
transport = paramiko.Transport((server_ip, server_port))
|
||||
transport.connect(username=server_user, password=server_pwd)
|
||||
sftp = paramiko.SFTPClient.from_transport(transport)
|
||||
sftp.put(zip_name, remote_path)
|
||||
sftp.close()
|
||||
transport.close()
|
||||
success('上传到服务器完成')
|
||||
|
||||
try:
|
||||
# 如果远程文件已存在,先删除
|
||||
try:
|
||||
sftp.remove(remote_path)
|
||||
info('已删除远程旧文件')
|
||||
except:
|
||||
pass # 文件不存在,忽略
|
||||
|
||||
# 确保远程目录存在
|
||||
remote_dir = os.path.dirname(remote_path)
|
||||
try:
|
||||
sftp.stat(remote_dir)
|
||||
except:
|
||||
error(f'远程目录不存在: {remote_dir}')
|
||||
raise
|
||||
|
||||
# 上传进度回调
|
||||
def progress_callback(transferred, total):
|
||||
percent = (transferred / total) * 100
|
||||
if transferred % (1024 * 1024) == 0 or transferred == total: # 每MB或完成时显示
|
||||
info(f'上传进度: {transferred / 1024 / 1024:.2f}MB / {total / 1024 / 1024:.2f}MB ({percent:.1f}%)')
|
||||
|
||||
# 上传文件
|
||||
sftp.put(zip_name, remote_path, callback=progress_callback, confirm=True)
|
||||
|
||||
# 验证远程文件大小
|
||||
remote_size = sftp.stat(remote_path).st_size
|
||||
info(f'远程文件大小: {remote_size / 1024 / 1024:.2f} MB')
|
||||
|
||||
if remote_size != local_size:
|
||||
raise IOError(f'文件大小不匹配!本地: {local_size}, 远程: {remote_size}')
|
||||
|
||||
success('上传到服务器完成')
|
||||
|
||||
finally:
|
||||
sftp.close()
|
||||
transport.close()
|
||||
|
||||
# 删除本地 dist.zip
|
||||
try:
|
||||
|
||||
@@ -114,6 +114,7 @@ export interface weChatGroup {
|
||||
nickname: string;
|
||||
chatroomAvatar: string;
|
||||
groupId: number;
|
||||
aiType?: number; // AI类型(0=普通,1=AI辅助)
|
||||
config?: {
|
||||
top?: false;
|
||||
chat?: boolean;
|
||||
@@ -151,6 +152,7 @@ export interface ContractData {
|
||||
isPassed: boolean;
|
||||
tenantId: number;
|
||||
groupId: number;
|
||||
aiType?: number; // AI类型(0=普通,1=AI辅助)
|
||||
thirdParty: null;
|
||||
additionalPicture: string;
|
||||
desc: string;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Layout, Button, Avatar, Space, Tooltip, Dropdown } from "antd";
|
||||
import {
|
||||
Layout,
|
||||
Button,
|
||||
Avatar,
|
||||
Space,
|
||||
Tooltip,
|
||||
Dropdown,
|
||||
message,
|
||||
} from "antd";
|
||||
import {
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
@@ -19,6 +27,9 @@ import TodoListModal from "./components/TodoListModal";
|
||||
import ChatRecordSearch from "./components/ChatRecordSearch";
|
||||
import { setFriendInjectConfig } from "@/pages/pc/ckbox/weChat/api";
|
||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||
import { MessageManager } from "@/utils/dbAction/message";
|
||||
import { ContactManager } from "@/utils/dbAction/contact";
|
||||
import { useUserStore } from "@storeModule/user";
|
||||
const { Header, Content } = Layout;
|
||||
|
||||
interface ChatWindowProps {
|
||||
@@ -41,6 +52,10 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
||||
const showChatRecordModel = useWeChatStore(
|
||||
state => state.showChatRecordModel,
|
||||
);
|
||||
const setCurrentContact = useWeChatStore(state => state.setCurrentContact);
|
||||
const { user } = useUserStore();
|
||||
const currentUserId = user?.id || 0;
|
||||
|
||||
const [showProfile, setShowProfile] = useState(true);
|
||||
const [followupModalVisible, setFollowupModalVisible] = useState(false);
|
||||
const [todoModalVisible, setTodoModalVisible] = useState(false);
|
||||
@@ -76,20 +91,72 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ contract }) => {
|
||||
}, [aiQuoteMessageContent]);
|
||||
|
||||
// 处理配置选择
|
||||
const handleConfigChange = option => {
|
||||
const handleConfigChange = async option => {
|
||||
setCurrentConfig({
|
||||
value: option.value,
|
||||
label: option.label,
|
||||
});
|
||||
|
||||
// 保存配置到后端
|
||||
setFriendInjectConfig({
|
||||
type: option.value,
|
||||
wechatAccountId: contract.wechatAccountId,
|
||||
friendId: contract.id,
|
||||
}).then(() => {
|
||||
try {
|
||||
// 1. 保存配置到后端
|
||||
await setFriendInjectConfig({
|
||||
type: option.value,
|
||||
wechatAccountId: contract.wechatAccountId,
|
||||
friendId: contract.id,
|
||||
});
|
||||
|
||||
// 2. 更新 Store 中的 AI 配置状态
|
||||
updateAiQuoteMessageContent(option.value);
|
||||
});
|
||||
|
||||
// 3. 确定联系人类型
|
||||
const contactType: "friend" | "group" = contract.chatroomId
|
||||
? "group"
|
||||
: "friend";
|
||||
const aiType = option.value;
|
||||
|
||||
console.log(
|
||||
`开始更新 aiType: contactId=${contract.id}, type=${contactType}, aiType=${aiType}`,
|
||||
);
|
||||
|
||||
// 4. 更新会话列表数据库的 aiType
|
||||
await MessageManager.updateSession({
|
||||
userId: currentUserId,
|
||||
id: contract.id!,
|
||||
type: contactType,
|
||||
aiType: aiType,
|
||||
});
|
||||
console.log("✅ 会话列表数据库 aiType 更新成功");
|
||||
|
||||
// 5. 更新联系人数据库的 aiType
|
||||
const contactInDb = await ContactManager.getContactByIdAndType(
|
||||
currentUserId,
|
||||
contract.id!,
|
||||
contactType,
|
||||
);
|
||||
|
||||
if (contactInDb) {
|
||||
await ContactManager.updateContact({
|
||||
...contactInDb,
|
||||
aiType: aiType,
|
||||
});
|
||||
console.log("✅ 联系人数据库 aiType 更新成功");
|
||||
} else {
|
||||
console.warn("⚠️ 联系人数据库中未找到该联系人");
|
||||
}
|
||||
|
||||
// 6. 更新 Store 中的 currentContract(通过重新设置)
|
||||
const updatedContract = {
|
||||
...contract,
|
||||
aiType: aiType,
|
||||
};
|
||||
setCurrentContact(updatedContract);
|
||||
console.log("✅ Store currentContract aiType 更新成功");
|
||||
|
||||
message.success(`已切换为${option.label}`);
|
||||
} catch (error) {
|
||||
console.error("更新 AI 配置失败:", error);
|
||||
message.error("配置更新失败,请重试");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -709,6 +709,9 @@ const MessageList: React.FC<MessageListProps> = () => {
|
||||
|
||||
// 点击会话
|
||||
const onContactClick = async (session: ChatSession) => {
|
||||
console.log("onContactClick", session);
|
||||
console.log("session.aiType:", session.aiType); // 调试:查看 aiType 字段
|
||||
|
||||
// 设置当前会话
|
||||
setCurrentContact(session as any);
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ export interface weChatGroup {
|
||||
nickname: string;
|
||||
chatroomAvatar: string;
|
||||
groupId: number;
|
||||
aiType?: number; // AI类型(0=普通,1=AI辅助)
|
||||
config?: {
|
||||
top?: false;
|
||||
chat?: boolean;
|
||||
@@ -149,6 +150,7 @@ export interface ContractData {
|
||||
isPassed: boolean;
|
||||
tenantId: number;
|
||||
groupId: number;
|
||||
aiType?: number; // AI类型(0=普通,1=AI辅助)
|
||||
thirdParty: null;
|
||||
additionalPicture: string;
|
||||
desc: string;
|
||||
|
||||
@@ -138,6 +138,10 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
},
|
||||
/** 设置当前联系人并加载相关数据 */
|
||||
setCurrentContact: (contract: ContractData | weChatGroup) => {
|
||||
console.log(
|
||||
"setCurrentContact - contract.aiType:",
|
||||
(contract as any).aiType,
|
||||
); // 调试:查看 aiType 字段
|
||||
const state = useWeChatStore.getState();
|
||||
// 切换联系人时清空当前消息,等待重新加载
|
||||
set({ currentMessages: [] });
|
||||
@@ -294,7 +298,10 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
}));
|
||||
|
||||
// 只有文字消息才触发AI(msgType === 1)
|
||||
if (message.msgType === 1) {
|
||||
if (
|
||||
message.msgType === 1 &&
|
||||
(currentContract as any).aiType === 1
|
||||
) {
|
||||
//把数据传到存客宝
|
||||
const params: any = {
|
||||
type: "CmdNewMessage",
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface ChatSession {
|
||||
avatar: string; // 头像
|
||||
content: string; // 最新消息内容
|
||||
lastUpdateTime: string; // 最后更新时间
|
||||
aiType?: number; // AI类型(0=普通,1=AI辅助)
|
||||
config: {
|
||||
unreadCount: number; // 未读数量
|
||||
top: number; // 是否置顶(1=置顶,0=非置顶)
|
||||
@@ -72,6 +73,7 @@ export interface Contact {
|
||||
conRemark?: string; // 备注名
|
||||
avatar: string; // 头像
|
||||
lastUpdateTime: string; // 最后更新时间
|
||||
aiType?: number; // AI类型(0=普通,1=AI辅助)
|
||||
config?: any; // 配置信息
|
||||
sortKey: string; // 预计算排序键
|
||||
searchKey: string; // 预计算搜索键
|
||||
@@ -142,6 +144,47 @@ class CunkebaoDatabase extends Dexie {
|
||||
userLoginRecords:
|
||||
"serverId, userId, lastLoginTime, loginCount, createTime, lastActiveTime",
|
||||
});
|
||||
|
||||
// 版本2:添加 aiType 字段
|
||||
this.version(2)
|
||||
.stores({
|
||||
// 会话表索引:添加 aiType 索引
|
||||
chatSessions:
|
||||
"serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+lastUpdateTime], [userId+aiType], sortKey, nickname, conRemark, avatar, content, lastUpdateTime, aiType",
|
||||
|
||||
// 联系人表索引:添加 aiType 索引
|
||||
contactsUnified:
|
||||
"serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+aiType], sortKey, searchKey, nickname, conRemark, avatar, lastUpdateTime, groupId, aiType",
|
||||
|
||||
// 联系人标签映射表索引:保持不变
|
||||
contactLabelMap:
|
||||
"serverId, userId, labelId, contactId, contactType, [userId+labelId], [userId+contactId], [userId+labelId+sortKey], sortKey, searchKey, avatar, nickname, conRemark, unreadCount, lastUpdateTime",
|
||||
|
||||
// 用户登录记录表索引:保持不变
|
||||
userLoginRecords:
|
||||
"serverId, userId, lastLoginTime, loginCount, createTime, lastActiveTime",
|
||||
})
|
||||
.upgrade(tx => {
|
||||
// 数据迁移:为现有数据添加 aiType 默认值
|
||||
return tx
|
||||
.table("chatSessions")
|
||||
.toCollection()
|
||||
.modify(session => {
|
||||
if (session.aiType === undefined) {
|
||||
session.aiType = 0; // 默认为普通类型
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return tx
|
||||
.table("contactsUnified")
|
||||
.toCollection()
|
||||
.modify(contact => {
|
||||
if (contact.aiType === undefined) {
|
||||
contact.aiType = 0; // 默认为普通类型
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,8 @@ export class ContactManager {
|
||||
local.nickname !== server.nickname ||
|
||||
local.conRemark !== server.conRemark ||
|
||||
local.avatar !== server.avatar ||
|
||||
local.wechatAccountId !== server.wechatAccountId
|
||||
local.wechatAccountId !== server.wechatAccountId ||
|
||||
(local.aiType ?? 0) !== (server.aiType ?? 0) // 添加 aiType 比较
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ export class MessageManager {
|
||||
avatar: friend.avatar || "",
|
||||
content: (friend as any).content || "",
|
||||
lastUpdateTime: friend.lastUpdateTime || new Date().toISOString(),
|
||||
aiType: (friend as any).aiType ?? 0, // AI类型,默认为0(普通)
|
||||
config: {
|
||||
unreadCount: friend.config?.unreadCount || 0,
|
||||
top: (friend.config as any)?.top || false,
|
||||
@@ -123,6 +124,7 @@ export class MessageManager {
|
||||
avatar: group.chatroomAvatar || "",
|
||||
content: (group as any).content || "",
|
||||
lastUpdateTime: (group as any).lastUpdateTime || new Date().toISOString(),
|
||||
aiType: (group as any).aiType ?? 0, // AI类型,默认为0(普通)
|
||||
config: {
|
||||
unreadCount: (group.config as any)?.unreadCount || 0,
|
||||
top: (group.config as any)?.top || false,
|
||||
@@ -195,6 +197,7 @@ export class MessageManager {
|
||||
"conRemark",
|
||||
"avatar",
|
||||
"wechatAccountId", // 添加wechatAccountId比较
|
||||
"aiType", // 添加aiType比较
|
||||
];
|
||||
|
||||
for (const field of fieldsToCompare) {
|
||||
|
||||
Reference in New Issue
Block a user