forked from Rockachopa/Timmy-time-dashboard
feat: enhance v1 API with streaming and improved history
This commit is contained in:
@@ -17,13 +17,13 @@ import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, File, HTTPException, Query, Request, UploadFile
|
||||
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, Query
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from config import APP_START_TIME, settings
|
||||
from dashboard.routes.health import _check_ollama
|
||||
from dashboard.store import message_log
|
||||
from timmy.session import _get_agent
|
||||
from dashboard.routes.health import _check_ollama
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -64,9 +64,11 @@ async def api_v1_chat(request: Request):
|
||||
return JSONResponse(status_code=400, content={"error": "message is required"})
|
||||
|
||||
# Prepare context for the agent
|
||||
now = datetime.now()
|
||||
timestamp = now.strftime("%H:%M:%S")
|
||||
context_prefix = (
|
||||
f"[System: Current date/time is "
|
||||
f"{datetime.now().strftime('%A, %B %d, %Y at %I:%M %p')}]\n"
|
||||
f"{now.strftime('%A, %B %d, %Y at %I:%M %p')}]\n"
|
||||
f"[System: iPad App client]\n"
|
||||
)
|
||||
|
||||
@@ -76,7 +78,11 @@ async def api_v1_chat(request: Request):
|
||||
context_prefix += "\n"
|
||||
full_prompt = context_prefix + message
|
||||
|
||||
# Log user message
|
||||
message_log.append(role="user", content=message, timestamp=timestamp, source="api-v1")
|
||||
|
||||
async def event_generator():
|
||||
full_response = ""
|
||||
try:
|
||||
agent = _get_agent()
|
||||
# Using streaming mode for SSE
|
||||
@@ -84,8 +90,13 @@ async def api_v1_chat(request: Request):
|
||||
# Agno chunks can be strings or RunOutput
|
||||
content = chunk.content if hasattr(chunk, "content") else str(chunk)
|
||||
if content:
|
||||
full_response += content
|
||||
yield f"data: {json.dumps({'text': content})}\n\n"
|
||||
|
||||
# Log agent response once complete
|
||||
message_log.append(
|
||||
role="agent", content=full_response, timestamp=timestamp, source="api-v1"
|
||||
)
|
||||
yield "data: [DONE]\n\n"
|
||||
except Exception as exc:
|
||||
logger.error("SSE stream error: %s", exc)
|
||||
@@ -102,12 +113,9 @@ async def api_v1_chat_history(
|
||||
session_id: str = Query("ipad-app"), limit: int = Query(50, ge=1, le=100)
|
||||
):
|
||||
"""Return recent chat history for a specific session."""
|
||||
# Filter and limit the message log
|
||||
# Note: message_log.all() returns all messages; we filter by source or just return last N
|
||||
all_msgs = message_log.all()
|
||||
# Using the optimized .recent() method from infrastructure.chat_store
|
||||
all_msgs = message_log.recent(limit=limit)
|
||||
|
||||
# In a real implementation, we'd filter by session_id if message_log supported it.
|
||||
# For now, we return the last 'limit' messages.
|
||||
history = [
|
||||
{
|
||||
"role": msg.role,
|
||||
@@ -115,7 +123,7 @@ async def api_v1_chat_history(
|
||||
"timestamp": msg.timestamp,
|
||||
"source": msg.source,
|
||||
}
|
||||
for msg in all_msgs[-limit:]
|
||||
for msg in all_msgs
|
||||
]
|
||||
|
||||
return {"messages": history}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Absolute path to src
|
||||
src_path = "/home/ubuntu/timmy-time/Timmy-time-dashboard/src"
|
||||
if src_path not in sys.path:
|
||||
sys.path.insert(0, src_path)
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).resolve().parents[1]
|
||||
src_path = project_root / "src"
|
||||
if str(src_path) not in sys.path:
|
||||
sys.path.insert(0, str(src_path))
|
||||
|
||||
from fastapi.testclient import TestClient # noqa: E402
|
||||
|
||||
try:
|
||||
from dashboard.app import app # noqa: E402
|
||||
|
||||
print("✓ Successfully imported dashboard.app")
|
||||
except ImportError as e:
|
||||
print(f"✗ Failed to import dashboard.app: {e}")
|
||||
@@ -17,7 +19,6 @@ except ImportError as e:
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_v1_status():
|
||||
response = client.get("/api/v1/status")
|
||||
assert response.status_code == 200
|
||||
@@ -26,20 +27,24 @@ def test_v1_status():
|
||||
assert "model" in data
|
||||
assert "uptime" in data
|
||||
|
||||
|
||||
def test_v1_chat_history():
|
||||
# Append a message first to ensure history is not empty
|
||||
from dashboard.store import message_log
|
||||
message_log.append(role="user", content="test message", timestamp="12:00:00", source="api-v1")
|
||||
|
||||
response = client.get("/api/v1/chat/history")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "messages" in data
|
||||
|
||||
assert len(data["messages"]) > 0
|
||||
# The message_log.recent() returns reversed(rows) so the last one should be our test message
|
||||
assert data["messages"][-1]["content"] == "test message"
|
||||
|
||||
def test_v1_upload_fail():
|
||||
# Test without file
|
||||
response = client.post("/api/v1/upload")
|
||||
assert response.status_code == 422 # Unprocessable Entity (missing file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running API v1 tests...")
|
||||
try:
|
||||
@@ -52,4 +57,6 @@ if __name__ == "__main__":
|
||||
print("All tests passed!")
|
||||
except Exception as e:
|
||||
print(f"Test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user