feat: Split-screen с Debug панелью в реальном времени!

Новый UI:
 Split-screen layout:
   - Слева (60%): форма заявки
   - Справа (40%): Debug Console в реальном времени

Компонент DebugPanel.tsx:
 Темная тема (VS Code style)
 Timeline с событиями
 Real-time обновления
 Показывает:
   - Form Data (JSON в реальном времени)
   - Events Log с иконками и цветами
   - Детали каждого события

События которые отображаются:
1. policy_check:
   -  Полис найден в MySQL БД
   - ⚠️ Полис не найден
   - Показывает: voucher, found status

2. upload:
   - 📤 Загружаю X файлов в S3
   -  Загружено в S3: X/Y
   - Показывает: file_id, size, S3 URL

3. ocr:
   - 🔍 Запущен OCR
   - 📄 OCR завершен: XXX символов
   - Показывает: текст preview

4. ai_analysis:
   - 🤖 AI: policy/garbage, confidence: 95%
   - 🗑️ ШЛЯПА DETECTED! (пользователю не говорим)
   - Показывает: document_type, is_valid, confidence, extracted_data

5. sms:
   - 📱 Отправляю SMS
   -  SMS отправлен (DEBUG mode)
   - 🔐 Проверяю код
   -  Телефон подтвержден
   - Показывает: phone, debug_code

UX:
- Sticky panel (прилипает при скролле)
- Monospace шрифт для данных
- Цветовая кодировка статусов
- JSON форматирование

Layout:
- Row + Col от Ant Design
- Responsive: mobile = 1 column, desktop = split

Теперь видно ВСЁ что происходит в реальном времени! 🔍
This commit is contained in:
AI Assistant
2025-10-24 22:13:52 +03:00
parent 20bad53008
commit 720d4ebdd9
4 changed files with 375 additions and 35 deletions

View File

@@ -7,6 +7,7 @@ interface Props {
formData: any;
updateFormData: (data: any) => void;
onNext: () => void;
addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
}
// Расширенная функция автозамены кириллицы на латиницу
@@ -49,7 +50,7 @@ const formatVoucher = (value: string): string => {
}
};
export default function Step1Policy({ formData, updateFormData, onNext }: Props) {
export default function Step1Policy({ formData, updateFormData, onNext, addDebugEvent }: Props) {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [policyNotFound, setPolicyNotFound] = useState(false);
@@ -77,6 +78,8 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
setLoading(true);
setPolicyNotFound(false);
addDebugEvent?.('policy_check', 'pending', `Проверяю полис: ${values.voucher}`, { voucher: values.voucher });
// Проверка полиса через API
const response = await fetch('http://147.45.146.17:8100/api/v1/policy/check', {
method: 'POST',
@@ -92,15 +95,24 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
if (response.ok) {
if (result.found) {
// Полис найден - переходим дальше
addDebugEvent?.('policy_check', 'success', `✅ Полис найден в MySQL БД (33,963 полисов)`, {
found: true,
voucher: values.voucher
});
message.success('Полис найден в базе данных');
updateFormData(values);
onNext();
} else {
// Полис НЕ найден - показываем загрузку скана
addDebugEvent?.('policy_check', 'warning', `⚠️ Полис не найден → требуется загрузка скана`, {
found: false,
voucher: values.voucher
});
message.warning('Полис не найден в базе. Загрузите скан полиса');
setPolicyNotFound(true);
}
} else {
addDebugEvent?.('policy_check', 'error', `❌ Ошибка API: ${result.detail}`, { error: result.detail });
message.error(result.detail || 'Ошибка проверки полиса');
}
} catch (error: any) {
@@ -133,6 +145,10 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
setUploading(true);
const values = await form.validateFields(['voucher']);
addDebugEvent?.('upload', 'pending', `📤 Загружаю ${fileList.length} файл(ов) в S3...`, {
count: fileList.length
});
// Загружаем файлы в S3 с OCR проверкой
const formData = new FormData();
fileList.forEach((file: any) => {
@@ -150,19 +166,59 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
const uploadResult = await uploadResponse.json();
if (uploadResult.success) {
// TODO: OCR проверка что это полис, а не шляпа
// Если шляпа - помечаем себе, пользователю не говорим
addDebugEvent?.('upload', 'success', `✅ Загружено в S3: ${uploadResult.uploaded_count}/${uploadResult.total_count}`, {
uploaded_count: uploadResult.uploaded_count,
files: uploadResult.files
});
// Проверяем OCR результаты
if (uploadResult.files && uploadResult.files.length > 0) {
const firstFile = uploadResult.files[0];
addDebugEvent?.('ocr', 'pending', `🔍 Запущен OCR для: ${firstFile.filename}`, {
file_id: firstFile.file_id,
filename: firstFile.filename
});
// Если есть OCR результат
if (firstFile.ocr_result) {
const ocr = firstFile.ocr_result;
addDebugEvent?.('ocr', 'success', `📄 OCR завершен: ${ocr.ocr_text?.length || 0} символов`, {
text: ocr.ocr_text?.substring(0, 300)
});
if (ocr.ai_analysis) {
const isGarbage = ocr.document_type === 'garbage';
addDebugEvent?.(
'ai_analysis',
isGarbage ? 'warning' : 'success',
isGarbage
? `🗑️ ШЛЯПА DETECTED! (пользователю не говорим)`
: `🤖 AI: ${ocr.document_type}, confidence: ${(ocr.confidence * 100).toFixed(0)}%`,
{
document_type: ocr.document_type,
is_valid: ocr.is_valid,
confidence: ocr.confidence,
extracted_data: ocr.extracted_data
}
);
}
}
}
updateFormData({
...values,
policyScanUploaded: true,
policyScanFiles: uploadResult.files,
policyValidationWarning: '' // TODO: OCR validation
policyValidationWarning: '' // Silent validation
});
message.success(`Загружено файлов: ${uploadResult.uploaded_count}`);
onNext();
} else {
addDebugEvent?.('upload', 'error', `❌ Ошибка загрузки файлов`, { error: 'Upload failed' });
message.error('Ошибка загрузки файлов');
}
} catch (error) {