| Server IP : 69.72.244.102 / Your IP : 216.73.216.164 Web Server : LiteSpeed System : Linux s3434.fra1.stableserver.net 4.18.0-513.24.1.lve.2.el8.x86_64 #1 SMP Fri May 24 12:42:50 UTC 2024 x86_64 User : konzalta ( 1271) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/konzalta/constituency.work/ |
Upload File : |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ConstituencyOS β AI Assistant</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
:root{
--green:#1a7a4a;--green-light:#e8f5ee;--green-mid:#2d9e63;
--red:#dc2626;--red-light:#fdecea;
--amber:#f59e0b;--amber-light:#fffbeb;
--blue:#2563eb;--blue-light:#eff6ff;
--purple:#7c3aed;--purple-light:#f5f3ff;
--text:#111827;--muted:#6b7280;--light:#9ca3af;
--border:#e5e7eb;--bg:#f3f4f6;--white:#ffffff;
--sidebar-w:216px;--radius:10px;--radius-sm:6px;
--shadow:0 1px 3px rgba(0,0,0,.08);
}
html,body{height:100%;margin:0;padding:0;}
body{font-family:'Inter',sans-serif;font-size:13px;color:var(--text);background:var(--bg);}
.sidebar{position:fixed;top:0;left:0;bottom:0;width:var(--sidebar-w);background:var(--white);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow-y:auto;overflow-x:hidden;z-index:40;}
.sidebar-logo{display:flex;align-items:center;gap:9px;padding:14px 14px 12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.logo-icon{width:38px;height:38px;background:linear-gradient(135deg,#b91c1c,#1a7a4a);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:17px;flex-shrink:0;}
.logo-text strong{display:block;font-size:12.5px;font-weight:700;color:var(--green);}
.logo-text small{font-size:10px;color:var(--muted);line-height:1.3;}
.sidebar-profile{display:flex;align-items:center;gap:9px;padding:11px 14px;border-bottom:1px solid var(--border);flex-shrink:0;}
.s-av{width:36px;height:36px;border-radius:50%;background:#374151;color:white;font-weight:600;font-size:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;}
.s-av .dot{position:absolute;bottom:1px;right:1px;width:8px;height:8px;border-radius:50%;background:var(--green-mid);border:2px solid white;}
.pname{font-size:11.5px;font-weight:600;}.psub{font-size:10.5px;color:var(--muted);}
.sidebar-nav{flex:1;padding:6px 0;}
.nav-item{display:flex;align-items:center;gap:9px;padding:8px 14px;font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;position:relative;transition:background .12s,color .12s;white-space:nowrap;}
.nav-item:hover{background:var(--bg);color:var(--text);}
.nav-item.active{background:var(--green-light);color:var(--green);font-weight:600;}
.nav-item.active::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--green);border-radius:0 2px 2px 0;}
.nav-icon{font-size:14px;width:16px;text-align:center;flex-shrink:0;}
.nav-badge{margin-left:auto;background:var(--red);color:white;font-size:9.5px;font-weight:700;padding:1px 5px;border-radius:10px;}
.nav-new{margin-left:auto;background:var(--green);color:white;font-size:9px;font-weight:700;padding:2px 5px;border-radius:4px;}
.nav-chev{margin-left:auto;font-size:10px;color:var(--light);}
.sidebar-footer{padding:12px 14px;border-top:1px solid var(--border);flex-shrink:0;}
.right-panel{margin-left:var(--sidebar-w);display:flex;flex-direction:column;height:100vh;}
.topbar{flex-shrink:0;height:52px;background:var(--white);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;padding:0 18px;}
.menu-btn{font-size:17px;cursor:pointer;color:var(--muted);}
.const-sel{display:flex;align-items:center;gap:6px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:5px 10px;cursor:pointer;font-size:12px;font-weight:500;}
.srch{flex:1;max-width:360px;display:flex;align-items:center;gap:7px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 11px;color:var(--muted);font-size:12px;}
.srch input{border:none;background:transparent;outline:none;width:100%;font-size:12px;font-family:inherit;}
.srch kbd{margin-left:auto;font-size:9.5px;background:var(--border);padding:1px 5px;border-radius:3px;font-family:inherit;}
.tbr{margin-left:auto;display:flex;align-items:center;gap:8px;}
.ibt{position:relative;width:32px;height:32px;border-radius:50%;background:var(--bg);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:15px;}
.ibt .bdg{position:absolute;top:-3px;right:-3px;background:var(--red);color:white;font-size:9px;font-weight:700;width:15px;height:15px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:1.5px solid white;}
.ibt .bdg.grn{background:var(--green-mid);}
.upill{display:flex;align-items:center;gap:7px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:3px 8px;cursor:pointer;font-size:12px;font-weight:500;}
/* ===== CHAT LAYOUT ===== */
.chat-shell{flex:1;display:flex;min-height:0;}
/* Left: conversation history rail */
.history-rail{width:230px;flex-shrink:0;background:var(--white);border-right:1px solid var(--border);display:flex;flex-direction:column;}
.history-hdr{padding:14px;border-bottom:1px solid var(--border);}
.new-chat-btn{width:100%;display:flex;align-items:center;justify-content:center;gap:6px;background:var(--green);color:white;border:none;border-radius:var(--radius-sm);padding:9px;font-size:12px;font-weight:600;cursor:pointer;}
.new-chat-btn:hover{background:var(--green-mid);}
.history-list{flex:1;overflow-y:auto;padding:8px;}
.history-group-lbl{font-size:10px;font-weight:700;color:var(--light);text-transform:uppercase;letter-spacing:.5px;padding:8px 8px 4px;}
.history-item{padding:9px 10px;border-radius:var(--radius-sm);font-size:11.5px;color:var(--muted);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px;}
.history-item:hover{background:var(--bg);color:var(--text);}
.history-item.active{background:var(--green-light);color:var(--green);font-weight:600;}
/* Center: chat column */
.chat-col{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bg);}
.chat-scroll{flex:1;overflow-y:auto;padding:24px 0;}
.chat-inner{max-width:760px;margin:0 auto;padding:0 24px;display:flex;flex-direction:column;gap:18px;}
/* Welcome state */
.welcome{text-align:center;padding:50px 20px 10px;}
.welcome-icon{width:56px;height:56px;border-radius:16px;background:linear-gradient(135deg,#1a7a4a,#2d9e63);display:flex;align-items:center;justify-content:center;font-size:26px;margin:0 auto 16px;color:white;}
.welcome-title{font-size:20px;font-weight:700;}
.welcome-sub{font-size:13px;color:var(--muted);margin-top:6px;max-width:420px;margin-left:auto;margin-right:auto;line-height:1.5;}
.suggestion-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:600px;margin:24px auto 0;}
.suggestion-card{background:var(--white);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;text-align:left;cursor:pointer;transition:box-shadow .15s,border-color .15s;}
.suggestion-card:hover{box-shadow:0 4px 14px rgba(0,0,0,.08);border-color:var(--green-mid);}
.suggestion-icon{font-size:16px;margin-bottom:6px;}
.suggestion-text{font-size:12px;font-weight:600;line-height:1.4;}
/* Messages */
.msg-row{display:flex;gap:12px;align-items:flex-start;}
.msg-row.user{flex-direction:row-reverse;}
.msg-avatar{width:30px;height:30px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:14px;color:white;}
.msg-avatar.ai{background:linear-gradient(135deg,#1a7a4a,#2d9e63);}
.msg-avatar.user{background:#374151;font-size:11px;font-weight:700;}
.msg-bubble{max-width:78%;padding:11px 15px;border-radius:14px;font-size:12.5px;line-height:1.6;}
.msg-row.ai .msg-bubble{background:var(--white);border:1px solid var(--border);border-top-left-radius:4px;}
.msg-row.user .msg-bubble{background:var(--green);color:white;border-top-right-radius:4px;}
.msg-bubble p{margin-bottom:8px;}
.msg-bubble p:last-child{margin-bottom:0;}
.msg-bubble ul{margin:6px 0 6px 18px;}
.msg-bubble li{margin-bottom:3px;}
.msg-bubble strong{font-weight:700;}
.msg-time{font-size:9.5px;color:var(--light);margin-top:4px;}
.msg-row.user .msg-time{text-align:right;}
/* Citation chips inside AI messages */
.cite-chip{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:600;background:var(--green-light);color:var(--green);padding:3px 9px;border-radius:20px;margin:6px 4px 0 0;cursor:pointer;text-decoration:none;}
.cite-chip:hover{background:#d1f0dd;}
/* Follow-up suggestion chips */
.followup-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;margin-left:42px;}
.followup-chip{font-size:11px;font-weight:500;background:var(--white);border:1px solid var(--border);color:var(--text);padding:6px 12px;border-radius:20px;cursor:pointer;transition:background .15s,border-color .15s;}
.followup-chip:hover{background:var(--green-light);border-color:var(--green-mid);color:var(--green);}
/* Typing indicator */
.typing-row{display:flex;gap:12px;align-items:flex-start;}
.typing-bubble{background:var(--white);border:1px solid var(--border);border-radius:14px;border-top-left-radius:4px;padding:13px 16px;display:flex;gap:4px;align-items:center;}
.typing-dot{width:6px;height:6px;border-radius:50%;background:var(--light);animation:typingBounce 1.2s infinite;}
.typing-dot:nth-child(2){animation-delay:.15s;}
.typing-dot:nth-child(3){animation-delay:.3s;}
@keyframes typingBounce{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-4px);opacity:1;}}
/* Composer */
.composer-wrap{flex-shrink:0;border-top:1px solid var(--border);background:var(--white);padding:14px 24px 16px;}
.composer-inner{max-width:760px;margin:0 auto;}
.disclaimer{font-size:10px;color:var(--light);text-align:center;margin-top:8px;}
.composer-box{display:flex;align-items:flex-end;gap:8px;background:var(--bg);border:1px solid var(--border);border-radius:14px;padding:8px 8px 8px 16px;transition:border-color .15s;}
.composer-box:focus-within{border-color:var(--green);background:var(--white);}
.composer-box textarea{flex:1;border:none;background:transparent;outline:none;resize:none;font-family:inherit;font-size:13px;color:var(--text);max-height:120px;line-height:1.5;padding:6px 0;}
.composer-box textarea::placeholder{color:var(--light);}
.composer-actions{display:flex;align-items:center;gap:4px;}
.composer-icon-btn{width:32px;height:32px;border-radius:50%;border:none;background:transparent;display:flex;align-items:center;justify-content:center;font-size:15px;cursor:pointer;color:var(--muted);}
.composer-icon-btn:hover{background:var(--border);}
.send-btn{width:34px;height:34px;border-radius:50%;border:none;background:var(--green);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;cursor:pointer;flex-shrink:0;transition:background .15s;}
.send-btn:hover{background:var(--green-mid);}
.send-btn:disabled{background:var(--border);color:var(--light);cursor:not-allowed;}
/* Quick prompt strip above composer when conversation active */
.quick-strip{display:flex;gap:6px;overflow-x:auto;padding-bottom:10px;max-width:760px;margin:0 auto;}
.quick-chip{flex-shrink:0;font-size:11px;font-weight:500;background:var(--bg);border:1px solid var(--border);color:var(--muted);padding:6px 12px;border-radius:20px;cursor:pointer;white-space:nowrap;}
.quick-chip:hover{background:var(--green-light);border-color:var(--green-mid);color:var(--green);}
</style>
</head>
<body>
<aside class="sidebar">
<div class="sidebar-logo"><div class="logo-icon">ποΈ</div><div class="logo-text"><strong>ConstituencyOS</strong><small>Everything in your constituency,<br>in one place.</small></div></div>
<div class="sidebar-profile"><div class="s-av">DM<div class="dot"></div></div><div><div class="pname">Hon. David Mwangi, MP</div><div class="psub">Embakasi South</div></div></div>
<nav class="sidebar-nav">
<div class="nav-item" onclick="location.href='index.html'"><span class="nav-icon">π </span>Dashboard</div>
<div class="nav-item" onclick="location.href='constituents.html'"><span class="nav-icon">π₯</span>Constituents</div>
<div class="nav-item" onclick="location.href='complaints.html'"><span class="nav-icon">π</span>Complaints<span class="nav-badge">9</span></div>
<div class="nav-item" onclick="location.href='projects.html'"><span class="nav-icon">π</span>Projects</div>
<div class="nav-item" onclick="location.href='bursaries.html'"><span class="nav-icon">π</span>Bursaries</div>
<div class="nav-item" onclick="location.href='events.html'"><span class="nav-icon">π
</span>Events</div>
<div class="nav-item" onclick="location.href='social_protection.html'"><span class="nav-icon">π‘οΈ</span>Social Protection</div>
<div class="nav-item" onclick="location.href='ward_management.html'"><span class="nav-icon">ποΈ</span>Ward Management</div>
<div class="nav-item" onclick="location.href='finance.html'"><span class="nav-icon">π°</span>Finance</div>
<div class="nav-item" onclick="location.href='documents.html'"><span class="nav-icon">π</span>Documents</div>
<div class="nav-item" onclick="location.href='staff_management.html'"><span class="nav-icon">π€</span>Staff Management</div>
<div class="nav-item" onclick="location.href='reports_analytics.html'"><span class="nav-icon">π</span>Reports & Analytics</div>
<div class="nav-item active"><span class="nav-icon">π€</span>AI Assistant<span class="nav-new">New</span></div>
<div class="nav-item" onclick="location.href='settings.html'"><span class="nav-icon">βοΈ</span>Settings</div>
<div class="nav-item" onclick="location.href='public_portal.html'"><span class="nav-icon">π</span>Public Portal</div>
</nav>
<div class="sidebar-footer"><div style="display:flex;align-items:center;gap:9px;cursor:pointer"><span>β</span><div><div style="font-size:11.5px;font-weight:600">Need Help?</div><div style="font-size:10.5px;color:var(--muted)">Contact Support</div></div></div></div>
</aside>
<div class="right-panel">
<header class="topbar">
<span class="menu-btn">β°</span>
<div class="const-sel"><span style="font-size:13px">ποΈ</span>Embakasi South Constituency<span style="font-size:10px;color:var(--muted)">βΌ</span></div>
<div class="srch"><span>π</span><input type="text" placeholder="Search for anythingβ¦"><kbd>βK</kbd></div>
<div class="tbr">
<div class="ibt">π<div class="bdg">8</div></div>
<div class="ibt">βοΈ<div class="bdg grn">3</div></div>
<div class="upill"><div class="s-av" style="width:26px;height:26px;font-size:10px">DM</div>Hon. David Mwangi<span style="font-size:10px;color:var(--muted)">βΌ</span></div>
</div>
</header>
<div class="chat-shell">
<!-- History rail -->
<div class="history-rail">
<div class="history-hdr">
<button class="new-chat-btn" onclick="newChat()">οΌ New Chat</button>
</div>
<div class="history-list">
<div class="history-group-lbl">Today</div>
<div class="history-item active" onclick="selectHistory(this)">Current conversation</div>
<div class="history-group-lbl">Previous 7 Days</div>
<div class="history-item" onclick="selectHistory(this)">Budget utilisation by ward</div>
<div class="history-item" onclick="selectHistory(this)">Draft Pipeline Ward baraza speech</div>
<div class="history-item" onclick="selectHistory(this)">Bursary approval summary β May</div>
<div class="history-item" onclick="selectHistory(this)">Complaints trending in Kwa Njenga</div>
<div class="history-group-lbl">Previous 30 Days</div>
<div class="history-item" onclick="selectHistory(this)">Q3 financial statement summary</div>
<div class="history-item" onclick="selectHistory(this)">Social protection enrolment trends</div>
<div class="history-item" onclick="selectHistory(this)">Project delay risk assessment</div>
</div>
</div>
<!-- Chat column -->
<div class="chat-col">
<div class="chat-scroll" id="chatScroll">
<div class="chat-inner" id="chatInner">
<!-- Welcome state -->
<div class="welcome" id="welcomeState">
<div class="welcome-icon">π€</div>
<div class="welcome-title">Hi David, how can I help today?</div>
<div class="welcome-sub">I can pull insights from your constituency data β complaints, projects, budgets, bursaries, and more. Ask me anything or try a suggestion below.</div>
<div class="suggestion-grid">
<div class="suggestion-card" onclick="sendSuggestion(this)">
<div class="suggestion-icon">π</div>
<div class="suggestion-text">Summarize this week's pending complaints</div>
</div>
<div class="suggestion-card" onclick="sendSuggestion(this)">
<div class="suggestion-icon">π°</div>
<div class="suggestion-text">What's our current budget utilisation?</div>
</div>
<div class="suggestion-card" onclick="sendSuggestion(this)">
<div class="suggestion-icon">βοΈ</div>
<div class="suggestion-text">Draft a speech for the Pipeline Ward baraza</div>
</div>
<div class="suggestion-card" onclick="sendSuggestion(this)">
<div class="suggestion-icon">π</div>
<div class="suggestion-text">How are this year's bursary applications trending?</div>
</div>
<div class="suggestion-card" onclick="sendSuggestion(this)">
<div class="suggestion-icon">π</div>
<div class="suggestion-text">Which projects are at risk of delay?</div>
</div>
<div class="suggestion-card" onclick="sendSuggestion(this)">
<div class="suggestion-icon">ποΈ</div>
<div class="suggestion-text">Compare ward performance this quarter</div>
</div>
</div>
</div>
</div>
</div>
<!-- Composer -->
<div class="composer-wrap">
<div class="quick-strip" id="quickStrip" style="display:none;">
<div class="quick-chip" onclick="sendQuick(this)">Open complaints by ward</div>
<div class="quick-chip" onclick="sendQuick(this)">Bursary approval rate</div>
<div class="quick-chip" onclick="sendQuick(this)">Project delay risks</div>
<div class="quick-chip" onclick="sendQuick(this)">Today's schedule</div>
</div>
<div class="composer-inner">
<div class="composer-box">
<textarea id="composerInput" rows="1" placeholder="Ask about complaints, projects, budgets, bursariesβ¦" onkeydown="handleKeydown(event)" oninput="autoGrow(this)"></textarea>
<div class="composer-actions">
<button class="composer-icon-btn" title="Attach file">π</button>
<button class="send-btn" id="sendBtn" onclick="sendMessage()" disabled>β€</button>
</div>
</div>
<div class="disclaimer">AI Assistant is a prototype feature. Responses are illustrative and based on demo data β verify before acting.</div>
</div>
</div>
</div>
</div>
</div>
<script>
const composerInput = document.getElementById('composerInput');
const sendBtn = document.getElementById('sendBtn');
const chatInner = document.getElementById('chatInner');
const chatScroll = document.getElementById('chatScroll');
const welcomeState = document.getElementById('welcomeState');
const quickStrip = document.getElementById('quickStrip');
let conversationStarted = false;
function autoGrow(el){
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
sendBtn.disabled = el.value.trim().length === 0;
}
function handleKeydown(e){
if(e.key==='Enter' && !e.shiftKey){
e.preventDefault();
sendMessage();
}
}
function nowTime(){
return new Date().toLocaleTimeString('en-GB',{hour:'2-digit',minute:'2-digit'});
}
function scrollToBottom(){
setTimeout(()=>{ chatScroll.scrollTop = chatScroll.scrollHeight; }, 50);
}
function startConversationIfNeeded(){
if(!conversationStarted){
welcomeState.style.display = 'none';
quickStrip.style.display = 'flex';
conversationStarted = true;
}
}
function appendUserMessage(text){
startConversationIfNeeded();
const row = document.createElement('div');
row.className = 'msg-row user';
row.innerHTML = `
<div class="msg-avatar user">DM</div>
<div>
<div class="msg-bubble">${escapeHtml(text)}</div>
<div class="msg-time">${nowTime()}</div>
</div>
`;
chatInner.appendChild(row);
scrollToBottom();
}
function escapeHtml(s){
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function appendTypingIndicator(){
const row = document.createElement('div');
row.className = 'typing-row';
row.id = 'typingRow';
row.innerHTML = `
<div class="msg-avatar ai">π€</div>
<div class="typing-bubble">
<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>
</div>
`;
chatInner.appendChild(row);
scrollToBottom();
}
function removeTypingIndicator(){
const row = document.getElementById('typingRow');
if(row) row.remove();
}
function appendAiMessage(html, citations, followups){
const row = document.createElement('div');
row.className = 'msg-row ai';
let citeHtml = '';
if(citations && citations.length){
citeHtml = citations.map(c=>`<a class="cite-chip" href="${c.href}">π ${c.label}</a>`).join('');
}
row.innerHTML = `
<div class="msg-avatar ai">π€</div>
<div>
<div class="msg-bubble">${html}${citeHtml}</div>
<div class="msg-time">${nowTime()}</div>
</div>
`;
chatInner.appendChild(row);
if(followups && followups.length){
const fRow = document.createElement('div');
fRow.className = 'followup-row';
fRow.innerHTML = followups.map(f=>`<div class="followup-chip" onclick="sendFollowup(this)">${f}</div>`).join('');
chatInner.appendChild(fRow);
}
scrollToBottom();
}
// ===== Canned response engine =====
function getResponse(query){
const q = query.toLowerCase();
if(q.includes('complaint')){
return {
html: `<p>Here's the current complaints snapshot for Embakasi South:</p>
<ul>
<li><strong>23 complaints</strong> are currently pending across all wards (β²12% vs last week)</li>
<li><strong>Kwa Njenga Ward</strong> and <strong>Pipeline Ward</strong> have the highest open counts</li>
<li><strong>3 high-priority</strong> issues remain unassigned, including a broken bridge in Imara Daima</li>
<li>Average resolution time is <strong>2.4 days</strong>, 20% faster than target</li>
</ul>
<p>Want me to draft assignment notes for the unassigned high-priority items?</p>`,
citations: [{label:'View Complaints', href:'complaints.html'}],
followups: ['Draft assignment notes', 'Show high-priority only', 'Compare to last month']
};
}
if(q.includes('budget') || q.includes('finance') || q.includes('financ')){
return {
html: `<p>Budget utilisation for FY 2023/2024 stands at <strong>63%</strong>:</p>
<ul>
<li><strong>KSh 123.4M</strong> disbursed of a <strong>KSh 195.2M</strong> total allocation</li>
<li><strong>KSh 71.8M</strong> remains unutilised, with Q3 disbursements due in June</li>
<li><strong>Roads & Infrastructure</strong> and <strong>Health</strong> sectors have the highest absolute spend</li>
<li><strong>3 projects</strong> are flagged pending audit review, totalling KSh 4.2M</li>
</ul>
<p>I can break this down by ward or by sector if that's more useful.</p>`,
citations: [{label:'View Finance', href:'finance.html'}],
followups: ['Break down by ward', 'Break down by sector', 'Show flagged audits']
};
}
if(q.includes('speech') || q.includes('baraza') || q.includes('draft')){
return {
html: `<p>Here's a draft opening for the Pipeline Ward baraza:</p>
<p style="background:#f3f4f6;padding:10px 12px;border-radius:8px;font-style:italic;">"Wananchi wa Pipeline, asanteni kwa kunikaribisha leo. Tumepiga hatua kubwa katika mradi wa maji wa Kiandutu β sasa umefikia asilimia 68. Tutaendelea kushirikiana ili kuhakikisha kila kaya inapata maji safi. Pia, leo tutazungumzia kuhusu barabara, bursary, na masuala mengine mnayotaka kuyajadili..."</p>
<p>This covers the water project update and opens the floor for community input. I can extend it with road project updates or bursary disbursement figures if you'd like.</p>`,
citations: [{label:'View Ward Management', href:'ward_management.html'}],
followups: ['Add road project update', 'Add bursary figures', 'Make it shorter']
};
}
if(q.includes('bursary') || q.includes('bursaries') || q.includes('scholarship')){
return {
html: `<p>Bursary applications for FY 2023/2024 so far:</p>
<ul>
<li><strong>3,248 total applications</strong> received, up 14% on last year</li>
<li><strong>1,892 approved</strong> (58% approval rate), <strong>1,156 under review</strong></li>
<li><strong>200 rejected</strong>, mostly due to incomplete documentation</li>
<li><strong>Pipeline</strong> and <strong>Imara Daima</strong> wards have the highest application volume</li>
</ul>
<p>Would you like a list of applications still pending review for more than 14 days?</p>`,
citations: [{label:'View Bursaries', href:'bursaries.html'}],
followups: ['Show overdue reviews', 'Approval rate by ward', 'Export application list']
};
}
if(q.includes('project') && (q.includes('delay') || q.includes('risk'))){
return {
html: `<p><strong>5 projects</strong> are currently flagged as delayed:</p>
<ul>
<li><strong>Pipeline Road Rehabilitation</strong> β 30% complete, blocked on contractor materials</li>
<li><strong>Imara Daima Footbridge Repair</strong> β 10% complete, urgent due to safety risk</li>
<li>3 additional projects are tracking 1β3 weeks behind schedule</li>
</ul>
<p>The footbridge repair is the highest priority given the safety concern. Want me to draft a follow-up to the contractor?</p>`,
citations: [{label:'View Projects', href:'projects.html'}],
followups: ['Draft contractor follow-up', 'Show all delayed projects', 'Notify ward administrator']
};
}
if(q.includes('ward') && (q.includes('compar') || q.includes('performance'))){
return {
html: `<p>Quick ward performance comparison for this quarter:</p>
<ul>
<li><strong>Umoja Ward</strong> leads in complaint resolution rate at 97%</li>
<li><strong>Pipeline Ward</strong> has the highest budget utilisation at 88%</li>
<li><strong>Kwa Njenga Ward</strong> has the lowest resolution rate (84%) β water-related issues are the main driver</li>
<li>Bursary recipients are fairly proportional to ward population across the board</li>
</ul>
<p>Full breakdown with population-adjusted metrics is available in Reports & Analytics.</p>`,
citations: [{label:'View Reports & Analytics', href:'reports_analytics.html'}],
followups: ['Focus on Kwa Njenga', 'Show population-adjusted view', 'Export comparison table']
};
}
if(q.includes('schedule') || q.includes('today') || q.includes('event')){
return {
html: `<p>Here's what's on today, 20 May 2024:</p>
<ul>
<li><strong>8:00 AM</strong> β Constituency Staff Meeting, Parliamentary Office</li>
<li><strong>10:00 AM</strong> β School Opening, St. Bakhita Primary (Umoja II)</li>
<li><strong>1:00 PM</strong> β Baraza, Pipeline Ward Office</li>
<li><strong>4:00 PM</strong> β Youth Group Fundraiser, Mihango Social Hall</li>
<li><strong>6:30 PM</strong> β Church Service, ACK Imara Daima</li>
</ul>
<p>You have 6 events today and tomorrow combined. Want a prep summary for the baraza?</p>`,
citations: [{label:'View Events', href:'events.html'}],
followups: ['Prep summary for baraza', 'Show tomorrow\'s events', 'Add a new event']
};
}
if(q.includes('social protection') || q.includes('elderly') || q.includes('pwd') || q.includes('vulnerable')){
return {
html: `<p>Social Protection programme overview:</p>
<ul>
<li><strong>1,024 active beneficiaries</strong> across 3 categories</li>
<li><strong>312 Elderly</strong>, <strong>278 PWDs</strong>, <strong>434 Orphans & Vulnerable Children</strong></li>
<li>Q2 stipend disbursement is <strong>50% complete</strong>, on track for June payout</li>
<li><strong>2 cases</strong> are flagged under review pending document verification</li>
</ul>
<p>Let me know if you'd like the ward-by-ward breakdown.</p>`,
citations: [{label:'View Social Protection', href:'social_protection.html'}],
followups: ['Ward-by-ward breakdown', 'Show flagged cases', 'Payment schedule']
};
}
if(q.includes('staff') || q.includes('team') || q.includes('officer')){
return {
html: `<p>Your office currently has <strong>24 staff members</strong> across 4 departments:</p>
<ul>
<li><strong>Administration</strong> β 9 staff, including all 6 Ward Administrators</li>
<li><strong>Social Services</strong> β 6 social workers</li>
<li><strong>Field Operations</strong> β 5 coordinators</li>
<li><strong>Technical / Engineering</strong> β 3 engineers</li>
</ul>
<p>2 staff members are currently on leave. Want their tasks reassigned temporarily?</p>`,
citations: [{label:'View Staff Management', href:'staff_management.html'}],
followups: ['Reassign tasks', 'Show org chart', 'Who is on leave?']
};
}
if(q.includes('hello') || q.includes('hi') || q.includes('hey')){
return {
html: `<p>Hello, David! I'm ready to help β ask me about complaints, projects, the budget, bursaries, social protection, or your team. I can also help draft messages and speeches.</p>`,
citations: [],
followups: ['Summarize complaints', 'Budget overview', 'Today\'s schedule']
};
}
// Default fallback
return {
html: `<p>I don't have a specific answer for that yet in this demo, but here's what I can help with:</p>
<ul>
<li>Summarising complaints, projects, and budget data</li>
<li>Drafting speeches, letters, and constituent messages</li>
<li>Comparing ward performance and flagging risks</li>
<li>Answering questions about bursaries and social protection</li>
</ul>
<p>Try rephrasing, or pick one of the suggestions below.</p>`,
citations: [],
followups: ['Summarize complaints', 'Budget overview', 'Draft a speech']
};
}
function processAndRespond(text){
appendTypingIndicator();
const delay = 900 + Math.random()*700;
setTimeout(()=>{
removeTypingIndicator();
const r = getResponse(text);
appendAiMessage(r.html, r.citations, r.followups);
}, delay);
}
function sendMessage(){
const text = composerInput.value.trim();
if(!text) return;
appendUserMessage(text);
composerInput.value = '';
composerInput.style.height = 'auto';
sendBtn.disabled = true;
processAndRespond(text);
}
function sendSuggestion(el){
const text = el.querySelector('.suggestion-text').textContent;
appendUserMessage(text);
processAndRespond(text);
}
function sendQuick(el){
const text = el.textContent;
appendUserMessage(text);
processAndRespond(text);
}
function sendFollowup(el){
const text = el.textContent;
appendUserMessage(text);
processAndRespond(text);
}
function newChat(){
chatInner.innerHTML = '';
chatInner.appendChild(welcomeState);
welcomeState.style.display = 'block';
quickStrip.style.display = 'none';
conversationStarted = false;
document.querySelectorAll('.history-item').forEach(h=>h.classList.remove('active'));
document.querySelector('.history-item').classList.add('active');
}
function selectHistory(el){
document.querySelectorAll('.history-item').forEach(h=>h.classList.remove('active'));
el.classList.add('active');
if(el.textContent !== 'Current conversation'){
// Simulate loading a past conversation
chatInner.innerHTML = '';
welcomeState.style.display = 'none';
quickStrip.style.display = 'flex';
conversationStarted = true;
appendUserMessage(el.textContent);
setTimeout(()=>{
const r = getResponse(el.textContent);
appendAiMessage(r.html, r.citations, r.followups);
}, 200);
}
}
</script>
</body>
</html>