Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Update app.py
Browse files
app.py
CHANGED
@@ -9,6 +9,13 @@ import threading
|
|
9 |
import concurrent.futures
|
10 |
from openai import OpenAI
|
11 |
import fitz # PyMuPDF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
# λ‘κΉ
μ€μ
|
14 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
@@ -948,6 +955,191 @@ async def upload_pdf(file: UploadFile = File(...)):
|
|
948 |
status_code=500
|
949 |
)
|
950 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
951 |
# κ΄λ¦¬μ μΈμ¦ μλν¬μΈνΈ
|
952 |
@app.post("/api/admin-login")
|
953 |
async def admin_login(password: str = Form(...)):
|
@@ -963,9 +1155,17 @@ async def delete_pdf(path: str):
|
|
963 |
if not pdf_file.exists():
|
964 |
return {"success": False, "message": "νμΌμ μ°Ύμ μ μμ΅λλ€"}
|
965 |
|
966 |
-
# PDF
|
|
|
|
|
|
|
967 |
pdf_file.unlink()
|
968 |
|
|
|
|
|
|
|
|
|
|
|
969 |
# κ΄λ ¨ μΊμ νμΌ μμ
|
970 |
pdf_name = pdf_file.stem
|
971 |
cache_path = get_cache_path(pdf_name)
|
@@ -979,7 +1179,7 @@ async def delete_pdf(path: str):
|
|
979 |
# λ©νλ°μ΄ν°μμ ν΄λΉ νμΌ ID μ κ±°
|
980 |
to_remove = []
|
981 |
for pid, fpath in pdf_metadata.items():
|
982 |
-
if os.path.basename(fpath) ==
|
983 |
to_remove.append(pid)
|
984 |
|
985 |
for pid in to_remove:
|
@@ -1351,7 +1551,9 @@ HTML = """
|
|
1351 |
font-weight: 500;
|
1352 |
display: flex;
|
1353 |
align-items: center;
|
1354 |
-
|
|
|
|
|
1355 |
transition: var(--transition);
|
1356 |
position: relative;
|
1357 |
overflow: hidden;
|
@@ -2111,7 +2313,11 @@ HTML = """
|
|
2111 |
<button class="upload" id="pdfUploadBtn">
|
2112 |
<i class="fas fa-file-pdf"></i> PDF Upload
|
2113 |
</button>
|
|
|
|
|
|
|
2114 |
<input id="pdfInput" type="file" accept="application/pdf" style="display:none">
|
|
|
2115 |
</div>
|
2116 |
|
2117 |
<div class="section-title">Projects</div>
|
@@ -2436,9 +2642,6 @@ async function submitQuestion(question) {
|
|
2436 |
}
|
2437 |
}
|
2438 |
|
2439 |
-
|
2440 |
-
|
2441 |
-
|
2442 |
|
2443 |
// DOMμ΄ λ‘λλλ©΄ μ€ν
|
2444 |
document.addEventListener('DOMContentLoaded', function() {
|
@@ -2473,6 +2676,26 @@ async function submitQuestion(question) {
|
|
2473 |
console.error("PDF μ
λ‘λ μμλ₯Ό μ°Ύμ μ μμ");
|
2474 |
}
|
2475 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2476 |
// μλ² PDF λ‘λ λ° μΊμ μν νμΈ
|
2477 |
loadServerPDFs();
|
2478 |
|
@@ -2573,6 +2796,40 @@ async function submitQuestion(question) {
|
|
2573 |
}
|
2574 |
}
|
2575 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2576 |
function addCard(i, thumb, title, isCached = false, pdfId = null) {
|
2577 |
const d = document.createElement('div');
|
2578 |
d.className = 'card fade-in';
|
@@ -3636,8 +3893,7 @@ async function submitQuestion(question) {
|
|
3636 |
}
|
3637 |
</script>
|
3638 |
</body>
|
3639 |
-
</html>
|
3640 |
-
"""
|
3641 |
|
3642 |
if __name__ == "__main__":
|
3643 |
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
|
|
|
9 |
import concurrent.futures
|
10 |
from openai import OpenAI
|
11 |
import fitz # PyMuPDF
|
12 |
+
import tempfile
|
13 |
+
from reportlab.lib.pagesizes import letter
|
14 |
+
from reportlab.pdfgen import canvas
|
15 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
16 |
+
from reportlab.lib.styles import getSampleStyleSheet
|
17 |
+
import io
|
18 |
+
import docx2txt
|
19 |
|
20 |
# λ‘κΉ
μ€μ
|
21 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
|
955 |
status_code=500
|
956 |
)
|
957 |
|
958 |
+
# ν
μ€νΈ νμΌμ PDFλ‘ λ³ννλ ν¨μ
|
959 |
+
async def convert_text_to_pdf(text_content: str, title: str) -> str:
|
960 |
+
try:
|
961 |
+
# μ λͺ©μμ μ ν¨ν νμΌλͺ
μμ±
|
962 |
+
import re
|
963 |
+
safe_title = re.sub(r'[^\w\-_\. ]', '_', title)
|
964 |
+
if not safe_title:
|
965 |
+
safe_title = "aibook"
|
966 |
+
|
967 |
+
# νμμ€ν¬ν μΆκ°λ‘ κ³ μ ν νμΌλͺ
μμ±
|
968 |
+
timestamp = int(time.time())
|
969 |
+
filename = f"{safe_title}_{timestamp}.pdf"
|
970 |
+
|
971 |
+
# μꡬ μ μ₯μμ νμΌ κ²½λ‘
|
972 |
+
file_path = PERMANENT_PDF_DIR / filename
|
973 |
+
|
974 |
+
# μμ PDF νμΌ μμ±
|
975 |
+
pdf_buffer = io.BytesIO()
|
976 |
+
doc = SimpleDocTemplate(pdf_buffer, pagesize=letter)
|
977 |
+
styles = getSampleStyleSheet()
|
978 |
+
|
979 |
+
# λ΄μ©μ λ¬Έλ¨μΌλ‘ λΆν
|
980 |
+
content = []
|
981 |
+
|
982 |
+
# μ λͺ© μΆκ°
|
983 |
+
title_style = styles['Title']
|
984 |
+
content.append(Paragraph(title, title_style))
|
985 |
+
content.append(Spacer(1, 12))
|
986 |
+
|
987 |
+
# λ³Έλ¬Έ ν
μ€νΈ μ€νμΌ
|
988 |
+
normal_style = styles['Normal']
|
989 |
+
|
990 |
+
# ν
μ€νΈλ₯Ό λ¨λ½μΌλ‘ λΆλ¦¬νμ¬ μΆκ°
|
991 |
+
paragraphs = text_content.split('\n\n')
|
992 |
+
for para in paragraphs:
|
993 |
+
if para.strip():
|
994 |
+
p = Paragraph(para.replace('\n', '<br/>'), normal_style)
|
995 |
+
content.append(p)
|
996 |
+
content.append(Spacer(1, 10))
|
997 |
+
|
998 |
+
# PDF μμ±
|
999 |
+
doc.build(content)
|
1000 |
+
|
1001 |
+
# νμΌλ‘ μ μ₯
|
1002 |
+
with open(file_path, 'wb') as f:
|
1003 |
+
f.write(pdf_buffer.getvalue())
|
1004 |
+
|
1005 |
+
# λ©μΈ λλ ν 리μλ 볡μ¬
|
1006 |
+
with open(PDF_DIR / filename, 'wb') as f:
|
1007 |
+
f.write(pdf_buffer.getvalue())
|
1008 |
+
|
1009 |
+
# PDF ID μμ± λ° λ©νλ°μ΄ν° μ μ₯
|
1010 |
+
pdf_id = generate_pdf_id(filename)
|
1011 |
+
pdf_metadata[pdf_id] = str(file_path)
|
1012 |
+
save_pdf_metadata()
|
1013 |
+
|
1014 |
+
# λ°±κ·ΈλΌμ΄λμμ μΊμ± μμ
|
1015 |
+
asyncio.create_task(cache_pdf(str(file_path)))
|
1016 |
+
|
1017 |
+
return {
|
1018 |
+
"path": str(file_path),
|
1019 |
+
"filename": filename,
|
1020 |
+
"id": pdf_id
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
except Exception as e:
|
1024 |
+
logger.error(f"ν
μ€νΈλ₯Ό PDFλ‘ λ³ν μ€ μ€λ₯: {e}")
|
1025 |
+
raise e
|
1026 |
+
|
1027 |
+
# AIλ₯Ό μ¬μ©νμ¬ ν
μ€νΈλ₯Ό λ ꡬ쑰νλ νμμΌλ‘ λ³ν
|
1028 |
+
async def enhance_text_with_ai(text_content: str, title: str) -> str:
|
1029 |
+
try:
|
1030 |
+
# API ν€κ° μκ±°λ μ ν¨νμ§ μμ κ²½μ° μλ³Έ ν
μ€νΈ λ°ν
|
1031 |
+
if not HAS_VALID_API_KEY or not openai_client:
|
1032 |
+
return text_content
|
1033 |
+
|
1034 |
+
# ν
μ€νΈκ° μ§§μ κ²½μ° μλ³Έ λ°ν
|
1035 |
+
if len(text_content) < 100:
|
1036 |
+
return text_content
|
1037 |
+
|
1038 |
+
# 컨ν
μ€νΈ ν¬κΈ°λ₯Ό κ³ λ €νμ¬ ν
μ€νΈκ° λ무 κΈΈλ©΄ μλΆλΆλ§ μ¬μ©
|
1039 |
+
max_context_length = 60000
|
1040 |
+
if len(text_content) > max_context_length:
|
1041 |
+
text_to_process = text_content[:max_context_length] + "...(μ΄ν μλ΅)"
|
1042 |
+
else:
|
1043 |
+
text_to_process = text_content
|
1044 |
+
|
1045 |
+
# OpenAI API νΈμΆνμ¬ ν
μ€νΈ ꡬ쑰ν
|
1046 |
+
try:
|
1047 |
+
system_prompt = """
|
1048 |
+
λΉμ μ ν
μ€νΈλ₯Ό μ ꡬ쑰νλ μ μμ±
νμμΌλ‘ λ³ννλ μ λ¬Έκ°μ
λλ€.
|
1049 |
+
μ 곡λ μλ³Έ ν
μ€νΈλ₯Ό λΆμνκ³ , λ€μκ³Ό κ°μ΄ κ°μ ν΄μ£ΌμΈμ:
|
1050 |
+
|
1051 |
+
1. λ
Όλ¦¬μ μΈ μΉμ
μΌλ‘ λλκ³ μ μ ν μ λͺ©κ³Ό λΆμ λͺ© μΆκ°
|
1052 |
+
2. λ¨λ½μ μμ°μ€λ½κ² ꡬμ±νκ³ κ°λ
μ± κ°μ
|
1053 |
+
3. μ€μν λ΄μ©μ κ°μ‘°νκ±°λ μμ½νμ¬ νμ
|
1054 |
+
4. μλ³Έ λ΄μ©μ μλ―Έμ λ§₯λ½μ μ μ§νλ©΄μ κΉλνκ² μ 리
|
1055 |
+
5. λ§μΆ€λ²κ³Ό λ¬Έλ² μ€λ₯ μμ
|
1056 |
+
|
1057 |
+
μλ³Έ λ΄μ©μ λͺ¨λ μ μ§νλ, μ μ 리λ μ μμ±
μ²λΌ 보μ΄λλ‘ κ΅¬μ‘°νν΄μ£ΌμΈμ.
|
1058 |
+
μμ λ³Έμ μ§μ λ°ννκ³ , μλ³Έ λ΄μ©μ λͺ¨λ ν¬ν¨ν΄μΌ ν©λλ€.
|
1059 |
+
"""
|
1060 |
+
|
1061 |
+
response = openai_client.chat.completions.create(
|
1062 |
+
model="gpt-4.1-mini",
|
1063 |
+
messages=[
|
1064 |
+
{"role": "system", "content": system_prompt},
|
1065 |
+
{"role": "user", "content": f"μ λͺ©: {title}\n\nμλ³Έ ν
μ€νΈ:\n{text_to_process}"}
|
1066 |
+
],
|
1067 |
+
temperature=0.7,
|
1068 |
+
max_tokens=4000,
|
1069 |
+
timeout=60.0
|
1070 |
+
)
|
1071 |
+
|
1072 |
+
enhanced_text = response.choices[0].message.content
|
1073 |
+
return enhanced_text
|
1074 |
+
|
1075 |
+
except Exception as api_error:
|
1076 |
+
logger.error(f"AI ν
μ€νΈ ν₯μ μ€λ₯: {api_error}")
|
1077 |
+
# μ€λ₯ λ°μ μ μλ³Έ ν
μ€νΈ λ°ν
|
1078 |
+
return text_content
|
1079 |
+
|
1080 |
+
except Exception as e:
|
1081 |
+
logger.error(f"AI ν
μ€νΈ ν₯μ μ€λ₯: {e}")
|
1082 |
+
return text_content
|
1083 |
+
|
1084 |
+
# ν
μ€νΈ νμΌμ PDFλ‘ λ³ννλ μλν¬μΈνΈ
|
1085 |
+
@app.post("/api/text-to-pdf")
|
1086 |
+
async def text_to_pdf(file: UploadFile = File(...)):
|
1087 |
+
try:
|
1088 |
+
# μ§μνλ νμΌ νμ νμΈ
|
1089 |
+
filename = file.filename.lower()
|
1090 |
+
if not (filename.endswith('.txt') or filename.endswith('.docx') or filename.endswith('.doc')):
|
1091 |
+
return JSONResponse(
|
1092 |
+
content={"success": False, "message": "μ§μνλ νμΌ νμμ .txt, .docx, .docμ
λλ€."},
|
1093 |
+
status_code=400
|
1094 |
+
)
|
1095 |
+
|
1096 |
+
# νμΌ λ΄μ© μ½κΈ°
|
1097 |
+
content = await file.read()
|
1098 |
+
|
1099 |
+
# νμΌ νμ
μ λ°λΌ ν
μ€νΈ μΆμΆ
|
1100 |
+
if filename.endswith('.txt'):
|
1101 |
+
text_content = content.decode('utf-8', errors='replace')
|
1102 |
+
elif filename.endswith('.docx') or filename.endswith('.doc'):
|
1103 |
+
# μμ νμΌλ‘ μ μ₯
|
1104 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(filename)[1]) as temp_file:
|
1105 |
+
temp_file.write(content)
|
1106 |
+
temp_path = temp_file.name
|
1107 |
+
|
1108 |
+
try:
|
1109 |
+
# docx2txtλ‘ ν
μ€νΈ μΆμΆ
|
1110 |
+
text_content = docx2txt.process(temp_path)
|
1111 |
+
finally:
|
1112 |
+
# μμ νμΌ μμ
|
1113 |
+
os.unlink(temp_path)
|
1114 |
+
|
1115 |
+
# νμΌλͺ
μμ μ λͺ© μΆμΆ (νμ₯μ μ μΈ)
|
1116 |
+
title = os.path.splitext(filename)[0]
|
1117 |
+
|
1118 |
+
# AIλ‘ ν
μ€νΈ λ΄μ© ν₯μ
|
1119 |
+
enhanced_text = await enhance_text_with_ai(text_content, title)
|
1120 |
+
|
1121 |
+
# ν
μ€νΈλ₯Ό PDFλ‘ λ³ν
|
1122 |
+
pdf_info = await convert_text_to_pdf(enhanced_text, title)
|
1123 |
+
|
1124 |
+
return JSONResponse(
|
1125 |
+
content={
|
1126 |
+
"success": True,
|
1127 |
+
"path": pdf_info["path"],
|
1128 |
+
"name": os.path.splitext(pdf_info["filename"])[0],
|
1129 |
+
"id": pdf_info["id"],
|
1130 |
+
"viewUrl": f"/view/{pdf_info['id']}"
|
1131 |
+
},
|
1132 |
+
status_code=200
|
1133 |
+
)
|
1134 |
+
except Exception as e:
|
1135 |
+
import traceback
|
1136 |
+
error_details = traceback.format_exc()
|
1137 |
+
logger.error(f"ν
μ€νΈλ₯Ό PDFλ‘ λ³ν μ€ μ€λ₯: {str(e)}\n{error_details}")
|
1138 |
+
return JSONResponse(
|
1139 |
+
content={"success": False, "message": str(e)},
|
1140 |
+
status_code=500
|
1141 |
+
)
|
1142 |
+
|
1143 |
# κ΄λ¦¬μ μΈμ¦ μλν¬μΈνΈ
|
1144 |
@app.post("/api/admin-login")
|
1145 |
async def admin_login(password: str = Form(...)):
|
|
|
1155 |
if not pdf_file.exists():
|
1156 |
return {"success": False, "message": "νμΌμ μ°Ύμ μ μμ΅λλ€"}
|
1157 |
|
1158 |
+
# PDF νμΌλͺ
κ°μ Έμ€κΈ°
|
1159 |
+
filename = pdf_file.name
|
1160 |
+
|
1161 |
+
# PDF νμΌ μμ (μꡬ μ μ₯μμμ)
|
1162 |
pdf_file.unlink()
|
1163 |
|
1164 |
+
# λ©μΈ λλ ν 리μμλ λμΌν νμΌμ΄ μμΌλ©΄ μμ (λ²κ·Έ μμ )
|
1165 |
+
main_file_path = PDF_DIR / filename
|
1166 |
+
if main_file_path.exists():
|
1167 |
+
main_file_path.unlink()
|
1168 |
+
|
1169 |
# κ΄λ ¨ μΊμ νμΌ μμ
|
1170 |
pdf_name = pdf_file.stem
|
1171 |
cache_path = get_cache_path(pdf_name)
|
|
|
1179 |
# λ©νλ°μ΄ν°μμ ν΄λΉ νμΌ ID μ κ±°
|
1180 |
to_remove = []
|
1181 |
for pid, fpath in pdf_metadata.items():
|
1182 |
+
if os.path.basename(fpath) == filename:
|
1183 |
to_remove.append(pid)
|
1184 |
|
1185 |
for pid in to_remove:
|
|
|
1551 |
font-weight: 500;
|
1552 |
display: flex;
|
1553 |
align-items: center;
|
1554 |
+
|
1555 |
+
|
1556 |
+
box-shadow: var(--shadow-sm);
|
1557 |
transition: var(--transition);
|
1558 |
position: relative;
|
1559 |
overflow: hidden;
|
|
|
2313 |
<button class="upload" id="pdfUploadBtn">
|
2314 |
<i class="fas fa-file-pdf"></i> PDF Upload
|
2315 |
</button>
|
2316 |
+
<button class="upload" id="textToAIBookBtn">
|
2317 |
+
<i class="fas fa-file-alt"></i> Text to AI-Book
|
2318 |
+
</button>
|
2319 |
<input id="pdfInput" type="file" accept="application/pdf" style="display:none">
|
2320 |
+
<input id="textInput" type="file" accept=".txt,.docx,.doc" style="display:none">
|
2321 |
</div>
|
2322 |
|
2323 |
<div class="section-title">Projects</div>
|
|
|
2642 |
}
|
2643 |
}
|
2644 |
|
|
|
|
|
|
|
2645 |
|
2646 |
// DOMμ΄ λ‘λλλ©΄ μ€ν
|
2647 |
document.addEventListener('DOMContentLoaded', function() {
|
|
|
2676 |
console.error("PDF μ
λ‘λ μμλ₯Ό μ°Ύμ μ μμ");
|
2677 |
}
|
2678 |
|
2679 |
+
// ν
μ€νΈ μ
λ‘λ λ²νΌ
|
2680 |
+
const textBtn = document.getElementById('textToAIBookBtn');
|
2681 |
+
const textInput = document.getElementById('textInput');
|
2682 |
+
|
2683 |
+
if (textBtn && textInput) {
|
2684 |
+
// λ²νΌ ν΄λ¦ μ νμΌ μ
λ ₯ νΈλ¦¬κ±°
|
2685 |
+
textBtn.addEventListener('click', function() {
|
2686 |
+
textInput.click();
|
2687 |
+
});
|
2688 |
+
|
2689 |
+
// νμΌ μ ν μ μ²λ¦¬
|
2690 |
+
textInput.addEventListener('change', function(e) {
|
2691 |
+
const file = e.target.files[0];
|
2692 |
+
if (!file) return;
|
2693 |
+
|
2694 |
+
// μλ²μ ν
μ€νΈ νμΌ μ
λ‘λ (μꡬ μ μ₯μμ PDFλ‘ λ³ννμ¬ μ μ₯)
|
2695 |
+
uploadTextToServer(file);
|
2696 |
+
});
|
2697 |
+
}
|
2698 |
+
|
2699 |
// μλ² PDF λ‘λ λ° μΊμ μν νμΈ
|
2700 |
loadServerPDFs();
|
2701 |
|
|
|
2796 |
}
|
2797 |
}
|
2798 |
|
2799 |
+
// μλ²μ ν
μ€νΈ νμΌμ μ
λ‘λνμ¬ PDFλ‘ λ³ννλ ν¨μ
|
2800 |
+
async function uploadTextToServer(file) {
|
2801 |
+
try {
|
2802 |
+
showLoading("ν
μ€νΈ λΆμ λ° PDF λ³ν μ€...");
|
2803 |
+
|
2804 |
+
const formData = new FormData();
|
2805 |
+
formData.append('file', file);
|
2806 |
+
|
2807 |
+
const response = await fetch('/api/text-to-pdf', {
|
2808 |
+
method: 'POST',
|
2809 |
+
body: formData
|
2810 |
+
});
|
2811 |
+
|
2812 |
+
const result = await response.json();
|
2813 |
+
|
2814 |
+
if (result.success) {
|
2815 |
+
hideLoading();
|
2816 |
+
|
2817 |
+
// μ
λ‘λ μ±κ³΅ μ μλ² PDF 리μ€νΈ 리λ‘λ
|
2818 |
+
await loadServerPDFs();
|
2819 |
+
|
2820 |
+
// μ±κ³΅ λ©μμ§
|
2821 |
+
showMessage("ν
μ€νΈκ° μ±κ³΅μ μΌλ‘ PDFλ‘ λ³νλμμ΅λλ€! 곡μ URL: " + result.viewUrl);
|
2822 |
+
} else {
|
2823 |
+
hideLoading();
|
2824 |
+
showError("λ³ν μ€ν¨: " + (result.message || "μ μ μλ μ€λ₯"));
|
2825 |
+
}
|
2826 |
+
} catch (error) {
|
2827 |
+
console.error("ν
μ€νΈ λ³ν μ€λ₯:", error);
|
2828 |
+
hideLoading();
|
2829 |
+
showError("ν
μ€νΈλ₯Ό PDFλ‘ λ³ννλ μ€ μ€λ₯κ° λ°μνμ΅λλ€.");
|
2830 |
+
}
|
2831 |
+
}
|
2832 |
+
|
2833 |
function addCard(i, thumb, title, isCached = false, pdfId = null) {
|
2834 |
const d = document.createElement('div');
|
2835 |
d.className = 'card fade-in';
|
|
|
3893 |
}
|
3894 |
</script>
|
3895 |
</body>
|
3896 |
+
</html>
|
|
|
3897 |
|
3898 |
if __name__ == "__main__":
|
3899 |
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
|