📦 Initialize Geulbeot structure and merge Prompts & test projects
This commit is contained in:
355
03. Code/geulbeot_7th/app.py
Normal file
355
03. Code/geulbeot_7th/app.py
Normal file
@@ -0,0 +1,355 @@
|
||||
# -*- 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/<path:filename>')
|
||||
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/<template_id>', 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)
|
||||
Reference in New Issue
Block a user