diff --git a/packages/napcat-webui-frontend/src/components/display_card/container.tsx b/packages/napcat-webui-frontend/src/components/display_card/container.tsx index 845759bc..c96c9f8a 100644 --- a/packages/napcat-webui-frontend/src/components/display_card/container.tsx +++ b/packages/napcat-webui-frontend/src/components/display_card/container.tsx @@ -4,19 +4,19 @@ import clsx from 'clsx'; import { title } from '../primitives'; export interface ContainerProps { - title: string - tag?: React.ReactNode - action: React.ReactNode - enableSwitch: React.ReactNode - children: React.ReactNode + title: string; + tag?: React.ReactNode; + action: React.ReactNode; + enableSwitch: React.ReactNode; + children: React.ReactNode; } export interface DisplayCardProps { - showType?: boolean - onEdit: () => void - onEnable: () => Promise - onDelete: () => Promise - onEnableDebug: () => Promise + showType?: boolean; + onEdit: () => void; + onEnable: () => Promise; + onDelete: () => Promise; + onEnableDebug: () => Promise; } const DisplayCardContainer: React.FC = ({ @@ -27,7 +27,7 @@ const DisplayCardContainer: React.FC = ({ children, }) => { return ( - + {tag && (
diff --git a/packages/napcat-webui-frontend/src/components/display_network_item.tsx b/packages/napcat-webui-frontend/src/components/display_network_item.tsx index 39563b90..322977b3 100644 --- a/packages/napcat-webui-frontend/src/components/display_network_item.tsx +++ b/packages/napcat-webui-frontend/src/components/display_network_item.tsx @@ -1,12 +1,12 @@ import { Card, CardBody } from '@heroui/card'; import clsx from 'clsx'; -import { title } from '@/components/primitives'; + export interface NetworkItemDisplayProps { - count: number - label: string - size?: 'sm' | 'md' + count: number; + label: string; + size?: 'sm' | 'md'; } const NetworkItemDisplay: React.FC = ({ @@ -17,35 +17,26 @@ const NetworkItemDisplay: React.FC = ({ return (
{count}
{label} diff --git a/packages/napcat-webui-frontend/src/components/file_manage/file_table.tsx b/packages/napcat-webui-frontend/src/components/file_manage/file_table.tsx index b0e0b2d2..d0538adc 100644 --- a/packages/napcat-webui-frontend/src/components/file_manage/file_table.tsx +++ b/packages/napcat-webui-frontend/src/components/file_manage/file_table.tsx @@ -25,21 +25,21 @@ import { supportedPreviewExts } from './file_preview_modal'; import ImageNameButton, { PreviewImage, imageExts } from './image_name_button'; export interface FileTableProps { - files: FileInfo[] - currentPath: string - loading: boolean - sortDescriptor: SortDescriptor - onSortChange: (descriptor: SortDescriptor) => void - selectedFiles: Selection - onSelectionChange: (selected: Selection) => void - onDirectoryClick: (dirPath: string) => void - onEdit: (filePath: string) => void - onPreview: (filePath: string) => void - onRenameRequest: (name: string) => void - onMoveRequest: (name: string) => void - onCopyPath: (fileName: string) => void - onDelete: (filePath: string) => void - onDownload: (filePath: string) => void + files: FileInfo[]; + currentPath: string; + loading: boolean; + sortDescriptor: SortDescriptor; + onSortChange: (descriptor: SortDescriptor) => void; + selectedFiles: Selection; + onSelectionChange: (selected: Selection) => void; + onDirectoryClick: (dirPath: string) => void; + onEdit: (filePath: string) => void; + onPreview: (filePath: string) => void; + onRenameRequest: (name: string) => void; + onMoveRequest: (name: string) => void; + onCopyPath: (fileName: string) => void; + onDelete: (filePath: string) => void; + onDownload: (filePath: string) => void; } const PAGE_SIZE = 20; @@ -112,7 +112,7 @@ export default function FileTable ({ selectedKeys={selectedFiles} selectionMode='multiple' bottomContent={ -
+
setPage(page)} + classNames={{ + cursor: 'bg-primary shadow-lg', + }} />
} + classNames={{ + wrapper: 'bg-white/60 dark:bg-black/40 backdrop-blur-xl border border-white/40 dark:border-white/10 shadow-sm p-0', + th: 'bg-white/40 dark:bg-white/5 backdrop-blur-md text-default-600', + td: 'group-data-[first=true]:first:before:rounded-none group-data-[first=true]:last:before:rounded-none', + }} > @@ -180,7 +188,7 @@ export default function FileTable ({ name={file.name} isDirectory={file.isDirectory} /> - } + } > {file.name} @@ -194,43 +202,43 @@ export default function FileTable ({ {new Date(file.mtime).toLocaleString()} - + diff --git a/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx b/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx index 7e7cf94f..d7785a5e 100644 --- a/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx +++ b/packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx @@ -2,11 +2,12 @@ import { Button } from '@heroui/button'; import { Card, CardBody, CardHeader } from '@heroui/card'; import { Input } from '@heroui/input'; import { Snippet } from '@heroui/snippet'; +import { Modal, ModalBody, ModalContent, ModalHeader } from '@heroui/modal'; +import { Popover, PopoverContent, PopoverTrigger } from '@heroui/popover'; import { useLocalStorage } from '@uidotdev/usehooks'; -import { motion } from 'motion/react'; import { useEffect, useRef, useState } from 'react'; import toast from 'react-hot-toast'; -import { IoLink, IoSend } from 'react-icons/io5'; +import { IoLink, IoSend, IoSettingsSharp } from 'react-icons/io5'; import { PiCatDuotone } from 'react-icons/pi'; import key from '@/const/key'; @@ -38,9 +39,8 @@ const OneBotApiDebug: React.FC = (props) => { }); const [requestBody, setRequestBody] = useState('{}'); const [responseContent, setResponseContent] = useState(''); - const [isCodeEditorOpen, setIsCodeEditorOpen] = useState(false); - const [isResponseOpen, setIsResponseOpen] = useState(false); const [isFetching, setIsFetching] = useState(false); + const [isStructOpen, setIsStructOpen] = useState(false); const responseRef = useRef(null); const parsedRequest = parse(data.request); const parsedResponse = parse(data.response); @@ -70,7 +70,6 @@ const OneBotApiDebug: React.FC = (props) => { }) .finally(() => { setIsFetching(false); - setIsResponseOpen(true); responseRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start', @@ -90,149 +89,202 @@ const OneBotApiDebug: React.FC = (props) => { }, [path]); return ( -
-

- - {data.description} -

-

- } - tooltipProps={{ - content: '点击复制地址', - }} - > - {path} - -

-
- - setHttpConfig({ ...httpConfig, url: e.target.value })} - /> - - setHttpConfig({ ...httpConfig, token: e.target.value })} - /> - -
- - - 请求体 +
+
+
+

+ + {data.description} +

+ } + tooltipProps={{ content: '点击复制地址' }} + > + {path} + - - - - setRequestBody(value ?? '')} - language='json' - height='400px' - /> +
-
+
+ + + + + +
+

请求配置

+ setHttpConfig({ ...httpConfig, url: e.target.value })} + variant='bordered' + labelPlacement='outside' + classNames={{ + inputWrapper: 'bg-default-100/50 backdrop-blur-sm border-default-200/50', + }} + /> + setHttpConfig({ ...httpConfig, token: e.target.value })} + variant='bordered' + labelPlacement='outside' + classNames={{ + inputWrapper: 'bg-default-100/50 backdrop-blur-sm border-default-200/50', + }} + /> +
+
+
+ + +
+
+ +
+ {/* Request Column */} + + +
+ + 请求体 (Request) +
+
- - -
- - - - 响应 - - - - - -
-              
-                {responseContent || (
-                  
暂无响应
- )} -
-
-
-
-
-
-

请求体结构

- -

响应体结构

- + + +
+ setRequestBody(value ?? '')} + language='json' + options={{ + minimap: { enabled: false }, + fontSize: 13, + padding: { top: 10, bottom: 10 }, + scrollBeyondLastLine: false, + }} + /> +
+
+ + + {/* Response Column */} + + + +
+ + 响应 (Response) +
+ +
+ +
+
+                {responseContent || 等待请求响应...}
+              
+
+
+
+ + {/* Struct Display - maybe put in a modal or separate tab? + For now, putting it in a collapsed/compact area at bottom is tricky with "h-[calc(100vh)]". + User wants "Thorough optimization". + I will make Struct Display a Drawer or Modal, OR put it below if we want scrolling. + But I set height to fixed full screen. + Let's put Struct Display in a Tab or Toggle at Top? + Or just let the main container scroll and remove fixed height? + Layout choice: Fixed height editors are good for workflow. Structure is reference. + I will leave Struct Display OUT of the fixed view, or add a toggle to show it. + Let's add a "View Structure" button in header that opens a Modal. + Yes, that's cleaner. + */} + + + {() => ( + <> + + API 数据结构定义 + + +
+
+

请求体结构 (Request)

+ +
+
+

响应体结构 (Response)

+ +
+
+
+ + )} +
+
+ ); }; diff --git a/packages/napcat-webui-frontend/src/components/onebot/api/display_struct.tsx b/packages/napcat-webui-frontend/src/components/onebot/api/display_struct.tsx index 1cb42064..b1acda21 100644 --- a/packages/napcat-webui-frontend/src/components/onebot/api/display_struct.tsx +++ b/packages/napcat-webui-frontend/src/components/onebot/api/display_struct.tsx @@ -8,15 +8,15 @@ import { TbSquareRoundedChevronRightFilled } from 'react-icons/tb'; import type { LiteralValue, ParsedSchema } from '@/utils/zod'; interface DisplayStructProps { - schema: ParsedSchema | ParsedSchema[] + schema: ParsedSchema | ParsedSchema[]; } const SchemaType = ({ type, value, }: { - type: string - value?: LiteralValue + type: string; + value?: LiteralValue; }) => { let name = type; switch (type) { @@ -57,7 +57,7 @@ const SchemaType = ({ }; const SchemaLabel: React.FC<{ - schema: ParsedSchema + schema: ParsedSchema; }> = ({ schema }) => ( <> {Array.isArray(schema.type) @@ -81,8 +81,8 @@ const SchemaLabel: React.FC<{ ); const SchemaContainer: React.FC<{ - schema: ParsedSchema - children: React.ReactNode + schema: ParsedSchema; + children: React.ReactNode; }> = ({ schema, children }) => { const [expanded, setExpanded] = useState(false); @@ -126,7 +126,7 @@ const SchemaContainer: React.FC<{ ); }; -const RenderSchema: React.FC<{ schema: ParsedSchema }> = ({ schema }) => { +const RenderSchema: React.FC<{ schema: ParsedSchema; }> = ({ schema }) => { if (schema.type === 'object') { return ( @@ -193,7 +193,7 @@ const RenderSchema: React.FC<{ schema: ParsedSchema }> = ({ schema }) => { const DisplayStruct: React.FC = ({ schema }) => { return ( -
+
{Array.isArray(schema) ? ( schema.map((s, i) => ) diff --git a/packages/napcat-webui-frontend/src/components/onebot/api/nav_list.tsx b/packages/napcat-webui-frontend/src/components/onebot/api/nav_list.tsx index f035a831..71f1cc91 100644 --- a/packages/napcat-webui-frontend/src/components/onebot/api/nav_list.tsx +++ b/packages/napcat-webui-frontend/src/components/onebot/api/nav_list.tsx @@ -7,10 +7,10 @@ import { useState } from 'react'; import type { OneBotHttpApi, OneBotHttpApiPath } from '@/const/ob_api'; export interface OneBotApiNavListProps { - data: OneBotHttpApi - selectedApi: OneBotHttpApiPath - onSelect: (apiName: OneBotHttpApiPath) => void - openSideBar: boolean + data: OneBotHttpApi; + selectedApi: OneBotHttpApiPath; + onSelect: (apiName: OneBotHttpApiPath) => void; + openSideBar: boolean; } const OneBotApiNavList: React.FC = (props) => { @@ -19,8 +19,8 @@ const OneBotApiNavList: React.FC = (props) => { return ( = (props) => { >
= (props) => { key={apiName} shadow='none' className={clsx( - 'w-full border border-primary-100 rounded-lg mb-1 bg-opacity-30 backdrop-blur-sm text-primary-400', + 'w-full border border-transparent rounded-xl mb-1 bg-transparent hover:bg-white/40 dark:hover:bg-white/10 transition-all text-default-600 dark:text-gray-300', { hidden: !( apiName.includes(searchValue) || @@ -59,7 +59,7 @@ const OneBotApiNavList: React.FC = (props) => { ), }, { - '!bg-opacity-40 border border-primary-400 bg-primary-50 text-primary-600': + '!bg-white/60 dark:!bg-white/10 !border-white/20 shadow-sm !text-primary font-medium': apiName === selectedApi, } )} @@ -69,8 +69,8 @@ const OneBotApiNavList: React.FC = (props) => {

{api.description}

{apiName} diff --git a/packages/napcat-webui-frontend/src/components/onebot/display_card/render.tsx b/packages/napcat-webui-frontend/src/components/onebot/display_card/render.tsx index eb03ea71..06156c83 100644 --- a/packages/napcat-webui-frontend/src/components/onebot/display_card/render.tsx +++ b/packages/napcat-webui-frontend/src/components/onebot/display_card/render.tsx @@ -30,14 +30,14 @@ const itemVariants = { }, }; -function RequestComponent ({ data: _ }: { data: OB11Request }) { +function RequestComponent ({ data: _ }: { data: OB11Request; }) { return
Request消息,暂未适配
; } export interface OneBotItemRenderProps { - data: AllOB11WsResponse[] - index: number - style: React.CSSProperties + data: AllOB11WsResponse[]; + index: number; + style: React.CSSProperties; } export const getItemSize = (event: OB11AllEvent['post_type']) => { @@ -90,7 +90,7 @@ const OneBotItemRender = ({ data, index, style }: OneBotItemRenderProps) => { animate='visible' className='h-full px-2' > - +
{isEvent ? getEventName(msg.post_type) : '请求响应'} diff --git a/packages/napcat-webui-frontend/src/components/qq_info_card.tsx b/packages/napcat-webui-frontend/src/components/qq_info_card.tsx index 36b35846..eb5e2bd2 100644 --- a/packages/napcat-webui-frontend/src/components/qq_info_card.tsx +++ b/packages/napcat-webui-frontend/src/components/qq_info_card.tsx @@ -8,15 +8,15 @@ import { SelfInfo } from '@/types/user'; import PageLoading from './page_loading'; export interface QQInfoCardProps { - data?: SelfInfo - error?: Error - loading?: boolean + data?: SelfInfo; + error?: Error; + loading?: boolean; } const QQInfoCard: React.FC = ({ data, error, loading }) => { return ( @@ -31,28 +31,32 @@ const QQInfoCard: React.FC = ({ data, error, loading }) => { ) : ( - -
+ +
-
-
{data?.nick}
-
{data?.uin}
+
+
+ {data?.nick || '未知用户'} +
+
+ {data?.uin || 'Unknown'} +
)} diff --git a/packages/napcat-webui-frontend/src/components/system_info.tsx b/packages/napcat-webui-frontend/src/components/system_info.tsx index a5535b27..7608867b 100644 --- a/packages/napcat-webui-frontend/src/components/system_info.tsx +++ b/packages/napcat-webui-frontend/src/components/system_info.tsx @@ -30,10 +30,10 @@ const SystemInfoItem: React.FC = ({ endContent, }) => { return ( -
- {icon} -
{title}
-
{value}
+
+
{icon}
+
{title}
+
{value}
{endContent}
); @@ -303,13 +303,13 @@ const SystemInfo: React.FC = (props) => { error: qqVersionError, } = useRequest(WebUIManager.getQQVersion); return ( - - - + + + 系统信息 -
+
= ({ @@ -24,21 +24,21 @@ const SystemStatusItem: React.FC = ({ return (
-
{title}
-
+
{title}
+
{value} - {unit} + {unit && {unit}}
); }; export interface SystemStatusDisplayProps { - data?: SystemStatus + data?: SystemStatus; } const SystemStatusDisplay: React.FC = ({ data }) => { @@ -55,7 +55,7 @@ const SystemStatusDisplay: React.FC = ({ data }) => { } return ( - +
= ({ data }) => {
-

- +

+ CPU

@@ -88,8 +88,8 @@ const SystemStatusDisplay: React.FC = ({ data }) => { unit='%' />
-

- +

+ 内存

diff --git a/packages/napcat-webui-frontend/src/hooks/use-websocket-debug.ts b/packages/napcat-webui-frontend/src/hooks/use-websocket-debug.ts index 4d20954a..87bb0939 100644 --- a/packages/napcat-webui-frontend/src/hooks/use-websocket-debug.ts +++ b/packages/napcat-webui-frontend/src/hooks/use-websocket-debug.ts @@ -1,5 +1,4 @@ import type { Selection } from '@react-types/shared'; -import { useReactive } from 'ahooks'; import { useCallback, useState } from 'react'; import toast from 'react-hot-toast'; import useWebSocket, { ReadyState } from 'react-use-websocket'; @@ -11,8 +10,8 @@ import { isOB11Event, isOB11RequestResponse } from '@/utils/onebot'; import type { AllOB11WsResponse } from '@/types/onebot'; export { ReadyState } from 'react-use-websocket'; -export function useWebSocketDebug (url: string, token: string) { - const messageHistory = useReactive([]); +export function useWebSocketDebug (url: string, token: string, connectOnMount: boolean = true) { + const [messageHistory, setMessageHistory] = useState([]); const [filterTypes, setFilterTypes] = useState('all'); const filteredMessages = messageHistory.filter((msg) => { @@ -22,11 +21,18 @@ export function useWebSocketDebug (url: string, token: string) { return false; }); - const { sendMessage, readyState } = useWebSocket(url, { + const { sendMessage, readyState } = useWebSocket(connectOnMount ? url : null, { + share: false, onMessage: useCallback((event: WebSocketEventMap['message']) => { try { const data = JSON.parse(event.data); - messageHistory.unshift(data); + setMessageHistory((prev) => { + const newHistory = [data, ...prev]; + if (newHistory.length > 500) { + return newHistory.slice(0, 500); + } + return newHistory; + }); } catch (_error) { toast.error('WebSocket 消息解析失败'); } @@ -39,7 +45,7 @@ export function useWebSocketDebug (url: string, token: string) { console.error('WebSocket error:', event); }, onOpen: () => { - messageHistory.splice(0, messageHistory.length); + setMessageHistory([]); }, }); @@ -50,6 +56,10 @@ export function useWebSocketDebug (url: string, token: string) { sendMessage(msg); }; + const clearMessages = useCallback(() => { + setMessageHistory([]); + }, []); + const FilterMessagesType = renderFilterMessageType( filterTypes, setFilterTypes @@ -63,5 +73,6 @@ export function useWebSocketDebug (url: string, token: string) { filterTypes, setFilterTypes, FilterMessagesType, + clearMessages, }; } diff --git a/packages/napcat-webui-frontend/src/layouts/default.tsx b/packages/napcat-webui-frontend/src/layouts/default.tsx index f1a65905..21b3cc4b 100644 --- a/packages/napcat-webui-frontend/src/layouts/default.tsx +++ b/packages/napcat-webui-frontend/src/layouts/default.tsx @@ -99,10 +99,8 @@ const Layout: React.FC<{ children: React.ReactNode; }> = ({ children }) => { transition={{ duration: 0.4 }} className={clsx( 'flex-1 overflow-y-auto', - 'bg-white/60 dark:bg-black/40 backdrop-blur-xl', - 'shadow-[0_8px_32px_0_rgba(31,38,135,0.07)]', 'transition-all duration-300 ease-in-out', - openSideBar ? 'm-3 ml-0 rounded-3xl border border-white/40 dark:border-white/10' : 'm-0 rounded-none', + openSideBar ? 'ml-0' : 'ml-0', 'pb-10 md:pb-0' )} > diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/debug/websocket/index.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/debug/websocket/index.tsx index a1bac1fb..f75c53ea 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/debug/websocket/index.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/debug/websocket/index.tsx @@ -24,9 +24,10 @@ export default function WSDebug () { }); const [inputUrl, setInputUrl] = useState(socketConfig.url); const [inputToken, setInputToken] = useState(socketConfig.token); + const [shouldConnect, setShouldConnect] = useState(false); - const { sendMessage, readyState, FilterMessagesType, filteredMessages } = - useWebSocketDebug(socketConfig.url, socketConfig.token); + const { sendMessage, readyState, FilterMessagesType, filteredMessages, clearMessages } = + useWebSocketDebug(socketConfig.url, socketConfig.token, shouldConnect); const handleConnect = useCallback(() => { if (!inputUrl.startsWith('ws://') && !inputUrl.startsWith('wss://')) { @@ -37,13 +38,18 @@ export default function WSDebug () { url: inputUrl, token: inputToken, }); - }, [inputUrl, inputToken]); + setShouldConnect(true); + }, [inputUrl, inputToken, setSocketConfig]); + + const handleDisconnect = useCallback(() => { + setShouldConnect(false); + }, []); return ( <> Websocket调试 - NapCat WebUI
- +
-
+
{FilterMessagesType}
- +
+ + +
diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/file_manager.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/file_manager.tsx index ffab3a29..7461854f 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/file_manager.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/file_manager.tsx @@ -329,8 +329,8 @@ export default function FileManagerPage () { }); return ( -
-
+
+
- + diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/logs.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/logs.tsx index a6150483..cafb5dcf 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/logs.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/logs.tsx @@ -53,8 +53,8 @@ export default function LogsPage () { classNames={{ panel: 'w-full flex-1 h-full py-0 flex flex-col gap-4', base: 'flex-shrink-0 !h-fit', - tabList: 'bg-opacity-50 backdrop-blur-sm', - cursor: 'bg-opacity-60 backdrop-blur-sm', + tabList: 'bg-white/40 dark:bg-black/20 backdrop-blur-md', + cursor: 'bg-white/80 dark:bg-white/10 backdrop-blur-md shadow-sm', }} > diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/network.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/network.tsx index 8c15619c..5aa4ea93 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/network.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/network.tsx @@ -388,8 +388,8 @@ export default function NetworkPage () { className='max-w-full' items={tabs} classNames={{ - tabList: 'bg-opacity-50 backdrop-blur-sm', - cursor: 'bg-opacity-60 backdrop-blur-sm', + tabList: 'bg-white/40 dark:bg-black/20 backdrop-blur-md', + cursor: 'bg-white/80 dark:bg-white/10 backdrop-blur-md shadow-sm', }} > {(item) => ( diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/terminal.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/terminal.tsx index 9484d310..152943d9 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/terminal.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/terminal.tsx @@ -112,7 +112,7 @@ export default function TerminalPage () { className='h-full overflow-hidden' >
- +