bot_host / static /css /ai_chat.css
ItsBounvy's picture
deploy: english UI + admin creds + dataset wiring
096629f verified
Raw History Blame Contribute Delete
11.3 kB
/* ============================================================
MMaxAI Hosting Panel — AI chat overlay
------------------------------------------------------------
Floating overlay that opens from either the dedicated
"Ask AI" button in the topbar or the inline cmd-bar
Enter-press. Layout uses the same dark Copilot tokens
already defined in copilot.css, so it picks up theme
changes automatically.
============================================================ */
/* AI trigger button (topbar) — sits between Cmd+K and the
language toggle. Mirrors the .search-trigger shape. */
.ai-trigger {
display: inline-flex; align-items: center; gap: 10px;
width: auto;
height: 36px;
padding: 0 14px;
border-radius: var(--radius-pill);
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-faint);
font-size: 13px;
cursor: pointer;
transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
text-align: left;
}
.ai-trigger:hover { background: var(--surface-hover); color: var(--text); border-color: var(--border-strong); }
.ai-trigger svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--accent-2); }
.ai-trigger .label { flex: 1; }
.ai-trigger kbd {
padding: 2px 6px;
border-radius: 4px;
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text-muted);
font-family: var(--font);
font-size: 11px;
font-weight: 600;
}
/* AI trigger — a touch more eye-catching so people actually notice it.
Slightly stronger border, accent-tinted icon, and a soft glow on hover. */
.ai-trigger-primary {
background: linear-gradient(180deg, rgba(124,108,255,0.10), rgba(124,108,255,0.04));
border-color: rgba(124,108,255,0.45);
color: var(--text);
}
.ai-trigger-primary:hover {
background: linear-gradient(180deg, rgba(124,108,255,0.18), rgba(124,108,255,0.06));
border-color: rgba(124,108,255,0.75);
box-shadow: 0 0 0 3px rgba(124,108,255,0.12);
}
.ai-trigger-primary svg { color: var(--accent); }
/* Hide the AI trigger when no API key is set. The JS toggles
``[data-ai-disabled]`` on <body> based on /api/ai/status. */
body[data-ai-disabled="true"] .ai-trigger { display: none; }
/* ---- Overlay shell ---- */
.ai-overlay {
position: fixed; inset: 0;
z-index: 200;
display: none;
align-items: center;
justify-content: center;
pointer-events: none;
}
.ai-overlay[data-open="true"] {
display: flex;
pointer-events: auto;
animation: ai-fade 140ms var(--ease);
}
@keyframes ai-fade {
from { opacity: 0; }
to { opacity: 1; }
}
.ai-backdrop {
position: absolute; inset: 0;
background: rgba(4, 6, 14, 0.55);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.ai-panel {
position: relative;
width: min(720px, calc(100% - 32px));
height: min(720px, calc(100vh - 80px));
background: var(--bg-elev-1);
border: 1px solid var(--border-strong);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg), 0 0 0 1px rgba(255,255,255,0.03);
display: flex;
flex-direction: column;
overflow: hidden;
animation: ai-pop 180ms var(--ease);
}
@keyframes ai-pop {
from { transform: translateY(8px) scale(0.98); opacity: 0; }
to { transform: translateY(0) scale(1); opacity: 1; }
}
@media (max-width: 720px) {
.ai-panel {
height: calc(100vh - 24px);
border-radius: var(--radius);
}
}
/* ---- Header ---- */
.ai-head {
display: flex; align-items: center;
gap: 12px;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
background: rgba(10, 14, 28, 0.4);
}
.ai-head-left {
display: flex; align-items: center; gap: 10px;
flex: 1; min-width: 0;
}
.ai-spark { color: var(--accent-2); flex: 0 0 auto; }
.ai-title {
font-size: 15px;
font-weight: 600;
color: var(--text);
letter-spacing: -0.005em;
}
.ai-sub {
margin-top: 1px;
font-size: 11.5px;
color: var(--text-muted);
}
.ai-head-right {
display: flex; align-items: center; gap: 6px;
flex: 0 0 auto;
}
.ai-model-tag {
font-size: 11px;
padding: 3px 9px;
border-radius: var(--radius-pill);
background: var(--surface-2);
color: var(--text-muted);
border: 1px solid var(--border);
font-family: var(--font-mono);
margin-right: 4px;
}
.ai-icon {
width: 32px; height: 32px;
border-radius: 8px;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-muted);
display: grid; place-items: center;
cursor: pointer;
transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.ai-icon:hover { background: var(--surface-hover); color: var(--text); }
/* ---- Transcript scroll ---- */
.ai-transcript {
flex: 1;
overflow-y: auto;
padding: 18px;
display: flex;
flex-direction: column;
gap: 12px;
scrollbar-width: thin;
scrollbar-color: rgba(255,255,255,0.18) transparent;
/* Reserve room for the scrollbar so messages don't get a
1-frame jump when content overflows vs when it doesn't. */
scrollbar-gutter: stable;
}
/* WebKit (Chrome / Edge / Safari) — slim, themed scrollbar */
.ai-transcript::-webkit-scrollbar { width: 8px; height: 8px; }
.ai-transcript::-webkit-scrollbar-track { background: transparent; }
.ai-transcript::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.14);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
.ai-transcript::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.24); background-clip: padding-box; border: 2px solid transparent; }
/* ---- Message bubbles ---- */
.ai-msg {
display: flex;
width: 100%;
animation: ai-bubble-in 160ms var(--ease);
}
@keyframes ai-bubble-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.ai-msg-user { justify-content: flex-end; }
.ai-msg-assistant { justify-content: flex-start; }
.ai-bubble {
max-width: 86%;
padding: 10px 14px;
border-radius: 14px;
line-height: 1.5;
font-size: 14px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: anywhere;
}
.ai-msg-user .ai-bubble {
background: var(--surface-2);
color: var(--text);
border-bottom-right-radius: 4px;
}
.ai-msg-assistant .ai-bubble {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-bottom-left-radius: 4px;
}
.ai-msg-error .ai-bubble {
background: rgba(248, 113, 113, 0.10);
color: var(--status-error);
border: 1px solid rgba(248, 113, 113, 0.32);
}
/* Markdown-ish styling inside assistant bubbles. Light touch —
the upstream is instructed to keep replies short, so we don't
need a full markdown renderer. */
.ai-bubble code {
background: var(--bg);
padding: 1px 6px;
border-radius: 5px;
font-family: var(--font-mono);
font-size: 12.5px;
color: var(--accent-2);
}
.ai-bubble pre {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
overflow-x: auto;
margin: 8px 0;
font-family: var(--font-mono);
font-size: 12.5px;
line-height: 1.55;
}
.ai-bubble pre code {
background: transparent;
padding: 0;
color: var(--text);
}
.ai-bubble strong { font-weight: 600; }
.ai-bubble em { font-style: italic; color: var(--text-muted); }
.ai-msg-meta {
margin-top: 4px;
font-size: 11px;
color: var(--text-faint);
}
/* Quick-reply chips on the empty-state bubble. */
.ai-hint {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.ai-quick {
padding: 5px 10px;
font-size: 12px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-pill);
color: var(--text-muted);
cursor: pointer;
transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.ai-quick:hover {
background: var(--surface);
color: var(--text);
border-color: var(--border-strong);
}
/* Typing indicator — three pulsing dots. */
.ai-typing {
display: inline-flex;
gap: 4px;
align-items: center;
height: 1em;
}
.ai-typing span {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--text-muted);
animation: ai-bounce 1.2s infinite ease-in-out;
}
.ai-typing span:nth-child(2) { animation-delay: 0.15s; }
.ai-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes ai-bounce {
0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
40% { opacity: 1; transform: translateY(-3px); }
}
/* ---- Input form ---- */
.ai-form {
display: flex;
gap: 10px;
align-items: flex-end;
padding: 14px 16px;
border-top: 1px solid var(--border);
background: rgba(10, 14, 28, 0.4);
}
.ai-form textarea {
flex: 1;
resize: none;
min-height: 44px;
max-height: 200px;
line-height: 1.45;
padding: 12px 14px;
border-radius: 12px;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
font: inherit;
font-size: 14px;
outline: none;
transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.ai-form textarea:focus {
border-color: var(--border-strong);
box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.18);
}
.ai-form textarea::placeholder { color: var(--text-faint); }
.ai-send {
width: 44px; height: 44px;
border-radius: 12px;
background: var(--text);
color: var(--bg);
display: grid; place-items: center;
border: 0;
cursor: pointer;
transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
flex: 0 0 auto;
}
.ai-send:hover { opacity: 0.9; }
.ai-send:disabled { opacity: 0.4; cursor: not-allowed; }
.ai-send svg { width: 14px; height: 14px; }
.ai-send.is-streaming { background: var(--status-error); color: #1f0405; }
/* Disabled state — overlay still renders the empty hint but the
input doesn't accept content. Toggled by JS when /api/ai/status
reports `enabled: false`. */
body[data-ai-disabled="true"] .ai-form textarea { display: none; }
body[data-ai-disabled="true"] .ai-send { display: none; }
body[data-ai-disabled="true"] .ai-form {
padding: 18px;
justify-content: center;
color: var(--text-muted);
font-size: 13px;
}
body[data-ai-disabled="true"] .ai-form::before {
content: "AI assistant is not configured on this panel. Set NVIDIA_API_KEY in env.";
}
/* Empty-state message is hidden once there's any conversation. */
.ai-transcript[data-has-messages="true"] [data-empty] { display: none; }
/* Mobile stacking — chat bubble spans more of the width on small
screens, signature stays legible. */
@media (max-width: 540px) {
.ai-bubble { max-width: 95%; }
.ai-form { padding: 10px; }
.ai-form textarea { font-size: 15px; }
}
/* ---- Cmd-bar → AI button trigger (topbar) is hidden on mobile
to leave room for the Cmd+K shortcut. ---- */
@media (max-width: 720px) {
.ai-trigger .label { display: none; }
}