|
|
@@ -2,53 +2,42 @@ import React, { useState, useRef, useEffect, useCallback } from "react";
|
|
|
import {
|
|
|
Input,
|
|
|
Button,
|
|
|
- Avatar,
|
|
|
Dropdown,
|
|
|
- message,
|
|
|
Tooltip,
|
|
|
Space,
|
|
|
- Spin,
|
|
|
MenuProps,
|
|
|
Card,
|
|
|
Affix,
|
|
|
- Typography,
|
|
|
} from "antd";
|
|
|
import {
|
|
|
SendOutlined,
|
|
|
- CopyOutlined,
|
|
|
- EditOutlined,
|
|
|
- ReloadOutlined,
|
|
|
DownOutlined,
|
|
|
- UserOutlined,
|
|
|
- RobotOutlined,
|
|
|
PaperClipOutlined,
|
|
|
- LeftOutlined,
|
|
|
- RightOutlined,
|
|
|
- CheckOutlined,
|
|
|
- CloseOutlined,
|
|
|
} from "@ant-design/icons";
|
|
|
-import Marked from "../general/Marked";
|
|
|
import { IAiModel, IAiModelListResponse } from "../api/ai";
|
|
|
import { get } from "../../request";
|
|
|
-import User from "../auth/User";
|
|
|
+import MsgUser from "./MsgUser";
|
|
|
+import MsgAssistant from "./MsgAssistant";
|
|
|
+import MsgTyping from "./MsgTyping";
|
|
|
+import MsgLoading from "./MsgLoading";
|
|
|
+import MsgSystem from "./MsgSystem";
|
|
|
+import MsgError from "./MsgError";
|
|
|
|
|
|
const { TextArea } = Input;
|
|
|
-const { Text } = Typography;
|
|
|
|
|
|
// 类型定义
|
|
|
export interface MessageVersion {
|
|
|
+ id: number;
|
|
|
content: string;
|
|
|
model: string;
|
|
|
+ role: "system" | "user" | "assistant";
|
|
|
+ timestamp: string;
|
|
|
}
|
|
|
|
|
|
export interface Message {
|
|
|
id: number;
|
|
|
type: "user" | "ai" | "error";
|
|
|
- content: string;
|
|
|
- timestamp: string;
|
|
|
- model?: string;
|
|
|
- versions?: MessageVersion[];
|
|
|
- currentVersionIndex?: number;
|
|
|
+ versions: MessageVersion[];
|
|
|
}
|
|
|
|
|
|
interface OpenAIMessage {
|
|
|
@@ -69,6 +58,10 @@ interface OpenAIStreamResponse {
|
|
|
}>;
|
|
|
}
|
|
|
|
|
|
+const endOfMsg = (msg: Message) => {
|
|
|
+ return msg.versions[msg.versions.length - 1];
|
|
|
+};
|
|
|
+
|
|
|
interface IWidget {
|
|
|
initMessage?: string;
|
|
|
systemPrompt?: string;
|
|
|
@@ -84,8 +77,7 @@ const AIChatComponent = ({
|
|
|
const [inputValue, setInputValue] = useState<string>("");
|
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
|
const [selectedModel, setSelectedModel] = useState<string>("");
|
|
|
- const [editingMessageId, setEditingMessageId] = useState<number | null>(null);
|
|
|
- const [editingContent, setEditingContent] = useState<string>("");
|
|
|
+ const [fetchModel, setFetchModel] = useState<string>("");
|
|
|
const [refreshingMessageId, setRefreshingMessageId] = useState<number | null>(
|
|
|
null
|
|
|
);
|
|
|
@@ -95,6 +87,8 @@ const AIChatComponent = ({
|
|
|
const [currentTypingMessage, setCurrentTypingMessage] = useState<string>("");
|
|
|
const [models, setModels] = useState<IAiModel[]>();
|
|
|
|
|
|
+ const [error, setError] = useState<string>();
|
|
|
+
|
|
|
const scrollToBottom = useCallback(() => {
|
|
|
messagesEndRef.current?.scrollIntoView({
|
|
|
behavior: "smooth",
|
|
|
@@ -159,34 +153,39 @@ const AIChatComponent = ({
|
|
|
const callOpenAI = useCallback(
|
|
|
async (
|
|
|
messages: OpenAIMessage[],
|
|
|
+ modelId: string,
|
|
|
isRegenerate: boolean = false,
|
|
|
messageIndex?: number
|
|
|
): Promise<{ success: boolean; content?: string; error?: string }> => {
|
|
|
- setIsLoading(false);
|
|
|
+ setError(undefined);
|
|
|
if (typeof process.env.REACT_APP_OPENAI_PROXY === "undefined") {
|
|
|
console.error("no REACT_APP_OPENAI_PROXY");
|
|
|
return { success: false, error: "API配置错误" };
|
|
|
}
|
|
|
+
|
|
|
try {
|
|
|
+ setFetchModel(modelId);
|
|
|
const payload = {
|
|
|
- model: models?.find((value) => value.uid === selectedModel)?.model,
|
|
|
+ model: models?.find((value) => value.uid === modelId)?.model,
|
|
|
messages: messages,
|
|
|
stream: true,
|
|
|
temperature: 0.7,
|
|
|
- max_tokens: 2000,
|
|
|
+ max_tokens: 3000, //本次回复”最大输出长度
|
|
|
};
|
|
|
const url = process.env.REACT_APP_OPENAI_PROXY;
|
|
|
- console.info("api request", url, payload);
|
|
|
+ const data = {
|
|
|
+ model_id: modelId,
|
|
|
+ payload: payload,
|
|
|
+ };
|
|
|
+ console.info("api request", url, data);
|
|
|
+ setIsLoading(true);
|
|
|
const response = await fetch(url, {
|
|
|
method: "POST",
|
|
|
headers: {
|
|
|
"Content-Type": "application/json",
|
|
|
Authorization: `Bearer AIzaSyCzr8KqEdaQ3cRCxsFwSHh8c7kF3RZTZWw`,
|
|
|
},
|
|
|
- body: JSON.stringify({
|
|
|
- model_id: selectedModel,
|
|
|
- payload: payload,
|
|
|
- }),
|
|
|
+ body: JSON.stringify(data),
|
|
|
});
|
|
|
|
|
|
if (!response.ok) {
|
|
|
@@ -204,28 +203,23 @@ const AIChatComponent = ({
|
|
|
const typeController = streamTypeWriter(
|
|
|
(content: string) => {},
|
|
|
(finalContent: string) => {
|
|
|
+ console.log("newData in callOpenAI", finalContent);
|
|
|
+ const newData: MessageVersion = {
|
|
|
+ id: Date.now(),
|
|
|
+ content: finalContent,
|
|
|
+ model: modelId,
|
|
|
+ role: "assistant",
|
|
|
+ timestamp: new Date().toLocaleTimeString(),
|
|
|
+ };
|
|
|
if (isRegenerate && messageIndex !== undefined) {
|
|
|
setMessages((prev) => {
|
|
|
const newMessages = [...prev];
|
|
|
const targetMessage = newMessages[messageIndex];
|
|
|
if (targetMessage) {
|
|
|
if (!targetMessage.versions) {
|
|
|
- targetMessage.versions = [
|
|
|
- {
|
|
|
- content: targetMessage.content,
|
|
|
- model: targetMessage.model || "",
|
|
|
- },
|
|
|
- ];
|
|
|
- targetMessage.currentVersionIndex = 0;
|
|
|
+ targetMessage.versions = [];
|
|
|
}
|
|
|
- targetMessage.versions.push({
|
|
|
- content: finalContent,
|
|
|
- model: selectedModel,
|
|
|
- });
|
|
|
- targetMessage.currentVersionIndex =
|
|
|
- targetMessage.versions.length - 1;
|
|
|
- targetMessage.content = finalContent;
|
|
|
- targetMessage.model = selectedModel;
|
|
|
+ targetMessage.versions.push(newData);
|
|
|
}
|
|
|
setRefreshingMessageId(null);
|
|
|
return newMessages;
|
|
|
@@ -234,11 +228,7 @@ const AIChatComponent = ({
|
|
|
const aiMessage: Message = {
|
|
|
id: Date.now(),
|
|
|
type: "ai",
|
|
|
- content: finalContent,
|
|
|
- timestamp: new Date().toLocaleTimeString(),
|
|
|
- model: selectedModel,
|
|
|
- versions: [{ content: finalContent, model: selectedModel }],
|
|
|
- currentVersionIndex: 0,
|
|
|
+ versions: [newData],
|
|
|
};
|
|
|
setMessages((prev) => [...prev, aiMessage]);
|
|
|
setRefreshingMessageId(null);
|
|
|
@@ -292,20 +282,24 @@ const AIChatComponent = ({
|
|
|
return { success: false, error: "API调用失败,请重试" };
|
|
|
}
|
|
|
},
|
|
|
- [models, selectedModel, streamTypeWriter, currentTypingMessage]
|
|
|
+ [models, streamTypeWriter, fetchModel, currentTypingMessage]
|
|
|
);
|
|
|
|
|
|
const sendMessage = useCallback(
|
|
|
async (messageText: string = inputValue): Promise<void> => {
|
|
|
if (!messageText.trim()) return;
|
|
|
|
|
|
- const userMessage: Message = {
|
|
|
+ const newData: MessageVersion = {
|
|
|
id: Date.now(),
|
|
|
- type: "user",
|
|
|
content: messageText,
|
|
|
+ model: "",
|
|
|
+ role: "user",
|
|
|
timestamp: new Date().toLocaleTimeString(),
|
|
|
- versions: [{ content: messageText, model: "" }],
|
|
|
- currentVersionIndex: 0,
|
|
|
+ };
|
|
|
+ const userMessage: Message = {
|
|
|
+ id: Date.now(),
|
|
|
+ type: "user",
|
|
|
+ versions: [newData],
|
|
|
};
|
|
|
|
|
|
setMessages((prev) => [...prev, userMessage]);
|
|
|
@@ -322,55 +316,30 @@ const AIChatComponent = ({
|
|
|
...messages.map((msg) => {
|
|
|
const data: OpenAIMessage = {
|
|
|
role: msg.type === "user" ? "user" : "assistant",
|
|
|
- content: msg.content,
|
|
|
+ content: msg.versions[msg.versions.length - 1].content,
|
|
|
};
|
|
|
return data;
|
|
|
}),
|
|
|
{ role: "user", content: messageText },
|
|
|
];
|
|
|
|
|
|
- const result = await callOpenAI(conversationHistory);
|
|
|
+ const result = await callOpenAI(conversationHistory, selectedModel);
|
|
|
+ setIsLoading(false);
|
|
|
if (!result.success) {
|
|
|
- setMessages((prev) => [
|
|
|
- ...prev,
|
|
|
- {
|
|
|
- id: Date.now(),
|
|
|
- type: "error",
|
|
|
- content: result.error || "请求失败,请重试",
|
|
|
- timestamp: new Date().toLocaleTimeString(),
|
|
|
- },
|
|
|
- ]);
|
|
|
- setIsLoading(false);
|
|
|
+ setError("请求失败,请重试");
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error("发送消息失败:", error);
|
|
|
- setMessages((prev) => [
|
|
|
- ...prev,
|
|
|
- {
|
|
|
- id: Date.now(),
|
|
|
- type: "error",
|
|
|
- content: "请求失败,请重试",
|
|
|
- timestamp: new Date().toLocaleTimeString(),
|
|
|
- },
|
|
|
- ]);
|
|
|
+ setError("请求失败,请重试");
|
|
|
setIsLoading(false);
|
|
|
}
|
|
|
},
|
|
|
[inputValue, messages, systemPrompt, callOpenAI, scrollToBottom]
|
|
|
);
|
|
|
|
|
|
- const copyMessage = useCallback(async (content: string): Promise<void> => {
|
|
|
- try {
|
|
|
- await navigator.clipboard.writeText(content);
|
|
|
- message.success("已复制到剪贴板");
|
|
|
- } catch (error) {
|
|
|
- console.error("复制失败:", error);
|
|
|
- message.error("复制失败");
|
|
|
- }
|
|
|
- }, []);
|
|
|
-
|
|
|
const refreshAIResponse = useCallback(
|
|
|
- async (messageIndex: number): Promise<void> => {
|
|
|
+ async (messageIndex: number, modelId: string): Promise<void> => {
|
|
|
+ console.debug("refresh", messageIndex);
|
|
|
const userMessage = messages[messageIndex - 1];
|
|
|
if (userMessage && userMessage.type === "user") {
|
|
|
setRefreshingMessageId(messages[messageIndex].id);
|
|
|
@@ -379,149 +348,80 @@ const AIChatComponent = ({
|
|
|
...messages.slice(0, messageIndex - 1).map((msg) => {
|
|
|
const data: OpenAIMessage = {
|
|
|
role: msg.type === "user" ? "user" : "assistant",
|
|
|
- content: msg.content,
|
|
|
+ content: endOfMsg(msg).content,
|
|
|
};
|
|
|
return data;
|
|
|
}),
|
|
|
- { role: "user", content: userMessage.content },
|
|
|
+ { role: "user", content: endOfMsg(userMessage).content },
|
|
|
];
|
|
|
|
|
|
try {
|
|
|
const result = await callOpenAI(
|
|
|
conversationHistory,
|
|
|
+ modelId,
|
|
|
true,
|
|
|
messageIndex
|
|
|
);
|
|
|
+ setIsLoading(false);
|
|
|
if (!result.success) {
|
|
|
- setMessages((prev) => {
|
|
|
- const newMessages = [...prev];
|
|
|
- newMessages[messageIndex] = {
|
|
|
- id: Date.now(),
|
|
|
- type: "error",
|
|
|
- content: result.error || "重新生成失败,请重试",
|
|
|
- timestamp: new Date().toLocaleTimeString(),
|
|
|
- };
|
|
|
- setRefreshingMessageId(null);
|
|
|
- return newMessages;
|
|
|
- });
|
|
|
+ setError("重新生成失败,请重试");
|
|
|
+ setRefreshingMessageId(null);
|
|
|
} else {
|
|
|
- // Ensure the message type is set to "ai" on successful refresh
|
|
|
+ /*
|
|
|
+ console.log("newData refreshAIResponse", result);
|
|
|
setMessages((prev) => {
|
|
|
const newMessages = [...prev];
|
|
|
const targetMessage = newMessages[messageIndex];
|
|
|
if (targetMessage) {
|
|
|
+ const newData: MessageVersion = {
|
|
|
+ id: Date.now(),
|
|
|
+ content: result.content || "",
|
|
|
+ model: modelId,
|
|
|
+ role: "assistant",
|
|
|
+ timestamp: new Date().toLocaleTimeString(),
|
|
|
+ };
|
|
|
targetMessage.type = "ai"; // Update type to "ai"
|
|
|
- targetMessage.content = result.content || "";
|
|
|
- targetMessage.model = selectedModel;
|
|
|
if (!targetMessage.versions) {
|
|
|
- targetMessage.versions = [
|
|
|
- {
|
|
|
- content: targetMessage.content,
|
|
|
- model: targetMessage.model || "",
|
|
|
- },
|
|
|
- ];
|
|
|
- targetMessage.currentVersionIndex = 0;
|
|
|
+ targetMessage.versions = [];
|
|
|
}
|
|
|
- targetMessage.versions.push({
|
|
|
- content: result.content || "",
|
|
|
- model: selectedModel,
|
|
|
- });
|
|
|
- targetMessage.currentVersionIndex =
|
|
|
- targetMessage.versions.length - 1;
|
|
|
+ targetMessage.versions.push(newData);
|
|
|
}
|
|
|
setRefreshingMessageId(null);
|
|
|
return newMessages;
|
|
|
});
|
|
|
+ */
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error("刷新回答失败:", error);
|
|
|
- setMessages((prev) => {
|
|
|
- const newMessages = [...prev];
|
|
|
- newMessages[messageIndex] = {
|
|
|
- id: Date.now(),
|
|
|
- type: "error",
|
|
|
- content: "重新生成失败,请重试",
|
|
|
- timestamp: new Date().toLocaleTimeString(),
|
|
|
- };
|
|
|
- setRefreshingMessageId(null);
|
|
|
- return newMessages;
|
|
|
- });
|
|
|
+ setIsLoading(false);
|
|
|
+ setError("请求失败,请重试");
|
|
|
+ setRefreshingMessageId(null);
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- [messages, systemPrompt, callOpenAI, selectedModel]
|
|
|
+ [messages, systemPrompt, callOpenAI, fetchModel]
|
|
|
);
|
|
|
|
|
|
- const switchMessageVersion = useCallback(
|
|
|
- (messageIndex: number, direction: "prev" | "next"): void => {
|
|
|
- setMessages((prev) => {
|
|
|
- const newMessages = [...prev];
|
|
|
+ const confirmEdit = useCallback((id: number, text: string): void => {
|
|
|
+ setMessages((prev) => {
|
|
|
+ const newMessages = [...prev];
|
|
|
+ const messageIndex = newMessages.findIndex((m) => m.id === id);
|
|
|
+ if (messageIndex !== -1) {
|
|
|
const message = newMessages[messageIndex];
|
|
|
- if (
|
|
|
- message &&
|
|
|
- message.versions &&
|
|
|
- message.currentVersionIndex !== undefined
|
|
|
- ) {
|
|
|
- const currentIndex = message.currentVersionIndex;
|
|
|
- const maxIndex = message.versions.length - 1;
|
|
|
-
|
|
|
- let newIndex = currentIndex;
|
|
|
- if (direction === "prev" && currentIndex > 0) {
|
|
|
- newIndex = currentIndex - 1;
|
|
|
- } else if (direction === "next" && currentIndex < maxIndex) {
|
|
|
- newIndex = currentIndex + 1;
|
|
|
- }
|
|
|
-
|
|
|
- if (newIndex !== currentIndex) {
|
|
|
- message.currentVersionIndex = newIndex;
|
|
|
- message.content = message.versions[newIndex].content;
|
|
|
- message.model = message.versions[newIndex].model;
|
|
|
- }
|
|
|
+ if (!message.versions) {
|
|
|
+ message.versions = [];
|
|
|
}
|
|
|
- return newMessages;
|
|
|
- });
|
|
|
- },
|
|
|
- []
|
|
|
- );
|
|
|
-
|
|
|
- const startEditingMessage = useCallback(
|
|
|
- (messageIndex: number): void => {
|
|
|
- const message = messages[messageIndex];
|
|
|
- if (message && message.type === "user") {
|
|
|
- setEditingMessageId(message.id);
|
|
|
- setEditingContent(message.content);
|
|
|
+ const newData: MessageVersion = {
|
|
|
+ id: Date.now(),
|
|
|
+ content: text,
|
|
|
+ model: "",
|
|
|
+ role: "user",
|
|
|
+ timestamp: new Date().toLocaleTimeString(),
|
|
|
+ };
|
|
|
+ message.versions.push(newData);
|
|
|
}
|
|
|
- },
|
|
|
- [messages]
|
|
|
- );
|
|
|
-
|
|
|
- const confirmEdit = useCallback((): void => {
|
|
|
- if (editingMessageId !== null) {
|
|
|
- setMessages((prev) => {
|
|
|
- const newMessages = [...prev];
|
|
|
- const messageIndex = newMessages.findIndex(
|
|
|
- (m) => m.id === editingMessageId
|
|
|
- );
|
|
|
- if (messageIndex !== -1) {
|
|
|
- const message = newMessages[messageIndex];
|
|
|
- if (!message.versions) {
|
|
|
- message.versions = [{ content: message.content, model: "" }];
|
|
|
- message.currentVersionIndex = 0;
|
|
|
- }
|
|
|
- message.versions.push({ content: editingContent, model: "" });
|
|
|
- message.currentVersionIndex = message.versions.length - 1;
|
|
|
- message.content = editingContent;
|
|
|
- }
|
|
|
- return newMessages;
|
|
|
- });
|
|
|
- setEditingMessageId(null);
|
|
|
- setEditingContent("");
|
|
|
- }
|
|
|
- }, [editingMessageId, editingContent]);
|
|
|
-
|
|
|
- const cancelEdit = useCallback((): void => {
|
|
|
- setEditingMessageId(null);
|
|
|
- setEditingContent("");
|
|
|
+ return newMessages;
|
|
|
+ });
|
|
|
}, []);
|
|
|
|
|
|
const handleKeyPress = useCallback(
|
|
|
@@ -534,18 +434,6 @@ const AIChatComponent = ({
|
|
|
[sendMessage]
|
|
|
);
|
|
|
|
|
|
- const handleEditKeyPress = useCallback(
|
|
|
- (e: React.KeyboardEvent<HTMLTextAreaElement>): void => {
|
|
|
- if (e.key === "Enter" && e.ctrlKey) {
|
|
|
- e.preventDefault();
|
|
|
- confirmEdit();
|
|
|
- } else if (e.key === "Escape") {
|
|
|
- cancelEdit();
|
|
|
- }
|
|
|
- },
|
|
|
- [confirmEdit, cancelEdit]
|
|
|
- );
|
|
|
-
|
|
|
const modelMenu: MenuProps = {
|
|
|
selectedKeys: [selectedModel],
|
|
|
onClick: ({ key }) => setSelectedModel(key),
|
|
|
@@ -555,340 +443,62 @@ const AIChatComponent = ({
|
|
|
})),
|
|
|
};
|
|
|
|
|
|
- const refreshMenu = useCallback(
|
|
|
- (messageIndex: number): MenuProps => ({
|
|
|
- onClick: ({ key }) => {
|
|
|
- if (key === "refresh") {
|
|
|
- refreshAIResponse(messageIndex);
|
|
|
- }
|
|
|
- },
|
|
|
- items: [
|
|
|
- {
|
|
|
- key: "refresh",
|
|
|
- label: "重新生成",
|
|
|
- },
|
|
|
- {
|
|
|
- type: "divider",
|
|
|
- },
|
|
|
- {
|
|
|
- key: "model-submenu",
|
|
|
- label: "选择模型重新生成",
|
|
|
- children: models?.map((model) => ({
|
|
|
- key: model.uid,
|
|
|
- label: model.name,
|
|
|
- onClick: () => {
|
|
|
- setSelectedModel(model.uid);
|
|
|
- refreshAIResponse(messageIndex);
|
|
|
- },
|
|
|
- })),
|
|
|
- },
|
|
|
- ],
|
|
|
- }),
|
|
|
- [refreshAIResponse, models]
|
|
|
- );
|
|
|
-
|
|
|
return (
|
|
|
<div
|
|
|
style={{
|
|
|
display: "flex",
|
|
|
flexDirection: "column",
|
|
|
- backgroundColor: "#f5f5f5",
|
|
|
+ width: "100%",
|
|
|
}}
|
|
|
>
|
|
|
<div style={{ flex: 1, overflowY: "auto", padding: "16px" }}>
|
|
|
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
|
|
|
- {messages.map(
|
|
|
- (msg, index) =>
|
|
|
- msg.id !== refreshingMessageId && (
|
|
|
- <div
|
|
|
- key={msg.id}
|
|
|
- style={{
|
|
|
- display: "flex",
|
|
|
- justifyContent:
|
|
|
- msg.type === "user"
|
|
|
- ? "flex-end"
|
|
|
- : msg.type === "error"
|
|
|
- ? "center"
|
|
|
- : "flex-start",
|
|
|
- }}
|
|
|
- >
|
|
|
- <div
|
|
|
- style={{
|
|
|
- maxWidth: msg.type === "error" ? "100%" : "70%",
|
|
|
- backgroundColor:
|
|
|
- msg.type === "user"
|
|
|
- ? "#1890ff"
|
|
|
- : msg.type === "error"
|
|
|
- ? "#fff1f0"
|
|
|
- : "#ffffff",
|
|
|
- color:
|
|
|
- msg.type === "user"
|
|
|
- ? "white"
|
|
|
- : msg.type === "error"
|
|
|
- ? "#ff4d4f"
|
|
|
- : "black",
|
|
|
- borderRadius: "8px",
|
|
|
- padding: "16px",
|
|
|
- position: "relative",
|
|
|
- border:
|
|
|
- msg.type === "ai"
|
|
|
- ? "1px solid #d9d9d9"
|
|
|
- : msg.type === "error"
|
|
|
- ? "1px solid #ff4d4f"
|
|
|
- : "none",
|
|
|
- boxShadow:
|
|
|
- msg.type === "ai" || msg.type === "error"
|
|
|
- ? "0 1px 2px rgba(0, 0, 0, 0.03)"
|
|
|
- : "none",
|
|
|
- textAlign: msg.type === "error" ? "center" : "left",
|
|
|
+ <MsgSystem value={systemPrompt} />
|
|
|
+ {messages.map((msg, index) => {
|
|
|
+ if (msg.id === refreshingMessageId) {
|
|
|
+ return <></>;
|
|
|
+ } else {
|
|
|
+ if (msg.type === "user") {
|
|
|
+ return (
|
|
|
+ <MsgUser
|
|
|
+ msg={msg}
|
|
|
+ onChange={(value: string) => confirmEdit(index, value)}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ } else if (msg.type === "ai") {
|
|
|
+ return (
|
|
|
+ <MsgAssistant
|
|
|
+ msg={msg}
|
|
|
+ models={models}
|
|
|
+ onRefresh={(modelId: string) => {
|
|
|
+ refreshAIResponse(index, modelId);
|
|
|
}}
|
|
|
- >
|
|
|
- <div style={{ display: "flex", alignItems: "flex-start" }}>
|
|
|
- <Space>
|
|
|
- {msg.type !== "error" && (
|
|
|
- <Avatar
|
|
|
- size={32}
|
|
|
- icon={
|
|
|
- msg.type === "user" ? (
|
|
|
- <UserOutlined />
|
|
|
- ) : (
|
|
|
- <RobotOutlined />
|
|
|
- )
|
|
|
- }
|
|
|
- style={{
|
|
|
- backgroundColor:
|
|
|
- msg.type === "user"
|
|
|
- ? "rgb(0 132 253 / 50%)"
|
|
|
- : "#595959",
|
|
|
- }}
|
|
|
- />
|
|
|
- )}
|
|
|
- <div>
|
|
|
- {msg.type !== "error" && (
|
|
|
- <div
|
|
|
- style={{
|
|
|
- fontSize: "14px",
|
|
|
- fontWeight: 500,
|
|
|
- marginBottom: "4px",
|
|
|
- }}
|
|
|
- >
|
|
|
- {msg.type === "user"
|
|
|
- ? "你"
|
|
|
- : msg.model
|
|
|
- ? models?.find((m) => m.uid === msg.model)?.name
|
|
|
- : "AI助手"}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {editingMessageId === msg.id ? (
|
|
|
- <div>
|
|
|
- <TextArea
|
|
|
- value={editingContent}
|
|
|
- onChange={(e) =>
|
|
|
- setEditingContent(e.target.value)
|
|
|
- }
|
|
|
- onKeyPress={handleEditKeyPress}
|
|
|
- autoSize={{ minRows: 2, maxRows: 8 }}
|
|
|
- style={{ marginBottom: "8px" }}
|
|
|
- />
|
|
|
- <Space size="small">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="primary"
|
|
|
- icon={<CheckOutlined />}
|
|
|
- onClick={confirmEdit}
|
|
|
- >
|
|
|
- 确认
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- icon={<CloseOutlined />}
|
|
|
- onClick={cancelEdit}
|
|
|
- >
|
|
|
- 取消
|
|
|
- </Button>
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <div>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- fontSize: "14px",
|
|
|
- lineHeight: "1.5",
|
|
|
- whiteSpace: "pre-wrap",
|
|
|
- wordBreak: "break-word",
|
|
|
- }}
|
|
|
- >
|
|
|
- <Marked text={msg.content} />
|
|
|
- </div>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- fontSize: "12px",
|
|
|
- opacity: 0.6,
|
|
|
- marginTop: "8px",
|
|
|
- }}
|
|
|
- >
|
|
|
- {msg.timestamp}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- <Space>
|
|
|
- {msg.versions && msg.versions.length > 1 && (
|
|
|
- <div style={{ marginBottom: "8px" }}>
|
|
|
- <Space size="small">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="text"
|
|
|
- icon={<LeftOutlined />}
|
|
|
- disabled={msg.currentVersionIndex === 0}
|
|
|
- onClick={() =>
|
|
|
- switchMessageVersion(index, "prev")
|
|
|
- }
|
|
|
- />
|
|
|
- <Text
|
|
|
- style={{
|
|
|
- fontSize: "12px",
|
|
|
- color:
|
|
|
- msg.type === "user"
|
|
|
- ? "rgba(255,255,255,0.7)"
|
|
|
- : "#666",
|
|
|
- }}
|
|
|
- >
|
|
|
- {(msg.currentVersionIndex || 0) + 1}/
|
|
|
- {msg.versions.length}
|
|
|
- </Text>
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="text"
|
|
|
- icon={<RightOutlined />}
|
|
|
- disabled={
|
|
|
- msg.currentVersionIndex ===
|
|
|
- msg.versions.length - 1
|
|
|
- }
|
|
|
- onClick={() =>
|
|
|
- switchMessageVersion(index, "next")
|
|
|
- }
|
|
|
- />
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {editingMessageId !== msg.id && (
|
|
|
- <div>
|
|
|
- <Space size="small">
|
|
|
- <Tooltip title="复制">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="text"
|
|
|
- icon={<CopyOutlined />}
|
|
|
- onClick={() => copyMessage(msg.content)}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- {msg.type === "user" ? (
|
|
|
- <Tooltip title="编辑">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="text"
|
|
|
- icon={<EditOutlined />}
|
|
|
- onClick={() => startEditingMessage(index)}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- ) : msg.type === "error" ? (
|
|
|
- <Tooltip title="重试">
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="text"
|
|
|
- icon={<ReloadOutlined />}
|
|
|
- onClick={() => refreshAIResponse(index)}
|
|
|
- />
|
|
|
- </Tooltip>
|
|
|
- ) : (
|
|
|
- <Dropdown
|
|
|
- menu={refreshMenu(index)}
|
|
|
- trigger={["hover"]}
|
|
|
- >
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="text"
|
|
|
- icon={<ReloadOutlined />}
|
|
|
- />
|
|
|
- </Dropdown>
|
|
|
- )}
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- )
|
|
|
+ />
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ return <>unknown</>;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })}
|
|
|
+ {error ? (
|
|
|
+ <MsgError
|
|
|
+ message={error}
|
|
|
+ onRefresh={() =>
|
|
|
+ refreshAIResponse(messages.length - 1, fetchModel)
|
|
|
+ }
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <></>
|
|
|
)}
|
|
|
-
|
|
|
{isTyping && (
|
|
|
- <div style={{ display: "flex", justifyContent: "flex-start" }}>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- maxWidth: "70%",
|
|
|
- backgroundColor: "#ffffff",
|
|
|
- border: "1px solid #d9d9d9",
|
|
|
- borderRadius: "8px",
|
|
|
- padding: "16px",
|
|
|
- boxShadow: "0 1px 2px rgba(0, 0, 0, 0.03)",
|
|
|
- }}
|
|
|
- >
|
|
|
- <div style={{ display: "flex", alignItems: "flex-start" }}>
|
|
|
- <Space>
|
|
|
- <Avatar
|
|
|
- size={32}
|
|
|
- icon={<RobotOutlined />}
|
|
|
- style={{ backgroundColor: "#595959" }}
|
|
|
- />
|
|
|
- <div>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- fontSize: "14px",
|
|
|
- fontWeight: 500,
|
|
|
- marginBottom: "4px",
|
|
|
- }}
|
|
|
- >
|
|
|
- {models?.find((m) => m.uid === selectedModel)?.name ||
|
|
|
- "AI助手"}
|
|
|
- </div>
|
|
|
- <Marked text={currentTypingMessage} />
|
|
|
- </div>
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <MsgTyping
|
|
|
+ text={currentTypingMessage}
|
|
|
+ model={models?.find((m) => m.uid === fetchModel)}
|
|
|
+ />
|
|
|
)}
|
|
|
|
|
|
{isLoading && !isTyping && (
|
|
|
- <div style={{ display: "flex", justifyContent: "flex-start" }}>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- maxWidth: "70%",
|
|
|
- backgroundColor: "#ffffff",
|
|
|
- border: "1px solid #d9d9d9",
|
|
|
- borderRadius: "8px",
|
|
|
- padding: "16px",
|
|
|
- boxShadow: "0 1px 2px rgba(0, 0, 0, 0.03)",
|
|
|
- }}
|
|
|
- >
|
|
|
- <div style={{ display: "flex", alignItems: "center" }}>
|
|
|
- <Space>
|
|
|
- <Avatar
|
|
|
- size={32}
|
|
|
- icon={<RobotOutlined />}
|
|
|
- style={{ backgroundColor: "#595959" }}
|
|
|
- />
|
|
|
- <Spin size="small" />
|
|
|
- <span style={{ fontSize: "14px", color: "#666" }}>
|
|
|
- 正在思考...
|
|
|
- </span>
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <MsgLoading model={models?.find((m) => m.uid === fetchModel)} />
|
|
|
)}
|
|
|
</Space>
|
|
|
<div ref={messagesEndRef} />
|