medicodeapp / frontend /src /app /features /chat /chat-bot.component.ts
Denisijcu's picture
upload files
c98875e
import { Component, signal, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
@Component({
selector: 'app-chat-bot',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="chat-wrapper" [class.is-active]="isOpen()">
<div class="chat-window">
<div class="chat-header">
<div class="status-dot"></div>
<div>
<h3>MediCore AI</h3>
<p>Vertex Support Core</p>
</div>
</div>
<div class="chat-body" #scrollContainer [scrollTop]="scrollContainer.scrollHeight">
@for (m of messages(); track $index) {
<div class="msg" [class.me]="m.role === 'user'" [class.bot]="m.role === 'bot'">
{{ m.text }}
</div>
}
@if (isTyping()) {
<div class="msg bot typing">
<span></span><span></span><span></span>
</div>
}
</div>
<div class="chat-footer">
<input [(ngModel)]="query" (keyup.enter)="send()"
placeholder="Ask about codes, patients, reports..."
[disabled]="isTyping()" />
<button (click)="send()" [disabled]="isTyping()">β†’</button>
</div>
</div>
<button class="fab" (click)="toggle()">
@if (!isOpen()) { πŸ’¬ } @else { βœ• }
</button>
</div>
`,
styles: [`
.chat-wrapper { position:fixed; bottom:30px; right:30px; z-index:9999; font-family:'Inter',sans-serif; }
.fab { width:60px; height:60px; border-radius:50%; background:#0EA5E9; color:white; border:none;
cursor:pointer; box-shadow:0 4px 15px rgba(0,0,0,0.2); font-size:24px; transition:0.3s; }
.fab:hover { transform:scale(1.1); background:#0284c7; }
.chat-window { position:absolute; bottom:80px; right:0; width:360px; height:520px;
background:white; border-radius:16px; box-shadow:0 10px 40px rgba(0,0,0,0.15);
display:none; flex-direction:column; overflow:hidden; border:1px solid #e2e8f0; }
.is-active .chat-window { display:flex; animation:slideUp 0.3s ease; }
.chat-header { background:linear-gradient(135deg,#0EA5E9,#0284c7); color:white; padding:15px;
display:flex; align-items:center; gap:10px; }
.status-dot { width:10px; height:10px; background:#4ade80; border-radius:50%;
box-shadow:0 0 6px #4ade80; animation:pulse 2s infinite; }
.chat-header h3 { margin:0; font-size:1rem; font-weight:700; }
.chat-header p { margin:0; font-size:0.75rem; opacity:0.85; }
.chat-body { flex:1; padding:15px; overflow-y:auto; background:#f8fafc;
display:flex; flex-direction:column; gap:10px; }
.msg { padding:10px 14px; border-radius:12px; font-size:0.875rem; max-width:85%;
line-height:1.5; white-space:pre-wrap; }
.msg.bot { background:white; border:1px solid #e2e8f0; align-self:flex-start;
border-radius:12px 12px 12px 4px; color:#1e293b; }
.msg.me { background:#0EA5E9; color:white; align-self:flex-end;
border-radius:12px 12px 4px 12px; }
.typing { display:flex; gap:4px; align-items:center; padding:12px 16px; }
.typing span { width:7px; height:7px; border-radius:50%; background:#94a3b8;
animation:bounce 1.2s infinite; }
.typing span:nth-child(2) { animation-delay:0.2s; }
.typing span:nth-child(3) { animation-delay:0.4s; }
.chat-footer { padding:12px; display:flex; gap:8px; border-top:1px solid #e2e8f0; background:white; }
.chat-footer input { flex:1; border:1px solid #cbd5e1; border-radius:8px; padding:8px 12px;
outline:none; font-size:0.875rem; }
.chat-footer input:focus { border-color:#0EA5E9; }
.chat-footer button { background:#0EA5E9; color:white; border:none; border-radius:8px;
padding:0 16px; cursor:pointer; font-size:1.1rem; font-weight:700; }
.chat-footer button:disabled { opacity:0.5; cursor:not-allowed; }
@keyframes slideUp { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:translateY(0); } }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }
@keyframes bounce { 0%,60%,100% { transform:translateY(0); } 30% { transform:translateY(-6px); } }
`]
})
export class ChatBotComponent {
private http = inject(HttpClient);
private router = inject(Router);
isOpen = signal(false);
isTyping = signal(false);
query = '';
messages = signal([{
role: 'bot',
text: 'Hi! I\'m MediCore AI. I can help you with ICD-10/CPT codes, patient management, encounters, and reports. What do you need?'
}]);
toggle() { this.isOpen.update(v => !v); }
send() {
if (!this.query.trim() || this.isTyping()) return;
const userRawInput = this.query;
// ── VERTEX CONTEXT INJECTION ──────────────────────────────────────────────
const medicodeContext = `
[MEDICODE PLATFORM CONTEXT]
You are the AI assistant for MediCode, a professional medical coding platform.
Platform features:
- Dashboard (/dashboard): Stats on patients, encounters, billing status (draft/coded/billed/paid), charts for encounters by month, top ICD-10 diagnoses and CPT procedures.
- Code Search (/codes): Search ICD-10 diagnostic codes and CPT procedure codes. Filter by category, billable status, pagination.
- Patients (/patients): Full CRUD β€” create, edit, delete patients with MRN, DOB, gender, insurance. Search by name or MRN.
- Patient Detail (/patients/:id): View patient info, manage encounters, attach ICD-10/CPT codes, advance encounter status through workflow: draft β†’ coded β†’ billed β†’ paid.
- Reports (/reports): View top diagnoses, top procedures, encounter volume by month, status breakdown. Export to PDF and Excel.
- AI Assistant: You β€” helping with code suggestions, billing questions, coding guidelines, HIPAA compliance tips.
ICD-10 codes: Diagnostic codes (e.g. I10 = Hypertension, E11.9 = Type 2 Diabetes, J18.9 = Pneumonia).
CPT codes: Procedure codes (e.g. 99214 = Office visit moderate, 71046 = Chest X-ray, 83036 = HbA1c).
Encounter statuses: draft (new), coded (codes assigned), billed (submitted to insurance), paid (payment received).
RULES: Be concise, professional, and helpful. Answer in English. If asked about a specific ICD-10 or CPT code, provide the code, description, and clinical context. If asked to navigate, mention the route.
User question: ${userRawInput}`;
this.messages.update(m => [...m, { role: 'user', text: userRawInput }]);
this.query = '';
this.isTyping.set(true);
this.http.post<any>('https://denisijcu-vertex-ai-bridge.hf.space/chat', {
user_input: medicodeContext
}).subscribe({
next: (res) => {
this.messages.update(m => [...m, { role: 'bot', text: res.response }]);
this.isTyping.set(false);
},
error: () => {
this.messages.update(m => [...m, { role: 'bot', text: 'Connection error. Please try again.' }]);
this.isTyping.set(false);
}
});
}
}