LifeFlow-AI / core /utils.py
Marco310's picture
buildup gradio app
3b3daa9
raw
history blame
16.1 kB
"""
LifeFlow AI - Core Utilities
"""
import plotly.graph_objects as go
from config import AGENTS_INFO
from datetime import datetime
def create_agent_stream_output() -> str:
"""ๅ‰ตๅปบ Agent ไธฒๆต่ผธๅ‡บ HTML"""
return """
<div class="stream-container">
<div class="stream-text">Ready to analyze your tasks...<span class="stream-cursor"></span></div>
</div>
"""
def create_agent_card_enhanced(agent_key: str, status: str = "idle", message: str = "") -> str:
"""
ๅ‰ตๅปบๅขžๅผท็š„ Agent ๅก็‰‡
Args:
agent_key: Agent ๆจ™่ญ˜็ฌฆ
status: ็‹€ๆ…‹ (idle/working/complete)
message: ้กฏ็คบ่จŠๆฏ
"""
agent = AGENTS_INFO.get(agent_key, {})
avatar = agent.get("avatar", "๐Ÿค–")
name = agent.get("name", "Agent")
role = agent.get("role", "")
color = agent.get("color", "#4A90E2")
glow = agent.get("glow", "rgba(74, 144, 226, 0.3)")
status_class = f"status-{status}"
card_class = "agent-card active" if status == "working" else "agent-card"
status_text = {
"idle": "On Standby",
"working": "Working...",
"complete": "Complete โœ“"
}.get(status, "Unknown")
message_html = f'<div class="agent-message">{message}</div>' if message else ""
return f"""
<div class="{card_class}" style="--agent-color: {color}; --agent-glow: {glow};">
<div class="agent-header">
<div class="agent-avatar">{avatar}</div>
<div class="agent-info">
<h3>{name}</h3>
<div class="agent-role">{role}</div>
</div>
</div>
<span class="agent-status {status_class}">{status_text}</span>
{message_html}
</div>
"""
def create_task_card(task_num: int, task_title: str, priority: str,
time_window: dict, duration: str, location: str, icon: str = "๐Ÿ“‹") -> str:
"""ๅ‰ตๅปบไปปๅ‹™ๅก็‰‡"""
priority_color_map = {
"HIGH": "#FF6B6B",
"MEDIUM": "#F5A623",
"LOW": "#7ED321"
}
if time_window is None:
time_window = "Anytime"
elif isinstance(time_window, dict):
start = time_window.get('earliest_time', None)
end = time_window.get('latest_time', None)
start = datetime.fromisoformat(start).strftime("%H:%M") if start else "Before"
end = datetime.fromisoformat(end).strftime("%H:%M") if end else "After"
if start == "Before" and end == "After":
time_window = "Anytime"
elif start == "After":
time_window = f"After {end}"
elif end == "Before":
time_window = f"Before {start}"
else:
time_window = f"{start} - {end}"
priority_class = f"priority-{priority.lower()}"
task_color = priority_color_map.get(priority, "#999")
return f"""
<div class="task-card" style="--task-priority-color: {task_color};">
<div class="task-header">
<div class="task-title">
<span class="task-icon">{icon}</span>
<span>#{task_num}: {task_title}</span>
</div>
<span class="priority-badge {priority_class}">{priority}</span>
</div>
<div class="task-details">
<div class="task-detail-item">
<span>๐Ÿ•</span>
<span>{time_window}</span>
</div>
<div class="task-detail-item">
<span>โฑ๏ธ</span>
<span>{duration}</span>
</div>
<div class="task-detail-item">
<span>๐Ÿ“</span>
<span>{location}</span>
</div>
</div>
</div>
"""
def create_summary_card(total_tasks: int, high_priority: int, total_time: int) -> str:
"""ๅ‰ตๅปบๆ‘˜่ฆๅก็‰‡""" #style="color: #FF6B6B;
return f"""
<div class="summary-card">
<h3 style="margin: 0 0 15px 0; font-size: 20px;">๐Ÿ“‹ Task Summary</h3>
<div class="summary-stats">
<div class="stat-item">
<span class="stat-value">{total_tasks}</span>
<span class="stat-label">Tasks</span>
</div>
<div class="stat-item">
<span class="stat-value">{high_priority}</span>
<span class="stat-label">High Priority</span>
</div>
<div class="stat-item">
<span class="stat-value">{total_time}</span>
<span class="stat-label">min total</span>
</div>
</div>
</div>
"""
def create_animated_map():
"""ๅ‰ตๅปบๅ‹•็•ซๅœฐๅœ–๏ผˆๆจกๆ“ฌ๏ผ‰"""
fig = go.Figure()
# ๆจกๆ“ฌ่ทฏ็ทš้ปž
lats = [25.033, 25.045, 25.055, 25.040]
lons = [121.565, 121.575, 121.585, 121.570]
fig.add_trace(go.Scattermapbox(
lat=lats,
lon=lons,
mode='markers+lines',
marker=dict(size=12, color='#4A90E2'),
line=dict(width=3, color='#50C878'),
text=['Start', 'Task 1', 'Task 2', 'End'],
hoverinfo='text'
))
fig.update_layout(
mapbox=dict(
style='open-street-map',
center=dict(lat=25.043, lon=121.575),
zoom=12
),
margin=dict(l=0, r=0, t=0, b=0),
height=500
)
return fig
def get_reasoning_html_reversed(reasoning_messages: list = None) -> str:
"""็ฒๅ–ๆŽจ็†้Ž็จ‹ HTML๏ผˆๅๅ‘ๆŽ’ๅบ๏ผŒๆœ€ๆ–ฐๅœจไธŠ๏ผ‰"""
if not reasoning_messages:
return '<div class="reasoning-timeline"><p style="text-align: center; opacity: 0.6;">No reasoning messages yet...</p></div>'
items_html = ""
for msg in reversed(reasoning_messages[-20:]): # ๅช้กฏ็คบๆœ€่ฟ‘20ๆข๏ผŒๅๅ‘
agent_info = AGENTS_INFO.get(msg.get('agent', 'planner'), {})
color = agent_info.get('color', '#4A90E2')
avatar = agent_info.get('avatar', '๐Ÿค–')
items_html += f"""
<div class="reasoning-item" style="--agent-color: {color};">
<div class="reasoning-header">
<span>{avatar}</span>
<span class="reasoning-agent">{msg.get('agent', 'Agent')}</span>
<span class="reasoning-time">{msg.get('time', '')}</span>
</div>
<div class="reasoning-content">{msg.get('message', '')}</div>
</div>
"""
return f'<div class="reasoning-timeline">{items_html}</div>'
def create_celebration_animation() -> str:
"""ๅ‰ตๅปบๆ…ถ็ฅๅ‹•็•ซ๏ผˆ็ด” CSS ๆ’’่Šฑๆ•ˆๆžœ๏ผ‰- Gradio ๅ…ผๅฎน็‰ˆๆœฌ"""
import random
# ็”Ÿๆˆ 100 ๅ€‹้šจๆฉŸไฝ็ฝฎๅ’Œๅ‹•็•ซ็š„ๆ’’่Šฑๅ…ƒ็ด 
confetti_html = ""
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2']
for i in range(100):
left = random.randint(0, 100)
delay = random.uniform(0, 2)
duration = random.uniform(3, 6)
rotation = random.randint(0, 360)
color = random.choice(colors)
size = random.randint(8, 15)
confetti_html += f"""
<div class="confetti" style="
left: {left}%;
animation-delay: {delay}s;
animation-duration: {duration}s;
background-color: {color};
width: {size}px;
height: {size}px;
transform: rotate({rotation}deg);
"></div>
"""
return f"""
<div class="celebration-overlay">
{confetti_html}
</div>
<style>
@keyframes confetti-fall {{
0% {{
transform: translateY(-100vh) rotate(0deg);
opacity: 1;
}}
100% {{
transform: translateY(100vh) rotate(720deg);
opacity: 0;
}}
}}
@keyframes celebration-fade {{
0% {{ opacity: 1; }}
80% {{ opacity: 1; }}
100% {{ opacity: 0; }}
}}
.celebration-overlay {{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
overflow: hidden;
animation: celebration-fade 6s forwards;
}}
.confetti {{
position: absolute;
top: -20px;
animation: confetti-fall linear infinite;
opacity: 0.9;
}}
</style>
"""
def decode_polyline(polyline_str):
"""
่งฃ็ขผ Google Maps Polyline ๅญ—็ฌฆไธฒ็‚บ (lat, lng) ๅˆ—่กจ
็ด” Python ๅฏฆ็พ๏ผŒ็„ก้œ€้กๅค–ไพ่ณด
"""
index, lat, lng = 0, 0, 0
coordinates = []
changes = {'latitude': 0, 'longitude': 0}
while index < len(polyline_str):
for unit in ['latitude', 'longitude']:
shift, result = 0, 0
while True:
byte = ord(polyline_str[index]) - 63
index += 1
result |= (byte & 0x1f) << shift
shift += 5
if not byte >= 0x20:
break
if (result & 1):
changes[unit] = ~(result >> 1)
else:
changes[unit] = (result >> 1)
lat += changes['latitude']
lng += changes['longitude']
coordinates.append((lat / 100000.0, lng / 100000.0))
return coordinates
def create_result_visualization(task_list: list, structured_data: dict) -> str:
"""
ๅ‰ตๅปบ่ฉณ็ดฐ็š„็ตๆžœ่ฆ–่ฆบๅŒ–ๅก็‰‡ (็ถๅฎš็œŸๅฏฆๆ•ธๆ“š)
"""
# ๆๅ–ๆ•ธๆ“š
metrics = structured_data.get("metrics", {})
traffic = structured_data.get("traffic_summary", {})
timeline = structured_data.get("timeline", [])
# 1. ่จˆ็ฎ—ๆ‘˜่ฆๆ•ธๆ“š
completed_tasks = metrics.get("completed_tasks", 0)
total_tasks = metrics.get("total_tasks", 0)
total_dist_km = traffic.get("total_distance_km", 0)
total_dur_min = traffic.get("total_duration_min", 0)
# ๆ ผๅผๅŒ–ๆ™‚้–“ (ไพ‹ๅฆ‚ 85 min -> 1h 25min)
hours = int(total_dur_min // 60)
mins = int(total_dur_min % 60)
time_str = f"{hours}h {mins}m" if hours > 0 else f"{mins}min"
# 2. ๅ„ชๅŒ–ๆŒ‡ๆจ™
efficiency = metrics.get("route_efficiency_pct", 90)
time_saved_pct = metrics.get("time_improvement_pct", 0)
dist_saved_pct = metrics.get("distance_improvement_pct", 0)
time_saved_val = metrics.get("time_saved_min", 0)
dist_saved_val_km = metrics.get("distance_saved_m", 0) / 1000
html = f"""
<div class="result-visualization">
<div class="result-header">
<h2 style="color: #50C878; margin: 0; font-size: 28px; display: flex; align-items: center; gap: 10px;">
<span class="celebration-icon" style="font-size: 36px; animation: bounce 1s infinite;">๐ŸŽ‰</span>
<span>Planning Complete!</span>
</h2>
<p style="margin: 10px 0 0 0; opacity: 0.8; font-size: 14px;">Optimized based on real-time traffic & weather</p>
</div>
<div class="quick-summary" style="margin-top: 25px;">
<h3 style="font-size: 18px; margin: 0 0 15px 0; color: #333;">๐Ÿ“Š Quick Summary</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
<div class="summary-metric">
<div class="metric-icon">๐ŸŽฏ</div>
<div class="metric-content">
<div class="metric-value">{completed_tasks} / {total_tasks}</div>
<div class="metric-label">Tasks Completed</div>
</div>
</div>
<div class="summary-metric">
<div class="metric-icon">โฑ๏ธ</div>
<div class="metric-content">
<div class="metric-value">{time_str}</div>
<div class="metric-label">Total Duration</div>
</div>
</div>
<div class="summary-metric">
<div class="metric-icon">๐Ÿš—</div>
<div class="metric-content">
<div class="metric-value">{total_dist_km:.1f} km</div>
<div class="metric-label">Total Distance</div>
</div>
</div>
<div class="summary-metric success">
<div class="metric-icon">โšก</div>
<div class="metric-content">
<div class="metric-value">{efficiency}%</div>
<div class="metric-label">Efficiency Score</div>
</div>
</div>
</div>
</div>
<div class="timeline-preview" style="margin-top: 25px;">
<h3 style="font-size: 18px; margin: 0 0 15px 0; color: #333;">๐Ÿ—“๏ธ Timeline Preview</h3>
<div class="timeline-container">
"""
# ็ถๅฎšๆ™‚้–“็ทšๆ•ธๆ“š
# ่ทณ้Ž start point (index 0) ๅฆ‚ๆžœไธ้œ€่ฆ้กฏ็คบ๏ผŒๆˆ–่€…ๅ…จ้ƒจ้กฏ็คบ
for i, stop in enumerate(timeline):
time = stop.get("time", "")
loc_name = stop.get("location", "")
weather = stop.get("weather", "")
travel_time = stop.get("travel_time_from_prev", "")
# ็ฐกๅ–ฎ็š„ๅœ–ๆจ™้‚่ผฏ
icon = "๐Ÿ" if i == 0 else ("๐Ÿ”š" if i == len(timeline) - 1 else "๐Ÿ“")
html += f"""
<div class="timeline-item">
<div class="timeline-time">{time}</div>
<div class="timeline-connector">โ”โ”</div>
<div class="timeline-task">
<span class="timeline-icon">{icon}</span>
<div class="timeline-info">
<span class="timeline-title">{loc_name}</span>
<span class="timeline-meta">{weather}</span>
</div>
</div>
</div>
"""
# ๆทปๅŠ ไบค้€šๆ™‚้–“ (ๅฆ‚ๆžœไธๆ˜ฏๆœ€ๅพŒไธ€ๅ€‹)
if i < len(timeline) - 1:
next_travel = timeline[i + 1].get("travel_time_from_prev", "0 mins")
html += f"""
<div class="timeline-travel">
<div class="travel-arrow">โ†“</div>
<div class="travel-time">Drive: {next_travel}</div>
</div>
"""
html += """
</div>
</div>
<div class="optimization-metrics" style="margin-top: 25px;">
<h3 style="font-size: 18px; margin: 0 0 15px 0; color: #333;">๐Ÿ“ˆ Optimization Metrics</h3>
<div class="metrics-grid">
"""
# ็ถๅฎšๆŒ‡ๆจ™ๆ•ธๆ“š
viz_metrics = [
("Route Efficiency", efficiency, "#50C878"),
("Time Saved", time_saved_pct, "#4A90E2"),
("Distance Reduced", dist_saved_pct, "#F5A623")
]
for label, value, color in viz_metrics:
# ้™ๅˆถ value ๅœจ 0-100 ไน‹้–“้กฏ็คบ
display_val = min(max(value, 0), 100)
html += f"""
<div class="metric-bar">
<div class="metric-bar-header">
<span class="metric-bar-label">{label}</span>
<span class="metric-bar-value">{value:.1f}%</span>
</div>
<div class="metric-bar-track">
<div class="metric-bar-fill" style="width: {display_val}%; background: {color};"></div>
</div>
</div>
"""
html += f"""
</div>
<div class="optimization-stats" style="margin-top: 15px; display: flex; gap: 20px; flex-wrap: wrap;">
<div class="stat-badge">
<span class="stat-badge-icon">โฑ๏ธ</span>
<span class="stat-badge-text">Saved: <strong>{time_saved_val} min</strong></span>
</div>
<div class="stat-badge">
<span class="stat-badge-icon">๐Ÿ“</span>
<span class="stat-badge-text">Reduced: <strong>{dist_saved_val_km:.1f} km</strong></span>
</div>
</div>
</div>
</div>
<style>
/* ... ไฟ็•™ๅŽŸๆœฌ็š„ CSS ... */
.timeline-info {{display: flex; flex-direction: column; }}
.timeline-meta {{font - size: 11px; color: #666; margin-top: 2px; }}
</style>
"""
return html