Spaces:
Build error
Build error
feat: unified Wilderness AI chatbot & 1s GPS updates with proximity highlights
Browse files- README.md +14 -9
- app.py +40 -32
- assets/custom.css +5 -0
- hackathon_submission_article.md +6 -3
- requirements.txt +6 -6
- src/llm.py +34 -24
README.md
CHANGED
|
@@ -60,15 +60,20 @@ graph TD
|
|
| 60 |
* **Live HUD Dashboard:** Telemetry tracking route completion percentage, cumulative distance hiked, current altitude, and next-checkpoint ETA.
|
| 61 |
* **Offline Proximity Alerts:** Audio-visual indicators triggered automatically when the hiker is within 150m of any filtered POI.
|
| 62 |
|
| 63 |
-
### 3.
|
| 64 |
-
* **
|
| 65 |
-
* **
|
| 66 |
-
* **
|
| 67 |
-
* **
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
* **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
---
|
| 74 |
|
|
|
|
| 60 |
* **Live HUD Dashboard:** Telemetry tracking route completion percentage, cumulative distance hiked, current altitude, and next-checkpoint ETA.
|
| 61 |
* **Offline Proximity Alerts:** Audio-visual indicators triggered automatically when the hiker is within 150m of any filtered POI.
|
| 62 |
|
| 63 |
+
### 3. Unified Wilderness Guide & First-Aid AI
|
| 64 |
+
* **Unified Chatbot Interface:** Merges the **Wilderness Guide AI** and **Wilderness First-Aid manual** query engine into a single chatbot interface.
|
| 65 |
+
* **Emergency Quick-Lookup:** Column layout integrates a sidebar with the offline Emergency Card and a Manual Quick Search accordion for instant access.
|
| 66 |
+
* **Robust Local LLM Processing:** Configured with a `120s` timeout threshold to prevent premature mock fallback during heavy local prompt prefilling.
|
| 67 |
+
* **Keyword RAG Search:** Local keyword intersection retriever indexes the manual (`first_aid_guide.json`) and guides the local `gemma-2b-it` LLM model to return highly grounded first-aid instructions with manual citations.
|
| 68 |
+
* **Proximity Checkpoint Narration:** Delivers terrain updates, safety advice, and target destination briefings as hikers approach landmarks.
|
| 69 |
+
|
| 70 |
+
### 4. Live GPS Tracking & 1s Updates
|
| 71 |
+
* **1-Second Updates:** Configured GPS tracking refresh interval to 1s, enabling high-resolution position updates on the trail.
|
| 72 |
+
* **Active Proximity POI Highlights:** Automatically detects and highlights close Points of Interest (POIs) near the hiker's current coordinates in the Active Proximity Alerts panel.
|
| 73 |
+
|
| 74 |
+
### 5. Offline Voice Journal & Post-Trek Reports
|
| 75 |
+
* **ASR Voice Logs:** Dictate logs hands-free in the cold using whisper.cpp tiny. Logs transcribing audio, time, and coordinates are saved directly to SQLite.
|
| 76 |
+
* **Post-Trek Storyteller:** Converts journal entries and raw GPS points into an engaging, non-technical first-person narrative (optimized for social media sharing) without listing raw coordinates.
|
| 77 |
|
| 78 |
---
|
| 79 |
|
app.py
CHANGED
|
@@ -25,6 +25,17 @@ MAP_HTML_INITIALIZER = """
|
|
| 25 |
|
| 26 |
MAP_INIT_JS = r"""
|
| 27 |
() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
// 1. Dynamically append Leaflet CSS
|
| 29 |
if (!document.getElementById("leaflet-css")) {
|
| 30 |
var link = document.createElement("link");
|
|
@@ -386,7 +397,7 @@ MAP_INIT_JS = r"""
|
|
| 386 |
window.gpsWatchId = navigator.geolocation.watchPosition(
|
| 387 |
function(position) {
|
| 388 |
var now = Date.now();
|
| 389 |
-
if (now - window.lastGpsTime <
|
| 390 |
window.lastGpsTime = now;
|
| 391 |
|
| 392 |
var lat = position.coords.latitude;
|
|
@@ -417,7 +428,7 @@ MAP_INIT_JS = r"""
|
|
| 417 |
},
|
| 418 |
{
|
| 419 |
enableHighAccuracy: true,
|
| 420 |
-
maximumAge:
|
| 421 |
timeout: 15000
|
| 422 |
}
|
| 423 |
);
|
|
@@ -1429,10 +1440,9 @@ def handle_generate_story(route_state_val, style):
|
|
| 1429 |
f"Format Style: {style_instruction}\n"
|
| 1430 |
"Emphasize the hiker's voice notes, detailing their personal reflections, physical state, and "
|
| 1431 |
"wilderness observations. Incorporate the trek details (distance, elevation, altitude) to frame the physical challenge. "
|
| 1432 |
-
"
|
| 1433 |
-
"
|
| 1434 |
"Do not invent external landmarks, voice notes, or major events not provided. Keep the tone rugged, epic, and highly tactical. "
|
| 1435 |
-
"Organize the story using headings corresponding to distance milestones.\n"
|
| 1436 |
"At the end, sign off as 'Trailhead AI Storyteller'."
|
| 1437 |
)
|
| 1438 |
|
|
@@ -1464,9 +1474,9 @@ with gr.Blocks(css="assets/custom.css", title="Trailhead β Tactical Trail Comp
|
|
| 1464 |
route_state = gr.State(None)
|
| 1465 |
null_state = gr.State(None)
|
| 1466 |
current_point_idx = gr.State(0)
|
| 1467 |
-
hiker_pos_coords = gr.Textbox(visible=
|
| 1468 |
-
live_gps_coords = gr.Textbox(visible=
|
| 1469 |
-
route_data_json = gr.Textbox(visible=
|
| 1470 |
|
| 1471 |
hiker_pos_coords.change(
|
| 1472 |
fn=None,
|
|
@@ -1583,33 +1593,31 @@ with gr.Blocks(css="assets/custom.css", title="Trailhead β Tactical Trail Comp
|
|
| 1583 |
col_count=(4, "fixed")
|
| 1584 |
)
|
| 1585 |
|
| 1586 |
-
with gr.TabItem("
|
| 1587 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1588 |
with gr.Column(scale=1):
|
| 1589 |
gr.HTML(EMERGENCY_CARD)
|
| 1590 |
-
|
| 1591 |
-
|
| 1592 |
-
|
| 1593 |
-
|
| 1594 |
-
rag_output = gr.Markdown(value="*Manual results will be displayed here.*")
|
| 1595 |
-
|
| 1596 |
-
with gr.TabItem("π¬ Wilderness Guide AI"):
|
| 1597 |
-
# Dynamically configure chatbot to use "messages" type if on Gradio 5
|
| 1598 |
-
gradio_version = getattr(gr, "__version__", "5.0.0")
|
| 1599 |
-
if gradio_version.startswith("6"):
|
| 1600 |
-
chatbot_component = gr.Chatbot()
|
| 1601 |
-
else:
|
| 1602 |
-
chatbot_component = gr.Chatbot(type="messages")
|
| 1603 |
-
|
| 1604 |
-
gr.ChatInterface(
|
| 1605 |
-
respond,
|
| 1606 |
-
chatbot=chatbot_component,
|
| 1607 |
-
examples=[
|
| 1608 |
-
"What gear checklist do I need for a 3-day high-altitude trek?",
|
| 1609 |
-
"How do I treat a sprained ankle on the trail?",
|
| 1610 |
-
"What is Naismith's Rule for calculating hiking time?"
|
| 1611 |
-
]
|
| 1612 |
-
)
|
| 1613 |
|
| 1614 |
with gr.TabItem("ποΈ Voice Journal & Reports"):
|
| 1615 |
with gr.Row():
|
|
|
|
| 25 |
|
| 26 |
MAP_INIT_JS = r"""
|
| 27 |
() => {
|
| 28 |
+
// Hide communication textboxes instantly
|
| 29 |
+
var hideElements = function() {
|
| 30 |
+
['hiker-pos-coords', 'live-gps-coords', 'route-data-json'].forEach(function(id) {
|
| 31 |
+
var el = document.getElementById(id);
|
| 32 |
+
if (el) el.style.setProperty("display", "none", "important");
|
| 33 |
+
});
|
| 34 |
+
};
|
| 35 |
+
hideElements();
|
| 36 |
+
var hideInterval = setInterval(hideElements, 50);
|
| 37 |
+
setTimeout(function() { clearInterval(hideInterval); }, 4000);
|
| 38 |
+
|
| 39 |
// 1. Dynamically append Leaflet CSS
|
| 40 |
if (!document.getElementById("leaflet-css")) {
|
| 41 |
var link = document.createElement("link");
|
|
|
|
| 397 |
window.gpsWatchId = navigator.geolocation.watchPosition(
|
| 398 |
function(position) {
|
| 399 |
var now = Date.now();
|
| 400 |
+
if (now - window.lastGpsTime < 1000) return;
|
| 401 |
window.lastGpsTime = now;
|
| 402 |
|
| 403 |
var lat = position.coords.latitude;
|
|
|
|
| 428 |
},
|
| 429 |
{
|
| 430 |
enableHighAccuracy: true,
|
| 431 |
+
maximumAge: 1000,
|
| 432 |
timeout: 15000
|
| 433 |
}
|
| 434 |
);
|
|
|
|
| 1440 |
f"Format Style: {style_instruction}\n"
|
| 1441 |
"Emphasize the hiker's voice notes, detailing their personal reflections, physical state, and "
|
| 1442 |
"wilderness observations. Incorporate the trek details (distance, elevation, altitude) to frame the physical challenge. "
|
| 1443 |
+
"Synthesize the encountered amenities (water sources, campsites, alpine huts, shelters, viewpoints) naturally in a non-technical, "
|
| 1444 |
+
"cohesive narrative flow rather than listing every single route coordinate or waypoint. Do not list every milestone or amenity one-by-one. "
|
| 1445 |
"Do not invent external landmarks, voice notes, or major events not provided. Keep the tone rugged, epic, and highly tactical. "
|
|
|
|
| 1446 |
"At the end, sign off as 'Trailhead AI Storyteller'."
|
| 1447 |
)
|
| 1448 |
|
|
|
|
| 1474 |
route_state = gr.State(None)
|
| 1475 |
null_state = gr.State(None)
|
| 1476 |
current_point_idx = gr.State(0)
|
| 1477 |
+
hiker_pos_coords = gr.Textbox(visible=True, elem_id="hiker-pos-coords")
|
| 1478 |
+
live_gps_coords = gr.Textbox(visible=True, elem_id="live-gps-coords")
|
| 1479 |
+
route_data_json = gr.Textbox(visible=True, elem_id="route-data-json")
|
| 1480 |
|
| 1481 |
hiker_pos_coords.change(
|
| 1482 |
fn=None,
|
|
|
|
| 1593 |
col_count=(4, "fixed")
|
| 1594 |
)
|
| 1595 |
|
| 1596 |
+
with gr.TabItem("π¬ Wilderness Guide & First-Aid AI"):
|
| 1597 |
with gr.Row():
|
| 1598 |
+
with gr.Column(scale=2):
|
| 1599 |
+
# Dynamically configure chatbot to use "messages" type if on Gradio 5
|
| 1600 |
+
gradio_version = getattr(gr, "__version__", "5.0.0")
|
| 1601 |
+
if gradio_version.startswith("6"):
|
| 1602 |
+
chatbot_component = gr.Chatbot()
|
| 1603 |
+
else:
|
| 1604 |
+
chatbot_component = gr.Chatbot(type="messages")
|
| 1605 |
+
|
| 1606 |
+
gr.ChatInterface(
|
| 1607 |
+
respond,
|
| 1608 |
+
chatbot=chatbot_component,
|
| 1609 |
+
examples=[
|
| 1610 |
+
"What gear checklist do I need for a 3-day high-altitude trek?",
|
| 1611 |
+
"How do I treat a sprained ankle on the trail?",
|
| 1612 |
+
"What is Naismith's Rule for calculating hiking time?"
|
| 1613 |
+
]
|
| 1614 |
+
)
|
| 1615 |
with gr.Column(scale=1):
|
| 1616 |
gr.HTML(EMERGENCY_CARD)
|
| 1617 |
+
with gr.Accordion("π First-Aid Manual Quick Search", open=False):
|
| 1618 |
+
rag_query = gr.Textbox(placeholder="What symptoms or injury do you want to query?", label="Query Symptoms")
|
| 1619 |
+
rag_search_btn = gr.Button("Search manual", variant="primary")
|
| 1620 |
+
rag_output = gr.Markdown(value="*Manual results will be displayed here.*")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1621 |
|
| 1622 |
with gr.TabItem("ποΈ Voice Journal & Reports"):
|
| 1623 |
with gr.Row():
|
assets/custom.css
CHANGED
|
@@ -156,3 +156,8 @@ p, span, label {
|
|
| 156 |
letter-spacing: 0.1em;
|
| 157 |
margin-top: 5px;
|
| 158 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
letter-spacing: 0.1em;
|
| 157 |
margin-top: 5px;
|
| 158 |
}
|
| 159 |
+
|
| 160 |
+
/* Hide communication textboxes from the layout while keeping them active in the DOM */
|
| 161 |
+
#hiker-pos-coords, #live-gps-coords, #route-data-json {
|
| 162 |
+
display: none !important;
|
| 163 |
+
}
|
hackathon_submission_article.md
CHANGED
|
@@ -78,10 +78,13 @@ To prevent the 2B model from hallucinating medical advice in life-or-death scena
|
|
| 78 |
Trailhead parses GPX files, corrects noisy elevation data using a moving average and a 2.0-meter minimum threshold, and accurately predicts trek times using Naismith's Rule.
|
| 79 |
|
| 80 |
### 2. πΊοΈ Tactical HUD & Interactive Mapping
|
| 81 |
-
The frontend is a completely custom, mobile-optimized Gradio interface featuring a native Leaflet canvas. It supports live GPS tracking, simulated trek playback, and
|
| 82 |
|
| 83 |
-
### 3.
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
---
|
| 87 |
|
|
|
|
| 78 |
Trailhead parses GPX files, corrects noisy elevation data using a moving average and a 2.0-meter minimum threshold, and accurately predicts trek times using Naismith's Rule.
|
| 79 |
|
| 80 |
### 2. πΊοΈ Tactical HUD & Interactive Mapping
|
| 81 |
+
The frontend is a completely custom, mobile-optimized Gradio interface featuring a native Leaflet canvas. It supports live GPS tracking with high-frequency 1-second refresh intervals, simulated trek playback, active proximity alert updates, and highlights POI markers near the hiker's current position on the map without needing external map tile fetches on the trail.
|
| 82 |
|
| 83 |
+
### 3. π¬ Unified Wilderness AI Chatbot
|
| 84 |
+
Combines the Wilderness Guide AI and the Wilderness First-Aid RAG manuals into a single chatbot interface tab. Features a side-by-side split screen with an Emergency Card and Quick Search Manual sidebar alongside the main chatbot window.
|
| 85 |
+
|
| 86 |
+
### 4. ποΈ Geotagged Voice Journal & Storyteller
|
| 87 |
+
Hikers can log voice entries while hiking. The application transcribes the audio, stamps it with current GPS coordinates and altitude, and stores it in an SQLite database. Post-trek, the LLM compiles these logs, stats, and POI encounters into an engaging, non-technical, shareable social media expedition report.
|
| 88 |
|
| 89 |
---
|
| 90 |
|
requirements.txt
CHANGED
|
@@ -9,10 +9,10 @@ plotly
|
|
| 9 |
pydantic>=2.0.0,<2.11.0
|
| 10 |
Pillow
|
| 11 |
# ASR: transformers + torch as fallback for Hugging Face (no pywhispercpp available there)
|
| 12 |
-
transformers
|
| 13 |
-
torch
|
| 14 |
-
soundfile
|
| 15 |
# Local-only ASR dependencies (optional β only work when pywhispercpp native binaries are present)
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
| 9 |
pydantic>=2.0.0,<2.11.0
|
| 10 |
Pillow
|
| 11 |
# ASR: transformers + torch as fallback for Hugging Face (no pywhispercpp available there)
|
| 12 |
+
# transformers
|
| 13 |
+
# torch
|
| 14 |
+
# soundfile
|
| 15 |
# Local-only ASR dependencies (optional β only work when pywhispercpp native binaries are present)
|
| 16 |
+
pywhispercpp # Uncomment for local GPU-accelerated ASR
|
| 17 |
+
miniaudio # Uncomment for local audio resampling (required by pywhispercpp path)
|
| 18 |
+
llama-cpp-python # Uncomment to run local GGUF LLM instead of mock backend
|
src/llm.py
CHANGED
|
@@ -14,7 +14,8 @@ try:
|
|
| 14 |
default_backend = "llama_cpp"
|
| 15 |
except ImportError:
|
| 16 |
default_backend = "mock"
|
| 17 |
-
BACKEND =
|
|
|
|
| 18 |
|
| 19 |
# Constants for Hugging Face Space model loading
|
| 20 |
MODEL_REPO = "bartowski/google_gemma-4-E2B-it-GGUF"
|
|
@@ -334,39 +335,48 @@ def generate_mock(prompt, system="", image_path=None, audio_path=None, history=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!
|
|
|
|
| 340 |
|
| 341 |
-
|
| 342 |
-
for
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
elif m_type == "log":
|
| 353 |
-
transcript_lower = data['transcript'].lower()
|
| 354 |
icon = "ποΈ"
|
| 355 |
-
title = "
|
| 356 |
-
if "water" in transcript_lower:
|
| 357 |
icon = "π§"
|
| 358 |
title = "Water Source & Hydration Check"
|
| 359 |
-
elif "view" in transcript_lower or "
|
| 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"
|
|
@@ -455,8 +465,8 @@ def generate_llama_cpp(prompt, system="", image_path=None, audio_path=None, hist
|
|
| 455 |
return
|
| 456 |
|
| 457 |
init_duration = time.time() - start_time
|
| 458 |
-
if init_duration >
|
| 459 |
-
print(f"[llm.py] Warning: Model loading took {init_duration:.2f}s (exceeded
|
| 460 |
generate_llama_cpp.disabled = True
|
| 461 |
for chunk in generate_mock(prompt, system, image_path, audio_path, history):
|
| 462 |
yield chunk
|
|
@@ -490,7 +500,7 @@ def generate_llama_cpp(prompt, system="", image_path=None, audio_path=None, hist
|
|
| 490 |
stream=True
|
| 491 |
)
|
| 492 |
|
| 493 |
-
first_token_timeout =
|
| 494 |
response_iter = iter(response)
|
| 495 |
|
| 496 |
first_chunk_start = time.time()
|
|
|
|
| 14 |
default_backend = "llama_cpp"
|
| 15 |
except ImportError:
|
| 16 |
default_backend = "mock"
|
| 17 |
+
BACKEND = "llama_cpp" # Force llama_cpp backend
|
| 18 |
+
|
| 19 |
|
| 20 |
# Constants for Hugging Face Space model loading
|
| 21 |
MODEL_REPO = "bartowski/google_gemma-4-E2B-it-GGUF"
|
|
|
|
| 335 |
else:
|
| 336 |
response += "- No voice logs recorded.\n"
|
| 337 |
else:
|
| 338 |
+
water_count = sum(1 for am in amenities if "water" in am["name"].lower() or "fountain" in am["name"].lower())
|
| 339 |
+
camp_count = sum(1 for am in amenities if "camp" in am["name"].lower() or "shelter" in am["name"].lower())
|
| 340 |
+
other_count = len(amenities) - water_count - camp_count
|
| 341 |
+
|
| 342 |
response += "π² **MY WILDERNESS EXPEDITION REPORT** π²\n"
|
| 343 |
response += "*Powered by Trailhead Tactical Trail Computer*\n\n"
|
| 344 |
+
response += f"What an absolute journey! ποΈ Just finished an intense trek covering **{total_dist} km** with **{ele_gain} m** of vertical climb! "
|
| 345 |
+
response += f"The altitude range profile spanned from **{alt_range}**, offering challenging terrain but rewarding views.\n\n"
|
| 346 |
|
| 347 |
+
response += "### π₯Ύ The Journey & Resource Milestones\n"
|
| 348 |
+
response += "Setting off, the trail presented a rugged path but was well-equipped for resource management. "
|
| 349 |
+
if water_count > 0 or camp_count > 0 or other_count > 0:
|
| 350 |
+
parts = []
|
| 351 |
+
if water_count > 0:
|
| 352 |
+
parts.append(f"{water_count} drinking water and fountain stations")
|
| 353 |
+
if camp_count > 0:
|
| 354 |
+
parts.append(f"{camp_count} campsite/shelter areas")
|
| 355 |
+
if other_count > 0:
|
| 356 |
+
parts.append(f"{other_count} other points of interest")
|
| 357 |
+
response += f"Along the way, I passed through **{', '.join(parts)}** situated conveniently off the path, ensuring hydration and safety were never compromised. "
|
| 358 |
+
response += "Navigating these waypoints required careful planning, but it paid off beautifully.\n\n"
|
| 359 |
|
| 360 |
+
if voice_logs:
|
| 361 |
+
response += "### ποΈ Trail Reflections & Audio Log Highlights\n"
|
| 362 |
+
for log in voice_logs:
|
| 363 |
+
transcript_lower = log['transcript'].lower()
|
|
|
|
|
|
|
| 364 |
icon = "ποΈ"
|
| 365 |
+
title = "Trail Observation"
|
| 366 |
+
if "water" in transcript_lower or "waterfall" in transcript_lower:
|
| 367 |
icon = "π§"
|
| 368 |
title = "Water Source & Hydration Check"
|
| 369 |
+
elif "view" in transcript_lower or "scenic" in transcript_lower:
|
| 370 |
icon = "ποΈ"
|
| 371 |
title = "Scenic Viewpoint Reflection"
|
| 372 |
elif "finish" in transcript_lower or "complete" in transcript_lower:
|
| 373 |
icon = "π"
|
| 374 |
title = "Trek Completion Signoff"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
+
response += f"{icon} **Km {log['km']:.2f} | {title}** π\n"
|
| 377 |
+
response += f"Recorded voice entry at {log['alt']}m altitude:\n"
|
| 378 |
+
response += f"> *\"{log['transcript']}\"*\n\n"
|
| 379 |
+
|
| 380 |
response += "π **Trek Complete!**\n"
|
| 381 |
response += "Every step was worth it. Pushed my limits, managed my resources, and conquered the route. π₯Ύ\n\n"
|
| 382 |
response += "---\n"
|
|
|
|
| 465 |
return
|
| 466 |
|
| 467 |
init_duration = time.time() - start_time
|
| 468 |
+
if init_duration > 120.0:
|
| 469 |
+
print(f"[llm.py] Warning: Model loading took {init_duration:.2f}s (exceeded 120s limit). Disabling llama_cpp and falling back to mock backend.")
|
| 470 |
generate_llama_cpp.disabled = True
|
| 471 |
for chunk in generate_mock(prompt, system, image_path, audio_path, history):
|
| 472 |
yield chunk
|
|
|
|
| 500 |
stream=True
|
| 501 |
)
|
| 502 |
|
| 503 |
+
first_token_timeout = 120.0
|
| 504 |
response_iter = iter(response)
|
| 505 |
|
| 506 |
first_chunk_start = time.time()
|