优化头像和增加禁言功能

This commit is contained in:
maojindao55
2025-02-18 15:27:36 +08:00
parent bf4ec49ef7
commit c18f189be8
6 changed files with 108 additions and 27 deletions

BIN
assets/doubao.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/doubao_new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
chat_bubble.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/x-icon" href="/chat_bubble.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>AI机器人群聊</title>
</head>

View File

@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { Send, Menu, MoreHorizontal, UserPlus, UserMinus, Users2, Users, MoreVertical, MessageCircle } from 'lucide-react';
import { Send, Menu, MoreHorizontal, UserPlus, UserMinus, Users2, Users, MoreVertical, Mic, MicOff } from 'lucide-react';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -56,6 +56,18 @@ const SingleAvatar = ({ user }: { user: User | AICharacter }) => {
// 左右分半头像
const HalfAvatar = ({ user, isFirst }: { user: User, isFirst: boolean }) => {
if ('avatar' in user && user.avatar) {
return (
<div
className="w-1/2 h-full"
style={{
borderRight: isFirst ? '1px solid white' : 'none'
}}
>
<img src={user.avatar} alt={user.name} className="w-full h-full object-cover" />
</div>
);
}
const avatarData = getAvatarData(user.name);
return (
<div
@@ -72,6 +84,19 @@ const HalfAvatar = ({ user, isFirst }: { user: User, isFirst: boolean }) => {
// 四分之一头像
const QuarterAvatar = ({ user, index }: { user: User, index: number }) => {
if ('avatar' in user && user.avatar) {
return (
<div
className="aspect-square"
style={{
borderRight: index % 2 === 0 ? '1px solid white' : 'none',
borderBottom: index < 2 ? '1px solid white' : 'none'
}}
>
<img src={user.avatar} alt={user.name} className="w-full h-full object-cover" />
</div>
);
}
const avatarData = getAvatarData(user.name);
return (
<div
@@ -107,6 +132,9 @@ const ChatUI = () => {
const accumulatedContentRef = useRef(""); // 用于跟踪完整内容
const messagesEndRef = useRef<HTMLDivElement>(null);
// 添加禁言状态
const [mutedUsers, setMutedUsers] = useState<string[]>([]);
const abortController = new AbortController();
const handleRemoveUser = (userId: number) => {
@@ -121,6 +149,15 @@ const ChatUI = () => {
scrollToBottom();
}, [messages]);
// 添加禁言/取消禁言处理函数
const handleToggleMute = (userId: string) => {
setMutedUsers(prev =>
prev.includes(userId)
? prev.filter(id => id !== userId)
: [...prev, userId]
);
};
const handleSendMessage = async () => {
if (!inputMessage.trim()) return;
@@ -144,12 +181,15 @@ const ChatUI = () => {
name: msg.sender.name
}));
// 依次请求两个 AI 的回复
for (let i = 0; i < groupAiCharacters.length; i++) {
//禁言
if (mutedUsers.includes(groupAiCharacters[i].id)) {
continue;
}
// 创建当前 AI 角色的消息
const aiMessage = {
id: messages.length + 2 + i,
sender: { id: groupAiCharacters[i].id, name: groupAiCharacters[i].name },
sender: { id: groupAiCharacters[i].id, name: groupAiCharacters[i].name, avatar: groupAiCharacters[i].avatar },
content: "",
isAI: true
};
@@ -287,19 +327,16 @@ const ChatUI = () => {
{/* 左侧群组信息 */}
<div className="flex items-center gap-1.5">
<div className="relative w-10 h-10">
<div className="w-full h-full rounded-full border-2 border-white overflow-hidden bg-white">
<div className="w-full h-full overflow-hidden bg-white border border-gray-200">
{users.length === 1 ? (
// 单个成员时显示一个大头像
<SingleAvatar user={users[0]} />
) : users.length === 2 ? (
// 两个成员时左右分布
<div className="h-full flex">
{users.slice(0, 2).map((user, index) => (
<HalfAvatar key={user.id} user={user} isFirst={index === 0} />
))}
</div>
) : users.length === 3 ? (
// 三个成员时上2下1
<div className="h-full flex flex-col">
<div className="flex h-1/2">
{users.slice(0, 2).map((user, index) => (
@@ -311,7 +348,6 @@ const ChatUI = () => {
</div>
</div>
) : (
// 四个或更多成员时显示2x2网格
<div className="h-full grid grid-cols-2">
{users.slice(0, 4).map((user, index) => (
<QuarterAvatar key={user.id} user={user} index={index} />
@@ -319,7 +355,7 @@ const ChatUI = () => {
</div>
)}
</div>
<div className="absolute -bottom-0.5 -right-0.5 bg-green-500 w-3 h-3 rounded-full border-2 border-white"></div>
<div className="absolute -bottom-0.5 -right-0.5 bg-green-500 w-3 h-3 border-2 border-white"></div>
</div>
<div>
<h1 className="font-medium text-base">{group.name}</h1>
@@ -337,9 +373,13 @@ const ChatUI = () => {
<Tooltip>
<TooltipTrigger>
<Avatar className="w-7 h-7 border-2 border-white">
<AvatarFallback style={{ backgroundColor: avatarData.backgroundColor, color: 'white' }}>
{avatarData.text}
</AvatarFallback>
{'avatar' in user && user.avatar ? (
<AvatarImage src={user.avatar} />
) : (
<AvatarFallback style={{ backgroundColor: avatarData.backgroundColor, color: 'white' }}>
{avatarData.text}
</AvatarFallback>
)}
</Avatar>
</TooltipTrigger>
<TooltipContent>
@@ -371,9 +411,13 @@ const ChatUI = () => {
className={`flex items-start gap-2 ${message.sender.name === "我" ? "justify-end" : ""}`}>
{message.sender.name !== "我" && (
<Avatar>
{'avatar' in message.sender && message.sender.avatar ? (
<AvatarImage src={message.sender.avatar} className="w-10 h-10" />
) : (
<AvatarFallback style={{ backgroundColor: getAvatarData(message.sender.name).backgroundColor, color: 'white' }}>
{message.sender.name[0]}
</AvatarFallback>
)}
</Avatar>
)}
<div className={message.sender.name === "我" ? "text-right" : ""}>
@@ -385,6 +429,8 @@ const ChatUI = () => {
className={`prose dark:prose-invert max-w-none ${
message.sender.name === "我" ? "text-white [&_*]:text-white" : ""
}
[&_h3]:py-1.5
[&_h3]:m-0
[&_p]:m-0
[&_pre]:bg-gray-900
[&_pre]:p-2
@@ -419,9 +465,13 @@ const ChatUI = () => {
</div>
{message.sender.name === "我" && (
<Avatar>
{'avatar' in message.sender && message.sender.avatar ? (
<AvatarImage src={message.sender.avatar} className="w-10 h-10" />
) : (
<AvatarFallback style={{ backgroundColor: getAvatarData(message.sender.name).backgroundColor, color: 'white' }}>
{message.sender.name[0]}
</AvatarFallback>
)}
</Avatar>
)}
</div>
@@ -475,21 +525,52 @@ const ChatUI = () => {
<div key={user.id} className="flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg">
<div className="flex items-center gap-3">
<Avatar>
<AvatarFallback style={{ backgroundColor: getAvatarData(user.name).backgroundColor, color: 'white' }}>
{user.name[0]}
</AvatarFallback>
{'avatar' in user && user.avatar ? (
<AvatarImage src={user.avatar} className="w-10 h-10" />
) : (
<AvatarFallback style={{ backgroundColor: getAvatarData(user.name).backgroundColor, color: 'white' }}>
{user.name[0]}
</AvatarFallback>
)}
</Avatar>
<span>{user.name}</span>
<div className="flex flex-col">
<span>{user.name}</span>
{mutedUsers.includes(user.id) && (
<span className="text-xs text-red-500"></span>
)}
</div>
</div>
{/*user.name !== "我" && (
<Button
variant="ghost"
size="icon"
onClick={() => handleRemoveUser(user.id)}
>
<UserMinus className="w-4 h-4 text-red-500" />
</Button>
)*/}
{user.name !== "我" && (
<div className="flex gap-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => handleToggleMute(user.id)}
>
{mutedUsers.includes(user.id) ? (
<MicOff className="w-4 h-4 text-red-500" />
) : (
<Mic className="w-4 h-4 text-green-500" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>
{mutedUsers.includes(user.id) ? '取消禁言' : '禁言'}
</TooltipContent>
</Tooltip>
</TooltipProvider>
{/*<Button
variant="ghost"
size="icon"
onClick={() => handleRemoveUser(user.id)}
>
<UserMinus className="w-4 h-4 text-red-500" />
</Button>*/}
</div>
)}
</div>
))}
</div>

View File

@@ -64,7 +64,7 @@ export function generateAICharacters(groupName: string): AICharacter[] {
name: "豆包",
personality: "doubao",
model: "ep-20250217191935-wzj8l",
avatar: "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/samantha/logo-icon-white-bg.png",
avatar: "/assets/doubao_new.png",
custom_prompt: `你是一个名叫"豆包"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`
},
{