ginipick commited on
Commit
7007e56
ยท
verified ยท
1 Parent(s): 722e2a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -293
app.py CHANGED
@@ -206,7 +206,7 @@ async def cache_pdf(pdf_path: str):
206
  except Exception as e:
207
  import traceback
208
  logger.error(f"PDF ์บ์‹ฑ ์˜ค๋ฅ˜: {str(e)}\n{traceback.format_exc()}")
209
- if pdf_name in pdf_cache:
210
  pdf_cache[pdf_name]["status"] = "error"
211
  pdf_cache[pdf_name]["error"] = str(e)
212
 
@@ -310,7 +310,6 @@ async def get_cached_pdf(path: str, background_tasks: BackgroundTasks):
310
  pages = pdf_cache[pdf_name].get("pages", [])
311
  total_pages = pdf_cache[pdf_name].get("total_pages", 0)
312
 
313
- # ์ผ๋ถ€๋งŒ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ์—๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŽ˜์ด์ง€ ์ œ๊ณต
314
  return {
315
  "status": "processing",
316
  "progress": progress,
@@ -417,11 +416,11 @@ HTML = """
417
 
418
  body {
419
  margin: 0;
420
- background: var(--bg-color);
 
 
421
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
422
  color: var(--text-color);
423
- background-image: linear-gradient(120deg, var(--tertiary-color) 0%, var(--bg-color) 100%);
424
- background-attachment: fixed;
425
  }
426
 
427
  /* ํ—ค๋” ์ œ๋ชฉ ์ œ๊ฑฐ ๋ฐ Home ๋ฒ„ํŠผ ๋ ˆ์ด์–ด ์ฒ˜๋ฆฌ */
@@ -484,7 +483,7 @@ HTML = """
484
  opacity: 1;
485
  transform: translateX(0);
486
  }
487
-
488
  #home, #viewerPage {
489
  padding-top: 100px;
490
  max-width: 1200px;
@@ -510,7 +509,7 @@ HTML = """
510
  background: white;
511
  margin: 0 10px;
512
  font-weight: 500;
513
- display: flex;
514
  align-items: center;
515
  box-shadow: var(--shadow-sm);
516
  transition: var(--transition);
@@ -525,7 +524,7 @@ HTML = """
525
  left: 0;
526
  width: 100%;
527
  height: 100%;
528
- background: linear-gradient(120deg, var(--primary-color) 0%, var(--secondary-color) 100%);
529
  opacity: 0.08;
530
  z-index: -1;
531
  }
@@ -801,30 +800,30 @@ HTML = """
801
  }
802
 
803
  /* ํ—ค๋” ๋กœ๊ณ  ๋ฐ ํƒ€์ดํ‹€ */
 
804
  .library-header {
805
  position: fixed;
806
- top: 20px;
807
  left: 0;
808
  right: 0;
809
  text-align: center;
810
  z-index: 100;
811
- pointer-events: none;
812
  }
813
 
814
  .library-header .title {
815
  display: inline-block;
816
- padding: 12px 30px;
 
817
  background: rgba(255, 255, 255, 0.85);
818
  backdrop-filter: blur(10px);
819
  border-radius: 30px;
820
  box-shadow: var(--shadow-md);
821
- font-size: 1.5rem;
822
  font-weight: 600;
823
  background-image: linear-gradient(120deg, #667eea 0%, #764ba2 100%);
824
  -webkit-background-clip: text;
825
  background-clip: text;
826
  color: transparent;
827
- pointer-events: all;
828
  }
829
 
830
  /* ์ ์ง„์  ๋กœ๋”ฉ ํ‘œ์‹œ */
@@ -855,8 +854,8 @@ HTML = """
855
  }
856
 
857
  .library-header .title {
858
- font-size: 1.25rem;
859
- padding: 10px 20px;
860
  }
861
 
862
  .floating-home {
@@ -884,18 +883,16 @@ HTML = """
884
 
885
  <section id="home" class="fade-in">
886
  <div class="upload-container">
887
- <button type="button" id="imageUploadBtn" class="upload">
888
  <i class="fas fa-images"></i> ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
 
889
  </button>
890
- <button type="button" id="pdfUploadBtn" class="upload">
891
  <i class="fas fa-file-pdf"></i> PDF ์ถ”๊ฐ€
 
892
  </button>
893
  </div>
894
 
895
- <!-- ํŒŒ์ผ ์ž…๋ ฅ ์š”์†Œ๋ฅผ ๋ฒ„ํŠผ ๋ฐ–์œผ๋กœ ์ด๋™ํ•˜์—ฌ ๋ณ„๋„ ๋ฐฐ์น˜ -->
896
- <input id="imgInput" type="file" accept="image/*" multiple style="display:none">
897
- <input id="pdfInput" type="file" accept="application/pdf" style="display:none">
898
-
899
  <div class="section-title">๋‚ด ํ”„๋กœ์ ํŠธ</div>
900
  <div class="grid" id="grid">
901
  <!-- ์นด๋“œ๊ฐ€ ์—ฌ๊ธฐ์— ๋™์ ์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค -->
@@ -928,133 +925,104 @@ HTML = """
928
  .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
929
  {once:true,capture:true});
930
  });
931
-
932
- // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ด๋ฒคํŠธ ์„ค์ •
933
- document.addEventListener('DOMContentLoaded', function() {
934
- console.log('DOM Content Loaded - ์ด๋ฒคํŠธ ์„ค์ • ์‹œ์ž‘');
935
-
936
- // ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
937
- var imageUploadBtn = document.getElementById('imageUploadBtn');
938
- var imgInput = document.getElementById('imgInput');
939
-
940
- if (imageUploadBtn && imgInput) {
941
- console.log('์ด๋ฏธ์ง€ ์—…๏ฟฝ๏ฟฝ๋“œ ๋ฒ„ํŠผ ๋ฐœ๊ฒฌ');
942
- imageUploadBtn.addEventListener('click', function() {
943
- console.log('์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ๋จ');
944
- imgInput.click();
945
- });
946
- } else {
947
- console.error('์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฒ„ํŠผ ๋˜๋Š” ์ž…๋ ฅ ์š”์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ');
948
  }
949
 
950
- // PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
951
- var pdfUploadBtn = document.getElementById('pdfUploadBtn');
952
- var pdfInput = document.getElementById('pdfInput');
953
-
954
- if (pdfUploadBtn && pdfInput) {
955
- console.log('PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ ๋ฐœ๊ฒฌ');
956
- pdfUploadBtn.addEventListener('click', function() {
957
- console.log('PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ๋จ');
958
  pdfInput.click();
959
- });
960
- } else {
961
- console.error('PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ ๋˜๋Š” ์ž…๋ ฅ ์š”์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ');
962
- }
963
-
964
- console.log('PDF/์ด๋ฏธ์ง€ ์ž…๋ ฅ ํ•„๋“œ ์ด๋ฒคํŠธ ์„ค์ •');
965
- if (imgInput) {
966
- imgInput.addEventListener('change', function(e) {
967
- console.log('์ด๋ฏธ์ง€ ์ž…๋ ฅ ๋ณ€๊ฒฝ ๊ฐ์ง€๋จ');
968
- handleImageUpload(e);
969
- });
970
  }
 
 
 
 
 
 
971
 
972
- if (pdfInput) {
973
- pdfInput.addEventListener('change', function(e) {
974
- console.log('PDF ์ž…๋ ฅ ๋ณ€๊ฒฝ ๊ฐ์ง€๋จ');
975
- handlePdfUpload(e);
976
- });
977
- }
978
 
979
- // ํ™ˆ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์„ค์ •
980
- var homeButton = document.getElementById('homeButton');
981
- if (homeButton) {
982
- homeButton.addEventListener('click', function() {
983
- console.log('ํ™ˆ ๋ฒ„ํŠผ ํด๋ฆญ๋จ');
984
- homeButtonClicked();
985
- });
986
- }
987
 
988
- // ์„œ๋ฒ„์—์„œ PDF ๋กœ๋“œ
989
- loadServerPDFs();
 
 
 
 
 
 
990
 
991
- // ์บ์‹œ ์ƒํƒœ ์ฃผ๊ธฐ์  ํ™•์ธ
992
- setInterval(checkCacheStatus, 3000);
993
- });
994
-
995
- // ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
996
- function handleImageUpload(e) {
997
- const files = Array.from(e.target.files);
998
- if (!files.length) return;
999
 
1000
- // ๋กœ๋”ฉ ํ‘œ์‹œ ์ถ”๊ฐ€
1001
  showLoading("์ด๋ฏธ์ง€ ๋กœ๋”ฉ ์ค‘...");
1002
 
1003
- const pages = [];
1004
  let done = 0;
1005
- const total = files.length;
1006
-
1007
- files.forEach((file, index) => {
1008
- const reader = new FileReader();
1009
- reader.onload = function(event) {
1010
- pages[index] = {
1011
- src: event.target.result,
1012
- thumb: event.target.result
1013
- };
1014
- done++;
1015
- if (done === total) {
1016
  save(pages, '์ด๋ฏธ์ง€ ์ปฌ๋ ‰์…˜');
1017
  hideLoading();
1018
  }
1019
  };
1020
- reader.readAsDataURL(file);
1021
  });
1022
- }
1023
-
1024
- // PDF ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
1025
- function handlePdfUpload(e) {
1026
  const file = e.target.files[0];
1027
- if (!file) return;
1028
 
1029
- // ๋กœ๋”ฉ ํ‘œ์‹œ ์ถ”๊ฐ€
1030
  showLoading("PDF ๋กœ๋”ฉ ์ค‘...");
1031
 
1032
- const reader = new FileReader();
1033
- reader.onload = function(event) {
1034
- pdfjsLib.getDocument({data: event.target.result}).promise.then(async pdf => {
1035
  const pages = [];
1036
 
1037
- for (let p = 1; p <= pdf.numPages; p++) {
1038
- // ๋กœ๋”ฉ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
1039
- updateLoading(`PDF ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ค‘... (${p}/${pdf.numPages})`);
1040
-
1041
- const page = await pdf.getPage(p);
1042
- const viewport = page.getViewport({scale: 1});
1043
- const canvas = document.createElement('canvas');
1044
- canvas.width = viewport.width;
1045
- canvas.height = viewport.height;
1046
-
1047
- await page.render({
1048
- canvasContext: canvas.getContext('2d'),
1049
- viewport: viewport
1050
- }).promise;
1051
-
1052
- pages.push({
1053
- src: canvas.toDataURL(),
1054
- thumb: canvas.toDataURL()
1055
- });
1056
  }
1057
-
1058
  hideLoading();
1059
  save(pages, file.name.replace('.pdf', ''));
1060
  }).catch(error => {
@@ -1063,56 +1031,9 @@ HTML = """
1063
  showError("PDF ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1064
  });
1065
  };
1066
- reader.readAsArrayBuffer(file);
1067
- }
1068
-
1069
- // ํ™ˆ ๋ฒ„ํŠผ ํด๋ฆญ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
1070
- function homeButtonClicked() {
1071
- if (fb) {
1072
- fb.destroy();
1073
- viewer.innerHTML = '';
1074
- fb = null;
1075
- }
1076
- toggle(true);
1077
-
1078
- // ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์ •๋ฆฌ
1079
- if (pageLoadingInterval) {
1080
- clearInterval(pageLoadingInterval);
1081
- pageLoadingInterval = null;
1082
- }
1083
- document.getElementById('loadingPages').style.display = 'none';
1084
- currentLoadingPdfPath = null;
1085
- }
1086
- const d = document.createElement('div');
1087
- d.className = 'card fade-in';
1088
- d.onclick = () => open(i);
1089
-
1090
- // ์ œ๋ชฉ ์ฒ˜๋ฆฌ
1091
- const displayTitle = title ?
1092
- (title.length > 15 ? title.substring(0, 15) + '...' : title) :
1093
- 'ํ”„๋กœ์ ํŠธ ' + (i+1);
1094
-
1095
- // ์บ์‹œ ์ƒํƒœ ๋ฑƒ์ง€ ์ถ”๊ฐ€
1096
- const cachedBadge = isCached ?
1097
- '<div class="cached-status">์บ์‹œ๋จ</div>' : '';
1098
-
1099
- d.innerHTML = `
1100
- <div class="card-inner">
1101
- ${cachedBadge}
1102
- <img src="${thumb}" alt="${displayTitle}" loading="lazy">
1103
- <p title="${title || 'ํ”„๋กœ์ ํŠธ ' + (i+1)}">${displayTitle}</p>
1104
- </div>
1105
- `;
1106
- grid.appendChild(d);
1107
-
1108
- // ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ์œผ๋ฉด 'ํ”„๋กœ์ ํŠธ ์—†์Œ' ๋ฉ”์‹œ์ง€ ์ˆจ๊ธฐ๊ธฐ
1109
- $id('noProjects').style.display = 'none';
1110
- }
1111
 
1112
- /* โ”€โ”€ ์œ ํ‹ธ โ”€โ”€ */
1113
- function $id(id){return document.getElementById(id)}
1114
-
1115
- /* โ”€โ”€ ํ”„๋กœ์ ํŠธ ์ €์žฅ โ”€โ”€ */
1116
  function save(pages, title, isCached = false){
1117
  const id=projects.push(pages)-1;
1118
  addCard(id, pages[0].thumb, title, isCached);
@@ -1121,7 +1042,6 @@ HTML = """
1121
  /* โ”€โ”€ ์„œ๋ฒ„ PDF ๋กœ๋“œ ๋ฐ ์บ์‹œ ์ƒํƒœ ํ™•์ธ โ”€โ”€ */
1122
  async function loadServerPDFs() {
1123
  try {
1124
- // ๋กœ๋”ฉ ํ‘œ์‹œ ์ถ”๊ฐ€
1125
  if (document.querySelectorAll('.card').length === 0) {
1126
  showLoading("๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๋”ฉ ์ค‘...");
1127
  }
@@ -1140,16 +1060,15 @@ HTML = """
1140
  return;
1141
  }
1142
 
1143
- // ์„œ๋ฒ„ PDF ๋กœ๋“œ ๋ฐ ์ธ๋„ค์ผ ์ƒ์„ฑ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ์ตœ์ ํ™”)
1144
  const thumbnailPromises = serverProjects.map(async (project, index) => {
1145
- updateLoading(`PDF ํ”„๋กœ์ ํŠธ ๋กœ๋”ฉ ์ค‘... (${index+1}/${serverProjects.length})`);
1146
 
1147
  const pdfName = project.name;
1148
  const isCached = cacheStatus[pdfName] && cacheStatus[pdfName].status === "completed";
1149
 
1150
  try {
1151
  // ์ธ๋„ค์ผ ๊ฐ€์ ธ์˜ค๊ธฐ
1152
- const response = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(project.path)}`);
1153
  const data = await response.json();
1154
 
1155
  if(data.thumbnail) {
@@ -1159,31 +1078,23 @@ HTML = """
1159
  path: project.path,
1160
  cached: isCached
1161
  }];
1162
-
1163
- return {
1164
- pages,
1165
- name: project.name,
1166
- isCached
1167
- };
1168
  }
1169
  } catch (err) {
1170
- console.error(`์ธ๋„ค์ผ ๋กœ๋“œ ์˜ค๋ฅ˜ (${project.name}):`, err);
1171
  }
1172
 
1173
  return null;
1174
  });
1175
 
1176
- // ๋ชจ๋“  ์ธ๋„ค์ผ ์š”์ฒญ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ
1177
  const results = await Promise.all(thumbnailPromises);
1178
 
1179
- // ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ๋งŒ ํ‘œ์‹œ
1180
  results.filter(result => result !== null).forEach(result => {
1181
  save(result.pages, result.name, result.isCached);
1182
  });
1183
 
1184
  hideLoading();
1185
 
1186
- // ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
1187
  if (document.querySelectorAll('.card').length === 0) {
1188
  $id('noProjects').style.display = 'block';
1189
  }
@@ -1208,7 +1119,6 @@ HTML = """
1208
  const pdfPath = projects[i][0].path;
1209
  const pdfName = pdfPath.split('/').pop().replace('.pdf', '');
1210
 
1211
- // ์บ์‹œ ์ƒํƒœ ๋ฑƒ์ง€ ์—…๋ฐ์ดํŠธ
1212
  let badgeEl = cards[i].querySelector('.cached-status');
1213
 
1214
  if(cacheStatus[pdfName] && cacheStatus[pdfName].status === "completed") {
@@ -1228,7 +1138,7 @@ HTML = """
1228
  badgeEl.className = 'cached-status';
1229
  cards[i].querySelector('.card-inner')?.appendChild(badgeEl);
1230
  }
1231
- badgeEl.textContent = `${cacheStatus[pdfName].progress}%`;
1232
  badgeEl.style.background = 'var(--secondary-color)';
1233
  }
1234
  }
@@ -1237,27 +1147,21 @@ HTML = """
1237
  // ํ˜„์žฌ ๋กœ๋”ฉ ์ค‘์ธ PDF๊ฐ€ ์žˆ์œผ๋ฉด ์ƒํƒœ ํ™•์ธ
1238
  if (currentLoadingPdfPath && pageLoadingInterval) {
1239
  const pdfName = currentLoadingPdfPath.split('/').pop().replace('.pdf', '');
1240
-
1241
  if (cacheStatus[pdfName]) {
1242
  const status = cacheStatus[pdfName].status;
1243
  const progress = cacheStatus[pdfName].progress || 0;
1244
 
1245
  if (status === "completed") {
1246
- // ์บ์‹ฑ ์™„๋ฃŒ ์‹œ
1247
  clearInterval(pageLoadingInterval);
1248
  $id('loadingPages').style.display = 'none';
1249
  currentLoadingPdfPath = null;
1250
-
1251
- // ์™„๋ฃŒ๋œ ์บ์‹œ๋กœ ํ”Œ๋ฆฝ๋ถ ๋‹ค์‹œ ๋กœ๋“œ
1252
  refreshFlipBook();
1253
  } else if (status === "processing") {
1254
- // ์ง„ํ–‰ ์ค‘์ผ ๋•Œ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
1255
  $id('loadingPages').style.display = 'block';
1256
- $id('loadingPagesCount').textContent = `${progress}%`;
1257
  }
1258
  }
1259
  }
1260
-
1261
  } catch(error) {
1262
  console.error('์บ์‹œ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:', error);
1263
  }
@@ -1268,27 +1172,20 @@ HTML = """
1268
  toggle(false);
1269
  const pages = projects[i];
1270
 
1271
- // ๊ธฐ์กด FlipBook ์ •๋ฆฌ
1272
  if(fb) {
1273
  fb.destroy();
1274
  viewer.innerHTML = '';
1275
  }
1276
 
1277
- // ์„œ๋ฒ„ PDF ๋˜๋Š” ๋กœ์ปฌ ํ”„๋กœ์ ํŠธ ์ฒ˜๋ฆฌ
1278
  if(pages[0].path) {
1279
  const pdfPath = pages[0].path;
1280
-
1281
- // ์ ์ง„์  ๋กœ๋”ฉ ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™”
1282
  let progressiveLoading = false;
1283
  currentLoadingPdfPath = pdfPath;
1284
 
1285
- // ์บ์‹œ ์—ฌ๋ถ€ ํ™•์ธ
1286
  if(pages[0].cached) {
1287
- // ์บ์‹œ๋œ PDF ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
1288
  showLoading("์บ์‹œ๋œ PDF ๋กœ๋”ฉ ์ค‘...");
1289
-
1290
  try {
1291
- const response = await fetch(`/api/cached-pdf?path=${encodeURIComponent(pdfPath)}`);
1292
  const cachedData = await response.json();
1293
 
1294
  if(cachedData.status === "completed" && cachedData.pages) {
@@ -1297,29 +1194,22 @@ HTML = """
1297
  currentLoadingPdfPath = null;
1298
  return;
1299
  } else if(cachedData.status === "processing" && cachedData.pages && cachedData.pages.length > 0) {
1300
- // ์ผ๋ถ€ ํŽ˜์ด์ง€๊ฐ€ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ ์ ์ง„์  ๋กœ๋”ฉ ์‚ฌ์šฉ
1301
  hideLoading();
1302
  createFlipBook(cachedData.pages);
1303
  progressiveLoading = true;
1304
-
1305
- // ์ ์ง„์  ๋กœ๋”ฉ ์ค‘์ž„์„ ํ‘œ์‹œ
1306
  startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1307
  }
1308
  } catch(error) {
1309
  console.error("์บ์‹œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ค๋ฅ˜:", error);
1310
- // ์บ์‹œ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ์›๋ณธ PDF๋กœ ๋Œ€์ฒด
1311
  }
1312
  }
1313
 
1314
- if (!progressiveLoading) {
1315
- // ์บ์‹œ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ์„œ๋ฒ„ PDF ๋กœ๋“œ
1316
  showLoading("PDF ์ค€๋น„ ์ค‘...");
1317
-
1318
  try {
1319
- const response = await fetch(`/api/pdf-content?path=${encodeURIComponent(pdfPath)}`);
1320
  const data = await response.json();
1321
 
1322
- // ์บ์‹œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋œ ๊ฒฝ์šฐ
1323
  if(data.redirect) {
1324
  const redirectRes = await fetch(data.redirect);
1325
  const cachedData = await redirectRes.json();
@@ -1330,20 +1220,15 @@ HTML = """
1330
  currentLoadingPdfPath = null;
1331
  return;
1332
  } else if(cachedData.status === "processing" && cachedData.pages && cachedData.pages.length > 0) {
1333
- // ์ผ๋ถ€ ํŽ˜์ด์ง€๊ฐ€ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ ์ ์ง„์  ๋กœ๋”ฉ ์‚ฌ์šฉ
1334
  hideLoading();
1335
  createFlipBook(cachedData.pages);
1336
-
1337
- // ์ ์ง„์  ๋กœ๋”ฉ ์ค‘์ž„์„ ํ‘œ์‹œ
1338
  startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1339
  return;
1340
  }
1341
  }
1342
 
1343
- // ์›๋ณธ PDF ๋กœ๋“œ (ArrayBuffer ํ˜•ํƒœ)
1344
- const pdfResponse = await fetch(`/api/pdf-content?path=${encodeURIComponent(pdfPath)}`);
1345
 
1346
- // JSON ์‘๋‹ต์ธ ๊ฒฝ์šฐ (๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋“ฑ)
1347
  try {
1348
  const jsonData = await pdfResponse.clone().json();
1349
  if (jsonData.redirect) {
@@ -1353,7 +1238,6 @@ HTML = """
1353
  if(cachedData.pages && cachedData.pages.length > 0) {
1354
  hideLoading();
1355
  createFlipBook(cachedData.pages);
1356
-
1357
  if(cachedData.status === "processing") {
1358
  startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1359
  } else {
@@ -1363,78 +1247,63 @@ HTML = """
1363
  }
1364
  }
1365
  } catch (e) {
1366
- // JSON ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ์›๋ณธ PDF ๋ฐ์ดํ„ฐ๋กœ ์ฒ˜๋ฆฌ
1367
  }
1368
 
1369
- // ArrayBuffer ํ˜•ํƒœ์˜ PDF ๋ฐ์ดํ„ฐ
1370
  const pdfData = await pdfResponse.arrayBuffer();
1371
-
1372
- // PDF ๋กœ๋“œ ๋ฐ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง
1373
  const pdf = await pdfjsLib.getDocument({data: pdfData}).promise;
1374
  const pdfPages = [];
1375
 
1376
- for(let p = 1; p <= pdf.numPages; p++) {
1377
- updateLoading(`ํŽ˜์ด์ง€ ์ค€๋น„ ์ค‘... (${p}/${pdf.numPages})`);
1378
-
1379
  const pg = await pdf.getPage(p);
1380
  const vp = pg.getViewport({scale: 1});
1381
  const c = document.createElement('canvas');
1382
  c.width = vp.width;
1383
  c.height = vp.height;
1384
-
1385
  await pg.render({canvasContext: c.getContext('2d'), viewport: vp}).promise;
1386
  pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()});
1387
  }
1388
-
1389
  hideLoading();
1390
  createFlipBook(pdfPages);
1391
  currentLoadingPdfPath = null;
1392
-
1393
  } catch(error) {
1394
- console.error('PDF ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
1395
  hideLoading();
1396
  showError("PDF๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1397
  currentLoadingPdfPath = null;
1398
  }
1399
  }
1400
  } else {
1401
- // ๋กœ์ปฌ ์—…๋กœ๋“œ๋œ ํ”„๋กœ์ ํŠธ ์‹คํ–‰
1402
  createFlipBook(pages);
1403
  currentLoadingPdfPath = null;
1404
  }
1405
  }
1406
 
1407
- /* โ”€โ”€ ์ ์ง„์  ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์‹œ์ž‘ โ”€โ”€ */
1408
  function startProgressiveLoadingIndicator(progress, totalPages) {
1409
- // ์ง„ํ–‰ ์ƒํƒœ ํ‘œ์‹œ ํ™œ์„ฑํ™”
1410
  $id('loadingPages').style.display = 'block';
1411
- $id('loadingPagesCount').textContent = `${progress}%`;
1412
 
1413
- // ๊ธฐ์กด ์ธํ„ฐ๋ฒŒ ์ œ๊ฑฐ
1414
  if (pageLoadingInterval) {
1415
  clearInterval(pageLoadingInterval);
1416
  }
1417
-
1418
- // ์ฃผ๊ธฐ์ ์œผ๋กœ ์บ์‹œ ์ƒํƒœ ํ™•์ธ (2์ดˆ๋งˆ๋‹ค)
1419
  pageLoadingInterval = setInterval(async () => {
1420
  if (!currentLoadingPdfPath) {
1421
  clearInterval(pageLoadingInterval);
1422
  $id('loadingPages').style.display = 'none';
1423
  return;
1424
  }
1425
-
1426
  try {
1427
- const response = await fetch(`/api/cache-status?path=${encodeURIComponent(currentLoadingPdfPath)}`);
1428
  const status = await response.json();
1429
 
1430
- // ์ƒํƒœ ์—…๋ฐ์ดํŠธ
1431
  if (status.status === "completed") {
1432
  clearInterval(pageLoadingInterval);
1433
  $id('loadingPages').style.display = 'none';
1434
- refreshFlipBook(); // ์™„๋ฃŒ๋œ ๋ฐ์ดํ„ฐ๋กœ ์ƒˆ๋กœ๊ณ ์นจ
1435
  currentLoadingPdfPath = null;
1436
  } else if (status.status === "processing") {
1437
- $id('loadingPagesCount').textContent = `${status.progress}%`;
1438
  }
1439
  } catch (e) {
1440
  console.error("์บ์‹œ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:", e);
@@ -1442,20 +1311,15 @@ HTML = """
1442
  }, 1000);
1443
  }
1444
 
1445
- /* โ”€โ”€ ํ”Œ๋ฆฝ๋ถ ์ƒˆ๋กœ๊ณ ์นจ โ”€โ”€ */
1446
  async function refreshFlipBook() {
1447
  if (!currentLoadingPdfPath || !fb) return;
1448
-
1449
  try {
1450
- const response = await fetch(`/api/cached-pdf?path=${encodeURIComponent(currentLoadingPdfPath)}`);
1451
  const cachedData = await response.json();
1452
 
1453
  if(cachedData.status === "completed" && cachedData.pages) {
1454
- // ๊ธฐ์กด ํ”Œ๋ฆฝ๋ถ ์ •๋ฆฌ
1455
  fb.destroy();
1456
  viewer.innerHTML = '';
1457
-
1458
- // ์ƒˆ ๋ฐ์ดํ„ฐ๋กœ ์žฌ์ƒ์„ฑ
1459
  createFlipBook(cachedData.pages);
1460
  currentLoadingPdfPath = null;
1461
  }
@@ -1466,47 +1330,39 @@ HTML = """
1466
 
1467
  function createFlipBook(pages) {
1468
  console.log('FlipBook ์ƒ์„ฑ ์‹œ์ž‘. ํŽ˜์ด์ง€ ์ˆ˜:', pages.length);
1469
-
1470
  try {
1471
- // ํ™”๋ฉด ๋น„์œจ ๊ณ„์‚ฐ
1472
  const calculateAspectRatio = () => {
1473
  const windowWidth = window.innerWidth;
1474
  const windowHeight = window.innerHeight;
1475
  const aspectRatio = windowWidth / windowHeight;
1476
 
1477
- // ๋„ˆ๋น„ ๋˜๋Š” ๋†’์ด ๊ธฐ์ค€์œผ๋กœ ์ตœ๋Œ€ 90% ์ œํ•œ
1478
  let width, height;
1479
- if (aspectRatio > 1) { // ๊ฐ€๋กœ ํ™”๋ฉด
1480
  height = Math.min(windowHeight * 0.9, windowHeight - 40);
1481
- width = height * aspectRatio * 0.8; // ๊ฐ€๋กœ ํ™”๋ฉด์—์„œ๋Š” ์•ฝ๊ฐ„ ์ค„์ž„
1482
  if (width > windowWidth * 0.9) {
1483
  width = windowWidth * 0.9;
1484
  height = width / (aspectRatio * 0.8);
1485
  }
1486
- } else { // ์„ธ๋กœ ํ™”๋ฉด
1487
  width = Math.min(windowWidth * 0.9, windowWidth - 40);
1488
- height = width / aspectRatio * 0.9; // ์„ธ๋กœ ํ™”๋ฉด์—์„œ๋Š” ์•ฝ๊ฐ„ ๋Š˜๋ฆผ
1489
  if (height > windowHeight * 0.9) {
1490
  height = windowHeight * 0.9;
1491
  width = height * aspectRatio * 0.9;
1492
  }
1493
  }
1494
-
1495
- // ์ตœ์  ์‚ฌ์ด์ฆˆ ๋ฐ˜ํ™˜
1496
  return {
1497
  width: Math.round(width),
1498
  height: Math.round(height)
1499
  };
1500
  };
1501
 
1502
- // ์ดˆ๊ธฐ ํ™”๋ฉด ๋น„์œจ ๊ณ„์‚ฐ
1503
  const size = calculateAspectRatio();
1504
  viewer.style.width = size.width + 'px';
1505
  viewer.style.height = size.height + 'px';
1506
 
1507
- // ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ ์ •์ œ (๋นˆ ํŽ˜์ด์ง€ ์ฒ˜๋ฆฌ)
1508
  const validPages = pages.map(page => {
1509
- // src๊ฐ€ ์—†๋Š” ํŽ˜์ด์ง€๋Š” ๋กœ๋”ฉ ์ค‘ ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด
1510
  if (!page || !page.src) {
1511
  return {
1512
  src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iIGZpbGw9IiM1NTUiPkxvYWRpbmcuLi48L3RleHQ+PC9zdmc+',
@@ -1522,7 +1378,6 @@ HTML = """
1522
  autoSize: true,
1523
  flipDuration: 800,
1524
  backgroundColor: '#fff',
1525
- /* ๐Ÿ”Š ๋‚ด์žฅ ์‚ฌ์šด๋“œ */
1526
  sound: true,
1527
  assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'},
1528
  controlsProps: {
@@ -1537,22 +1392,21 @@ HTML = """
1537
  enableAnnotation: false,
1538
  enableSound: true,
1539
  enableLightbox: false,
1540
- layout: 10, // ๋ ˆ์ด์•„์›ƒ ์˜ต์…˜
1541
- skin: 'light', // ์Šคํ‚จ ์Šคํƒ€์ผ
1542
- autoNavigationTime: 3600, // ์ž๋™ ๋„˜๊น€ ์‹œ๊ฐ„(์ดˆ)
1543
- hideControls: false, // ์ปจํŠธ๋กค ์ˆจ๊น€ ๋น„ํ™œ์„ฑํ™”
1544
- paddingTop: 10, // ์ƒ๋‹จ ํŒจ๋”ฉ
1545
- paddingLeft: 10, // ์ขŒ์ธก ํŒจ๋”ฉ
1546
- paddingRight: 10, // ์šฐ์ธก ํŒจ๋”ฉ
1547
- paddingBottom: 10, // ํ•˜๋‹จ ํŒจ๋”ฉ
1548
- pageTextureSize: 1024, // ํŽ˜์ด์ง€ ํ…์Šค์ฒ˜ ํฌ๊ธฐ
1549
- thumbnails: true, // ์„ฌ๋„ค์ผ ํ™œ์„ฑํ™”
1550
- autoHideControls: false, // ์ž๋™ ์ˆจ๊น€ ๋น„ํ™œ์„ฑํ™”
1551
- controlsTimeout: 8000 // ์ปจํŠธ๋กค ํ‘œ์‹œ ์‹œ๊ฐ„ ์—ฐ์žฅ
1552
  }
1553
  });
1554
 
1555
- // ํ™”๋ฉด ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ FlipBook ํฌ๊ธฐ ์กฐ์ •
1556
  window.addEventListener('resize', () => {
1557
  if (fb) {
1558
  const newSize = calculateAspectRatio();
@@ -1562,10 +1416,8 @@ HTML = """
1562
  }
1563
  });
1564
 
1565
- // FlipBook ์ƒ์„ฑ ํ›„ ์ปจํŠธ๋กค๋ฐ” ๊ฐ•์ œ ํ‘œ์‹œ
1566
  setTimeout(() => {
1567
  try {
1568
- // ์ปจํŠธ๋กค๋ฐ” ๊ด€๋ จ ์š”์†Œ ์ฐพ๊ธฐ ๋ฐ ์Šคํƒ€์ผ ์ ์šฉ
1569
  const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar');
1570
  if (menuBars && menuBars.length > 0) {
1571
  menuBars.forEach(menuBar => {
@@ -1587,15 +1439,26 @@ HTML = """
1587
  }
1588
  }
1589
 
1590
- /* โ”€โ”€ ๋„ค๋น„๊ฒŒ์ด์…˜ โ”€โ”€ */
1591
- $id('homeButton').onclick=homeButtonClicked;
 
 
 
 
 
 
 
 
 
 
 
 
1592
 
1593
  function toggle(showHome){
1594
- $id('home').style.display=showHome?'block':'none';
1595
- $id('viewerPage').style.display=showHome?'none':'block';
1596
- $id('homeButton').style.display=showHome?'none':'block';
1597
 
1598
- // ๋ทฐ์–ด ๋ชจ๋“œ์ผ ๋•Œ ์Šคํƒ€์ผ ๋ณ€๊ฒฝ
1599
  if(!showHome) {
1600
  document.body.classList.add('viewer-mode');
1601
  } else {
@@ -1603,11 +1466,8 @@ HTML = """
1603
  }
1604
  }
1605
 
1606
- /* -- ๋กœ๋”ฉ ๋ฐ ์˜ค๋ฅ˜ ํ‘œ์‹œ -- */
1607
  function showLoading(message, progress = -1) {
1608
- // ๊ธฐ์กด ๋กœ๋”ฉ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ œ๊ฑฐ
1609
  hideLoading();
1610
-
1611
  const loadingContainer = document.createElement('div');
1612
  loadingContainer.className = 'loading-container fade-in';
1613
  loadingContainer.id = 'loadingContainer';
@@ -1638,7 +1498,6 @@ HTML = """
1638
 
1639
  if (progress >= 0) {
1640
  let progressBar = $id('progressBar');
1641
-
1642
  if (!progressBar) {
1643
  const loadingContainer = $id('loadingContainer');
1644
  if (loadingContainer) {
@@ -1649,7 +1508,7 @@ HTML = """
1649
  progressBar = $id('progressBar');
1650
  }
1651
  } else {
1652
- progressBar.style.width = `${progress}%`;
1653
  }
1654
  }
1655
  }
@@ -1662,7 +1521,6 @@ HTML = """
1662
  }
1663
 
1664
  function showError(message) {
1665
- // ๊ธฐ์กด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด ์ œ๊ฑฐ
1666
  const existingError = $id('errorContainer');
1667
  if (existingError) {
1668
  existingError.remove();
@@ -1678,12 +1536,10 @@ HTML = """
1678
 
1679
  document.body.appendChild(errorContainer);
1680
 
1681
- // ํ™•์ธ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
1682
  $id('errorCloseBtn').onclick = () => {
1683
  errorContainer.remove();
1684
  };
1685
 
1686
- // 5์ดˆ ํ›„ ์ž๋™์œผ๋กœ ๋‹ซ๊ธฐ
1687
  setTimeout(() => {
1688
  if ($id('errorContainer')) {
1689
  $id('errorContainer').remove();
@@ -1691,7 +1547,15 @@ HTML = """
1691
  }, 5000);
1692
  }
1693
 
1694
- function addCard(i, thumb, title, isCached = false) {
 
 
 
 
 
 
 
 
1695
  </script>
1696
  </body>
1697
  </html>
@@ -1702,4 +1566,4 @@ async def root():
1702
  return get_html_content()
1703
 
1704
  if __name__ == "__main__":
1705
- uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
 
206
  except Exception as e:
207
  import traceback
208
  logger.error(f"PDF ์บ์‹ฑ ์˜ค๋ฅ˜: {str(e)}\n{traceback.format_exc()}")
209
+ if 'pdf_name' in locals() and pdf_name in pdf_cache:
210
  pdf_cache[pdf_name]["status"] = "error"
211
  pdf_cache[pdf_name]["error"] = str(e)
212
 
 
310
  pages = pdf_cache[pdf_name].get("pages", [])
311
  total_pages = pdf_cache[pdf_name].get("total_pages", 0)
312
 
 
313
  return {
314
  "status": "processing",
315
  "progress": progress,
 
416
 
417
  body {
418
  margin: 0;
419
+ /* โ–ผ ๊ณ ๊ธ‰์ง„ ๋ธ”๋ฃจ ๊ณ„์—ด ๊ทธ๋ผ๋””์—์ด์…˜์œผ๋กœ ๋ณ€๊ฒฝ */
420
+ background-image: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
421
+ background-attachment: fixed;
422
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
423
  color: var(--text-color);
 
 
424
  }
425
 
426
  /* ํ—ค๋” ์ œ๋ชฉ ์ œ๊ฑฐ ๋ฐ Home ๋ฒ„ํŠผ ๋ ˆ์ด์–ด ์ฒ˜๋ฆฌ */
 
483
  opacity: 1;
484
  transform: translateX(0);
485
  }
486
+
487
  #home, #viewerPage {
488
  padding-top: 100px;
489
  max-width: 1200px;
 
509
  background: white;
510
  margin: 0 10px;
511
  font-weight: 500;
512
+ display: inline-flex;
513
  align-items: center;
514
  box-shadow: var(--shadow-sm);
515
  transition: var(--transition);
 
524
  left: 0;
525
  width: 100%;
526
  height: 100%;
527
+ background: linear-gradient(120deg, var(--primary-color), var(--secondary-color));
528
  opacity: 0.08;
529
  z-index: -1;
530
  }
 
800
  }
801
 
802
  /* ํ—ค๋” ๋กœ๊ณ  ๋ฐ ํƒ€์ดํ‹€ */
803
+ /* โ–ผ pointer-events: none ์ œ๊ฑฐ, ๋†’์ด์™€ ํŒจ๋”ฉ ์กฐ์ •ํ•˜์—ฌ ์ œ๋ชฉ์„ ๋” ์œ„๋กœ */
804
  .library-header {
805
  position: fixed;
806
+ top: 10px; /* ๊ธฐ์กด 20px -> 10px์œผ๋กœ */
807
  left: 0;
808
  right: 0;
809
  text-align: center;
810
  z-index: 100;
 
811
  }
812
 
813
  .library-header .title {
814
  display: inline-block;
815
+ /* ๋†’์ด ์ค„์ด๊ธฐ ์œ„ํ•œ ํŒจ๋”ฉ/ํฐํŠธ ํฌ๊ธฐ ์กฐ์ • */
816
+ padding: 8px 20px; /* ๊ธฐ์กด 12px 30px -> 8px 20px */
817
  background: rgba(255, 255, 255, 0.85);
818
  backdrop-filter: blur(10px);
819
  border-radius: 30px;
820
  box-shadow: var(--shadow-md);
821
+ font-size: 1.2rem; /* ๊ธฐ์กด 1.5rem -> 1.2rem */
822
  font-weight: 600;
823
  background-image: linear-gradient(120deg, #667eea 0%, #764ba2 100%);
824
  -webkit-background-clip: text;
825
  background-clip: text;
826
  color: transparent;
 
827
  }
828
 
829
  /* ์ ์ง„์  ๋กœ๋”ฉ ํ‘œ์‹œ */
 
854
  }
855
 
856
  .library-header .title {
857
+ font-size: 1rem;
858
+ padding: 6px 16px;
859
  }
860
 
861
  .floating-home {
 
883
 
884
  <section id="home" class="fade-in">
885
  <div class="upload-container">
886
+ <button class="upload" id="imageUploadBtn">
887
  <i class="fas fa-images"></i> ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
888
+ <input id="imgInput" type="file" accept="image/*" multiple hidden>
889
  </button>
890
+ <button class="upload" id="pdfUploadBtn">
891
  <i class="fas fa-file-pdf"></i> PDF ์ถ”๊ฐ€
892
+ <input id="pdfInput" type="file" accept="application/pdf" hidden>
893
  </button>
894
  </div>
895
 
 
 
 
 
896
  <div class="section-title">๋‚ด ํ”„๋กœ์ ํŠธ</div>
897
  <div class="grid" id="grid">
898
  <!-- ์นด๋“œ๊ฐ€ ์—ฌ๊ธฐ์— ๋™์ ์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค -->
 
925
  .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
926
  {once:true,capture:true});
927
  });
928
+
929
+ /* โ”€โ”€ ์œ ํ‹ธ โ”€โ”€ */
930
+ function $id(id){return document.getElementById(id)}
931
+
932
+ // ์ง์ ‘ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ • (์—…๋กœ๋“œ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ํŒŒ์ผ ์„ ํƒ์ฐฝ ์—ด๊ธฐ)
933
+ function setupDirectEvents() {
934
+ // ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฒ„ํŠผ
935
+ const imageBtn = $id('imageUploadBtn');
936
+ const imageInput = $id('imgInput');
937
+ if (imageBtn && imageInput) {
938
+ console.log('์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์„ค์ •');
939
+ imageBtn.onclick = function(e) {
940
+ e.preventDefault();
941
+ e.stopPropagation();
942
+ imageInput.click();
943
+ };
 
944
  }
945
 
946
+ // PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ
947
+ const pdfBtn = $id('pdfUploadBtn');
948
+ const pdfInput = $id('pdfInput');
949
+ if (pdfBtn && pdfInput) {
950
+ console.log('PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์„ค์ •');
951
+ pdfBtn.onclick = function(e) {
952
+ e.preventDefault();
953
+ e.stopPropagation();
954
  pdfInput.click();
955
+ };
 
 
 
 
 
 
 
 
 
 
956
  }
957
+ }
958
+
959
+ function addCard(i, thumb, title, isCached = false) {
960
+ const d = document.createElement('div');
961
+ d.className = 'card fade-in';
962
+ d.onclick = () => open(i);
963
 
964
+ const displayTitle = title ?
965
+ (title.length > 15 ? title.substring(0, 15) + '...' : title) :
966
+ 'ํ”„๋กœ์ ํŠธ ' + (i+1);
 
 
 
967
 
968
+ const cachedBadge = isCached ?
969
+ '<div class="cached-status">์บ์‹œ๋จ</div>' : '';
 
 
 
 
 
 
970
 
971
+ d.innerHTML = `
972
+ <div class="card-inner">
973
+ ${cachedBadge}
974
+ <img src="${thumb}" alt="${displayTitle}" loading="lazy">
975
+ <p title="${title || 'ํ”„๋กœ์ ํŠธ ' + (i+1)}">${displayTitle}</p>
976
+ </div>
977
+ `;
978
+ grid.appendChild(d);
979
 
980
+ // ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ์œผ๋ฉด 'ํ”„๋กœ์ ํŠธ ์—†์Œ' ๋ฉ”์‹œ์ง€ ์ˆจ๊ธฐ๊ธฐ
981
+ $id('noProjects').style.display = 'none';
982
+ }
983
+
984
+ /* โ”€โ”€ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ โ”€โ”€ */
985
+ $id('imgInput').onchange = e => {
986
+ const files = [...e.target.files];
987
+ if(!files.length) return;
988
 
 
989
  showLoading("์ด๋ฏธ์ง€ ๋กœ๋”ฉ ์ค‘...");
990
 
991
+ const pages=[], tot = files.length;
992
  let done = 0;
993
+ files.forEach((f,i)=> {
994
+ const r=new FileReader();
995
+ r.onload=x=>{
996
+ pages[i] = {src: x.target.result, thumb: x.target.result};
997
+ if(++done===tot) {
 
 
 
 
 
 
998
  save(pages, '์ด๋ฏธ์ง€ ์ปฌ๋ ‰์…˜');
999
  hideLoading();
1000
  }
1001
  };
1002
+ r.readAsDataURL(f);
1003
  });
1004
+ };
1005
+
1006
+ /* โ”€โ”€ PDF ์—…๋กœ๋“œ โ”€โ”€ */
1007
+ $id('pdfInput').onchange = e => {
1008
  const file = e.target.files[0];
1009
+ if(!file) return;
1010
 
 
1011
  showLoading("PDF ๋กœ๋”ฉ ์ค‘...");
1012
 
1013
+ const fr = new FileReader();
1014
+ fr.onload = v => {
1015
+ pdfjsLib.getDocument({data: v.target.result}).promise.then(async pdf=>{
1016
  const pages = [];
1017
 
1018
+ for(let p=1; p <= pdf.numPages; p++){
1019
+ updateLoading(\`PDF ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ค‘... (\${p}/\${pdf.numPages})\`);
1020
+ const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1});
1021
+ const c=document.createElement('canvas');
1022
+ c.width=vp.width;c.height=vp.height;
1023
+ await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise;
1024
+ pages.push({src:c.toDataURL(), thumb:c.toDataURL()});
 
 
 
 
 
 
 
 
 
 
 
 
1025
  }
 
1026
  hideLoading();
1027
  save(pages, file.name.replace('.pdf', ''));
1028
  }).catch(error => {
 
1031
  showError("PDF ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1032
  });
1033
  };
1034
+ fr.readAsArrayBuffer(file);
1035
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
 
 
 
 
 
1037
  function save(pages, title, isCached = false){
1038
  const id=projects.push(pages)-1;
1039
  addCard(id, pages[0].thumb, title, isCached);
 
1042
  /* โ”€โ”€ ์„œ๋ฒ„ PDF ๋กœ๋“œ ๋ฐ ์บ์‹œ ์ƒํƒœ ํ™•์ธ โ”€โ”€ */
1043
  async function loadServerPDFs() {
1044
  try {
 
1045
  if (document.querySelectorAll('.card').length === 0) {
1046
  showLoading("๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๋”ฉ ์ค‘...");
1047
  }
 
1060
  return;
1061
  }
1062
 
 
1063
  const thumbnailPromises = serverProjects.map(async (project, index) => {
1064
+ updateLoading(\`PDF ํ”„๋กœ์ ํŠธ ๋กœ๋”ฉ ์ค‘... (\${index+1}/\${serverProjects.length})\`);
1065
 
1066
  const pdfName = project.name;
1067
  const isCached = cacheStatus[pdfName] && cacheStatus[pdfName].status === "completed";
1068
 
1069
  try {
1070
  // ์ธ๋„ค์ผ ๊ฐ€์ ธ์˜ค๊ธฐ
1071
+ const response = await fetch(\`/api/pdf-thumbnail?path=\${encodeURIComponent(project.path)}\`);
1072
  const data = await response.json();
1073
 
1074
  if(data.thumbnail) {
 
1078
  path: project.path,
1079
  cached: isCached
1080
  }];
1081
+ return { pages, name: project.name, isCached };
 
 
 
 
 
1082
  }
1083
  } catch (err) {
1084
+ console.error(\`์ธ๋„ค์ผ ๋กœ๋“œ ์˜ค๋ฅ˜ (\${project.name}):\`, err);
1085
  }
1086
 
1087
  return null;
1088
  });
1089
 
 
1090
  const results = await Promise.all(thumbnailPromises);
1091
 
 
1092
  results.filter(result => result !== null).forEach(result => {
1093
  save(result.pages, result.name, result.isCached);
1094
  });
1095
 
1096
  hideLoading();
1097
 
 
1098
  if (document.querySelectorAll('.card').length === 0) {
1099
  $id('noProjects').style.display = 'block';
1100
  }
 
1119
  const pdfPath = projects[i][0].path;
1120
  const pdfName = pdfPath.split('/').pop().replace('.pdf', '');
1121
 
 
1122
  let badgeEl = cards[i].querySelector('.cached-status');
1123
 
1124
  if(cacheStatus[pdfName] && cacheStatus[pdfName].status === "completed") {
 
1138
  badgeEl.className = 'cached-status';
1139
  cards[i].querySelector('.card-inner')?.appendChild(badgeEl);
1140
  }
1141
+ badgeEl.textContent = \`\${cacheStatus[pdfName].progress}%\`;
1142
  badgeEl.style.background = 'var(--secondary-color)';
1143
  }
1144
  }
 
1147
  // ํ˜„์žฌ ๋กœ๋”ฉ ์ค‘์ธ PDF๊ฐ€ ์žˆ์œผ๋ฉด ์ƒํƒœ ํ™•์ธ
1148
  if (currentLoadingPdfPath && pageLoadingInterval) {
1149
  const pdfName = currentLoadingPdfPath.split('/').pop().replace('.pdf', '');
 
1150
  if (cacheStatus[pdfName]) {
1151
  const status = cacheStatus[pdfName].status;
1152
  const progress = cacheStatus[pdfName].progress || 0;
1153
 
1154
  if (status === "completed") {
 
1155
  clearInterval(pageLoadingInterval);
1156
  $id('loadingPages').style.display = 'none';
1157
  currentLoadingPdfPath = null;
 
 
1158
  refreshFlipBook();
1159
  } else if (status === "processing") {
 
1160
  $id('loadingPages').style.display = 'block';
1161
+ $id('loadingPagesCount').textContent = \`\${progress}%\`;
1162
  }
1163
  }
1164
  }
 
1165
  } catch(error) {
1166
  console.error('์บ์‹œ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:', error);
1167
  }
 
1172
  toggle(false);
1173
  const pages = projects[i];
1174
 
 
1175
  if(fb) {
1176
  fb.destroy();
1177
  viewer.innerHTML = '';
1178
  }
1179
 
 
1180
  if(pages[0].path) {
1181
  const pdfPath = pages[0].path;
 
 
1182
  let progressiveLoading = false;
1183
  currentLoadingPdfPath = pdfPath;
1184
 
 
1185
  if(pages[0].cached) {
 
1186
  showLoading("์บ์‹œ๋œ PDF ๋กœ๋”ฉ ์ค‘...");
 
1187
  try {
1188
+ const response = await fetch(\`/api/cached-pdf?path=\${encodeURIComponent(pdfPath)}\`);
1189
  const cachedData = await response.json();
1190
 
1191
  if(cachedData.status === "completed" && cachedData.pages) {
 
1194
  currentLoadingPdfPath = null;
1195
  return;
1196
  } else if(cachedData.status === "processing" && cachedData.pages && cachedData.pages.length > 0) {
 
1197
  hideLoading();
1198
  createFlipBook(cachedData.pages);
1199
  progressiveLoading = true;
 
 
1200
  startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1201
  }
1202
  } catch(error) {
1203
  console.error("์บ์‹œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ค๋ฅ˜:", error);
 
1204
  }
1205
  }
1206
 
1207
+ if(!progressiveLoading) {
 
1208
  showLoading("PDF ์ค€๋น„ ์ค‘...");
 
1209
  try {
1210
+ const response = await fetch(\`/api/pdf-content?path=\${encodeURIComponent(pdfPath)}\`);
1211
  const data = await response.json();
1212
 
 
1213
  if(data.redirect) {
1214
  const redirectRes = await fetch(data.redirect);
1215
  const cachedData = await redirectRes.json();
 
1220
  currentLoadingPdfPath = null;
1221
  return;
1222
  } else if(cachedData.status === "processing" && cachedData.pages && cachedData.pages.length > 0) {
 
1223
  hideLoading();
1224
  createFlipBook(cachedData.pages);
 
 
1225
  startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1226
  return;
1227
  }
1228
  }
1229
 
1230
+ const pdfResponse = await fetch(\`/api/pdf-content?path=\${encodeURIComponent(pdfPath)}\`);
 
1231
 
 
1232
  try {
1233
  const jsonData = await pdfResponse.clone().json();
1234
  if (jsonData.redirect) {
 
1238
  if(cachedData.pages && cachedData.pages.length > 0) {
1239
  hideLoading();
1240
  createFlipBook(cachedData.pages);
 
1241
  if(cachedData.status === "processing") {
1242
  startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1243
  } else {
 
1247
  }
1248
  }
1249
  } catch (e) {
1250
+ // JSON ํŒŒ์‹ฑ ์‹คํŒจ -> PDF ์›๋ณธ ๋ฐ์ดํ„ฐ๋กœ ์ฒ˜๋ฆฌ
1251
  }
1252
 
 
1253
  const pdfData = await pdfResponse.arrayBuffer();
 
 
1254
  const pdf = await pdfjsLib.getDocument({data: pdfData}).promise;
1255
  const pdfPages = [];
1256
 
1257
+ for(let p=1; p <= pdf.numPages; p++) {
1258
+ updateLoading(\`ํŽ˜์ด์ง€ ์ค€๋น„ ์ค‘... (\${p}/\${pdf.numPages})\`);
 
1259
  const pg = await pdf.getPage(p);
1260
  const vp = pg.getViewport({scale: 1});
1261
  const c = document.createElement('canvas');
1262
  c.width = vp.width;
1263
  c.height = vp.height;
 
1264
  await pg.render({canvasContext: c.getContext('2d'), viewport: vp}).promise;
1265
  pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()});
1266
  }
 
1267
  hideLoading();
1268
  createFlipBook(pdfPages);
1269
  currentLoadingPdfPath = null;
 
1270
  } catch(error) {
1271
+ console.error('PDF ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜:', error);
1272
  hideLoading();
1273
  showError("PDF๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1274
  currentLoadingPdfPath = null;
1275
  }
1276
  }
1277
  } else {
 
1278
  createFlipBook(pages);
1279
  currentLoadingPdfPath = null;
1280
  }
1281
  }
1282
 
 
1283
  function startProgressiveLoadingIndicator(progress, totalPages) {
 
1284
  $id('loadingPages').style.display = 'block';
1285
+ $id('loadingPagesCount').textContent = \`\${progress}%\`;
1286
 
 
1287
  if (pageLoadingInterval) {
1288
  clearInterval(pageLoadingInterval);
1289
  }
 
 
1290
  pageLoadingInterval = setInterval(async () => {
1291
  if (!currentLoadingPdfPath) {
1292
  clearInterval(pageLoadingInterval);
1293
  $id('loadingPages').style.display = 'none';
1294
  return;
1295
  }
 
1296
  try {
1297
+ const response = await fetch(\`/api/cache-status?path=\${encodeURIComponent(currentLoadingPdfPath)}\`);
1298
  const status = await response.json();
1299
 
 
1300
  if (status.status === "completed") {
1301
  clearInterval(pageLoadingInterval);
1302
  $id('loadingPages').style.display = 'none';
1303
+ refreshFlipBook();
1304
  currentLoadingPdfPath = null;
1305
  } else if (status.status === "processing") {
1306
+ $id('loadingPagesCount').textContent = \`\${status.progress}%\`;
1307
  }
1308
  } catch (e) {
1309
  console.error("์บ์‹œ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:", e);
 
1311
  }, 1000);
1312
  }
1313
 
 
1314
  async function refreshFlipBook() {
1315
  if (!currentLoadingPdfPath || !fb) return;
 
1316
  try {
1317
+ const response = await fetch(\`/api/cached-pdf?path=\${encodeURIComponent(currentLoadingPdfPath)}\`);
1318
  const cachedData = await response.json();
1319
 
1320
  if(cachedData.status === "completed" && cachedData.pages) {
 
1321
  fb.destroy();
1322
  viewer.innerHTML = '';
 
 
1323
  createFlipBook(cachedData.pages);
1324
  currentLoadingPdfPath = null;
1325
  }
 
1330
 
1331
  function createFlipBook(pages) {
1332
  console.log('FlipBook ์ƒ์„ฑ ์‹œ์ž‘. ํŽ˜์ด์ง€ ์ˆ˜:', pages.length);
 
1333
  try {
 
1334
  const calculateAspectRatio = () => {
1335
  const windowWidth = window.innerWidth;
1336
  const windowHeight = window.innerHeight;
1337
  const aspectRatio = windowWidth / windowHeight;
1338
 
 
1339
  let width, height;
1340
+ if (aspectRatio > 1) {
1341
  height = Math.min(windowHeight * 0.9, windowHeight - 40);
1342
+ width = height * aspectRatio * 0.8;
1343
  if (width > windowWidth * 0.9) {
1344
  width = windowWidth * 0.9;
1345
  height = width / (aspectRatio * 0.8);
1346
  }
1347
+ } else {
1348
  width = Math.min(windowWidth * 0.9, windowWidth - 40);
1349
+ height = width / aspectRatio * 0.9;
1350
  if (height > windowHeight * 0.9) {
1351
  height = windowHeight * 0.9;
1352
  width = height * aspectRatio * 0.9;
1353
  }
1354
  }
 
 
1355
  return {
1356
  width: Math.round(width),
1357
  height: Math.round(height)
1358
  };
1359
  };
1360
 
 
1361
  const size = calculateAspectRatio();
1362
  viewer.style.width = size.width + 'px';
1363
  viewer.style.height = size.height + 'px';
1364
 
 
1365
  const validPages = pages.map(page => {
 
1366
  if (!page || !page.src) {
1367
  return {
1368
  src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iIGZpbGw9IiM1NTUiPkxvYWRpbmcuLi48L3RleHQ+PC9zdmc+',
 
1378
  autoSize: true,
1379
  flipDuration: 800,
1380
  backgroundColor: '#fff',
 
1381
  sound: true,
1382
  assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'},
1383
  controlsProps: {
 
1392
  enableAnnotation: false,
1393
  enableSound: true,
1394
  enableLightbox: false,
1395
+ layout: 10,
1396
+ skin: 'light',
1397
+ autoNavigationTime: 3600,
1398
+ hideControls: false,
1399
+ paddingTop: 10,
1400
+ paddingLeft: 10,
1401
+ paddingRight: 10,
1402
+ paddingBottom: 10,
1403
+ pageTextureSize: 1024,
1404
+ thumbnails: true,
1405
+ autoHideControls: false,
1406
+ controlsTimeout: 8000
1407
  }
1408
  });
1409
 
 
1410
  window.addEventListener('resize', () => {
1411
  if (fb) {
1412
  const newSize = calculateAspectRatio();
 
1416
  }
1417
  });
1418
 
 
1419
  setTimeout(() => {
1420
  try {
 
1421
  const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar');
1422
  if (menuBars && menuBars.length > 0) {
1423
  menuBars.forEach(menuBar => {
 
1439
  }
1440
  }
1441
 
1442
+ $id('homeButton').onclick = () => {
1443
+ if(fb) {
1444
+ fb.destroy();
1445
+ viewer.innerHTML = '';
1446
+ fb = null;
1447
+ }
1448
+ toggle(true);
1449
+ if (pageLoadingInterval) {
1450
+ clearInterval(pageLoadingInterval);
1451
+ pageLoadingInterval = null;
1452
+ }
1453
+ $id('loadingPages').style.display = 'none';
1454
+ currentLoadingPdfPath = null;
1455
+ };
1456
 
1457
  function toggle(showHome){
1458
+ $id('home').style.display = showHome?'block':'none';
1459
+ $id('viewerPage').style.display = showHome?'none':'block';
1460
+ $id('homeButton').style.display = showHome?'none':'block';
1461
 
 
1462
  if(!showHome) {
1463
  document.body.classList.add('viewer-mode');
1464
  } else {
 
1466
  }
1467
  }
1468
 
 
1469
  function showLoading(message, progress = -1) {
 
1470
  hideLoading();
 
1471
  const loadingContainer = document.createElement('div');
1472
  loadingContainer.className = 'loading-container fade-in';
1473
  loadingContainer.id = 'loadingContainer';
 
1498
 
1499
  if (progress >= 0) {
1500
  let progressBar = $id('progressBar');
 
1501
  if (!progressBar) {
1502
  const loadingContainer = $id('loadingContainer');
1503
  if (loadingContainer) {
 
1508
  progressBar = $id('progressBar');
1509
  }
1510
  } else {
1511
+ progressBar.style.width = \`\${progress}%\`;
1512
  }
1513
  }
1514
  }
 
1521
  }
1522
 
1523
  function showError(message) {
 
1524
  const existingError = $id('errorContainer');
1525
  if (existingError) {
1526
  existingError.remove();
 
1536
 
1537
  document.body.appendChild(errorContainer);
1538
 
 
1539
  $id('errorCloseBtn').onclick = () => {
1540
  errorContainer.remove();
1541
  };
1542
 
 
1543
  setTimeout(() => {
1544
  if ($id('errorContainer')) {
1545
  $id('errorContainer').remove();
 
1547
  }, 5000);
1548
  }
1549
 
1550
+ window.addEventListener('DOMContentLoaded', () => {
1551
+ // ์—…๋กœ๋“œ ๋ฒ„ํŠผ์ด ์ •์ƒ ๋™์ž‘ํ•˜๋„๋ก ์ง์ ‘ ์ด๋ฒคํŠธ ์„ค์ •
1552
+ setupDirectEvents();
1553
+
1554
+ loadServerPDFs();
1555
+
1556
+ // ์บ์‹œ ์ƒํƒœ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ™•์ธ
1557
+ setInterval(checkCacheStatus, 3000);
1558
+ });
1559
  </script>
1560
  </body>
1561
  </html>
 
1566
  return get_html_content()
1567
 
1568
  if __name__ == "__main__":
1569
+ uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))