Update UI components, styling, and add theme-sentient and intents
This commit is contained in:
parent
db06e42289
commit
9fe234aa3c
28 changed files with 5140 additions and 1791 deletions
|
|
@ -18,8 +18,7 @@ use log::{debug, error, info};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
use tokio_tungstenite::{
|
use tokio_tungstenite::{
|
||||||
connect_async_tls_with_config,
|
connect_async_tls_with_config, tungstenite::protocol::Message as TungsteniteMessage,
|
||||||
tungstenite::protocol::Message as TungsteniteMessage,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::shared::AppState;
|
use crate::shared::AppState;
|
||||||
|
|
@ -99,7 +98,10 @@ async fn proxy_api(
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
let path = original_uri.path();
|
let path = original_uri.path();
|
||||||
let query = original_uri.query().map(|q| format!("?{}", q)).unwrap_or_default();
|
let query = original_uri
|
||||||
|
.query()
|
||||||
|
.map(|q| format!("?{}", q))
|
||||||
|
.unwrap_or_default();
|
||||||
let method = req.method().clone();
|
let method = req.method().clone();
|
||||||
let headers = req.headers().clone();
|
let headers = req.headers().clone();
|
||||||
|
|
||||||
|
|
@ -217,7 +219,11 @@ async fn ws_proxy(
|
||||||
async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQuery) {
|
async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQuery) {
|
||||||
let backend_url = format!(
|
let backend_url = format!(
|
||||||
"{}/ws?session_id={}&user_id={}",
|
"{}/ws?session_id={}&user_id={}",
|
||||||
state.client.base_url().replace("https://", "wss://").replace("http://", "ws://"),
|
state
|
||||||
|
.client
|
||||||
|
.base_url()
|
||||||
|
.replace("https://", "wss://")
|
||||||
|
.replace("http://", "ws://"),
|
||||||
params.session_id,
|
params.session_id,
|
||||||
params.user_id
|
params.user_id
|
||||||
);
|
);
|
||||||
|
|
@ -234,12 +240,8 @@ async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQu
|
||||||
let connector = tokio_tungstenite::Connector::NativeTls(tls_connector);
|
let connector = tokio_tungstenite::Connector::NativeTls(tls_connector);
|
||||||
|
|
||||||
// Connect to backend WebSocket
|
// Connect to backend WebSocket
|
||||||
let backend_result = connect_async_tls_with_config(
|
let backend_result =
|
||||||
&backend_url,
|
connect_async_tls_with_config(&backend_url, None, false, Some(connector)).await;
|
||||||
None,
|
|
||||||
false,
|
|
||||||
Some(connector),
|
|
||||||
).await;
|
|
||||||
|
|
||||||
let backend_socket = match backend_result {
|
let backend_socket = match backend_result {
|
||||||
Ok((socket, _)) => socket,
|
Ok((socket, _)) => socket,
|
||||||
|
|
@ -260,22 +262,38 @@ async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQu
|
||||||
while let Some(msg) = client_rx.next().await {
|
while let Some(msg) = client_rx.next().await {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(AxumMessage::Text(text)) => {
|
Ok(AxumMessage::Text(text)) => {
|
||||||
if backend_tx.send(TungsteniteMessage::Text(text)).await.is_err() {
|
if backend_tx
|
||||||
|
.send(TungsteniteMessage::Text(text))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Binary(data)) => {
|
Ok(AxumMessage::Binary(data)) => {
|
||||||
if backend_tx.send(TungsteniteMessage::Binary(data)).await.is_err() {
|
if backend_tx
|
||||||
|
.send(TungsteniteMessage::Binary(data))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Ping(data)) => {
|
Ok(AxumMessage::Ping(data)) => {
|
||||||
if backend_tx.send(TungsteniteMessage::Ping(data)).await.is_err() {
|
if backend_tx
|
||||||
|
.send(TungsteniteMessage::Ping(data))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AxumMessage::Pong(data)) => {
|
Ok(AxumMessage::Pong(data)) => {
|
||||||
if backend_tx.send(TungsteniteMessage::Pong(data)).await.is_err() {
|
if backend_tx
|
||||||
|
.send(TungsteniteMessage::Pong(data))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -323,8 +341,32 @@ async fn handle_ws_proxy(client_socket: WebSocket, state: AppState, params: WsQu
|
||||||
|
|
||||||
/// Create WebSocket proxy router
|
/// Create WebSocket proxy router
|
||||||
fn create_ws_router() -> Router<AppState> {
|
fn create_ws_router() -> Router<AppState> {
|
||||||
|
Router::new().fallback(any(ws_proxy))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create UI HTMX proxy router (for HTML fragment endpoints)
|
||||||
|
fn create_ui_router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.fallback(get(ws_proxy))
|
// Email UI endpoints
|
||||||
|
.route("/email/accounts", any(proxy_api))
|
||||||
|
.route("/email/list", any(proxy_api))
|
||||||
|
.route("/email/folders", any(proxy_api))
|
||||||
|
.route("/email/compose", any(proxy_api))
|
||||||
|
.route("/email/labels", any(proxy_api))
|
||||||
|
.route("/email/templates", any(proxy_api))
|
||||||
|
.route("/email/signatures", any(proxy_api))
|
||||||
|
.route("/email/rules", any(proxy_api))
|
||||||
|
.route("/email/search", any(proxy_api))
|
||||||
|
.route("/email/auto-responder", any(proxy_api))
|
||||||
|
.route("/email/{id}", any(proxy_api))
|
||||||
|
.route("/email/{id}/delete", any(proxy_api))
|
||||||
|
// Calendar UI endpoints
|
||||||
|
.route("/calendar/list", any(proxy_api))
|
||||||
|
.route("/calendar/upcoming", any(proxy_api))
|
||||||
|
.route("/calendar/event/new", any(proxy_api))
|
||||||
|
.route("/calendar/new", any(proxy_api))
|
||||||
|
// Fallback for any other /ui/* routes
|
||||||
|
.fallback(any(proxy_api))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure and return the main router
|
/// Configure and return the main router
|
||||||
|
|
@ -338,6 +380,8 @@ pub fn configure_router() -> Router {
|
||||||
.route("/health", get(health))
|
.route("/health", get(health))
|
||||||
// API proxy routes
|
// API proxy routes
|
||||||
.nest("/api", create_api_router())
|
.nest("/api", create_api_router())
|
||||||
|
// UI HTMX proxy routes (for /ui/* endpoints that return HTML fragments)
|
||||||
|
.nest("/ui", create_ui_router())
|
||||||
// WebSocket proxy routes
|
// WebSocket proxy routes
|
||||||
.nest("/ws", create_ws_router())
|
.nest("/ws", create_ws_router())
|
||||||
// UI routes
|
// UI routes
|
||||||
|
|
@ -373,6 +417,62 @@ pub fn configure_router() -> Router {
|
||||||
"/suite/tasks",
|
"/suite/tasks",
|
||||||
tower_http::services::ServeDir::new(suite_path.join("tasks")),
|
tower_http::services::ServeDir::new(suite_path.join("tasks")),
|
||||||
)
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/calendar",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("calendar")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/meet",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("meet")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/paper",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("paper")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/research",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("research")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/analytics",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("analytics")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/monitoring",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("monitoring")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/admin",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("admin")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/auth",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("auth")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/settings",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("settings")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/sources",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("sources")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/attendant",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("attendant")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/tools",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("tools")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/assets",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("assets")),
|
||||||
|
)
|
||||||
|
.nest_service(
|
||||||
|
"/suite/partials",
|
||||||
|
tower_http::services::ServeDir::new(suite_path.join("partials")),
|
||||||
|
)
|
||||||
// Legacy paths for backward compatibility (serve suite assets)
|
// Legacy paths for backward compatibility (serve suite assets)
|
||||||
.nest_service(
|
.nest_service(
|
||||||
"/js",
|
"/js",
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,20 @@
|
||||||
.admin-layout {
|
.admin-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 260px 1fr;
|
grid-template-columns: 260px 1fr;
|
||||||
min-height: calc(100vh - 56px);
|
height: 100%;
|
||||||
background: var(--bg);
|
min-height: 0;
|
||||||
|
background: var(--bg, var(--bg-primary, #0a0a0f));
|
||||||
|
color: var(--text, var(--text-primary, #ffffff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
.admin-sidebar {
|
.admin-sidebar {
|
||||||
background: var(--surface);
|
background: var(--surface, var(--bg-secondary, #12121a));
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border, #2a2a3a);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: sticky;
|
overflow-y: auto;
|
||||||
top: 56px;
|
height: 100%;
|
||||||
height: calc(100vh - 56px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-header {
|
.admin-header {
|
||||||
|
|
@ -138,10 +139,22 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-icon.users { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
|
.stat-icon.users {
|
||||||
.stat-icon.groups { background: rgba(16, 185, 129, 0.1); color: #10b981; }
|
background: rgba(59, 130, 246, 0.1);
|
||||||
.stat-icon.bots { background: rgba(168, 85, 247, 0.1); color: #a855f7; }
|
color: #3b82f6;
|
||||||
.stat-icon.storage { background: rgba(249, 115, 22, 0.1); color: #f97316; }
|
}
|
||||||
|
.stat-icon.groups {
|
||||||
|
background: rgba(16, 185, 129, 0.1);
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
.stat-icon.bots {
|
||||||
|
background: rgba(168, 85, 247, 0.1);
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
.stat-icon.storage {
|
||||||
|
background: rgba(249, 115, 22, 0.1);
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
.stat-content {
|
.stat-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -287,9 +300,15 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.health-status.healthy { background: #10b981; }
|
.health-status.healthy {
|
||||||
.health-status.warning { background: #f59e0b; }
|
background: #10b981;
|
||||||
.health-status.error { background: #ef4444; }
|
}
|
||||||
|
.health-status.warning {
|
||||||
|
background: #f59e0b;
|
||||||
|
}
|
||||||
|
.health-status.error {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
.health-value {
|
.health-value {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|
@ -458,7 +477,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
.attendant-container {
|
.attendant-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 60px);
|
height: 100%;
|
||||||
background: var(--bg);
|
min-height: 0;
|
||||||
|
background: var(--bg, var(--bg-primary, #0a0a0f));
|
||||||
|
color: var(--text, var(--text-primary, #ffffff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Queue Panel */
|
/* Queue Panel */
|
||||||
|
|
@ -300,10 +302,18 @@
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot.online { background: #22c55e; }
|
.status-dot.online {
|
||||||
.status-dot.away { background: #f59e0b; }
|
background: #22c55e;
|
||||||
.status-dot.busy { background: #ef4444; }
|
}
|
||||||
.status-dot.offline { background: #6b7280; }
|
.status-dot.away {
|
||||||
|
background: #f59e0b;
|
||||||
|
}
|
||||||
|
.status-dot.busy {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
.status-dot.offline {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
|
|
|
||||||
|
|
@ -362,182 +362,64 @@
|
||||||
<!-- Theme Selection -->
|
<!-- Theme Selection -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="settings-section-title">Theme</div>
|
<div class="settings-section-title">Theme</div>
|
||||||
<div class="theme-grid">
|
<select id="themeSelector" class="theme-dropdown">
|
||||||
<div
|
<optgroup label="Core Themes">
|
||||||
class="theme-option theme-dark"
|
<option value="sentient">
|
||||||
data-theme="dark"
|
🤖 Sentient
|
||||||
title="Dark"
|
</option>
|
||||||
>
|
<option value="dark">🌑 Dark</option>
|
||||||
<div class="theme-option-inner">
|
<option value="light">☀️ Light</option>
|
||||||
<div class="theme-option-header">
|
<option value="blue">🌊 Ocean</option>
|
||||||
<div class="theme-option-dot"></div>
|
<option value="purple">💜 Violet</option>
|
||||||
<div class="theme-option-dot"></div>
|
<option value="green">🌲 Forest</option>
|
||||||
<div class="theme-option-dot"></div>
|
<option value="orange">🌅 Sunset</option>
|
||||||
</div>
|
</optgroup>
|
||||||
<div class="theme-option-body">
|
<optgroup label="Retro Themes">
|
||||||
<div
|
<option value="cyberpunk">
|
||||||
class="theme-option-line"
|
🌃 Cyberpunk
|
||||||
style="width: 80%"
|
</option>
|
||||||
></div>
|
<option value="retrowave">
|
||||||
<div
|
🌴 Retrowave
|
||||||
class="theme-option-line"
|
</option>
|
||||||
style="width: 60%"
|
<option value="vapordream">
|
||||||
></div>
|
💭 Vapor Dream
|
||||||
<div
|
</option>
|
||||||
class="theme-option-line"
|
<option value="y2kglow">✨ Y2K</option>
|
||||||
style="width: 70%"
|
<option value="arcadeflash">
|
||||||
></div>
|
🕹️ Arcade
|
||||||
</div>
|
</option>
|
||||||
</div>
|
<option value="discofever">🪩 Disco</option>
|
||||||
<span class="theme-option-name">Dark</span>
|
<option value="grungeera">🎸 Grunge</option>
|
||||||
</div>
|
</optgroup>
|
||||||
<div
|
<optgroup label="Classic Themes">
|
||||||
class="theme-option theme-light"
|
<option value="jazzage">🎺 Jazz Age</option>
|
||||||
data-theme="light"
|
<option value="mellowgold">
|
||||||
title="Light"
|
🌻 Mellow Gold
|
||||||
>
|
</option>
|
||||||
<div class="theme-option-inner">
|
<option value="midcenturymod">
|
||||||
<div class="theme-option-header">
|
🏠 Mid Century
|
||||||
<div class="theme-option-dot"></div>
|
</option>
|
||||||
<div class="theme-option-dot"></div>
|
<option value="polaroidmemories">
|
||||||
<div class="theme-option-dot"></div>
|
📷 Polaroid
|
||||||
</div>
|
</option>
|
||||||
<div class="theme-option-body">
|
<option value="saturdaycartoons">
|
||||||
<div
|
📺 Cartoons
|
||||||
class="theme-option-line"
|
</option>
|
||||||
style="width: 80%"
|
<option value="seasidepostcard">
|
||||||
></div>
|
🏖️ Seaside
|
||||||
<div
|
</option>
|
||||||
class="theme-option-line"
|
<option value="typewriter">
|
||||||
style="width: 60%"
|
⌨️ Typewriter
|
||||||
></div>
|
</option>
|
||||||
<div
|
</optgroup>
|
||||||
class="theme-option-line"
|
<optgroup label="Tech Themes">
|
||||||
style="width: 70%"
|
<option value="3dbevel">🔲 3D Bevel</option>
|
||||||
></div>
|
<option value="xeroxui">📠 Xerox UI</option>
|
||||||
</div>
|
<option value="xtreegold">
|
||||||
</div>
|
📁 XTree Gold
|
||||||
<span class="theme-option-name">Light</span>
|
</option>
|
||||||
</div>
|
</optgroup>
|
||||||
<div
|
</select>
|
||||||
class="theme-option theme-blue"
|
|
||||||
data-theme="blue"
|
|
||||||
title="Ocean"
|
|
||||||
>
|
|
||||||
<div class="theme-option-inner">
|
|
||||||
<div class="theme-option-header">
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="theme-option-body">
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 80%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 60%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 70%"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="theme-option-name">Ocean</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="theme-option theme-purple"
|
|
||||||
data-theme="purple"
|
|
||||||
title="Violet"
|
|
||||||
>
|
|
||||||
<div class="theme-option-inner">
|
|
||||||
<div class="theme-option-header">
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="theme-option-body">
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 80%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 60%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 70%"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="theme-option-name"
|
|
||||||
>Violet</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="theme-option theme-green"
|
|
||||||
data-theme="green"
|
|
||||||
title="Forest"
|
|
||||||
>
|
|
||||||
<div class="theme-option-inner">
|
|
||||||
<div class="theme-option-header">
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="theme-option-body">
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 80%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 60%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 70%"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="theme-option-name"
|
|
||||||
>Forest</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="theme-option theme-orange"
|
|
||||||
data-theme="orange"
|
|
||||||
title="Sunset"
|
|
||||||
>
|
|
||||||
<div class="theme-option-inner">
|
|
||||||
<div class="theme-option-header">
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
<div class="theme-option-dot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="theme-option-body">
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 80%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 60%"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="theme-option-line"
|
|
||||||
style="width: 70%"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="theme-option-name"
|
|
||||||
>Sunset</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-divider"></div>
|
<div class="settings-divider"></div>
|
||||||
|
|
@ -741,12 +623,21 @@
|
||||||
<p class="ai-status">Ready to help</p>
|
<p class="ai-status">Ready to help</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="ai-panel-toggle" onclick="toggleAIPanel()" aria-label="Close AI Panel">✕</button>
|
<button
|
||||||
|
class="ai-panel-toggle"
|
||||||
|
onclick="toggleAIPanel()"
|
||||||
|
aria-label="Close AI Panel"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ai-panel-messages" id="ai-messages">
|
<div class="ai-panel-messages" id="ai-messages">
|
||||||
<div class="ai-message assistant">
|
<div class="ai-message assistant">
|
||||||
<div class="ai-message-bubble">Olá! Sou seu assistente AI. Posso ajudar com qualquer tarefa nesta tela.</div>
|
<div class="ai-message-bubble">
|
||||||
|
Olá! Sou seu assistente AI. Posso ajudar com
|
||||||
|
qualquer tarefa nesta tela.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -758,14 +649,30 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ai-panel-input">
|
<div class="ai-panel-input">
|
||||||
<input type="text" class="ai-input" placeholder="Como posso ajudar?" id="ai-input"
|
<input
|
||||||
onkeypress="if(event.key==='Enter')sendAIMessage()">
|
type="text"
|
||||||
<button class="ai-send-btn" onclick="sendAIMessage()" aria-label="Send message">➤</button>
|
class="ai-input"
|
||||||
|
placeholder="Como posso ajudar?"
|
||||||
|
id="ai-input"
|
||||||
|
onkeypress="if(event.key==='Enter')sendAIMessage()"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="ai-send-btn"
|
||||||
|
onclick="sendAIMessage()"
|
||||||
|
aria-label="Send message"
|
||||||
|
>
|
||||||
|
➤
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- AI Panel Toggle Button (when panel is collapsed) -->
|
<!-- AI Panel Toggle Button (when panel is collapsed) -->
|
||||||
<button class="ai-panel-fab" id="ai-fab" onclick="toggleAIPanel()" aria-label="Open AI Assistant">
|
<button
|
||||||
|
class="ai-panel-fab"
|
||||||
|
id="ai-fab"
|
||||||
|
onclick="toggleAIPanel()"
|
||||||
|
aria-label="Open AI Assistant"
|
||||||
|
>
|
||||||
🤖
|
🤖
|
||||||
</button>
|
</button>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -868,23 +775,26 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme handling
|
// Theme handling with dropdown
|
||||||
const themeOptions = document.querySelectorAll(".theme-option");
|
const themeSelector = document.getElementById("themeSelector");
|
||||||
const savedTheme = localStorage.getItem("gb-theme") || "dark";
|
const savedTheme = localStorage.getItem("gb-theme") || "sentient";
|
||||||
document.body.setAttribute("data-theme", savedTheme);
|
document.body.setAttribute("data-theme", savedTheme);
|
||||||
document
|
|
||||||
.querySelector(`.theme-option[data-theme="${savedTheme}"]`)
|
|
||||||
?.classList.add("active");
|
|
||||||
|
|
||||||
themeOptions.forEach((option) => {
|
// Set dropdown to saved value
|
||||||
option.addEventListener("click", () => {
|
if (themeSelector) {
|
||||||
const theme = option.getAttribute("data-theme");
|
themeSelector.value = savedTheme;
|
||||||
|
|
||||||
|
themeSelector.addEventListener("change", (e) => {
|
||||||
|
const theme = e.target.value;
|
||||||
document.body.setAttribute("data-theme", theme);
|
document.body.setAttribute("data-theme", theme);
|
||||||
localStorage.setItem("gb-theme", theme);
|
localStorage.setItem("gb-theme", theme);
|
||||||
themeOptions.forEach((o) => o.classList.remove("active"));
|
|
||||||
option.classList.add("active");
|
// Also notify ThemeManager if it exists
|
||||||
});
|
if (window.ThemeManager && window.ThemeManager.loadTheme) {
|
||||||
|
window.ThemeManager.loadTheme(theme);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function toggleQuickSetting(el) {
|
function toggleQuickSetting(el) {
|
||||||
el.classList.toggle("active");
|
el.classList.toggle("active");
|
||||||
|
|
@ -1187,142 +1097,145 @@
|
||||||
// Quick actions per app
|
// Quick actions per app
|
||||||
const aiQuickActions = {
|
const aiQuickActions = {
|
||||||
drive: [
|
drive: [
|
||||||
{ label: 'Upload file', action: 'upload_file' },
|
{ label: "Upload file", action: "upload_file" },
|
||||||
{ label: 'Create folder', action: 'create_folder' },
|
{ label: "Create folder", action: "create_folder" },
|
||||||
{ label: 'Search files', action: 'search_files' },
|
{ label: "Search files", action: "search_files" },
|
||||||
{ label: 'Share', action: 'share_item' }
|
{ label: "Share", action: "share_item" },
|
||||||
],
|
],
|
||||||
tasks: [
|
tasks: [
|
||||||
{ label: 'New task', action: 'create_task' },
|
{ label: "New task", action: "create_task" },
|
||||||
{ label: 'Due today', action: 'show_due_today' },
|
{ label: "Due today", action: "show_due_today" },
|
||||||
{ label: 'Summary', action: 'tasks_summary' },
|
{ label: "Summary", action: "tasks_summary" },
|
||||||
{ label: 'Priorities', action: 'show_priorities' }
|
{ label: "Priorities", action: "show_priorities" },
|
||||||
],
|
],
|
||||||
mail: [
|
mail: [
|
||||||
{ label: 'Compose', action: 'compose_email' },
|
{ label: "Compose", action: "compose_email" },
|
||||||
{ label: 'Unread', action: 'show_unread' },
|
{ label: "Unread", action: "show_unread" },
|
||||||
{ label: 'Search', action: 'search_mail' },
|
{ label: "Search", action: "search_mail" },
|
||||||
{ label: 'Summary', action: 'mail_summary' }
|
{ label: "Summary", action: "mail_summary" },
|
||||||
],
|
],
|
||||||
calendar: [
|
calendar: [
|
||||||
{ label: 'New event', action: 'create_event' },
|
{ label: "New event", action: "create_event" },
|
||||||
{ label: 'Today', action: 'show_today' },
|
{ label: "Today", action: "show_today" },
|
||||||
{ label: 'This week', action: 'show_week' },
|
{ label: "This week", action: "show_week" },
|
||||||
{ label: 'Find time', action: 'find_free_time' }
|
{ label: "Find time", action: "find_free_time" },
|
||||||
],
|
],
|
||||||
meet: [
|
meet: [
|
||||||
{ label: 'Start call', action: 'start_meeting' },
|
{ label: "Start call", action: "start_meeting" },
|
||||||
{ label: 'Schedule', action: 'schedule_meeting' },
|
{ label: "Schedule", action: "schedule_meeting" },
|
||||||
{ label: 'Join', action: 'join_meeting' }
|
{ label: "Join", action: "join_meeting" },
|
||||||
],
|
],
|
||||||
paper: [
|
paper: [
|
||||||
{ label: 'New doc', action: 'create_document' },
|
{ label: "New doc", action: "create_document" },
|
||||||
{ label: 'Templates', action: 'show_templates' },
|
{ label: "Templates", action: "show_templates" },
|
||||||
{ label: 'Recent', action: 'show_recent' }
|
{ label: "Recent", action: "show_recent" },
|
||||||
],
|
],
|
||||||
research: [
|
research: [
|
||||||
{ label: 'New search', action: 'new_research' },
|
{ label: "New search", action: "new_research" },
|
||||||
{ label: 'Sources', action: 'show_sources' },
|
{ label: "Sources", action: "show_sources" },
|
||||||
{ label: 'Citations', action: 'generate_citations' }
|
{ label: "Citations", action: "generate_citations" },
|
||||||
],
|
],
|
||||||
sources: [
|
sources: [
|
||||||
{ label: 'Add source', action: 'add_source' },
|
{ label: "Add source", action: "add_source" },
|
||||||
{ label: 'Import', action: 'import_sources' },
|
{ label: "Import", action: "import_sources" },
|
||||||
{ label: 'Categories', action: 'show_categories' }
|
{ label: "Categories", action: "show_categories" },
|
||||||
],
|
],
|
||||||
analytics: [
|
analytics: [
|
||||||
{ label: 'Dashboard', action: 'show_dashboard' },
|
{ label: "Dashboard", action: "show_dashboard" },
|
||||||
{ label: 'Reports', action: 'show_reports' },
|
{ label: "Reports", action: "show_reports" },
|
||||||
{ label: 'Export', action: 'export_data' }
|
{ label: "Export", action: "export_data" },
|
||||||
],
|
],
|
||||||
admin: [
|
admin: [
|
||||||
{ label: 'Users', action: 'manage_users' },
|
{ label: "Users", action: "manage_users" },
|
||||||
{ label: 'Settings', action: 'show_settings' },
|
{ label: "Settings", action: "show_settings" },
|
||||||
{ label: 'Logs', action: 'show_logs' }
|
{ label: "Logs", action: "show_logs" },
|
||||||
],
|
],
|
||||||
monitoring: [
|
monitoring: [
|
||||||
{ label: 'Status', action: 'show_status' },
|
{ label: "Status", action: "show_status" },
|
||||||
{ label: 'Alerts', action: 'show_alerts' },
|
{ label: "Alerts", action: "show_alerts" },
|
||||||
{ label: 'Metrics', action: 'show_metrics' }
|
{ label: "Metrics", action: "show_metrics" },
|
||||||
],
|
],
|
||||||
default: [
|
default: [
|
||||||
{ label: 'Help', action: 'show_help' },
|
{ label: "Help", action: "show_help" },
|
||||||
{ label: 'Shortcuts', action: 'show_shortcuts' },
|
{ label: "Shortcuts", action: "show_shortcuts" },
|
||||||
{ label: 'Settings', action: 'open_settings' }
|
{ label: "Settings", action: "open_settings" },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get current app from URL or hash
|
// Get current app from URL or hash
|
||||||
function getCurrentApp() {
|
function getCurrentApp() {
|
||||||
const hash = window.location.hash.replace('#', '');
|
const hash = window.location.hash.replace("#", "");
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
if (hash) return hash;
|
if (hash) return hash;
|
||||||
const match = path.match(/\/([a-z]+)\//);
|
const match = path.match(/\/([a-z]+)\//);
|
||||||
return match ? match[1] : 'default';
|
return match ? match[1] : "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update body data-app attribute
|
// Update body data-app attribute
|
||||||
function updateCurrentApp() {
|
function updateCurrentApp() {
|
||||||
const app = getCurrentApp();
|
const app = getCurrentApp();
|
||||||
document.body.setAttribute('data-app', app);
|
document.body.setAttribute("data-app", app);
|
||||||
loadQuickActions(app);
|
loadQuickActions(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load quick actions for current app
|
// Load quick actions for current app
|
||||||
function loadQuickActions(app) {
|
function loadQuickActions(app) {
|
||||||
const container = document.getElementById('ai-quick-actions');
|
const container = document.getElementById("ai-quick-actions");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const actions = aiQuickActions[app] || aiQuickActions.default;
|
const actions = aiQuickActions[app] || aiQuickActions.default;
|
||||||
container.innerHTML = actions.map(a =>
|
container.innerHTML = actions
|
||||||
`<button class="quick-action-btn" onclick="handleQuickAction('${a.action}')">${a.label}</button>`
|
.map(
|
||||||
).join('');
|
(a) =>
|
||||||
|
`<button class="quick-action-btn" onclick="handleQuickAction('${a.action}')">${a.label}</button>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle quick action click
|
// Handle quick action click
|
||||||
function handleQuickAction(action) {
|
function handleQuickAction(action) {
|
||||||
const input = document.getElementById('ai-input');
|
const input = document.getElementById("ai-input");
|
||||||
const actionMessages = {
|
const actionMessages = {
|
||||||
upload_file: 'Help me upload a file',
|
upload_file: "Help me upload a file",
|
||||||
create_folder: 'Create a new folder',
|
create_folder: "Create a new folder",
|
||||||
search_files: 'Search for files',
|
search_files: "Search for files",
|
||||||
share_item: 'Help me share this item',
|
share_item: "Help me share this item",
|
||||||
create_task: 'Create a new task',
|
create_task: "Create a new task",
|
||||||
show_due_today: 'Show tasks due today',
|
show_due_today: "Show tasks due today",
|
||||||
tasks_summary: 'Give me a summary of my tasks',
|
tasks_summary: "Give me a summary of my tasks",
|
||||||
show_priorities: 'Show my priority tasks',
|
show_priorities: "Show my priority tasks",
|
||||||
compose_email: 'Help me compose an email',
|
compose_email: "Help me compose an email",
|
||||||
show_unread: 'Show unread emails',
|
show_unread: "Show unread emails",
|
||||||
search_mail: 'Search my emails',
|
search_mail: "Search my emails",
|
||||||
mail_summary: 'Summarize my inbox',
|
mail_summary: "Summarize my inbox",
|
||||||
create_event: 'Create a calendar event',
|
create_event: "Create a calendar event",
|
||||||
show_today: 'Show today\'s schedule',
|
show_today: "Show today's schedule",
|
||||||
show_week: 'Show this week\'s events',
|
show_week: "Show this week's events",
|
||||||
find_free_time: 'Find free time slots',
|
find_free_time: "Find free time slots",
|
||||||
start_meeting: 'Start a new meeting',
|
start_meeting: "Start a new meeting",
|
||||||
schedule_meeting: 'Schedule a meeting',
|
schedule_meeting: "Schedule a meeting",
|
||||||
join_meeting: 'Join a meeting',
|
join_meeting: "Join a meeting",
|
||||||
create_document: 'Create a new document',
|
create_document: "Create a new document",
|
||||||
show_templates: 'Show document templates',
|
show_templates: "Show document templates",
|
||||||
show_recent: 'Show recent documents',
|
show_recent: "Show recent documents",
|
||||||
new_research: 'Start new research',
|
new_research: "Start new research",
|
||||||
show_sources: 'Show my sources',
|
show_sources: "Show my sources",
|
||||||
generate_citations: 'Generate citations',
|
generate_citations: "Generate citations",
|
||||||
add_source: 'Add a new source',
|
add_source: "Add a new source",
|
||||||
import_sources: 'Import sources',
|
import_sources: "Import sources",
|
||||||
show_categories: 'Show categories',
|
show_categories: "Show categories",
|
||||||
show_dashboard: 'Show analytics dashboard',
|
show_dashboard: "Show analytics dashboard",
|
||||||
show_reports: 'Show reports',
|
show_reports: "Show reports",
|
||||||
export_data: 'Export analytics data',
|
export_data: "Export analytics data",
|
||||||
manage_users: 'Manage users',
|
manage_users: "Manage users",
|
||||||
show_settings: 'Show admin settings',
|
show_settings: "Show admin settings",
|
||||||
show_logs: 'Show system logs',
|
show_logs: "Show system logs",
|
||||||
show_status: 'Show system status',
|
show_status: "Show system status",
|
||||||
show_alerts: 'Show active alerts',
|
show_alerts: "Show active alerts",
|
||||||
show_metrics: 'Show performance metrics',
|
show_metrics: "Show performance metrics",
|
||||||
show_help: 'Help me get started',
|
show_help: "Help me get started",
|
||||||
show_shortcuts: 'Show keyboard shortcuts',
|
show_shortcuts: "Show keyboard shortcuts",
|
||||||
open_settings: 'Open settings'
|
open_settings: "Open settings",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (input && actionMessages[action]) {
|
if (input && actionMessages[action]) {
|
||||||
|
|
@ -1333,34 +1246,38 @@
|
||||||
|
|
||||||
// Toggle AI panel
|
// Toggle AI panel
|
||||||
function toggleAIPanel() {
|
function toggleAIPanel() {
|
||||||
document.body.classList.toggle('ai-panel-collapsed');
|
document.body.classList.toggle("ai-panel-collapsed");
|
||||||
localStorage.setItem('ai-panel-collapsed', document.body.classList.contains('ai-panel-collapsed'));
|
localStorage.setItem(
|
||||||
|
"ai-panel-collapsed",
|
||||||
|
document.body.classList.contains("ai-panel-collapsed"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send AI message
|
// Send AI message
|
||||||
function sendAIMessage() {
|
function sendAIMessage() {
|
||||||
const input = document.getElementById('ai-input');
|
const input = document.getElementById("ai-input");
|
||||||
const messagesContainer = document.getElementById('ai-messages');
|
const messagesContainer =
|
||||||
|
document.getElementById("ai-messages");
|
||||||
const message = input?.value?.trim();
|
const message = input?.value?.trim();
|
||||||
|
|
||||||
if (!message || !messagesContainer) return;
|
if (!message || !messagesContainer) return;
|
||||||
|
|
||||||
// Add user message
|
// Add user message
|
||||||
const userMsg = document.createElement('div');
|
const userMsg = document.createElement("div");
|
||||||
userMsg.className = 'ai-message user';
|
userMsg.className = "ai-message user";
|
||||||
userMsg.innerHTML = `<div class="ai-message-bubble">${escapeHtml(message)}</div>`;
|
userMsg.innerHTML = `<div class="ai-message-bubble">${escapeHtml(message)}</div>`;
|
||||||
messagesContainer.appendChild(userMsg);
|
messagesContainer.appendChild(userMsg);
|
||||||
|
|
||||||
// Clear input
|
// Clear input
|
||||||
input.value = '';
|
input.value = "";
|
||||||
|
|
||||||
// Scroll to bottom
|
// Scroll to bottom
|
||||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||||
|
|
||||||
// Show typing indicator
|
// Show typing indicator
|
||||||
const typing = document.createElement('div');
|
const typing = document.createElement("div");
|
||||||
typing.className = 'ai-message assistant';
|
typing.className = "ai-message assistant";
|
||||||
typing.id = 'ai-typing';
|
typing.id = "ai-typing";
|
||||||
typing.innerHTML = `<div class="ai-typing-indicator"><span></span><span></span><span></span></div>`;
|
typing.innerHTML = `<div class="ai-typing-indicator"><span></span><span></span><span></span></div>`;
|
||||||
messagesContainer.appendChild(typing);
|
messagesContainer.appendChild(typing);
|
||||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||||
|
|
@ -1368,42 +1285,44 @@
|
||||||
// Simulate AI response (replace with actual API call)
|
// Simulate AI response (replace with actual API call)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
typing.remove();
|
typing.remove();
|
||||||
const aiMsg = document.createElement('div');
|
const aiMsg = document.createElement("div");
|
||||||
aiMsg.className = 'ai-message assistant';
|
aiMsg.className = "ai-message assistant";
|
||||||
aiMsg.innerHTML = `<div class="ai-message-bubble">Entendi! Estou processando sua solicitação: "${escapeHtml(message)}". Como posso ajudar mais?</div>`;
|
aiMsg.innerHTML = `<div class="ai-message-bubble">Entendi! Estou processando sua solicitação: "${escapeHtml(message)}". Como posso ajudar mais?</div>`;
|
||||||
messagesContainer.appendChild(aiMsg);
|
messagesContainer.appendChild(aiMsg);
|
||||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
messagesContainer.scrollTop =
|
||||||
|
messagesContainer.scrollHeight;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape HTML
|
// Escape HTML
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore AI panel state on load
|
// Restore AI panel state on load
|
||||||
function initAIPanel() {
|
function initAIPanel() {
|
||||||
const collapsed = localStorage.getItem('ai-panel-collapsed') === 'true';
|
const collapsed =
|
||||||
|
localStorage.getItem("ai-panel-collapsed") === "true";
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
document.body.classList.add('ai-panel-collapsed');
|
document.body.classList.add("ai-panel-collapsed");
|
||||||
}
|
}
|
||||||
updateCurrentApp();
|
updateCurrentApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize on DOM ready
|
// Initialize on DOM ready
|
||||||
document.addEventListener('DOMContentLoaded', initAIPanel);
|
document.addEventListener("DOMContentLoaded", initAIPanel);
|
||||||
|
|
||||||
// Update app on navigation
|
// Update app on navigation
|
||||||
document.body.addEventListener('htmx:afterSwap', function(e) {
|
document.body.addEventListener("htmx:afterSwap", function (e) {
|
||||||
if (e.detail.target.id === 'main-content') {
|
if (e.detail.target.id === "main-content") {
|
||||||
updateCurrentApp();
|
updateCurrentApp();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also track hash changes
|
// Also track hash changes
|
||||||
window.addEventListener('hashchange', updateCurrentApp);
|
window.addEventListener("hashchange", updateCurrentApp);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
/* Calendar Container */
|
/* Calendar Container */
|
||||||
.calendar-container {
|
.calendar-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 60px);
|
height: 100%;
|
||||||
background: var(--background);
|
min-height: 0;
|
||||||
color: var(--foreground);
|
background: var(--background, var(--bg-primary, #0a0a0f));
|
||||||
|
color: var(--foreground, var(--text-primary, #ffffff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
|
|
@ -135,7 +136,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini-day.has-events::after {
|
.mini-day.has-events::after {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
|
|
@ -199,7 +200,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-checkbox.checked::after {
|
.calendar-checkbox.checked::after {
|
||||||
content: '✓';
|
content: "✓";
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -562,11 +563,26 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.month-event.blue { background: rgba(59, 130, 246, 0.2); color: #3b82f6; }
|
.month-event.blue {
|
||||||
.month-event.green { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
|
background: rgba(59, 130, 246, 0.2);
|
||||||
.month-event.red { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
|
color: #3b82f6;
|
||||||
.month-event.purple { background: rgba(168, 85, 247, 0.2); color: #a855f7; }
|
}
|
||||||
.month-event.orange { background: rgba(249, 115, 22, 0.2); color: #f97316; }
|
.month-event.green {
|
||||||
|
background: rgba(34, 197, 94, 0.2);
|
||||||
|
color: #22c55e;
|
||||||
|
}
|
||||||
|
.month-event.red {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
.month-event.purple {
|
||||||
|
background: rgba(168, 85, 247, 0.2);
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
.month-event.orange {
|
||||||
|
background: rgba(249, 115, 22, 0.2);
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
.more-events {
|
.more-events {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|
@ -592,11 +608,26 @@
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-event.blue { background: rgba(59, 130, 246, 0.9); color: white; }
|
.calendar-event.blue {
|
||||||
.calendar-event.green { background: rgba(34, 197, 94, 0.9); color: white; }
|
background: rgba(59, 130, 246, 0.9);
|
||||||
.calendar-event.red { background: rgba(239, 68, 68, 0.9); color: white; }
|
color: white;
|
||||||
.calendar-event.purple { background: rgba(168, 85, 247, 0.9); color: white; }
|
}
|
||||||
.calendar-event.orange { background: rgba(249, 115, 22, 0.9); color: white; }
|
.calendar-event.green {
|
||||||
|
background: rgba(34, 197, 94, 0.9);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.calendar-event.red {
|
||||||
|
background: rgba(239, 68, 68, 0.9);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.calendar-event.purple {
|
||||||
|
background: rgba(168, 85, 247, 0.9);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.calendar-event.orange {
|
||||||
|
background: rgba(249, 115, 22, 0.9);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.event-title {
|
.event-title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
@ -622,7 +653,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-time-indicator::before {
|
.current-time-indicator::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
top: -4px;
|
top: -4px;
|
||||||
|
|
@ -782,7 +813,9 @@
|
||||||
|
|
||||||
.color-option input:checked + .color-dot {
|
.color-option input:checked + .color-dot {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
box-shadow: 0 0 0 3px var(--background), 0 0 0 5px currentColor;
|
box-shadow:
|
||||||
|
0 0 0 3px var(--background),
|
||||||
|
0 0 0 5px currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
|
|
@ -909,7 +942,9 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s, border-color 0.2s;
|
transition:
|
||||||
|
background 0.2s,
|
||||||
|
border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ical-btn:hover {
|
.ical-btn:hover {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<!-- Quick Actions -->
|
<!-- Quick Actions -->
|
||||||
<div class="sidebar-actions">
|
<div class="sidebar-actions">
|
||||||
<button class="btn-primary-full" id="new-event-btn"
|
<button class="btn-primary-full" id="new-event-btn"
|
||||||
hx-get="/api/calendar/event/new"
|
hx-get="/ui/calendar/event/new"
|
||||||
hx-target="#event-modal-content"
|
hx-target="#event-modal-content"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3>My Calendars</h3>
|
<h3>My Calendars</h3>
|
||||||
<button class="btn-icon-sm" title="Add Calendar"
|
<button class="btn-icon-sm" title="Add Calendar"
|
||||||
hx-get="/api/calendar/new"
|
hx-get="/ui/calendar/new"
|
||||||
hx-target="#calendar-modal-content">
|
hx-target="#calendar-modal-content">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="calendars-list" id="calendars-list"
|
<div class="calendars-list" id="calendars-list"
|
||||||
hx-get="/api/calendar/list"
|
hx-get="/ui/calendar/list"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<!-- Calendars loaded here -->
|
<!-- Calendars loaded here -->
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
<div class="sidebar-section">
|
<div class="sidebar-section">
|
||||||
<h3>Upcoming</h3>
|
<h3>Upcoming</h3>
|
||||||
<div class="upcoming-events" id="upcoming-events"
|
<div class="upcoming-events" id="upcoming-events"
|
||||||
hx-get="/api/calendar/upcoming"
|
hx-get="/ui/calendar/upcoming"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<!-- Upcoming events loaded here -->
|
<!-- Upcoming events loaded here -->
|
||||||
|
|
@ -423,7 +423,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Import to calendar</label>
|
<label>Import to calendar</label>
|
||||||
<select name="calendar_id"
|
<select name="calendar_id"
|
||||||
hx-get="/api/calendar/list"
|
hx-get="/ui/calendar/list"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<option value="">Default Calendar</option>
|
<option value="">Default Calendar</option>
|
||||||
|
|
@ -1692,6 +1692,8 @@
|
||||||
|
|
||||||
function updateCurrentTimeIndicator() {
|
function updateCurrentTimeIndicator() {
|
||||||
const indicator = document.getElementById('current-time-indicator');
|
const indicator = document.getElementById('current-time-indicator');
|
||||||
|
if (!indicator) return; // Guard against null element
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const minutes = now.getHours() * 60 + now.getMinutes();
|
const minutes = now.getHours() * 60 + now.getMinutes();
|
||||||
const top = (minutes / 60) * 48; // 48px per hour
|
const top = (minutes / 60) * 48; // 48px per hour
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 1; transform: scale(1); }
|
0%,
|
||||||
50% { opacity: 0.5; transform: scale(1.2); }
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Messages Area */
|
/* Messages Area */
|
||||||
|
|
@ -62,8 +69,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from { opacity: 0; transform: translateY(10px); }
|
from {
|
||||||
to { opacity: 1; transform: translateY(0); }
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.user {
|
.message.user {
|
||||||
|
|
@ -113,7 +126,7 @@
|
||||||
background: rgba(0, 0, 0, 0.1);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: 'Monaco', 'Menlo', monospace;
|
font-family: "Monaco", "Menlo", monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,25 +178,44 @@ footer {
|
||||||
/* Input Container */
|
/* Input Container */
|
||||||
.input-container {
|
.input-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--surface, var(--secondary-bg, #f9fafb));
|
||||||
|
border: 1px solid var(--border, var(--border-color, #e5e7eb));
|
||||||
|
border-radius: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .input-container,
|
||||||
|
[data-theme="sentient"] .input-container {
|
||||||
|
background: var(--surface, #1a1a24);
|
||||||
|
border-color: var(--border, #2a2a3a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container:focus-within {
|
||||||
|
border-color: var(--accent, var(--accent-color, #3b82f6));
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-glow, rgba(59, 130, 246, 0.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#messageInput {
|
#messageInput {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12px 16px;
|
padding: 8px 12px;
|
||||||
border-radius: 24px;
|
border-radius: 20px;
|
||||||
border: 1px solid var(--border-color, #e5e7eb);
|
border: none;
|
||||||
background: var(--secondary-bg, #f9fafb);
|
background: transparent;
|
||||||
color: var(--text-primary, #1f2937);
|
color: var(--text-primary, #1f2937);
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #messageInput,
|
||||||
|
[data-theme="sentient"] #messageInput {
|
||||||
|
color: var(--text-primary, #ffffff);
|
||||||
|
}
|
||||||
|
|
||||||
#messageInput:focus {
|
#messageInput:focus {
|
||||||
border-color: var(--accent-color, #3b82f6);
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px var(--accent-light, rgba(59, 130, 246, 0.1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#messageInput::placeholder {
|
#messageInput::placeholder {
|
||||||
|
|
@ -192,24 +224,41 @@ footer {
|
||||||
|
|
||||||
#voiceBtn,
|
#voiceBtn,
|
||||||
#sendBtn {
|
#sendBtn {
|
||||||
width: 40px;
|
width: 44px;
|
||||||
height: 40px;
|
height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: none;
|
border: none;
|
||||||
background: var(--accent-color, #3b82f6);
|
background: var(--accent, var(--accent-color, #3b82f6));
|
||||||
color: white;
|
color: var(--accent-foreground, white);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendBtn {
|
||||||
|
background: var(--accent, var(--accent-color, #3b82f6));
|
||||||
|
}
|
||||||
|
|
||||||
|
#voiceBtn {
|
||||||
|
background: var(--surface, var(--secondary-bg, #f9fafb));
|
||||||
|
color: var(--text-primary, #374151);
|
||||||
|
border: 1px solid var(--border, var(--border-color, #e5e7eb));
|
||||||
}
|
}
|
||||||
|
|
||||||
#voiceBtn:hover,
|
#voiceBtn:hover,
|
||||||
#sendBtn:hover {
|
#sendBtn:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
background: var(--accent-hover, #2563eb);
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendBtn:hover {
|
||||||
|
background: var(--accent-hover, var(--accent, #2563eb));
|
||||||
}
|
}
|
||||||
|
|
||||||
#voiceBtn:active,
|
#voiceBtn:active,
|
||||||
|
|
@ -217,6 +266,20 @@ footer {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark/Sentient theme overrides */
|
||||||
|
[data-theme="dark"] #sendBtn,
|
||||||
|
[data-theme="sentient"] #sendBtn {
|
||||||
|
background: var(--accent, #d4f505);
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #voiceBtn,
|
||||||
|
[data-theme="sentient"] #voiceBtn {
|
||||||
|
background: var(--surface, #1a1a24);
|
||||||
|
color: var(--text-primary, #ffffff);
|
||||||
|
border-color: var(--border, #2a2a3a);
|
||||||
|
}
|
||||||
|
|
||||||
/* Scroll to Bottom Button */
|
/* Scroll to Bottom Button */
|
||||||
.scroll-to-bottom {
|
.scroll-to-bottom {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -257,7 +320,6 @@ footer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Projector Overlay */
|
/* Projector Overlay */
|
||||||
.projector-overlay {
|
.projector-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -278,8 +340,12 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
to { opacity: 1; }
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Container */
|
/* Container */
|
||||||
|
|
@ -394,7 +460,9 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Video Player */
|
/* Video Player */
|
||||||
|
|
@ -445,7 +513,7 @@ footer {
|
||||||
|
|
||||||
.projector-code pre {
|
.projector-code pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
|
font-family: "Fira Code", "Monaco", "Consolas", monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #d4d4d4;
|
color: #d4d4d4;
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,86 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* SENTIENT THEME VARIABLES */
|
||||||
|
/* Dark background with neon lime/green accents */
|
||||||
|
/* ============================================ */
|
||||||
|
:root {
|
||||||
|
/* Sentient Core Colors - used by tasks, attendant, and other apps */
|
||||||
|
--sentient-accent: #d4f505;
|
||||||
|
--sentient-accent-hover: #bfdd04;
|
||||||
|
--sentient-accent-light: rgba(212, 245, 5, 0.15);
|
||||||
|
--sentient-accent-glow: rgba(212, 245, 5, 0.3);
|
||||||
|
|
||||||
|
/* Sentient Background Hierarchy */
|
||||||
|
--sentient-bg-primary: #0a0a0a;
|
||||||
|
--sentient-bg-secondary: #111111;
|
||||||
|
--sentient-bg-tertiary: #161616;
|
||||||
|
--sentient-bg-elevated: #1a1a1a;
|
||||||
|
--sentient-bg-hover: #1e1e1e;
|
||||||
|
--sentient-bg-active: #252525;
|
||||||
|
|
||||||
|
/* Sentient Borders */
|
||||||
|
--sentient-border: #2a2a2a;
|
||||||
|
--sentient-border-light: #222222;
|
||||||
|
--sentient-border-accent: rgba(212, 245, 5, 0.3);
|
||||||
|
|
||||||
|
/* Sentient Text Colors */
|
||||||
|
--sentient-text-primary: #ffffff;
|
||||||
|
--sentient-text-secondary: #888888;
|
||||||
|
--sentient-text-tertiary: #666666;
|
||||||
|
--sentient-text-muted: #444444;
|
||||||
|
--sentient-text-accent: #d4f505;
|
||||||
|
|
||||||
|
/* Sentient Status Colors */
|
||||||
|
--sentient-success: #22c55e;
|
||||||
|
--sentient-success-bg: rgba(34, 197, 94, 0.15);
|
||||||
|
--sentient-warning: #f59e0b;
|
||||||
|
--sentient-warning-bg: rgba(245, 158, 11, 0.15);
|
||||||
|
--sentient-error: #ef4444;
|
||||||
|
--sentient-error-bg: rgba(239, 68, 68, 0.15);
|
||||||
|
--sentient-info: #3b82f6;
|
||||||
|
--sentient-info-bg: rgba(59, 130, 246, 0.15);
|
||||||
|
|
||||||
|
/* Sentient Shadows */
|
||||||
|
--sentient-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||||
|
--sentient-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||||
|
--sentient-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||||
|
--sentient-shadow-accent: 0 0 20px rgba(212, 245, 5, 0.2);
|
||||||
|
--sentient-shadow-glow: 0 0 30px rgba(212, 245, 5, 0.15);
|
||||||
|
|
||||||
|
/* Sentient Typography */
|
||||||
|
--sentient-font-family:
|
||||||
|
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
|
sans-serif;
|
||||||
|
|
||||||
|
/* Sentient Border Radius */
|
||||||
|
--sentient-radius-sm: 6px;
|
||||||
|
--sentient-radius-md: 8px;
|
||||||
|
--sentient-radius-lg: 12px;
|
||||||
|
--sentient-radius-xl: 16px;
|
||||||
|
--sentient-radius-full: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply sentient variables when theme is active */
|
||||||
|
[data-theme="sentient"] {
|
||||||
|
--primary-bg: var(--sentient-bg-primary);
|
||||||
|
--primary-fg: var(--sentient-text-primary);
|
||||||
|
--secondary-bg: var(--sentient-bg-secondary);
|
||||||
|
--secondary-fg: var(--sentient-text-secondary);
|
||||||
|
--accent-color: var(--sentient-accent);
|
||||||
|
--accent-hover: var(--sentient-accent-hover);
|
||||||
|
--accent-light: var(--sentient-accent-light);
|
||||||
|
--border-color: var(--sentient-border);
|
||||||
|
--text-primary: var(--sentient-text-primary);
|
||||||
|
--text-secondary: var(--sentient-text-secondary);
|
||||||
|
--text-tertiary: var(--sentient-text-tertiary);
|
||||||
|
--success-color: var(--sentient-success);
|
||||||
|
--warning-color: var(--sentient-warning);
|
||||||
|
--error-color: var(--sentient-error);
|
||||||
|
--info-color: var(--sentient-info);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
/* GLOBAL RESETS */
|
/* GLOBAL RESETS */
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
@ -216,10 +296,11 @@ body {
|
||||||
/* LAYOUT STRUCTURE */
|
/* LAYOUT STRUCTURE */
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
#main-content {
|
#main-content {
|
||||||
height: 100vh;
|
height: calc(100vh - 64px);
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-top: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -83,6 +83,22 @@
|
||||||
--border: #5c3a1e;
|
--border: #5c3a1e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] {
|
||||||
|
--primary: #d4f505;
|
||||||
|
--primary-hover: #bfdd04;
|
||||||
|
--primary-light: rgba(212, 245, 5, 0.15);
|
||||||
|
--bg: #0a0a0a;
|
||||||
|
--surface: #161616;
|
||||||
|
--surface-hover: #1e1e1e;
|
||||||
|
--border: #2a2a2a;
|
||||||
|
--text: #ffffff;
|
||||||
|
--text-secondary: #888888;
|
||||||
|
--success: #22c55e;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--error: #ef4444;
|
||||||
|
--info: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
/* BASE RESETS */
|
/* BASE RESETS */
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
@ -94,7 +110,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||||
|
Arial, sans-serif;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
@ -167,7 +185,9 @@ body {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s, color 0.2s;
|
transition:
|
||||||
|
background 0.2s,
|
||||||
|
color 0.2s;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -314,9 +334,51 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
/* THEME SELECTOR */
|
/* THEME SELECTOR DROPDOWN */
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
||||||
|
.theme-dropdown {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text, #ffffff);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 12px center;
|
||||||
|
padding-right: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dropdown:hover {
|
||||||
|
border-color: var(--text-secondary, #888888);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dropdown:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary, #d4f505);
|
||||||
|
box-shadow: 0 0 0 3px rgba(212, 245, 5, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dropdown option {
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
color: var(--text, #ffffff);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dropdown optgroup {
|
||||||
|
background: var(--bg, #0a0a0a);
|
||||||
|
color: var(--text-secondary, #888888);
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy theme-grid support (if needed) */
|
||||||
.theme-grid {
|
.theme-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
|
@ -385,35 +447,139 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Theme Preview Colors */
|
/* Theme Preview Colors */
|
||||||
.theme-dark .theme-option-inner { background: #0f172a; color: #f8fafc; }
|
.theme-dark .theme-option-inner {
|
||||||
.theme-dark .theme-option-header { background: #1e293b; }
|
background: #0f172a;
|
||||||
.theme-dark .theme-option-dot { background: #3b82f6; }
|
color: #f8fafc;
|
||||||
.theme-dark .theme-option-line { background: #334155; }
|
}
|
||||||
|
.theme-dark .theme-option-header {
|
||||||
|
background: #1e293b;
|
||||||
|
}
|
||||||
|
.theme-dark .theme-option-dot {
|
||||||
|
background: #3b82f6;
|
||||||
|
}
|
||||||
|
.theme-dark .theme-option-line {
|
||||||
|
background: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-light .theme-option-inner { background: #f8fafc; color: #1e293b; }
|
.theme-light .theme-option-inner {
|
||||||
.theme-light .theme-option-header { background: #ffffff; }
|
background: #f8fafc;
|
||||||
.theme-light .theme-option-dot { background: #3b82f6; }
|
color: #1e293b;
|
||||||
.theme-light .theme-option-line { background: #e2e8f0; }
|
}
|
||||||
|
.theme-light .theme-option-header {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
.theme-light .theme-option-dot {
|
||||||
|
background: #3b82f6;
|
||||||
|
}
|
||||||
|
.theme-light .theme-option-line {
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-blue .theme-option-inner { background: #0c1929; color: #f8fafc; }
|
.theme-blue .theme-option-inner {
|
||||||
.theme-blue .theme-option-header { background: #1a2f47; }
|
background: #0c1929;
|
||||||
.theme-blue .theme-option-dot { background: #0ea5e9; }
|
color: #f8fafc;
|
||||||
.theme-blue .theme-option-line { background: #2d4a6f; }
|
}
|
||||||
|
.theme-blue .theme-option-header {
|
||||||
|
background: #1a2f47;
|
||||||
|
}
|
||||||
|
.theme-blue .theme-option-dot {
|
||||||
|
background: #0ea5e9;
|
||||||
|
}
|
||||||
|
.theme-blue .theme-option-line {
|
||||||
|
background: #2d4a6f;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-purple .theme-option-inner { background: #1a0a2e; color: #f8fafc; }
|
.theme-purple .theme-option-inner {
|
||||||
.theme-purple .theme-option-header { background: #2d1b4e; }
|
background: #1a0a2e;
|
||||||
.theme-purple .theme-option-dot { background: #a855f7; }
|
color: #f8fafc;
|
||||||
.theme-purple .theme-option-line { background: #4c2f7e; }
|
}
|
||||||
|
.theme-purple .theme-option-header {
|
||||||
|
background: #2d1b4e;
|
||||||
|
}
|
||||||
|
.theme-purple .theme-option-dot {
|
||||||
|
background: #a855f7;
|
||||||
|
}
|
||||||
|
.theme-purple .theme-option-line {
|
||||||
|
background: #4c2f7e;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-green .theme-option-inner { background: #0a1f15; color: #f8fafc; }
|
.theme-green .theme-option-inner {
|
||||||
.theme-green .theme-option-header { background: #14332a; }
|
background: #0a1f15;
|
||||||
.theme-green .theme-option-dot { background: #22c55e; }
|
color: #f8fafc;
|
||||||
.theme-green .theme-option-line { background: #28604f; }
|
}
|
||||||
|
.theme-green .theme-option-header {
|
||||||
|
background: #14332a;
|
||||||
|
}
|
||||||
|
.theme-green .theme-option-dot {
|
||||||
|
background: #22c55e;
|
||||||
|
}
|
||||||
|
.theme-green .theme-option-line {
|
||||||
|
background: #28604f;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-orange .theme-option-inner { background: #1a1008; color: #f8fafc; }
|
.theme-orange .theme-option-inner {
|
||||||
.theme-orange .theme-option-header { background: #2d1c0f; }
|
background: #1a1008;
|
||||||
.theme-orange .theme-option-dot { background: #f97316; }
|
color: #f8fafc;
|
||||||
.theme-orange .theme-option-line { background: #5c3a1e; }
|
}
|
||||||
|
.theme-orange .theme-option-header {
|
||||||
|
background: #2d1c0f;
|
||||||
|
}
|
||||||
|
.theme-orange .theme-option-dot {
|
||||||
|
background: #f97316;
|
||||||
|
}
|
||||||
|
.theme-orange .theme-option-line {
|
||||||
|
background: #5c3a1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-sentient .theme-option-inner {
|
||||||
|
background: #0a0a0a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.theme-sentient .theme-option-header {
|
||||||
|
background: #161616;
|
||||||
|
}
|
||||||
|
.theme-sentient .theme-option-dot {
|
||||||
|
background: #d4f505;
|
||||||
|
}
|
||||||
|
.theme-sentient .theme-option-line {
|
||||||
|
background: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* SETTINGS PANEL */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
.settings-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
width: 280px;
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel.show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary, #888888);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
/* SETTINGS SHORTCUTS */
|
/* SETTINGS SHORTCUTS */
|
||||||
|
|
@ -647,7 +813,12 @@ body {
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
background: linear-gradient(90deg, var(--border) 25%, var(--surface-hover) 50%, var(--border) 75%);
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--border) 25%,
|
||||||
|
var(--surface-hover) 50%,
|
||||||
|
var(--border) 75%
|
||||||
|
);
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: skeleton-loading 1.5s infinite;
|
animation: skeleton-loading 1.5s infinite;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -668,8 +839,12 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes skeleton-loading {
|
@keyframes skeleton-loading {
|
||||||
0% { background-position: 200% 0; }
|
0% {
|
||||||
100% { background-position: -200% 0; }
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
@ -687,7 +862,9 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
@ -717,10 +894,18 @@ body {
|
||||||
animation: slideIn 0.3s ease;
|
animation: slideIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification.success { border-left: 3px solid var(--success); }
|
.notification.success {
|
||||||
.notification.error { border-left: 3px solid var(--error); }
|
border-left: 3px solid var(--success);
|
||||||
.notification.warning { border-left: 3px solid var(--warning); }
|
}
|
||||||
.notification.info { border-left: 3px solid var(--info); }
|
.notification.error {
|
||||||
|
border-left: 3px solid var(--error);
|
||||||
|
}
|
||||||
|
.notification.warning {
|
||||||
|
border-left: 3px solid var(--warning);
|
||||||
|
}
|
||||||
|
.notification.info {
|
||||||
|
border-left: 3px solid var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from {
|
from {
|
||||||
|
|
|
||||||
|
|
@ -413,18 +413,44 @@ select:focus {
|
||||||
/* MODALS */
|
/* MODALS */
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
||||||
.modal {
|
.modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: hsla(var(--foreground) / 0.5);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 200;
|
z-index: 1100;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
animation: modalFadeIn 0.2s ease;
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 520px;
|
||||||
|
max-height: calc(100vh - 32px);
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: scale(0.95) translateY(-10px);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show .modal {
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy support */
|
||||||
.modal.hidden {
|
.modal.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
@ -442,23 +468,12 @@ select:focus {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
max-height: calc(100vh - 32px);
|
max-height: calc(100vh - 32px);
|
||||||
background: hsl(var(--card));
|
background: var(--surface, #161616);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
animation: modalSlideIn 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes modalSlideIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.95) translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1) translateY(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-sm .modal-content {
|
.modal-sm .modal-content {
|
||||||
|
|
@ -482,28 +497,97 @@ select:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 16px 20px;
|
padding: 20px 24px;
|
||||||
border-bottom: 1px solid hsl(var(--border));
|
border-bottom: 1px solid var(--border, #2a2a2a);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h3 {
|
.modal-header h3 {
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--text, #ffffff);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-secondary, #888888);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: var(--surface-hover, #1e1e1e);
|
||||||
|
color: var(--text, #ffffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 16px 20px;
|
padding: 16px 24px;
|
||||||
border-top: 1px solid hsl(var(--border));
|
border-top: 1px solid var(--border, #2a2a2a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form styles within modals */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary, #888888);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: var(--bg, #0a0a0a);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text, #ffffff);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent, #d4f505);
|
||||||
|
box-shadow: 0 0 0 3px rgba(212, 245, 5, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input::placeholder {
|
||||||
|
color: var(--text-tertiary, #666666);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.form-input {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.form-input {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
@ -940,7 +1024,7 @@ select:focus {
|
||||||
|
|
||||||
.divider-text::before,
|
.divider-text::before,
|
||||||
.divider-text::after {
|
.divider-text::after {
|
||||||
content: '';
|
content: "";
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: hsl(var(--border));
|
background: hsl(var(--border));
|
||||||
|
|
@ -1019,28 +1103,70 @@ select:focus {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gap-1 { gap: 4px; }
|
.gap-1 {
|
||||||
.gap-2 { gap: 8px; }
|
gap: 4px;
|
||||||
.gap-3 { gap: 12px; }
|
}
|
||||||
.gap-4 { gap: 16px; }
|
.gap-2 {
|
||||||
.gap-5 { gap: 20px; }
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.gap-3 {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.gap-4 {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.gap-5 {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.p-1 { padding: 4px; }
|
.p-1 {
|
||||||
.p-2 { padding: 8px; }
|
padding: 4px;
|
||||||
.p-3 { padding: 12px; }
|
}
|
||||||
.p-4 { padding: 16px; }
|
.p-2 {
|
||||||
.p-5 { padding: 20px; }
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.p-3 {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.p-4 {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.m-1 { margin: 4px; }
|
.m-1 {
|
||||||
.m-2 { margin: 8px; }
|
margin: 4px;
|
||||||
.m-3 { margin: 12px; }
|
}
|
||||||
.m-4 { margin: 16px; }
|
.m-2 {
|
||||||
.m-5 { margin: 20px; }
|
margin: 8px;
|
||||||
|
}
|
||||||
|
.m-3 {
|
||||||
|
margin: 12px;
|
||||||
|
}
|
||||||
|
.m-4 {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
.m-5 {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded { border-radius: 6px; }
|
.rounded {
|
||||||
.rounded-lg { border-radius: 12px; }
|
border-radius: 6px;
|
||||||
.rounded-full { border-radius: 9999px; }
|
}
|
||||||
|
.rounded-lg {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.rounded-full {
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
.shadow-sm { box-shadow: 0 1px 2px hsla(var(--foreground) / 0.05); }
|
.shadow-sm {
|
||||||
.shadow { box-shadow: 0 2px 8px hsla(var(--foreground) / 0.08); }
|
box-shadow: 0 1px 2px hsla(var(--foreground) / 0.05);
|
||||||
.shadow-lg { box-shadow: 0 8px 24px hsla(var(--foreground) / 0.12); }
|
}
|
||||||
|
.shadow {
|
||||||
|
box-shadow: 0 2px 8px hsla(var(--foreground) / 0.08);
|
||||||
|
}
|
||||||
|
.shadow-lg {
|
||||||
|
box-shadow: 0 8px 24px hsla(var(--foreground) / 0.12);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||||
|
sans-serif;
|
||||||
background: #0f172a;
|
background: #0f172a;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
@ -49,8 +55,9 @@ nav a.active {
|
||||||
|
|
||||||
/* Main Content */
|
/* Main Content */
|
||||||
#main-content {
|
#main-content {
|
||||||
height: calc(100vh - 60px);
|
height: calc(100vh - 64px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section {
|
.content-section {
|
||||||
|
|
@ -81,7 +88,9 @@ button:disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Utility */
|
/* Utility */
|
||||||
h1, h2, h3 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
977
ui/suite/css/theme-sentient.css
Normal file
977
ui/suite/css/theme-sentient.css
Normal file
|
|
@ -0,0 +1,977 @@
|
||||||
|
/* General Bots Suite - Sentient Theme */
|
||||||
|
/* Dark background with neon lime/green accents */
|
||||||
|
/* Inspired by modern AI dashboard aesthetics */
|
||||||
|
|
||||||
|
[data-theme="sentient"] {
|
||||||
|
/* Core Colors */
|
||||||
|
--accent: #d4f505;
|
||||||
|
--accent-hover: #bfdd04;
|
||||||
|
--accent-light: rgba(212, 245, 5, 0.15);
|
||||||
|
--accent-glow: rgba(212, 245, 5, 0.3);
|
||||||
|
--accent-rgb: 212, 245, 5;
|
||||||
|
|
||||||
|
/* Background Hierarchy */
|
||||||
|
--bg: #0a0a0a;
|
||||||
|
--bg-secondary: #111111;
|
||||||
|
--surface: #161616;
|
||||||
|
--surface-hover: #1e1e1e;
|
||||||
|
--surface-active: #252525;
|
||||||
|
--surface-elevated: #1a1a1a;
|
||||||
|
|
||||||
|
/* Border Colors */
|
||||||
|
--border: #2a2a2a;
|
||||||
|
--border-light: #222222;
|
||||||
|
--border-accent: rgba(212, 245, 5, 0.3);
|
||||||
|
|
||||||
|
/* Text Colors */
|
||||||
|
--text: #ffffff;
|
||||||
|
--text-secondary: #888888;
|
||||||
|
--text-tertiary: #666666;
|
||||||
|
--text-muted: #444444;
|
||||||
|
--text-accent: #d4f505;
|
||||||
|
|
||||||
|
/* Status Colors */
|
||||||
|
--success: #22c55e;
|
||||||
|
--success-bg: rgba(34, 197, 94, 0.15);
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--warning-bg: rgba(245, 158, 11, 0.15);
|
||||||
|
--error: #ef4444;
|
||||||
|
--error-bg: rgba(239, 68, 68, 0.15);
|
||||||
|
--info: #3b82f6;
|
||||||
|
--info-bg: rgba(59, 130, 246, 0.15);
|
||||||
|
|
||||||
|
/* Component Specific */
|
||||||
|
--header-bg: rgba(10, 10, 10, 0.95);
|
||||||
|
--header-border: #1e1e1e;
|
||||||
|
--sidebar-bg: #0f0f0f;
|
||||||
|
--sidebar-border: #1e1e1e;
|
||||||
|
--card-bg: #161616;
|
||||||
|
--card-border: #2a2a2a;
|
||||||
|
--card-hover-border: rgba(212, 245, 5, 0.4);
|
||||||
|
--input-bg: #1a1a1a;
|
||||||
|
--input-border: #2a2a2a;
|
||||||
|
--input-focus-border: #d4f505;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||||
|
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||||
|
--shadow-accent: 0 0 20px rgba(212, 245, 5, 0.2);
|
||||||
|
--shadow-glow: 0 0 30px rgba(212, 245, 5, 0.15);
|
||||||
|
|
||||||
|
/* HSL Bridge Variables for compatibility */
|
||||||
|
--background: 0 0% 4%;
|
||||||
|
--foreground: 0 0% 100%;
|
||||||
|
--card: 0 0% 9%;
|
||||||
|
--card-foreground: 0 0% 100%;
|
||||||
|
--popover: 0 0% 7%;
|
||||||
|
--popover-foreground: 0 0% 100%;
|
||||||
|
--primary: 67 96% 49%;
|
||||||
|
--primary-foreground: 0 0% 0%;
|
||||||
|
--secondary: 0 0% 12%;
|
||||||
|
--secondary-foreground: 0 0% 100%;
|
||||||
|
--muted: 0 0% 15%;
|
||||||
|
--muted-foreground: 0 0% 53%;
|
||||||
|
--accent-hsl: 67 96% 49%;
|
||||||
|
--accent-foreground: 0 0% 0%;
|
||||||
|
--destructive: 0 84% 60%;
|
||||||
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
--border-hsl: 0 0% 17%;
|
||||||
|
--input-hsl: 0 0% 17%;
|
||||||
|
--ring: 67 96% 49%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* SENTIENT THEME - GLOBAL OVERRIDES */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styling */
|
||||||
|
[data-theme="sentient"] .float-header {
|
||||||
|
background: var(--header-bg);
|
||||||
|
border-bottom: 1px solid var(--header-border);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .header-app-tabs .app-tab {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .header-app-tabs .app-tab:hover {
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .header-app-tabs .app-tab.active {
|
||||||
|
color: #000000;
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .header-app-tabs .app-tab.active svg {
|
||||||
|
stroke: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Bar */
|
||||||
|
[data-theme="sentient"] .header-search {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .header-search:focus-within {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: var(--shadow-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .search-shortcut {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon Buttons */
|
||||||
|
[data-theme="sentient"] .icon-button {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .icon-button:hover {
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notification Badge */
|
||||||
|
[data-theme="sentient"] .notification-badge {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User Avatar */
|
||||||
|
[data-theme="sentient"] .user-avatar {
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .user-avatar:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* CARDS & PANELS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .card,
|
||||||
|
[data-theme="sentient"] .panel,
|
||||||
|
[data-theme="sentient"] .intent-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .card:hover,
|
||||||
|
[data-theme="sentient"] .panel:hover,
|
||||||
|
[data-theme="sentient"] .intent-card:hover {
|
||||||
|
border-color: var(--card-hover-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* BUTTONS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-primary,
|
||||||
|
[data-theme="sentient"] .button-primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
border: none;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-primary:hover,
|
||||||
|
[data-theme="sentient"] .button-primary:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
box-shadow: var(--shadow-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-secondary,
|
||||||
|
[data-theme="sentient"] .button-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-secondary:hover,
|
||||||
|
[data-theme="sentient"] .button-secondary:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-ghost {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-ghost:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* STATUS FILTERS / TABS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-filter,
|
||||||
|
[data-theme="sentient"] .filter-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-filter:hover,
|
||||||
|
[data-theme="sentient"] .filter-tab:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-filter.active,
|
||||||
|
[data-theme="sentient"] .filter-tab.active {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
border-color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-filter .count,
|
||||||
|
[data-theme="sentient"] .filter-tab .count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-filter.active .count,
|
||||||
|
[data-theme="sentient"] .filter-tab.active .count {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* PROGRESS BARS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-bar {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-percentage {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* BADGES & TAGS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .badge {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .badge-primary,
|
||||||
|
[data-theme="sentient"] .badge-active {
|
||||||
|
background: var(--accent-light);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .badge-success,
|
||||||
|
[data-theme="sentient"] .badge-complete {
|
||||||
|
background: var(--success-bg);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .badge-warning,
|
||||||
|
[data-theme="sentient"] .badge-pending {
|
||||||
|
background: var(--warning-bg);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .badge-danger,
|
||||||
|
[data-theme="sentient"] .badge-blocked {
|
||||||
|
background: var(--error-bg);
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .badge-secondary,
|
||||||
|
[data-theme="sentient"] .badge-paused {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag pills */
|
||||||
|
[data-theme="sentient"] .tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* INTENT/TASK CARDS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-card {
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-status.active {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-meta-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-health {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--accent-light);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-health-good {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-health-warning {
|
||||||
|
background: var(--warning-bg);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .intent-health-bad {
|
||||||
|
background: var(--error-bg);
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* DECISION REQUIRED PANEL */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-panel {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-title {
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-context {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-btn-primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-btn-primary:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
box-shadow: var(--shadow-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .decision-btn-secondary:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* PROGRESS LOG */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-icon.complete {
|
||||||
|
background: var(--success-bg);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-icon.pending {
|
||||||
|
background: var(--warning-bg);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-icon.active {
|
||||||
|
background: var(--accent-light);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-title {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-detail {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-step {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-step.complete {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-time {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* SIDEBAR / NAVIGATION */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .sidebar {
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
border-right: 1px solid var(--sidebar-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .sidebar-item {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 4px 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .sidebar-item:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .sidebar-item.active {
|
||||||
|
background: var(--accent-light);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* DROPDOWN MENUS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .apps-dropdown,
|
||||||
|
[data-theme="sentient"] .dropdown-menu {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .apps-dropdown-title {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .app-item {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .app-item:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .app-item.active {
|
||||||
|
background: var(--accent-light);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* FORMS & INPUTS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] input,
|
||||||
|
[data-theme="sentient"] textarea,
|
||||||
|
[data-theme="sentient"] select {
|
||||||
|
background: var(--input-bg);
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] input:focus,
|
||||||
|
[data-theme="sentient"] textarea:focus,
|
||||||
|
[data-theme="sentient"] select:focus {
|
||||||
|
border-color: var(--input-focus-border);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] input::placeholder,
|
||||||
|
[data-theme="sentient"] textarea::placeholder {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* MODALS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .modal-content {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .modal-header {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .modal-header h3 {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .modal-body {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .modal-footer {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* TABLES */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] th {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] td {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] tr:hover td {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* SCROLLBAR */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] ::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] ::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] ::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* LOADING STATES */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .loading-overlay {
|
||||||
|
background: rgba(10, 10, 10, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .loading-spinner {
|
||||||
|
border-color: var(--surface-hover);
|
||||||
|
border-top-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .skeleton {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--surface) 25%,
|
||||||
|
var(--surface-hover) 50%,
|
||||||
|
var(--surface) 75%
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-loading 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* CHAT SPECIFIC */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .message-user {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .message-bot {
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .chat-input-container {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .chat-input-container:focus-within {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* ANIMATIONS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .pulse-accent {
|
||||||
|
animation: pulse-accent 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-accent {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 0 0 var(--accent-glow);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 10px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* STATS & METRICS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .stat-card {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .stat-value {
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .stat-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .stat-highlight {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Time saved indicator */
|
||||||
|
[data-theme="sentient"] .time-saved {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--accent-light);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* DETAIL VIEW */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-panel {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-section-title {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* PAUSE/ACTION BUTTONS */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .action-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .action-btn:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text);
|
||||||
|
border-color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .action-btn.pause {
|
||||||
|
border-color: var(--warning);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .action-btn.pause:hover {
|
||||||
|
|
@ -2,17 +2,22 @@
|
||||||
|
|
||||||
.drive-container {
|
.drive-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 60px);
|
height: 100%;
|
||||||
background: var(--bg);
|
min-height: 0;
|
||||||
|
background: var(--bg, var(--bg-primary, #0a0a0f));
|
||||||
|
color: var(--text, var(--text-primary, #ffffff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Drive Sidebar */
|
/* Drive Sidebar */
|
||||||
.drive-sidebar {
|
.drive-sidebar {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
background: var(--surface);
|
min-width: 240px;
|
||||||
border-right: 1px solid var(--border);
|
background: var(--surface, var(--bg-secondary, #12121a));
|
||||||
|
border-right: 1px solid var(--border, #2a2a3a);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drive-sidebar-header {
|
.drive-sidebar-header {
|
||||||
|
|
@ -76,6 +81,87 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drive Header */
|
||||||
|
.drive-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-bottom: 1px solid var(--border, #2a2a3a);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drive Content - File Grid */
|
||||||
|
.drive-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 150px 100px;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary, #6b6b80);
|
||||||
|
border-bottom: 1px solid var(--border, #2a2a3a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-item,
|
||||||
|
.drive-file-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 150px 100px;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-item:hover,
|
||||||
|
.drive-file-item:hover {
|
||||||
|
background: var(--surface-hover, rgba(255, 255, 255, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-item.selected,
|
||||||
|
.drive-file-item.selected {
|
||||||
|
background: var(--accent-light, rgba(59, 130, 246, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text, var(--text-primary, #ffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-date,
|
||||||
|
.file-size {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary, #6b6b80);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drive-toolbar {
|
.drive-toolbar {
|
||||||
|
|
@ -164,11 +250,26 @@
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.folder { background: rgba(249, 115, 22, 0.1); color: #f97316; }
|
.file-icon.folder {
|
||||||
.file-icon.document { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
|
background: rgba(249, 115, 22, 0.1);
|
||||||
.file-icon.image { background: rgba(168, 85, 247, 0.1); color: #a855f7; }
|
color: #f97316;
|
||||||
.file-icon.video { background: rgba(239, 68, 68, 0.1); color: #ef4444; }
|
}
|
||||||
.file-icon.audio { background: rgba(34, 197, 94, 0.1); color: #22c55e; }
|
.file-icon.document {
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
.file-icon.image {
|
||||||
|
background: rgba(168, 85, 247, 0.1);
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
.file-icon.video {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
.file-icon.audio {
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
color: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
|
||||||
|
|
@ -170,24 +170,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="drive-toolbar-right">
|
<div class="drive-toolbar-right">
|
||||||
<div class="drive-search">
|
|
||||||
<svg
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
||||||
</svg>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search files..."
|
|
||||||
id="drive-search-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
class="btn-icon view-toggle"
|
class="btn-icon view-toggle"
|
||||||
data-view="grid"
|
data-view="grid"
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,35 @@
|
||||||
/* Drive Module JavaScript */
|
/* Drive Module JavaScript */
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
let selectedFiles = [];
|
let selectedFiles = [];
|
||||||
let currentPath = '/';
|
let currentPath = "/";
|
||||||
let viewMode = 'grid';
|
let viewMode = "grid";
|
||||||
|
|
||||||
|
// Global function for onclick handlers in HTML
|
||||||
|
window.selectFile = function (element) {
|
||||||
|
const fileId = element.dataset.id;
|
||||||
|
const isSelected = element.classList.contains("selected");
|
||||||
|
|
||||||
|
// If not holding Ctrl/Cmd, deselect all others first
|
||||||
|
if (!event.ctrlKey && !event.metaKey) {
|
||||||
|
document
|
||||||
|
.querySelectorAll(
|
||||||
|
".drive-file-item.selected, .file-item.selected, .file-card.selected, .file-list-item.selected",
|
||||||
|
)
|
||||||
|
.forEach((f) => f.classList.remove("selected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle selection on clicked element
|
||||||
|
if (isSelected && (event.ctrlKey || event.metaKey)) {
|
||||||
|
element.classList.remove("selected");
|
||||||
|
} else {
|
||||||
|
element.classList.add("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedFiles();
|
||||||
|
};
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
bindNavigation();
|
bindNavigation();
|
||||||
|
|
@ -16,29 +40,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindNavigation() {
|
function bindNavigation() {
|
||||||
document.querySelectorAll('.drive-nav-item').forEach(item => {
|
document.querySelectorAll(".drive-nav-item").forEach((item) => {
|
||||||
item.addEventListener('click', function() {
|
item.addEventListener("click", function () {
|
||||||
document.querySelectorAll('.drive-nav-item').forEach(i => i.classList.remove('active'));
|
document
|
||||||
this.classList.add('active');
|
.querySelectorAll(".drive-nav-item")
|
||||||
|
.forEach((i) => i.classList.remove("active"));
|
||||||
|
this.classList.add("active");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindFileSelection() {
|
function bindFileSelection() {
|
||||||
document.querySelectorAll('.file-card, .file-list-item').forEach(file => {
|
document.querySelectorAll(".file-card, .file-list-item").forEach((file) => {
|
||||||
file.addEventListener('click', function(e) {
|
file.addEventListener("click", function (e) {
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
this.classList.toggle('selected');
|
this.classList.toggle("selected");
|
||||||
} else {
|
} else {
|
||||||
document.querySelectorAll('.file-card.selected, .file-list-item.selected')
|
document
|
||||||
.forEach(f => f.classList.remove('selected'));
|
.querySelectorAll(".file-card.selected, .file-list-item.selected")
|
||||||
this.classList.add('selected');
|
.forEach((f) => f.classList.remove("selected"));
|
||||||
|
this.classList.add("selected");
|
||||||
}
|
}
|
||||||
updateSelectedFiles();
|
updateSelectedFiles();
|
||||||
});
|
});
|
||||||
|
|
||||||
file.addEventListener('dblclick', function() {
|
file.addEventListener("dblclick", function () {
|
||||||
const isFolder = this.dataset.type === 'folder';
|
const isFolder = this.dataset.type === "folder";
|
||||||
if (isFolder) {
|
if (isFolder) {
|
||||||
navigateToFolder(this.dataset.path);
|
navigateToFolder(this.dataset.path);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -49,52 +76,56 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindDragAndDrop() {
|
function bindDragAndDrop() {
|
||||||
const uploadZone = document.querySelector('.upload-zone');
|
const uploadZone = document.querySelector(".upload-zone");
|
||||||
if (!uploadZone) return;
|
if (!uploadZone) return;
|
||||||
|
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
|
["dragenter", "dragover", "dragleave", "drop"].forEach((event) => {
|
||||||
uploadZone.addEventListener(event, (e) => {
|
uploadZone.addEventListener(event, (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
['dragenter', 'dragover'].forEach(event => {
|
["dragenter", "dragover"].forEach((event) => {
|
||||||
uploadZone.addEventListener(event, () => uploadZone.classList.add('dragover'));
|
uploadZone.addEventListener(event, () =>
|
||||||
|
uploadZone.classList.add("dragover"),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
['dragleave', 'drop'].forEach(event => {
|
["dragleave", "drop"].forEach((event) => {
|
||||||
uploadZone.addEventListener(event, () => uploadZone.classList.remove('dragover'));
|
uploadZone.addEventListener(event, () =>
|
||||||
|
uploadZone.classList.remove("dragover"),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadZone.addEventListener('drop', (e) => {
|
uploadZone.addEventListener("drop", (e) => {
|
||||||
const files = e.dataTransfer.files;
|
const files = e.dataTransfer.files;
|
||||||
handleFileUpload(files);
|
handleFileUpload(files);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindContextMenu() {
|
function bindContextMenu() {
|
||||||
document.addEventListener('contextmenu', (e) => {
|
document.addEventListener("contextmenu", (e) => {
|
||||||
const fileCard = e.target.closest('.file-card, .file-list-item');
|
const fileCard = e.target.closest(".file-card, .file-list-item");
|
||||||
if (fileCard) {
|
if (fileCard) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showContextMenu(e.clientX, e.clientY, fileCard);
|
showContextMenu(e.clientX, e.clientY, fileCard);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', hideContextMenu);
|
document.addEventListener("click", hideContextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindKeyboardShortcuts() {
|
function bindKeyboardShortcuts() {
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener("keydown", (e) => {
|
||||||
if (e.key === 'Delete' && selectedFiles.length > 0) {
|
if (e.key === "Delete" && selectedFiles.length > 0) {
|
||||||
deleteSelectedFiles();
|
deleteSelectedFiles();
|
||||||
}
|
}
|
||||||
if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
|
if (e.key === "a" && (e.ctrlKey || e.metaKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selectAllFiles();
|
selectAllFiles();
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === "Escape") {
|
||||||
deselectAllFiles();
|
deselectAllFiles();
|
||||||
hideContextMenu();
|
hideContextMenu();
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +133,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedFiles() {
|
function updateSelectedFiles() {
|
||||||
selectedFiles = Array.from(document.querySelectorAll('.file-card.selected, .file-list-item.selected'))
|
selectedFiles = Array.from(
|
||||||
.map(f => f.dataset.id);
|
document.querySelectorAll(
|
||||||
|
".file-card.selected, .file-list-item.selected",
|
||||||
|
),
|
||||||
|
).map((f) => f.dataset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateToFolder(path) {
|
function navigateToFolder(path) {
|
||||||
|
|
@ -112,50 +146,53 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function openFile(fileId) {
|
function openFile(fileId) {
|
||||||
console.log('Opening file:', fileId);
|
console.log("Opening file:", fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileUpload(files) {
|
function handleFileUpload(files) {
|
||||||
console.log('Uploading files:', files);
|
console.log("Uploading files:", files);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showContextMenu(x, y, fileCard) {
|
function showContextMenu(x, y, fileCard) {
|
||||||
hideContextMenu();
|
hideContextMenu();
|
||||||
const menu = document.getElementById('context-menu');
|
const menu = document.getElementById("context-menu");
|
||||||
if (menu) {
|
if (menu) {
|
||||||
menu.style.left = x + 'px';
|
menu.style.left = x + "px";
|
||||||
menu.style.top = y + 'px';
|
menu.style.top = y + "px";
|
||||||
menu.classList.remove('hidden');
|
menu.classList.remove("hidden");
|
||||||
menu.dataset.fileId = fileCard.dataset.id;
|
menu.dataset.fileId = fileCard.dataset.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideContextMenu() {
|
function hideContextMenu() {
|
||||||
const menu = document.getElementById('context-menu');
|
const menu = document.getElementById("context-menu");
|
||||||
if (menu) menu.classList.add('hidden');
|
if (menu) menu.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAllFiles() {
|
function selectAllFiles() {
|
||||||
document.querySelectorAll('.file-card, .file-list-item').forEach(f => f.classList.add('selected'));
|
document
|
||||||
|
.querySelectorAll(".file-card, .file-list-item")
|
||||||
|
.forEach((f) => f.classList.add("selected"));
|
||||||
updateSelectedFiles();
|
updateSelectedFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deselectAllFiles() {
|
function deselectAllFiles() {
|
||||||
document.querySelectorAll('.file-card.selected, .file-list-item.selected')
|
document
|
||||||
.forEach(f => f.classList.remove('selected'));
|
.querySelectorAll(".file-card.selected, .file-list-item.selected")
|
||||||
|
.forEach((f) => f.classList.remove("selected"));
|
||||||
selectedFiles = [];
|
selectedFiles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteSelectedFiles() {
|
function deleteSelectedFiles() {
|
||||||
if (confirm(`Delete ${selectedFiles.length} file(s)?`)) {
|
if (confirm(`Delete ${selectedFiles.length} file(s)?`)) {
|
||||||
console.log('Deleting:', selectedFiles);
|
console.log("Deleting:", selectedFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.DriveModule = { init };
|
window.DriveModule = { init };
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
} else {
|
} else {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,21 +148,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar-center">
|
|
||||||
<div class="search-box">
|
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
||||||
</svg>
|
|
||||||
<input type="text"
|
|
||||||
placeholder="Search in Drive..."
|
|
||||||
name="q"
|
|
||||||
hx-get="/api/drive/search"
|
|
||||||
hx-trigger="keyup changed delay:300ms"
|
|
||||||
hx-target="#file-grid"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
<div class="view-toggle">
|
<div class="view-toggle">
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,14 @@
|
||||||
name="description"
|
name="description"
|
||||||
content="General Bots - AI-powered workspace"
|
content="General Bots - AI-powered workspace"
|
||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#3b82f6" />
|
<meta name="theme-color" content="#d4f505" />
|
||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
<link rel="stylesheet" href="css/app.css" />
|
||||||
<link rel="stylesheet" href="css/apps-extended.css" />
|
<link rel="stylesheet" href="css/apps-extended.css" />
|
||||||
<link rel="stylesheet" href="css/components.css" />
|
<link rel="stylesheet" href="css/components.css" />
|
||||||
<link rel="stylesheet" href="css/base.css" />
|
<link rel="stylesheet" href="css/base.css" />
|
||||||
|
<link rel="stylesheet" href="css/theme-sentient.css" />
|
||||||
|
|
||||||
<!-- App-specific CSS -->
|
<!-- App-specific CSS -->
|
||||||
<link rel="stylesheet" href="chat/chat.css" />
|
<link rel="stylesheet" href="chat/chat.css" />
|
||||||
|
|
@ -41,7 +42,7 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body data-theme="sentient">
|
||||||
<!-- Loading overlay -->
|
<!-- Loading overlay -->
|
||||||
<div class="loading-overlay" id="loadingOverlay">
|
<div class="loading-overlay" id="loadingOverlay">
|
||||||
<div class="loading-spinner"></div>
|
<div class="loading-spinner"></div>
|
||||||
|
|
@ -231,7 +232,13 @@
|
||||||
</svg>
|
</svg>
|
||||||
<span class="notification-badge">3</span>
|
<span class="notification-badge">3</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="icon-button" title="Settings">
|
<button
|
||||||
|
class="icon-button"
|
||||||
|
id="settingsBtn"
|
||||||
|
title="Settings"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-haspopup="true"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
|
|
@ -247,6 +254,108 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Settings/Theme Panel -->
|
||||||
|
<div
|
||||||
|
class="settings-panel"
|
||||||
|
id="settingsPanel"
|
||||||
|
role="menu"
|
||||||
|
aria-label="Settings"
|
||||||
|
>
|
||||||
|
<div class="settings-panel-title">Theme</div>
|
||||||
|
<div class="theme-grid">
|
||||||
|
<button
|
||||||
|
class="theme-option theme-sentient"
|
||||||
|
data-theme="sentient"
|
||||||
|
title="Sentient"
|
||||||
|
>
|
||||||
|
<div class="theme-option-inner">
|
||||||
|
<div class="theme-option-header">
|
||||||
|
<div class="theme-option-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-option-body">
|
||||||
|
<div class="theme-option-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="theme-option-name">Sentient</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="theme-option theme-dark"
|
||||||
|
data-theme="dark"
|
||||||
|
title="Dark"
|
||||||
|
>
|
||||||
|
<div class="theme-option-inner">
|
||||||
|
<div class="theme-option-header">
|
||||||
|
<div class="theme-option-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-option-body">
|
||||||
|
<div class="theme-option-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="theme-option-name">Dark</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="theme-option theme-light"
|
||||||
|
data-theme="light"
|
||||||
|
title="Light"
|
||||||
|
>
|
||||||
|
<div class="theme-option-inner">
|
||||||
|
<div class="theme-option-header">
|
||||||
|
<div class="theme-option-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-option-body">
|
||||||
|
<div class="theme-option-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="theme-option-name">Light</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="theme-option theme-orange"
|
||||||
|
data-theme="orange"
|
||||||
|
title="Orange"
|
||||||
|
>
|
||||||
|
<div class="theme-option-inner">
|
||||||
|
<div class="theme-option-header">
|
||||||
|
<div class="theme-option-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-option-body">
|
||||||
|
<div class="theme-option-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="theme-option-name">Orange</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="theme-option theme-purple"
|
||||||
|
data-theme="purple"
|
||||||
|
title="Purple"
|
||||||
|
>
|
||||||
|
<div class="theme-option-inner">
|
||||||
|
<div class="theme-option-header">
|
||||||
|
<div class="theme-option-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-option-body">
|
||||||
|
<div class="theme-option-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="theme-option-name">Purple</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="theme-option theme-green"
|
||||||
|
data-theme="green"
|
||||||
|
title="Green"
|
||||||
|
>
|
||||||
|
<div class="theme-option-inner">
|
||||||
|
<div class="theme-option-header">
|
||||||
|
<div class="theme-option-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="theme-option-body">
|
||||||
|
<div class="theme-option-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="theme-option-name">Green</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Apps dropdown menu -->
|
<!-- Apps dropdown menu -->
|
||||||
<nav
|
<nav
|
||||||
class="apps-dropdown"
|
class="apps-dropdown"
|
||||||
|
|
@ -804,12 +913,17 @@
|
||||||
// Simple apps menu handling
|
// Simple apps menu handling
|
||||||
const appsBtn = document.getElementById("appsButton");
|
const appsBtn = document.getElementById("appsButton");
|
||||||
const appsDropdown = document.getElementById("appsDropdown");
|
const appsDropdown = document.getElementById("appsDropdown");
|
||||||
|
const settingsBtn = document.getElementById("settingsBtn");
|
||||||
|
const settingsPanel = document.getElementById("settingsPanel");
|
||||||
|
|
||||||
if (appsBtn && appsDropdown) {
|
if (appsBtn && appsDropdown) {
|
||||||
appsBtn.addEventListener("click", (e) => {
|
appsBtn.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const isOpen = appsDropdown.classList.toggle("show");
|
const isOpen = appsDropdown.classList.toggle("show");
|
||||||
appsBtn.setAttribute("aria-expanded", isOpen);
|
appsBtn.setAttribute("aria-expanded", isOpen);
|
||||||
|
// Close settings panel
|
||||||
|
if (settingsPanel)
|
||||||
|
settingsPanel.classList.remove("show");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
|
|
@ -823,6 +937,69 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings panel handling
|
||||||
|
if (settingsBtn && settingsPanel) {
|
||||||
|
settingsBtn.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const isOpen = settingsPanel.classList.toggle("show");
|
||||||
|
settingsBtn.setAttribute("aria-expanded", isOpen);
|
||||||
|
// Close apps dropdown
|
||||||
|
if (appsDropdown) appsDropdown.classList.remove("show");
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (
|
||||||
|
!settingsPanel.contains(e.target) &&
|
||||||
|
!settingsBtn.contains(e.target)
|
||||||
|
) {
|
||||||
|
settingsPanel.classList.remove("show");
|
||||||
|
settingsBtn.setAttribute("aria-expanded", "false");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme selection handling
|
||||||
|
const themeOptions = document.querySelectorAll(".theme-option");
|
||||||
|
const savedTheme =
|
||||||
|
localStorage.getItem("gb-theme") || "sentient";
|
||||||
|
|
||||||
|
// Apply saved theme
|
||||||
|
document.body.setAttribute("data-theme", savedTheme);
|
||||||
|
document
|
||||||
|
.querySelector(`.theme-option[data-theme="${savedTheme}"]`)
|
||||||
|
?.classList.add("active");
|
||||||
|
|
||||||
|
themeOptions.forEach((option) => {
|
||||||
|
option.addEventListener("click", () => {
|
||||||
|
const theme = option.getAttribute("data-theme");
|
||||||
|
document.body.setAttribute("data-theme", theme);
|
||||||
|
localStorage.setItem("gb-theme", theme);
|
||||||
|
themeOptions.forEach((o) =>
|
||||||
|
o.classList.remove("active"),
|
||||||
|
);
|
||||||
|
option.classList.add("active");
|
||||||
|
|
||||||
|
// Update theme-color meta tag
|
||||||
|
const themeColors = {
|
||||||
|
dark: "#3b82f6",
|
||||||
|
light: "#3b82f6",
|
||||||
|
purple: "#a855f7",
|
||||||
|
green: "#22c55e",
|
||||||
|
orange: "#f97316",
|
||||||
|
sentient: "#d4f505",
|
||||||
|
};
|
||||||
|
const metaTheme = document.querySelector(
|
||||||
|
'meta[name="theme-color"]',
|
||||||
|
);
|
||||||
|
if (metaTheme) {
|
||||||
|
metaTheme.setAttribute(
|
||||||
|
"content",
|
||||||
|
themeColors[theme] || "#d4f505",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Handle app item clicks - update active state
|
// Handle app item clicks - update active state
|
||||||
document.querySelectorAll(".app-item").forEach((item) => {
|
document.querySelectorAll(".app-item").forEach((item) => {
|
||||||
item.addEventListener("click", function () {
|
item.addEventListener("click", function () {
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,19 @@ if (settingsBtn) {
|
||||||
|
|
||||||
// Close dropdowns when clicking outside
|
// Close dropdowns when clicking outside
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
if (appsDropdown && !appsDropdown.contains(e.target) && !appsBtn.contains(e.target)) {
|
if (
|
||||||
|
appsDropdown &&
|
||||||
|
!appsDropdown.contains(e.target) &&
|
||||||
|
!appsBtn.contains(e.target)
|
||||||
|
) {
|
||||||
appsDropdown.classList.remove("show");
|
appsDropdown.classList.remove("show");
|
||||||
appsBtn.setAttribute("aria-expanded", "false");
|
appsBtn.setAttribute("aria-expanded", "false");
|
||||||
}
|
}
|
||||||
if (settingsPanel && !settingsPanel.contains(e.target) && !settingsBtn.contains(e.target)) {
|
if (
|
||||||
|
settingsPanel &&
|
||||||
|
!settingsPanel.contains(e.target) &&
|
||||||
|
!settingsBtn.contains(e.target)
|
||||||
|
) {
|
||||||
settingsPanel.classList.remove("show");
|
settingsPanel.classList.remove("show");
|
||||||
settingsBtn.setAttribute("aria-expanded", "false");
|
settingsBtn.setAttribute("aria-expanded", "false");
|
||||||
}
|
}
|
||||||
|
|
@ -99,10 +107,31 @@ document.body.addEventListener("htmx:afterSwap", (e) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme handling
|
// Theme handling
|
||||||
|
// Available themes: dark, light, blue, purple, green, orange, sentient
|
||||||
const themeOptions = document.querySelectorAll(".theme-option");
|
const themeOptions = document.querySelectorAll(".theme-option");
|
||||||
const savedTheme = localStorage.getItem("gb-theme") || "dark";
|
const savedTheme = localStorage.getItem("gb-theme") || "sentient";
|
||||||
document.body.setAttribute("data-theme", savedTheme);
|
document.body.setAttribute("data-theme", savedTheme);
|
||||||
document.querySelector(`.theme-option[data-theme="${savedTheme}"]`)?.classList.add("active");
|
document
|
||||||
|
.querySelector(`.theme-option[data-theme="${savedTheme}"]`)
|
||||||
|
?.classList.add("active");
|
||||||
|
|
||||||
|
// Update theme-color meta tag based on theme
|
||||||
|
function updateThemeColor(theme) {
|
||||||
|
const themeColors = {
|
||||||
|
dark: "#3b82f6",
|
||||||
|
light: "#3b82f6",
|
||||||
|
blue: "#0ea5e9",
|
||||||
|
purple: "#a855f7",
|
||||||
|
green: "#22c55e",
|
||||||
|
orange: "#f97316",
|
||||||
|
sentient: "#d4f505",
|
||||||
|
};
|
||||||
|
const metaTheme = document.querySelector('meta[name="theme-color"]');
|
||||||
|
if (metaTheme) {
|
||||||
|
metaTheme.setAttribute("content", themeColors[theme] || "#d4f505");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateThemeColor(savedTheme);
|
||||||
|
|
||||||
themeOptions.forEach((option) => {
|
themeOptions.forEach((option) => {
|
||||||
option.addEventListener("click", () => {
|
option.addEventListener("click", () => {
|
||||||
|
|
@ -111,9 +140,20 @@ themeOptions.forEach((option) => {
|
||||||
localStorage.setItem("gb-theme", theme);
|
localStorage.setItem("gb-theme", theme);
|
||||||
themeOptions.forEach((o) => o.classList.remove("active"));
|
themeOptions.forEach((o) => o.classList.remove("active"));
|
||||||
option.classList.add("active");
|
option.classList.add("active");
|
||||||
|
updateThemeColor(theme);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Global theme setter function (can be called from settings or elsewhere)
|
||||||
|
window.setTheme = function (theme) {
|
||||||
|
document.body.setAttribute("data-theme", theme);
|
||||||
|
localStorage.setItem("gb-theme", theme);
|
||||||
|
themeOptions.forEach((o) => {
|
||||||
|
o.classList.toggle("active", o.getAttribute("data-theme") === theme);
|
||||||
|
});
|
||||||
|
updateThemeColor(theme);
|
||||||
|
};
|
||||||
|
|
||||||
// Quick Settings Toggle
|
// Quick Settings Toggle
|
||||||
function toggleQuickSetting(el) {
|
function toggleQuickSetting(el) {
|
||||||
el.classList.toggle("active");
|
el.classList.toggle("active");
|
||||||
|
|
@ -135,7 +175,7 @@ function showKeyboardShortcuts() {
|
||||||
window.showNotification(
|
window.showNotification(
|
||||||
"Alt+1-9,0 for apps, Alt+A Admin, Alt+M Monitoring, Alt+S Settings, Alt+, quick settings",
|
"Alt+1-9,0 for apps, Alt+A Admin, Alt+M Monitoring, Alt+S Settings, Alt+, quick settings",
|
||||||
"info",
|
"info",
|
||||||
8000
|
8000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +224,9 @@ document.addEventListener("keydown", function (e) {
|
||||||
if (!appsGrid || !appsGrid.closest(".show")) return;
|
if (!appsGrid || !appsGrid.closest(".show")) return;
|
||||||
|
|
||||||
const items = Array.from(appsGrid.querySelectorAll(".app-item"));
|
const items = Array.from(appsGrid.querySelectorAll(".app-item"));
|
||||||
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
const currentIndex = items.findIndex(
|
||||||
|
(item) => item === document.activeElement,
|
||||||
|
);
|
||||||
|
|
||||||
if (currentIndex === -1) return;
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
|
@ -317,14 +359,17 @@ document.body.addEventListener("htmx:responseError", function (e) {
|
||||||
let currentRetries = htmxRetryConfig.retryCount.get(retryKey) || 0;
|
let currentRetries = htmxRetryConfig.retryCount.get(retryKey) || 0;
|
||||||
|
|
||||||
// Auto-retry for network errors (status 0) or server errors (5xx)
|
// Auto-retry for network errors (status 0) or server errors (5xx)
|
||||||
if ((xhr.status === 0 || xhr.status >= 500) && currentRetries < htmxRetryConfig.maxRetries) {
|
if (
|
||||||
|
(xhr.status === 0 || xhr.status >= 500) &&
|
||||||
|
currentRetries < htmxRetryConfig.maxRetries
|
||||||
|
) {
|
||||||
htmxRetryConfig.retryCount.set(retryKey, currentRetries + 1);
|
htmxRetryConfig.retryCount.set(retryKey, currentRetries + 1);
|
||||||
const delay = htmxRetryConfig.retryDelay * Math.pow(2, currentRetries);
|
const delay = htmxRetryConfig.retryDelay * Math.pow(2, currentRetries);
|
||||||
|
|
||||||
window.showNotification(
|
window.showNotification(
|
||||||
`Request failed. Retrying in ${delay / 1000}s... (${currentRetries + 1}/${htmxRetryConfig.maxRetries})`,
|
`Request failed. Retrying in ${delay / 1000}s... (${currentRetries + 1}/${htmxRetryConfig.maxRetries})`,
|
||||||
"warning",
|
"warning",
|
||||||
delay
|
delay,
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -342,9 +387,11 @@ document.body.addEventListener("htmx:responseError", function (e) {
|
||||||
} else if (xhr.status === 404) {
|
} else if (xhr.status === 404) {
|
||||||
errorMessage = "The requested content was not found.";
|
errorMessage = "The requested content was not found.";
|
||||||
} else if (xhr.status >= 500) {
|
} else if (xhr.status >= 500) {
|
||||||
errorMessage = "The server is experiencing issues. Please try again later.";
|
errorMessage =
|
||||||
|
"The server is experiencing issues. Please try again later.";
|
||||||
} else if (xhr.status === 0) {
|
} else if (xhr.status === 0) {
|
||||||
errorMessage = "Unable to connect. Please check your internet connection.";
|
errorMessage =
|
||||||
|
"Unable to connect. Please check your internet connection.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target && target.id === "main-content") {
|
if (target && target.id === "main-content") {
|
||||||
|
|
@ -365,10 +412,18 @@ document.body.addEventListener("htmx:afterRequest", function (e) {
|
||||||
|
|
||||||
// Handle timeout errors
|
// Handle timeout errors
|
||||||
document.body.addEventListener("htmx:timeout", function (e) {
|
document.body.addEventListener("htmx:timeout", function (e) {
|
||||||
window.showNotification("Request timed out. Please try again.", "warning", 5000);
|
window.showNotification(
|
||||||
|
"Request timed out. Please try again.",
|
||||||
|
"warning",
|
||||||
|
5000,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle send errors (network issues before request sent)
|
// Handle send errors (network issues before request sent)
|
||||||
document.body.addEventListener("htmx:sendError", function (e) {
|
document.body.addEventListener("htmx:sendError", function (e) {
|
||||||
window.showNotification("Network error. Please check your connection.", "error", 5000);
|
window.showNotification(
|
||||||
|
"Network error. Please check your connection.",
|
||||||
|
"error",
|
||||||
|
5000,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// HTMX-based application initialization
|
// HTMX-based application initialization
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const config = {
|
const config = {
|
||||||
wsUrl: '/ws',
|
wsUrl: "/ws",
|
||||||
apiBase: '/api',
|
apiBase: "/api",
|
||||||
reconnectDelay: 3000,
|
reconnectDelay: 3000,
|
||||||
maxReconnectAttempts: 5
|
maxReconnectAttempts: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
@ -17,50 +17,70 @@
|
||||||
// Initialize HTMX extensions
|
// Initialize HTMX extensions
|
||||||
function initHTMX() {
|
function initHTMX() {
|
||||||
// Configure HTMX
|
// Configure HTMX
|
||||||
htmx.config.defaultSwapStyle = 'innerHTML';
|
htmx.config.defaultSwapStyle = "innerHTML";
|
||||||
htmx.config.defaultSettleDelay = 100;
|
htmx.config.defaultSettleDelay = 100;
|
||||||
htmx.config.timeout = 10000;
|
htmx.config.timeout = 10000;
|
||||||
|
|
||||||
// Add CSRF token to all requests if available
|
// Add CSRF token to all requests if available
|
||||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
document.body.addEventListener("htmx:configRequest", (event) => {
|
||||||
const token = localStorage.getItem('csrf_token');
|
const token = localStorage.getItem("csrf_token");
|
||||||
if (token) {
|
if (token) {
|
||||||
event.detail.headers['X-CSRF-Token'] = token;
|
event.detail.headers["X-CSRF-Token"] = token;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle errors globally
|
// Handle errors globally
|
||||||
document.body.addEventListener('htmx:responseError', (event) => {
|
document.body.addEventListener("htmx:responseError", (event) => {
|
||||||
console.error('HTMX Error:', event.detail);
|
console.error("HTMX Error:", event.detail);
|
||||||
showNotification('Connection error. Please try again.', 'error');
|
showNotification("Connection error. Please try again.", "error");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle before swap to prevent errors when target doesn't exist
|
||||||
|
document.body.addEventListener("htmx:beforeSwap", (event) => {
|
||||||
|
const target = event.detail.target;
|
||||||
|
const status = event.detail.xhr?.status;
|
||||||
|
|
||||||
|
// If target doesn't exist or response is 404, prevent the swap
|
||||||
|
if (!target || status === 404) {
|
||||||
|
event.detail.shouldSwap = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For empty responses, set empty content to prevent insertBefore errors
|
||||||
|
if (
|
||||||
|
!event.detail.serverResponse ||
|
||||||
|
event.detail.serverResponse.trim() === ""
|
||||||
|
) {
|
||||||
|
event.detail.serverResponse = "<!-- empty -->";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle successful swaps
|
// Handle successful swaps
|
||||||
document.body.addEventListener('htmx:afterSwap', (event) => {
|
document.body.addEventListener("htmx:afterSwap", (event) => {
|
||||||
// Auto-scroll messages if in chat
|
// Auto-scroll messages if in chat
|
||||||
const messages = document.getElementById('messages');
|
const messages = document.getElementById("messages");
|
||||||
if (messages && event.detail.target === messages) {
|
if (messages && event.detail.target === messages) {
|
||||||
messages.scrollTop = messages.scrollHeight;
|
messages.scrollTop = messages.scrollHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle WebSocket messages
|
// Handle WebSocket messages
|
||||||
document.body.addEventListener('htmx:wsMessage', (event) => {
|
document.body.addEventListener("htmx:wsMessage", (event) => {
|
||||||
handleWebSocketMessage(JSON.parse(event.detail.message));
|
handleWebSocketMessage(JSON.parse(event.detail.message));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle WebSocket connection events
|
// Handle WebSocket connection events
|
||||||
document.body.addEventListener('htmx:wsConnecting', () => {
|
document.body.addEventListener("htmx:wsConnecting", () => {
|
||||||
updateConnectionStatus('connecting');
|
updateConnectionStatus("connecting");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.addEventListener('htmx:wsOpen', () => {
|
document.body.addEventListener("htmx:wsOpen", () => {
|
||||||
updateConnectionStatus('connected');
|
updateConnectionStatus("connected");
|
||||||
reconnectAttempts = 0;
|
reconnectAttempts = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.addEventListener('htmx:wsClose', () => {
|
document.body.addEventListener("htmx:wsClose", () => {
|
||||||
updateConnectionStatus('disconnected');
|
updateConnectionStatus("disconnected");
|
||||||
attemptReconnect();
|
attemptReconnect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -68,30 +88,30 @@
|
||||||
// Handle WebSocket messages
|
// Handle WebSocket messages
|
||||||
function handleWebSocketMessage(message) {
|
function handleWebSocketMessage(message) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'message':
|
case "message":
|
||||||
appendMessage(message);
|
appendMessage(message);
|
||||||
break;
|
break;
|
||||||
case 'notification':
|
case "notification":
|
||||||
showNotification(message.text, message.severity);
|
showNotification(message.text, message.severity);
|
||||||
break;
|
break;
|
||||||
case 'status':
|
case "status":
|
||||||
updateStatus(message);
|
updateStatus(message);
|
||||||
break;
|
break;
|
||||||
case 'suggestion':
|
case "suggestion":
|
||||||
addSuggestion(message.text);
|
addSuggestion(message.text);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unknown message type:', message.type);
|
console.log("Unknown message type:", message.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append message to chat
|
// Append message to chat
|
||||||
function appendMessage(message) {
|
function appendMessage(message) {
|
||||||
const messagesEl = document.getElementById('messages');
|
const messagesEl = document.getElementById("messages");
|
||||||
if (!messagesEl) return;
|
if (!messagesEl) return;
|
||||||
|
|
||||||
const messageEl = document.createElement('div');
|
const messageEl = document.createElement("div");
|
||||||
messageEl.className = `message ${message.sender === 'user' ? 'user' : 'bot'}`;
|
messageEl.className = `message ${message.sender === "user" ? "user" : "bot"}`;
|
||||||
messageEl.innerHTML = `
|
messageEl.innerHTML = `
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<span class="sender">${message.sender}</span>
|
<span class="sender">${message.sender}</span>
|
||||||
|
|
@ -106,16 +126,16 @@
|
||||||
|
|
||||||
// Add suggestion chip
|
// Add suggestion chip
|
||||||
function addSuggestion(text) {
|
function addSuggestion(text) {
|
||||||
const suggestionsEl = document.getElementById('suggestions');
|
const suggestionsEl = document.getElementById("suggestions");
|
||||||
if (!suggestionsEl) return;
|
if (!suggestionsEl) return;
|
||||||
|
|
||||||
const chip = document.createElement('button');
|
const chip = document.createElement("button");
|
||||||
chip.className = 'suggestion-chip';
|
chip.className = "suggestion-chip";
|
||||||
chip.textContent = text;
|
chip.textContent = text;
|
||||||
chip.setAttribute('hx-post', '/api/sessions/current/message');
|
chip.setAttribute("hx-post", "/api/sessions/current/message");
|
||||||
chip.setAttribute('hx-vals', JSON.stringify({content: text}));
|
chip.setAttribute("hx-vals", JSON.stringify({ content: text }));
|
||||||
chip.setAttribute('hx-target', '#messages');
|
chip.setAttribute("hx-target", "#messages");
|
||||||
chip.setAttribute('hx-swap', 'beforeend');
|
chip.setAttribute("hx-swap", "beforeend");
|
||||||
|
|
||||||
suggestionsEl.appendChild(chip);
|
suggestionsEl.appendChild(chip);
|
||||||
htmx.process(chip);
|
htmx.process(chip);
|
||||||
|
|
@ -123,7 +143,7 @@
|
||||||
|
|
||||||
// Update connection status
|
// Update connection status
|
||||||
function updateConnectionStatus(status) {
|
function updateConnectionStatus(status) {
|
||||||
const statusEl = document.getElementById('connectionStatus');
|
const statusEl = document.getElementById("connectionStatus");
|
||||||
if (!statusEl) return;
|
if (!statusEl) return;
|
||||||
|
|
||||||
statusEl.className = `connection-status ${status}`;
|
statusEl.className = `connection-status ${status}`;
|
||||||
|
|
@ -132,7 +152,7 @@
|
||||||
|
|
||||||
// Update general status
|
// Update general status
|
||||||
function updateStatus(message) {
|
function updateStatus(message) {
|
||||||
const statusEl = document.getElementById('status-' + message.id);
|
const statusEl = document.getElementById("status-" + message.id);
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.textContent = message.text;
|
statusEl.textContent = message.text;
|
||||||
statusEl.className = `status ${message.severity}`;
|
statusEl.className = `status ${message.severity}`;
|
||||||
|
|
@ -140,16 +160,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show notification
|
// Show notification
|
||||||
function showNotification(text, type = 'info') {
|
function showNotification(text, type = "info") {
|
||||||
const notification = document.createElement('div');
|
const notification = document.createElement("div");
|
||||||
notification.className = `notification ${type}`;
|
notification.className = `notification ${type}`;
|
||||||
notification.textContent = text;
|
notification.textContent = text;
|
||||||
|
|
||||||
const container = document.getElementById('notifications') || document.body;
|
const container = document.getElementById("notifications") || document.body;
|
||||||
container.appendChild(notification);
|
container.appendChild(notification);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
notification.classList.add('fade-out');
|
notification.classList.add("fade-out");
|
||||||
setTimeout(() => notification.remove(), 300);
|
setTimeout(() => notification.remove(), 300);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
@ -157,86 +177,86 @@
|
||||||
// Attempt to reconnect WebSocket
|
// Attempt to reconnect WebSocket
|
||||||
function attemptReconnect() {
|
function attemptReconnect() {
|
||||||
if (reconnectAttempts >= config.maxReconnectAttempts) {
|
if (reconnectAttempts >= config.maxReconnectAttempts) {
|
||||||
showNotification('Connection lost. Please refresh the page.', 'error');
|
showNotification("Connection lost. Please refresh the page.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnectAttempts++;
|
reconnectAttempts++;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log(`Reconnection attempt ${reconnectAttempts}...`);
|
console.log(`Reconnection attempt ${reconnectAttempts}...`);
|
||||||
htmx.trigger(document.body, 'htmx:wsReconnect');
|
htmx.trigger(document.body, "htmx:wsReconnect");
|
||||||
}, config.reconnectDelay);
|
}, config.reconnectDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility: Escape HTML
|
// Utility: Escape HTML
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility: Format timestamp
|
// Utility: Format timestamp
|
||||||
function formatTime(timestamp) {
|
function formatTime(timestamp) {
|
||||||
if (!timestamp) return '';
|
if (!timestamp) return "";
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
return date.toLocaleTimeString('en-US', {
|
return date.toLocaleTimeString("en-US", {
|
||||||
hour: 'numeric',
|
hour: "numeric",
|
||||||
minute: '2-digit',
|
minute: "2-digit",
|
||||||
hour12: true
|
hour12: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle navigation
|
// Handle navigation
|
||||||
function initNavigation() {
|
function initNavigation() {
|
||||||
// Update active nav item on page change
|
// Update active nav item on page change
|
||||||
document.addEventListener('htmx:pushedIntoHistory', (event) => {
|
document.addEventListener("htmx:pushedIntoHistory", (event) => {
|
||||||
const path = event.detail.path;
|
const path = event.detail.path;
|
||||||
updateActiveNav(path);
|
updateActiveNav(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle browser back/forward
|
// Handle browser back/forward
|
||||||
window.addEventListener('popstate', (event) => {
|
window.addEventListener("popstate", (event) => {
|
||||||
updateActiveNav(window.location.pathname);
|
updateActiveNav(window.location.pathname);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update active navigation item
|
// Update active navigation item
|
||||||
function updateActiveNav(path) {
|
function updateActiveNav(path) {
|
||||||
document.querySelectorAll('.nav-item, .app-item').forEach(item => {
|
document.querySelectorAll(".nav-item, .app-item").forEach((item) => {
|
||||||
const href = item.getAttribute('href');
|
const href = item.getAttribute("href");
|
||||||
if (href === path || (path === '/' && href === '/chat')) {
|
if (href === path || (path === "/" && href === "/chat")) {
|
||||||
item.classList.add('active');
|
item.classList.add("active");
|
||||||
} else {
|
} else {
|
||||||
item.classList.remove('active');
|
item.classList.remove("active");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize keyboard shortcuts
|
// Initialize keyboard shortcuts
|
||||||
function initKeyboardShortcuts() {
|
function initKeyboardShortcuts() {
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener("keydown", (e) => {
|
||||||
// Send message on Enter (when in input)
|
// Send message on Enter (when in input)
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
const input = document.getElementById('messageInput');
|
const input = document.getElementById("messageInput");
|
||||||
if (input && document.activeElement === input) {
|
if (input && document.activeElement === input) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const form = input.closest('form');
|
const form = input.closest("form");
|
||||||
if (form) {
|
if (form) {
|
||||||
htmx.trigger(form, 'submit');
|
htmx.trigger(form, "submit");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus input on /
|
// Focus input on /
|
||||||
if (e.key === '/' && document.activeElement.tagName !== 'INPUT') {
|
if (e.key === "/" && document.activeElement.tagName !== "INPUT") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const input = document.getElementById('messageInput');
|
const input = document.getElementById("messageInput");
|
||||||
if (input) input.focus();
|
if (input) input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape to blur input
|
// Escape to blur input
|
||||||
if (e.key === 'Escape') {
|
if (e.key === "Escape") {
|
||||||
const input = document.getElementById('messageInput');
|
const input = document.getElementById("messageInput");
|
||||||
if (input && document.activeElement === input) {
|
if (input && document.activeElement === input) {
|
||||||
input.blur();
|
input.blur();
|
||||||
}
|
}
|
||||||
|
|
@ -246,21 +266,23 @@
|
||||||
|
|
||||||
// Initialize scroll behavior
|
// Initialize scroll behavior
|
||||||
function initScrollBehavior() {
|
function initScrollBehavior() {
|
||||||
const scrollBtn = document.getElementById('scrollToBottom');
|
const scrollBtn = document.getElementById("scrollToBottom");
|
||||||
const messages = document.getElementById('messages');
|
const messages = document.getElementById("messages");
|
||||||
|
|
||||||
if (scrollBtn && messages) {
|
if (scrollBtn && messages) {
|
||||||
// Show/hide scroll button
|
// Show/hide scroll button
|
||||||
messages.addEventListener('scroll', () => {
|
messages.addEventListener("scroll", () => {
|
||||||
const isAtBottom = messages.scrollHeight - messages.scrollTop <= messages.clientHeight + 100;
|
const isAtBottom =
|
||||||
scrollBtn.style.display = isAtBottom ? 'none' : 'flex';
|
messages.scrollHeight - messages.scrollTop <=
|
||||||
|
messages.clientHeight + 100;
|
||||||
|
scrollBtn.style.display = isAtBottom ? "none" : "flex";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scroll to bottom on click
|
// Scroll to bottom on click
|
||||||
scrollBtn.addEventListener('click', () => {
|
scrollBtn.addEventListener("click", () => {
|
||||||
messages.scrollTo({
|
messages.scrollTo({
|
||||||
top: messages.scrollHeight,
|
top: messages.scrollHeight,
|
||||||
behavior: 'smooth'
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -275,7 +297,7 @@
|
||||||
|
|
||||||
// Main initialization
|
// Main initialization
|
||||||
function init() {
|
function init() {
|
||||||
console.log('Initializing HTMX application...');
|
console.log("Initializing HTMX application...");
|
||||||
|
|
||||||
// Initialize HTMX
|
// Initialize HTMX
|
||||||
initHTMX();
|
initHTMX();
|
||||||
|
|
@ -295,12 +317,12 @@
|
||||||
// Set initial active nav
|
// Set initial active nav
|
||||||
updateActiveNav(window.location.pathname);
|
updateActiveNav(window.location.pathname);
|
||||||
|
|
||||||
console.log('HTMX application initialized');
|
console.log("HTMX application initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for DOM and HTMX to be ready
|
// Wait for DOM and HTMX to be ready
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
} else {
|
} else {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
@ -310,6 +332,6 @@
|
||||||
showNotification,
|
showNotification,
|
||||||
appendMessage,
|
appendMessage,
|
||||||
updateConnectionStatus,
|
updateConnectionStatus,
|
||||||
config
|
config,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<!-- Folder List -->
|
<!-- Folder List -->
|
||||||
<div class="folders-section">
|
<div class="folders-section">
|
||||||
<div class="nav-item active" data-folder="inbox" hx-get="/api/email/list?folder=inbox" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item active" data-folder="inbox" hx-get="/ui/email/list?folder=inbox" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/>
|
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/>
|
||||||
<path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>
|
<path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>
|
||||||
|
|
@ -20,20 +20,20 @@
|
||||||
<span>Inbox</span>
|
<span>Inbox</span>
|
||||||
<span class="folder-badge unread" id="inbox-count">0</span>
|
<span class="folder-badge unread" id="inbox-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-folder="starred" hx-get="/api/email/list?folder=starred" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item" data-folder="starred" hx-get="/ui/email/list?folder=starred" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Starred</span>
|
<span>Starred</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-folder="sent" hx-get="/api/email/list?folder=sent" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item" data-folder="sent" hx-get="/ui/email/list?folder=sent" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<line x1="22" y1="2" x2="11" y2="13"/>
|
<line x1="22" y1="2" x2="11" y2="13"/>
|
||||||
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Sent</span>
|
<span>Sent</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-folder="scheduled" hx-get="/api/email/list?folder=scheduled" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item" data-folder="scheduled" hx-get="/ui/email/list?folder=scheduled" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="10"/>
|
<circle cx="12" cy="12" r="10"/>
|
||||||
<polyline points="12 6 12 12 16 14"/>
|
<polyline points="12 6 12 12 16 14"/>
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
<span>Scheduled</span>
|
<span>Scheduled</span>
|
||||||
<span class="folder-badge" id="scheduled-count">0</span>
|
<span class="folder-badge" id="scheduled-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-folder="drafts" hx-get="/api/email/list?folder=drafts" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item" data-folder="drafts" hx-get="/ui/email/list?folder=drafts" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||||
<polyline points="14 2 14 8 20 8"/>
|
<polyline points="14 2 14 8 20 8"/>
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
<span>Tracking</span>
|
<span>Tracking</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-folder="spam" hx-get="/api/email/list?folder=spam" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item" data-folder="spam" hx-get="/ui/email/list?folder=spam" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
||||||
<line x1="12" y1="9" x2="12" y2="13"/>
|
<line x1="12" y1="9" x2="12" y2="13"/>
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
<span>Spam</span>
|
<span>Spam</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-folder="trash" hx-get="/api/email/list?folder=trash" hx-target="#mail-list" hx-swap="innerHTML">
|
<div class="nav-item" data-folder="trash" hx-get="/ui/email/list?folder=trash" hx-target="#mail-list" hx-swap="innerHTML">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="3 6 5 6 21 6"/>
|
<polyline points="3 6 5 6 21 6"/>
|
||||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="accounts-list"
|
<div id="accounts-list"
|
||||||
hx-get="/api/email/accounts"
|
hx-get="/ui/email/accounts"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<!-- Accounts loaded here -->
|
<!-- Accounts loaded here -->
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="labels-list" hx-get="/api/email/labels" hx-trigger="load" hx-swap="innerHTML">
|
<div id="labels-list" hx-get="/ui/email/labels" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<div class="label-item" style="--label-color: #ef4444;">
|
<div class="label-item" style="--label-color: #ef4444;">
|
||||||
<span class="label-dot"></span>
|
<span class="label-dot"></span>
|
||||||
<span>Important</span>
|
<span>Important</span>
|
||||||
|
|
@ -169,7 +169,7 @@
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" placeholder="Search emails..." id="email-search"
|
<input type="text" placeholder="Search emails..." id="email-search"
|
||||||
hx-get="/api/email/search" hx-trigger="keyup changed delay:300ms"
|
hx-get="/ui/email/search" hx-trigger="keyup changed delay:300ms"
|
||||||
hx-target="#mail-list" hx-include="this" name="q"/>
|
hx-target="#mail-list" hx-include="this" name="q"/>
|
||||||
</div>
|
</div>
|
||||||
<button class="icon-btn" onclick="refreshMailList()" title="Refresh">
|
<button class="icon-btn" onclick="refreshMailList()" title="Refresh">
|
||||||
|
|
@ -212,7 +212,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="mail-list" hx-get="/api/email/list?folder=inbox" hx-trigger="load" hx-swap="innerHTML">
|
<div id="mail-list" hx-get="/ui/email/list?folder=inbox" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<div class="loading-state">
|
<div class="loading-state">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Loading emails...</p>
|
<p>Loading emails...</p>
|
||||||
|
|
@ -421,7 +421,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="templates-list" id="templates-list" hx-get="/api/email/templates" hx-trigger="load" hx-swap="innerHTML">
|
<div class="templates-list" id="templates-list" hx-get="/ui/email/templates" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<div class="loading-state"><div class="spinner"></div></div>
|
<div class="loading-state"><div class="spinner"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
@ -443,7 +443,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="signatures-list" id="signatures-list" hx-get="/api/email/signatures" hx-trigger="load" hx-swap="innerHTML">
|
<div class="signatures-list" id="signatures-list" hx-get="/ui/email/signatures" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<div class="loading-state"><div class="spinner"></div></div>
|
<div class="loading-state"><div class="spinner"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
@ -465,7 +465,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="rules-list" id="rules-list" hx-get="/api/email/rules" hx-trigger="load" hx-swap="innerHTML">
|
<div class="rules-list" id="rules-list" hx-get="/ui/email/rules" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<div class="loading-state"><div class="spinner"></div></div>
|
<div class="loading-state"><div class="spinner"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
@ -487,7 +487,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="autoresponder-form" hx-post="/api/email/auto-responder" hx-swap="none">
|
<form id="autoresponder-form" hx-post="/ui/email/auto-responder" hx-swap="none">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="toggle-label">
|
<label class="toggle-label">
|
||||||
<input type="checkbox" name="enabled" id="autoresponder-enabled"/>
|
<input type="checkbox" name="enabled" id="autoresponder-enabled"/>
|
||||||
|
|
@ -1013,19 +1013,28 @@
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modals */
|
/* Modals - dialog elements are hidden by default */
|
||||||
.modal {
|
dialog.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 1rem;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 100vw;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.modal[open] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1000;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal::backdrop {
|
dialog.modal::backdrop {
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,36 @@
|
||||||
/* Logs page JavaScript */
|
/* Logs page JavaScript */
|
||||||
|
|
||||||
// Logs State
|
// Logs State - guard against duplicate declarations on HTMX reload
|
||||||
let isStreaming = true;
|
if (typeof window.logsModuleInitialized === "undefined") {
|
||||||
let autoScroll = true;
|
window.logsModuleInitialized = true;
|
||||||
let logCounts = { debug: 0, info: 0, warn: 0, error: 0, fatal: 0 };
|
var isStreaming = true;
|
||||||
let searchDebounceTimer = null;
|
var autoScroll = true;
|
||||||
let currentFilters = {
|
var logCounts = { debug: 0, info: 0, warn: 0, error: 0, fatal: 0 };
|
||||||
level: 'all',
|
var searchDebounceTimer = null;
|
||||||
service: 'all',
|
var currentFilters = {
|
||||||
search: ''
|
level: "all",
|
||||||
|
service: "all",
|
||||||
|
search: "",
|
||||||
};
|
};
|
||||||
let logsWs = null;
|
var logsWs = null;
|
||||||
|
}
|
||||||
|
|
||||||
function initLogsWebSocket() {
|
function initLogsWebSocket() {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
logsWs = new WebSocket(`${protocol}//${window.location.host}/ws/logs`);
|
logsWs = new WebSocket(`${protocol}//${window.location.host}/ws/logs`);
|
||||||
|
|
||||||
logsWs.onopen = function () {
|
logsWs.onopen = function () {
|
||||||
updateLogsConnectionStatus('connected', 'Connected');
|
updateLogsConnectionStatus("connected", "Connected");
|
||||||
};
|
};
|
||||||
|
|
||||||
logsWs.onclose = function () {
|
logsWs.onclose = function () {
|
||||||
updateLogsConnectionStatus('disconnected', 'Disconnected');
|
updateLogsConnectionStatus("disconnected", "Disconnected");
|
||||||
// Reconnect after 3 seconds
|
// Reconnect after 3 seconds
|
||||||
setTimeout(initLogsWebSocket, 3000);
|
setTimeout(initLogsWebSocket, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
logsWs.onerror = function () {
|
logsWs.onerror = function () {
|
||||||
updateLogsConnectionStatus('disconnected', 'Error');
|
updateLogsConnectionStatus("disconnected", "Error");
|
||||||
};
|
};
|
||||||
|
|
||||||
logsWs.onmessage = function (event) {
|
logsWs.onmessage = function (event) {
|
||||||
|
|
@ -37,39 +40,39 @@ function initLogsWebSocket() {
|
||||||
const logData = JSON.parse(event.data);
|
const logData = JSON.parse(event.data);
|
||||||
appendLog(logData);
|
appendLog(logData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse log message:', e);
|
console.error("Failed to parse log message:", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLogsConnectionStatus(status, text) {
|
function updateLogsConnectionStatus(status, text) {
|
||||||
const statusEl = document.getElementById('connection-status');
|
const statusEl = document.getElementById("connection-status");
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.className = `connection-status ${status}`;
|
statusEl.className = `connection-status ${status}`;
|
||||||
statusEl.querySelector('.status-text').textContent = text;
|
statusEl.querySelector(".status-text").textContent = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendLog(log) {
|
function appendLog(log) {
|
||||||
const stream = document.getElementById('log-stream');
|
const stream = document.getElementById("log-stream");
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
|
|
||||||
const placeholder = stream.querySelector('.log-placeholder');
|
const placeholder = stream.querySelector(".log-placeholder");
|
||||||
if (placeholder) {
|
if (placeholder) {
|
||||||
placeholder.remove();
|
placeholder.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = document.createElement('div');
|
const entry = document.createElement("div");
|
||||||
entry.className = 'log-entry';
|
entry.className = "log-entry";
|
||||||
entry.dataset.level = log.level || 'info';
|
entry.dataset.level = log.level || "info";
|
||||||
entry.dataset.service = log.service || 'unknown';
|
entry.dataset.service = log.service || "unknown";
|
||||||
entry.dataset.id = log.id || Date.now();
|
entry.dataset.id = log.id || Date.now();
|
||||||
|
|
||||||
entry.innerHTML = `
|
entry.innerHTML = `
|
||||||
<span class="log-timestamp">${formatLogTimestamp(log.timestamp)}</span>
|
<span class="log-timestamp">${formatLogTimestamp(log.timestamp)}</span>
|
||||||
<span class="log-level">${(log.level || 'INFO').toUpperCase()}</span>
|
<span class="log-level">${(log.level || "INFO").toUpperCase()}</span>
|
||||||
<span class="log-service">${log.service || 'unknown'}</span>
|
<span class="log-service">${log.service || "unknown"}</span>
|
||||||
<span class="log-message">${escapeLogHtml(log.message || '')}</span>
|
<span class="log-message">${escapeLogHtml(log.message || "")}</span>
|
||||||
<button class="log-expand" onclick="expandLog(this)" title="View details">
|
<button class="log-expand" onclick="expandLog(this)" title="View details">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="9 18 15 12 9 6"></polyline>
|
<polyline points="9 18 15 12 9 6"></polyline>
|
||||||
|
|
@ -82,19 +85,19 @@ function appendLog(log) {
|
||||||
|
|
||||||
// Apply current filters
|
// Apply current filters
|
||||||
if (!matchesLogFilters(entry)) {
|
if (!matchesLogFilters(entry)) {
|
||||||
entry.classList.add('hidden');
|
entry.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.appendChild(entry);
|
stream.appendChild(entry);
|
||||||
|
|
||||||
// Update counts
|
// Update counts
|
||||||
const level = log.level || 'info';
|
const level = log.level || "info";
|
||||||
if (logCounts[level] !== undefined) {
|
if (logCounts[level] !== undefined) {
|
||||||
logCounts[level]++;
|
logCounts[level]++;
|
||||||
const countEl = document.getElementById(`${level}-count`);
|
const countEl = document.getElementById(`${level}-count`);
|
||||||
if (countEl) countEl.textContent = logCounts[level];
|
if (countEl) countEl.textContent = logCounts[level];
|
||||||
}
|
}
|
||||||
const totalEl = document.getElementById('total-count');
|
const totalEl = document.getElementById("total-count");
|
||||||
if (totalEl) {
|
if (totalEl) {
|
||||||
totalEl.textContent = Object.values(logCounts).reduce((a, b) => a + b, 0);
|
totalEl.textContent = Object.values(logCounts).reduce((a, b) => a + b, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +112,7 @@ function appendLog(log) {
|
||||||
while (stream.children.length > maxEntries) {
|
while (stream.children.length > maxEntries) {
|
||||||
const removed = stream.firstChild;
|
const removed = stream.firstChild;
|
||||||
if (removed._logData) {
|
if (removed._logData) {
|
||||||
const removedLevel = removed._logData.level || 'info';
|
const removedLevel = removed._logData.level || "info";
|
||||||
if (logCounts[removedLevel] > 0) {
|
if (logCounts[removedLevel] > 0) {
|
||||||
logCounts[removedLevel]--;
|
logCounts[removedLevel]--;
|
||||||
}
|
}
|
||||||
|
|
@ -119,25 +122,31 @@ function appendLog(log) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatLogTimestamp(timestamp) {
|
function formatLogTimestamp(timestamp) {
|
||||||
if (!timestamp) return '--';
|
if (!timestamp) return "--";
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
return date.toISOString().replace('T', ' ').slice(0, 23);
|
return date.toISOString().replace("T", " ").slice(0, 23);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeLogHtml(text) {
|
function escapeLogHtml(text) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchesLogFilters(entry) {
|
function matchesLogFilters(entry) {
|
||||||
// Level filter
|
// Level filter
|
||||||
if (currentFilters.level !== 'all' && entry.dataset.level !== currentFilters.level) {
|
if (
|
||||||
|
currentFilters.level !== "all" &&
|
||||||
|
entry.dataset.level !== currentFilters.level
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service filter
|
// Service filter
|
||||||
if (currentFilters.service !== 'all' && entry.dataset.service !== currentFilters.service) {
|
if (
|
||||||
|
currentFilters.service !== "all" &&
|
||||||
|
entry.dataset.service !== currentFilters.service
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,15 +162,17 @@ function matchesLogFilters(entry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLogFilters() {
|
function applyLogFilters() {
|
||||||
currentFilters.level = document.getElementById('log-level-filter')?.value || 'all';
|
currentFilters.level =
|
||||||
currentFilters.service = document.getElementById('service-filter')?.value || 'all';
|
document.getElementById("log-level-filter")?.value || "all";
|
||||||
|
currentFilters.service =
|
||||||
|
document.getElementById("service-filter")?.value || "all";
|
||||||
|
|
||||||
const entries = document.querySelectorAll('.log-entry');
|
const entries = document.querySelectorAll(".log-entry");
|
||||||
entries.forEach(entry => {
|
entries.forEach((entry) => {
|
||||||
if (matchesLogFilters(entry)) {
|
if (matchesLogFilters(entry)) {
|
||||||
entry.classList.remove('hidden');
|
entry.classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
entry.classList.add('hidden');
|
entry.classList.add("hidden");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -176,11 +187,11 @@ function debounceLogSearch(value) {
|
||||||
|
|
||||||
function toggleStream() {
|
function toggleStream() {
|
||||||
isStreaming = !isStreaming;
|
isStreaming = !isStreaming;
|
||||||
const btn = document.getElementById('stream-toggle');
|
const btn = document.getElementById("stream-toggle");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
btn.classList.remove('paused');
|
btn.classList.remove("paused");
|
||||||
btn.innerHTML = `
|
btn.innerHTML = `
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<rect x="6" y="4" width="4" height="16"></rect>
|
<rect x="6" y="4" width="4" height="16"></rect>
|
||||||
|
|
@ -189,7 +200,7 @@ function toggleStream() {
|
||||||
<span>Pause</span>
|
<span>Pause</span>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
btn.classList.add('paused');
|
btn.classList.add("paused");
|
||||||
btn.innerHTML = `
|
btn.innerHTML = `
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||||
|
|
@ -200,8 +211,8 @@ function toggleStream() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLogs() {
|
function clearLogs() {
|
||||||
if (confirm('Are you sure you want to clear all logs?')) {
|
if (confirm("Are you sure you want to clear all logs?")) {
|
||||||
const stream = document.getElementById('log-stream');
|
const stream = document.getElementById("log-stream");
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
|
|
||||||
stream.innerHTML = `
|
stream.innerHTML = `
|
||||||
|
|
@ -220,30 +231,32 @@ function clearLogs() {
|
||||||
|
|
||||||
// Reset counts
|
// Reset counts
|
||||||
logCounts = { debug: 0, info: 0, warn: 0, error: 0, fatal: 0 };
|
logCounts = { debug: 0, info: 0, warn: 0, error: 0, fatal: 0 };
|
||||||
Object.keys(logCounts).forEach(level => {
|
Object.keys(logCounts).forEach((level) => {
|
||||||
const el = document.getElementById(`${level}-count`);
|
const el = document.getElementById(`${level}-count`);
|
||||||
if (el) el.textContent = '0';
|
if (el) el.textContent = "0";
|
||||||
});
|
});
|
||||||
const totalEl = document.getElementById('total-count');
|
const totalEl = document.getElementById("total-count");
|
||||||
if (totalEl) totalEl.textContent = '0';
|
if (totalEl) totalEl.textContent = "0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadLogs() {
|
function downloadLogs() {
|
||||||
const entries = document.querySelectorAll('.log-entry');
|
const entries = document.querySelectorAll(".log-entry");
|
||||||
let logs = [];
|
let logs = [];
|
||||||
|
|
||||||
entries.forEach(entry => {
|
entries.forEach((entry) => {
|
||||||
if (entry._logData) {
|
if (entry._logData) {
|
||||||
logs.push(entry._logData);
|
logs.push(entry._logData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(logs, null, 2)], {
|
||||||
|
type: "application/json",
|
||||||
|
});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = `logs-${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.json`;
|
a.download = `logs-${new Date().toISOString().slice(0, 19).replace(/[T:]/g, "-")}.json`;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
|
|
@ -251,7 +264,7 @@ function downloadLogs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToTop() {
|
function scrollToTop() {
|
||||||
const stream = document.getElementById('log-stream');
|
const stream = document.getElementById("log-stream");
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.scrollTop = 0;
|
stream.scrollTop = 0;
|
||||||
autoScroll = false;
|
autoScroll = false;
|
||||||
|
|
@ -260,7 +273,7 @@ function scrollToTop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
const stream = document.getElementById('log-stream');
|
const stream = document.getElementById("log-stream");
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.scrollTop = stream.scrollHeight;
|
stream.scrollTop = stream.scrollHeight;
|
||||||
autoScroll = true;
|
autoScroll = true;
|
||||||
|
|
@ -269,68 +282,76 @@ function scrollToBottom() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLogScrollButtons() {
|
function updateLogScrollButtons() {
|
||||||
const topBtn = document.getElementById('scroll-top-btn');
|
const topBtn = document.getElementById("scroll-top-btn");
|
||||||
const bottomBtn = document.getElementById('scroll-bottom-btn');
|
const bottomBtn = document.getElementById("scroll-bottom-btn");
|
||||||
if (topBtn) topBtn.classList.toggle('active', !autoScroll);
|
if (topBtn) topBtn.classList.toggle("active", !autoScroll);
|
||||||
if (bottomBtn) bottomBtn.classList.toggle('active', autoScroll);
|
if (bottomBtn) bottomBtn.classList.toggle("active", autoScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandLog(btn) {
|
function expandLog(btn) {
|
||||||
const entry = btn.closest('.log-entry');
|
const entry = btn.closest(".log-entry");
|
||||||
const logData = entry._logData || {
|
const logData = entry._logData || {
|
||||||
timestamp: entry.querySelector('.log-timestamp').textContent,
|
timestamp: entry.querySelector(".log-timestamp").textContent,
|
||||||
level: entry.dataset.level,
|
level: entry.dataset.level,
|
||||||
service: entry.dataset.service,
|
service: entry.dataset.service,
|
||||||
message: entry.querySelector('.log-message').textContent
|
message: entry.querySelector(".log-message").textContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
const panel = document.getElementById('log-detail-panel');
|
const panel = document.getElementById("log-detail-panel");
|
||||||
const content = document.getElementById('log-detail-content');
|
const content = document.getElementById("log-detail-content");
|
||||||
if (!panel || !content) return;
|
if (!panel || !content) return;
|
||||||
|
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<div class="detail-label">Timestamp</div>
|
<div class="detail-label">Timestamp</div>
|
||||||
<div class="detail-value">${logData.timestamp || '--'}</div>
|
<div class="detail-value">${logData.timestamp || "--"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<div class="detail-label">Level</div>
|
<div class="detail-label">Level</div>
|
||||||
<div class="detail-value">${(logData.level || 'info').toUpperCase()}</div>
|
<div class="detail-value">${(logData.level || "info").toUpperCase()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<div class="detail-label">Service</div>
|
<div class="detail-label">Service</div>
|
||||||
<div class="detail-value">${logData.service || 'unknown'}</div>
|
<div class="detail-value">${logData.service || "unknown"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<div class="detail-label">Message</div>
|
<div class="detail-label">Message</div>
|
||||||
<div class="detail-value">${escapeLogHtml(logData.message || '')}</div>
|
<div class="detail-value">${escapeLogHtml(logData.message || "")}</div>
|
||||||
</div>
|
</div>
|
||||||
${logData.stack ? `
|
${
|
||||||
|
logData.stack
|
||||||
|
? `
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<div class="detail-label">Stack Trace</div>
|
<div class="detail-label">Stack Trace</div>
|
||||||
<div class="detail-value">${escapeLogHtml(logData.stack)}</div>
|
<div class="detail-value">${escapeLogHtml(logData.stack)}</div>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
`
|
||||||
${logData.context ? `
|
: ""
|
||||||
|
}
|
||||||
|
${
|
||||||
|
logData.context
|
||||||
|
? `
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<div class="detail-label">Context</div>
|
<div class="detail-label">Context</div>
|
||||||
<div class="detail-value">${escapeLogHtml(JSON.stringify(logData.context, null, 2))}</div>
|
<div class="detail-value">${escapeLogHtml(JSON.stringify(logData.context, null, 2))}</div>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
panel.classList.add('open');
|
panel.classList.add("open");
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeLogDetail() {
|
function closeLogDetail() {
|
||||||
const panel = document.getElementById('log-detail-panel');
|
const panel = document.getElementById("log-detail-panel");
|
||||||
if (panel) panel.classList.remove('open');
|
if (panel) panel.classList.remove("open");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize on page load
|
// Initialize on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Initialize WebSocket connection if on logs page
|
// Initialize WebSocket connection if on logs page
|
||||||
if (document.getElementById('log-stream')) {
|
if (document.getElementById("log-stream")) {
|
||||||
initLogsWebSocket();
|
initLogsWebSocket();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,75 @@
|
||||||
/* Monitoring module - shared/base JavaScript */
|
/* Monitoring module - shared/base JavaScript */
|
||||||
|
|
||||||
function setActiveNav(element) {
|
function setActiveNav(element) {
|
||||||
document.querySelectorAll('.monitoring-nav .nav-item').forEach(item => {
|
document.querySelectorAll(".monitoring-nav .nav-item").forEach((item) => {
|
||||||
item.classList.remove('active');
|
item.classList.remove("active");
|
||||||
});
|
});
|
||||||
element.classList.add('active');
|
element.classList.add("active");
|
||||||
|
|
||||||
// Update page title
|
// Update page title
|
||||||
const title = element.querySelector('span:not(.alert-badge):not(.health-indicator)').textContent;
|
const title = element.querySelector(
|
||||||
document.getElementById('page-title').textContent = title;
|
"span:not(.alert-badge):not(.health-indicator)",
|
||||||
|
).textContent;
|
||||||
|
document.getElementById("page-title").textContent = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTimeRange(range) {
|
function updateTimeRange(range) {
|
||||||
// Store selected time range
|
// Store selected time range
|
||||||
localStorage.setItem('monitoring-time-range', range);
|
localStorage.setItem("monitoring-time-range", range);
|
||||||
|
|
||||||
// Trigger refresh of current view
|
// Trigger refresh of current view
|
||||||
htmx.trigger('#monitoring-content', 'refresh');
|
htmx.trigger("#monitoring-content", "refresh");
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshMonitoring() {
|
function refreshMonitoring() {
|
||||||
htmx.trigger('#monitoring-content', 'refresh');
|
htmx.trigger("#monitoring-content", "refresh");
|
||||||
|
|
||||||
// Visual feedback
|
// Visual feedback
|
||||||
const btn = event.currentTarget;
|
const btn = event.currentTarget;
|
||||||
btn.classList.add('active');
|
btn.classList.add("active");
|
||||||
setTimeout(() => btn.classList.remove('active'), 500);
|
setTimeout(() => btn.classList.remove("active"), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard against duplicate declarations on HTMX reload
|
||||||
|
if (typeof window.monitoringModuleInitialized === "undefined") {
|
||||||
|
window.monitoringModuleInitialized = true;
|
||||||
|
var autoRefresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let autoRefresh = true;
|
|
||||||
function toggleAutoRefresh() {
|
function toggleAutoRefresh() {
|
||||||
autoRefresh = !autoRefresh;
|
autoRefresh = !autoRefresh;
|
||||||
const btn = document.getElementById('auto-refresh-btn');
|
const btn = document.getElementById("auto-refresh-btn");
|
||||||
btn.classList.toggle('active', autoRefresh);
|
btn.classList.toggle("active", autoRefresh);
|
||||||
|
|
||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
// Re-enable polling by refreshing the page content
|
// Re-enable polling by refreshing the page content
|
||||||
htmx.trigger('#monitoring-content', 'refresh');
|
htmx.trigger("#monitoring-content", "refresh");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportData() {
|
function exportData() {
|
||||||
const timeRange = document.getElementById('time-range').value;
|
const timeRange = document.getElementById("time-range").value;
|
||||||
window.open(`/api/monitoring/export?range=${timeRange}`, '_blank');
|
window.open(`/api/monitoring/export?range=${timeRange}`, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Restore time range preference
|
// Restore time range preference
|
||||||
const savedRange = localStorage.getItem('monitoring-time-range');
|
const savedRange = localStorage.getItem("monitoring-time-range");
|
||||||
if (savedRange) {
|
if (savedRange) {
|
||||||
const timeRangeEl = document.getElementById('time-range');
|
const timeRangeEl = document.getElementById("time-range");
|
||||||
if (timeRangeEl) timeRangeEl.value = savedRange;
|
if (timeRangeEl) timeRangeEl.value = savedRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set auto-refresh button state
|
// Set auto-refresh button state
|
||||||
const autoRefreshBtn = document.getElementById('auto-refresh-btn');
|
const autoRefreshBtn = document.getElementById("auto-refresh-btn");
|
||||||
if (autoRefreshBtn) autoRefreshBtn.classList.toggle('active', autoRefresh);
|
if (autoRefreshBtn) autoRefreshBtn.classList.toggle("active", autoRefresh);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle HTMX events for loading states
|
// Handle HTMX events for loading states
|
||||||
document.body.addEventListener('htmx:beforeRequest', function(evt) {
|
document.body.addEventListener("htmx:beforeRequest", function (evt) {
|
||||||
if (evt.target.id === 'monitoring-content') {
|
if (evt.target.id === "monitoring-content") {
|
||||||
evt.target.innerHTML = '<div class="loading-state"><div class="spinner"></div><p>Loading...</p></div>';
|
evt.target.innerHTML =
|
||||||
|
'<div class="loading-state"><div class="spinner"></div><p>Loading...</p></div>';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
/* Research Container */
|
/* Research Container */
|
||||||
.research-container {
|
.research-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 60px);
|
height: 100%;
|
||||||
background: var(--background);
|
min-height: 0;
|
||||||
color: var(--foreground);
|
background: var(--background, var(--bg-primary, #0a0a0f));
|
||||||
|
color: var(--foreground, var(--text-primary, #ffffff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
|
|
@ -398,12 +399,22 @@
|
||||||
animation: bounce 1.4s infinite ease-in-out both;
|
animation: bounce 1.4s infinite ease-in-out both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator-dots span:nth-child(1) { animation-delay: -0.32s; }
|
.indicator-dots span:nth-child(1) {
|
||||||
.indicator-dots span:nth-child(2) { animation-delay: -0.16s; }
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
.indicator-dots span:nth-child(2) {
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes bounce {
|
@keyframes bounce {
|
||||||
0%, 80%, 100% { transform: scale(0); }
|
0%,
|
||||||
40% { transform: scale(1); }
|
80%,
|
||||||
|
100% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator-text {
|
.indicator-text {
|
||||||
|
|
@ -557,7 +568,7 @@
|
||||||
background: var(--muted);
|
background: var(--muted);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
379
ui/suite/tasks/intents.html
Normal file
379
ui/suite/tasks/intents.html
Normal file
|
|
@ -0,0 +1,379 @@
|
||||||
|
<!-- Intents Dashboard - Sentient Theme -->
|
||||||
|
<!-- AI-powered intent tracking and decision management -->
|
||||||
|
|
||||||
|
<div class="intents-dashboard">
|
||||||
|
<!-- Dashboard Header -->
|
||||||
|
<div class="dashboard-header">
|
||||||
|
<div class="dashboard-title">
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<span class="dashboard-subtitle">Analytics</span>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-actions">
|
||||||
|
<div class="search-bar">
|
||||||
|
<svg
|
||||||
|
class="search-bar-icon"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8" />
|
||||||
|
<path d="m21 21-4.35-4.35" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="search-bar-input"
|
||||||
|
placeholder="Search Intents"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="profile-selector">
|
||||||
|
<div class="profile-avatar">A</div>
|
||||||
|
<span class="profile-name">Profile 1</span>
|
||||||
|
<svg
|
||||||
|
class="profile-dropdown-icon"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="notification-indicator">
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||||
|
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
|
||||||
|
</svg>
|
||||||
|
<span class="notification-count">3</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn-new-intent">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19" />
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" />
|
||||||
|
</svg>
|
||||||
|
New Intent
|
||||||
|
</button>
|
||||||
|
<button class="icon-button" title="Settings">
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
<path
|
||||||
|
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Filters -->
|
||||||
|
<div class="status-filters">
|
||||||
|
<button class="status-filter active">
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
|
Complete
|
||||||
|
<span class="count">8</span>
|
||||||
|
</button>
|
||||||
|
<button class="status-filter">
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polygon points="10 8 16 12 10 16 10 8" />
|
||||||
|
</svg>
|
||||||
|
Active Intents
|
||||||
|
<span class="count">12</span>
|
||||||
|
</button>
|
||||||
|
<button class="status-filter">
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||||
|
<line x1="9" y1="9" x2="15" y2="15" />
|
||||||
|
<line x1="15" y1="9" x2="9" y2="15" />
|
||||||
|
</svg>
|
||||||
|
Awaiting Decision
|
||||||
|
<span class="count">5</span>
|
||||||
|
</button>
|
||||||
|
<button class="status-filter">
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="6" y="4" width="4" height="16" />
|
||||||
|
<rect x="14" y="4" width="4" height="16" />
|
||||||
|
</svg>
|
||||||
|
Paused
|
||||||
|
<span class="count">2</span>
|
||||||
|
</button>
|
||||||
|
<button class="status-filter">
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||||
|
/>
|
||||||
|
<line x1="12" y1="9" x2="12" y2="13" />
|
||||||
|
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||||
|
</svg>
|
||||||
|
Blocked/Issues
|
||||||
|
<span class="count">1</span>
|
||||||
|
</button>
|
||||||
|
<div class="time-saved-indicator">
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
|
Active Time Saved: 23.5 hrs this week
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content Area -->
|
||||||
|
<div class="dashboard-layout">
|
||||||
|
<!-- Left Column: Intent Cards -->
|
||||||
|
<div class="dashboard-main">
|
||||||
|
<div class="intents-grid">
|
||||||
|
<!-- Intent Card 1: Active -->
|
||||||
|
<article class="intent-card">
|
||||||
|
<div class="intent-card-header">
|
||||||
|
<h3 class="intent-title">
|
||||||
|
Launch Q4 Campaign for Verse
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="intent-progress">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div
|
||||||
|
class="progress-bar-fill"
|
||||||
|
style="width: 75%"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<span class="progress-text">5/7 Steps</span>
|
||||||
|
<span class="progress-text">75%</span>
|
||||||
|
</div>
|
||||||
|
<div class="intent-status-line">
|
||||||
|
<span class="intent-status-label">STATUS</span>
|
||||||
|
<span class="intent-status-badge active">
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="6" />
|
||||||
|
</svg>
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="intent-description">
|
||||||
|
<strong>Budget Allocation: 50k</strong><br />
|
||||||
|
<span class="pending"
|
||||||
|
>Distributing budget in media channels</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="intent-meta-tag"
|
||||||
|
style="margin-left: 8px; display: inline-flex"
|
||||||
|
>Est: 15min ⏱</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="intent-status-line" style="margin-top: 16px">
|
||||||
|
<span class="intent-status-label">INTEGRITY</span>
|
||||||
|
</div>
|
||||||
|
<div class="intent-meta">
|
||||||
|
<span class="intent-meta-tag">Started 2h ago</span>
|
||||||
|
<span class="intent-meta-tag">Due Nov 15th</span>
|
||||||
|
<span class="intent-health good"
|
||||||
|
>Intent Health 90%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="intent-detailed-link">Detailed View</a>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Intent Card 2: Awaiting Decision -->
|
||||||
|
<article class="intent-card selected">
|
||||||
|
<div class="intent-card-header">
|
||||||
|
<h3 class="intent-title">
|
||||||
|
Make a financial CRM for Deloitte
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="intent-progress">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div
|
||||||
|
class="progress-bar-fill warning"
|
||||||
|
style="width: 45%"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<span class="progress-text warning">25/60 Steps</span>
|
||||||
|
<span class="progress-text warning">45%</span>
|
||||||
|
</div>
|
||||||
|
<div class="intent-status-line">
|
||||||
|
<span class="intent-status-label">STATUS</span>
|
||||||
|
<span class="intent-status-badge awaiting">
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="3"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
rx="2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Awaiting Decision
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="intent-description">
|
||||||
|
<strong>Implement User Authentication System</strong
|
||||||
|
><br />
|
||||||
|
<span class="pending"
|
||||||
|
>The authentication system needs to balance
|
||||||
|
security...</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="intent-meta-tag"
|
||||||
|
style="margin-left: 8px; display: inline-flex"
|
||||||
|
>Pending ⏸</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="intent-status-line" style="margin-top: 16px">
|
||||||
|
<span class="intent-status-label">INTEGRITY</span>
|
||||||
|
</div>
|
||||||
|
<div class="intent-meta">
|
||||||
|
<span class="intent-meta-tag">Started 5d ago</span>
|
||||||
|
<span class="intent-meta-tag">Due Nov 30th</span>
|
||||||
|
<span class="intent-health warning"
|
||||||
|
>Intent Health 85%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Detail Panel -->
|
||||||
|
<div class="dashboard-sidebar">
|
||||||
|
<div class="detail-panel">
|
||||||
|
<!-- Detail Header -->
|
||||||
|
<div class="detail-panel-header">
|
||||||
|
<div class="detail-panel-nav">
|
||||||
|
<button class="nav-btn" title="Previous">
|
||||||
|
<svg
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="15 18 9 12 15 6" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h2>Make a financial CRM for Deloitte</h2>
|
||||||
|
<div class="detail-panel-actions">
|
||||||
|
<span class="intent-status-badge awaiting">
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="3"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
rx="2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Awaiting Decision
|
||||||
|
</span>
|
||||||
|
<button class="action-btn-pause" title="Pause Intent">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="6" y="4" width="4" height="16" />
|
||||||
|
<rect x="14" y="4" width="4" height="16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar in Header -->
|
||||||
|
<div class="detail-status">
|
||||||
|
<div class="detail-progress-bar" style="flex: 1"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -10,10 +10,14 @@
|
||||||
.tasks-app {
|
.tasks-app {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
background: var(--sentient-bg-primary);
|
min-height: 0;
|
||||||
color: var(--sentient-text-primary);
|
background: var(--bg-primary, var(--sentient-bg-primary, #0a0a0f));
|
||||||
font-family: var(--sentient-font-family);
|
color: var(--text-primary, var(--sentient-text-primary, #ffffff));
|
||||||
|
font-family: var(
|
||||||
|
--font-family,
|
||||||
|
var(--sentient-font-family, system-ui, -apple-system, sans-serif)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
|
|
@ -25,8 +29,35 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
background: var(--sentient-bg-secondary);
|
background: var(--bg-secondary, var(--sentient-bg-secondary, #12121a));
|
||||||
border-bottom: 1px solid var(--sentient-border);
|
border-bottom: 1px solid var(--border, var(--sentient-border, #2a2a3a));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New Intent Button */
|
||||||
|
.btn-new-intent {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--accent, var(--sentient-accent, #d4f505));
|
||||||
|
color: var(--accent-foreground, #000000);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-new-intent:hover {
|
||||||
|
background: var(--accent-hover, var(--sentient-accent-hover, #bfdd04));
|
||||||
|
box-shadow: 0 0 20px var(--accent-glow, rgba(212, 245, 5, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-new-intent svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar-left {
|
.topbar-left {
|
||||||
|
|
@ -44,6 +75,18 @@
|
||||||
color: var(--sentient-text-primary);
|
color: var(--sentient-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tasks List Title Row */
|
||||||
|
.tasks-list-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks-list-title h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.topbar-logo-icon {
|
.topbar-logo-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
@ -211,6 +254,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
|
|
@ -221,13 +265,17 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: 1px solid var(--sentient-border);
|
border-right: 1px solid var(--border, var(--sentient-border, #2a2a3a));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-list-header {
|
.tasks-list-header {
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
border-bottom: 1px solid var(--sentient-border);
|
border-bottom: 1px solid var(--border, var(--sentient-border, #2a2a3a));
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-list-title {
|
.tasks-list-title {
|
||||||
|
|
@ -240,13 +288,13 @@
|
||||||
.tasks-list-title h1 {
|
.tasks-list-title h1 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--sentient-text-primary);
|
color: var(--text-primary, var(--sentient-text-primary, #ffffff));
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-count {
|
.tasks-count {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--sentient-text-muted);
|
color: var(--text-muted, var(--sentient-text-muted, #6b6b80));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status Filter Pills */
|
/* Status Filter Pills */
|
||||||
|
|
@ -254,6 +302,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-pill {
|
.status-pill {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,24 @@
|
||||||
<span class="tasks-count" id="tasks-total-count"
|
<span class="tasks-count" id="tasks-total-count"
|
||||||
>24 tasks</span
|
>24 tasks</span
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
class="btn-new-intent"
|
||||||
|
onclick="showNewIntentModal()"
|
||||||
|
title="Create new intent"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19" />
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" />
|
||||||
|
</svg>
|
||||||
|
New Intent
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Filter Pills -->
|
<!-- Status Filter Pills -->
|
||||||
|
|
@ -505,4 +523,156 @@
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/suite/tasks/tasks.js"></script>
|
<!-- New Intent Modal -->
|
||||||
|
<div class="modal-overlay" id="newIntentModal">
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Create New Intent</h3>
|
||||||
|
<button class="modal-close" onclick="closeNewIntentModal()">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18" />
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form
|
||||||
|
id="newIntentForm"
|
||||||
|
hx-post="/api/tasks"
|
||||||
|
hx-target="#task-list"
|
||||||
|
hx-swap="afterbegin"
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="intentTitle"
|
||||||
|
>Intent Title</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-input"
|
||||||
|
id="intentTitle"
|
||||||
|
name="title"
|
||||||
|
placeholder="e.g., Launch Q4 Campaign for Verse"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="intentDescription"
|
||||||
|
>Description</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="form-input"
|
||||||
|
id="intentDescription"
|
||||||
|
name="description"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Describe what this intent should accomplish..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="intentDueDate"
|
||||||
|
>Due Date</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
class="form-input"
|
||||||
|
id="intentDueDate"
|
||||||
|
name="due_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="intentPriority"
|
||||||
|
>Priority</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
class="form-input"
|
||||||
|
id="intentPriority"
|
||||||
|
name="priority"
|
||||||
|
>
|
||||||
|
<option value="normal">Normal</option>
|
||||||
|
<option value="high">High</option>
|
||||||
|
<option value="urgent">Urgent</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="intentGoal"
|
||||||
|
>Goal / Success Criteria</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="form-input"
|
||||||
|
id="intentGoal"
|
||||||
|
name="goal"
|
||||||
|
rows="2"
|
||||||
|
placeholder="How will you know when this intent is complete?"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
class="decision-btn decision-btn-secondary"
|
||||||
|
onclick="closeNewIntentModal()"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="decision-btn decision-btn-primary"
|
||||||
|
type="submit"
|
||||||
|
form="newIntentForm"
|
||||||
|
>
|
||||||
|
Create Intent
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Conditionally load tasks.js only if not already loaded
|
||||||
|
if (typeof TasksState === "undefined") {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = "tasks/tasks.js";
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// New Intent Modal functions
|
||||||
|
function showNewIntentModal() {
|
||||||
|
document.getElementById("newIntentModal").classList.add("show");
|
||||||
|
document.getElementById("intentTitle").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNewIntentModal() {
|
||||||
|
document.getElementById("newIntentModal").classList.remove("show");
|
||||||
|
document.getElementById("newIntentForm").reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal on escape key
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
closeNewIntentModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal when clicking overlay
|
||||||
|
document
|
||||||
|
.getElementById("newIntentModal")
|
||||||
|
?.addEventListener("click", (e) => {
|
||||||
|
if (e.target.classList.contains("modal-overlay")) {
|
||||||
|
closeNewIntentModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal after successful form submission
|
||||||
|
document.body.addEventListener("htmx:afterRequest", (e) => {
|
||||||
|
if (e.detail.elt.id === "newIntentForm" && e.detail.successful) {
|
||||||
|
closeNewIntentModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@
|
||||||
// STATE MANAGEMENT
|
// STATE MANAGEMENT
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
const TasksState = {
|
// Prevent duplicate declaration when script is reloaded via HTMX
|
||||||
|
if (typeof TasksState === "undefined") {
|
||||||
|
var TasksState = {
|
||||||
selectedTaskId: 2, // Default selected task
|
selectedTaskId: 2, // Default selected task
|
||||||
currentFilter: "complete",
|
currentFilter: "complete",
|
||||||
tasks: [],
|
tasks: [],
|
||||||
wsConnection: null,
|
wsConnection: null,
|
||||||
agentLogPaused: false,
|
agentLogPaused: false,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// INITIALIZATION
|
// INITIALIZATION
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue