97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
|
|
from fastapi import APIRouter, Depends, HTTPException
|
||
|
|
|
||
|
|
from app.middleware.auth import get_current_user_id
|
||
|
|
from app.models import (
|
||
|
|
ProactiveExecuteRequest,
|
||
|
|
ProactiveExecuteResponse,
|
||
|
|
ProactivePreference,
|
||
|
|
ProactivePreferencesResponse,
|
||
|
|
ProactiveRespondRequest,
|
||
|
|
ProactiveRespondResponse,
|
||
|
|
)
|
||
|
|
from app.services.db import get_pool
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/proactive", tags=["proactive"])
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/respond", response_model=ProactiveRespondResponse)
|
||
|
|
async def respond_to_action(req: ProactiveRespondRequest, user_id: str = Depends(get_current_user_id)):
|
||
|
|
pool = await get_pool()
|
||
|
|
|
||
|
|
row = await pool.fetchrow(
|
||
|
|
"SELECT id, user_id FROM proactive_actions WHERE id = $1 AND user_id = $2::uuid",
|
||
|
|
req.proactive_action_id,
|
||
|
|
user_id,
|
||
|
|
)
|
||
|
|
if not row:
|
||
|
|
raise HTTPException(status_code=404, detail="Proactive action not found")
|
||
|
|
|
||
|
|
await pool.execute(
|
||
|
|
"UPDATE proactive_actions SET user_choice = $1, chosen_action = $2, responded_at = now() WHERE id = $3",
|
||
|
|
req.user_choice,
|
||
|
|
req.chosen_action,
|
||
|
|
req.proactive_action_id,
|
||
|
|
)
|
||
|
|
|
||
|
|
return ProactiveRespondResponse(
|
||
|
|
logged=True,
|
||
|
|
should_execute=req.user_choice == "accepted",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/execute", response_model=ProactiveExecuteResponse)
|
||
|
|
async def execute_action(req: ProactiveExecuteRequest, user_id: str = Depends(get_current_user_id)):
|
||
|
|
pool = await get_pool()
|
||
|
|
|
||
|
|
row = await pool.fetchrow(
|
||
|
|
"SELECT id, user_choice FROM proactive_actions WHERE id = $1 AND user_id = $2::uuid",
|
||
|
|
req.proactive_action_id,
|
||
|
|
user_id,
|
||
|
|
)
|
||
|
|
if not row:
|
||
|
|
raise HTTPException(status_code=404, detail="Proactive action not found")
|
||
|
|
|
||
|
|
# Mark as executed — actual execution happens device-side (AppleScript/Computer Use)
|
||
|
|
# This endpoint logs that it was executed and can store results
|
||
|
|
await pool.execute(
|
||
|
|
"UPDATE proactive_actions SET executed = true WHERE id = $1",
|
||
|
|
req.proactive_action_id,
|
||
|
|
)
|
||
|
|
|
||
|
|
return ProactiveExecuteResponse(
|
||
|
|
executed=True,
|
||
|
|
result=f"Action {req.action_type} marked as executed. Device-side execution handles the actual work.",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/preferences", response_model=ProactivePreferencesResponse)
|
||
|
|
async def get_preferences(user_id: str = Depends(get_current_user_id)):
|
||
|
|
pool = await get_pool()
|
||
|
|
|
||
|
|
rows = await pool.fetch(
|
||
|
|
"""SELECT
|
||
|
|
friction_type,
|
||
|
|
COUNT(*) as total,
|
||
|
|
COUNT(*) FILTER (WHERE user_choice = 'accepted') as accepted,
|
||
|
|
(SELECT chosen_action FROM proactive_actions pa2
|
||
|
|
WHERE pa2.user_id = $1::uuid AND pa2.friction_type = pa.friction_type
|
||
|
|
AND pa2.user_choice = 'accepted'
|
||
|
|
GROUP BY chosen_action ORDER BY COUNT(*) DESC LIMIT 1) as top_action
|
||
|
|
FROM proactive_actions pa
|
||
|
|
WHERE user_id = $1::uuid AND user_choice IS NOT NULL
|
||
|
|
GROUP BY friction_type""",
|
||
|
|
user_id,
|
||
|
|
)
|
||
|
|
|
||
|
|
preferences = {}
|
||
|
|
for r in rows:
|
||
|
|
total = r["total"]
|
||
|
|
accepted = r["accepted"]
|
||
|
|
preferences[r["friction_type"]] = ProactivePreference(
|
||
|
|
preferred_action=r["top_action"],
|
||
|
|
total_choices=total,
|
||
|
|
acceptance_rate=accepted / total if total > 0 else 0.0,
|
||
|
|
)
|
||
|
|
|
||
|
|
return ProactivePreferencesResponse(preferences=preferences)
|