import React, { useContext, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Layout, Toast, Modal } from '@douyinfe/semi-ui';
// Context
import { UserContext } from '../../context/User/index.js';
import { StyleContext } from '../../context/Style/index.js';
// Utils and hooks
import { API, showError, getLogo, isMobile } from '../../helpers/index.js';
import { stringToColor } from '../../helpers/render.js';
import { usePlaygroundState } from '../../hooks/usePlaygroundState.js';
import { useMessageActions } from '../../hooks/useMessageActions.js';
import { useApiRequest } from '../../hooks/useApiRequest.js';
// Constants and utils
import {
DEFAULT_MESSAGES,
MESSAGE_ROLES,
API_ENDPOINTS
} from '../../utils/constants.js';
import {
buildMessageContent,
createMessage,
createLoadingAssistantMessage,
getTextContent
} from '../../utils/messageUtils.js';
import {
buildApiPayload,
processModelsData,
processGroupsData
} from '../../utils/apiUtils.js';
// Components
import SettingsPanel from '../../components/playground/SettingsPanel.js';
import ChatArea from '../../components/playground/ChatArea.js';
import DebugPanel from '../../components/playground/DebugPanel.js';
import MessageContent from '../../components/playground/MessageContent.js';
import MessageActions from '../../components/playground/MessageActions.js';
import FloatingButtons from '../../components/playground/FloatingButtons.js';
// 生成头像
const generateAvatarDataUrl = (username) => {
if (!username) {
return 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png';
}
const firstLetter = username[0].toUpperCase();
const bgColor = stringToColor(username);
const svg = `
`;
return `data:image/svg+xml;base64,${btoa(svg)}`;
};
const Playground = () => {
const { t } = useTranslation();
const [userState] = useContext(UserContext);
const [styleState, styleDispatch] = useContext(StyleContext);
const [searchParams] = useSearchParams();
// 使用自定义hooks
const state = usePlaygroundState();
const {
inputs,
parameterEnabled,
systemPrompt,
showDebugPanel,
showSettings,
models,
groups,
status,
message,
debugData,
activeDebugTab,
previewPayload,
editingMessageId,
editValue,
sseSourceRef,
chatRef,
handleInputChange,
handleParameterToggle,
debouncedSaveConfig,
handleConfigImport,
handleConfigReset,
setShowSettings,
setModels,
setGroups,
setStatus,
setMessage,
setDebugData,
setActiveDebugTab,
setPreviewPayload,
setEditingMessageId,
setEditValue,
setSystemPrompt,
setShowDebugPanel,
} = state;
// API 请求相关
const { sendRequest, onStopGenerator } = useApiRequest(
setMessage,
setDebugData,
setActiveDebugTab,
sseSourceRef
);
// 角色信息
const roleInfo = {
user: {
name: userState?.user?.username || 'User',
avatar: generateAvatarDataUrl(userState?.user?.username),
},
assistant: {
name: 'Assistant',
avatar: getLogo(),
},
system: {
name: 'System',
avatar: getLogo(),
},
};
// 消息操作
const messageActions = useMessageActions(message, setMessage, onMessageSend);
// 构建预览请求体
const constructPreviewPayload = useCallback(() => {
try {
const systemMessage = systemPrompt !== '' ? createMessage(
MESSAGE_ROLES.SYSTEM,
systemPrompt,
{ id: '1', createAt: 1715676751919 }
) : null;
let messages = [...message];
// 如果没有用户消息,添加默认消息
if (messages.length === 0 || messages.every(msg => msg.role !== MESSAGE_ROLES.USER)) {
const validImageUrls = inputs.imageUrls ? inputs.imageUrls.filter(url => url.trim() !== '') : [];
const content = buildMessageContent('你好', validImageUrls, inputs.imageEnabled);
messages = [createMessage(MESSAGE_ROLES.USER, content)];
} else {
// 处理最后一个用户消息的图片
const lastUserMessageIndex = messages.length - 1;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === MESSAGE_ROLES.USER) {
if (inputs.imageEnabled && inputs.imageUrls) {
const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
if (validImageUrls.length > 0) {
const textContent = getTextContent(messages[i]) || '示例消息';
const content = buildMessageContent(textContent, validImageUrls, true);
messages[i] = { ...messages[i], content };
}
}
break;
}
}
}
return buildApiPayload(messages, systemMessage, inputs, parameterEnabled);
} catch (error) {
console.error('构造预览请求体失败:', error);
return null;
}
}, [inputs, parameterEnabled, systemPrompt, message]);
// 发送消息
function onMessageSend(content, attachment) {
console.log('attachment: ', attachment);
const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
const messageContent = buildMessageContent(content, validImageUrls, inputs.imageEnabled);
const userMessage = createMessage(MESSAGE_ROLES.USER, messageContent);
const loadingMessage = createLoadingAssistantMessage();
setMessage(prevMessage => {
const newMessages = [...prevMessage, userMessage];
const systemMessage = systemPrompt !== '' ? createMessage(
MESSAGE_ROLES.SYSTEM,
systemPrompt,
{ id: '1', createAt: 1715676751919 }
) : null;
const payload = buildApiPayload(newMessages, systemMessage, inputs, parameterEnabled);
sendRequest(payload, inputs.stream);
// 禁用图片模式
if (inputs.imageEnabled) {
setTimeout(() => {
handleInputChange('imageEnabled', false);
}, 100);
}
return [...newMessages, loadingMessage];
});
}
// 加载模型和分组
const loadModels = async () => {
try {
const res = await API.get(API_ENDPOINTS.USER_MODELS);
const { success, message, data } = res.data;
if (success) {
const { modelOptions, selectedModel } = processModelsData(data, inputs.model);
setModels(modelOptions);
if (selectedModel !== inputs.model) {
handleInputChange('model', selectedModel);
}
} else {
showError(t(message));
}
} catch (error) {
showError(t('加载模型失败'));
}
};
const loadGroups = async () => {
try {
const res = await API.get(API_ENDPOINTS.USER_GROUPS);
const { success, message, data } = res.data;
if (success) {
const userGroup = userState?.user?.group || JSON.parse(localStorage.getItem('user'))?.group;
const groupOptions = processGroupsData(data, userGroup);
setGroups(groupOptions);
const hasCurrentGroup = groupOptions.some(option => option.value === inputs.group);
if (!hasCurrentGroup) {
handleInputChange('group', groupOptions[0]?.value || '');
}
} else {
showError(t(message));
}
} catch (error) {
showError(t('加载分组失败'));
}
};
// 编辑消息相关
const handleMessageEdit = useCallback((targetMessage) => {
const editableContent = getTextContent(targetMessage);
setEditingMessageId(targetMessage.id);
setEditValue(editableContent);
}, [setEditingMessageId, setEditValue]);
const handleEditSave = useCallback(() => {
if (!editingMessageId || !editValue.trim()) return;
setMessage(prevMessages => {
const messageIndex = prevMessages.findIndex(msg => msg.id === editingMessageId);
if (messageIndex === -1) return prevMessages;
const targetMessage = prevMessages[messageIndex];
let newContent;
if (Array.isArray(targetMessage.content)) {
newContent = targetMessage.content.map(item =>
item.type === 'text' ? { ...item, text: editValue.trim() } : item
);
} else {
newContent = editValue.trim();
}
const updatedMessages = prevMessages.map(msg =>
msg.id === editingMessageId ? { ...msg, content: newContent } : msg
);
// 处理用户消息编辑后的重新生成
if (targetMessage.role === MESSAGE_ROLES.USER) {
const hasSubsequentAssistantReply = messageIndex < prevMessages.length - 1 &&
prevMessages[messageIndex + 1].role === MESSAGE_ROLES.ASSISTANT;
if (hasSubsequentAssistantReply) {
Modal.confirm({
title: t('消息已编辑'),
content: t('检测到该消息后有AI回复,是否删除后续回复并重新生成?'),
okText: t('重新生成'),
cancelText: t('仅保存'),
onOk: () => {
const messagesUntilUser = updatedMessages.slice(0, messageIndex + 1);
setMessage(messagesUntilUser);
setTimeout(() => {
const systemMessage = systemPrompt !== '' ? createMessage(
MESSAGE_ROLES.SYSTEM,
systemPrompt,
{ id: '1', createAt: 1715676751919 }
) : null;
const payload = buildApiPayload(messagesUntilUser, systemMessage, inputs, parameterEnabled);
setMessage(prevMsg => [...prevMsg, createLoadingAssistantMessage()]);
sendRequest(payload, inputs.stream);
}, 100);
},
onCancel: () => setMessage(updatedMessages)
});
return prevMessages;
}
}
return updatedMessages;
});
setEditingMessageId(null);
setEditValue('');
Toast.success({ content: t('消息已更新'), duration: 2 });
}, [editingMessageId, editValue, t, systemPrompt, inputs, parameterEnabled, sendRequest, setMessage, setEditingMessageId, setEditValue]);
const handleEditCancel = useCallback(() => {
setEditingMessageId(null);
setEditValue('');
}, [setEditingMessageId, setEditValue]);
// 切换推理展开状态
const toggleReasoningExpansion = (messageId) => {
setMessage(prevMessages =>
prevMessages.map(msg =>
msg.id === messageId && msg.role === MESSAGE_ROLES.ASSISTANT
? { ...msg, isReasoningExpanded: !msg.isReasoningExpanded }
: msg
)
);
};
// 渲染函数
const renderCustomChatContent = useCallback(
({ message, className }) => {
const isCurrentlyEditing = editingMessageId === message.id;
return (