Edit Code · SiloAI Chat
Back to CMS
Open Page
Source Code Viewer
17277 bytes loaded
<?php declare(strict_types=1); if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } function ai_read_json(string $path): array { if (!is_file($path)) return []; $data = json_decode((string)file_get_contents($path), true); return is_array($data) ? $data : []; } function ai_h($value): string { return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); } function ai_sanitize_uid(string $uid): string { $uid = preg_replace('/[^a-zA-Z0-9_\-:.@]/', '_', trim($uid)); return $uid ?: 'demo-user-001'; } function ai_initials(string $name, string $email = ''): string { $base = trim($name) ?: trim($email) ?: 'User'; $parts = preg_split('/\s+/', $base) ?: []; if (count($parts) >= 2) return strtoupper(substr($parts[0], 0, 1) . substr($parts[1], 0, 1)); return strtoupper(substr($base, 0, 2)); } function ai_normalize_avatar(string $avatar): string { $avatar = trim($avatar); if ($avatar === '') return ''; if (str_starts_with($avatar, 'http://') || str_starts_with($avatar, 'https://')) return $avatar; if (str_starts_with($avatar, '/')) return 'https://silocloud.dev' . $avatar; return 'https://silocloud.dev/siloblockchain/cloud/user-files/' . ltrim($avatar, '/'); } function ai_authority_profile(string $requestedUid = ''): array { $authority = $_SESSION['siloarray_authority'] ?? $_SESSION['silo_customer_user'] ?? []; if (!is_array($authority)) $authority = []; foreach ([ '/var/www/html/silocloud/apps/siloarray/data/runtime-authority.json', '/var/www/html/siloarray.com/data/runtime-authority.json', ] as $path) { $fileProfile = ai_read_json($path); if (!$fileProfile) continue; $fileUid = ai_sanitize_uid((string)($fileProfile['customer_uid'] ?? $fileProfile['user_uid'] ?? '')); $sessionUid = ai_sanitize_uid((string)($authority['customer_uid'] ?? $authority['user_uid'] ?? '')); if ($requestedUid === '' || $fileUid === ai_sanitize_uid($requestedUid) || $sessionUid === $fileUid) { $authority = array_merge($authority, $fileProfile); break; } } $uid = ai_sanitize_uid((string)($_GET['user_uid'] ?? $authority['customer_uid'] ?? $authority['user_uid'] ?? $_SESSION['user_uid'] ?? 'demo-user-001')); $email = (string)($_GET['email'] ?? $authority['email'] ?? $_SESSION['email'] ?? $_SESSION['user_email'] ?? ''); $name = (string)($_GET['display_name'] ?? $authority['display_name'] ?? $authority['workspace_name'] ?? $authority['name'] ?? $email ?: 'SiloAI User'); $role = (string)($authority['role'] ?? $_SESSION['role'] ?? 'user'); $avatar = ai_normalize_avatar((string)($authority['avatar_url'] ?? $authority['avatar'] ?? $authority['profile_image'] ?? $authority['photo_url'] ?? '')); return [ 'uid' => $uid, 'email' => $email, 'name' => $name, 'role' => $role, 'avatar' => $avatar, 'initials' => ai_initials($name, $email), ]; } $profile = ai_authority_profile((string)($_GET['user_uid'] ?? '')); $platformQuery = [ 'source' => 'siloai', 'user_uid' => $profile['uid'], 'display_name' => $profile['name'], ]; if ($profile['email'] !== '') $platformQuery['email'] = $profile['email']; $links = [ [ 'label' => 'SiloDrive', 'url' => 'https://silocloud.dev/silocloud/apps/drive/index.php?' . http_build_query($platformQuery), 'icon' => '/silocloud/apps/siloai/assets/platforms/silodrive-logo.png', ], [ 'label' => 'SiloArray', 'url' => 'https://siloarray.com/runtime-appliance/launch-from-drive.php?user_uid=' . rawurlencode($profile['uid']), 'icon' => '/silocloud/apps/siloai/assets/platforms/siloarray-logo.png', ], [ 'label' => 'SiloTalk', 'url' => 'https://siloarray.com/runtime-appliance/launch-communications.php?app=silotalk', 'icon' => '/silocloud/apps/siloai/assets/platforms/silotalk-logo.png', ], [ 'label' => 'SiloStreamDeck', 'url' => 'https://silostreamdeck.com/products.php?' . http_build_query($platformQuery), 'icon' => '/silocloud/apps/siloai/assets/platforms/streamdeck-logo.png', ], ]; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>SiloAI Chat</title> <link rel="icon" type="image/png" href="/silocloud/assets/images/cloud.png"> <style> :root{--bg:#f7f9fc;--panel:#fff;--line:#e4e9f2;--text:#101828;--muted:#667085;--blue:#1266ff;--green:#16a34a;--dark:#071326;--shadow:0 10px 28px rgba(15,23,42,.07);--side:280px;--top:76px;--composer:96px} *{box-sizing:border-box}html,body{height:100%;overflow:hidden}body{margin:0;background:var(--bg);font-family:Arial,Helvetica,sans-serif;color:var(--text)} .app{height:100vh;display:grid;grid-template-columns:var(--side) minmax(0,1fr);overflow:hidden}.sidebar{background:#fff;border-right:1px solid var(--line);padding:16px;display:grid;grid-template-rows:auto auto minmax(0,1fr) auto;gap:14px;overflow:hidden} .brand{display:flex;align-items:center;gap:12px;height:52px}.brandIcon{width:42px;height:42px;border-radius:12px;border:1px solid var(--line);background:linear-gradient(135deg,#101828,#1266ff);color:#fff;display:grid;place-items:center;font-weight:900}.brand b{font-size:24px}.brand small{color:var(--muted)} .new-chat{height:42px;border:0;border-radius:10px;background:var(--blue);color:white;font-weight:900;cursor:pointer}.thread-list{overflow:auto}.thread{min-height:42px;border-radius:10px;display:flex;align-items:center;padding:0 12px;color:#344054;font-size:14px;margin-bottom:6px;cursor:pointer}.thread.active,.thread:hover{background:#eef4ff;color:var(--blue);font-weight:800} .side-foot{border-top:1px solid var(--line);padding-top:14px;font-size:13px;color:var(--muted);line-height:1.5}.main{height:100vh;display:grid;grid-template-rows:var(--top) minmax(0,1fr) var(--composer);overflow:hidden} .top{height:var(--top);background:#fff;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:18px;padding:0 24px}.topTitle{min-width:220px}.top h1{margin:0;font-size:24px}.top .sub{font-size:12px;color:var(--muted);margin-top:3px}.search{flex:1;max-width:620px}.search input{width:100%;height:42px;border:1px solid var(--line);border-radius:12px;padding:0 14px} .topRight{margin-left:auto;display:flex;align-items:center;gap:10px}.platform-switcher{display:flex;align-items:center;gap:8px;padding-right:12px;border-right:1px solid var(--line)}.platform-link{width:38px;height:38px;border:1px solid var(--line);background:#fff;border-radius:10px;display:grid;place-items:center;text-decoration:none;box-shadow:0 6px 16px rgba(15,23,42,.06)}.platform-link:hover{border-color:var(--blue);box-shadow:0 0 0 3px #eef4ff}.platform-link img{max-width:28px;max-height:28px;object-fit:contain;display:block} .status{color:var(--green);font-weight:900;font-size:13px}.profileChip{display:flex;align-items:center;gap:10px;padding-left:12px;border-left:1px solid var(--line)}.profileAvatar{width:38px;height:38px;border-radius:50%;background:#111827;color:#fff;display:grid;place-items:center;font-weight:900;overflow:hidden}.profileAvatar img{width:100%;height:100%;object-fit:cover}.profileCopy b{display:block;font-size:13px;line-height:1.1}.profileCopy span{display:block;color:var(--muted);font-size:11px;margin-top:3px} .chat-area{height:100%;overflow:hidden;display:flex;justify-content:center;padding:18px}.chat-card{width:min(1040px,100%);height:100%;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);display:grid;grid-template-rows:auto minmax(0,1fr);overflow:hidden}.chat-head{padding:18px 22px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:14px}.ai-title{display:flex;align-items:center;gap:12px}.ai-title .aiMark{width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,#111827,#1266ff);color:#fff;display:grid;place-items:center;font-weight:900}.ai-title h2{margin:0;font-size:21px}.ai-title small{color:var(--muted)}.model-pill{border:1px solid var(--line);border-radius:999px;padding:8px 12px;font-size:12px;color:#344054;background:#f8fafc} .messages{overflow:auto;padding:24px;display:flex;flex-direction:column;gap:16px;background:linear-gradient(#fff,#fbfcff)}.msg{display:grid;grid-template-columns:42px minmax(0,1fr);gap:12px;align-items:start}.msg.user{grid-template-columns:minmax(0,1fr) 42px}.avatar{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:900;overflow:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.ai-avatar{background:#eef4ff;color:var(--blue)}.user-avatar{background:#111827;color:#fff}.bubble{max-width:780px;border:1px solid var(--line);background:#fff;border-radius:15px;padding:14px 16px;line-height:1.55;box-shadow:0 5px 18px rgba(15,23,42,.04);white-space:pre-wrap}.msg.user .bubble{justify-self:end;background:#1266ff;color:#fff;border-color:#1266ff}.bubble b{display:block;margin-bottom:5px;font-size:13px} .suggestions{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:4px}.suggestion{border:1px solid var(--line);border-radius:14px;padding:14px;background:#fff;text-align:left;cursor:pointer}.suggestion:hover{border-color:var(--blue);box-shadow:0 0 0 3px #eef4ff}.suggestion b{display:block;margin-bottom:5px}.suggestion small{color:var(--muted)} .composer-wrap{height:var(--composer);background:#fff;border-top:1px solid var(--line);display:flex;justify-content:center;align-items:center;padding:14px 18px}.composer{width:min(1040px,100%);min-height:64px;border:1px solid var(--line);border-radius:18px;background:#fff;box-shadow:var(--shadow);display:grid;grid-template-columns:46px minmax(0,1fr) 96px;align-items:center;gap:8px;padding:10px}.composer button{height:42px;border:0;border-radius:12px;font-weight:900;cursor:pointer}.plus{background:#f1f4f9;color:#111827}.send{background:var(--blue);color:white}.composer textarea{height:42px;max-height:84px;border:0;outline:none;font-size:15px;resize:none;font-family:inherit;padding:11px 4px}.composer textarea:disabled,.composer button:disabled{opacity:.55;cursor:not-allowed} @media(max-width:1200px){.app{grid-template-columns:230px 1fr}.search{display:none}.suggestions{grid-template-columns:1fr}.profileCopy{display:none}}@media(max-width:820px){.app{grid-template-columns:1fr}.sidebar{display:none}.top{padding:0 12px}.platform-switcher{gap:5px}.platform-link{width:34px;height:34px}.model-pill{display:none}} </style> </head> <body> <div class="app"> <aside class="sidebar"> <div class="brand"><div class="brandIcon">AI</div><div><b>SiloAI</b><br><small>Workspace Assistant</small></div></div> <button class="new-chat" id="newChatBtn">+ New Chat</button> <div class="thread-list" id="threadList"> <div class="thread active">Workspace command center</div> <div class="thread">Files and SiloDrive</div> <div class="thread">SiloTalk contacts</div> <div class="thread">Runtime services</div> </div> <div class="side-foot"><b>Connected Context</b><br><br>SiloDrive, SiloArray, SiloTalk, StreamDeck, SiloGrid, SiloStorage</div> </aside> <main class="main"> <header class="top"> <div class="topTitle"><h1>SiloAI Chat</h1><div class="sub">Customer workspace assistant</div></div> <div class="search"><input id="searchInput" placeholder="Search chats, files, contacts, services..."></div> <div class="topRight"> <nav class="platform-switcher" aria-label="Core Silo platforms"> <?php foreach ($links as $link): ?> <a class="platform-link" href="<?= ai_h($link['url']) ?>" title="Open <?= ai_h($link['label']) ?>" aria-label="Open <?= ai_h($link['label']) ?>"> <img src="<?= ai_h($link['icon']) ?>" alt=""> </a> <?php endforeach; ?> </nav> <span class="status" id="aiStatus">Online</span> <div class="profileChip"> <div class="profileAvatar"><?= $profile['avatar'] ? '<img src="' . ai_h($profile['avatar']) . '" alt="' . ai_h($profile['name']) . '">' : ai_h($profile['initials']) ?></div> <div class="profileCopy"><b><?= ai_h($profile['name']) ?></b><span><?= ai_h(($profile['email'] ?: $profile['uid']) . ' · ' . $profile['role']) ?></span></div> </div> </div> </header> <section class="chat-area"> <div class="chat-card"> <div class="chat-head"> <div class="ai-title"><div class="aiMark">AI</div><div><h2>SiloAI Assistant</h2><small>Ask about files, contacts, calls, runtime services, deployment, and Silo workspace operations.</small></div></div> <div class="model-pill" id="modelPill">SiloAI Agent · Workspace Mode</div> </div> <div class="messages" id="messages"> <div class="msg"> <div class="avatar ai-avatar">AI</div> <div class="bubble"><b>SiloAI</b>Ready. I can help inspect SiloDrive files, SiloTalk contacts and call flows, SiloArray runtime services, StreamDeck production tools, and deployment tasks from this workspace.</div> </div> <div class="suggestions" id="suggestions"> <button class="suggestion" data-prompt="Summarize my active Silo services and what needs attention."><b>Workspace Status</b><small>Summarize active services and next priorities.</small></button> <button class="suggestion" data-prompt="Help me organize SiloDrive assets for Talk, StreamDeck, Caster, Canvas, and Array."><b>Organize Assets</b><small>Plan SiloDrive storage across apps.</small></button> <button class="suggestion" data-prompt="Create a deployment checklist for wiring SiloAI into the SiloArray workspace launcher."><b>SiloArray Launch</b><small>Prepare the next integration pass.</small></button> </div> </div> </div> </section> <footer class="composer-wrap"> <form class="composer" id="chatForm"> <button class="plus" type="button" id="clearBtn">+</button> <textarea id="messageInput" rows="1" placeholder="Message SiloAI..."></textarea> <button class="send" id="sendBtn" type="submit">Send</button> </form> </footer> </main> </div> <script> const USER_PROFILE = <?= json_encode($profile, JSON_UNESCAPED_SLASHES) ?>; const API_URL = '/silocloud/api/siloai-chat.php'; const messagesEl = document.getElementById('messages'); const form = document.getElementById('chatForm'); const input = document.getElementById('messageInput'); const sendBtn = document.getElementById('sendBtn'); const statusEl = document.getElementById('aiStatus'); const suggestions = document.getElementById('suggestions'); let history = []; function esc(value){return String(value ?? '').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));} function scrollDown(){messagesEl.scrollTop = messagesEl.scrollHeight;} function userAvatarHtml(){return USER_PROFILE.avatar ? '<img src="'+esc(USER_PROFILE.avatar)+'" alt="'+esc(USER_PROFILE.name)+'">' : esc(USER_PROFILE.initials || 'U');} function addMessage(role, text){ const div = document.createElement('div'); div.className = 'msg' + (role === 'user' ? ' user' : ''); if(role === 'user'){ div.innerHTML = '<div class="bubble"><b>You</b>'+esc(text)+'</div><div class="avatar user-avatar">'+userAvatarHtml()+'</div>'; }else{ div.innerHTML = '<div class="avatar ai-avatar">AI</div><div class="bubble"><b>SiloAI</b>'+esc(text)+'</div>'; } if(suggestions) suggestions.remove(); messagesEl.appendChild(div); scrollDown(); } function setBusy(busy){ input.disabled = busy; sendBtn.disabled = busy; statusEl.textContent = busy ? 'Thinking' : 'Online'; } async function sendMessage(text){ text = text.trim(); if(!text) return; addMessage('user', text); history.push({role:'user', content:text}); setBusy(true); try{ const res = await fetch(API_URL, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({message:text, messages:history.slice(-12), user:USER_PROFILE}) }); const data = await res.json().catch(()=>({ok:false,message:'Invalid AI response.'})); if(!res.ok || !data.ok) throw new Error(data.message || 'SiloAI request failed.'); const reply = data.reply || 'I am connected, but I did not receive a response.'; history.push({role:'assistant', content:reply}); addMessage('assistant', reply); }catch(err){ addMessage('assistant', 'I could not complete that request yet: ' + (err.message || err)); }finally{ setBusy(false); input.focus(); } } form.addEventListener('submit', (event)=>{ event.preventDefault(); const text = input.value; input.value = ''; sendMessage(text); }); input.addEventListener('input', ()=>{ input.style.height = '42px'; input.style.height = Math.min(84, input.scrollHeight) + 'px'; }); document.querySelectorAll('[data-prompt]').forEach(btn=>btn.addEventListener('click',()=>sendMessage(btn.dataset.prompt || ''))); document.getElementById('clearBtn').addEventListener('click',()=>{ history = []; messagesEl.innerHTML = '<div class="msg"><div class="avatar ai-avatar">AI</div><div class="bubble"><b>SiloAI</b>New chat ready. What should we work on?</div></div>'; input.focus(); }); </script> </body> </html>