feat: chat sidebar and inbox, feat: saved chats, fix: wake jobs, fix: sandbox sends, ux: negotiation replies and draft box
This commit is contained in:
+79
-3
@@ -21,7 +21,7 @@ from pydantic import BaseModel
|
||||
from traderai.agent import OllamaAgent, OllamaUnavailable
|
||||
from traderai.config import save_settings, settings_payload
|
||||
from traderai.config import get_settings
|
||||
from traderai.memory import MemoryStore
|
||||
from traderai.memory import DEFAULT_THREAD_ID, MemoryStore
|
||||
from traderai.scheduler import WakeScheduler
|
||||
from traderai.tools import ToolRegistry
|
||||
from traderai.uex_client import UEXClient
|
||||
@@ -35,6 +35,19 @@ def resource_path(*parts: str) -> Path:
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
message: str
|
||||
thread_id: str | None = DEFAULT_THREAD_ID
|
||||
|
||||
|
||||
class ChatThreadRequest(BaseModel):
|
||||
title: str | None = None
|
||||
|
||||
|
||||
class RenameChatThreadRequest(BaseModel):
|
||||
title: str
|
||||
|
||||
|
||||
class DirectNegotiationMessageRequest(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class ClearMemoryRequest(BaseModel):
|
||||
@@ -246,18 +259,43 @@ def create_app() -> FastAPI:
|
||||
@app.post("/api/chat")
|
||||
async def chat(request: ChatRequest) -> dict:
|
||||
try:
|
||||
return await agent.chat(request.message)
|
||||
return await agent.chat(request.message, thread_id=request.thread_id)
|
||||
except OllamaUnavailable as exc:
|
||||
raise HTTPException(status_code=503, detail=str(exc)) from exc
|
||||
|
||||
@app.post("/api/chat/stream")
|
||||
async def chat_stream(request: ChatRequest) -> StreamingResponse:
|
||||
async def events():
|
||||
async for event in agent.chat_events(request.message):
|
||||
async for event in agent.chat_events(request.message, thread_id=request.thread_id):
|
||||
yield f"data: {json.dumps(event)}\n\n"
|
||||
|
||||
return StreamingResponse(events(), media_type="text/event-stream")
|
||||
|
||||
@app.get("/api/chats")
|
||||
async def chats() -> dict:
|
||||
return {"chats": memory.list_threads()}
|
||||
|
||||
@app.post("/api/chats")
|
||||
async def create_chat(request: ChatThreadRequest) -> dict:
|
||||
return {"chat": memory.create_thread(request.title)}
|
||||
|
||||
@app.get("/api/chats/{thread_id}/messages")
|
||||
async def chat_messages(thread_id: str) -> dict:
|
||||
memory.ensure_thread(thread_id)
|
||||
return {"thread_id": thread_id, "messages": memory.recent_conversation(limit=200, thread_id=thread_id)}
|
||||
|
||||
@app.delete("/api/chats/{thread_id}")
|
||||
async def delete_chat(thread_id: str) -> dict:
|
||||
deleted = memory.delete_thread(thread_id)
|
||||
return {"deleted": deleted, "chats": memory.list_threads()}
|
||||
|
||||
@app.patch("/api/chats/{thread_id}")
|
||||
async def rename_chat(thread_id: str, request: RenameChatThreadRequest) -> dict:
|
||||
chat = memory.rename_thread(thread_id, request.title)
|
||||
if not chat:
|
||||
raise HTTPException(status_code=400, detail="A non-empty chat title is required.")
|
||||
return {"chat": chat, "chats": memory.list_threads()}
|
||||
|
||||
@app.get("/api/pending-actions")
|
||||
async def pending_actions() -> dict:
|
||||
return {"pending_actions": agent._pending_payloads()}
|
||||
@@ -266,6 +304,35 @@ def create_app() -> FastAPI:
|
||||
async def notifications() -> dict:
|
||||
return {"notifications": memory.undelivered_outbox()}
|
||||
|
||||
@app.get("/api/inbox")
|
||||
async def inbox() -> dict:
|
||||
return {"inbox": memory.list_outbox()}
|
||||
|
||||
@app.post("/api/inbox/{inbox_id}/continue")
|
||||
async def continue_inbox(inbox_id: int) -> dict:
|
||||
item = memory.get_outbox(inbox_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Inbox item not found.")
|
||||
thread = memory.create_thread("Inbox follow-up")
|
||||
memory.add_conversation("assistant", item["content"], thread["id"])
|
||||
return {"chat": thread, "message": item}
|
||||
|
||||
@app.delete("/api/inbox/{inbox_id}")
|
||||
async def delete_inbox(inbox_id: int) -> dict:
|
||||
deleted = memory.delete_outbox(inbox_id)
|
||||
return {"deleted": deleted, "inbox": memory.list_outbox()}
|
||||
|
||||
@app.get("/api/negotiations/{identifier}/messages")
|
||||
async def negotiation_messages(identifier: str) -> dict:
|
||||
params = negotiation_identifier_params(identifier)
|
||||
return await uex.get("marketplace_negotiations_messages", params, authenticated=True)
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/messages")
|
||||
async def send_negotiation_message(identifier: str, request: DirectNegotiationMessageRequest) -> dict:
|
||||
params = negotiation_identifier_params(identifier)
|
||||
payload = {**params, "message": request.message, "is_production": 1}
|
||||
return await uex.post("marketplace_negotiations_messages", payload, authenticated=True)
|
||||
|
||||
@app.get("/api/wake-jobs")
|
||||
async def wake_jobs() -> dict:
|
||||
return {"scheduled_jobs": scheduler.list_jobs()}
|
||||
@@ -300,6 +367,15 @@ def create_app() -> FastAPI:
|
||||
return app
|
||||
|
||||
|
||||
def negotiation_identifier_params(identifier: str) -> dict[str, Any]:
|
||||
value = identifier.strip()
|
||||
if not value:
|
||||
raise HTTPException(status_code=400, detail="Negotiation id or hash is required.")
|
||||
if value.isdigit():
|
||||
return {"id_negotiation": int(value)}
|
||||
return {"hash": value}
|
||||
|
||||
|
||||
async def inspect_ollama() -> dict[str, Any]:
|
||||
settings = get_settings()
|
||||
executable = find_ollama_executable()
|
||||
|
||||
Reference in New Issue
Block a user