DebugPanel.jsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useState, useEffect } from 'react';
  16. import {
  17. Card,
  18. Typography,
  19. Tabs,
  20. TabPane,
  21. Button,
  22. Dropdown,
  23. } from '@douyinfe/semi-ui';
  24. import { Code, Zap, Clock, X, Eye, Send } from 'lucide-react';
  25. import { useTranslation } from 'react-i18next';
  26. import CodeViewer from './CodeViewer';
  27. import SSEViewer from './SSEViewer';
  28. const DebugPanel = ({
  29. debugData,
  30. activeDebugTab,
  31. onActiveDebugTabChange,
  32. styleState,
  33. onCloseDebugPanel,
  34. customRequestMode,
  35. }) => {
  36. const { t } = useTranslation();
  37. const [activeKey, setActiveKey] = useState(activeDebugTab);
  38. useEffect(() => {
  39. setActiveKey(activeDebugTab);
  40. }, [activeDebugTab]);
  41. const handleTabChange = (key) => {
  42. setActiveKey(key);
  43. onActiveDebugTabChange(key);
  44. };
  45. const renderArrow = (items, pos, handleArrowClick, defaultNode) => {
  46. const style = {
  47. width: 32,
  48. height: 32,
  49. margin: '0 12px',
  50. display: 'flex',
  51. justifyContent: 'center',
  52. alignItems: 'center',
  53. borderRadius: '100%',
  54. background: 'rgba(var(--semi-grey-1), 1)',
  55. color: 'var(--semi-color-text)',
  56. cursor: 'pointer',
  57. };
  58. return (
  59. <Dropdown
  60. render={
  61. <Dropdown.Menu>
  62. {items.map((item) => {
  63. return (
  64. <Dropdown.Item
  65. key={item.itemKey}
  66. onClick={() => handleTabChange(item.itemKey)}
  67. >
  68. {item.tab}
  69. </Dropdown.Item>
  70. );
  71. })}
  72. </Dropdown.Menu>
  73. }
  74. >
  75. {pos === 'start' ? (
  76. <div style={style} onClick={handleArrowClick}>
  77. </div>
  78. ) : (
  79. <div style={style} onClick={handleArrowClick}>
  80. </div>
  81. )}
  82. </Dropdown>
  83. );
  84. };
  85. return (
  86. <Card
  87. className='h-full flex flex-col'
  88. bordered={false}
  89. bodyStyle={{
  90. padding: styleState.isMobile ? '16px' : '24px',
  91. height: '100%',
  92. display: 'flex',
  93. flexDirection: 'column',
  94. }}
  95. >
  96. <div className='flex items-center justify-between mb-6 flex-shrink-0'>
  97. <div className='flex items-center'>
  98. <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'>
  99. <Code size={20} className='text-white' />
  100. </div>
  101. <Typography.Title heading={5} className='mb-0'>
  102. {t('调试信息')}
  103. </Typography.Title>
  104. </div>
  105. {styleState.isMobile && onCloseDebugPanel && (
  106. <Button
  107. icon={<X size={16} />}
  108. onClick={onCloseDebugPanel}
  109. theme='borderless'
  110. type='tertiary'
  111. size='small'
  112. className='!rounded-lg'
  113. />
  114. )}
  115. </div>
  116. <div className='flex-1 overflow-hidden debug-panel'>
  117. <Tabs
  118. renderArrow={renderArrow}
  119. type='card'
  120. collapsible
  121. className='h-full'
  122. style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
  123. activeKey={activeKey}
  124. onChange={handleTabChange}
  125. >
  126. <TabPane
  127. tab={
  128. <div className='flex items-center gap-2'>
  129. <Eye size={16} />
  130. {t('预览请求体')}
  131. {customRequestMode && (
  132. <span className='px-1.5 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full'>
  133. 自定义
  134. </span>
  135. )}
  136. </div>
  137. }
  138. itemKey='preview'
  139. >
  140. <CodeViewer
  141. content={debugData.previewRequest}
  142. title='preview'
  143. language='json'
  144. />
  145. </TabPane>
  146. <TabPane
  147. tab={
  148. <div className='flex items-center gap-2'>
  149. <Send size={16} />
  150. {t('实际请求体')}
  151. </div>
  152. }
  153. itemKey='request'
  154. >
  155. <CodeViewer
  156. content={debugData.request}
  157. title='request'
  158. language='json'
  159. />
  160. </TabPane>
  161. <TabPane
  162. tab={
  163. <div className='flex items-center gap-2'>
  164. <Zap size={16} />
  165. {t('响应')}
  166. {debugData.sseMessages && debugData.sseMessages.length > 0 && (
  167. <span className='px-1.5 py-0.5 text-xs bg-blue-100 text-blue-600 rounded-full'>
  168. SSE ({debugData.sseMessages.length})
  169. </span>
  170. )}
  171. </div>
  172. }
  173. itemKey='response'
  174. >
  175. {debugData.sseMessages && debugData.sseMessages.length > 0 ? (
  176. <SSEViewer
  177. sseData={debugData.sseMessages}
  178. title='response'
  179. />
  180. ) : (
  181. <CodeViewer
  182. content={debugData.response}
  183. title='response'
  184. language='json'
  185. />
  186. )}
  187. </TabPane>
  188. </Tabs>
  189. </div>
  190. <div className='flex items-center justify-between mt-4 pt-4 flex-shrink-0'>
  191. {(debugData.timestamp || debugData.previewTimestamp) && (
  192. <div className='flex items-center gap-2'>
  193. <Clock size={14} className='text-gray-500' />
  194. <Typography.Text className='text-xs text-gray-500'>
  195. {activeKey === 'preview' && debugData.previewTimestamp
  196. ? `${t('预览更新')}: ${new Date(debugData.previewTimestamp).toLocaleString()}`
  197. : debugData.timestamp
  198. ? `${t('最后请求')}: ${new Date(debugData.timestamp).toLocaleString()}`
  199. : ''}
  200. </Typography.Text>
  201. </div>
  202. )}
  203. </div>
  204. </Card>
  205. );
  206. };
  207. export default DebugPanel;