File size: 5,481 Bytes
c27eaf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a267a62
 
 
 
 
c27eaf1
 
 
 
 
 
 
 
 
 
 
 
 
 
a267a62
 
c27eaf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a267a62
c27eaf1
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""FastAPI application β€” consultant chatbot microservice.

Endpoints:
    POST /chat                       β€” Main chat endpoint
    GET  /sessions                   β€” List all sessions
    POST /sessions/new               β€” Create a new session
    GET  /sessions/{session_id}      β€” Get session details + history
    DELETE /sessions/{session_id}    β€” Delete a session
    GET  /health                     β€” Health check

The frontend is served as static files from ../frontend/.
"""

from __future__ import annotations

import os
import uuid
from contextlib import asynccontextmanager

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

from models import (
    ChatRequest,
    ChatResponse,
    Message,
    NewSessionResponse,
    Phase,
    SessionInfo,
    SessionState,
)
from storage import delete_session, init_db, list_sessions, load_session, save_session
from graph import run_consultation


# ---------------------------------------------------------------------------
# Greeting message β€” shown at the start of every new session
# ---------------------------------------------------------------------------

GREETING_MESSAGE = (
    "**Welcome to Stacklogix.** I'm your consultant for this session. πŸ‘‹\n\n"
    "Whether you have a **clear request** (e.g. build a product or fix an issue) or a **problem** "
    "(e.g. something isn't working as you'd like), I'll ask a few questions to understand your needs. "
    "Our team will then follow up with you.\n\n"
    "**What would you like to achieve or what do you need help with?**"
)


# ---------------------------------------------------------------------------
# App lifecycle
# ---------------------------------------------------------------------------

@asynccontextmanager
async def lifespan(app: FastAPI):
    await init_db()
    yield


app = FastAPI(
    title="Stacklogix Consultant Chatbot API",
    description="Stacklogix requirement-collection chatbot β€” gathers client requirements and explains how Stacklogix will address them. LangGraph + Groq.",
    version="1.0.0",
    lifespan=lifespan,
)

# CORS β€” allow all origins for microservice usage
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# ---------------------------------------------------------------------------
# Chat endpoint
# ---------------------------------------------------------------------------

@app.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
    """Process a user message and return the consultant's response."""
    # Load or create session
    session = await load_session(req.session_id)
    if session is None:
        session = SessionState(session_id=req.session_id)

    # Run consultation graph
    result = await run_consultation(session, req.message)

    # Persist updated state
    await save_session(req.session_id, result["session_state"])

    return ChatResponse(
        reply=result["assistant_reply"],
        phase=result["new_phase"],
        confidence=result["new_confidence"],
        understanding=result["new_understanding"],
    )


# ---------------------------------------------------------------------------
# Session management endpoints
# ---------------------------------------------------------------------------

@app.get("/sessions")
async def get_sessions():
    """List all active sessions."""
    sessions = await list_sessions()
    return {"sessions": sessions}


@app.post("/sessions/new")
async def create_session():
    """Create a new session with an opening greeting message."""
    session_id = str(uuid.uuid4())
    state = SessionState(
        session_id=session_id,
        messages=[Message(role="assistant", content=GREETING_MESSAGE)],
    )
    await save_session(session_id, state)
    return {
        "session_id": session_id,
        "greeting": GREETING_MESSAGE,
    }


@app.get("/sessions/{session_id}")
async def get_session(session_id: str):
    """Get full session details including conversation history."""
    session = await load_session(session_id)
    if session is None:
        raise HTTPException(status_code=404, detail="Session not found")
    return session.model_dump()


@app.delete("/sessions/{session_id}")
async def remove_session(session_id: str):
    """Delete a session."""
    deleted = await delete_session(session_id)
    if not deleted:
        raise HTTPException(status_code=404, detail="Session not found")
    return {"status": "deleted", "session_id": session_id}


# ---------------------------------------------------------------------------
# Health check
# ---------------------------------------------------------------------------

@app.get("/health")
async def health():
    return {"status": "healthy", "service": "stacklogix-chatbot"}


# ---------------------------------------------------------------------------
# Serve frontend static files
# ---------------------------------------------------------------------------

FRONTEND_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "frontend")

if os.path.isdir(FRONTEND_DIR):
    @app.get("/")
    async def serve_index():
        return FileResponse(os.path.join(FRONTEND_DIR, "index.html"))

    app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static")