Add admin features, responsive UI, user reviews, visit stats, and channel-colored markers
- Admin: video management with Google Maps match status, manual restaurant mapping, restaurant remap on name change - Admin: user management tab with favorites/reviews detail - Admin: channel deletion fix for IDs with slashes - Frontend: responsive mobile layout (map top, list bottom, 2-row header) - Frontend: channel-colored map markers with legend - Frontend: my reviews list, favorites toggle, visit counter overlay - Frontend: force light mode for dark theme devices - Backend: visit tracking (site_visits table), user reviews endpoint - Backend: bulk transcript/extract streaming, geocode key fixes - Nginx config for production deployment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ def _parse_json(raw: str) -> dict | list:
|
||||
return json.JSONDecoder(strict=False).decode(raw)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
# recover truncated array
|
||||
# recover truncated array — extract complete objects one by one
|
||||
if raw.lstrip().startswith("["):
|
||||
decoder = json.JSONDecoder(strict=False)
|
||||
items: list = []
|
||||
@@ -71,8 +71,19 @@ def _parse_json(raw: str) -> dict | list:
|
||||
items.append(obj)
|
||||
idx = end
|
||||
except json.JSONDecodeError:
|
||||
# Try to recover truncated last object by closing braces
|
||||
remainder = raw[idx:]
|
||||
for fix in ["}", "}]", '"}', '"}' , '"}]', "null}", "null}]"]:
|
||||
try:
|
||||
patched = remainder.rstrip().rstrip(",") + fix
|
||||
obj = json.loads(patched)
|
||||
if isinstance(obj, dict) and obj.get("name"):
|
||||
items.append(obj)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
continue
|
||||
break
|
||||
if items:
|
||||
logger.info("Recovered %d restaurants from truncated JSON", len(items))
|
||||
return items
|
||||
raise ValueError(f"JSON parse failed: {raw[:80]!r}")
|
||||
|
||||
@@ -104,7 +115,7 @@ _EXTRACT_PROMPT = """\
|
||||
JSON 배열:"""
|
||||
|
||||
|
||||
def extract_restaurants(title: str, transcript: str) -> tuple[list[dict], str]:
|
||||
def extract_restaurants(title: str, transcript: str, custom_prompt: str | None = None) -> tuple[list[dict], str]:
|
||||
"""Extract restaurant info from a video transcript using LLM.
|
||||
|
||||
Returns (list of restaurant dicts, raw LLM response text).
|
||||
@@ -113,10 +124,11 @@ def extract_restaurants(title: str, transcript: str) -> tuple[list[dict], str]:
|
||||
if len(transcript) > 8000:
|
||||
transcript = transcript[:7000] + "\n...(중략)...\n" + transcript[-1000:]
|
||||
|
||||
prompt = _EXTRACT_PROMPT.format(title=title, transcript=transcript)
|
||||
template = custom_prompt if custom_prompt else _EXTRACT_PROMPT
|
||||
prompt = template.format(title=title, transcript=transcript)
|
||||
|
||||
try:
|
||||
raw = _llm(prompt, max_tokens=4096)
|
||||
raw = _llm(prompt, max_tokens=8192)
|
||||
result = _parse_json(raw)
|
||||
if isinstance(result, list):
|
||||
return result, raw
|
||||
|
||||
Reference in New Issue
Block a user