# app/main.py
import asyncio
import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
# Schemas
from app.models import ChatRequest, RegisterRequest, ScheduleRequest
# Services
from app.services.chat_service import process_chat_message
from app.services.scheduler_service import (
scheduler,
schedule_new_reminder,
send_proactive_message_task,
device_tokens,
cancel_reminder,
)
app = FastAPI(title="Mini-chan Tsundere API (Proactive Text Assistant)")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.on_event("startup")
async def startup_event():
scheduler.start()
print("Scheduler sudah hidup, siap menerima jadwal, Baka!")
@app.on_event("shutdown")
async def shutdown_event():
scheduler.shutdown()
print("Scheduler dimatikan. Hmph.")
@app.get("/", include_in_schema=False)
def root():
return HTMLResponse(
"""
🤖 Mini-chan Tsundere API Running
Hmph… aku udah hidup di Hugging Face Spaces. Baka 😤
• Buka /docs buat Swagger UI.
• Healthcheck: GET /health
• Endpoints utama: /chat, /register-device, dan /schedule
""",
status_code=200,
)
@app.get("/health", tags=["Health"])
def health():
return {"status": "ok"}
@app.post("/chat", tags=["Chat"])
async def handle_chat_endpoint(request: ChatRequest):
response_text = await process_chat_message(request)
return {"response": response_text}
@app.post("/register-device", tags=["Scheduler"])
async def register_device(request: RegisterRequest):
if not request.fcm_token:
return {"error": "FCM token tidak boleh kosong, Baka!"}
device_tokens.add(request.fcm_token)
print(f"Device baru tercatat: {request.fcm_token}")
return {"message": "Hmph, yaudah, devicemu aku catat. Jangan aneh-aneh."}
@app.post("/schedule", tags=["Scheduler"])
async def schedule_reminder_endpoint(request: ScheduleRequest):
user_id = request.user_id.strip()
task = request.task.strip()
if not user_id:
raise HTTPException(
status_code=400,
detail="user_id tidak boleh kosong."
)
if not task:
raise HTTPException(
status_code=400,
detail="Task tidak boleh kosong."
)
job_id = schedule_new_reminder(
user_id=user_id,
task=task,
remind_at=request.remind_at_utc
)
return {
"message": (
f"Cih, yaudah aku ingetin nanti jam "
f"{request.remind_at_utc.isoformat()}."
),
"job_id": job_id,
}
@app.get("/debug/jobs", tags=["Debug"])
async def list_jobs():
jobs = []
for job in scheduler.get_jobs():
jobs.append({
"id": job.id,
"next_run_time": str(job.next_run_time),
"func": str(job.func_ref),
})
return {"jobs": jobs}
class DebugSendNow(BaseModel):
user_id: str
task: str
@app.post("/debug/send-now", tags=["Debug"])
async def debug_send_now(body: DebugSendNow):
asyncio.create_task(
send_proactive_message_task(
body.user_id,
body.task
)
)
return {
"message": "Mini-chan mencoba mengirim sekarang."
}
@app.post("/debug/cancel/{job_id}", tags=["Debug"])
async def debug_cancel(job_id: str):
ok = cancel_reminder(job_id)
return {"cancelled": ok, "job_id": job_id}