Fix form submit cancelability, improve opencode error handling
This commit is contained in:
parent
4a8a071b86
commit
2aca0e5d42
@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
from fastapi import APIRouter, Request, Depends, Form
|
||||
@ -12,7 +13,7 @@ from app.models.workout import Workout
|
||||
from app.models.checkin import Checkin
|
||||
from app.models.measurement import Measurement, MeasurementType
|
||||
from app.auth import get_current_user
|
||||
from app.services.opencode_proxy import query_opencode
|
||||
from app.services.opencode_proxy import query_opencode, OpenCodeUnavailableError
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
@ -26,6 +27,9 @@ ONBOARDING_PROMPT = (
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger("chat")
|
||||
|
||||
|
||||
@router.get("/api/chat/messages")
|
||||
async def get_chat_messages(request: Request, user: User = Depends(get_current_user)):
|
||||
session_id = request.cookies.get("chat_session_id")
|
||||
@ -159,9 +163,15 @@ async def chat_send(
|
||||
await session.commit()
|
||||
|
||||
assistant_content = ""
|
||||
try:
|
||||
async for chunk in query_opencode(message, session_id, user_context):
|
||||
assistant_content += chunk
|
||||
yield f"data: {chunk}\n\n"
|
||||
except OpenCodeUnavailableError as e:
|
||||
logger.error("Chat failed for user %s: %s", user.username, e)
|
||||
yield f"data: [error] {e}\n\n"
|
||||
# Don't save an error assistant message
|
||||
return
|
||||
|
||||
async with async_session() as session:
|
||||
assistant_msg = ChatMessage(
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from typing import AsyncGenerator
|
||||
from app.config import OPENCODE_SERVE_URL
|
||||
|
||||
logger = logging.getLogger("opencode_proxy")
|
||||
|
||||
|
||||
class OpenCodeUnavailableError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
async def query_opencode(
|
||||
message: str,
|
||||
@ -37,6 +44,10 @@ async def query_opencode(
|
||||
|
||||
await proc.wait()
|
||||
except FileNotFoundError:
|
||||
yield "AI coach is not available. Install opencode and run `opencode serve` to enable."
|
||||
logger.error("opencode binary not found in PATH")
|
||||
raise OpenCodeUnavailableError(
|
||||
"opencode binary not found. Install opencode or run `opencode serve`."
|
||||
)
|
||||
except Exception as e:
|
||||
yield f"Error connecting to AI coach: {e}"
|
||||
logger.error("opencode proxy error: %s", e)
|
||||
raise OpenCodeUnavailableError(f"AI coach unavailable: {e}")
|
||||
|
||||
@ -79,7 +79,12 @@
|
||||
buffer = lines.pop() || '';
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
p.innerHTML += line.slice(6);
|
||||
const chunk = line.slice(6);
|
||||
if (chunk.startsWith('[error] ')) {
|
||||
p.innerHTML = '<strong style="color: var(--del-color, #b33)">' + chunk.slice(8) + '</strong>';
|
||||
break;
|
||||
}
|
||||
p.innerHTML += chunk;
|
||||
}
|
||||
}
|
||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||
@ -104,7 +109,9 @@
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
form.dispatchEvent(new Event('submit'));
|
||||
const event = new Event('submit', {cancelable: true});
|
||||
form.dispatchEvent(event);
|
||||
if (!event.defaultPrevented) sendMessage(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,7 +33,9 @@ const ONBOARDING_PROMPT = chatForm.dataset.onboarding;
|
||||
chatInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
chatForm.dispatchEvent(new Event('submit'));
|
||||
const event = new Event('submit', {cancelable: true});
|
||||
chatForm.dispatchEvent(event);
|
||||
if (!event.defaultPrevented) sendMessage(event);
|
||||
}
|
||||
});
|
||||
|
||||
@ -90,7 +92,12 @@ async function sendMessage(event) {
|
||||
buffer = lines.pop() || '';
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
p.innerHTML += line.slice(6);
|
||||
const chunk = line.slice(6);
|
||||
if (chunk.startsWith('[error] ')) {
|
||||
p.innerHTML = '<strong style="color: var(--del-color, #b33)">' + chunk.slice(8) + '</strong>';
|
||||
break;
|
||||
}
|
||||
p.innerHTML += chunk;
|
||||
}
|
||||
}
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
@ -114,7 +121,9 @@ async function sendMessage(event) {
|
||||
|
||||
if (IS_FIRST && ONBOARDING_PROMPT) {
|
||||
chatInput.value = ONBOARDING_PROMPT;
|
||||
chatForm.dispatchEvent(new Event('submit'));
|
||||
const event = new Event('submit', {cancelable: true});
|
||||
chatForm.dispatchEvent(event);
|
||||
if (!event.defaultPrevented) sendMessage(event);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user