refactor: extract model tester debug panel
This commit is contained in:
@@ -53,6 +53,7 @@ import {
|
|||||||
isConversationUploadedFileSupported,
|
isConversationUploadedFileSupported,
|
||||||
resolveConversationFileCapability,
|
resolveConversationFileCapability,
|
||||||
} from './helpers/conversationFileCapabilities.js';
|
} from './helpers/conversationFileCapabilities.js';
|
||||||
|
import DebugPanel from './model-tester/DebugPanel.js';
|
||||||
import ModernSelect from '../components/ModernSelect.js';
|
import ModernSelect from '../components/ModernSelect.js';
|
||||||
import { useAnimatedVisibility } from '../components/useAnimatedVisibility.js';
|
import { useAnimatedVisibility } from '../components/useAnimatedVisibility.js';
|
||||||
import { useIsMobile } from '../components/useIsMobile.js';
|
import { useIsMobile } from '../components/useIsMobile.js';
|
||||||
@@ -3104,102 +3105,15 @@ export default function ModelTester() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{debugPanelPresence.shouldRender && (
|
<DebugPanel
|
||||||
<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 }}>
|
presence={debugPanelPresence}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
|
isMobile={isMobile}
|
||||||
<h3 style={{ margin: 0, fontSize: 15 }}>调试</h3>
|
debugTimestamp={debugTimestamp}
|
||||||
<div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
|
activeDebugTab={activeDebugTab}
|
||||||
{debugTimestamp ? new Date(debugTimestamp).toLocaleString() : '--'}
|
onTabChange={setActiveDebugTab}
|
||||||
</div>
|
debugTabContent={debugTabContent}
|
||||||
</div>
|
debugTimeline={debugTimeline}
|
||||||
|
/>
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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('时间线');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user