Skip to content

Commit 2b91ebb

Browse files
Re-styled the weather widget, supported multi-day function call
1 parent 4f92fbe commit 2b91ebb

File tree

8 files changed

+125
-34
lines changed

8 files changed

+125
-34
lines changed

main.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from fastapi.responses import RedirectResponse
99
from routers import chat, files, api_keys, assistants
1010
from utils.threads import create_thread
11+
from fastapi.exceptions import HTTPException
1112

1213

1314
logger = logging.getLogger("uvicorn.error")
@@ -32,6 +33,25 @@ async def lifespan(app: FastAPI):
3233
# Initialize Jinja2 templates
3334
templates = Jinja2Templates(directory="templates")
3435

36+
@app.exception_handler(Exception)
37+
async def general_exception_handler(request: Request, exc: Exception):
38+
logger.error(f"Unhandled error: {exc}")
39+
return templates.TemplateResponse(
40+
"error.html",
41+
{"request": request, "error_message": str(exc)},
42+
status_code=500
43+
)
44+
45+
@app.exception_handler(HTTPException)
46+
async def http_exception_handler(request: Request, exc: HTTPException):
47+
logger.error(f"HTTP error: {exc.detail}")
48+
return templates.TemplateResponse(
49+
"error.html",
50+
{"request": request, "error_message": exc.detail},
51+
status_code=exc.status_code
52+
)
53+
54+
3555
# TODO: Implement some kind of thread id storage or management logic to allow
3656
# user to load an old thread, delete an old thread, etc. instead of start new
3757
@app.get("/")

routers/assistants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, Depends
1+
from fastapi import APIRouter, Depends, HTTPException
22
from fastapi.responses import RedirectResponse
33
from openai import AsyncOpenAI
44
from utils.create_assistant import create_or_update_assistant, request
@@ -34,5 +34,7 @@ async def create_update_assistant(
3434
request=request,
3535
logger=logger
3636
)
37+
if not assistant_id:
38+
raise HTTPException(status_code=400, detail="Failed to create or update assistant")
3739

3840
return RedirectResponse(url="/", status_code=303)

routers/chat.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import time
3+
from datetime import datetime
34
from typing import Any, AsyncGenerator
45
from fastapi.templating import Jinja2Templates
56
from fastapi import APIRouter, Form, Depends, Request
@@ -43,7 +44,7 @@ async def post_tool_outputs(client: AsyncOpenAI, data: dict, thread_id: str):
4344
data is expected to be something like
4445
{
4546
"tool_outputs": {
46-
"output": {"location": "City", "temperature": 70, "conditions": "Sunny"},
47+
"output": [{"location": "City", "temperature": 70, "conditions": "Sunny"}],
4748
"tool_call_id": "call_123"
4849
},
4950
"runId": "some-run-id",
@@ -85,7 +86,7 @@ async def send_message(
8586
await client.beta.threads.messages.create(
8687
thread_id=thread_id,
8788
role="user",
88-
content=userInput
89+
content=f"System: Today's date is {datetime.today().strftime('%Y-%m-%d')}\n{userInput}"
8990
)
9091

9192
# Render the component templates with the context
@@ -255,21 +256,19 @@ async def event_generator():
255256
try:
256257
args = json.loads(tool_call.function.arguments)
257258
location = args.get("location", "Unknown")
259+
dates = args.get("dates", [datetime.today()])
258260
except Exception as err:
259261
logger.error(f"Failed to parse function arguments: {err}")
260262
location = "Unknown"
261263

262-
weather_output: dict = get_weather(location)
264+
weather_output: list[dict] = get_weather(location, dates)
263265
logger.info(f"Weather output: {weather_output}")
264266

265267
# Render the weather widget
266268
weather_widget_html: str = templates.get_template(
267269
"components/weather-widget.html"
268270
).render(
269-
location=weather_output.get("location", "Unknown"),
270-
temperature=weather_output.get("temperature", "Unknown"),
271-
unit=weather_output.get("unit", "F"), # Default to Fahrenheit
272-
conditions=weather_output.get("conditions", "Unknown")
271+
reports=weather_output
273272
)
274273

275274
# Yield the rendered HTML

static/styles.css

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,6 @@ pre {
416416
display: flex;
417417
flex-direction: column;
418418
order: 2;
419-
white-space: pre-wrap;
420419
}
421420

422421
.userMessage,
@@ -505,15 +504,18 @@ pre {
505504

506505
.toolOutput {
507506
display: flex;
508-
flex-direction: row;
509-
align-items: center;
507+
flex-direction: column;
508+
align-items: flex-start;
510509
justify-content: space-between;
511510
padding: 10px;
512511
border-radius: var(--border-radius);
513-
background-color: #f9f9f9;
514-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
512+
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 100%);
513+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
515514
margin-bottom: 10px;
516515
gap: 10px;
516+
width: fit-content;
517+
height: fit-content;
518+
color: #333;
517519
}
518520

519521
.toolOutput h2,
@@ -522,6 +524,7 @@ pre {
522524
padding: 0 10px;
523525
flex: 1;
524526
text-align: center;
527+
color: #333;
525528
}
526529

527530
@media (max-width: 600px) {
@@ -536,3 +539,31 @@ pre {
536539
padding: 5px 0;
537540
}
538541
}
542+
543+
.error-container {
544+
display: flex;
545+
flex-direction: column;
546+
gap: 10px;
547+
}
548+
549+
.weatherReport {
550+
display: flex;
551+
flex-direction: row;
552+
align-items: center;
553+
margin-bottom: 20px;
554+
}
555+
556+
.weatherContainer {
557+
display: flex;
558+
align-items: flex-start;
559+
gap: 20px;
560+
justify-content: flex-start;
561+
}
562+
563+
.location {
564+
flex: 0 0 auto;
565+
}
566+
567+
.reports {
568+
flex: 1;
569+
}
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
<!-- weather-widget.html -->
22
<div class="toolOutput">
3-
<!-- Location -->
4-
<h2>{{location}}</h2>
5-
<!-- Temperature -->
6-
<p>{{temperature}}&deg;{{unit}}</p>
7-
<!-- Conditions -->
8-
<p>{{conditions}}</p>
3+
<div class="weatherContainer">
4+
<!-- Location -->
5+
<div class="location"><h2>{{ reports[0].location }}</h2></div>
6+
<div class="reports">
7+
{% for report in reports %}
8+
<div class="weatherReport">
9+
<!-- Date -->
10+
<p>{{ report.date }}</p>
11+
<!-- Temperature -->
12+
<p>{{ report.temperature }}&deg;{{ report.unit }}</p>
13+
<!-- Conditions -->
14+
<p>{{ report.conditions }}</p>
15+
</div>
16+
{% endfor %}
17+
</div>
18+
</div>
919
</div>

templates/error.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "layout.html" %}
2+
3+
{% block content %}
4+
<div class="error-container">
5+
<h1>An Error Occurred</h1>
6+
<p>{{ error_message }}</p>
7+
<a href="/">Return to Home</a>
8+
</div>
9+
{% endblock %}

utils/create_assistant.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@
2727
"location": {
2828
"type": "string",
2929
"description": "The city and state e.g. San Francisco, CA"
30+
},
31+
"dates": {
32+
"type": "array",
33+
"description": "The dates (\"YYYY-MM-DD\") to get weather for",
34+
"items": {
35+
"type": "string"
36+
}
3037
}
3138
},
32-
"required": ["location"],
39+
# Currently OpenAI requires that all properties be required
40+
"required": ["location", "dates"],
3341
"additionalProperties": False,
3442
},
3543
strict=True,

utils/custom_functions.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
import random
22
import logging
3+
from datetime import datetime
34

45
logger = logging.getLogger("uvicorn.error")
56

6-
def get_weather(location):
7+
def get_weather(location, dates: list[str | datetime] = [datetime.today()]):
78
"""
8-
Generate a random weather report for a given location.
9+
Generate random weather reports for a given location over a date range.
910
1011
Args:
1112
location (str): The location for which to generate the weather report.
13+
start_date (datetime, optional): The start date for the weather report range.
14+
end_date (datetime, optional): The end date for the weather report range. Defaults to today.
1215
1316
Returns:
14-
dict: A dictionary containing the location, temperature, unit, and conditions.
17+
list: A list of dictionaries, each containing the location, date, temperature, unit, and conditions.
1518
"""
16-
# Choose a random temperature and condition
17-
random_temperature = random.randint(50, 80)
18-
conditions = ["Cloudy", "Sunny", "Rainy", "Snowy", "Windy"]
19-
random_condition = random.choice(conditions)
20-
21-
return {
22-
"location": location,
23-
"temperature": random_temperature,
24-
"unit": "F",
25-
"conditions": random_condition,
26-
}
19+
weather_reports = []
20+
21+
for date in dates:
22+
if isinstance(date, datetime):
23+
date = date.strftime("%Y-%m-%d")
24+
25+
# Choose a random temperature and condition
26+
random_temperature = random.randint(50, 80)
27+
conditions = ["Cloudy", "Sunny", "Rainy", "Snowy", "Windy"]
28+
random_condition = random.choice(conditions)
29+
30+
weather_reports.append({
31+
"location": location,
32+
"date": date,
33+
"temperature": random_temperature,
34+
"unit": "F",
35+
"conditions": random_condition,
36+
})
37+
38+
return weather_reports
2739

0 commit comments

Comments
 (0)