Merge pull request #6 from maojindao55/develop

新增群成员回复决策调度
This commit is contained in:
maojindao55
2025-02-25 14:03:46 +08:00
committed by GitHub
9 changed files with 509 additions and 99 deletions

138
functions/api/scheduler.ts Normal file
View 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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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 组件 */}

View 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
View 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,
}

View 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 }

View File

@@ -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: ["深度推理", "编程", "文字游戏", "数学", "信息总结"]
}
];
}

View File

@@ -126,3 +126,14 @@
@keyframes blink {
50% { opacity: 0; }
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}