sxandie commited on
Commit
4a8eec7
Β·
1 Parent(s): 686f86c

Implement customizable post-trek storyteller styles (Minimal Technical Gist & Social Media Post) with robust mock parser fallback

Browse files
Files changed (2) hide show
  1. app.py +23 -2
  2. src/llm.py +156 -0
app.py CHANGED
@@ -1039,7 +1039,7 @@ def handle_clear_journal():
1039
  db.clear_journal_logs()
1040
  return "", [], "<span style='color:#ef4444;'>Cleared all voice journal logs.</span>"
1041
 
1042
- def handle_generate_story(route_state_val):
1043
  logs = db.get_journal_entries()
1044
  if not logs:
1045
  return "### πŸ“– No Voice Logs Found\n\nPlease record and save some voice journal entries during your simulated trek before generating your AI story!", None
@@ -1084,10 +1084,26 @@ def handle_generate_story(route_state_val):
1084
  else:
1085
  amenities_text += "- General alpine huts, shelters, and water streams close to the path.\n"
1086
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1087
  system_prompt = (
1088
  "You are a classic wilderness novelist and explorer. Write a compelling, first-person "
1089
  "adventure story summarizing the trek based on the provided trek details, checkpoints, amenities, "
1090
  "and the hiker's voice journal logs.\n"
 
1091
  "Emphasize the hiker's voice notes, detailing their personal reflections, physical state, and "
1092
  "wilderness observations. Incorporate the trek details (distance, elevation, altitude) to frame the physical challenge. "
1093
  "Weave in the amenities (water sources, campsites, alpine huts, shelters, viewpoints) as milestones or locations where the hiker is resting, "
@@ -1308,6 +1324,11 @@ with gr.Blocks(css="assets/custom.css", title="Trailhead β€” Tactical Trail Comp
1308
  with gr.Column(scale=2):
1309
  gr.Markdown("## πŸ“– Post-Trek AI Storyteller")
1310
  gr.Markdown("Click below to compile all your saved voice journal logs and route statistics into an AI-narrated story of your adventure!")
 
 
 
 
 
1311
  generate_story_btn = gr.Button("🎬 Generate AI Trek Story", variant="primary")
1312
  story_output = gr.Markdown(value="*Your adventure narrative will be generated here.*")
1313
  with gr.Column(scale=1):
@@ -1422,7 +1443,7 @@ with gr.Blocks(css="assets/custom.css", title="Trailhead β€” Tactical Trail Comp
1422
 
1423
  generate_story_btn.click(
1424
  fn=handle_generate_story,
1425
- inputs=[route_state],
1426
  outputs=[story_output, story_download]
1427
  )
1428
 
 
1039
  db.clear_journal_logs()
1040
  return "", [], "<span style='color:#ef4444;'>Cleared all voice journal logs.</span>"
1041
 
1042
+ def handle_generate_story(route_state_val, style):
1043
  logs = db.get_journal_entries()
1044
  if not logs:
1045
  return "### πŸ“– No Voice Logs Found\n\nPlease record and save some voice journal entries during your simulated trek before generating your AI story!", None
 
1084
  else:
1085
  amenities_text += "- General alpine huts, shelters, and water streams close to the path.\n"
1086
 
1087
+ if style == "Minimal Technical Gist":
1088
+ style_instruction = (
1089
+ "Write a concise, bullet-pointed, and highly technical summary of the trek. "
1090
+ "Focus on the exact telemetry (distances, altitudes, checkpoints reached), voice note transcripts, "
1091
+ "and amenities used (water sources, huts, campsites, viewpoints). Keep it factual, objective, and brief."
1092
+ )
1093
+ else: # "Social Media Post (Elaborative)"
1094
+ style_instruction = (
1095
+ "Write a highly engaging, elaborative, and inspiring story formatted as a social media post (e.g., for Instagram or LinkedIn) "
1096
+ "targeted at an audience of outdoor enthusiasts.\n"
1097
+ "Include emojis, a narrative hook, paragraphs of descriptions of the journey's highs and lows, "
1098
+ "reflections on the voice notes, details about the amenities (water sources, viewpoints, campsites) encountered, "
1099
+ "and end with relevant hashtags (e.g., #HikingAdventurer, #Trailhead, #BackcountryExploration)."
1100
+ )
1101
+
1102
  system_prompt = (
1103
  "You are a classic wilderness novelist and explorer. Write a compelling, first-person "
1104
  "adventure story summarizing the trek based on the provided trek details, checkpoints, amenities, "
1105
  "and the hiker's voice journal logs.\n"
1106
+ f"Format Style: {style_instruction}\n"
1107
  "Emphasize the hiker's voice notes, detailing their personal reflections, physical state, and "
1108
  "wilderness observations. Incorporate the trek details (distance, elevation, altitude) to frame the physical challenge. "
1109
  "Weave in the amenities (water sources, campsites, alpine huts, shelters, viewpoints) as milestones or locations where the hiker is resting, "
 
1324
  with gr.Column(scale=2):
1325
  gr.Markdown("## πŸ“– Post-Trek AI Storyteller")
1326
  gr.Markdown("Click below to compile all your saved voice journal logs and route statistics into an AI-narrated story of your adventure!")
1327
+ story_style = gr.Radio(
1328
+ choices=["Minimal Technical Gist", "Social Media Post (Elaborative)"],
1329
+ value="Social Media Post (Elaborative)",
1330
+ label="Story Style / Format"
1331
+ )
1332
  generate_story_btn = gr.Button("🎬 Generate AI Trek Story", variant="primary")
1333
  story_output = gr.Markdown(value="*Your adventure narrative will be generated here.*")
1334
  with gr.Column(scale=1):
 
1443
 
1444
  generate_story_btn.click(
1445
  fn=handle_generate_story,
1446
+ inputs=[route_state, story_style],
1447
  outputs=[story_output, story_download]
1448
  )
1449
 
src/llm.py CHANGED
@@ -221,6 +221,162 @@ def generate_mock(prompt, system="", image_path=None, audio_path=None, history=N
221
 
222
  prompt_lower = prompt.lower()
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  # 1. Checkpoint / Narration Queries
225
  if "checkpoint" in prompt_lower or "narration" in prompt_lower or "current position" in prompt_lower:
226
  response += (
 
221
 
222
  prompt_lower = prompt.lower()
223
 
224
+ # 0.5 Check if this is a Storyteller request (before other keyword matches)
225
+ if "first-person adventure story of my trek" in prompt_lower or "storyteller" in system.lower() or "adventure story" in system.lower():
226
+ import re
227
+
228
+ # Parse stats
229
+ total_dist_match = re.search(r"Total Distance: ([\d\.]+) km", prompt)
230
+ ele_gain_match = re.search(r"Total Elevation Gain: ([\d\.]+) m", prompt)
231
+ alt_range_match = re.search(r"Altitude Range: (.*?)\n", prompt)
232
+
233
+ total_dist = total_dist_match.group(1) if total_dist_match else "3.49"
234
+ ele_gain = ele_gain_match.group(1) if ele_gain_match else "120.0"
235
+ alt_range = alt_range_match.group(1) if alt_range_match else "100m - 250m"
236
+
237
+ # Parse voice logs
238
+ voice_logs = []
239
+ log_pattern = r"- Log #(\d+)\s+\((.*?)\)\s+at Km\s+([\d\.]+)\s+\(Alt:\s+([\d\.]+)m\):\s*\"(.*?)\""
240
+ matches = re.findall(log_pattern, prompt, re.DOTALL)
241
+ for num, timestamp, km, alt, transcript in matches:
242
+ voice_logs.append({
243
+ "num": num,
244
+ "time": timestamp,
245
+ "km": float(km),
246
+ "alt": alt,
247
+ "transcript": transcript.strip()
248
+ })
249
+
250
+ if not voice_logs:
251
+ # Fallback line-by-line parsing
252
+ lines = prompt.split("\n")
253
+ current_log = None
254
+ for line in lines:
255
+ if "- Log #" in line:
256
+ try:
257
+ parts = line.split(" at Km ")
258
+ header_part = parts[0]
259
+ km_alt_part = parts[1]
260
+ num_time = header_part.replace("- Log #", "").strip()
261
+ num = num_time.split(" ")[0]
262
+ time_str = num_time.replace(num, "").strip("() ")
263
+ km = km_alt_part.split(" ")[0]
264
+ alt = km_alt_part.split("Alt: ")[1].split("m")[0]
265
+ current_log = {
266
+ "num": num,
267
+ "time": time_str,
268
+ "km": float(km),
269
+ "alt": alt,
270
+ "transcript": ""
271
+ }
272
+ except Exception:
273
+ current_log = None
274
+ elif current_log and line.strip().startswith('"'):
275
+ current_log["transcript"] = line.strip().strip('"')
276
+ voice_logs.append(current_log)
277
+ current_log = None
278
+
279
+ # Parse amenities
280
+ amenities = []
281
+ amenity_pattern = r"- (.*?)\s+\((.*?)\)\s+at approx\.\s+Km\s+([\d\.]+)\s+\(located\s+([\d\.]+) meters off the trail\)"
282
+ amenity_matches = re.findall(amenity_pattern, prompt)
283
+ for name, type_str, km, offset in amenity_matches:
284
+ amenities.append({
285
+ "name": name,
286
+ "type": type_str,
287
+ "km": float(km),
288
+ "offset": offset
289
+ })
290
+
291
+ if not amenities:
292
+ lines = prompt.split("\n")
293
+ for line in lines:
294
+ if "meters off the trail" in line:
295
+ try:
296
+ clean_line = line.strip().lstrip("- ")
297
+ name_part = clean_line.split(" (")[0]
298
+ rest = clean_line.split(" (")[1]
299
+ type_part = rest.split(") at approx. Km ")[0]
300
+ km_offset = rest.split(") at approx. Km ")[1]
301
+ km = km_offset.split(" (located ")[0]
302
+ offset = km_offset.split(" (located ")[1].split(" meters off the trail")[0]
303
+ amenities.append({
304
+ "name": name_part,
305
+ "type": type_part,
306
+ "km": float(km),
307
+ "offset": offset
308
+ })
309
+ except Exception:
310
+ pass
311
+
312
+ voice_logs = sorted(voice_logs, key=lambda x: x["km"])
313
+ is_technical = "technical" in system.lower()
314
+
315
+ if is_technical:
316
+ response += "🧭 **Trailhead Technical Trek Report**\n"
317
+ response += "*Compiled by Trailhead AI Storyteller*\n\n"
318
+ response += "### πŸ“Š Trek Telemetry\n"
319
+ response += f"- **Total Distance:** {total_dist} km\n"
320
+ response += f"- **Total Elevation Gain:** {ele_gain} m\n"
321
+ response += f"- **Altitude Profile:** {alt_range}\n\n"
322
+
323
+ response += "### πŸŽ’ Amenities & Points of Interest\n"
324
+ if amenities:
325
+ for am in amenities:
326
+ response += f"- **{am['name']}** ({am['type']}) at approx. Km {am['km']:.2f} ({am['offset']}m off-trail)\n"
327
+ else:
328
+ response += "- No significant amenities detected along the route.\n"
329
+
330
+ response += "\n### πŸŽ™οΈ Geotagged Voice Logs\n"
331
+ if voice_logs:
332
+ for log in voice_logs:
333
+ response += f"- **Km {log['km']:.2f}** (Alt: {log['alt']}m) | *{log['time']}*:\n > \"{log['transcript']}\"\n"
334
+ else:
335
+ response += "- No voice logs recorded.\n"
336
+ else:
337
+ response += "🌲 **MY WILDERNESS EXPEDITION REPORT** 🌲\n"
338
+ response += "*Powered by Trailhead Tactical Trail Computer*\n\n"
339
+ response += f"What an absolute journey! πŸ”οΈ Just finished an intense trek covering **{total_dist} km** with **{ele_gain} m** of vertical climb! Here is the live play-by-play of how it went down:\n\n"
340
+
341
+ milestones = []
342
+ for am in amenities:
343
+ milestones.append(("amenity", am["km"], am))
344
+ for log in voice_logs:
345
+ milestones.append(("log", log["km"], log))
346
+ milestones = sorted(milestones, key=lambda x: x[1])
347
+
348
+ for m_type, km, data in milestones:
349
+ if m_type == "amenity":
350
+ response += f"πŸ“ **Km {km:.2f} | Amenity Spot** πŸŽ’\n"
351
+ response += f"Encountered **{data['name']}** ({data['type']}) situated just {data['offset']}m off the path. A crucial waypoint for resource management!\n\n"
352
+ elif m_type == "log":
353
+ transcript_lower = data['transcript'].lower()
354
+ icon = "πŸŽ™οΈ"
355
+ title = "Hiker Log"
356
+ if "water" in transcript_lower:
357
+ icon = "πŸ’§"
358
+ title = "Water Source & Hydration Check"
359
+ elif "view" in transcript_lower or "point of view" in transcript_lower:
360
+ icon = "πŸ‘οΈ"
361
+ title = "Scenic Viewpoint Reflection"
362
+ elif "finish" in transcript_lower or "complete" in transcript_lower:
363
+ icon = "🏁"
364
+ title = "Trek Completion Signoff"
365
+
366
+ response += f"{icon} **Km {km:.2f} | {title}** πŸ“\n"
367
+ response += f"Recorded voice entry at {data['alt']}m altitude:\n"
368
+ response += f"> *\"{data['transcript']}\"*\n\n"
369
+
370
+ response += "🏁 **Trek Complete!**\n"
371
+ response += "Every step was worth it. Pushed my limits, managed my resources, and conquered the route. πŸ₯Ύ\n\n"
372
+ response += "---\n"
373
+ response += "#HikingAdventure #BackcountryExploration #TrailheadAI #WildernessLiving #TrekTelemetry #OptOutside\n"
374
+
375
+ for word in response.split(" "):
376
+ yield word + " "
377
+ time.sleep(0.02)
378
+ return
379
+
380
  # 1. Checkpoint / Narration Queries
381
  if "checkpoint" in prompt_lower or "narration" in prompt_lower or "current position" in prompt_lower:
382
  response += (