@@ -0,0 +1,167 @@
// AI Chat Panel — DashCaddy AI Intent Router frontend
( function ( ) {
let chatMessages = [ ] ;
let chatHistory = [ ] ;
function createChatPanel ( ) {
const existing = document . getElementById ( 'ai-chat-panel' ) ;
if ( existing ) existing . remove ( ) ;
const panel = document . createElement ( 'div' ) ;
panel . id = 'ai-chat-panel' ;
panel . style . cssText = `
position: fixed; bottom: 80px; right: 20px; width: 400px; max-width: 95vw;
height: 500px; max-height: 70vh; background: var(--bg-card, #1a1a2e);
border: 1px solid var(--border, #333); border-radius: 12px;
display: none; flex-direction: column; z-index: 9999;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
` ;
panel . innerHTML = `
<div style="padding: 14px 18px; border-bottom: 1px solid var(--border,#333); display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.3rem;">🤖</span>
<div style="flex:1;">
<div style="font-weight: 700; font-size: 0.95rem;">AI Assistant</div>
<div id="ai-status" style="font-size: 0.72rem; color: var(--text-muted,#666);">Pattern matching • No key needed</div>
</div>
<button onclick="document.getElementById('ai-chat-panel').style.display='none'" style="background:none;border:none;font-size:1.4rem;cursor:pointer;color:var(--text-muted,#888);">× </button>
</div>
<div id="ai-chat-messages" style="flex:1; overflow-y:auto; padding:14px; display:flex; flex-direction:column; gap:10px;">
<div style="background:rgba(99,102,241,0.08); border-radius:10px; padding:12px; font-size:0.85rem; color:var(--text-muted,#aaa);">
👋 Hi! I can help you deploy apps, diagnose issues, and manage your server. Try:
<div style="margin-top:8px; display:flex; flex-wrap:wrap; gap:6px;">
<button class="ai-suggestion" onclick="window.aiChat.send('"Deploy Plex"')" style="background:rgba(99,102,241,0.15); border:1px solid rgba(99,102,241,0.3); border-radius:16px; padding:4px 12px; font-size:0.78rem; cursor:pointer; color:#a5b4fc;">Deploy Plex</button>
<button class="ai-suggestion" onclick="window.aiChat.send('"I want to stream movies"')" style="background:rgba(99,102,241,0.15); border:1px solid rgba(99,102,241,0.3); border-radius:16px; padding:4px 12px; font-size:0.78rem; cursor:pointer; color:#a5b4fc;">Stream movies</button>
<button class="ai-suggestion" onclick="window.aiChat.send('"Block ads on my network"')" style="background:rgba(99,102,241,0.15); border:1px solid rgba(99,102,241,0.3); border-radius:16px; padding:4px 12px; font-size:0.78rem; cursor:pointer; color:#a5b4fc;">Block ads</button>
<button class="ai-suggestion" onclick="window.aiChat.send('"Is everything OK?')" style="background:rgba(99,102,241,0.15); border:1px solid rgba(99,102,241,0.3); border-radius:16px; padding:4px 12px; font-size:0.78rem; cursor:pointer; color:#a5b4fc;">System health</button>
</div>
</div>
</div>
<div style="padding:12px; border-top:1px solid var(--border,#333);">
<div style="display:flex; gap:8px;">
<input id="ai-chat-input" type="text" placeholder="Ask me anything..."
onkeypress="if(event.key==='Enter') window.aiChat.send()"
style="flex:1; background:var(--bg-input,#0f0f1a); border:1px solid var(--border,#333); border-radius:8px; padding:10px 14px; color:var(--text,#fff); font-size:0.88rem; outline:none;">
<button onclick="window.aiChat.send()" style="background:#6366f1; border:none; border-radius:8px; padding:0 16px; color:#fff; cursor:pointer; font-weight:600;">→</button>
</div>
</div>
` ;
document . body . appendChild ( panel ) ;
}
function toggle ( ) {
let panel = document . getElementById ( 'ai-chat-panel' ) ;
if ( ! panel ) { createChatPanel ( ) ; panel = document . getElementById ( 'ai-chat-panel' ) ; }
panel . style . display = panel . style . display === 'flex' ? 'none' : 'flex' ;
if ( panel . style . display === 'flex' ) document . getElementById ( 'ai-chat-input' ) . focus ( ) ;
}
function addMessage ( role , text , extra ) {
const container = document . getElementById ( 'ai-chat-messages' ) ;
if ( ! container ) return ;
const div = document . createElement ( 'div' ) ;
const isUser = role === 'user' ;
div . style . cssText = isUser
? 'align-self:flex-end; background:rgba(99,102,241,0.15); border-radius:10px 10px 2px 10px; padding:10px 14px; max-width:85%; font-size:0.85rem;'
: 'align-self:flex-start; background:rgba(255,255,255,0.05); border-radius:10px 10px 10px 2px; padding:10px 14px; max-width:85%; font-size:0.85rem;' ;
div . textContent = text ;
container . appendChild ( div ) ;
// Extra content (recommendations, action buttons)
if ( extra ) {
const extraDiv = document . createElement ( 'div' ) ;
extraDiv . style . cssText = 'align-self:flex-start; max-width:85%; margin-top:4px;' ;
if ( extra . recommendations ) {
extra . recommendations . forEach ( rec => {
const btn = document . createElement ( 'button' ) ;
btn . textContent = ` 📦 Deploy ${ rec . app } ` ;
btn . style . cssText = 'display:block; margin:4px 0; background:rgba(34,197,94,0.12); border:1px solid rgba(34,197,94,0.3); border-radius:8px; padding:8px 12px; cursor:pointer; font-size:0.8rem; color:#4ade80;' ;
btn . onclick = ( ) => window . aiChat . send ( 'Deploy ' + rec . app ) ;
extraDiv . appendChild ( btn ) ;
} ) ;
}
if ( extra . appId ) {
const btn = document . createElement ( 'button' ) ;
btn . textContent = ` 🚀 Deploy ${ extra . appId } ` ;
btn . style . cssText = 'display:block; margin:4px 0; background:rgba(34,197,94,0.12); border:1px solid rgba(34,197,94,0.3); border-radius:8px; padding:8px 12px; cursor:pointer; font-size:0.8rem; color:#4ade80;' ;
btn . onclick = ( ) => window . aiChat . deploy ( extra . appId ) ;
extraDiv . appendChild ( btn ) ;
}
if ( extra . suggestions ) {
const list = document . createElement ( 'div' ) ;
list . style . cssText = 'font-size:0.78rem; color:var(--text-muted,#888); padding:4px 0;' ;
list . innerHTML = extra . suggestions . map ( s => '• ' + s ) . join ( '<br>' ) ;
extraDiv . appendChild ( list ) ;
}
if ( extra . action === 'diagnose' ) {
const btn = document . createElement ( 'button' ) ;
btn . textContent = '🔍 Run Diagnostics' ;
btn . style . cssText = 'display:block; margin:4px 0; background:rgba(251,191,36,0.12); border:1px solid rgba(251,191,36,0.3); border-radius:8px; padding:8px 12px; cursor:pointer; font-size:0.8rem; color:#fbbf24;' ;
btn . onclick = ( ) => window . location . href = '#health' ;
extraDiv . appendChild ( btn ) ;
}
if ( extra . action === 'backup' ) {
const btn = document . createElement ( 'button' ) ;
btn . textContent = '💾 Create Backup' ;
btn . style . cssText = 'display:block; margin:4px 0; background:rgba(99,102,241,0.12); border:1px solid rgba(99,102,241,0.3); border-radius:8px; padding:8px 12px; cursor:pointer; font-size:0.8rem; color:#a5b4fc;' ;
btn . onclick = ( ) => { if ( window . createBackup ) window . createBackup ( ) ; } ;
extraDiv . appendChild ( btn ) ;
}
container . appendChild ( extraDiv ) ;
}
container . scrollTop = container . scrollHeight ;
}
async function send ( prefilled ) {
const input = document . getElementById ( 'ai-chat-input' ) ;
const message = prefilled || input . value . trim ( ) ;
if ( ! message ) return ;
input . value = '' ;
addMessage ( 'user' , message ) ;
// Typing indicator
const container = document . getElementById ( 'ai-chat-messages' ) ;
const typing = document . createElement ( 'div' ) ;
typing . id = 'ai-typing' ;
typing . style . cssText = 'align-self:flex-start; color:var(--text-muted,#666); font-size:0.8rem; padding:8px 14px;' ;
typing . textContent = '🤖 Thinking...' ;
container . appendChild ( typing ) ;
container . scrollTop = container . scrollHeight ;
try {
const res = await secureFetch ( '/api/v1/ai/intent' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { message , context : { } } )
} ) ;
const data = await res . json ( ) ;
typing . remove ( ) ;
if ( data . success !== false && data . data ) {
const r = data . data ;
const response = r . response || { } ;
addMessage ( 'ai' , response . message || r . response ? . message || 'Action: ' + ( r . action || r . intent ) , response ) ;
} else {
addMessage ( 'ai' , data . error || 'Sorry, something went wrong.' ) ;
}
} catch ( e ) {
typing . remove ( ) ;
addMessage ( 'ai' , 'Connection error: ' + e . message ) ;
}
}
async function deploy ( appId ) {
addMessage ( 'user' , 'Deploy ' + appId ) ;
addMessage ( 'ai' , ` Launching ${ appId } from the catalog... ` ) ;
// Trigger the app selector with this app
if ( window . openAppSelector ) {
window . openAppSelector ( appId ) ;
} else {
addMessage ( 'ai' , ` Search for ${ appId } in the App Catalog to deploy it. ` ) ;
}
}
window . aiChat = { send , deploy , toggle } ;
window . toggleAIChat = toggle ;
} ) ( ) ;