diff --git a/functions/api/scheduler.ts b/functions/api/scheduler.ts new file mode 100644 index 0000000..693faeb --- /dev/null +++ b/functions/api/scheduler.ts @@ -0,0 +1,126 @@ +import { modelConfigs, shedulerAICharacter } from '../../src/config/aiCharacters'; +import OpenAI from 'openai'; + +interface AICharacter { + id: string; + name: string; + tags?: string[]; +} + +interface MessageHistory { + role: string; + content: string; + name: string; +} + +export async function onRequestPost({ env, request }) { + try { + const { message, history, availableAIs } = await request.json(); + const selectedAIs = await scheduleAIResponses(message, history, availableAIs); + + return Response.json({ + selectedAIs: selectedAIs + }); + } catch (error) { + console.error(error); + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +} + +async function analyzeMessageWithAI(message: string, allTags: string[]): Promise { + const shedulerAI = shedulerAICharacter(message, allTags); + const modelConfig = modelConfigs.find(config => config.model === shedulerAI.model); + const openai = new OpenAI({ + apiKey: modelConfig.apiKey, + baseURL: modelConfig.baseURL, // DeepSeek API 的基础URL + }); + + const prompt = shedulerAI.custom_prompt; + + try { + const completion = await openai.chat.completions.create({ + model: shedulerAI.model, + messages: [ + { role: "user", content: prompt } + ], + }); + + const matchedTags = completion.choices[0].message.content?.split(',').map(tag => tag.trim()) || []; + return matchedTags; + } catch (error) { + console.error('AI分析失败:', error); + return []; + } +} + +async function scheduleAIResponses( + message: string, + history: MessageHistory[], + availableAIs: AICharacter[] +): Promise { + // 1. 收集所有可用的标签 + const allTags = new Set(); + availableAIs.forEach(ai => { + ai.tags?.forEach(tag => allTags.add(tag)); + }); + + // 2. 使用AI模型分析消息并匹配标签 + const matchedTags = await analyzeMessageWithAI(message, Array.from(allTags)); + + // 3. 计算每个AI的匹配分数 + const aiScores = new Map(); + const messageLC = message.toLowerCase(); + + for (const ai of availableAIs) { + if (!ai.tags) continue; + + let score = 0; + // 标签匹配分数 + matchedTags.forEach(tag => { + if (ai.tags?.includes(tag)) { + score += 2; // 每个匹配的标签得2分 + } + }); + + // 直接提到AI名字额外加分 + if (messageLC.includes(ai.name.toLowerCase())) { + score += 5; + } + + // 历史对话相关性加分 + const recentHistory = history.slice(-5); // 只看最近5条消息 + recentHistory.forEach(hist => { + if (hist.name === ai.name && hist.content.length > 0) { + score += 1; // 最近有参与对话的AI加分 + } + }); + + if (score > 0) { + aiScores.set(ai.id, score); + } + } + + // 4. 根据分数排序选择AI + const sortedAIs = Array.from(aiScores.entries()) + .sort((a, b) => b[1] - a[1]) + .map(([id]) => id); + + // 5. 如果没有匹配到任何AI,随机选择1-2个 + if (sortedAIs.length === 0) { + const maxResponders = Math.min(2, availableAIs.length); + const numResponders = Math.floor(Math.random() * maxResponders) + 1; + + const shuffledAIs = [...availableAIs] + .sort(() => Math.random() - 0.5) + .slice(0, numResponders); + + return shuffledAIs.map(ai => ai.id); + } + + // 6. 限制最大回复数量 + const MAX_RESPONDERS = 3; + return sortedAIs.slice(0, MAX_RESPONDERS); +} \ No newline at end of file diff --git a/src/components/ChatUI.tsx b/src/components/ChatUI.tsx index 16723b2..2cff218 100644 --- a/src/components/ChatUI.tsx +++ b/src/components/ChatUI.tsx @@ -214,16 +214,27 @@ const ChatUI = () => { content: msg.sender.name == "我" ? 'user:' + msg.content : msg.sender.name + ':' + msg.content, name: msg.sender.name })); - - for (let i = 0; i < groupAiCharacters.length; i++) { + 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)); + for (let i = 0; i < selectedGroupAiCharacters.length; i++) { //禁言 - if (mutedUsers.includes(groupAiCharacters[i].id)) { + if (mutedUsers.includes(selectedGroupAiCharacters[i].id)) { continue; } // 创建当前 AI 角色的消息 const aiMessage = { id: messages.length + 2 + i, - sender: { id: groupAiCharacters[i].id, name: groupAiCharacters[i].name, avatar: groupAiCharacters[i].avatar }, + sender: { id: selectedGroupAiCharacters[i].id, name: selectedGroupAiCharacters[i].name, avatar: selectedGroupAiCharacters[i].avatar }, content: "", isAI: true }; @@ -238,13 +249,13 @@ const ChatUI = () => { 'Content-Type': 'application/json', }, body: JSON.stringify({ - model: groupAiCharacters[i].model, + model: selectedGroupAiCharacters[i].model, message: inputMessage, - personality: groupAiCharacters[i].personality, + personality: selectedGroupAiCharacters[i].personality, history: messageHistory, index: i, - aiName: groupAiCharacters[i].name, - custom_prompt: groupAiCharacters[i].custom_prompt + aiName: selectedGroupAiCharacters[i].name, + custom_prompt: selectedGroupAiCharacters[i].custom_prompt }), }); diff --git a/src/config/aiCharacters.ts b/src/config/aiCharacters.ts index 3ea333c..ab76c78 100644 --- a/src/config/aiCharacters.ts +++ b/src/config/aiCharacters.ts @@ -19,7 +19,12 @@ export const modelConfigs = [ model: "ep-20250217191935-wzj8l",//火山引擎接入点(改成自己的) apiKey: "ARK_API_KEY", baseURL: "https://ark.cn-beijing.volces.com/api/v3" - } + }, + { + model: "hunyuan-lite",//免费模型 + apiKey: "HUNYUAN_API_KEY", + baseURL: "https://api.hunyuan.cloud.tencent.com/v1" + }, ] as const; export type ModelType = typeof modelConfigs[number]["model"]; @@ -30,6 +35,23 @@ export interface AICharacter { model: ModelType; avatar?: string; // 可选的头像 URL custom_prompt?: string; // 可选的个性提示 + tags?: string[]; // 可选的标签 +} + +// 调度器配置信息 +export function shedulerAICharacter(message: string, allTags: string[]): AICharacter { + return { + id: 'ai0', + name: "调度器", + personality: "sheduler", + model: modelConfigs[4].model, + avatar: "", + custom_prompt: `作为一个语义分析专家,请分析以下用户消息,并从给定的标签列表中选择最相关的标签(最多选择3个)。 + 请只返回标签列表,用逗号分隔,不要有其他解释。 + 用户消息:"${message}" + 可选标签:${allTags.join(', ')} + 回复格式示例:标签1,标签2,标签3` + } } // 添加一个函数来生成带有群名的角色配置 @@ -82,15 +104,17 @@ export function generateAICharacters(groupName: string): AICharacter[] { personality: "yuanbao", model: modelConfigs[2].model, avatar: "/img/yuanbao.png", - custom_prompt: `你是一个名叫"元宝"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里` + custom_prompt: `你是一个名叫"元宝"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, + tags: ["微信生态", "新闻搜索", "社交互动", "生活助手", "娱乐向", "信息总结"] }, { id: 'ai5', name: "豆包", personality: "doubao", - model: modelConfigs[3].model,//火山引擎接入点(改成自己的) + model: modelConfigs[3].model, avatar: "/img/doubao_new.png", - custom_prompt: `你是一个名叫"豆包"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里` + custom_prompt: `你是一个名叫"豆包"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, + tags: ["生活助手", "语音交互", "学生党福音", "娱乐利器", "抖音生态"] }, { id: 'ai6', @@ -98,7 +122,8 @@ export function generateAICharacters(groupName: string): AICharacter[] { personality: "qianwen", model: modelConfigs[0].model, avatar: "/img/qwen.jpg", - custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里` + custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, + tags: ["广告文案", "行业应用", "分析数据", "企业级AI", "阿里云生态"] }, { id: 'ai7', @@ -106,7 +131,8 @@ export function generateAICharacters(groupName: string): AICharacter[] { personality: "deepseek-v3", model: modelConfigs[1].model, avatar: "/img/ds.svg", - custom_prompt: `你是一个名叫"DeepSeek"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里` + custom_prompt: `你是一个名叫"DeepSeek"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`, + tags: ["深度推理", "编程神器", "中文优化", "性价比之王", "开源先锋"] } ]; }