Fix suite app routing - use HTML files instead of API endpoints, add drive.html

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-15 16:40:04 -03:00
parent 4451cffdb5
commit 45b23af166
2 changed files with 429 additions and 79 deletions

319
ui/suite/drive/drive.html Normal file
View file

@ -0,0 +1,319 @@
<!-- =============================================================================
DRIVE APP
File Management Interface
============================================================================= -->
<link rel="stylesheet" href="/themes/sentient/sentient.css" />
<link rel="stylesheet" href="/suite/drive/drive.css" />
<div class="drive-app sentient-theme">
<!-- Top Header Bar -->
<header class="drive-topbar">
<div class="topbar-left">
<div class="topbar-logo">
<div class="topbar-logo-icon">📁</div>
<span>Drive</span>
</div>
<nav class="topbar-nav">
<button class="topbar-nav-item active">My Files</button>
<button class="topbar-nav-item">Shared</button>
<button class="topbar-nav-item">Recent</button>
<button class="topbar-nav-item">Starred</button>
</nav>
</div>
<div class="topbar-center">
<div class="topbar-search">
<span class="topbar-search-icon">🔍</span>
<input
type="text"
class="topbar-search-input"
placeholder="Search files and folders..."
/>
</div>
</div>
<div class="topbar-right">
<button class="topbar-btn-primary" onclick="uploadFile()">
<span>⬆️</span> Upload
</button>
<button class="topbar-icon-btn" title="New Folder" onclick="createFolder()">
📁+
</button>
<button class="topbar-icon-btn" title="Grid View" onclick="toggleView('grid')">
</button>
<button class="topbar-icon-btn" title="List View" onclick="toggleView('list')">
</button>
</div>
</header>
<!-- Main Content Area -->
<main class="drive-main">
<!-- Left: File Browser -->
<section class="drive-content-panel">
<!-- Stat Cards -->
<div class="drive-stat-cards">
<div class="stat-card highlight">
<div class="stat-card-icon">💾</div>
<div class="stat-card-content">
<div class="stat-card-label">Storage Used</div>
<div class="stat-card-value">12.4 GB</div>
<div class="stat-card-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: 62%"></div>
</div>
<span class="progress-text">62% of 20 GB</span>
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📄</div>
<div class="stat-card-content">
<div class="stat-card-label">Files</div>
<div class="stat-card-value">1,847</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">📂</div>
<div class="stat-card-content">
<div class="stat-card-label">Folders</div>
<div class="stat-card-value">156</div>
</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">🔗</div>
<div class="stat-card-content">
<div class="stat-card-label">Shared</div>
<div class="stat-card-value">42</div>
</div>
</div>
</div>
<!-- Breadcrumb Navigation -->
<div class="drive-breadcrumb">
<button class="breadcrumb-item" onclick="navigateTo('root')">
🏠 Home
</button>
<span class="breadcrumb-separator"></span>
<button class="breadcrumb-item" onclick="navigateTo('projects')">
Projects
</button>
<span class="breadcrumb-separator"></span>
<span class="breadcrumb-item current">General Bots</span>
</div>
<!-- File Grid -->
<div
class="drive-file-grid"
id="file-view"
hx-get="/api/drive/list"
hx-trigger="load"
hx-swap="innerHTML"
>
<!-- Folders -->
<article class="file-item folder" data-id="1" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-info">
<div class="file-name">Documents</div>
<div class="file-meta">12 items • Modified 2 hours ago</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item folder" data-id="2" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-info">
<div class="file-name">Images</div>
<div class="file-meta">45 items • Modified yesterday</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item folder" data-id="3" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-info">
<div class="file-name">Source Code</div>
<div class="file-meta">89 items • Modified 3 days ago</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item folder" data-id="4" onclick="openFolder(this)">
<div class="file-icon">📁</div>
<div class="file-info">
<div class="file-name">Backups</div>
<div class="file-meta">8 items • Modified last week</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<!-- Files -->
<article class="file-item" data-id="5" onclick="selectFile(this)">
<div class="file-icon">📄</div>
<div class="file-info">
<div class="file-name">project-report.pdf</div>
<div class="file-meta">2.4 MB • Modified today</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Download">⬇️</button>
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item" data-id="6" onclick="selectFile(this)">
<div class="file-icon">🖼️</div>
<div class="file-info">
<div class="file-name">dashboard-mockup.png</div>
<div class="file-meta">1.8 MB • Modified yesterday</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Download">⬇️</button>
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item" data-id="7" onclick="selectFile(this)">
<div class="file-icon">📊</div>
<div class="file-info">
<div class="file-name">analytics-2025.xlsx</div>
<div class="file-meta">890 KB • Modified 2 days ago</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Download">⬇️</button>
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item" data-id="8" onclick="selectFile(this)">
<div class="file-icon">📝</div>
<div class="file-info">
<div class="file-name">meeting-notes.md</div>
<div class="file-meta">45 KB • Modified 3 days ago</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Download">⬇️</button>
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item" data-id="9" onclick="selectFile(this)">
<div class="file-icon">🎬</div>
<div class="file-info">
<div class="file-name">demo-video.mp4</div>
<div class="file-meta">125 MB • Modified last week</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Download">⬇️</button>
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
<article class="file-item" data-id="10" onclick="selectFile(this)">
<div class="file-icon">📦</div>
<div class="file-info">
<div class="file-name">release-v2.0.zip</div>
<div class="file-meta">45 MB • Modified last week</div>
</div>
<div class="file-actions">
<button class="file-action-btn" title="Download">⬇️</button>
<button class="file-action-btn" title="Share">🔗</button>
<button class="file-action-btn" title="More"></button>
</div>
</article>
</div>
</section>
<!-- Right: File Details / AI Panel -->
<aside class="drive-detail-panel" id="detail-panel">
<!-- File Preview Section -->
<div class="detail-header">
<h2 class="detail-title">File Details</h2>
<div class="detail-actions">
<button class="detail-action-btn" title="Close"></button>
</div>
</div>
<div class="detail-content">
<div class="detail-preview">
<div class="preview-placeholder">
<span class="preview-icon">📄</span>
<p>Select a file to preview</p>
</div>
</div>
<div class="detail-info">
<div class="info-row">
<span class="info-label">Name</span>
<span class="info-value" id="detail-name"></span>
</div>
<div class="info-row">
<span class="info-label">Size</span>
<span class="info-value" id="detail-size"></span>
</div>
<div class="info-row">
<span class="info-label">Type</span>
<span class="info-value" id="detail-type"></span>
</div>
<div class="info-row">
<span class="info-label">Modified</span>
<span class="info-value" id="detail-modified"></span>
</div>
<div class="info-row">
<span class="info-label">Location</span>
<span class="info-value" id="detail-location"></span>
</div>
</div>
<div class="detail-quick-actions">
<button class="quick-action-btn">⬇️ Download</button>
<button class="quick-action-btn">🔗 Share</button>
<button class="quick-action-btn">✏️ Rename</button>
<button class="quick-action-btn">🗑️ Delete</button>
</div>
</div>
<!-- AI Assistant Section -->
<section class="drive-ai-section">
<div class="ai-section-header">
<span class="ai-icon">🤖</span>
<span class="ai-title">AI Assistant</span>
</div>
<div class="ai-suggestions">
<button class="ai-suggestion-btn" onclick="aiAction('organize')">
Organize files
</button>
<button class="ai-suggestion-btn" onclick="aiAction('find')">
Find duplicates
</button>
<button class="ai-suggestion-btn" onclick="aiAction('analyze')">
Analyze content
</button>
<button class="ai-suggestion-btn" onclick="aiAction('summarize')">
Summarize document
</button>
</div>
</section>
</aside>
</main>
</div>
<script src="/suite/drive/drive.js"></script>

View file

@ -193,7 +193,7 @@
data-section="drive" data-section="drive"
role="menuitem" role="menuitem"
aria-label="Drive application" aria-label="Drive application"
hx-get="/api/drive/list" hx-get="drive/drive.html"
hx-target="#main-content" hx-target="#main-content"
hx-push-url="true" hx-push-url="true"
> >
@ -257,7 +257,7 @@
data-section="tasks" data-section="tasks"
role="menuitem" role="menuitem"
aria-label="Tasks application" aria-label="Tasks application"
hx-get="/api/tasks" hx-get="tasks/tasks.html"
hx-target="#main-content" hx-target="#main-content"
hx-push-url="true" hx-push-url="true"
> >
@ -286,7 +286,7 @@
data-section="mail" data-section="mail"
role="menuitem" role="menuitem"
aria-label="Mail application" aria-label="Mail application"
hx-get="/api/email/latest" hx-get="mail/mail.html"
hx-target="#main-content" hx-target="#main-content"
hx-push-url="true" hx-push-url="true"
> >
@ -413,10 +413,7 @@
</header> </header>
<!-- Main content area --> <!-- Main content area -->
<main <main id="main-content" role="main">
id="main-content"
role="main"
>
<!-- Sections will be loaded dynamically --> <!-- Sections will be loaded dynamically -->
</main> </main>
@ -429,40 +426,59 @@
// Chat initialization function (called after HTMX loads chat content) // Chat initialization function (called after HTMX loads chat content)
function initChatModule() { function initChatModule() {
if (window.chatModuleInitialized) return; if (window.chatModuleInitialized) return;
const messageInput = document.getElementById('messageInput'); const messageInput = document.getElementById("messageInput");
const sendBtn = document.getElementById('sendBtn'); const sendBtn = document.getElementById("sendBtn");
if (!messageInput || !sendBtn) return; if (!messageInput || !sendBtn) return;
window.chatModuleInitialized = true; window.chatModuleInitialized = true;
console.log("Initializing chat module..."); console.log("Initializing chat module...");
// WebSocket URL // WebSocket URL
const WS_BASE_URL = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; const WS_BASE_URL =
window.location.protocol === "https:" ? "wss://" : "ws://";
const WS_URL = `${WS_BASE_URL}${window.location.host}`; const WS_URL = `${WS_BASE_URL}${window.location.host}`;
// Message Type Constants // Message Type Constants
const MessageType = { EXTERNAL: 0, USER: 1, BOT_RESPONSE: 2, CONTINUE: 3, SUGGESTION: 4, CONTEXT_CHANGE: 5 }; const MessageType = {
EXTERNAL: 0,
USER: 1,
BOT_RESPONSE: 2,
CONTINUE: 3,
SUGGESTION: 4,
CONTEXT_CHANGE: 5,
};
// State // State
let ws = null, currentSessionId = null, currentUserId = null, currentBotId = "default"; let ws = null,
let isStreaming = false, streamingMessageId = null, currentStreamingContent = ""; currentSessionId = null,
currentUserId = null,
currentBotId = "default";
let isStreaming = false,
streamingMessageId = null,
currentStreamingContent = "";
let reconnectAttempts = 0; let reconnectAttempts = 0;
const maxReconnectAttempts = 5; const maxReconnectAttempts = 5;
// Initialize auth and WebSocket // Initialize auth and WebSocket
async function initChat() { async function initChat() {
try { try {
updateConnectionStatus('connecting'); updateConnectionStatus("connecting");
const response = await fetch(`/api/auth?bot_name=default`); const response = await fetch(
`/api/auth?bot_name=default`,
);
const auth = await response.json(); const auth = await response.json();
currentUserId = auth.user_id; currentUserId = auth.user_id;
currentSessionId = auth.session_id; currentSessionId = auth.session_id;
currentBotId = auth.bot_id || "default"; currentBotId = auth.bot_id || "default";
console.log("Auth:", { currentUserId, currentSessionId, currentBotId }); console.log("Auth:", {
currentUserId,
currentSessionId,
currentBotId,
});
connectWebSocket(); connectWebSocket();
} catch (e) { } catch (e) {
console.error("Auth failed:", e); console.error("Auth failed:", e);
updateConnectionStatus('disconnected'); updateConnectionStatus("disconnected");
setTimeout(initChat, 3000); setTimeout(initChat, 3000);
} }
} }
@ -474,15 +490,17 @@
ws.onopen = () => { ws.onopen = () => {
console.log("WebSocket connected"); console.log("WebSocket connected");
updateConnectionStatus('connected'); updateConnectionStatus("connected");
reconnectAttempts = 0; reconnectAttempts = 0;
}; };
ws.onmessage = (event) => { ws.onmessage = (event) => {
try { try {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'connected') return; if (data.type === "connected") return;
if (data.message_type === MessageType.BOT_RESPONSE) { if (
data.message_type === MessageType.BOT_RESPONSE
) {
processMessage(data); processMessage(data);
} }
} catch (e) { } catch (e) {
@ -491,10 +509,13 @@
}; };
ws.onclose = () => { ws.onclose = () => {
updateConnectionStatus('disconnected'); updateConnectionStatus("disconnected");
if (reconnectAttempts < maxReconnectAttempts) { if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++; reconnectAttempts++;
setTimeout(connectWebSocket, 1000 * reconnectAttempts); setTimeout(
connectWebSocket,
1000 * reconnectAttempts,
);
} }
}; };
@ -504,29 +525,33 @@
function processMessage(data) { function processMessage(data) {
if (data.is_complete) { if (data.is_complete) {
if (isStreaming) finalizeStreaming(); if (isStreaming) finalizeStreaming();
else addMessage('bot', data.content); else addMessage("bot", data.content);
isStreaming = false; isStreaming = false;
} else { } else {
if (!isStreaming) { if (!isStreaming) {
isStreaming = true; isStreaming = true;
streamingMessageId = 'streaming-' + Date.now(); streamingMessageId = "streaming-" + Date.now();
currentStreamingContent = data.content || ''; currentStreamingContent = data.content || "";
addMessage('bot', currentStreamingContent, streamingMessageId); addMessage(
"bot",
currentStreamingContent,
streamingMessageId,
);
} else { } else {
currentStreamingContent += data.content || ''; currentStreamingContent += data.content || "";
updateStreaming(currentStreamingContent); updateStreaming(currentStreamingContent);
} }
} }
} }
function addMessage(sender, content, msgId = null) { function addMessage(sender, content, msgId = null) {
const messages = document.getElementById('messages'); const messages = document.getElementById("messages");
if (!messages) return; if (!messages) return;
const div = document.createElement('div'); const div = document.createElement("div");
div.className = `message ${sender}`; div.className = `message ${sender}`;
if (msgId) div.id = msgId; if (msgId) div.id = msgId;
if (sender === 'user') { if (sender === "user") {
div.innerHTML = `<div class="message-content user-message">${escapeHtml(content)}</div>`; div.innerHTML = `<div class="message-content user-message">${escapeHtml(content)}</div>`;
} else { } else {
div.innerHTML = `<div class="message-content bot-message">${marked.parse(content)}</div>`; div.innerHTML = `<div class="message-content bot-message">${marked.parse(content)}</div>`;
@ -537,55 +562,61 @@
function updateStreaming(content) { function updateStreaming(content) {
const el = document.getElementById(streamingMessageId); const el = document.getElementById(streamingMessageId);
if (el) el.querySelector('.message-content').innerHTML = marked.parse(content); if (el)
el.querySelector(".message-content").innerHTML =
marked.parse(content);
} }
function finalizeStreaming() { function finalizeStreaming() {
const el = document.getElementById(streamingMessageId); const el = document.getElementById(streamingMessageId);
if (el) { if (el) {
el.querySelector('.message-content').innerHTML = marked.parse(currentStreamingContent); el.querySelector(".message-content").innerHTML =
el.removeAttribute('id'); marked.parse(currentStreamingContent);
el.removeAttribute("id");
} }
streamingMessageId = null; streamingMessageId = null;
currentStreamingContent = ''; currentStreamingContent = "";
} }
function sendMessage() { function sendMessage() {
const input = document.getElementById('messageInput'); const input = document.getElementById("messageInput");
const content = input.value.trim(); const content = input.value.trim();
if (!content || !ws || ws.readyState !== WebSocket.OPEN) return; if (!content || !ws || ws.readyState !== WebSocket.OPEN)
return;
addMessage('user', content); addMessage("user", content);
ws.send(JSON.stringify({ ws.send(
bot_id: currentBotId, JSON.stringify({
user_id: currentUserId, bot_id: currentBotId,
session_id: currentSessionId, user_id: currentUserId,
channel: 'web', session_id: currentSessionId,
content: content, channel: "web",
message_type: MessageType.USER, content: content,
timestamp: new Date().toISOString() message_type: MessageType.USER,
})); timestamp: new Date().toISOString(),
}),
);
input.value = ''; input.value = "";
input.focus(); input.focus();
} }
function updateConnectionStatus(status) { function updateConnectionStatus(status) {
const el = document.getElementById('connectionStatus'); const el = document.getElementById("connectionStatus");
if (el) el.className = `connection-status ${status}`; if (el) el.className = `connection-status ${status}`;
} }
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;
} }
// Setup event handlers // Setup event handlers
sendBtn.onclick = sendMessage; sendBtn.onclick = sendMessage;
messageInput.addEventListener('keypress', (e) => { messageInput.addEventListener("keypress", (e) => {
if (e.key === 'Enter') sendMessage(); if (e.key === "Enter") sendMessage();
}); });
// Start chat // Start chat
@ -593,8 +624,8 @@
} }
// Listen for HTMX content swaps to initialize chat // Listen for HTMX content swaps to initialize chat
document.addEventListener('htmx:afterSettle', (e) => { document.addEventListener("htmx:afterSettle", (e) => {
if (document.getElementById('messageInput')) { if (document.getElementById("messageInput")) {
window.chatModuleInitialized = false; // Reset flag for reinitialization window.chatModuleInitialized = false; // Reset flag for reinitialization
initChatModule(); initChatModule();
} }