From 99ca8b1ec89a007f6717ba58dc956e61f8ac918a Mon Sep 17 00:00:00 2001 From: maojindao55 Date: Wed, 19 Mar 2025 22:41:30 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=83=A8=E5=88=86=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/api/init.ts | 20 ++++ functions/api/scheduler.ts | 4 +- src/components/ChatUI.tsx | 187 +++++++++++++++++++++++-------------- src/components/Sidebar.tsx | 5 +- src/config/aiCharacters.ts | 18 ++-- 5 files changed, 151 insertions(+), 83 deletions(-) create mode 100644 functions/api/init.ts diff --git a/functions/api/init.ts b/functions/api/init.ts new file mode 100644 index 0000000..68495b0 --- /dev/null +++ b/functions/api/init.ts @@ -0,0 +1,20 @@ +import {generateAICharacters } from '../../src/config/aiCharacters'; +import { groups } from '../../src/config/groups'; +export async function onRequestGet({ env, request }) { + console.log('init'); + try { + return Response.json({ + code: 200, + data: { + groups: groups, + characters: generateAICharacters('#groupName#', '#allTags#'), + } + }); + } catch (error) { + console.error(error); + return Response.json( + { error: error.message }, + { status: 500 } + ); + } + } \ No newline at end of file diff --git a/functions/api/scheduler.ts b/functions/api/scheduler.ts index 2827981..e425471 100644 --- a/functions/api/scheduler.ts +++ b/functions/api/scheduler.ts @@ -1,4 +1,4 @@ -import { modelConfigs, shedulerAICharacter } from '../../src/config/aiCharacters'; +import { modelConfigs, generateAICharacters } from '../../src/config/aiCharacters'; import OpenAI from 'openai'; interface AICharacter { @@ -32,7 +32,7 @@ export async function onRequestPost({ env, request }) { } async function analyzeMessageWithAI(message: string, allTags: string[], env: any, history: MessageHistory[] = []): Promise { - const shedulerAI = shedulerAICharacter(message, allTags); + const shedulerAI = generateAICharacters(message, allTags.join(','))[0]; const modelConfig = modelConfigs.find(config => config.model === shedulerAI.model); const apiKey = env[modelConfig.apiKey]; if (!apiKey) { diff --git a/src/components/ChatUI.tsx b/src/components/ChatUI.tsx index bc7bb82..5e21286 100644 --- a/src/components/ChatUI.tsx +++ b/src/components/ChatUI.tsx @@ -12,8 +12,6 @@ import { TooltipTrigger } from "@/components/ui/tooltip"; -import {generateAICharacters} from "@/config/aiCharacters"; -import { groups } from "@/config/groups"; import type { AICharacter } from "@/config/aiCharacters"; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm' @@ -139,54 +137,115 @@ const KaTeXStyle = () => ( `}} /> ); -const ChatUI = () => { - // 使用当前选中的群组在 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 allNames = groupAiCharacters.map(character => character.name); - allNames.push('user'); - const [users, setUsers] = useState([ - { id: 1, name: "我" }, - ...groupAiCharacters - ]); - const [showMembers, setShowMembers] = useState(false); - const [messages, setMessages] = useState([ +// Vite环境变量访问方式 +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; - ]); +const ChatUI = () => { + //获取url参数 + const urlParams = new URLSearchParams(window.location.search); + const id = urlParams.get('id')? parseInt(urlParams.get('id')!) : 0; + // 1. 所有的 useState 声明 + const [groups, setGroups] = useState([]); + const [selectedGroupIndex, setSelectedGroupIndex] = useState(id); + const [group, setGroup] = useState(null); + const [groupAiCharacters, setGroupAiCharacters] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isInitializing, setIsInitializing] = useState(false); + const [isGroupDiscussionMode, setIsGroupDiscussionMode] = useState(false); + const [users, setUsers] = useState([]); + const [allNames, setAllNames] = useState([]); + const [showMembers, setShowMembers] = useState(false); + const [messages, setMessages] = useState([]); const [showAd, setShowAd] = useState(true); const [inputMessage, setInputMessage] = useState(""); - const [isLoading, setIsLoading] = useState(false); const [pendingContent, setPendingContent] = useState(""); const [isTyping, setIsTyping] = useState(false); + const [mutedUsers, setMutedUsers] = useState([]); + const [showPoster, setShowPoster] = useState(false); + const [sidebarOpen, setSidebarOpen] = useState(false); + + // 2. 所有的 useRef 声明 const currentMessageRef = useRef(null); const typewriterRef = useRef(null); - const accumulatedContentRef = useRef(""); // 用于跟踪完整内容 + const accumulatedContentRef = useRef(""); const messagesEndRef = useRef(null); + const chatAreaRef = useRef(null); + const abortController = useRef(new AbortController()); - // 添加禁言状态 - const [mutedUsers, setMutedUsers] = useState([]); + // 添加一个 ref 来跟踪是否已经初始化 + const isInitialized = useRef(false); - const abortController = new AbortController(); + // 3. 所有的 useEffect + useEffect(() => { + // 如果已经初始化过,则直接返回 + if (isInitialized.current) return; - const handleRemoveUser = (userId: number) => { - setUsers(users.filter(user => user.id !== userId)); - }; + const initData = async () => { + try { + const response = await fetch(`${API_BASE_URL}/api/init`); + if (!response.ok) { + throw new Error('初始化数据失败'); + } + const {data} = await response.json(); + console.log("初始化数据", data); + const group = data.groups[selectedGroupIndex]; + const characters = data.characters; + setGroups(data.groups); + setGroup(group); + setIsInitializing(false); + setIsGroupDiscussionMode(group.isGroupDiscussionMode); + const groupAiCharacters = characters + .filter(character => group.members.includes(character.id)) + .filter(character => character.personality !== "sheduler") + .sort((a, b) => { + return group.members.indexOf(a.id) - group.members.indexOf(b.id); + }); + setGroupAiCharacters(groupAiCharacters); + const allNames = groupAiCharacters.map(character => character.name); + allNames.push('user'); + setAllNames(allNames); + setUsers([ + { id: 1, name: "我" }, + ...groupAiCharacters + ]); + } catch (error) { + console.error("初始化数据失败:", error); + setIsInitializing(false); + } + }; - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; + initData(); + // 标记为已初始化 + isInitialized.current = true; + }, []); // 依赖数组保持为空 useEffect(() => { scrollToBottom(); }, [messages]); - // 添加禁言/取消禁言处理函数 + useEffect(() => { + if (messages.length > 0) { + setShowAd(false); + } + }, [messages]); + + useEffect(() => { + return () => { + if (typewriterRef.current) { + clearInterval(typewriterRef.current); + } + }; + }, []); + + // 4. 工具函数 + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + const handleRemoveUser = (userId: number) => { + setUsers(users.filter(user => user.id !== userId)); + }; + const handleToggleMute = (userId: string) => { setMutedUsers(prev => prev.includes(userId) @@ -195,11 +254,22 @@ const ChatUI = () => { ); }; - useEffect(() => { - if (messages.length > 0) { - setShowAd(false); - } - }, [messages]); + const handleShareChat = () => { + setShowPoster(true); + }; + + const toggleSidebar = () => { + setSidebarOpen(!sidebarOpen); + }; + + // 5. 加载检查 + if (isInitializing || !group) { + return ( +
+
+
+ ); + } const handleSendMessage = async () => { //判断是否Loding @@ -227,7 +297,7 @@ const ChatUI = () => { })); let selectedGroupAiCharacters = groupAiCharacters; if (!isGroupDiscussionMode) { - const shedulerResponse = await fetch('/api/scheduler', { + const shedulerResponse = await fetch(`${API_BASE_URL}/api/scheduler`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -255,7 +325,7 @@ const ChatUI = () => { setMessages(prev => [...prev, aiMessage]); try { - const response = await fetch('/api/chat', { + const response = await fetch(`${API_BASE_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -267,7 +337,7 @@ const ChatUI = () => { history: messageHistory, index: i, aiName: selectedGroupAiCharacters[i].name, - custom_prompt: selectedGroupAiCharacters[i].custom_prompt + "\n" + group.description + custom_prompt: selectedGroupAiCharacters[i].custom_prompt.replace('#groupName#', group.name) + "\n" + group.description }), }); @@ -388,37 +458,16 @@ const ChatUI = () => { }; const handleCancel = () => { - abortController.abort(); - }; - - // 清理打字机效果 - useEffect(() => { - return () => { - if (typewriterRef.current) { - clearInterval(typewriterRef.current); - } - }; - }, []); - - // 添加对聊天区域的引用 - const chatAreaRef = useRef(null); - - // 更新分享函数 - const [showPoster, setShowPoster] = useState(false); - - const handleShareChat = () => { - setShowPoster(true); - }; - - const [sidebarOpen, setSidebarOpen] = useState(false); - - // 切换侧边栏状态的函数 - const toggleSidebar = () => { - setSidebarOpen(!sidebarOpen); + abortController.current.abort(); }; // 处理群组选择 const handleSelectGroup = (index: number) => { + //进行跳转到?id=index + window.location.href = `?id=${index}`; + return; + /* + //跳转后,关闭当前页面 setSelectedGroupIndex(index); const newGroup = groups[index]; setGroup(newGroup); @@ -444,6 +493,7 @@ const ChatUI = () => { if (window.innerWidth < 768) { setSidebarOpen(false); } + */ }; return ( @@ -457,6 +507,7 @@ const ChatUI = () => { toggleSidebar={toggleSidebar} selectedGroupIndex={selectedGroupIndex} onSelectGroup={handleSelectGroup} + groups={groups} /> {/* 聊天主界面 */} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index a879de7..9415f0a 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -4,7 +4,7 @@ import { MessageSquareIcon, PlusCircleIcon, MenuIcon, PanelLeftCloseIcon } from import { cn } from "@/lib/utils"; import GitHubButton from 'react-github-btn'; import '@fontsource/audiowide'; -import { groups } from "@/config/groups"; +//import { groups } from "@/config/groups"; import { AdSection } from './AdSection'; import { Tooltip, @@ -28,9 +28,10 @@ interface SidebarProps { toggleSidebar: () => void; selectedGroupIndex?: number; onSelectGroup?: (index: number) => void; + groups: Group[]; } -const Sidebar = ({ isOpen, toggleSidebar, selectedGroupIndex = 0, onSelectGroup }: SidebarProps) => { +const Sidebar = ({ isOpen, toggleSidebar, selectedGroupIndex = 0, onSelectGroup, groups }: SidebarProps) => { return ( <> diff --git a/src/config/aiCharacters.ts b/src/config/aiCharacters.ts index 3cf8841..bf5e2b9 100644 --- a/src/config/aiCharacters.ts +++ b/src/config/aiCharacters.ts @@ -63,24 +63,20 @@ export interface AICharacter { tags?: string[]; // 可选的标签 } -// 调度器配置信息 -export function shedulerAICharacter(message: string, allTags: string[]): AICharacter { - return { +// 添加一个函数来生成带有群名的角色配置 +export function generateAICharacters(groupName: string, allTags: string): AICharacter[] { + return [ + { id: 'ai0', name: "调度器", personality: "sheduler", model: modelConfigs[0].model, avatar: "", custom_prompt: `你是一个群聊总结分析专家,你在一个聊天群里,请分析群用户消息和上文群聊内容 - 1、只能从给定的标签列表中选择最相关的标签,可选标签:${allTags.join(', ')}。 + 1、只能从给定的标签列表中选择最相关的标签,可选标签:“${allTags}”。 2、请只返回标签列表,用逗号分隔,不要有其他解释, 不要有任何前缀。 - 3、回复格式示例:文字游戏, 生活助手, 娱乐` - } -} - -// 添加一个函数来生成带有群名的角色配置 -export function generateAICharacters(groupName: string): AICharacter[] { - return [ + 3、回复格式示例:文字游戏, 新闻报道, 娱乐` + }, { id: 'ai1', name: "暖心姐",