Spaces:
Sleeping
Sleeping
File size: 10,709 Bytes
cffeaa1 |
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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GenAI Loan Advisor</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3b82f6;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
display: inline-block;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
pre { white-space: pre-wrap; word-wrap: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
.data-box { max-height: 300px; overflow-y: auto; }
#finalOutput { white-space: pre-wrap; line-height: 1.6; }
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col items-center py-10">
<div class="w-full max-w-5xl bg-white shadow-xl rounded-xl p-8 border border-gray-200">
<div class="border-b pb-4 mb-6 text-center">
<h1 class="text-3xl font-extrabold text-gray-800 tracking-tight">GenAI Loan Advisor</h1>
<p class="text-gray-500 mt-2 text-sm">Autonomous Multi-Agent System (CrewAI + Mistral)</p>
</div>
<div class="flex flex-col gap-4 mb-6">
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-1">Access Code</label>
<input type="password" id="accessCode"
class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 shadow-sm text-gray-700 placeholder-gray-400"
placeholder="π Enter the class code (Required)">
</div>
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-1">Your Question</label>
<div class="flex gap-2">
<input type="text" id="userQuery"
class="w-full p-4 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 shadow-sm text-gray-700"
placeholder="e.g., What is the rate for Andy?">
<button onclick="sendQuery()" id="submitBtn"
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-8 rounded-lg transition duration-200 shadow-md whitespace-nowrap disabled:bg-gray-400 disabled:cursor-not-allowed">
Analyze
</button>
</div>
</div>
</div>
<p class="text-xs text-gray-400 text-center mb-6">Try Asking What is the recommended rate for Hilda? What is consider high risk? Or just say Hello</p>
<div id="loadingState" class="hidden mt-8 text-center">
<div class="loader mb-3"></div>
<p class="text-gray-500 text-sm font-medium animate-pulse">
π Agents are coordinating... this may take up to 45 seconds.
</p>
<p class="text-xs text-gray-400 mt-1">(Gathering data, checking policies, and underwriting)</p>
</div>
<div id="resultsArea" class="hidden mt-8 space-y-6 animate-fade-in">
<div class="bg-gradient-to-r from-green-50 to-white border-l-4 border-green-500 p-6 rounded shadow-sm">
<h3 class="text-lg font-bold text-green-800 flex items-center gap-2">β
Final Decision</h3>
<div id="finalOutput" class="text-gray-800 mt-3 text-base leading-relaxed"></div>
</div>
<div class="bg-blue-50 border-l-4 border-blue-500 p-5 rounded shadow-sm">
<h3 class="text-sm font-bold text-blue-800 uppercase tracking-wide mb-3 flex items-center gap-2">π§ Supervisor Strategy</h3>
<div id="planOutput" class="text-sm text-gray-700"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-50 border border-gray-200 p-5 rounded shadow-sm h-full">
<h3 class="text-xs font-bold text-gray-500 uppercase mb-3 flex items-center gap-2">π€ Customer Profile</h3>
<div id="customerOutput" class="data-box text-sm text-gray-700"></div>
</div>
<div class="bg-gray-50 border border-gray-200 p-5 rounded shadow-sm h-full">
<h3 class="text-xs font-bold text-gray-500 uppercase mb-3 flex items-center gap-2">π Policy Context (RAG)</h3>
<div id="policyOutput" class="data-box text-xs text-gray-600 bg-white p-3 rounded border border-gray-100 italic"></div>
</div>
</div>
</div>
</div>
<script>
async function sendQuery() {
const queryInput = document.getElementById('userQuery');
const codeInput = document.getElementById('accessCode');
const resultsArea = document.getElementById('resultsArea');
const loadingState = document.getElementById('loadingState'); // Target the new container
const submitBtn = document.getElementById('submitBtn'); // Target button to disable it
const query = queryInput.value.trim();
const code = codeInput.value.trim();
if (!code || !query) return alert("Please fill in both fields.");
// UI Updates: Hide results, Show Loader, Disable Button
resultsArea.classList.add('hidden');
loadingState.classList.remove('hidden');
submitBtn.disabled = true;
submitBtn.innerText = "Processing...";
try {
const response = await fetch('/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, code })
});
const result = await response.json();
if (result.status === 'success') {
const data = result.data;
// 1. FINAL RESOLUTION
let recText = data.final_recommendation || "System could not provide a resolution.";
recText = recText.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
document.getElementById('finalOutput').innerHTML = recText;
// 2. STRATEGY
const planObj = data.plan || {};
const planHtml = `
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-span-1 md:col-span-2"><span class="font-semibold text-blue-900">Thought:</span> <span class="italic text-gray-600">${planObj.thought_process || "Processing query..."}</span></div>
<div><span class="font-semibold text-gray-700">Intent:</span> ${planObj.intent || "Unknown"}</div>
<div><span class="font-semibold text-gray-700">Detected Name:</span> ${planObj.customer_name || "None"}</div>
<div><span class="font-semibold text-gray-700">DB Check:</span> <span class="${planObj.requires_database ? 'text-green-600 font-bold' : 'text-gray-400'}">${planObj.requires_database ? "YES" : "NO"}</span></div>
<div><span class="font-semibold text-gray-700">Policy Check:</span> <span class="${planObj.requires_policy ? 'text-green-600 font-bold' : 'text-gray-400'}">${planObj.requires_policy ? "YES" : "NO"}</span></div>
</div>`;
document.getElementById('planOutput').innerHTML = planHtml;
// 3. CUSTOMER DATA
let customerHtml = "";
try {
let custData = data.customer_data;
if (typeof custData === 'string') {
if (custData.includes("NOT_FOUND") || custData.includes("N/A")) {
customerHtml = `<div class="bg-red-50 text-red-700 p-3 rounded font-bold text-center">β No Record Found</div>`;
} else {
customerHtml = `<div class="text-gray-600 text-xs italic">${custData}</div>`;
}
}
else if (typeof custData === 'object' && custData !== null) {
customerHtml = `<div class="grid grid-cols-2 gap-y-2 gap-x-4">`;
for (const [key, value] of Object.entries(custData)) {
let label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
let valDisplay = value;
if (value === false) valDisplay = '<span class="text-red-500 font-bold">False</span>';
if (value === true) valDisplay = '<span class="text-green-600 font-bold">True</span>';
if (key === 'credit_score' && typeof value === 'number') {
valDisplay = value >= 700
? `<span class="text-green-600 font-bold">${value}</span>`
: `<span class="text-amber-600 font-bold">${value}</span>`;
}
customerHtml += `<div class="text-gray-400 text-[10px] uppercase pt-1">${label}</div><div class="font-medium text-gray-800 break-words">${valDisplay}</div>`;
}
customerHtml += `</div>`;
}
} catch (e) {
console.error("Rendering Error:", e);
customerHtml = `<div class="text-gray-600 text-xs italic">Error loading data.</div>`;
}
document.getElementById('customerOutput').innerHTML = customerHtml;
// 4. POLICY CONTEXT
document.getElementById('policyOutput').textContent = data.policy_data || "No relevant policy snippets retrieved.";
resultsArea.classList.remove('hidden');
} else {
alert(result.message || "Access Denied.");
}
} catch (error) {
console.error(error);
alert("Server connection failed.");
} finally {
// Restore UI state
loadingState.classList.add('hidden');
submitBtn.disabled = false;
submitBtn.innerText = "Analyze";
}
}
document.getElementById("userQuery").addEventListener("keypress", function(event) {
if (event.key === "Enter") sendQuery();
});
</script>
</body>
</html> |