重构部分接口数据
This commit is contained in:
20
functions/api/init.ts
Normal file
20
functions/api/init.ts
Normal file
@@ -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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { modelConfigs, shedulerAICharacter } from '../../src/config/aiCharacters';
|
import { modelConfigs, generateAICharacters } from '../../src/config/aiCharacters';
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
|
|
||||||
interface AICharacter {
|
interface AICharacter {
|
||||||
@@ -32,7 +32,7 @@ export async function onRequestPost({ env, request }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function analyzeMessageWithAI(message: string, allTags: string[], env: any, history: MessageHistory[] = []): Promise<string[]> {
|
async function analyzeMessageWithAI(message: string, allTags: string[], env: any, history: MessageHistory[] = []): Promise<string[]> {
|
||||||
const shedulerAI = shedulerAICharacter(message, allTags);
|
const shedulerAI = generateAICharacters(message, allTags.join(','))[0];
|
||||||
const modelConfig = modelConfigs.find(config => config.model === shedulerAI.model);
|
const modelConfig = modelConfigs.find(config => config.model === shedulerAI.model);
|
||||||
const apiKey = env[modelConfig.apiKey];
|
const apiKey = env[modelConfig.apiKey];
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import {
|
|||||||
TooltipTrigger
|
TooltipTrigger
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
|
||||||
import {generateAICharacters} from "@/config/aiCharacters";
|
|
||||||
import { groups } from "@/config/groups";
|
|
||||||
import type { AICharacter } from "@/config/aiCharacters";
|
import type { AICharacter } from "@/config/aiCharacters";
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
@@ -139,54 +137,115 @@ const KaTeXStyle = () => (
|
|||||||
`}} />
|
`}} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const ChatUI = () => {
|
// Vite环境变量访问方式
|
||||||
// 使用当前选中的群组在 groups 数组中的索引
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
|
||||||
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([
|
|
||||||
|
|
||||||
]);
|
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 [showAd, setShowAd] = useState(true);
|
||||||
const [inputMessage, setInputMessage] = useState("");
|
const [inputMessage, setInputMessage] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [pendingContent, setPendingContent] = useState("");
|
const [pendingContent, setPendingContent] = useState("");
|
||||||
const [isTyping, setIsTyping] = useState(false);
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const [mutedUsers, setMutedUsers] = useState<string[]>([]);
|
||||||
|
const [showPoster, setShowPoster] = useState(false);
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
|
// 2. 所有的 useRef 声明
|
||||||
const currentMessageRef = useRef<number | null>(null);
|
const currentMessageRef = useRef<number | null>(null);
|
||||||
const typewriterRef = useRef<NodeJS.Timeout | null>(null);
|
const typewriterRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const accumulatedContentRef = useRef(""); // 用于跟踪完整内容
|
const accumulatedContentRef = useRef("");
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const chatAreaRef = useRef<HTMLDivElement>(null);
|
||||||
|
const abortController = useRef(new AbortController());
|
||||||
|
|
||||||
// 添加禁言状态
|
// 添加一个 ref 来跟踪是否已经初始化
|
||||||
const [mutedUsers, setMutedUsers] = useState<string[]>([]);
|
const isInitialized = useRef(false);
|
||||||
|
|
||||||
const abortController = new AbortController();
|
// 3. 所有的 useEffect
|
||||||
|
useEffect(() => {
|
||||||
|
// 如果已经初始化过,则直接返回
|
||||||
|
if (isInitialized.current) return;
|
||||||
|
|
||||||
const handleRemoveUser = (userId: number) => {
|
const initData = async () => {
|
||||||
setUsers(users.filter(user => user.id !== userId));
|
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 = () => {
|
initData();
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
// 标记为已初始化
|
||||||
};
|
isInitialized.current = true;
|
||||||
|
}, []); // 依赖数组保持为空
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, [messages]);
|
}, [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) => {
|
const handleToggleMute = (userId: string) => {
|
||||||
setMutedUsers(prev =>
|
setMutedUsers(prev =>
|
||||||
prev.includes(userId)
|
prev.includes(userId)
|
||||||
@@ -195,11 +254,22 @@ const ChatUI = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleShareChat = () => {
|
||||||
if (messages.length > 0) {
|
setShowPoster(true);
|
||||||
setShowAd(false);
|
};
|
||||||
}
|
|
||||||
}, [messages]);
|
const toggleSidebar = () => {
|
||||||
|
setSidebarOpen(!sidebarOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5. 加载检查
|
||||||
|
if (isInitializing || !group) {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-gradient-to-br from-orange-50 via-orange-50/70 to-orange-100 flex items-center justify-center">
|
||||||
|
<div className="w-8 h-8 animate-spin rounded-full border-4 border-orange-500 border-t-transparent"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleSendMessage = async () => {
|
const handleSendMessage = async () => {
|
||||||
//判断是否Loding
|
//判断是否Loding
|
||||||
@@ -227,7 +297,7 @@ const ChatUI = () => {
|
|||||||
}));
|
}));
|
||||||
let selectedGroupAiCharacters = groupAiCharacters;
|
let selectedGroupAiCharacters = groupAiCharacters;
|
||||||
if (!isGroupDiscussionMode) {
|
if (!isGroupDiscussionMode) {
|
||||||
const shedulerResponse = await fetch('/api/scheduler', {
|
const shedulerResponse = await fetch(`${API_BASE_URL}/api/scheduler`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -255,7 +325,7 @@ const ChatUI = () => {
|
|||||||
setMessages(prev => [...prev, aiMessage]);
|
setMessages(prev => [...prev, aiMessage]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/chat', {
|
const response = await fetch(`${API_BASE_URL}/api/chat`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -267,7 +337,7 @@ const ChatUI = () => {
|
|||||||
history: messageHistory,
|
history: messageHistory,
|
||||||
index: i,
|
index: i,
|
||||||
aiName: selectedGroupAiCharacters[i].name,
|
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 = () => {
|
const handleCancel = () => {
|
||||||
abortController.abort();
|
abortController.current.abort();
|
||||||
};
|
|
||||||
|
|
||||||
// 清理打字机效果
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (typewriterRef.current) {
|
|
||||||
clearInterval(typewriterRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 添加对聊天区域的引用
|
|
||||||
const chatAreaRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// 更新分享函数
|
|
||||||
const [showPoster, setShowPoster] = useState(false);
|
|
||||||
|
|
||||||
const handleShareChat = () => {
|
|
||||||
setShowPoster(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
||||||
|
|
||||||
// 切换侧边栏状态的函数
|
|
||||||
const toggleSidebar = () => {
|
|
||||||
setSidebarOpen(!sidebarOpen);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理群组选择
|
// 处理群组选择
|
||||||
const handleSelectGroup = (index: number) => {
|
const handleSelectGroup = (index: number) => {
|
||||||
|
//进行跳转到?id=index
|
||||||
|
window.location.href = `?id=${index}`;
|
||||||
|
return;
|
||||||
|
/*
|
||||||
|
//跳转后,关闭当前页面
|
||||||
setSelectedGroupIndex(index);
|
setSelectedGroupIndex(index);
|
||||||
const newGroup = groups[index];
|
const newGroup = groups[index];
|
||||||
setGroup(newGroup);
|
setGroup(newGroup);
|
||||||
@@ -444,6 +493,7 @@ const ChatUI = () => {
|
|||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
setSidebarOpen(false);
|
setSidebarOpen(false);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -457,6 +507,7 @@ const ChatUI = () => {
|
|||||||
toggleSidebar={toggleSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
selectedGroupIndex={selectedGroupIndex}
|
selectedGroupIndex={selectedGroupIndex}
|
||||||
onSelectGroup={handleSelectGroup}
|
onSelectGroup={handleSelectGroup}
|
||||||
|
groups={groups}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 聊天主界面 */}
|
{/* 聊天主界面 */}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { MessageSquareIcon, PlusCircleIcon, MenuIcon, PanelLeftCloseIcon } from
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import GitHubButton from 'react-github-btn';
|
import GitHubButton from 'react-github-btn';
|
||||||
import '@fontsource/audiowide';
|
import '@fontsource/audiowide';
|
||||||
import { groups } from "@/config/groups";
|
//import { groups } from "@/config/groups";
|
||||||
import { AdSection } from './AdSection';
|
import { AdSection } from './AdSection';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -28,9 +28,10 @@ interface SidebarProps {
|
|||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
selectedGroupIndex?: number;
|
selectedGroupIndex?: number;
|
||||||
onSelectGroup?: (index: number) => void;
|
onSelectGroup?: (index: number) => void;
|
||||||
|
groups: Group[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sidebar = ({ isOpen, toggleSidebar, selectedGroupIndex = 0, onSelectGroup }: SidebarProps) => {
|
const Sidebar = ({ isOpen, toggleSidebar, selectedGroupIndex = 0, onSelectGroup, groups }: SidebarProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -63,24 +63,20 @@ export interface AICharacter {
|
|||||||
tags?: string[]; // 可选的标签
|
tags?: string[]; // 可选的标签
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调度器配置信息
|
// 添加一个函数来生成带有群名的角色配置
|
||||||
export function shedulerAICharacter(message: string, allTags: string[]): AICharacter {
|
export function generateAICharacters(groupName: string, allTags: string): AICharacter[] {
|
||||||
return {
|
return [
|
||||||
|
{
|
||||||
id: 'ai0',
|
id: 'ai0',
|
||||||
name: "调度器",
|
name: "调度器",
|
||||||
personality: "sheduler",
|
personality: "sheduler",
|
||||||
model: modelConfigs[0].model,
|
model: modelConfigs[0].model,
|
||||||
avatar: "",
|
avatar: "",
|
||||||
custom_prompt: `你是一个群聊总结分析专家,你在一个聊天群里,请分析群用户消息和上文群聊内容
|
custom_prompt: `你是一个群聊总结分析专家,你在一个聊天群里,请分析群用户消息和上文群聊内容
|
||||||
1、只能从给定的标签列表中选择最相关的标签,可选标签:${allTags.join(', ')}。
|
1、只能从给定的标签列表中选择最相关的标签,可选标签:“${allTags}”。
|
||||||
2、请只返回标签列表,用逗号分隔,不要有其他解释, 不要有任何前缀。
|
2、请只返回标签列表,用逗号分隔,不要有其他解释, 不要有任何前缀。
|
||||||
3、回复格式示例:文字游戏, 生活助手, 娱乐`
|
3、回复格式示例:文字游戏, 新闻报道, 娱乐`
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
// 添加一个函数来生成带有群名的角色配置
|
|
||||||
export function generateAICharacters(groupName: string): AICharacter[] {
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
id: 'ai1',
|
id: 'ai1',
|
||||||
name: "暖心姐",
|
name: "暖心姐",
|
||||||
|
|||||||
Reference in New Issue
Block a user