# -*- coding: utf-8 -*- """ 글벗 Light v2.0 Flask 라우팅 + 공통 기능 """ import os import io import tempfile from datetime import datetime from flask import Flask, render_template, request, jsonify, Response, session, send_file from handlers.template import TemplateProcessor # 문서 유형별 프로세서 from handlers.briefing import BriefingProcessor from handlers.report import ReportProcessor app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'geulbeot-light-secret-key-v2') # processors 딕셔너리에 추가 processors = { 'briefing': BriefingProcessor(), 'report': ReportProcessor(), 'template': TemplateProcessor() # 추가 } # ============== 메인 페이지 ============== @app.route('/') def index(): """메인 페이지""" return render_template('index.html') # ============== 생성 API ============== @app.route('/generate', methods=['POST']) def generate(): """기획서 생성 API""" try: content = "" if 'file' in request.files and request.files['file'].filename: file = request.files['file'] content = file.read().decode('utf-8') elif 'content' in request.form: content = request.form.get('content', '') options = { 'page_option': request.form.get('page_option', '1'), 'department': request.form.get('department', '총괄기획실'), 'instruction': request.form.get('instruction', '') } result = processors['briefing'].generate(content, options) if 'error' in result: return jsonify(result), 400 if 'trace' not in result else 500 return jsonify(result) except Exception as e: import traceback return jsonify({'error': str(e), 'trace': traceback.format_exc()}), 500 @app.route('/generate-report', methods=['POST']) def generate_report(): """보고서 생성 API""" try: data = request.get_json() or {} content = data.get('content', '') options = { 'folder_path': data.get('folder_path', ''), 'cover': data.get('cover', False), 'toc': data.get('toc', False), 'divider': data.get('divider', False), 'instruction': data.get('instruction', ''), 'template_id': data.get('template_id') } result = processors['report'].generate(content, options) if 'error' in result: return jsonify(result), 500 return jsonify(result) except Exception as e: import traceback return jsonify({'error': str(e), 'trace': traceback.format_exc()}), 500 # ============== 수정 API ============== @app.route('/refine', methods=['POST']) def refine(): """피드백 반영 API""" try: feedback = request.json.get('feedback', '') current_html = request.json.get('current_html', '') or session.get('current_html', '') original_html = session.get('original_html', '') doc_type = request.json.get('doc_type', 'briefing') processor = processors.get(doc_type, processors['briefing']) result = processor.refine(feedback, current_html, original_html) if 'error' in result: return jsonify(result), 400 return jsonify(result) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/refine-selection', methods=['POST']) def refine_selection(): """선택 부분 수정 API""" try: data = request.json current_html = data.get('current_html', '') selected_text = data.get('selected_text', '') user_request = data.get('request', '') doc_type = data.get('doc_type', 'briefing') processor = processors.get(doc_type, processors['briefing']) result = processor.refine_selection(current_html, selected_text, user_request) if 'error' in result: return jsonify(result), 400 return jsonify(result) except Exception as e: return jsonify({'error': str(e)}), 500 # ============== 다운로드 API ============== @app.route('/download/html', methods=['POST']) def download_html(): """HTML 파일 다운로드""" html_content = request.form.get('html', '') if not html_content: return "No content", 400 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f'report_{timestamp}.html' return Response( html_content, mimetype='text/html', headers={'Content-Disposition': f'attachment; filename={filename}'} ) @app.route('/download/pdf', methods=['POST']) def download_pdf(): """PDF 파일 다운로드""" try: from weasyprint import HTML html_content = request.form.get('html', '') if not html_content: return "No content", 400 pdf_buffer = io.BytesIO() HTML(string=html_content).write_pdf(pdf_buffer) pdf_buffer.seek(0) timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f'report_{timestamp}.pdf' return Response( pdf_buffer.getvalue(), mimetype='application/pdf', headers={'Content-Disposition': f'attachment; filename={filename}'} ) except ImportError: return jsonify({'error': 'PDF 변환 미지원'}), 501 except Exception as e: return jsonify({'error': f'PDF 변환 오류: {str(e)}'}), 500 # ============== 기타 API ============== @app.route('/assets/') def serve_assets(filename): """로컬 assets 폴더 서빙""" assets_dir = r"D:\for python\geulbeot-light\geulbeot-light\output\assets" return send_file(os.path.join(assets_dir, filename)) @app.route('/hwp-script') def hwp_script(): """HWP 변환 스크립트 안내""" return render_template('hwp_guide.html') @app.route('/health') def health(): """헬스 체크""" return jsonify({'status': 'healthy', 'version': '2.0.0'}) @app.route('/export-hwp', methods=['POST']) def export_hwp(): """HWP 변환 (스타일 그루핑 지원)""" try: data = request.get_json() html_content = data.get('html', '') doc_type = data.get('doc_type', 'briefing') use_style_grouping = data.get('style_grouping', False) # 새 옵션 if not html_content: return jsonify({'error': 'HTML 내용이 없습니다'}), 400 temp_dir = tempfile.gettempdir() html_path = os.path.join(temp_dir, 'geulbeot_temp.html') hwp_path = os.path.join(temp_dir, 'geulbeot_output.hwp') with open(html_path, 'w', encoding='utf-8') as f: f.write(html_content) # 변환기 선택 if doc_type == 'briefing': from converters.html_to_hwp_briefing import HtmlToHwpConverter else: from converters.html_to_hwp import HtmlToHwpConverter converter = HtmlToHwpConverter(visible=False) # 스타일 그루핑 사용 여부 if use_style_grouping: final_path = converter.convert_with_styles(html_path, hwp_path) # HWPX 파일 전송 return send_file( final_path, as_attachment=True, download_name=f'report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.hwpx', mimetype='application/vnd.hancom.hwpx' ) else: converter.convert(html_path, hwp_path) return send_file( hwp_path, as_attachment=True, download_name=f'report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.hwp', mimetype='application/x-hwp' ) except ImportError as e: return jsonify({'error': f'pyhwpx 필요: {str(e)}'}), 500 except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/analyze-styles', methods=['POST']) def analyze_styles(): """HTML 스타일 분석 미리보기""" try: data = request.get_json() html_content = data.get('html', '') if not html_content: return jsonify({'error': 'HTML 내용이 없습니다'}), 400 from converters.style_analyzer import StyleAnalyzer from converters.hwp_style_mapping import ROLE_TO_STYLE_NAME analyzer = StyleAnalyzer() elements = analyzer.analyze(html_content) # 요약 정보 summary = analyzer.get_role_summary() # 상세 정보 (처음 50개만) details = [] for elem in elements[:50]: details.append({ 'role': elem.role, 'hwp_style': ROLE_TO_STYLE_NAME.get(elem.role, '바탕글'), 'text': elem.text[:50] + ('...' if len(elem.text) > 50 else ''), 'section': elem.section }) return jsonify({ 'total_elements': len(elements), 'summary': summary, 'details': details }) except Exception as e: import traceback return jsonify({'error': str(e), 'trace': traceback.format_exc()}), 500 @app.route('/templates', methods=['GET']) def get_templates(): """저장된 템플릿 목록 조회""" try: result = processors['template'].get_list() return jsonify(result) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/analyze-template', methods=['POST']) def analyze_template(): """템플릿 분석 및 저장""" try: if 'file' not in request.files: return jsonify({'error': '파일이 없습니다'}), 400 file = request.files['file'] name = request.form.get('name', '').strip() if not name: return jsonify({'error': '템플릿 이름을 입력해주세요'}), 400 if not file.filename: return jsonify({'error': '파일을 선택해주세요'}), 400 result = processors['template'].analyze(file, name) if 'error' in result: return jsonify(result), 400 return jsonify(result) except Exception as e: import traceback return jsonify({'error': str(e), 'trace': traceback.format_exc()}), 500 @app.route('/delete-template/', methods=['DELETE']) def delete_template(template_id): """템플릿 삭제""" try: result = processors['template'].delete(template_id) if 'error' in result: return jsonify(result), 400 return jsonify(result) except Exception as e: return jsonify({'error': str(e)}), 500 if __name__ == '__main__': port = int(os.environ.get('PORT', 5000)) debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true' app.run(host='0.0.0.0', port=port, debug=debug)