refactor: extract model tester debug panel

This commit is contained in:
Cita
2026-03-23 13:35:42 +08:00
parent 9b21ed3fed
commit 8f14540549
3 changed files with 166 additions and 96 deletions
+10 -96
View File
@@ -53,6 +53,7 @@ import {
isConversationUploadedFileSupported,
resolveConversationFileCapability,
} from './helpers/conversationFileCapabilities.js';
import DebugPanel from './model-tester/DebugPanel.js';
import ModernSelect from '../components/ModernSelect.js';
import { useAnimatedVisibility } from '../components/useAnimatedVisibility.js';
import { useIsMobile } from '../components/useIsMobile.js';
@@ -3104,102 +3105,15 @@ export default function ModelTester() {
</div>
</div>
{debugPanelPresence.shouldRender && (
<div className={`card panel-presence ${debugPanelPresence.isVisible ? '' : 'is-closing'}`.trim()} style={{ padding: 14, minHeight: isMobile ? 'auto' : 680, maxHeight: isMobile ? 'none' : 740, display: 'flex', flexDirection: 'column', order: isMobile ? 3 : 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<h3 style={{ margin: 0, fontSize: 15 }}>调试</h3>
<div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
{debugTimestamp ? new Date(debugTimestamp).toLocaleString() : '--'}
</div>
</div>
<div style={{ display: 'flex', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
<button
className="btn btn-ghost"
style={{
border: activeDebugTab === DEBUG_TABS.PREVIEW ? '1px solid var(--color-primary)' : '1px solid var(--color-border)',
color: activeDebugTab === DEBUG_TABS.PREVIEW ? 'var(--color-primary)' : 'var(--color-text-secondary)',
}}
onClick={() => setActiveDebugTab(DEBUG_TABS.PREVIEW)}
>
预览
</button>
<button
className="btn btn-ghost"
style={{
border: activeDebugTab === DEBUG_TABS.REQUEST ? '1px solid var(--color-primary)' : '1px solid var(--color-border)',
color: activeDebugTab === DEBUG_TABS.REQUEST ? 'var(--color-primary)' : 'var(--color-text-secondary)',
}}
onClick={() => setActiveDebugTab(DEBUG_TABS.REQUEST)}
>
请求
</button>
<button
className="btn btn-ghost"
style={{
border: activeDebugTab === DEBUG_TABS.RESPONSE ? '1px solid var(--color-primary)' : '1px solid var(--color-border)',
color: activeDebugTab === DEBUG_TABS.RESPONSE ? 'var(--color-primary)' : 'var(--color-text-secondary)',
}}
onClick={() => setActiveDebugTab(DEBUG_TABS.RESPONSE)}
>
响应
</button>
</div>
<div style={{ flex: 1, overflow: 'hidden', border: '1px solid var(--color-border-light)', borderRadius: 'var(--radius-sm)', background: 'var(--color-bg)' }}>
<pre style={{
margin: 0,
padding: 12,
fontSize: 12,
lineHeight: 1.55,
fontFamily: 'var(--font-mono)',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
overflow: 'auto',
maxHeight: '100%',
}}>
{debugTabContent || '// 暂无数据'}
</pre>
</div>
<div style={{ marginTop: 10, fontSize: 12, fontWeight: 600 }}>时间线</div>
<div style={{
marginTop: 6,
border: '1px solid var(--color-border-light)',
borderRadius: 'var(--radius-sm)',
padding: 8,
minHeight: 120,
maxHeight: 170,
overflowY: 'auto',
background: 'var(--color-bg)',
}}>
{debugTimeline.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', fontSize: 12 }}>暂无事件。</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{debugTimeline.map((item, index) => (
<div key={`${item.at}-${index}`} style={{ fontSize: 11, color: 'var(--color-text-secondary)', lineHeight: 1.45 }}>
<span style={{
display: 'inline-block',
minWidth: 40,
marginRight: 6,
color: item.level === 'error' ? 'var(--color-danger)' : item.level === 'warn' ? 'var(--color-warning)' : 'var(--color-primary)',
fontWeight: 700,
textTransform: 'uppercase',
}}>
{item.level}
</span>
<span style={{ color: 'var(--color-text-muted)', marginRight: 6 }}>
{new Date(item.at).toLocaleTimeString()}
</span>
<span>{item.text}</span>
</div>
))}
</div>
)}
</div>
</div>
)}
<DebugPanel
presence={debugPanelPresence}
isMobile={isMobile}
debugTimestamp={debugTimestamp}
activeDebugTab={activeDebugTab}
onTabChange={setActiveDebugTab}
debugTabContent={debugTabContent}
debugTimeline={debugTimeline}
/>
</div>
</div>
);
+144
View File
@@ -0,0 +1,144 @@
import React from 'react';
import { DEBUG_TABS, type DebugTab } from '../helpers/modelTesterSession.js';
type DebugTimelineEntry = {
at: string;
level: 'info' | 'warn' | 'error';
text: string;
};
type DebugPanelPresence = {
shouldRender: boolean;
isVisible: boolean;
};
type DebugPanelProps = {
presence: DebugPanelPresence;
isMobile: boolean;
debugTimestamp: number | null;
activeDebugTab: DebugTab;
onTabChange: (tab: DebugTab) => void;
debugTabContent: string;
debugTimeline: DebugTimelineEntry[];
};
export default function DebugPanel({
presence,
isMobile,
debugTimestamp,
activeDebugTab,
onTabChange,
debugTabContent,
debugTimeline,
}: DebugPanelProps) {
if (!presence.shouldRender) {
return null;
}
return (
<div
className={`card panel-presence ${presence.isVisible ? '' : 'is-closing'}`.trim()}
style={{
padding: 14,
minHeight: isMobile ? 'auto' : 680,
maxHeight: isMobile ? 'none' : 740,
display: 'flex',
flexDirection: 'column',
order: isMobile ? 3 : 0,
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<h3 style={{ margin: 0, fontSize: 15 }}>调试</h3>
<div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
{debugTimestamp ? new Date(debugTimestamp).toLocaleString() : '--'}
</div>
</div>
<div style={{ display: 'flex', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
<button
className="btn btn-ghost"
style={{
border: activeDebugTab === DEBUG_TABS.PREVIEW ? '1px solid var(--color-primary)' : '1px solid var(--color-border)',
color: activeDebugTab === DEBUG_TABS.PREVIEW ? 'var(--color-primary)' : 'var(--color-text-secondary)',
}}
onClick={() => onTabChange(DEBUG_TABS.PREVIEW)}
>
预览
</button>
<button
className="btn btn-ghost"
style={{
border: activeDebugTab === DEBUG_TABS.REQUEST ? '1px solid var(--color-primary)' : '1px solid var(--color-border)',
color: activeDebugTab === DEBUG_TABS.REQUEST ? 'var(--color-primary)' : 'var(--color-text-secondary)',
}}
onClick={() => onTabChange(DEBUG_TABS.REQUEST)}
>
请求
</button>
<button
className="btn btn-ghost"
style={{
border: activeDebugTab === DEBUG_TABS.RESPONSE ? '1px solid var(--color-primary)' : '1px solid var(--color-border)',
color: activeDebugTab === DEBUG_TABS.RESPONSE ? 'var(--color-primary)' : 'var(--color-text-secondary)',
}}
onClick={() => onTabChange(DEBUG_TABS.RESPONSE)}
>
响应
</button>
</div>
<div style={{ flex: 1, overflow: 'hidden', border: '1px solid var(--color-border-light)', borderRadius: 'var(--radius-sm)', background: 'var(--color-bg)' }}>
<pre style={{
margin: 0,
padding: 12,
fontSize: 12,
lineHeight: 1.55,
fontFamily: 'var(--font-mono)',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
overflow: 'auto',
maxHeight: '100%',
}}>
{debugTabContent || '// 暂无数据'}
</pre>
</div>
<div style={{ marginTop: 10, fontSize: 12, fontWeight: 600 }}>时间线</div>
<div style={{
marginTop: 6,
border: '1px solid var(--color-border-light)',
borderRadius: 'var(--radius-sm)',
padding: 8,
minHeight: 120,
maxHeight: 170,
overflowY: 'auto',
background: 'var(--color-bg)',
}}>
{debugTimeline.length === 0 ? (
<div style={{ color: 'var(--color-text-muted)', fontSize: 12 }}>暂无事件。</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{debugTimeline.map((item, index) => (
<div key={`${item.at}-${index}`} style={{ fontSize: 11, color: 'var(--color-text-secondary)', lineHeight: 1.45 }}>
<span style={{
display: 'inline-block',
minWidth: 40,
marginRight: 6,
color: item.level === 'error' ? 'var(--color-danger)' : item.level === 'warn' ? 'var(--color-warning)' : 'var(--color-primary)',
fontWeight: 700,
textTransform: 'uppercase',
}}>
{item.level}
</span>
<span style={{ color: 'var(--color-text-muted)', marginRight: 6 }}>
{new Date(item.at).toLocaleTimeString()}
</span>
<span>{item.text}</span>
</div>
))}
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,12 @@
import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
describe('ModelTester debug panel extraction', () => {
it('delegates the debug sidebar to a dedicated model-tester component', () => {
const source = readFileSync(resolve(process.cwd(), 'src/web/pages/ModelTester.tsx'), 'utf8').replace(/\r\n/g, '\n');
expect(source).toContain("import DebugPanel from './model-tester/DebugPanel.js'");
expect(source).not.toContain('时间线');
});
});