mirror of
https://github.com/Kartvaya2008/autostream-ai-agent
synced 2026-04-21 15:47:55 +00:00
246 lines
No EOL
9.2 KiB
JavaScript
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,'&').replace(/</g,'<')
|
|
.replace(/>/g,'>').replace(/"/g,'"');
|
|
}
|
|
|
|
showWelcome(); |