File size: 4,899 Bytes
ca20ec1
 
71a3245
ca20ec1
71a3245
ca20ec1
 
 
71a3245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca20ec1
 
 
 
 
 
 
 
71a3245
ca20ec1
 
 
 
71a3245
ca20ec1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71a3245
ca20ec1
 
 
 
 
 
 
 
 
 
 
 
 
71a3245
ca20ec1
 
 
71a3245
ca20ec1
 
 
71a3245
ca20ec1
 
 
71a3245
 
 
 
ca20ec1
71a3245
ca20ec1
 
71a3245
ca20ec1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71a3245
 
ca20ec1
 
 
71a3245
ca20ec1
 
 
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
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, Field
from contextlib import asynccontextmanager
from typing import List, Optional
from app.database import db_manager
from app.agent import shl_agent

# 1. Define Schemas inside main.py to ensure compliance
class Recommendation(BaseModel):
    name: str
    url: str
    test_type: str

class ChatMessage(BaseModel):
    role: str
    content: str

class ChatRequest(BaseModel):
    # CRITICAL: The grader expects 'messages', not 'conversation'
    messages: List[ChatMessage]

class ChatResponse(BaseModel):
    reply: str
    recommendations: List[Recommendation] = Field(default_factory=list)
    end_of_conversation: bool = False

# 2. Lifecycle
@asynccontextmanager
async def lifespan(app: FastAPI):
    catalog_file_path = "shl_product_catalog.json"
    db_manager.initialize_catalog(catalog_file_path)
    yield

app = FastAPI(lifespan=lifespan)

# 3. Frontend UI
html_content = """
<!DOCTYPE html>
<html>
<head>
    <title>SHL Assessment Consultant</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f4f4f9; }
        #chat-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); height: 500px; overflow-y: auto; margin-bottom: 20px; }
        .message { margin-bottom: 15px; padding: 10px; border-radius: 5px; }
        .user { background-color: #e3f2fd; text-align: right; border-left: 4px solid #2196f3; }
        .agent { background-color: #f1f8e9; text-align: left; border-left: 4px solid #4caf50; }
        .system { text-align: center; color: #888; font-style: italic; font-size: 0.9em; }
        table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.9em; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #4caf50; color: white; }
        .input-area { display: flex; gap: 10px; }
        input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; }
        button { padding: 10px 20px; background-color: #2196f3; color: white; border: none; border-radius: 4px; cursor: pointer; }
    </style>
</head>
<body>
    <h2>SHL Assessment Consultant</h2>
    <div id="chat-container">
        <div class="message system">Start a conversation. E.g., "I'm hiring a Java developer."</div>
    </div>
    <div class="input-area">
        <input type="text" id="user-input" placeholder="Type your message..." onkeypress="if(event.key === 'Enter') sendMessage()">
        <button onclick="sendMessage()">Send</button>
    </div>

    <script>
        let conversationHistory = [];

        async function sendMessage() {
            const inputField = document.getElementById('user-input');
            const userText = inputField.value.trim();
            if (!userText) return;
            const chatContainer = document.getElementById('chat-container');

            chatContainer.innerHTML += `<div class="message user"><strong>You:</strong> ${userText}</div>`;
            conversationHistory.push({ "role": "user", "content": userText });
            inputField.value = '';

            try {
                // CRITICAL: Sending 'messages' key, not 'conversation'
                const response = await fetch('/chat', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ messages: conversationHistory })
                });

                const data = await response.json();
                
                chatContainer.innerHTML += `<div class="message agent"><strong>Consultant:</strong> ${data.reply}</div>`;
                conversationHistory.push({ "role": "assistant", "content": data.reply });
                
                if (data.end_of_conversation) {
                    conversationHistory = [];
                }
            } catch (error) {
                chatContainer.innerHTML += `<div class="message system" style="color:red;">Error.</div>`;
            }
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }
    </script>
</body>
</html>
"""

@app.get("/", response_class=HTMLResponse)
async def get_frontend():
    return html_content

@app.get("/health")
def health_check():
    return {"status": "ok"}

@app.post("/chat", response_model=ChatResponse)
def chat_endpoint(request: ChatRequest):
    try:
        # CRITICAL: Processing 'messages' not 'conversation'
        response_dict = shl_agent.handle_conversation(request.messages)
        return ChatResponse(**response_dict)
    except Exception as e:
        return ChatResponse(
            reply="I ran into an issue — could you rephrase it?",
            recommendations=[],
            end_of_conversation=False
        )