Files
aiform_dev/frontend/src/components/DebugPanel.tsx
AI Assistant 3621ae6021 feat: Session persistence with Redis + Draft management fixes
- Implement session management API (/api/v1/session/create, verify, logout)
- Add session restoration from localStorage on page reload
- Fix session_id priority when loading drafts (use current, not old from DB)
- Add unified_id and claim_id to wizard payload sent to n8n
- Add Docker volume for frontend HMR (Hot Module Replacement)
- Add comprehensive session logging for debugging

Components updated:
- backend/app/api/session.py (NEW) - Session management endpoints
- backend/app/main.py - Include session router
- frontend/src/components/form/Step1Phone.tsx v2.0 - Create session after SMS
- frontend/src/pages/ClaimForm.tsx v3.8 - Session restoration & priority fix
- frontend/src/components/form/StepWizardPlan.tsx v1.4 - Add unified_id/claim_id
- docker-compose.yml - Add frontend volume for live reload

Session flow:
1. User verifies phone -> session created in Redis (24h TTL)
2. session_token saved to localStorage
3. Page reload -> session restored automatically
4. Draft selected -> current session_id used (not old from DB)
5. Wizard submit -> unified_id, claim_id, session_id sent to n8n
6. Logout -> session removed from Redis & localStorage

Fixes:
- Session token not persisting after page reload
- unified_id missing in n8n webhook payload
- Old session_id from draft overwriting current session
- Frontend changes requiring container rebuild
2025-11-20 18:31:42 +03:00

274 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Card, Timeline, Tag, Descriptions } from 'antd';
import { CheckCircleOutlined, LoadingOutlined, ExclamationCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
interface DebugEvent {
timestamp: string;
type: 'policy_check' | 'ocr' | 'ai_analysis' | 'upload' | 'error';
status: 'pending' | 'success' | 'warning' | 'error';
message: string;
data?: any;
}
interface Props {
events: DebugEvent[];
formData: any;
}
export default function DebugPanel({ events, formData }: Props) {
const getIcon = (status: string) => {
switch (status) {
case 'success': return <CheckCircleOutlined style={{ color: '#52c41a' }} />;
case 'pending': return <LoadingOutlined style={{ color: '#1890ff' }} />;
case 'warning': return <ExclamationCircleOutlined style={{ color: '#faad14' }} />;
case 'error': return <CloseCircleOutlined style={{ color: '#f5222d' }} />;
default: return <CheckCircleOutlined />;
}
};
const getColor = (status: string) => {
switch (status) {
case 'success': return 'success';
case 'pending': return 'processing';
case 'warning': return 'warning';
case 'error': return 'error';
default: return 'default';
}
};
return (
<div style={{
position: 'sticky',
top: 20,
height: 'calc(100vh - 40px)',
overflowY: 'auto'
}}>
<Card
title="🔧 Debug Console"
size="small"
style={{
background: '#1e1e1e',
color: '#d4d4d4',
border: '1px solid #333'
}}
styles={{
header: {
background: '#252526',
color: '#fff',
borderBottom: '1px solid #333'
},
body: {
padding: 12
}
}}
>
{/* Текущие данные формы */}
<div style={{ marginBottom: 16, padding: 12, background: '#2d2d30', borderRadius: 4 }}>
<div style={{ fontSize: 12, color: '#9cdcfe', marginBottom: 8, fontFamily: 'monospace' }}>
<strong>Form Data:</strong>
</div>
<pre style={{
margin: 0,
fontSize: 11,
color: '#ce9178',
fontFamily: 'Consolas, monospace',
maxHeight: 150,
overflow: 'auto'
}}>
{JSON.stringify(formData, null, 2)}
</pre>
</div>
{/* События */}
<div style={{ marginBottom: 8, fontSize: 12, color: '#9cdcfe', fontFamily: 'monospace' }}>
<strong>Events Log:</strong>
</div>
<Timeline
style={{ marginTop: 16 }}
items={events.length === 0 ? [
{
color: 'gray',
children: <span style={{ color: '#888', fontSize: 12 }}>Нет событий...</span>
}
] : events.map((event, index) => ({
key: index,
dot: getIcon(event.status),
children: (
<div style={{ fontSize: 11, fontFamily: 'monospace' }}>
<div style={{ color: '#888', marginBottom: 4 }}>
{event.timestamp}
</div>
<div style={{ marginBottom: 8 }}>
<Tag color={getColor(event.status)} style={{ fontSize: 11 }}>
{event.type.toUpperCase()}
</Tag>
<span style={{ color: '#d4d4d4' }}>{event.message}</span>
</div>
{event.data && (
<div style={{
marginTop: 8,
padding: 8,
background: '#1e1e1e',
borderRadius: 4,
border: '1px solid #333'
}}>
{event.type === 'policy_check' && event.data.found !== undefined && (
<Descriptions size="small" column={1} bordered>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Found</span>}
labelStyle={{ background: '#252526', color: '#9cdcfe' }}
contentStyle={{ background: '#1e1e1e', color: event.data.found ? '#4ec9b0' : '#f48771' }}
>
{event.data.found ? 'TRUE' : 'FALSE'}
</Descriptions.Item>
{event.data.holder_name && (
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Holder</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: '#ce9178' }}
>
{event.data.holder_name}
</Descriptions.Item>
)}
</Descriptions>
)}
{event.type === 'ocr' && (
<pre style={{
margin: 0,
fontSize: 10,
color: '#ce9178',
maxHeight: 100,
overflow: 'auto',
whiteSpace: 'pre-wrap'
}}>
{event.data.text?.substring(0, 300)}...
</pre>
)}
{event.type === 'ai_analysis' && (
<Descriptions size="small" column={1} bordered>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Type</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: '#4ec9b0' }}
>
{event.data.document_type}
</Descriptions.Item>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Valid</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: event.data.is_valid ? '#4ec9b0' : '#f48771' }}
>
{event.data.is_valid ? 'TRUE' : 'FALSE'}
</Descriptions.Item>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Confidence</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
>
{(event.data.confidence * 100).toFixed(0)}%
</Descriptions.Item>
{event.data.extracted_data && Object.keys(event.data.extracted_data).length > 0 && (
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Extracted</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e' }}
>
<pre style={{
margin: 0,
fontSize: 10,
color: '#ce9178',
whiteSpace: 'pre-wrap'
}}>
{JSON.stringify(event.data.extracted_data, null, 2)}
</pre>
</Descriptions.Item>
)}
</Descriptions>
)}
{event.type === 'upload' && event.data.files && (
<div>
{event.data.files.map((file: any, idx: number) => (
<Descriptions key={idx} size="small" column={1} bordered style={{ marginBottom: 8 }}>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>File</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: '#ce9178', fontSize: 10 }}
>
{file.filename}
</Descriptions.Item>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>File ID</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: '#569cd6', fontSize: 9, fontFamily: 'monospace' }}
>
{file.file_id}
</Descriptions.Item>
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>Size</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
>
{(file.size / 1024).toFixed(1)} KB
</Descriptions.Item>
{file.url && (
<Descriptions.Item
label={<span style={{ color: '#9cdcfe' }}>S3 URL</span>}
labelStyle={{ background: '#252526' }}
contentStyle={{ background: '#1e1e1e', fontSize: 9, wordBreak: 'break-all' }}
>
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
style={{ color: '#4ec9b0', textDecoration: 'underline' }}
>
{file.url}
</a>
<div style={{ marginTop: 4 }}>
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
style={{
fontSize: 10,
color: '#569cd6',
background: '#1e1e1e',
padding: '2px 6px',
borderRadius: 3,
border: '1px solid #333'
}}
>
🔗 Открыть в новой вкладке
</a>
</div>
</Descriptions.Item>
)}
</Descriptions>
))}
</div>
)}
</div>
)}
</div>
)
}))}
/>
{events.length > 0 && (
<div style={{ marginTop: 16, padding: 8, background: '#2d2d30', borderRadius: 4, textAlign: 'center' }}>
<span style={{ fontSize: 11, color: '#888' }}>
Total events: {events.length}
</span>
</div>
)}
</Card>
</div>
);
}