autostream-ai-agent/frontend/script.js
2026-04-12 21:03:52 +05:30

246 lines
No EOL
9.2 KiB
JavaScript

/* ═══════════════════════════════════════════
AutoStream AI Agent — script.js
Backend: FastAPI on port 8000
═══════════════════════════════════════════ */
const STORAGE_KEY = 'autostream_sessions';
const API_BASE = 'http://127.0.0.1:8000';
const API_CHAT = `${API_BASE}/chat`;
const API_RESET = `${API_BASE}/reset`;
let currentChatId = null;
let conversationHistory = [];
let isStreaming = false;
let sessionId = 'session_' + Math.random().toString(36).slice(2, 10);
const chatBox = document.getElementById('chat-box');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const typingEl = document.getElementById('typing-indicator');
const typingText = document.getElementById('typingText');
const inputCard = document.getElementById('inputCard');
// ── Welcome ───────────────────────────────
function showWelcome() {
if (!chatBox) return;
chatBox.innerHTML = '';
appendBotMessage(
`Hello. I'm <strong>AutoStream AI</strong>, your dedicated support assistant.<br><br>` +
`I can assist you with:<br>` +
`<span class="help-item">Pricing and plan details</span>` +
`<span class="help-item">Product features and capabilities</span>` +
`<span class="help-item">Getting started with AutoStream</span>` +
`<span class="help-item">Account sign-up and onboarding</span><br>` +
`How can I help you today?`
);
const btnRow = document.createElement('div');
btnRow.className = 'quick-actions';
btnRow.id = 'quickActions';
btnRow.innerHTML = `
<button class="qa-btn" onclick="quickSend('What is the pricing?')">Pricing</button>
<button class="qa-btn" onclick="quickSend('Tell me about the Pro plan')">Pro Plan</button>
<button class="qa-btn" onclick="quickSend('What features does AutoStream offer?')">Features</button>
<button class="qa-btn" onclick="quickSend('I would like to sign up for AutoStream')">Get Started</button>
`;
chatBox.appendChild(btnRow);
scrollToBottom();
}
function quickSend(text) {
const qa = document.getElementById('quickActions');
if (qa) qa.remove();
sendMessage(text);
}
// ── New Chat ──────────────────────────────
function newChat() {
fetch(API_RESET, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: sessionId })
}).catch(() => {});
sessionId = 'session_' + Math.random().toString(36).slice(2, 10);
currentChatId = null;
conversationHistory = [];
showWelcome();
if (userInput) { userInput.value = ''; autoResize(); userInput.focus(); }
}
// ── Send Message ──────────────────────────
async function sendMessage(overrideText) {
if (isStreaming) return;
const text = overrideText || (userInput ? userInput.value.trim() : '');
if (!text) {
if (inputCard) {
inputCard.classList.add('shake');
setTimeout(() => inputCard.classList.remove('shake'), 320);
}
return;
}
const qa = document.getElementById('quickActions');
if (qa) qa.remove();
if (userInput && !overrideText) { userInput.value = ''; autoResize(); }
appendUserMessage(text);
if (!currentChatId) currentChatId = Date.now().toString();
conversationHistory.push({ role: 'user', content: text });
isStreaming = true;
if (sendBtn) sendBtn.disabled = true;
showTyping('Thinking');
try {
await callBackend(text);
} catch (err) {
hideTyping();
appendBotMessage(
`⚠️ Could not connect to backend. Make sure FastAPI is running on port 8000.<br>` +
`<small style="color:var(--text-muted)">Run: <code>uvicorn app:app --reload --port 8000</code></small>`,
true
);
console.error('AutoStream error:', err);
}
isStreaming = false;
if (sendBtn) sendBtn.disabled = false;
}
// ── Backend Call ──────────────────────────
async function callBackend(userText) {
const labels = ['Thinking', 'Searching knowledge base', 'Crafting response'];
let li = 0;
const labelTimer = setInterval(() => {
li = (li + 1) % labels.length;
if (typingText) typingText.textContent = labels[li];
}, 1400);
// FastAPI expects: { message: str, session_id: str }
const response = await fetch(API_CHAT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: userText, session_id: sessionId })
});
clearInterval(labelTimer);
if (!response.ok) {
const err = await response.json().catch(() => ({}));
throw new Error(err.detail || `API error ${response.status}`);
}
// FastAPI returns: { reply, intent, confidence, lead_status, turn, session_id }
const data = await response.json();
const reply = data.reply || '(no response)';
const intent = data.intent;
const confidence = data.confidence;
const lead = data.lead_status || {};
hideTyping();
const formattedReply = escHtml(reply).replace(/\n/g, '<br>');
appendBotMessage(formattedReply, false, { intent, confidence, lead });
conversationHistory.push({ role: 'assistant', content: reply });
try {
const sessions = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
sessions[currentChatId] = {
title: userText.slice(0, 40),
timestamp: Date.now(),
history: conversationHistory
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions));
} catch {}
}
// ── Append Messages ───────────────────────
function appendBotMessage(html, isError = false, meta = null) {
if (!chatBox) return;
const row = document.createElement('div');
row.className = `msg-row bot${isError ? ' error' : ''}`;
let badgeHtml = '';
if (meta && meta.intent && !isError) {
const colors = {
greeting: '#22c55e',
info_query: '#60b8ff',
high_intent: '#ffb340',
unknown: '#f05252'
};
const col = colors[meta.intent] || '#888';
const conf = meta.confidence ? ` · ${Math.round(meta.confidence * 100)}%` : '';
// Lead slot dots
let leadDots = '';
if (meta.intent === 'high_intent' || meta.lead.name || meta.lead.email || meta.lead.platform) {
const dot = (filled, label) =>
`<span style="display:inline-flex;align-items:center;gap:3px;margin-right:7px">` +
`<span style="width:6px;height:6px;border-radius:50%;background:${filled ? '#7c5cff' : 'rgba(124,92,255,0.2)'}"></span>` +
`<span style="font-size:9px;color:var(--text-muted)">${label}</span></span>`;
leadDots = dot(!!meta.lead.name,'name') + dot(!!meta.lead.email,'email') + dot(!!meta.lead.platform,'platform');
leadDots = `<span style="margin-left:4px">${leadDots}</span>`;
}
badgeHtml = `<div style="margin-top:7px;display:flex;align-items:center;flex-wrap:wrap;gap:4px;">
<span style="display:inline-flex;align-items:center;gap:5px;font-size:10px;
font-family:'JetBrains Mono',monospace;padding:3px 9px;border-radius:6px;
background:${col}18;border:1px solid ${col}40;color:${col};letter-spacing:0.04em;">
<span style="width:5px;height:5px;border-radius:50%;background:${col}"></span>
${meta.intent}${conf}
</span>${leadDots}
</div>`;
}
row.innerHTML = `
<div class="msg-sender">AutoStream AI</div>
<div class="bubble">${html}${badgeHtml}</div>`;
chatBox.appendChild(row);
scrollToBottom();
}
function appendUserMessage(text) {
if (!chatBox) return;
const row = document.createElement('div');
row.className = 'msg-row user';
row.innerHTML = `
<div class="msg-sender">You</div>
<div class="bubble">${escHtml(text)}</div>`;
chatBox.appendChild(row);
scrollToBottom();
}
// ── Typing ────────────────────────────────
function showTyping(label = 'Thinking') {
if (typingText) typingText.textContent = label;
if (typingEl) typingEl.classList.add('visible');
scrollToBottom();
}
function hideTyping() {
if (typingEl) typingEl.classList.remove('visible');
}
// ── Textarea auto-resize ──────────────────
function autoResize() {
if (!userInput) return;
userInput.style.height = 'auto';
userInput.style.height = Math.min(userInput.scrollHeight, 160) + 'px';
}
if (userInput) {
userInput.addEventListener('input', autoResize);
userInput.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
userInput.addEventListener('focus', () => inputCard?.classList.add('focused'));
userInput.addEventListener('blur', () => inputCard?.classList.remove('focused'));
}
// ── Utils ─────────────────────────────────
function scrollToBottom() { if (chatBox) chatBox.scrollTop = chatBox.scrollHeight; }
function escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;')
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
showWelcome();