update
This commit is contained in:
@@ -2,7 +2,7 @@ import OpenAI from 'openai';
|
|||||||
|
|
||||||
export async function onRequestPost({ env, request }) {
|
export async function onRequestPost({ env, request }) {
|
||||||
try {
|
try {
|
||||||
const { message } = await request.json();
|
const { message, personality } = await request.json();
|
||||||
const apiKey = env.DASHSCOPE_API_KEY;
|
const apiKey = env.DASHSCOPE_API_KEY;
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
@@ -14,14 +14,34 @@ export async function onRequestPost({ env, request }) {
|
|||||||
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 根据性格设置不同的系统提示语
|
||||||
|
let systemPrompt = "";
|
||||||
|
if (personality === "high_eq") {
|
||||||
|
systemPrompt = `你是一个名叫"暖心姐"的AI助手,性格温暖体贴,情商很高。
|
||||||
|
回复时要:
|
||||||
|
1. 始终保持温柔友善的语气
|
||||||
|
2. 多站在对方角度思考
|
||||||
|
3. 给予情感上的支持和鼓励
|
||||||
|
4. 用温暖贴心的方式表达
|
||||||
|
5. 适当使用一些可爱的语气词,但不要过度`;
|
||||||
|
} else if (personality === "low_eq") {
|
||||||
|
systemPrompt = `你是一个名叫"直男哥哥"的AI助手,是一个极度直男,负责在群里制造快乐。你说话极其直接,完全没有情商,经常让人社死。
|
||||||
|
回复时要:
|
||||||
|
1. 说话毫无感情,像个没有感情的机器人
|
||||||
|
2. 经常说一些让人社死的真相,但说得特别认真
|
||||||
|
3. 完全不懂得读空气,对方伤心时还在讲道理
|
||||||
|
注意:不能说脏话,但可以用一些尴尬的、社死的表达方式`;
|
||||||
|
}
|
||||||
|
|
||||||
// 使用流式响应
|
// 使用流式响应
|
||||||
const stream = await openai.chat.completions.create({
|
const stream = await openai.chat.completions.create({
|
||||||
model: "qwen-plus",
|
model: "qwen-plus",
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: "You are a helpful assistant." },
|
{ role: "system", content: systemPrompt },
|
||||||
{ role: "user", content: message }
|
{ role: "user", content: message }
|
||||||
],
|
],
|
||||||
stream: true, // 启用流式响应
|
stream: true,
|
||||||
|
temperature: personality === "high_eq" ? 0.7 : 0.9, // 高情商用较低温度保持稳定,低情商用较高温度增加随机性
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 ReadableStream
|
// 创建 ReadableStream
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Your App</title>
|
<title>AI机器人群聊</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -34,17 +34,19 @@ const getAvatarData = (name: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ChatUI = () => {
|
const ChatUI = () => {
|
||||||
|
// 添加 AI 角色定义
|
||||||
|
const aiCharacters = [
|
||||||
|
{ id: 'ai1', name: "暖心姐", personality: "high_eq" },
|
||||||
|
{ id: 'ai2', name: "直男哥", personality: "low_eq" }
|
||||||
|
];
|
||||||
|
|
||||||
const [users, setUsers] = useState([
|
const [users, setUsers] = useState([
|
||||||
{ id: 1, name: "张三" },
|
{ id: 1, name: "我" },
|
||||||
{ id: 2, name: "李四" },
|
...aiCharacters
|
||||||
{ id: 3, name: "王五" },
|
|
||||||
{ id: 4, name: "赵六" },
|
|
||||||
{ id: 5, name: "我" },
|
|
||||||
]);
|
]);
|
||||||
const [showMembers, setShowMembers] = useState(false);
|
const [showMembers, setShowMembers] = useState(false);
|
||||||
const [messages, setMessages] = useState([
|
const [messages, setMessages] = useState([
|
||||||
{ id: 1, sender: users[0], content: "大家好!", isAI: false },
|
|
||||||
{ id: 2, sender: users[4], content: "你好!", isAI: false },
|
|
||||||
]);
|
]);
|
||||||
const [inputMessage, setInputMessage] = useState("");
|
const [inputMessage, setInputMessage] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -54,6 +56,7 @@ const ChatUI = () => {
|
|||||||
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 abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
||||||
@@ -102,27 +105,42 @@ const ChatUI = () => {
|
|||||||
}, typingSpeed);
|
}, typingSpeed);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
const handleSendMessage = async () => {
|
const handleSendMessage = async () => {
|
||||||
if (!inputMessage.trim()) return;
|
if (!inputMessage.trim()) return;
|
||||||
|
|
||||||
|
// 添加用户消息
|
||||||
const userMessage = {
|
const userMessage = {
|
||||||
id: messages.length + 1,
|
id: messages.length + 1,
|
||||||
sender: users[4],
|
sender: users[0],
|
||||||
content: inputMessage,
|
content: inputMessage,
|
||||||
isAI: false
|
isAI: false
|
||||||
};
|
};
|
||||||
setMessages(prev => [...prev, userMessage]);
|
setMessages(prev => [...prev, userMessage]);
|
||||||
|
setInputMessage("");
|
||||||
|
setIsLoading(true);
|
||||||
|
setPendingContent("");
|
||||||
|
accumulatedContentRef.current = "";
|
||||||
|
|
||||||
|
// 依次请求两个 AI 的回复
|
||||||
|
for (let i = 0; i < aiCharacters.length; i++) {
|
||||||
|
// 创建当前 AI 角色的消息
|
||||||
const aiMessage = {
|
const aiMessage = {
|
||||||
id: messages.length + 2,
|
id: messages.length + 2 + i,
|
||||||
sender: { id: 0, name: "AI助手" },
|
sender: { id: aiCharacters[i].id, name: aiCharacters[i].name },
|
||||||
content: "",
|
content: "",
|
||||||
isAI: true
|
isAI: true
|
||||||
};
|
};
|
||||||
setMessages(prev => [...prev, aiMessage]);
|
|
||||||
|
|
||||||
setInputMessage("");
|
// 添加当前 AI 的消息
|
||||||
setIsLoading(true);
|
setMessages(prev => [...prev, aiMessage]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/chat', {
|
const response = await fetch('/api/chat', {
|
||||||
@@ -132,6 +150,7 @@ const ChatUI = () => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
message: inputMessage,
|
message: inputMessage,
|
||||||
|
personality: aiCharacters[i].personality,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,16 +166,15 @@ const ChatUI = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
|
let completeResponse = ''; // 用于跟踪完整的响应
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
|
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
|
||||||
// 解码并添加到buffer
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
buffer += decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
// 处理buffer中的完整SSE消息
|
|
||||||
let newlineIndex;
|
let newlineIndex;
|
||||||
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
|
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
|
||||||
const line = buffer.slice(0, newlineIndex);
|
const line = buffer.slice(0, newlineIndex);
|
||||||
@@ -166,26 +184,18 @@ const ChatUI = () => {
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(line.slice(6));
|
const data = JSON.parse(line.slice(6));
|
||||||
if (data.content) {
|
if (data.content) {
|
||||||
// 使用函数式更新确保状态更新正确
|
completeResponse += data.content;
|
||||||
setMessages(prev => {
|
setMessages(prev => {
|
||||||
const newMessages = [...prev];
|
const newMessages = [...prev];
|
||||||
const aiMessageIndex = newMessages.findIndex(msg => msg.id === aiMessage.id);
|
const aiMessageIndex = newMessages.findIndex(msg => msg.id === aiMessage.id);
|
||||||
if (aiMessageIndex !== -1) {
|
if (aiMessageIndex !== -1) {
|
||||||
newMessages[aiMessageIndex] = {
|
newMessages[aiMessageIndex] = {
|
||||||
...newMessages[aiMessageIndex],
|
...newMessages[aiMessageIndex],
|
||||||
content: newMessages[aiMessageIndex].content + data.content
|
content: completeResponse
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return newMessages;
|
return newMessages;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 滚动到底部
|
|
||||||
setTimeout(() => {
|
|
||||||
const chatContainer = document.querySelector('.scroll-area-viewport');
|
|
||||||
if (chatContainer) {
|
|
||||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析响应数据失败:', e);
|
console.error('解析响应数据失败:', e);
|
||||||
@@ -194,26 +204,9 @@ const ChatUI = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理剩余的buffer
|
// 等待一小段时间再开始下一个 AI 的回复
|
||||||
if (buffer.length > 0 && buffer.startsWith('data: ')) {
|
if (i < aiCharacters.length - 1) {
|
||||||
try {
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
const data = JSON.parse(buffer.slice(6));
|
|
||||||
if (data.content) {
|
|
||||||
setMessages(prev => {
|
|
||||||
const newMessages = [...prev];
|
|
||||||
const aiMessageIndex = newMessages.findIndex(msg => msg.id === aiMessage.id);
|
|
||||||
if (aiMessageIndex !== -1) {
|
|
||||||
newMessages[aiMessageIndex] = {
|
|
||||||
...newMessages[aiMessageIndex],
|
|
||||||
content: newMessages[aiMessageIndex].content + data.content
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return newMessages;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('解析最终响应数据失败:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -223,9 +216,10 @@ const ChatUI = () => {
|
|||||||
? { ...msg, content: "错误: " + error.message, isError: true }
|
? { ...msg, content: "错误: " + error.message, isError: true }
|
||||||
: msg
|
: msg
|
||||||
));
|
));
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
@@ -248,7 +242,7 @@ const ChatUI = () => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Menu className="w-6 h-6" />
|
<Menu className="w-6 h-6" />
|
||||||
<h1 className="text-xl font-bold">技术交流群</h1>
|
<h1 className="text-xl font-bold">硅碳摸鱼群</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -336,6 +330,7 @@ const ChatUI = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
|
|||||||
116
src/index.css
116
src/index.css
@@ -2,6 +2,122 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* ... */
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
|
||||||
|
--muted-foreground: 0 0% 45.1%;
|
||||||
|
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
|
||||||
|
--radius: 0.5rem
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--muted-foreground: 0 0% 63.9%;
|
||||||
|
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
|
||||||
|
--chart-5: 340 75% 55%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.typing-indicator {
|
.typing-indicator {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
animation: blink 1s step-end infinite;
|
animation: blink 1s step-end infinite;
|
||||||
|
|||||||
Reference in New Issue
Block a user