138
functions/api/scheduler.ts
Normal file
138
functions/api/scheduler.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
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, env);
|
||||
|
||||
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[], env: any, history: MessageHistory[] = []): Promise<string[]> {
|
||||
const shedulerAI = shedulerAICharacter(message, allTags);
|
||||
const modelConfig = modelConfigs.find(config => config.model === shedulerAI.model);
|
||||
const apiKey = env[modelConfig.apiKey];
|
||||
if (!apiKey) {
|
||||
throw new Error(`${modelConfig.model} 的API密钥未配置`);
|
||||
}
|
||||
const openai = new OpenAI({
|
||||
apiKey: apiKey,
|
||||
baseURL: modelConfig.baseURL,
|
||||
});
|
||||
|
||||
const prompt = shedulerAI.custom_prompt;
|
||||
|
||||
try {
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: shedulerAI.model,
|
||||
messages: [
|
||||
{ role: "user", content: prompt },
|
||||
...history.slice(-10), // 添加历史消息
|
||||
{ role: "user", content: message }
|
||||
],
|
||||
});
|
||||
|
||||
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[],
|
||||
env: any
|
||||
): Promise<string[]> {
|
||||
// 1. 收集所有可用的标签
|
||||
const allTags = new Set<string>();
|
||||
availableAIs.forEach(ai => {
|
||||
ai.tags?.forEach(tag => allTags.add(tag));
|
||||
});
|
||||
|
||||
// 2. 使用AI模型分析消息并匹配标签
|
||||
const matchedTags = await analyzeMessageWithAI(message, Array.from(allTags), env, history);
|
||||
console.log('matchedTags', matchedTags, allTags);
|
||||
//如果含有"文字游戏"标签,则需要全员参与
|
||||
if (matchedTags.includes("文字游戏")) {
|
||||
return availableAIs.map(ai => ai.id);
|
||||
}
|
||||
// 3. 计算每个AI的匹配分数
|
||||
const aiScores = new Map<string, number>();
|
||||
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) {
|
||||
console.log('没有匹配到任何AI,随机选择1-2个');
|
||||
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);
|
||||
}
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -941,7 +941,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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = () => (
|
||||
<style jsx global>{`
|
||||
<style dangerouslySetInnerHTML={{ __html: `
|
||||
/* 只在聊天消息内应用 KaTeX 样式 */
|
||||
.chat-message .katex-html {
|
||||
display: none;
|
||||
@@ -141,11 +142,12 @@ const KaTeXStyle = () => (
|
||||
|
||||
/* 其他必要的 KaTeX 样式 */
|
||||
@import "katex/dist/katex.min.css";
|
||||
`}</style>
|
||||
`}} />
|
||||
);
|
||||
|
||||
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: "我" },
|
||||
@@ -214,16 +216,28 @@ 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;
|
||||
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(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 +252,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
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -442,7 +456,7 @@ const ChatUI = () => {
|
||||
)}
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={() => setShowMembers(true)}>
|
||||
<Users className="w-5 h-5" />
|
||||
<Settings2 className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -579,78 +593,16 @@ const ChatUI = () => {
|
||||
</div>
|
||||
|
||||
{/* Members Management Dialog */}
|
||||
<Dialog open={showMembers} onOpenChange={setShowMembers}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>群成员管理</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<span className="text-sm text-gray-500">当前成员({users.length})</span>
|
||||
<Button variant="outline" size="sm">
|
||||
<UserPlus className="w-4 h-4 mr-2" />
|
||||
添加成员
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollArea className="h-[300px]">
|
||||
<div className="space-y-2">
|
||||
{users.map((user) => (
|
||||
<div key={user.id} className="flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
{'avatar' in user && user.avatar ? (
|
||||
<AvatarImage src={user.avatar} className="w-10 h-10" />
|
||||
) : (
|
||||
<AvatarFallback style={{ backgroundColor: getAvatarData(user.name).backgroundColor, color: 'white' }}>
|
||||
{user.name[0]}
|
||||
</AvatarFallback>
|
||||
)}
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span>{user.name}</span>
|
||||
{mutedUsers.includes(user.id) && (
|
||||
<span className="text-xs text-red-500">已禁言</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{user.name !== "我" && (
|
||||
<div className="flex gap-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleToggleMute(user.id)}
|
||||
>
|
||||
{mutedUsers.includes(user.id) ? (
|
||||
<MicOff className="w-4 h-4 text-red-500" />
|
||||
) : (
|
||||
<Mic className="w-4 h-4 text-green-500" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{mutedUsers.includes(user.id) ? '取消禁言' : '禁言'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{/*<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleRemoveUser(user.id)}
|
||||
>
|
||||
<UserMinus className="w-4 h-4 text-red-500" />
|
||||
</Button>*/}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<MembersManagement
|
||||
showMembers={showMembers}
|
||||
setShowMembers={setShowMembers}
|
||||
users={users}
|
||||
mutedUsers={mutedUsers}
|
||||
handleToggleMute={handleToggleMute}
|
||||
isGroupDiscussionMode={isGroupDiscussionMode}
|
||||
onToggleGroupDiscussion={() => setIsGroupDiscussionMode(!isGroupDiscussionMode)}
|
||||
getAvatarData={getAvatarData}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 添加 SharePoster 组件 */}
|
||||
|
||||
116
src/components/MembersManagement.tsx
Normal file
116
src/components/MembersManagement.tsx
Normal file
@@ -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 (
|
||||
<Sheet open={showMembers} onOpenChange={setShowMembers}>
|
||||
<SheetContent side="right" className="w-[300px] sm:w-[400px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle>群聊配置</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="mt-4">
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm">全员讨论模式</div>
|
||||
<div className="text-xs text-gray-500">开启后全员回复讨论</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={isGroupDiscussionMode}
|
||||
onCheckedChange={onToggleGroupDiscussion}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<span className="text-sm text-gray-500">当前成员({users.length})</span>
|
||||
<Button variant="outline" size="sm">
|
||||
<UserPlus className="w-4 h-4 mr-2" />
|
||||
添加成员
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollArea className="h-[calc(100vh-150px)]">
|
||||
<div className="space-y-2 pr-4">
|
||||
{users.map((user) => (
|
||||
<div key={user.id} className="flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
{'avatar' in user && user.avatar ? (
|
||||
<AvatarImage src={user.avatar} className="w-10 h-10" />
|
||||
) : (
|
||||
<AvatarFallback style={{ backgroundColor: getAvatarData(user.name).backgroundColor, color: 'white' }}>
|
||||
{user.name[0]}
|
||||
</AvatarFallback>
|
||||
)}
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span>{user.name}</span>
|
||||
{mutedUsers.includes(user.id as string) && (
|
||||
<span className="text-xs text-red-500">已禁言</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{user.name !== "我" && (
|
||||
<div className="flex gap-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleToggleMute(user.id as string)}
|
||||
>
|
||||
{mutedUsers.includes(user.id as string) ? (
|
||||
<MicOff className="w-4 h-4 text-red-500" />
|
||||
) : (
|
||||
<Mic className="w-4 h-4 text-green-500" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{mutedUsers.includes(user.id as string) ? '取消禁言' : '禁言'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
140
src/components/ui/sheet.tsx
Normal file
140
src/components/ui/sheet.tsx
Normal file
@@ -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<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={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<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = "right", className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">关闭</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetHeader.displayName = "SheetHeader"
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = "SheetFooter"
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetOverlay,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
27
src/components/ui/switch.tsx
Normal file
27
src/components/ui/switch.tsx
Normal file
@@ -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<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
))
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||
|
||||
export { Switch }
|
||||
@@ -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,22 @@ 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[0].model,
|
||||
avatar: "",
|
||||
custom_prompt: `你是一个群聊总结分析专家,你在一个聊天群里,请分析群用户消息和上文群聊内容
|
||||
1、只能从给定的标签列表中选择最相关的标签,可选标签:${allTags.join(', ')}。
|
||||
2、请只返回标签列表,用逗号分隔,不要有其他解释, 不要有任何前缀。
|
||||
3、回复格式示例:文字游戏, 生活助手, 娱乐`
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一个函数来生成带有群名的角色配置
|
||||
@@ -82,15 +103,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,15 +121,17 @@ export function generateAICharacters(groupName: string): AICharacter[] {
|
||||
personality: "qianwen",
|
||||
model: modelConfigs[0].model,
|
||||
avatar: "/img/qwen.jpg",
|
||||
custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`
|
||||
custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
|
||||
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}" 的聊天群里`
|
||||
custom_prompt: `你是一个名叫"DeepSeek"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
|
||||
tags: ["深度推理", "编程", "文字游戏", "数学", "信息总结"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -126,3 +126,14 @@
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user