Compare commits
1 Commits
fix/import
...
e28d0f46d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e28d0f46d0 |
@@ -79,12 +79,6 @@ router.post(
|
||||
return;
|
||||
}
|
||||
|
||||
sseWrite(res, {
|
||||
stage: 'import',
|
||||
progress: 88,
|
||||
message: 'Импорт в базу данных...',
|
||||
});
|
||||
|
||||
const result = await importStatement(converted);
|
||||
if (isValidationError(result)) {
|
||||
sseWrite(res, {
|
||||
|
||||
@@ -137,7 +137,7 @@ export async function convertPdfToStatement(
|
||||
return {
|
||||
status: 502,
|
||||
error: 'BAD_GATEWAY',
|
||||
message: 'Временная ошибка конвертации',
|
||||
message: extractLlmErrorMessage(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export type ProgressStage = 'pdf' | 'llm' | 'import';
|
||||
export type OnProgress = (stage: ProgressStage, progress: number, message: string) => void;
|
||||
|
||||
const LLM_PROGRESS_MIN = 10;
|
||||
const LLM_PROGRESS_MAX = 85;
|
||||
const LLM_PROGRESS_MAX = 98;
|
||||
const LLM_PROGRESS_RANGE = LLM_PROGRESS_MAX - LLM_PROGRESS_MIN;
|
||||
const THROTTLE_MS = 300;
|
||||
|
||||
@@ -248,11 +248,25 @@ export async function convertPdfToStatementStreaming(
|
||||
return {
|
||||
status: 502,
|
||||
error: 'BAD_GATEWAY',
|
||||
message: 'Временная ошибка конвертации',
|
||||
message: extractLlmErrorMessage(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function extractLlmErrorMessage(err: unknown): string {
|
||||
const raw = String(
|
||||
(err as Record<string, unknown>)?.message ??
|
||||
(err as Record<string, Record<string, unknown>>)?.error?.message ?? '',
|
||||
);
|
||||
if (/context.length|n_ctx|too.many.tokens|maximum.context/i.test(raw)) {
|
||||
return 'PDF-файл слишком большой для обработки. Попробуйте файл с меньшим количеством операций или используйте модель с большим контекстным окном.';
|
||||
}
|
||||
if (/timeout|timed?\s*out|ETIMEDOUT|ECONNREFUSED/i.test(raw)) {
|
||||
return 'LLM-сервер не отвечает. Проверьте, что сервер запущен и доступен.';
|
||||
}
|
||||
return 'Временная ошибка конвертации';
|
||||
}
|
||||
|
||||
function parseConversionResult(content: string): StatementFile | PdfConversionError {
|
||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||
const jsonStr = jsonMatch ? jsonMatch[0] : content;
|
||||
|
||||
@@ -66,13 +66,11 @@ export function ImportModal({ onClose, onDone }: Props) {
|
||||
onClose();
|
||||
}
|
||||
} else {
|
||||
if (result || importState.error) clearImport();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDone = () => {
|
||||
clearImport();
|
||||
onDone();
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAuth } from '../context/AuthContext';
|
||||
import { useImport } from '../context/ImportContext';
|
||||
|
||||
function ImportProgressBar() {
|
||||
const { importState, clearImport } = useImport();
|
||||
const { importState, clearImport, openModal } = useImport();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const hideTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
|
||||
@@ -31,9 +31,9 @@ function ImportProgressBar() {
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
|
||||
openModal();
|
||||
setVisible(false);
|
||||
clearImport();
|
||||
}, [clearImport]);
|
||||
}, [openModal]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ export interface ImportProgress {
|
||||
|
||||
interface ImportContextValue {
|
||||
importState: ImportProgress;
|
||||
showModal: boolean;
|
||||
openModal: () => void;
|
||||
closeModal: () => void;
|
||||
startImport: (file: File) => void;
|
||||
clearImport: () => void;
|
||||
}
|
||||
@@ -35,8 +38,12 @@ const ImportContext = createContext<ImportContextValue | null>(null);
|
||||
|
||||
export function ImportProvider({ children }: { children: ReactNode }) {
|
||||
const [importState, setImportState] = useState<ImportProgress>(INITIAL);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const runningRef = useRef(false);
|
||||
|
||||
const openModal = useCallback(() => setShowModal(true), []);
|
||||
const closeModal = useCallback(() => setShowModal(false), []);
|
||||
|
||||
const startImport = useCallback((file: File) => {
|
||||
if (runningRef.current) return;
|
||||
runningRef.current = true;
|
||||
@@ -92,7 +99,9 @@ export function ImportProvider({ children }: { children: ReactNode }) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ImportContext.Provider value={{ importState, startImport, clearImport }}>
|
||||
<ImportContext.Provider value={{
|
||||
importState, showModal, openModal, closeModal, startImport, clearImport,
|
||||
}}>
|
||||
{children}
|
||||
</ImportContext.Provider>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TransactionTable } from '../components/TransactionTable';
|
||||
import { Pagination } from '../components/Pagination';
|
||||
import { EditTransactionModal } from '../components/EditTransactionModal';
|
||||
import { ImportModal } from '../components/ImportModal';
|
||||
import { useImport } from '../context/ImportContext';
|
||||
import { toISODate } from '../utils/format';
|
||||
|
||||
const PARAM_KEYS = [
|
||||
@@ -125,7 +126,7 @@ export function HistoryPage() {
|
||||
const [accounts, setAccounts] = useState<Account[]>([]);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [editingTx, setEditingTx] = useState<Transaction | null>(null);
|
||||
const [showImport, setShowImport] = useState(false);
|
||||
const { showModal: showImport, openModal: openImport, closeModal: closeImport, clearImport } = useImport();
|
||||
|
||||
useEffect(() => {
|
||||
getAccounts().then(setAccounts).catch(() => {});
|
||||
@@ -197,7 +198,8 @@ export function HistoryPage() {
|
||||
};
|
||||
|
||||
const handleImportDone = () => {
|
||||
setShowImport(false);
|
||||
closeImport();
|
||||
clearImport();
|
||||
fetchData();
|
||||
getAccounts().then(setAccounts).catch(() => {});
|
||||
};
|
||||
@@ -208,7 +210,7 @@ export function HistoryPage() {
|
||||
<h1>История операций</h1>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setShowImport(true)}
|
||||
onClick={openImport}
|
||||
>
|
||||
Импорт выписки
|
||||
</button>
|
||||
@@ -262,7 +264,7 @@ export function HistoryPage() {
|
||||
|
||||
{showImport && (
|
||||
<ImportModal
|
||||
onClose={() => setShowImport(false)}
|
||||
onClose={closeImport}
|
||||
onDone={handleImportDone}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user