diff --git a/functions/api/chat.ts b/functions/api/chat.ts
index 323ec4a..076fd47 100644
--- a/functions/api/chat.ts
+++ b/functions/api/chat.ts
@@ -25,7 +25,7 @@ export async function onRequestPost({ env, request }) {
// 根据性格设置不同的系统提示语
let systemPrompt = "";
- systemPrompt = custom_prompt + "\n 注意重要:1、你在群里叫" + aiName + "认准自己的身份,你的输出内容不要加" + aiName + ":这种多余前缀;2、如果用户提出玩游戏,比如成语接龙等,严格按照游戏规则,不要说一大堆,要简短精炼; 3、不要重复别人的话!"
+ systemPrompt = custom_prompt + "\n 注意重要:1、你在群里叫" + aiName + "认准自己的身份; 2、你的输出内容不要加" + aiName + ":这种多余前缀;3、如果用户提出玩游戏,比如成语接龙等,严格按照游戏规则,不要说一大堆,要简短精炼; 4、不要重复别人的话!"
// 构建完整的消息历史
const baseMessages = [
diff --git a/package-lock.json b/package-lock.json
index d741afc..a791fab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.3",
+ "@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.8",
@@ -1303,9 +1304,31 @@
}
}
},
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-separator/-/react-separator-1.1.2.tgz",
+ "integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.2"
+ },
+ "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-slot": {
"version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
@@ -1350,7 +1373,7 @@
},
"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",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
"integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
@@ -2411,7 +2434,7 @@
},
"node_modules/class-variance-authority": {
"version": "0.7.1",
- "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"dependencies": {
"clsx": "^2.1.1"
@@ -3743,7 +3766,7 @@
},
"node_modules/lucide-react": {
"version": "0.263.1",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz",
+ "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.263.1.tgz",
"integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
diff --git a/package.json b/package.json
index 6f58ab7..67f5c97 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.3",
+ "@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.8",
diff --git a/src/components/ChatUI.tsx b/src/components/ChatUI.tsx
index a427fa7..56dea00 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, Settings2 } from 'lucide-react';
+import { Send, Share2, Settings2, ChevronLeft } from 'lucide-react';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -25,9 +25,9 @@ import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
-import html2canvas from 'html2canvas';
import { SharePoster } from '@/components/SharePoster';
import { MembersManagement } from '@/components/MembersManagement';
+import Sidebar from './Sidebar';
// 使用本地头像数据,避免外部依赖
const getAvatarData = (name: string) => {
@@ -146,9 +146,15 @@ 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));
+ // 使用当前选中的群组在 groups 数组中的索引
+ const [selectedGroupIndex, setSelectedGroupIndex] = useState(0); // 默认选中第1个群组
+ const [group, setGroup] = useState(groups[selectedGroupIndex]);
+ const [isGroupDiscussionMode, setIsGroupDiscussionMode] = useState(group.isGroupDiscussionMode);
+ const groupAiCharacters = generateAICharacters(group.name)
+ .filter(character => group.members.includes(character.id))
+ .sort((a, b) => {
+ return group.members.indexOf(a.id) - group.members.indexOf(b.id);
+ });
const [users, setUsers] = useState([
{ id: 1, name: "我" },
...groupAiCharacters
@@ -258,7 +264,7 @@ const ChatUI = () => {
history: messageHistory,
index: i,
aiName: selectedGroupAiCharacters[i].name,
- custom_prompt: selectedGroupAiCharacters[i].custom_prompt
+ custom_prompt: selectedGroupAiCharacters[i].custom_prompt + "\n" + group.description
}),
});
@@ -377,220 +383,240 @@ const ChatUI = () => {
setShowPoster(true);
};
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+
+ // 切换侧边栏状态的函数
+ const toggleSidebar = () => {
+ setSidebarOpen(!sidebarOpen);
+ };
+
+ // 处理群组选择
+ const handleSelectGroup = (index: number) => {
+ setSelectedGroupIndex(index);
+ const newGroup = groups[index];
+ setGroup(newGroup);
+
+ // 重新生成当前群组的 AI 角色,并按照 members 数组的顺序排序
+ const newGroupAiCharacters = generateAICharacters(newGroup.name)
+ .filter(character => newGroup.members.includes(character.id))
+ .sort((a, b) => {
+ return newGroup.members.indexOf(a.id) - newGroup.members.indexOf(b.id);
+ });
+
+ // 更新用户列表
+ setUsers([
+ { id: 1, name: "我" },
+ ...newGroupAiCharacters
+ ]);
+ setIsGroupDiscussionMode(newGroup.isGroupDiscussionMode);
+
+ // 重置消息
+ setMessages([]);
+
+ // 可选:关闭侧边栏(在移动设备上)
+ if (window.innerWidth < 768) {
+ setSidebarOpen(false);
+ }
+ };
+
return (
<>
-
- {/* Header */}
-
-
- {/* 左侧群组信息 */}
-
-
-
- {users.length === 1 ? (
-
- ) : users.length === 2 ? (
-
- {users.slice(0, 2).map((user, index) => (
-
- ))}
-
- ) : users.length === 3 ? (
-
-
- {users.slice(0, 2).map((user, index) => (
-
- ))}
-
-
-
-
-
- ) : (
-
- {users.slice(0, 4).map((user, index) => (
-
- ))}
+
+ {/* 传递 selectedGroupIndex 和 onSelectGroup 回调给 Sidebar */}
+
+
+ {/* 聊天主界面 */}
+
+ {/* Header */}
+
+
+ {/* 左侧群组信息 */}
+
+
+
+
+
+
{group.name}({users.length})
+
+
+ {/* 右侧头像组和按钮 */}
+
+
+ {users.slice(0, 4).map((user) => {
+ const avatarData = getAvatarData(user.name);
+ return (
+
+
+
+
+ {'avatar' in user && user.avatar ? (
+
+ ) : (
+
+ {avatarData.text}
+
+ )}
+
+
+
+ {user.name}
+
+
+
+ );
+ })}
+ {users.length > 4 && (
+
+ +{users.length - 4}
)}
-
-
-
-
{group.name}
-
{users.length} 名成员
+
-
- {/* 右侧头像组和按钮 */}
-
-
- {users.slice(0, 4).map((user) => {
- const avatarData = getAvatarData(user.name);
- return (
-
-
-
-
- {'avatar' in user && user.avatar ? (
-
- ) : (
-
- {avatarData.text}
-
- )}
-
-
-
- {user.name}
-
-
-
- );
- })}
- {users.length > 4 && (
-
- +{users.length - 4}
+
+
+ {/* Main Chat Area */}
+
+
+
+ {messages.map((message) => (
+
+ {message.sender.name !== "我" && (
+
+ {'avatar' in message.sender && message.sender.avatar ? (
+
+ ) : (
+
+ {message.sender.name[0]}
+
+ )}
+
+ )}
+
+
{message.sender.name}
+
+
+ {message.content}
+
+ {message.isAI && isTyping && currentMessageRef.current === message.id && (
+ ▋
+ )}
+
+
+ {message.sender.name === "我" && (
+
+ {'avatar' in message.sender && message.sender.avatar ? (
+
+ ) : (
+
+ {message.sender.name[0]}
+
+ )}
+
+ )}
- )}
+ ))}
+
+ {/* 添加一个二维码 */}
+
+

+
扫码体验AI群聊
+
-
+
+
+ {/* Input Area */}
+
+
+ {messages.length > 0 && (
+
+
+
+
+
+
+ 分享聊天记录
+
+
+
+ )}
+
setInputMessage(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
+ />
+
-
-
- {/* Main Chat Area */}
-
-
-
- {messages.map((message) => (
-
- {message.sender.name !== "我" && (
-
- {'avatar' in message.sender && message.sender.avatar ? (
-
- ) : (
-
- {message.sender.name[0]}
-
- )}
-
- )}
-
-
{message.sender.name}
-
-
- {message.content}
-
- {message.isAI && isTyping && currentMessageRef.current === message.id && (
- ▋
- )}
-
-
- {message.sender.name === "我" && (
-
- {'avatar' in message.sender && message.sender.avatar ? (
-
- ) : (
-
- {message.sender.name[0]}
-
- )}
-
- )}
-
- ))}
-
- {/* 添加一个二维码 */}
-
-

-
扫码体验AI群聊
-
-
-
-
-
- {/* Input Area */}
-
-
- {messages.length > 0 && (
-
-
-
-
-
-
- 分享聊天记录
-
-
-
- )}
-
setInputMessage(e.target.value)}
- onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
- />
-
-
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index e714782..c417600 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -4,10 +4,11 @@ import Header from './Header';
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
-
-
- {children}
-
+
+
+ {children}
+
+
);
};
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
new file mode 100644
index 0000000..b960def
--- /dev/null
+++ b/src/components/Sidebar.tsx
@@ -0,0 +1,166 @@
+import React from 'react';
+import { Button } from "@/components/ui/button";
+import { MessageSquareIcon, PlusCircleIcon, MenuIcon, PanelLeftCloseIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+import GitHubButton from 'react-github-btn';
+import '@fontsource/audiowide';
+import { groups } from "@/config/groups";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+// 根据群组ID生成固定的随机颜色
+const getRandomColor = (index: number) => {
+ const colors = ['blue', 'green', 'yellow', 'purple', 'pink', 'indigo', 'red', 'orange', 'teal'];
+ //增加hash
+ const hashCode = index.toString().split('').reduce((acc, char) => {
+ return char.charCodeAt(0) + ((acc << 5) - acc);
+ }, 0);
+ return colors[hashCode % colors.length];
+};
+
+interface SidebarProps {
+ isOpen: boolean;
+ toggleSidebar: () => void;
+ selectedGroupIndex?: number;
+ onSelectGroup?: (index: number) => void;
+}
+
+const Sidebar = ({ isOpen, toggleSidebar, selectedGroupIndex = 0, onSelectGroup }: SidebarProps) => {
+
+ return (
+ <>
+ {/* 侧边栏 - 在移动设备上可以隐藏,在桌面上始终显示 */}
+
+
+
+
+
+ 群列表
+
+
+
+
+
+
+
+ {/* GitHub Star Button - 只在侧边栏打开时显示,放在底部 */}
+
+ {/* 标题移至底部 */}
+
+
+ {isOpen && (
+
+
+ Star
+
+
+ )}
+
+
+
+
+ {/* 移动设备上的遮罩层,点击时关闭侧边栏 */}
+ {isOpen && (
+
+ )}
+ >
+ );
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000..6d7f122
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+ ) => (
+
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..dcf03e0
--- /dev/null
+++ b/src/components/ui/sidebar.tsx
@@ -0,0 +1,761 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { VariantProps, cva } from "class-variance-authority"
+import { PanelLeft } from "lucide-react"
+
+import { useIsMobile } from "@/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import { Sheet, SheetContent } from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContext = {
+ state: "expanded" | "collapsed"
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.")
+ }
+
+ return context
+}
+
+const SidebarProvider = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+ }
+>(
+ (
+ {
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProp ?? _open
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile
+ ? setOpenMobile((open) => !open)
+ : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+ (event.metaKey || event.ctrlKey)
+ ) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ window.addEventListener("keydown", handleKeyDown)
+ return () => window.removeEventListener("keydown", handleKeyDown)
+ }, [toggleSidebar])
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed"
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+
+ {children}
+
+
+
+ )
+ }
+)
+SidebarProvider.displayName = "SidebarProvider"
+
+const Sidebar = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ side?: "left" | "right"
+ variant?: "sidebar" | "floating" | "inset"
+ collapsible?: "offcanvas" | "icon" | "none"
+ }
+>(
+ (
+ {
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === "none") {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (isMobile) {
+ return (
+
+
+ {children}
+
+
+ )
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ )
+ }
+)
+Sidebar.displayName = "Sidebar"
+
+const SidebarTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentProps
+>(({ className, onClick, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+})
+SidebarTrigger.displayName = "SidebarTrigger"
+
+const SidebarRail = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button">
+>(({ className, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+})
+SidebarRail.displayName = "SidebarRail"
+
+const SidebarInset = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"main">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarInset.displayName = "SidebarInset"
+
+const SidebarInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentProps
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarInput.displayName = "SidebarInput"
+
+const SidebarHeader = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarHeader.displayName = "SidebarHeader"
+
+const SidebarFooter = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarFooter.displayName = "SidebarFooter"
+
+const SidebarSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentProps
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarSeparator.displayName = "SidebarSeparator"
+
+const SidebarContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarContent.displayName = "SidebarContent"
+
+const SidebarGroup = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarGroup.displayName = "SidebarGroup"
+
+const SidebarGroupLabel = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "div"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarGroupLabel.displayName = "SidebarGroupLabel"
+
+const SidebarGroupAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 after:md:hidden",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarGroupAction.displayName = "SidebarGroupAction"
+
+const SidebarGroupContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+
+))
+SidebarGroupContent.displayName = "SidebarGroupContent"
+
+const SidebarMenu = React.forwardRef<
+ HTMLUListElement,
+ React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+
+))
+SidebarMenu.displayName = "SidebarMenu"
+
+const SidebarMenuItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+
+))
+SidebarMenuItem.displayName = "SidebarMenuItem"
+
+const sidebarMenuButtonVariants = cva(
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline:
+ "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ },
+ size: {
+ default: "h-8 text-sm",
+ sm: "h-7 text-xs",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const SidebarMenuButton = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & {
+ asChild?: boolean
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps
+ } & VariantProps
+>(
+ (
+ {
+ asChild = false,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+ },
+ ref
+ ) => {
+ const Comp = asChild ? Slot : "button"
+ const { isMobile, state } = useSidebar()
+
+ const button = (
+
+ )
+
+ if (!tooltip) {
+ return button
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ }
+ }
+
+ return (
+
+ {button}
+
+
+ )
+ }
+)
+SidebarMenuButton.displayName = "SidebarMenuButton"
+
+const SidebarMenuAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & {
+ asChild?: boolean
+ showOnHover?: boolean
+ }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 after:md:hidden",
+ "peer-data-[size=sm]/menu-button:top-1",
+ "peer-data-[size=default]/menu-button:top-1.5",
+ "peer-data-[size=lg]/menu-button:top-2.5",
+ "group-data-[collapsible=icon]:hidden",
+ showOnHover &&
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarMenuAction.displayName = "SidebarMenuAction"
+
+const SidebarMenuBadge = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+
+))
+SidebarMenuBadge.displayName = "SidebarMenuBadge"
+
+const SidebarMenuSkeleton = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ showIcon?: boolean
+ }
+>(({ className, showIcon = false, ...props }, ref) => {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ }, [])
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ )
+})
+SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
+
+const SidebarMenuSub = React.forwardRef<
+ HTMLUListElement,
+ React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+
+))
+SidebarMenuSub.displayName = "SidebarMenuSub"
+
+const SidebarMenuSubItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentProps<"li">
+>(({ ...props }, ref) => )
+SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
+
+const SidebarMenuSubButton = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentProps<"a"> & {
+ asChild?: boolean
+ size?: "sm" | "md"
+ isActive?: boolean
+ }
+>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+ span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+ size === "sm" && "text-xs",
+ size === "md" && "text-sm",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+}
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..d7e45f7
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+ className,
+ ...props
+}: React.HTMLAttributes) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/config/aiCharacters.ts b/src/config/aiCharacters.ts
index 74b0184..1fbe486 100644
--- a/src/config/aiCharacters.ts
+++ b/src/config/aiCharacters.ts
@@ -54,7 +54,7 @@ export function shedulerAICharacter(message: string, allTags: string[]): AIChara
id: 'ai0',
name: "调度器",
personality: "sheduler",
- model: modelConfigs[6].model,
+ model: modelConfigs[5].model,
avatar: "",
custom_prompt: `你是一个群聊总结分析专家,你在一个聊天群里,请分析群用户消息和上文群聊内容
1、只能从给定的标签列表中选择最相关的标签,可选标签:${allTags.join(', ')}。
@@ -150,7 +150,7 @@ export function generateAICharacters(groupName: string): AICharacter[] {
model: modelConfigs[5].model,
avatar: "/img/glm.gif",
custom_prompt: `你是一个名叫"智谱"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
- tags: ["新闻报道","分析数据","文字游戏","信息总结", "聊天"]
+ tags: ["深度推理","数学","信息总结", "分析数据","文字游戏", "聊天"]
}
];
}
diff --git a/src/config/groups.ts b/src/config/groups.ts
index b9c0d89..925de8d 100644
--- a/src/config/groups.ts
+++ b/src/config/groups.ts
@@ -4,19 +4,22 @@ export interface Group {
name: string;
description: string;
members: string[];
+ isGroupDiscussionMode: boolean;
}
export const groups: Group[] = [
{
id: 'group1',
- name: '硅碳摸鱼交流群',
- description: '硅碳摸鱼交流群',
- members: ['ai1', 'ai2', 'ai3']
+ name: '硅碳生命体交流群',
+ description: '',
+ members: [ 'ai8', 'ai4', 'ai5', 'ai6', 'ai7'],
+ isGroupDiscussionMode: false
},
{
id: 'group2',
- name: '硅碳生命体交流群',
- description: '硅碳生命体交流群',
- members: ['ai4', 'ai5', 'ai6', 'ai7', 'ai8']
+ name: 'AI成语接龙游戏群',
+ description: '可以适当打招呼问候自我介绍,但是本群主线是成语接龙游戏,请严格按照文字成语接龙规则,不能过度闲聊',
+ isGroupDiscussionMode: true,
+ members: [ 'ai8', 'ai4', 'ai5', 'ai6', 'ai7'],
}
];
diff --git a/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx
new file mode 100644
index 0000000..2b0fe1d
--- /dev/null
+++ b/src/hooks/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener("change", onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener("change", onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/src/index.css b/src/index.css
index de4587f..2a5b1ac 100644
--- a/src/index.css
+++ b/src/index.css
@@ -56,7 +56,23 @@
--chart-5: 27 87% 67%;
--radius: 0.5rem
- }
+ ;
+
+ --sidebar-background: 0 0% 98%;
+
+ --sidebar-foreground: 240 5.3% 26.1%;
+
+ --sidebar-primary: 240 5.9% 10%;
+
+ --sidebar-primary-foreground: 0 0% 98%;
+
+ --sidebar-accent: 240 4.8% 95.9%;
+
+ --sidebar-accent-foreground: 240 5.9% 10%;
+
+ --sidebar-border: 220 13% 91%;
+
+ --sidebar-ring: 217.2 91.2% 59.8%}
.dark {
--background: 0 0% 3.9%;
@@ -106,7 +122,23 @@
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%
- }
+ ;
+
+ --sidebar-background: 240 5.9% 10%;
+
+ --sidebar-foreground: 240 4.8% 95.9%;
+
+ --sidebar-primary: 224.3 76.3% 48%;
+
+ --sidebar-primary-foreground: 0 0% 100%;
+
+ --sidebar-accent: 240 3.7% 15.9%;
+
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+
+ --sidebar-border: 240 3.7% 15.9%;
+
+ --sidebar-ring: 217.2 91.2% 59.8%}
}
@layer base {
diff --git a/tailwind.config.js b/tailwind.config.js
index 02f400e..609087a 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -49,6 +49,16 @@ module.exports = {
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
+ },
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))'
}
}
}