benjamin5607 commited on
Commit
3a8b8da
·
verified ·
1 Parent(s): b6b5daf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -25
app.py CHANGED
@@ -14,18 +14,20 @@ st.markdown("""
14
  </style>
15
  """, unsafe_allow_html=True)
16
 
17
- # 2. 데이터 로드 (파일 분리)
18
  try:
19
  with open('places.json', 'r', encoding='utf-8') as f:
20
  PLACE_DB = json.load(f)
21
  except FileNotFoundError:
22
- st.error("🚨 'places.json' 파일을 찾을 수 없습니다. 같은 폴더에 넣어주세요!")
23
- st.stop()
 
 
 
24
 
25
- # 데이터를 JSON 문자열로 변환 (자바스크립트용)
26
  db_json = json.dumps(PLACE_DB)
27
 
28
- # 4. HTML/JS 전체 코드 (Puter.js 엔진 탑재)
29
  html_code = f"""
30
  <!DOCTYPE html>
31
  <html>
@@ -157,13 +159,59 @@ html_code = f"""
157
  let selectedCountry = "";
158
  let selectedType = "All";
159
  let currentLang = "KO";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  const TRANSLATIONS = {{
162
  "KO": {{
163
  "subtitle": "글로벌 주류 헤리티지 투어",
164
  "filters": {{ "All": "전체", "Whiskey": "위스키", "Spirits": "증류주", "Beer": "맥주", "Wine": "와인", "Traditional": "전통주/토속주", "Bar": "바/펍" }},
165
  "report_header": "여행 리포트",
166
- "loader": "Puter AI가 리포트를<br>작성하고 있습니다...",
167
  "gmap": "구글맵에서 길찾기",
168
  "nmap": "네이버 지도에서 길찾기",
169
  "cta": "AI 리뷰 보기 →",
@@ -173,7 +221,6 @@ html_code = f"""
173
  "subtitle": "GLOBAL ALCOHOL HERITAGE TOUR",
174
  "filters": {{ "All": "All", "Whiskey": "Whiskey", "Spirits": "Spirits", "Beer": "Beer", "Wine": "Wine", "Traditional": "Traditional", "Bar": "Bar" }},
175
  "report_header": "TRAVEL REPORT",
176
- "loader": "Puter AI is summarizing...",
177
  "gmap": "Open in Google Maps",
178
  "nmap": "Open in Naver Map",
179
  "cta": "Read Review →",
@@ -183,7 +230,6 @@ html_code = f"""
183
  "subtitle": "世界のお酒ヘリテージツアー",
184
  "filters": {{ "All": "すべて", "Whiskey": "ウイスキー", "Spirits": "蒸留酒", "Beer": "ビール", "Wine": "ワイン", "Traditional": "伝統酒", "Bar": "バー" }},
185
  "report_header": "旅行レポート",
186
- "loader": "AIが要約中...",
187
  "gmap": "Googleマップで開く",
188
  "nmap": "Naverマップで開く",
189
  "cta": "レビューを見る →",
@@ -193,7 +239,6 @@ html_code = f"""
193
  "subtitle": "全球酒类遗产之旅",
194
  "filters": {{ "All": "全部", "Whiskey": "威士忌", "Spirits": "烈酒", "Beer": "啤酒", "Wine": "葡萄酒", "Traditional": "传统酒", "Bar": "酒吧" }},
195
  "report_header": "旅游报告",
196
- "loader": "AI正在总结...",
197
  "gmap": "在谷歌地图中打开",
198
  "nmap": "在Naver地图中打开",
199
  "cta": "查看评论 →",
@@ -203,7 +248,6 @@ html_code = f"""
203
 
204
  window.onload = function() {{
205
  initCountryDropdown();
206
- // Default select
207
  const keys = Object.keys(PLACE_DB).sort();
208
  const defaultKey = keys.find(k => k.includes("Korea")) || keys[0];
209
  document.getElementById('country-select').value = defaultKey;
@@ -223,7 +267,6 @@ html_code = f"""
223
  const t = TRANSLATIONS[currentLang];
224
  document.getElementById('app-subtitle').innerText = t.subtitle;
225
  document.getElementById('report-header').innerText = t.report_header;
226
- document.getElementById('loader-msg').innerHTML = t.loader;
227
 
228
  document.getElementById('filter-all').innerText = `🌐 ${{t.filters.All}}`;
229
  document.getElementById('filter-whiskey').innerText = `🥃 ${{t.filters.Whiskey}}`;
@@ -294,12 +337,14 @@ html_code = f"""
294
  async function openDetail(place) {{
295
  document.getElementById('main-page').style.transform = 'translateX(-30%)';
296
  document.getElementById('detail-page').style.transform = 'translateX(0%)';
297
- document.getElementById('loader').style.display = 'flex';
 
 
298
 
299
  document.getElementById('repo-title').innerText = place.name;
300
  document.getElementById('repo-meta').innerText = `${{place.label}} • ${{selectedCountry}}`;
301
 
302
- // 🔥 Map Logic: Korea -> Naver, Others -> Google
303
  const btn = document.getElementById('gmap-link');
304
  const isKorea = selectedCountry.includes("Korea");
305
  const query = encodeURIComponent(place.name);
@@ -311,7 +356,7 @@ html_code = f"""
311
  }} else {{
312
  btn.innerText = TRANSLATIONS[currentLang].gmap;
313
  btn.classList.remove("naver");
314
- btn.href = `https://www.google.com/maps/search/?api=1&query=$${{query}}%20${{selectedCountry}}`;
315
  }}
316
 
317
  document.getElementById('repo-content').innerHTML = "";
@@ -333,6 +378,27 @@ html_code = f"""
333
  await generatePuterReport(place, fallbackDesc);
334
  }}
335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  async function generatePuterReport(place, fallbackDesc) {{
337
  let langFull = "Korean";
338
  if(currentLang === "EN") langFull = "English";
@@ -340,16 +406,18 @@ html_code = f"""
340
  if(currentLang === "CN") langFull = "Chinese";
341
 
342
  const prompt = `
343
- You are a travel guide. Write a **concise 3-bullet point summary** about "${{place.name}}" in ${{selectedCountry}}.
344
- 1. History/Fact
345
- 2. Taste/Drink
346
- 3. Tip
 
 
 
347
  Language: ${{langFull}}.
348
- Do not use introduction or conclusion. Just the points.
349
  `;
350
 
351
  try {{
352
- // Puter.js 호출 (최대 3회 재시도)
353
  const response = await retryPuter(prompt, 3);
354
 
355
  let text = "";
@@ -359,9 +427,8 @@ html_code = f"""
359
  text = response.message.content;
360
  }}
361
 
362
- document.getElementById('loader').style.display = 'none';
363
 
364
- // 타이핑 효과 제거 -> 즉시 렌더링
365
  const el = document.getElementById('repo-content');
366
  el.innerHTML = text.replace(/\\n/g, '<br>');
367
 
@@ -371,7 +438,6 @@ html_code = f"""
371
  }}
372
  }}
373
 
374
- // 재시도 로직 함수
375
  async function retryPuter(prompt, retries) {{
376
  for (let i = 0; i < retries; i++) {{
377
  try {{
@@ -384,9 +450,9 @@ html_code = f"""
384
  }}
385
 
386
  function generateLocalReport(place, dbDesc) {{
387
- document.getElementById('loader').style.display = 'none';
388
  const report = `
389
- ✨ **[Offline Mode] ${{TRANSLATIONS[currentLang].ai_error}}**
390
 
391
  **${{place.name}}**
392
 
 
14
  </style>
15
  """, unsafe_allow_html=True)
16
 
17
+ # 2. 데이터 로드 (places.json 파일이 같은 폴더에 있어야 합니다)
18
  try:
19
  with open('places.json', 'r', encoding='utf-8') as f:
20
  PLACE_DB = json.load(f)
21
  except FileNotFoundError:
22
+ # 파일이 없을 경우를 대비한 비상용 더미 데이터 (에러 방지)
23
+ PLACE_DB = {
24
+ "South Korea 🇰🇷": [{"name": "Zest", "category": "Bar", "label": "🍸", "lat": 37.526, "lon": 127.040, "desc_ko": "데이터 파일을 확인해주세요.", "desc_en": "Check json file."}]
25
+ }
26
+ st.error("🚨 'places.json' 파일을 찾을 수 없습니다. V47.0 데이터를 저장해주세요!")
27
 
 
28
  db_json = json.dumps(PLACE_DB)
29
 
30
+ # 3. HTML/JS 전체 코드
31
  html_code = f"""
32
  <!DOCTYPE html>
33
  <html>
 
159
  let selectedCountry = "";
160
  let selectedType = "All";
161
  let currentLang = "KO";
162
+ let loadingInterval = null;
163
+
164
+ // 🔥 Dynamic Loading Messages
165
+ const LOADING_MSGS = {{
166
+ "KO": [
167
+ "AI가 비행기 타고 이동 중... ✈️",
168
+ "목적지 공항에 도착했습니다... 🛬",
169
+ "현지 교통편으로 이동 중... 🚕",
170
+ "가게 문을 열고 들어갑니다... 🚪",
171
+ "주변 분위기를 스캔하는 중... 👀",
172
+ "시그니처 메뉴를 시음 중... 🍷",
173
+ "사장님과 인터뷰 중... 🗣️",
174
+ "취하기 전에 리포트 작성 중... 📝",
175
+ "거의 다 됐습니다! 🚀"
176
+ ],
177
+ "EN": [
178
+ "AI flying to the location... ✈️",
179
+ "Arrived at the airport... 🛬",
180
+ "Taking a taxi to the venue... 🚕",
181
+ "Entering the door... 🚪",
182
+ "Scanning the vibes... 👀",
183
+ "Tasting signature drinks... 🍷",
184
+ "Interviewing the bartender... 🗣️",
185
+ "Writing report before getting drunk... 📝",
186
+ "Almost ready! 🚀"
187
+ ],
188
+ "JP": [
189
+ "AIが飛行機で移動中... ✈️",
190
+ "現地に到着しました... 🛬",
191
+ "お店のドアを開けています... 🚪",
192
+ "雰囲気をスキャン中... 👀",
193
+ "シグネチャーを試飲中... 🍷",
194
+ "マスターとインタビュー中... 🗣️",
195
+ "酔う前にレポート作成中... 📝",
196
+ "もうすぐ完成します! 🚀"
197
+ ],
198
+ "CN": [
199
+ "AI正在飞往目的地... ✈️",
200
+ "已到达当地机场... 🛬",
201
+ "正在进入店铺... 🚪",
202
+ "正在扫描氛围... 👀",
203
+ "品尝招牌酒饮中... 🍷",
204
+ "正在采访调酒师... 🗣️",
205
+ "趁没醉赶紧写报告... 📝",
206
+ "马上就好! 🚀"
207
+ ]
208
+ }};
209
 
210
  const TRANSLATIONS = {{
211
  "KO": {{
212
  "subtitle": "글로벌 주류 헤리티지 투어",
213
  "filters": {{ "All": "전체", "Whiskey": "위스키", "Spirits": "증류주", "Beer": "맥주", "Wine": "와인", "Traditional": "전통주/토속주", "Bar": "바/펍" }},
214
  "report_header": "여행 리포트",
 
215
  "gmap": "구글맵에서 길찾기",
216
  "nmap": "네이버 지도에서 길찾기",
217
  "cta": "AI 리뷰 보기 →",
 
221
  "subtitle": "GLOBAL ALCOHOL HERITAGE TOUR",
222
  "filters": {{ "All": "All", "Whiskey": "Whiskey", "Spirits": "Spirits", "Beer": "Beer", "Wine": "Wine", "Traditional": "Traditional", "Bar": "Bar" }},
223
  "report_header": "TRAVEL REPORT",
 
224
  "gmap": "Open in Google Maps",
225
  "nmap": "Open in Naver Map",
226
  "cta": "Read Review →",
 
230
  "subtitle": "世界のお酒ヘリテージツアー",
231
  "filters": {{ "All": "すべて", "Whiskey": "ウイスキー", "Spirits": "蒸留酒", "Beer": "ビール", "Wine": "ワイン", "Traditional": "伝統酒", "Bar": "バー" }},
232
  "report_header": "旅行レポート",
 
233
  "gmap": "Googleマップで開く",
234
  "nmap": "Naverマップで開く",
235
  "cta": "レビューを見る →",
 
239
  "subtitle": "全球酒类遗产之旅",
240
  "filters": {{ "All": "全部", "Whiskey": "威士忌", "Spirits": "烈酒", "Beer": "啤酒", "Wine": "葡萄酒", "Traditional": "传统酒", "Bar": "酒吧" }},
241
  "report_header": "旅游报告",
 
242
  "gmap": "在谷歌地图中打开",
243
  "nmap": "在Naver地图中打开",
244
  "cta": "查看评论 →",
 
248
 
249
  window.onload = function() {{
250
  initCountryDropdown();
 
251
  const keys = Object.keys(PLACE_DB).sort();
252
  const defaultKey = keys.find(k => k.includes("Korea")) || keys[0];
253
  document.getElementById('country-select').value = defaultKey;
 
267
  const t = TRANSLATIONS[currentLang];
268
  document.getElementById('app-subtitle').innerText = t.subtitle;
269
  document.getElementById('report-header').innerText = t.report_header;
 
270
 
271
  document.getElementById('filter-all').innerText = `🌐 ${{t.filters.All}}`;
272
  document.getElementById('filter-whiskey').innerText = `🥃 ${{t.filters.Whiskey}}`;
 
337
  async function openDetail(place) {{
338
  document.getElementById('main-page').style.transform = 'translateX(-30%)';
339
  document.getElementById('detail-page').style.transform = 'translateX(0%)';
340
+
341
+ // Start Dynamic Loading
342
+ startLoadingAnimation();
343
 
344
  document.getElementById('repo-title').innerText = place.name;
345
  document.getElementById('repo-meta').innerText = `${{place.label}} • ${{selectedCountry}}`;
346
 
347
+ // Map Logic
348
  const btn = document.getElementById('gmap-link');
349
  const isKorea = selectedCountry.includes("Korea");
350
  const query = encodeURIComponent(place.name);
 
356
  }} else {{
357
  btn.innerText = TRANSLATIONS[currentLang].gmap;
358
  btn.classList.remove("naver");
359
+ btn.href = `https://www.google.com/maps/search/?api=1&query=${{query}}%20${{selectedCountry}}`;
360
  }}
361
 
362
  document.getElementById('repo-content').innerHTML = "";
 
378
  await generatePuterReport(place, fallbackDesc);
379
  }}
380
 
381
+ function startLoadingAnimation() {{
382
+ const loader = document.getElementById('loader');
383
+ loader.style.display = 'flex';
384
+
385
+ const msgs = LOADING_MSGS[currentLang];
386
+ let step = 0;
387
+ document.getElementById('loader-msg').innerText = msgs[0];
388
+
389
+ if (loadingInterval) clearInterval(loadingInterval);
390
+
391
+ loadingInterval = setInterval(() => {{
392
+ step = (step + 1) % msgs.length;
393
+ document.getElementById('loader-msg').innerText = msgs[step];
394
+ }}, 1500); // Change text every 1.5 seconds
395
+ }}
396
+
397
+ function stopLoadingAnimation() {{
398
+ if (loadingInterval) clearInterval(loadingInterval);
399
+ document.getElementById('loader').style.display = 'none';
400
+ }}
401
+
402
  async function generatePuterReport(place, fallbackDesc) {{
403
  let langFull = "Korean";
404
  if(currentLang === "EN") langFull = "English";
 
406
  if(currentLang === "CN") langFull = "Chinese";
407
 
408
  const prompt = `
409
+ Act as a travel expert. Write a detailed review about "${{place.name}}" in ${{selectedCountry}}.
410
+ Structure:
411
+ 1. 📍 **Address**: (Find approximate address)
412
+ 2. ⭐ **Vibe**: (Atmosphere)
413
+ 3. 🍷 **Drink**: (Signature drink & process)
414
+ 4. ✨ **Tip**: (Best time to visit)
415
+
416
  Language: ${{langFull}}.
417
+ Format: Clean Markdown.
418
  `;
419
 
420
  try {{
 
421
  const response = await retryPuter(prompt, 3);
422
 
423
  let text = "";
 
427
  text = response.message.content;
428
  }}
429
 
430
+ stopLoadingAnimation();
431
 
 
432
  const el = document.getElementById('repo-content');
433
  el.innerHTML = text.replace(/\\n/g, '<br>');
434
 
 
438
  }}
439
  }}
440
 
 
441
  async function retryPuter(prompt, retries) {{
442
  for (let i = 0; i < retries; i++) {{
443
  try {{
 
450
  }}
451
 
452
  function generateLocalReport(place, dbDesc) {{
453
+ stopLoadingAnimation();
454
  const report = `
455
+ ✨ **[Offline Info] ${{TRANSLATIONS[currentLang].ai_error}}**
456
 
457
  **${{place.name}}**
458