2025-07-19 03:30:44 +08:00
|
|
|
|
/*
|
|
|
|
|
|
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
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
import React, { useContext, useEffect, useCallback, useRef } from 'react';
|
2025-05-26 14:35:35 +08:00
|
|
|
|
import { useSearchParams } from 'react-router-dom';
|
2025-05-30 22:14:44 +08:00
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
|
|
import { Layout, Toast, Modal } from '@douyinfe/semi-ui';
|
|
|
|
|
|
|
|
|
|
|
|
// Context
|
2024-12-11 18:27:30 +08:00
|
|
|
|
import { UserContext } from '../../context/User/index.js';
|
🚀 feat(web/channels): Deep modular refactor of Channels table
1. Split monolithic `ChannelsTable` (2200+ LOC) into focused components
• `channels/index.jsx` – composition entry
• `ChannelsTable.jsx` – pure `<Table>` rendering
• `ChannelsActions.jsx` – bulk & settings toolbar
• `ChannelsFilters.jsx` – search / create / column-settings form
• `ChannelsTabs.jsx` – type tabs
• `ChannelsColumnDefs.js` – column definitions & render helpers
• `modals/` – BatchTag, ColumnSelector, ModelTest modals
2. Extract domain hook
• Moved `useChannelsData.js` → `src/hooks/channels/useChannelsData.js`
– centralises state, API calls, pagination, filters, batch ops
– now exports `setActivePage`, fixing tab / status switch errors
3. Update wiring
• All sub-components consume data via `useChannelsData` props
• Adjusted import paths after hook relocation
4. Clean legacy file
• Legacy `components/table/ChannelsTable.js` now re-exports new module
5. Bug fixes
• Tab switching, status filter & tag aggregation restored
• Column selector & batch actions operate via unified hook
This commit completes the first phase of modularising the Channels feature, laying groundwork for consistent, maintainable table architecture across the app.
2025-07-18 21:05:36 +08:00
|
|
|
|
import { useIsMobile } from '../../hooks/common/useIsMobile.js';
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-03 23:56:39 +08:00
|
|
|
|
// hooks
|
🚀 feat(web/channels): Deep modular refactor of Channels table
1. Split monolithic `ChannelsTable` (2200+ LOC) into focused components
• `channels/index.jsx` – composition entry
• `ChannelsTable.jsx` – pure `<Table>` rendering
• `ChannelsActions.jsx` – bulk & settings toolbar
• `ChannelsFilters.jsx` – search / create / column-settings form
• `ChannelsTabs.jsx` – type tabs
• `ChannelsColumnDefs.js` – column definitions & render helpers
• `modals/` – BatchTag, ColumnSelector, ModelTest modals
2. Extract domain hook
• Moved `useChannelsData.js` → `src/hooks/channels/useChannelsData.js`
– centralises state, API calls, pagination, filters, batch ops
– now exports `setActivePage`, fixing tab / status switch errors
3. Update wiring
• All sub-components consume data via `useChannelsData` props
• Adjusted import paths after hook relocation
4. Clean legacy file
• Legacy `components/table/ChannelsTable.js` now re-exports new module
5. Bug fixes
• Tab switching, status filter & tag aggregation restored
• Column selector & batch actions operate via unified hook
This commit completes the first phase of modularising the Channels feature, laying groundwork for consistent, maintainable table architecture across the app.
2025-07-18 21:05:36 +08:00
|
|
|
|
import { usePlaygroundState } from '../../hooks/playground/usePlaygroundState.js';
|
|
|
|
|
|
import { useMessageActions } from '../../hooks/playground/useMessageActions.js';
|
|
|
|
|
|
import { useApiRequest } from '../../hooks/playground/useApiRequest.js';
|
|
|
|
|
|
import { useSyncMessageAndCustomBody } from '../../hooks/playground/useSyncMessageAndCustomBody.js';
|
|
|
|
|
|
import { useMessageEdit } from '../../hooks/playground/useMessageEdit.js';
|
|
|
|
|
|
import { useDataLoader } from '../../hooks/playground/useDataLoader.js';
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
// Constants and utils
|
|
|
|
|
|
import {
|
|
|
|
|
|
MESSAGE_ROLES,
|
2025-06-01 17:07:36 +08:00
|
|
|
|
ERROR_MESSAGES
|
2025-06-03 16:13:50 +08:00
|
|
|
|
} from '../../constants/playground.constants.js';
|
2025-05-30 22:14:44 +08:00
|
|
|
|
import {
|
2025-06-03 23:56:39 +08:00
|
|
|
|
getLogo,
|
|
|
|
|
|
stringToColor,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
buildMessageContent,
|
|
|
|
|
|
createMessage,
|
|
|
|
|
|
createLoadingAssistantMessage,
|
2025-06-01 17:07:36 +08:00
|
|
|
|
getTextContent,
|
|
|
|
|
|
buildApiPayload
|
2025-06-03 23:56:39 +08:00
|
|
|
|
} from '../../helpers';
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// Components
|
2025-06-01 17:07:36 +08:00
|
|
|
|
import {
|
|
|
|
|
|
OptimizedSettingsPanel,
|
|
|
|
|
|
OptimizedDebugPanel,
|
|
|
|
|
|
OptimizedMessageContent,
|
|
|
|
|
|
OptimizedMessageActions
|
|
|
|
|
|
} from '../../components/playground/OptimizedComponents.js';
|
2025-05-30 22:29:02 +08:00
|
|
|
|
import ChatArea from '../../components/playground/ChatArea.js';
|
|
|
|
|
|
import FloatingButtons from '../../components/playground/FloatingButtons.js';
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 生成头像
|
2025-05-26 14:35:35 +08:00
|
|
|
|
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 = `
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
|
|
|
|
<circle cx="16" cy="16" r="16" fill="${bgColor}" />
|
|
|
|
|
|
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-size="16" fill="#ffffff" font-family="sans-serif">${firstLetter}</text>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
`;
|
|
|
|
|
|
return `data:image/svg+xml;base64,${btoa(svg)}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-09-26 00:59:09 +08:00
|
|
|
|
const Playground = () => {
|
2024-12-13 19:03:14 +08:00
|
|
|
|
const { t } = useTranslation();
|
2025-05-30 22:14:44 +08:00
|
|
|
|
const [userState] = useContext(UserContext);
|
📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
2025-07-16 02:54:58 +08:00
|
|
|
|
const isMobile = useIsMobile();
|
|
|
|
|
|
const styleState = { isMobile };
|
2025-05-30 22:14:44 +08:00
|
|
|
|
const [searchParams] = useSearchParams();
|
|
|
|
|
|
|
|
|
|
|
|
const state = usePlaygroundState();
|
|
|
|
|
|
const {
|
|
|
|
|
|
inputs,
|
|
|
|
|
|
parameterEnabled,
|
|
|
|
|
|
showDebugPanel,
|
2025-06-01 17:07:36 +08:00
|
|
|
|
customRequestMode,
|
|
|
|
|
|
customRequestBody,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
showSettings,
|
|
|
|
|
|
models,
|
|
|
|
|
|
groups,
|
|
|
|
|
|
status,
|
|
|
|
|
|
message,
|
|
|
|
|
|
debugData,
|
|
|
|
|
|
activeDebugTab,
|
|
|
|
|
|
previewPayload,
|
|
|
|
|
|
sseSourceRef,
|
|
|
|
|
|
chatRef,
|
|
|
|
|
|
handleInputChange,
|
|
|
|
|
|
handleParameterToggle,
|
|
|
|
|
|
debouncedSaveConfig,
|
2025-06-02 21:21:46 +08:00
|
|
|
|
saveMessagesImmediately,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
handleConfigImport,
|
|
|
|
|
|
handleConfigReset,
|
|
|
|
|
|
setShowSettings,
|
|
|
|
|
|
setModels,
|
|
|
|
|
|
setGroups,
|
|
|
|
|
|
setStatus,
|
|
|
|
|
|
setMessage,
|
|
|
|
|
|
setDebugData,
|
|
|
|
|
|
setActiveDebugTab,
|
|
|
|
|
|
setPreviewPayload,
|
|
|
|
|
|
setShowDebugPanel,
|
2025-06-01 17:07:36 +08:00
|
|
|
|
setCustomRequestMode,
|
|
|
|
|
|
setCustomRequestBody,
|
2025-05-30 22:14:44 +08:00
|
|
|
|
} = state;
|
|
|
|
|
|
|
|
|
|
|
|
// API 请求相关
|
|
|
|
|
|
const { sendRequest, onStopGenerator } = useApiRequest(
|
|
|
|
|
|
setMessage,
|
|
|
|
|
|
setDebugData,
|
|
|
|
|
|
setActiveDebugTab,
|
2025-06-02 21:21:46 +08:00
|
|
|
|
sseSourceRef,
|
|
|
|
|
|
saveMessagesImmediately
|
2025-05-30 22:14:44 +08:00
|
|
|
|
);
|
2025-05-26 14:35:35 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
// 数据加载
|
|
|
|
|
|
useDataLoader(userState, inputs, handleInputChange, setModels, setGroups);
|
|
|
|
|
|
|
|
|
|
|
|
// 消息编辑
|
|
|
|
|
|
const {
|
|
|
|
|
|
editingMessageId,
|
|
|
|
|
|
editValue,
|
|
|
|
|
|
setEditValue,
|
|
|
|
|
|
handleMessageEdit,
|
|
|
|
|
|
handleEditSave,
|
|
|
|
|
|
handleEditCancel
|
2025-06-02 21:21:46 +08:00
|
|
|
|
} = useMessageEdit(setMessage, inputs, parameterEnabled, sendRequest, saveMessagesImmediately);
|
2025-06-01 17:07:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 消息和自定义请求体同步
|
|
|
|
|
|
const { syncMessageToCustomBody, syncCustomBodyToMessage } = useSyncMessageAndCustomBody(
|
|
|
|
|
|
customRequestMode,
|
|
|
|
|
|
customRequestBody,
|
|
|
|
|
|
message,
|
|
|
|
|
|
inputs,
|
|
|
|
|
|
setCustomRequestBody,
|
|
|
|
|
|
setMessage,
|
|
|
|
|
|
debouncedSaveConfig
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 角色信息
|
2025-05-26 14:35:35 +08:00
|
|
|
|
const roleInfo = {
|
|
|
|
|
|
user: {
|
|
|
|
|
|
name: userState?.user?.username || 'User',
|
|
|
|
|
|
avatar: generateAvatarDataUrl(userState?.user?.username),
|
|
|
|
|
|
},
|
|
|
|
|
|
assistant: {
|
|
|
|
|
|
name: 'Assistant',
|
|
|
|
|
|
avatar: getLogo(),
|
|
|
|
|
|
},
|
|
|
|
|
|
system: {
|
|
|
|
|
|
name: 'System',
|
2025-05-30 21:51:09 +08:00
|
|
|
|
avatar: getLogo(),
|
2025-05-26 14:35:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
};
|
2025-04-04 12:00:38 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 消息操作
|
2025-06-02 21:21:46 +08:00
|
|
|
|
const messageActions = useMessageActions(message, setMessage, onMessageSend, saveMessagesImmediately);
|
2025-05-30 21:34:13 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 构建预览请求体
|
2025-05-30 21:34:13 +08:00
|
|
|
|
const constructPreviewPayload = useCallback(() => {
|
|
|
|
|
|
try {
|
2025-06-01 17:07:36 +08:00
|
|
|
|
// 如果是自定义请求体模式且有自定义内容,直接返回解析后的自定义请求体
|
|
|
|
|
|
if (customRequestMode && customRequestBody && customRequestBody.trim()) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(customRequestBody);
|
|
|
|
|
|
} catch (parseError) {
|
|
|
|
|
|
console.warn('自定义请求体JSON解析失败,回退到默认预览:', parseError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
// 默认预览逻辑
|
2025-05-30 22:14:44 +08:00
|
|
|
|
let messages = [...message];
|
|
|
|
|
|
|
2025-06-03 01:25:21 +08:00
|
|
|
|
// 如果存在用户消息
|
|
|
|
|
|
if (!(messages.length === 0 || messages.every(msg => msg.role !== MESSAGE_ROLES.USER))) {
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 处理最后一个用户消息的图片
|
2025-05-30 21:34:13 +08:00
|
|
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
2025-05-30 22:14:44 +08:00
|
|
|
|
if (messages[i].role === MESSAGE_ROLES.USER) {
|
2025-05-30 21:34:13 +08:00
|
|
|
|
if (inputs.imageEnabled && inputs.imageUrls) {
|
|
|
|
|
|
const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
|
|
|
|
|
|
if (validImageUrls.length > 0) {
|
2025-05-30 22:14:44 +08:00
|
|
|
|
const textContent = getTextContent(messages[i]) || '示例消息';
|
|
|
|
|
|
const content = buildMessageContent(textContent, validImageUrls, true);
|
|
|
|
|
|
messages[i] = { ...messages[i], content };
|
2025-05-30 21:34:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
return buildApiPayload(messages, null, inputs, parameterEnabled);
|
2025-05-30 21:34:13 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('构造预览请求体失败:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-06-01 17:07:36 +08:00
|
|
|
|
}, [inputs, parameterEnabled, message, customRequestMode, customRequestBody]);
|
2025-05-30 21:34:13 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 发送消息
|
|
|
|
|
|
function onMessageSend(content, attachment) {
|
|
|
|
|
|
console.log('attachment: ', attachment);
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
// 创建用户消息和加载消息
|
|
|
|
|
|
const userMessage = createMessage(MESSAGE_ROLES.USER, content);
|
|
|
|
|
|
const loadingMessage = createLoadingAssistantMessage();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是自定义请求体模式
|
|
|
|
|
|
if (customRequestMode && customRequestBody) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const customPayload = JSON.parse(customRequestBody);
|
|
|
|
|
|
|
|
|
|
|
|
setMessage(prevMessage => {
|
|
|
|
|
|
const newMessages = [...prevMessage, userMessage, loadingMessage];
|
|
|
|
|
|
|
|
|
|
|
|
// 发送自定义请求体
|
|
|
|
|
|
sendRequest(customPayload, customPayload.stream !== false);
|
|
|
|
|
|
|
2025-06-02 21:39:51 +08:00
|
|
|
|
// 发送消息后保存,传入新消息列表
|
|
|
|
|
|
setTimeout(() => saveMessagesImmediately(newMessages), 0);
|
2025-06-02 21:21:46 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
return newMessages;
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('自定义请求体JSON解析失败:', error);
|
|
|
|
|
|
Toast.error(ERROR_MESSAGES.JSON_PARSE_ERROR);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 默认模式
|
2025-05-30 22:14:44 +08:00
|
|
|
|
const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
|
|
|
|
|
|
const messageContent = buildMessageContent(content, validImageUrls, inputs.imageEnabled);
|
2025-06-01 17:07:36 +08:00
|
|
|
|
const userMessageWithImages = createMessage(MESSAGE_ROLES.USER, messageContent);
|
2024-09-26 00:59:09 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
setMessage(prevMessage => {
|
2025-06-01 17:07:36 +08:00
|
|
|
|
const newMessages = [...prevMessage, userMessageWithImages];
|
2024-12-14 14:09:30 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
const payload = buildApiPayload(newMessages, null, inputs, parameterEnabled);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
sendRequest(payload, inputs.stream);
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 禁用图片模式
|
|
|
|
|
|
if (inputs.imageEnabled) {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
handleInputChange('imageEnabled', false);
|
|
|
|
|
|
}, 100);
|
2025-05-30 19:24:17 +08:00
|
|
|
|
}
|
2024-09-26 00:59:09 +08:00
|
|
|
|
|
2025-06-02 21:39:51 +08:00
|
|
|
|
// 发送消息后保存,传入新消息列表(包含用户消息和加载消息)
|
|
|
|
|
|
const messagesWithLoading = [...newMessages, loadingMessage];
|
|
|
|
|
|
setTimeout(() => saveMessagesImmediately(messagesWithLoading), 0);
|
2025-06-02 21:21:46 +08:00
|
|
|
|
|
2025-06-02 21:39:51 +08:00
|
|
|
|
return messagesWithLoading;
|
2025-05-30 22:14:44 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-05-29 03:56:08 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 切换推理展开状态
|
2025-06-01 17:07:36 +08:00
|
|
|
|
const toggleReasoningExpansion = useCallback((messageId) => {
|
2025-05-30 19:24:17 +08:00
|
|
|
|
setMessage(prevMessages =>
|
|
|
|
|
|
prevMessages.map(msg =>
|
2025-05-30 22:14:44 +08:00
|
|
|
|
msg.id === messageId && msg.role === MESSAGE_ROLES.ASSISTANT
|
2025-05-30 19:24:17 +08:00
|
|
|
|
? { ...msg, isReasoningExpanded: !msg.isReasoningExpanded }
|
|
|
|
|
|
: msg
|
|
|
|
|
|
)
|
2024-12-11 18:27:30 +08:00
|
|
|
|
);
|
2025-06-01 17:07:36 +08:00
|
|
|
|
}, [setMessage]);
|
2024-12-11 18:27:30 +08:00
|
|
|
|
|
2025-05-30 22:14:44 +08:00
|
|
|
|
// 渲染函数
|
2025-05-26 14:35:35 +08:00
|
|
|
|
const renderCustomChatContent = useCallback(
|
|
|
|
|
|
({ message, className }) => {
|
2025-05-30 22:14:44 +08:00
|
|
|
|
const isCurrentlyEditing = editingMessageId === message.id;
|
|
|
|
|
|
|
2025-05-26 14:35:35 +08:00
|
|
|
|
return (
|
2025-06-01 17:07:36 +08:00
|
|
|
|
<OptimizedMessageContent
|
2025-05-30 19:24:17 +08:00
|
|
|
|
message={message}
|
|
|
|
|
|
className={className}
|
|
|
|
|
|
styleState={styleState}
|
|
|
|
|
|
onToggleReasoningExpansion={toggleReasoningExpansion}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
isEditing={isCurrentlyEditing}
|
|
|
|
|
|
onEditSave={handleEditSave}
|
|
|
|
|
|
onEditCancel={handleEditCancel}
|
|
|
|
|
|
editValue={editValue}
|
|
|
|
|
|
onEditValueChange={setEditValue}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
/>
|
2025-05-26 14:35:35 +08:00
|
|
|
|
);
|
|
|
|
|
|
},
|
2025-06-01 17:07:36 +08:00
|
|
|
|
[styleState, editingMessageId, editValue, handleEditSave, handleEditCancel, setEditValue, toggleReasoningExpansion],
|
2025-05-26 14:35:35 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-05-30 19:24:17 +08:00
|
|
|
|
const renderChatBoxAction = useCallback((props) => {
|
|
|
|
|
|
const { message: currentMessage } = props;
|
2025-05-30 22:14:44 +08:00
|
|
|
|
const isAnyMessageGenerating = message.some(msg =>
|
|
|
|
|
|
msg.status === 'loading' || msg.status === 'incomplete'
|
|
|
|
|
|
);
|
|
|
|
|
|
const isCurrentlyEditing = editingMessageId === currentMessage.id;
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
2025-06-01 17:07:36 +08:00
|
|
|
|
<OptimizedMessageActions
|
2025-05-30 19:24:17 +08:00
|
|
|
|
message={currentMessage}
|
|
|
|
|
|
styleState={styleState}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
onMessageReset={messageActions.handleMessageReset}
|
|
|
|
|
|
onMessageCopy={messageActions.handleMessageCopy}
|
|
|
|
|
|
onMessageDelete={messageActions.handleMessageDelete}
|
|
|
|
|
|
onRoleToggle={messageActions.handleRoleToggle}
|
|
|
|
|
|
onMessageEdit={handleMessageEdit}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
isAnyMessageGenerating={isAnyMessageGenerating}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
isEditing={isCurrentlyEditing}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
/>
|
|
|
|
|
|
);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
}, [messageActions, styleState, message, editingMessageId, handleMessageEdit]);
|
|
|
|
|
|
|
|
|
|
|
|
// Effects
|
2025-06-01 17:07:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 同步消息和自定义请求体
|
2025-05-30 22:14:44 +08:00
|
|
|
|
useEffect(() => {
|
2025-06-01 17:07:36 +08:00
|
|
|
|
syncMessageToCustomBody();
|
|
|
|
|
|
}, [message, syncMessageToCustomBody]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
syncCustomBodyToMessage();
|
|
|
|
|
|
}, [customRequestBody, syncCustomBodyToMessage]);
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
// 处理URL参数
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (searchParams.get('expired')) {
|
|
|
|
|
|
Toast.warning(t('登录过期,请重新登录!'));
|
|
|
|
|
|
}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
}, [searchParams, t]);
|
|
|
|
|
|
|
📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
2025-07-16 02:54:58 +08:00
|
|
|
|
// Playground 组件无需再监听窗口变化,isMobile 由 useIsMobile Hook 自动更新
|
2025-05-30 22:14:44 +08:00
|
|
|
|
|
2025-06-01 17:07:36 +08:00
|
|
|
|
// 构建预览payload
|
2025-05-30 22:14:44 +08:00
|
|
|
|
useEffect(() => {
|
2025-06-01 17:07:36 +08:00
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
|
const preview = constructPreviewPayload();
|
|
|
|
|
|
setPreviewPayload(preview);
|
|
|
|
|
|
setDebugData(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
previewRequest: preview ? JSON.stringify(preview, null, 2) : null,
|
|
|
|
|
|
previewTimestamp: preview ? new Date().toISOString() : null
|
|
|
|
|
|
}));
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
|
|
}, [message, inputs, parameterEnabled, customRequestMode, customRequestBody, constructPreviewPayload, setPreviewPayload, setDebugData]);
|
|
|
|
|
|
|
|
|
|
|
|
// 自动保存配置
|
2025-05-30 22:14:44 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
debouncedSaveConfig();
|
2025-06-01 17:07:36 +08:00
|
|
|
|
}, [inputs, parameterEnabled, showDebugPanel, customRequestMode, customRequestBody, debouncedSaveConfig]);
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
2025-06-02 21:21:46 +08:00
|
|
|
|
// 清空对话的处理函数
|
|
|
|
|
|
const handleClearMessages = useCallback(() => {
|
|
|
|
|
|
setMessage([]);
|
2025-06-02 21:39:51 +08:00
|
|
|
|
// 清空对话后保存,传入空数组
|
|
|
|
|
|
setTimeout(() => saveMessagesImmediately([]), 0);
|
2025-06-02 21:21:46 +08:00
|
|
|
|
}, [setMessage, saveMessagesImmediately]);
|
|
|
|
|
|
|
2024-09-26 00:59:09 +08:00
|
|
|
|
return (
|
2025-07-20 18:30:42 +08:00
|
|
|
|
<div className="h-full">
|
|
|
|
|
|
<Layout className="h-full bg-transparent flex flex-col md:flex-row">
|
📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
2025-07-16 02:54:58 +08:00
|
|
|
|
{(showSettings || !isMobile) && (
|
2025-05-29 03:56:08 +08:00
|
|
|
|
<Layout.Sider
|
2025-07-20 18:30:42 +08:00
|
|
|
|
className={`
|
|
|
|
|
|
bg-transparent border-r-0 flex-shrink-0 overflow-auto mt-[60px]
|
|
|
|
|
|
${isMobile
|
|
|
|
|
|
? 'fixed top-0 left-0 right-0 bottom-0 z-[1000] w-full h-auto bg-white shadow-lg'
|
|
|
|
|
|
: 'relative z-[1] w-80 h-[calc(100vh-66px)]'
|
|
|
|
|
|
}
|
|
|
|
|
|
`}
|
📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
2025-07-16 02:54:58 +08:00
|
|
|
|
width={isMobile ? '100%' : 320}
|
2025-05-29 03:56:08 +08:00
|
|
|
|
>
|
2025-06-01 17:07:36 +08:00
|
|
|
|
<OptimizedSettingsPanel
|
2025-05-30 19:24:17 +08:00
|
|
|
|
inputs={inputs}
|
|
|
|
|
|
parameterEnabled={parameterEnabled}
|
|
|
|
|
|
models={models}
|
|
|
|
|
|
groups={groups}
|
|
|
|
|
|
styleState={styleState}
|
|
|
|
|
|
showSettings={showSettings}
|
|
|
|
|
|
showDebugPanel={showDebugPanel}
|
2025-06-01 17:07:36 +08:00
|
|
|
|
customRequestMode={customRequestMode}
|
|
|
|
|
|
customRequestBody={customRequestBody}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
onInputChange={handleInputChange}
|
|
|
|
|
|
onParameterToggle={handleParameterToggle}
|
|
|
|
|
|
onCloseSettings={() => setShowSettings(false)}
|
|
|
|
|
|
onConfigImport={handleConfigImport}
|
|
|
|
|
|
onConfigReset={handleConfigReset}
|
2025-06-01 17:07:36 +08:00
|
|
|
|
onCustomRequestModeChange={setCustomRequestMode}
|
|
|
|
|
|
onCustomRequestBodyChange={setCustomRequestBody}
|
|
|
|
|
|
previewPayload={previewPayload}
|
|
|
|
|
|
messages={message}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
/>
|
2025-05-29 03:56:08 +08:00
|
|
|
|
</Layout.Sider>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<Layout.Content className="relative flex-1 overflow-hidden">
|
2025-07-20 18:30:42 +08:00
|
|
|
|
<div className="overflow-hidden flex flex-col lg:flex-row h-[calc(100vh-66px)] mt-[60px]">
|
2025-05-29 03:56:08 +08:00
|
|
|
|
<div className="flex-1 flex flex-col">
|
2025-05-30 19:24:17 +08:00
|
|
|
|
<ChatArea
|
|
|
|
|
|
chatRef={chatRef}
|
|
|
|
|
|
message={message}
|
|
|
|
|
|
inputs={inputs}
|
|
|
|
|
|
styleState={styleState}
|
|
|
|
|
|
showDebugPanel={showDebugPanel}
|
|
|
|
|
|
roleInfo={roleInfo}
|
|
|
|
|
|
onMessageSend={onMessageSend}
|
2025-05-30 22:14:44 +08:00
|
|
|
|
onMessageCopy={messageActions.handleMessageCopy}
|
|
|
|
|
|
onMessageReset={messageActions.handleMessageReset}
|
|
|
|
|
|
onMessageDelete={messageActions.handleMessageDelete}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
onStopGenerator={onStopGenerator}
|
2025-06-02 21:21:46 +08:00
|
|
|
|
onClearMessages={handleClearMessages}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
onToggleDebugPanel={() => setShowDebugPanel(!showDebugPanel)}
|
|
|
|
|
|
renderCustomChatContent={renderCustomChatContent}
|
|
|
|
|
|
renderChatBoxAction={renderChatBoxAction}
|
|
|
|
|
|
/>
|
2024-12-11 18:27:30 +08:00
|
|
|
|
</div>
|
2025-05-29 03:56:08 +08:00
|
|
|
|
|
2025-05-30 19:24:17 +08:00
|
|
|
|
{/* 调试面板 - 桌面端 */}
|
📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
2025-07-16 02:54:58 +08:00
|
|
|
|
{showDebugPanel && !isMobile && (
|
2025-05-30 19:24:17 +08:00
|
|
|
|
<div className="w-96 flex-shrink-0 h-full">
|
2025-06-01 17:07:36 +08:00
|
|
|
|
<OptimizedDebugPanel
|
2025-05-30 19:24:17 +08:00
|
|
|
|
debugData={debugData}
|
|
|
|
|
|
activeDebugTab={activeDebugTab}
|
|
|
|
|
|
onActiveDebugTabChange={setActiveDebugTab}
|
|
|
|
|
|
styleState={styleState}
|
2025-06-01 17:07:36 +08:00
|
|
|
|
customRequestMode={customRequestMode}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
/>
|
2025-05-29 03:56:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-05-30 19:24:17 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 调试面板 - 移动端覆盖层 */}
|
📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
2025-07-16 02:54:58 +08:00
|
|
|
|
{showDebugPanel && isMobile && (
|
2025-07-20 18:30:42 +08:00
|
|
|
|
<div className="fixed top-0 left-0 right-0 bottom-0 z-[1000] bg-white overflow-auto shadow-lg">
|
2025-06-01 17:07:36 +08:00
|
|
|
|
<OptimizedDebugPanel
|
2025-05-30 19:24:17 +08:00
|
|
|
|
debugData={debugData}
|
|
|
|
|
|
activeDebugTab={activeDebugTab}
|
|
|
|
|
|
onActiveDebugTabChange={setActiveDebugTab}
|
|
|
|
|
|
styleState={styleState}
|
|
|
|
|
|
showDebugPanel={showDebugPanel}
|
|
|
|
|
|
onCloseDebugPanel={() => setShowDebugPanel(false)}
|
2025-06-01 17:07:36 +08:00
|
|
|
|
customRequestMode={customRequestMode}
|
2025-05-30 19:24:17 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 浮动按钮 */}
|
|
|
|
|
|
<FloatingButtons
|
|
|
|
|
|
styleState={styleState}
|
|
|
|
|
|
showSettings={showSettings}
|
|
|
|
|
|
showDebugPanel={showDebugPanel}
|
|
|
|
|
|
onToggleSettings={() => setShowSettings(!showSettings)}
|
|
|
|
|
|
onToggleDebugPanel={() => setShowDebugPanel(!showDebugPanel)}
|
|
|
|
|
|
/>
|
2025-05-29 03:56:08 +08:00
|
|
|
|
</Layout.Content>
|
|
|
|
|
|
</Layout>
|
|
|
|
|
|
</div>
|
2024-09-26 00:59:09 +08:00
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default Playground;
|