SOY NV AI
commited on
Commit
·
0276388
1
Parent(s):
4a8654b
파일 업로드 타임아웃 증가 및 에러 처리 개선
Browse files- 파일 업로드 타임아웃을 15분(900초)으로 증가
- progressContainer 스코프 문제 수정
- 에러 메시지 화면 표시 개선
- app/routes.py +17 -4
- templates/admin_webnovels.html +35 -11
app/routes.py
CHANGED
|
@@ -407,7 +407,7 @@ character_relationships는 이 청크에 등장하는 인물들 간의 현재
|
|
| 407 |
'num_predict': 500
|
| 408 |
}
|
| 409 |
},
|
| 410 |
-
timeout=
|
| 411 |
)
|
| 412 |
if ollama_response.status_code == 200:
|
| 413 |
response_data = ollama_response.json()
|
|
@@ -582,11 +582,16 @@ def analyze_episode(episode_content, episode_title, full_content=None, parent_ch
|
|
| 582 |
'num_predict': 2000
|
| 583 |
}
|
| 584 |
},
|
| 585 |
-
timeout=
|
| 586 |
)
|
| 587 |
if ollama_response.status_code == 200:
|
| 588 |
response_data = ollama_response.json()
|
| 589 |
return response_data.get('response', '').strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 590 |
except Exception as e:
|
| 591 |
print(f"[회차 분석] Ollama 오류: {str(e)}")
|
| 592 |
|
|
@@ -901,10 +906,18 @@ def create_parent_chunk_with_ai(file_id, content, model_name):
|
|
| 901 |
response_data = ollama_response.json()
|
| 902 |
analysis_result = response_data.get('message', {}).get('content', '')
|
| 903 |
print(f"[Parent Chunk 생성] Ollama API 응답 수신 성공: {len(analysis_result)}자")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 904 |
except requests.exceptions.RequestException as e:
|
| 905 |
-
print(f"[Parent Chunk 생성] ❌ Ollama API
|
| 906 |
print(f"[Parent Chunk 생성] 디버그: Ollama URL: {OLLAMA_BASE_URL}")
|
| 907 |
-
|
| 908 |
|
| 909 |
if not analysis_result:
|
| 910 |
print(f"[Parent Chunk 생성] ⚠️ 경고: 분석 결과가 비어있습니다.")
|
|
|
|
| 407 |
'num_predict': 500
|
| 408 |
}
|
| 409 |
},
|
| 410 |
+
timeout=120 # 2분 타임아웃
|
| 411 |
)
|
| 412 |
if ollama_response.status_code == 200:
|
| 413 |
response_data = ollama_response.json()
|
|
|
|
| 582 |
'num_predict': 2000
|
| 583 |
}
|
| 584 |
},
|
| 585 |
+
timeout=300 # 5분 타임아웃 (회차 분석은 시간이 오래 걸릴 수 있음)
|
| 586 |
)
|
| 587 |
if ollama_response.status_code == 200:
|
| 588 |
response_data = ollama_response.json()
|
| 589 |
return response_data.get('response', '').strip()
|
| 590 |
+
except requests.exceptions.Timeout:
|
| 591 |
+
print(f"[회차 분석] Ollama 타임아웃: 요청 시간이 초과되었습니다. (5분)")
|
| 592 |
+
print(f"[회차 분석] 회차 내용이 너무 길거나 모델 응답이 느릴 수 있습니다.")
|
| 593 |
+
except requests.exceptions.ConnectionError:
|
| 594 |
+
print(f"[회차 분석] Ollama 연결 오류: Ollama 서버에 연결할 수 없습니다.")
|
| 595 |
except Exception as e:
|
| 596 |
print(f"[회차 분석] Ollama 오류: {str(e)}")
|
| 597 |
|
|
|
|
| 906 |
response_data = ollama_response.json()
|
| 907 |
analysis_result = response_data.get('message', {}).get('content', '')
|
| 908 |
print(f"[Parent Chunk 생성] Ollama API 응답 수신 성공: {len(analysis_result)}자")
|
| 909 |
+
except requests.exceptions.Timeout:
|
| 910 |
+
print(f"[Parent Chunk 생성] ❌ Ollama 타임아웃: 요청 시간이 초과되었습니다. (5분)")
|
| 911 |
+
print(f"[Parent Chunk 생성] 파일이 너무 크거나 모델 응답이 느릴 수 있습니다.")
|
| 912 |
+
return None
|
| 913 |
+
except requests.exceptions.ConnectionError:
|
| 914 |
+
print(f"[Parent Chunk 생성] ❌ Ollama 연결 오류: Ollama 서버에 연결할 수 없습니다.")
|
| 915 |
+
print(f"[Parent Chunk 생성] 디버그: Ollama URL: {OLLAMA_BASE_URL}")
|
| 916 |
+
return None
|
| 917 |
except requests.exceptions.RequestException as e:
|
| 918 |
+
print(f"[Parent Chunk 생성] ❌ Ollama API 오류: {str(e)}")
|
| 919 |
print(f"[Parent Chunk 생성] 디버그: Ollama URL: {OLLAMA_BASE_URL}")
|
| 920 |
+
return None
|
| 921 |
|
| 922 |
if not analysis_result:
|
| 923 |
print(f"[Parent Chunk 생성] ⚠️ 경고: 분석 결과가 비어있습니다.")
|
templates/admin_webnovels.html
CHANGED
|
@@ -764,6 +764,10 @@
|
|
| 764 |
return;
|
| 765 |
}
|
| 766 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 767 |
try {
|
| 768 |
// 업로드 중 UI 비활성화
|
| 769 |
console.log('[handleFileUpload] UI 비활성화 시작');
|
|
@@ -776,8 +780,8 @@
|
|
| 776 |
fileInput.disabled = true;
|
| 777 |
|
| 778 |
// 진행 상태 초기화
|
| 779 |
-
|
| 780 |
-
|
| 781 |
|
| 782 |
if (!progressContainer || !progressItems) {
|
| 783 |
console.error('[handleFileUpload] 진행 상태 컨테이너를 찾을 수 없습니다');
|
|
@@ -886,12 +890,12 @@
|
|
| 886 |
console.log(`[단계 1] fetch 호출 시작: /api/upload`);
|
| 887 |
console.log(`[단계 1] FormData 항목:`, Array.from(formData.entries()).map(([k, v]) => [k, v instanceof File ? v.name : v]));
|
| 888 |
|
| 889 |
-
// 타임아웃이 있는 fetch 래퍼
|
| 890 |
-
const fetchWithTimeout = (url, options, timeout =
|
| 891 |
return Promise.race([
|
| 892 |
fetch(url, options),
|
| 893 |
new Promise((_, reject) =>
|
| 894 |
-
setTimeout(() => reject(new Error(`요청 타임아웃: ${timeout/1000}초 내에 응답이
|
| 895 |
)
|
| 896 |
]);
|
| 897 |
};
|
|
@@ -900,7 +904,7 @@
|
|
| 900 |
method: 'POST',
|
| 901 |
body: formData,
|
| 902 |
credentials: 'include' // 쿠키 포함 (세션 인증)
|
| 903 |
-
},
|
| 904 |
|
| 905 |
console.log(`[단계 1] fetch 응답 수신: ${response.status} ${response.statusText}`);
|
| 906 |
|
|
@@ -994,13 +998,29 @@
|
|
| 994 |
console.error(`[업로드 예외] 파일: ${file.name}`, error);
|
| 995 |
console.error(`[업로드 예외 스택]`, error.stack);
|
| 996 |
|
| 997 |
-
//
|
| 998 |
-
if (error.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 999 |
console.error(`[네트워크 오류] 서버와의 연결이 끊어졌습니다.`);
|
| 1000 |
if (fileUploadStatus) {
|
| 1001 |
fileUploadStatus.textContent = `[${i + 1}/${files.length}] 네트워크 오류: 서버 연결 실패`;
|
| 1002 |
fileUploadStatus.className = 'file-upload-status error';
|
| 1003 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1004 |
}
|
| 1005 |
console.error(`[스택 트레이스]`, error.stack);
|
| 1006 |
}
|
|
@@ -1031,7 +1051,7 @@
|
|
| 1031 |
} else {
|
| 1032 |
fileUploadStatus.textContent = '모든 파일 업로드 실패';
|
| 1033 |
fileUploadStatus.className = 'file-upload-status error';
|
| 1034 |
-
const errorDetails = errors.length > 0 ? '\n' + errors.slice(0,
|
| 1035 |
showAlert(`파일 업로드에 실패했습니다.${errorDetails}`, 'error');
|
| 1036 |
}
|
| 1037 |
}
|
|
@@ -1049,8 +1069,12 @@
|
|
| 1049 |
|
| 1050 |
// 3초 후 진행 상태 숨기기
|
| 1051 |
setTimeout(() => {
|
| 1052 |
-
progressContainer
|
| 1053 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
}, 3000);
|
| 1055 |
}
|
| 1056 |
|
|
|
|
| 764 |
return;
|
| 765 |
}
|
| 766 |
|
| 767 |
+
// 진행 상태 컨테이너를 함수 상단에서 선언 (스코프 문제 해결)
|
| 768 |
+
let progressContainer = null;
|
| 769 |
+
let progressItems = null;
|
| 770 |
+
|
| 771 |
try {
|
| 772 |
// 업로드 중 UI 비활성화
|
| 773 |
console.log('[handleFileUpload] UI 비활성화 시작');
|
|
|
|
| 780 |
fileInput.disabled = true;
|
| 781 |
|
| 782 |
// 진행 상태 초기화
|
| 783 |
+
progressContainer = document.getElementById('fileUploadProgress');
|
| 784 |
+
progressItems = document.getElementById('progressItems');
|
| 785 |
|
| 786 |
if (!progressContainer || !progressItems) {
|
| 787 |
console.error('[handleFileUpload] 진행 상태 컨테이너를 찾을 수 없습니다');
|
|
|
|
| 890 |
console.log(`[단계 1] fetch 호출 시작: /api/upload`);
|
| 891 |
console.log(`[단계 1] FormData 항목:`, Array.from(formData.entries()).map(([k, v]) => [k, v instanceof File ? v.name : v]));
|
| 892 |
|
| 893 |
+
// 타임아웃이 있는 fetch 래퍼 (15분 타임아웃 - 큰 파일 처리 시간 고려)
|
| 894 |
+
const fetchWithTimeout = (url, options, timeout = 900000) => { // 15분 타임아웃
|
| 895 |
return Promise.race([
|
| 896 |
fetch(url, options),
|
| 897 |
new Promise((_, reject) =>
|
| 898 |
+
setTimeout(() => reject(new Error(`요청 타임아웃: ${timeout/1000}초(${Math.floor(timeout/60000)}분) 내에 응답이 없습니다. 파일이 크거나 처리 시간이 오래 걸릴 수 있습니다.`)), timeout)
|
| 899 |
)
|
| 900 |
]);
|
| 901 |
};
|
|
|
|
| 904 |
method: 'POST',
|
| 905 |
body: formData,
|
| 906 |
credentials: 'include' // 쿠키 포함 (세션 인증)
|
| 907 |
+
}, 900000); // 15분 타임아웃
|
| 908 |
|
| 909 |
console.log(`[단계 1] fetch 응답 수신: ${response.status} ${response.statusText}`);
|
| 910 |
|
|
|
|
| 998 |
console.error(`[업로드 예외] 파일: ${file.name}`, error);
|
| 999 |
console.error(`[업로드 예외 스택]`, error.stack);
|
| 1000 |
|
| 1001 |
+
// 타임아웃 오류인 경우 사용자에게 명확한 메시지 표시
|
| 1002 |
+
if (error.message && error.message.includes('타임아웃')) {
|
| 1003 |
+
const timeoutMsg = `파일 업로드 타임아웃: ${file.name}\n파일이 크거나 처리 시간이 오래 걸려 타임아웃이 발생했습니다.`;
|
| 1004 |
+
console.error(`[타임아웃 오류] ${timeoutMsg}`);
|
| 1005 |
+
if (fileUploadStatus) {
|
| 1006 |
+
fileUploadStatus.textContent = `[${i + 1}/${files.length}] 타임아웃: ${file.name}`;
|
| 1007 |
+
fileUploadStatus.className = 'file-upload-status error';
|
| 1008 |
+
}
|
| 1009 |
+
// 사용자에게 알림 표시
|
| 1010 |
+
showAlert(timeoutMsg, 'error');
|
| 1011 |
+
}
|
| 1012 |
+
// 네트워크 오류인 경우 사용자에게 명확한 메시지 표시
|
| 1013 |
+
else if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
| 1014 |
console.error(`[네트워크 오류] 서버와의 연결이 끊어졌습니다.`);
|
| 1015 |
if (fileUploadStatus) {
|
| 1016 |
fileUploadStatus.textContent = `[${i + 1}/${files.length}] 네트워크 오류: 서버 연결 실패`;
|
| 1017 |
fileUploadStatus.className = 'file-upload-status error';
|
| 1018 |
}
|
| 1019 |
+
showAlert(`네트워크 오류: 서버와의 연결이 끊어졌습니다. (${file.name})`, 'error');
|
| 1020 |
+
}
|
| 1021 |
+
// 기타 오류도 사용자에게 표시
|
| 1022 |
+
else {
|
| 1023 |
+
showAlert(`파일 업로드 오류: ${file.name}\n${errorMsg}`, 'error');
|
| 1024 |
}
|
| 1025 |
console.error(`[스택 트레이스]`, error.stack);
|
| 1026 |
}
|
|
|
|
| 1051 |
} else {
|
| 1052 |
fileUploadStatus.textContent = '모든 파일 업로드 실패';
|
| 1053 |
fileUploadStatus.className = 'file-upload-status error';
|
| 1054 |
+
const errorDetails = errors.length > 0 ? '\n\n오류 상세:\n' + errors.slice(0, 5).map((e, idx) => `${idx + 1}. ${e}`).join('\n') + (errors.length > 5 ? `\n... 외 ${errors.length - 5}개 오류` : '') : '';
|
| 1055 |
showAlert(`파일 업로드에 실패했습니다.${errorDetails}`, 'error');
|
| 1056 |
}
|
| 1057 |
}
|
|
|
|
| 1069 |
|
| 1070 |
// 3초 후 진행 상태 숨기기
|
| 1071 |
setTimeout(() => {
|
| 1072 |
+
if (progressContainer) {
|
| 1073 |
+
progressContainer.classList.remove('active');
|
| 1074 |
+
}
|
| 1075 |
+
if (fileUploadStatus) {
|
| 1076 |
+
fileUploadStatus.textContent = '';
|
| 1077 |
+
}
|
| 1078 |
}, 3000);
|
| 1079 |
}
|
| 1080 |
|