diff --git a/package-lock.json b/package-lock.json index 34493dd..28ada8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -941,7 +942,7 @@ }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", "dependencies": { "@radix-ui/primitive": "1.1.1", @@ -1312,6 +1313,34 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz", + "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", @@ -1407,6 +1436,20 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", diff --git a/package.json b/package.json index 66e8518..ca30053 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/components/ChatUI.tsx b/src/components/ChatUI.tsx index e631d37..0cab0ec 100644 --- a/src/components/ChatUI.tsx +++ b/src/components/ChatUI.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { Send, Menu, MoreHorizontal, UserPlus, UserMinus, Users2, Users, MoreVertical, Share2, Mic, MicOff } from 'lucide-react'; +import { Send, Menu, MoreHorizontal, UserPlus, UserMinus, Users2, Users, MoreVertical, Share2, Mic, MicOff, Settings2 } from 'lucide-react'; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -12,11 +12,11 @@ import { } from "@/components/ui/tooltip"; import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" + Sheet, + SheetContent, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" import {generateAICharacters} from "@/config/aiCharacters"; import { groups } from "@/config/groups"; @@ -27,6 +27,7 @@ import remarkMath from 'remark-math' import rehypeKatex from 'rehype-katex' import html2canvas from 'html2canvas'; import { SharePoster } from '@/components/SharePoster'; +import { MembersManagement } from '@/components/MembersManagement'; // 使用本地头像数据,避免外部依赖 const getAvatarData = (name: string) => { @@ -117,9 +118,9 @@ const QuarterAvatar = ({ user, index }: { user: User, index: number }) => { ); }; -// 动态导入 KaTeX 样式 +// 修改 KaTeXStyle 组件 const KaTeXStyle = () => ( - + `}} /> ); const ChatUI = () => { const [group, setGroup] = useState(groups[1]); + const [isGroupDiscussionMode, setIsGroupDiscussionMode] = useState(false); const groupAiCharacters = generateAICharacters(group.name).filter(character => group.members.includes(character.id)); const [users, setUsers] = useState([ { id: 1, name: "我" }, @@ -215,17 +217,18 @@ const ChatUI = () => { name: msg.sender.name })); let selectedGroupAiCharacters = groupAiCharacters; - // 调度器api请求 - const shedulerResponse = await fetch('/api/scheduler', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ message: inputMessage, history: messageHistory, availableAIs: groupAiCharacters }) - }); - const shedulerData = await shedulerResponse.json(); - const selectedAIs = shedulerData.selectedAIs; - selectedGroupAiCharacters = selectedAIs.map(ai => groupAiCharacters.find(c => c.id === ai)); + if (!isGroupDiscussionMode) { + const shedulerResponse = await fetch('/api/scheduler', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ message: inputMessage, history: messageHistory, availableAIs: groupAiCharacters }) + }); + const shedulerData = await shedulerResponse.json(); + const selectedAIs = shedulerData.selectedAIs; + selectedGroupAiCharacters = selectedAIs.map(ai => groupAiCharacters.find(c => c.id === ai)); + } for (let i = 0; i < selectedGroupAiCharacters.length; i++) { //禁言 if (mutedUsers.includes(selectedGroupAiCharacters[i].id)) { @@ -454,7 +457,7 @@ const ChatUI = () => { )} @@ -589,81 +592,19 @@ const ChatUI = () => { - - {/* Members Management Dialog */} - - - - 群成员管理 - -
-
- 当前成员({users.length}) - -
- -
- {users.map((user) => ( -
-
- - {'avatar' in user && user.avatar ? ( - - ) : ( - - {user.name[0]} - - )} - -
- {user.name} - {mutedUsers.includes(user.id) && ( - 已禁言 - )} -
-
- {user.name !== "我" && ( -
- - - - - - - {mutedUsers.includes(user.id) ? '取消禁言' : '禁言'} - - - - {/**/} -
- )} -
- ))} -
-
-
-
-
+ + {/* Members Management Dialog */} + setIsGroupDiscussionMode(!isGroupDiscussionMode)} + getAvatarData={getAvatarData} + /> {/* 添加 SharePoster 组件 */} diff --git a/src/components/MembersManagement.tsx b/src/components/MembersManagement.tsx new file mode 100644 index 0000000..66ab258 --- /dev/null +++ b/src/components/MembersManagement.tsx @@ -0,0 +1,116 @@ +import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Button } from "@/components/ui/button"; +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { UserPlus, Mic, MicOff } from 'lucide-react'; +import { type AICharacter } from "@/config/aiCharacters"; +import { Switch } from "@/components/ui/switch"; + +interface User { + id: number | string; + name: string; + avatar?: string; +} + +interface MembersManagementProps { + showMembers: boolean; + setShowMembers: (show: boolean) => void; + users: (User | AICharacter)[]; + mutedUsers: string[]; + handleToggleMute: (userId: string) => void; + getAvatarData: (name: string) => { backgroundColor: string; text: string }; + isGroupDiscussionMode: boolean; + onToggleGroupDiscussion: () => void; +} + +export const MembersManagement = ({ + showMembers, + setShowMembers, + users, + mutedUsers, + handleToggleMute, + getAvatarData, + isGroupDiscussionMode, + onToggleGroupDiscussion +}: MembersManagementProps) => { + return ( + + + + 群聊配置 + +
+
+
+
+
全员讨论模式
+
开启后全员回复讨论
+
+ +
+
+
+ 当前成员({users.length}) + +
+ +
+ {users.map((user) => ( +
+
+ + {'avatar' in user && user.avatar ? ( + + ) : ( + + {user.name[0]} + + )} + +
+ {user.name} + {mutedUsers.includes(user.id as string) && ( + 已禁言 + )} +
+
+ {user.name !== "我" && ( +
+ + + + + + + {mutedUsers.includes(user.id as string) ? '取消禁言' : '禁言'} + + + +
+ )} +
+ ))} +
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/SharePoster.tsx b/src/components/SharePoster.tsx index f2a1ac8..327520f 100644 --- a/src/components/SharePoster.tsx +++ b/src/components/SharePoster.tsx @@ -60,7 +60,7 @@ export function SharePoster({ isOpen, onClose, chatAreaRef }: SharePosterProps) chatAreaRef.current.scrollTop = 0; const viewportWidth = Math.min(window.innerWidth, document.documentElement.clientWidth); - const extraSpace = 20; + const extraSpace = 6; const targetWidth = viewportWidth * 0.95 - (extraSpace * 2); const currentWidth = messageContainer.getBoundingClientRect().width; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx new file mode 100644 index 0000000..2c063f6 --- /dev/null +++ b/src/components/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + 关闭 + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} \ No newline at end of file diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..455c23b --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/config/aiCharacters.ts b/src/config/aiCharacters.ts index 594658c..f25d5cb 100644 --- a/src/config/aiCharacters.ts +++ b/src/config/aiCharacters.ts @@ -48,8 +48,8 @@ export function shedulerAICharacter(message: string, allTags: string[]): AIChara avatar: "", custom_prompt: `你是一个群聊总结分析专家,你在一个聊天群里,请分析群用户消息和上文群聊内容 1、只能从给定的标签列表中选择最相关的标签,可选标签:${allTags.join(', ')}。 - 2、请只返回标签列表,用逗号分隔,不要有其他解释。 - 3、回复格式示例:标签1,标签2,标签3` + 2、请只返回标签列表,用逗号分隔,不要有其他解释, 不要有任何前缀。 + 3、回复格式示例:文字游戏, 生活助手, 娱乐` } } @@ -104,7 +104,7 @@ export function generateAICharacters(groupName: string): AICharacter[] { model: modelConfigs[2].model, avatar: "/img/yuanbao.png", custom_prompt: `你是一个名叫"元宝"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, - tags: ["微信生态", "新闻搜索", "文字游戏", "生活助手", "娱乐", "信息总结"] + tags: ["微信生态", "新闻报道", "文字游戏", "生活助手", "娱乐", "信息总结"] }, { id: 'ai5', @@ -122,16 +122,16 @@ export function generateAICharacters(groupName: string): AICharacter[] { model: modelConfigs[0].model, avatar: "/img/qwen.jpg", custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, - tags: ["广告文案","分析数据","文字游戏", "阿里"] + tags: ["广告文案","分析数据","文字游戏","信息总结", "阿里"] }, { id: 'ai7', name: "DeepSeek", - personality: "deepseek-v3", - model: modelConfigs[1].model, + personality: "deepseek-r1", + model: modelConfigs[3].model, avatar: "/img/ds.svg", custom_prompt: `你是一个名叫"DeepSeek"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, - tags: ["深度推理", "编程", "文字游戏", "数学"] + tags: ["深度推理", "编程", "文字游戏", "数学", "信息总结"] } ]; } diff --git a/src/index.css b/src/index.css index c22511b..de4587f 100644 --- a/src/index.css +++ b/src/index.css @@ -126,3 +126,14 @@ @keyframes blink { 50% { opacity: 0; } } + + + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +}