mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-18 20:37:27 +00:00
- Add SSEViewer component for interactive SSE message inspection * Display SSE data stream with collapsible panels * Show parsed JSON with syntax highlighting * Display key information badges (content, tokens, finish reason) * Support copy individual or all SSE messages * Show error messages with detailed information - Support Ctrl+V to paste images in chat input * Enable image paste in CustomInputRender component * Auto-detect and add pasted images to image list * Show toast notifications for paste results - Add complete i18n support for 6 languages * Chinese (zh): Complete translations * English (en): Complete translations * Japanese (ja): Add 28 new translations * French (fr): Add 28 new translations * Russian (ru): Add 28 new translations * Vietnamese (vi): Add 32 new translations - Update .gitignore to exclude data directory
300 lines
8.5 KiB
JavaScript
300 lines
8.5 KiB
JavaScript
/*
|
|
Copyright (C) 2025 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
|
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
DEFAULT_MESSAGES,
|
|
getDefaultMessages,
|
|
DEFAULT_CONFIG,
|
|
DEBUG_TABS,
|
|
MESSAGE_STATUS,
|
|
} from '../../constants/playground.constants';
|
|
import {
|
|
loadConfig,
|
|
saveConfig,
|
|
loadMessages,
|
|
saveMessages,
|
|
} from '../../components/playground/configStorage';
|
|
import { processIncompleteThinkTags } from '../../helpers';
|
|
|
|
export const usePlaygroundState = () => {
|
|
const { t } = useTranslation();
|
|
|
|
// 使用惰性初始化,确保只在组件首次挂载时加载配置和消息
|
|
const [savedConfig] = useState(() => loadConfig());
|
|
const [initialMessages] = useState(() => {
|
|
const loaded = loadMessages();
|
|
// 检查是否是旧的中文默认消息,如果是则清除
|
|
if (loaded && loaded.length === 2 && loaded[0].id === '2' && loaded[1].id === '3') {
|
|
const hasOldChinese =
|
|
loaded[0].content === '你好' ||
|
|
loaded[1].content === '你好,请问有什么可以帮助您的吗?' ||
|
|
loaded[1].content === '你好!很高兴见到你。有什么我可以帮助你的吗?';
|
|
|
|
if (hasOldChinese) {
|
|
// 清除旧的默认消息
|
|
localStorage.removeItem('playground_messages');
|
|
return null;
|
|
}
|
|
}
|
|
return loaded;
|
|
});
|
|
|
|
// 基础配置状态
|
|
const [inputs, setInputs] = useState(
|
|
savedConfig.inputs || DEFAULT_CONFIG.inputs,
|
|
);
|
|
const [parameterEnabled, setParameterEnabled] = useState(
|
|
savedConfig.parameterEnabled || DEFAULT_CONFIG.parameterEnabled,
|
|
);
|
|
const [showDebugPanel, setShowDebugPanel] = useState(
|
|
savedConfig.showDebugPanel || DEFAULT_CONFIG.showDebugPanel,
|
|
);
|
|
const [customRequestMode, setCustomRequestMode] = useState(
|
|
savedConfig.customRequestMode || DEFAULT_CONFIG.customRequestMode,
|
|
);
|
|
const [customRequestBody, setCustomRequestBody] = useState(
|
|
savedConfig.customRequestBody || DEFAULT_CONFIG.customRequestBody,
|
|
);
|
|
|
|
// UI状态
|
|
const [showSettings, setShowSettings] = useState(false);
|
|
const [models, setModels] = useState([]);
|
|
const [groups, setGroups] = useState([]);
|
|
const [status, setStatus] = useState({});
|
|
|
|
// 消息相关状态 - 使用加载的消息或默认消息初始化
|
|
const [message, setMessage] = useState(() => initialMessages || getDefaultMessages(t));
|
|
|
|
// 当语言改变时,如果是默认消息则更新
|
|
useEffect(() => {
|
|
// 只在没有保存的消息时才更新默认消息
|
|
if (!initialMessages) {
|
|
setMessage(getDefaultMessages(t));
|
|
}
|
|
}, [t, initialMessages]); // 当语言改变时
|
|
|
|
// 调试状态
|
|
const [debugData, setDebugData] = useState({
|
|
request: null,
|
|
response: null,
|
|
timestamp: null,
|
|
previewRequest: null,
|
|
previewTimestamp: null,
|
|
});
|
|
const [activeDebugTab, setActiveDebugTab] = useState(DEBUG_TABS.PREVIEW);
|
|
const [previewPayload, setPreviewPayload] = useState(null);
|
|
|
|
// 编辑状态
|
|
const [editingMessageId, setEditingMessageId] = useState(null);
|
|
const [editValue, setEditValue] = useState('');
|
|
|
|
// Refs
|
|
const sseSourceRef = useRef(null);
|
|
const chatRef = useRef(null);
|
|
const saveConfigTimeoutRef = useRef(null);
|
|
const saveMessagesTimeoutRef = useRef(null);
|
|
|
|
// 配置更新函数
|
|
const handleInputChange = useCallback((name, value) => {
|
|
setInputs((prev) => ({ ...prev, [name]: value }));
|
|
}, []);
|
|
|
|
const handleParameterToggle = useCallback((paramName) => {
|
|
setParameterEnabled((prev) => ({
|
|
...prev,
|
|
[paramName]: !prev[paramName],
|
|
}));
|
|
}, []);
|
|
|
|
// 消息保存函数 - 改为立即保存,可以接受参数
|
|
const saveMessagesImmediately = useCallback(
|
|
(messagesToSave) => {
|
|
// 如果提供了参数,使用参数;否则使用当前状态
|
|
saveMessages(messagesToSave || message);
|
|
},
|
|
[message],
|
|
);
|
|
|
|
// 配置保存
|
|
const debouncedSaveConfig = useCallback(() => {
|
|
if (saveConfigTimeoutRef.current) {
|
|
clearTimeout(saveConfigTimeoutRef.current);
|
|
}
|
|
|
|
saveConfigTimeoutRef.current = setTimeout(() => {
|
|
const configToSave = {
|
|
inputs,
|
|
parameterEnabled,
|
|
showDebugPanel,
|
|
customRequestMode,
|
|
customRequestBody,
|
|
};
|
|
saveConfig(configToSave);
|
|
}, 1000);
|
|
}, [
|
|
inputs,
|
|
parameterEnabled,
|
|
showDebugPanel,
|
|
customRequestMode,
|
|
customRequestBody,
|
|
]);
|
|
|
|
// 配置导入/重置
|
|
const handleConfigImport = useCallback((importedConfig) => {
|
|
if (importedConfig.inputs) {
|
|
setInputs((prev) => ({ ...prev, ...importedConfig.inputs }));
|
|
}
|
|
if (importedConfig.parameterEnabled) {
|
|
setParameterEnabled((prev) => ({
|
|
...prev,
|
|
...importedConfig.parameterEnabled,
|
|
}));
|
|
}
|
|
if (typeof importedConfig.showDebugPanel === 'boolean') {
|
|
setShowDebugPanel(importedConfig.showDebugPanel);
|
|
}
|
|
if (importedConfig.customRequestMode) {
|
|
setCustomRequestMode(importedConfig.customRequestMode);
|
|
}
|
|
if (importedConfig.customRequestBody) {
|
|
setCustomRequestBody(importedConfig.customRequestBody);
|
|
}
|
|
// 如果导入的配置包含消息,也恢复消息
|
|
if (importedConfig.messages && Array.isArray(importedConfig.messages)) {
|
|
setMessage(importedConfig.messages);
|
|
}
|
|
}, []);
|
|
|
|
const handleConfigReset = useCallback((options = {}) => {
|
|
const { resetMessages = false } = options;
|
|
|
|
setInputs(DEFAULT_CONFIG.inputs);
|
|
setParameterEnabled(DEFAULT_CONFIG.parameterEnabled);
|
|
setShowDebugPanel(DEFAULT_CONFIG.showDebugPanel);
|
|
setCustomRequestMode(DEFAULT_CONFIG.customRequestMode);
|
|
setCustomRequestBody(DEFAULT_CONFIG.customRequestBody);
|
|
|
|
// 只有在明确指定时才重置消息
|
|
if (resetMessages) {
|
|
setMessage([]);
|
|
setTimeout(() => {
|
|
setMessage(getDefaultMessages(t));
|
|
}, 0);
|
|
}
|
|
}, []);
|
|
|
|
// 清理定时器
|
|
useEffect(() => {
|
|
return () => {
|
|
if (saveConfigTimeoutRef.current) {
|
|
clearTimeout(saveConfigTimeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
// 页面首次加载时,若最后一条消息仍处于 LOADING/INCOMPLETE 状态,自动修复
|
|
useEffect(() => {
|
|
if (!Array.isArray(message) || message.length === 0) return;
|
|
|
|
const lastMsg = message[message.length - 1];
|
|
if (
|
|
lastMsg.status === MESSAGE_STATUS.LOADING ||
|
|
lastMsg.status === MESSAGE_STATUS.INCOMPLETE
|
|
) {
|
|
const processed = processIncompleteThinkTags(
|
|
lastMsg.content || '',
|
|
lastMsg.reasoningContent || '',
|
|
);
|
|
|
|
const fixedLastMsg = {
|
|
...lastMsg,
|
|
status: MESSAGE_STATUS.COMPLETE,
|
|
content: processed.content,
|
|
reasoningContent: processed.reasoningContent || null,
|
|
isThinkingComplete: true,
|
|
};
|
|
|
|
const updatedMessages = [...message.slice(0, -1), fixedLastMsg];
|
|
setMessage(updatedMessages);
|
|
|
|
// 保存修复后的消息列表
|
|
setTimeout(() => saveMessagesImmediately(updatedMessages), 0);
|
|
}
|
|
}, []);
|
|
|
|
return {
|
|
// 配置状态
|
|
inputs,
|
|
parameterEnabled,
|
|
showDebugPanel,
|
|
customRequestMode,
|
|
customRequestBody,
|
|
|
|
// UI状态
|
|
showSettings,
|
|
models,
|
|
groups,
|
|
status,
|
|
|
|
// 消息状态
|
|
message,
|
|
|
|
// 调试状态
|
|
debugData,
|
|
activeDebugTab,
|
|
previewPayload,
|
|
|
|
// 编辑状态
|
|
editingMessageId,
|
|
editValue,
|
|
|
|
// Refs
|
|
sseSourceRef,
|
|
chatRef,
|
|
saveConfigTimeoutRef,
|
|
|
|
// 更新函数
|
|
setInputs,
|
|
setParameterEnabled,
|
|
setShowDebugPanel,
|
|
setCustomRequestMode,
|
|
setCustomRequestBody,
|
|
setShowSettings,
|
|
setModels,
|
|
setGroups,
|
|
setStatus,
|
|
setMessage,
|
|
setDebugData,
|
|
setActiveDebugTab,
|
|
setPreviewPayload,
|
|
setEditingMessageId,
|
|
setEditValue,
|
|
|
|
// 处理函数
|
|
handleInputChange,
|
|
handleParameterToggle,
|
|
debouncedSaveConfig,
|
|
saveMessagesImmediately,
|
|
handleConfigImport,
|
|
handleConfigReset,
|
|
};
|
|
};
|