diff --git a/devrun.sh b/devrun.sh
new file mode 100644
index 0000000..40cad01
--- /dev/null
+++ b/devrun.sh
@@ -0,0 +1 @@
+wrangler pages dev -- npm run dev
\ No newline at end of file
diff --git a/functions/api/chat.ts b/functions/api/chat.ts
index 8b025a2..2acf55f 100644
--- a/functions/api/chat.ts
+++ b/functions/api/chat.ts
@@ -2,7 +2,7 @@ import OpenAI from 'openai';
export async function onRequestPost({ env, request }) {
try {
- const { message } = await request.json();
+ const { message, personality } = await request.json();
const apiKey = env.DASHSCOPE_API_KEY;
if (!apiKey) {
@@ -14,14 +14,34 @@ export async function onRequestPost({ env, request }) {
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({
model: "qwen-plus",
messages: [
- { role: "system", content: "You are a helpful assistant." },
+ { role: "system", content: systemPrompt },
{ role: "user", content: message }
],
- stream: true, // 启用流式响应
+ stream: true,
+ temperature: personality === "high_eq" ? 0.7 : 0.9, // 高情商用较低温度保持稳定,低情商用较高温度增加随机性
});
// 创建 ReadableStream
diff --git a/index.html b/index.html
index a2f5c10..f93bf05 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
- Your App
+ AI机器人群聊
diff --git a/src/components/ChatUI.tsx b/src/components/ChatUI.tsx
index fd2fd5b..96e8e1d 100644
--- a/src/components/ChatUI.tsx
+++ b/src/components/ChatUI.tsx
@@ -34,17 +34,19 @@ const getAvatarData = (name: string) => {
};
const ChatUI = () => {
+ // 添加 AI 角色定义
+ const aiCharacters = [
+ { id: 'ai1', name: "暖心姐", personality: "high_eq" },
+ { id: 'ai2', name: "直男哥", personality: "low_eq" }
+ ];
+
const [users, setUsers] = useState([
- { id: 1, name: "张三" },
- { id: 2, name: "李四" },
- { id: 3, name: "王五" },
- { id: 4, name: "赵六" },
- { id: 5, name: "我" },
+ { id: 1, name: "我" },
+ ...aiCharacters
]);
const [showMembers, setShowMembers] = useState(false);
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 [isLoading, setIsLoading] = useState(false);
@@ -54,6 +56,7 @@ const ChatUI = () => {
const currentMessageRef = useRef(null);
const typewriterRef = useRef(null);
const accumulatedContentRef = useRef(""); // 用于跟踪完整内容
+ const messagesEndRef = useRef(null);
const abortController = new AbortController();
@@ -102,130 +105,121 @@ const ChatUI = () => {
}, typingSpeed);
};
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
const handleSendMessage = async () => {
if (!inputMessage.trim()) return;
+ // 添加用户消息
const userMessage = {
id: messages.length + 1,
- sender: users[4],
+ sender: users[0],
content: inputMessage,
isAI: false
};
setMessages(prev => [...prev, userMessage]);
-
- const aiMessage = {
- id: messages.length + 2,
- sender: { id: 0, name: "AI助手" },
- content: "",
- isAI: true
- };
- setMessages(prev => [...prev, aiMessage]);
-
setInputMessage("");
setIsLoading(true);
+ setPendingContent("");
+ accumulatedContentRef.current = "";
- try {
- const response = await fetch('/api/chat', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- message: inputMessage,
- }),
- });
-
- if (!response.ok) {
- throw new Error('请求失败');
- }
-
- const reader = response.body?.getReader();
- const decoder = new TextDecoder();
-
- if (!reader) {
- throw new Error('无法获取响应流');
- }
-
- let buffer = '';
+ // 依次请求两个 AI 的回复
+ for (let i = 0; i < aiCharacters.length; i++) {
+ // 创建当前 AI 角色的消息
+ const aiMessage = {
+ id: messages.length + 2 + i,
+ sender: { id: aiCharacters[i].id, name: aiCharacters[i].name },
+ content: "",
+ isAI: true
+ };
- while (true) {
- const { done, value } = await reader.read();
-
- if (done) break;
-
- // 解码并添加到buffer
- buffer += decoder.decode(value, { stream: true });
-
- // 处理buffer中的完整SSE消息
- let newlineIndex;
- while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
- const line = buffer.slice(0, newlineIndex);
- buffer = buffer.slice(newlineIndex + 1);
-
- if (line.startsWith('data: ')) {
- try {
- const data = JSON.parse(line.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;
- });
+ // 添加当前 AI 的消息
+ setMessages(prev => [...prev, aiMessage]);
- // 滚动到底部
- setTimeout(() => {
- const chatContainer = document.querySelector('.scroll-area-viewport');
- if (chatContainer) {
- chatContainer.scrollTop = chatContainer.scrollHeight;
- }
- }, 0);
+ try {
+ const response = await fetch('/api/chat', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ message: inputMessage,
+ personality: aiCharacters[i].personality,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('请求失败');
+ }
+
+ const reader = response.body?.getReader();
+ const decoder = new TextDecoder();
+
+ if (!reader) {
+ throw new Error('无法获取响应流');
+ }
+
+ let buffer = '';
+ let completeResponse = ''; // 用于跟踪完整的响应
+
+ while (true) {
+ const { done, value } = await reader.read();
+
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+
+ let newlineIndex;
+ while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
+ const line = buffer.slice(0, newlineIndex);
+ buffer = buffer.slice(newlineIndex + 1);
+
+ if (line.startsWith('data: ')) {
+ try {
+ const data = JSON.parse(line.slice(6));
+ if (data.content) {
+ completeResponse += data.content;
+ setMessages(prev => {
+ const newMessages = [...prev];
+ const aiMessageIndex = newMessages.findIndex(msg => msg.id === aiMessage.id);
+ if (aiMessageIndex !== -1) {
+ newMessages[aiMessageIndex] = {
+ ...newMessages[aiMessageIndex],
+ content: completeResponse
+ };
+ }
+ return newMessages;
+ });
+ }
+ } catch (e) {
+ console.error('解析响应数据失败:', e);
}
- } catch (e) {
- console.error('解析响应数据失败:', e);
}
}
}
- }
- // 处理剩余的buffer
- if (buffer.length > 0 && buffer.startsWith('data: ')) {
- try {
- 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);
+ // 等待一小段时间再开始下一个 AI 的回复
+ if (i < aiCharacters.length - 1) {
+ await new Promise(resolve => setTimeout(resolve, 1000));
}
- }
- } catch (error) {
- console.error("发送消息失败:", error);
- setMessages(prev => prev.map(msg =>
- msg.id === aiMessage.id
- ? { ...msg, content: "错误: " + error.message, isError: true }
- : msg
- ));
- } finally {
- setIsLoading(false);
+ } catch (error) {
+ console.error("发送消息失败:", error);
+ setMessages(prev => prev.map(msg =>
+ msg.id === aiMessage.id
+ ? { ...msg, content: "错误: " + error.message, isError: true }
+ : msg
+ ));
+ }
}
+
+ setIsLoading(false);
};
const handleCancel = () => {
@@ -248,7 +242,7 @@ const ChatUI = () => {
- 技术交流群
+ 硅碳摸鱼群
@@ -336,6 +330,7 @@ const ChatUI = () => {
)}
))}
+
diff --git a/src/index.css b/src/index.css
index 215ce6f..c22511b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,6 +2,122 @@
@tailwind components;
@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 {
display: inline-block;
animation: blink 1s step-end infinite;