| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import React, { useState, useEffect } from 'react'; |
| | import { |
| | Card, |
| | Typography, |
| | Tabs, |
| | TabPane, |
| | Button, |
| | Dropdown, |
| | } from '@douyinfe/semi-ui'; |
| | import { Code, Zap, Clock, X, Eye, Send } from 'lucide-react'; |
| | import { useTranslation } from 'react-i18next'; |
| | import CodeViewer from './CodeViewer'; |
| | import SSEViewer from './SSEViewer'; |
| |
|
| | const DebugPanel = ({ |
| | debugData, |
| | activeDebugTab, |
| | onActiveDebugTabChange, |
| | styleState, |
| | onCloseDebugPanel, |
| | customRequestMode, |
| | }) => { |
| | const { t } = useTranslation(); |
| |
|
| | const [activeKey, setActiveKey] = useState(activeDebugTab); |
| |
|
| | useEffect(() => { |
| | setActiveKey(activeDebugTab); |
| | }, [activeDebugTab]); |
| |
|
| | const handleTabChange = (key) => { |
| | setActiveKey(key); |
| | onActiveDebugTabChange(key); |
| | }; |
| |
|
| | const renderArrow = (items, pos, handleArrowClick, defaultNode) => { |
| | const style = { |
| | width: 32, |
| | height: 32, |
| | margin: '0 12px', |
| | display: 'flex', |
| | justifyContent: 'center', |
| | alignItems: 'center', |
| | borderRadius: '100%', |
| | background: 'rgba(var(--semi-grey-1), 1)', |
| | color: 'var(--semi-color-text)', |
| | cursor: 'pointer', |
| | }; |
| |
|
| | return ( |
| | <Dropdown |
| | render={ |
| | <Dropdown.Menu> |
| | {items.map((item) => { |
| | return ( |
| | <Dropdown.Item |
| | key={item.itemKey} |
| | onClick={() => handleTabChange(item.itemKey)} |
| | > |
| | {item.tab} |
| | </Dropdown.Item> |
| | ); |
| | })} |
| | </Dropdown.Menu> |
| | } |
| | > |
| | {pos === 'start' ? ( |
| | <div style={style} onClick={handleArrowClick}> |
| | ← |
| | </div> |
| | ) : ( |
| | <div style={style} onClick={handleArrowClick}> |
| | → |
| | </div> |
| | )} |
| | </Dropdown> |
| | ); |
| | }; |
| |
|
| | return ( |
| | <Card |
| | className='h-full flex flex-col' |
| | bordered={false} |
| | bodyStyle={{ |
| | padding: styleState.isMobile ? '16px' : '24px', |
| | height: '100%', |
| | display: 'flex', |
| | flexDirection: 'column', |
| | }} |
| | > |
| | <div className='flex items-center justify-between mb-6 flex-shrink-0'> |
| | <div className='flex items-center'> |
| | <div className='w-10 h-10 rounded-full bg-gradient-to-r from-green-500 to-blue-500 flex items-center justify-center mr-3'> |
| | <Code size={20} className='text-white' /> |
| | </div> |
| | <Typography.Title heading={5} className='mb-0'> |
| | {t('调试信息')} |
| | </Typography.Title> |
| | </div> |
| | |
| | {styleState.isMobile && onCloseDebugPanel && ( |
| | <Button |
| | icon={<X size={16} />} |
| | onClick={onCloseDebugPanel} |
| | theme='borderless' |
| | type='tertiary' |
| | size='small' |
| | className='!rounded-lg' |
| | /> |
| | )} |
| | </div> |
| | |
| | <div className='flex-1 overflow-hidden debug-panel'> |
| | <Tabs |
| | renderArrow={renderArrow} |
| | type='card' |
| | collapsible |
| | className='h-full' |
| | style={{ height: '100%', display: 'flex', flexDirection: 'column' }} |
| | activeKey={activeKey} |
| | onChange={handleTabChange} |
| | > |
| | <TabPane |
| | tab={ |
| | <div className='flex items-center gap-2'> |
| | <Eye size={16} /> |
| | {t('预览请求体')} |
| | {customRequestMode && ( |
| | <span className='px-1.5 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full'> |
| | 自定义 |
| | </span> |
| | )} |
| | </div> |
| | } |
| | itemKey='preview' |
| | > |
| | <CodeViewer |
| | content={debugData.previewRequest} |
| | title='preview' |
| | language='json' |
| | /> |
| | </TabPane> |
| | |
| | <TabPane |
| | tab={ |
| | <div className='flex items-center gap-2'> |
| | <Send size={16} /> |
| | {t('实际请求体')} |
| | </div> |
| | } |
| | itemKey='request' |
| | > |
| | <CodeViewer |
| | content={debugData.request} |
| | title='request' |
| | language='json' |
| | /> |
| | </TabPane> |
| | |
| | <TabPane |
| | tab={ |
| | <div className='flex items-center gap-2'> |
| | <Zap size={16} /> |
| | {t('响应')} |
| | {debugData.sseMessages && debugData.sseMessages.length > 0 && ( |
| | <span className='px-1.5 py-0.5 text-xs bg-blue-100 text-blue-600 rounded-full'> |
| | SSE ({debugData.sseMessages.length}) |
| | </span> |
| | )} |
| | </div> |
| | } |
| | itemKey='response' |
| | > |
| | {debugData.sseMessages && debugData.sseMessages.length > 0 ? ( |
| | <SSEViewer |
| | sseData={debugData.sseMessages} |
| | title='response' |
| | /> |
| | ) : ( |
| | <CodeViewer |
| | content={debugData.response} |
| | title='response' |
| | language='json' |
| | /> |
| | )} |
| | </TabPane> |
| | </Tabs> |
| | </div> |
| | |
| | <div className='flex items-center justify-between mt-4 pt-4 flex-shrink-0'> |
| | {(debugData.timestamp || debugData.previewTimestamp) && ( |
| | <div className='flex items-center gap-2'> |
| | <Clock size={14} className='text-gray-500' /> |
| | <Typography.Text className='text-xs text-gray-500'> |
| | {activeKey === 'preview' && debugData.previewTimestamp |
| | ? `${t('预览更新')}: ${new Date(debugData.previewTimestamp).toLocaleString()}` |
| | : debugData.timestamp |
| | ? `${t('最后请求')}: ${new Date(debugData.timestamp).toLocaleString()}` |
| | : ''} |
| | </Typography.Text> |
| | </div> |
| | )} |
| | </div> |
| | </Card> |
| | ); |
| | }; |
| |
|
| | export default DebugPanel; |
| |
|