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

@ -21,7 +21,7 @@
<script src="js/vendor/htmx-ws.js"></script>
<script src="js/vendor/htmx-json-enc.js"></script>
<script src="js/vendor/marked.min.js"></script>
<!-- Enable HTMX to process inline scripts in swapped content -->
<script>
htmx.config.allowEval = true;
@ -193,7 +193,7 @@
data-section="drive"
role="menuitem"
aria-label="Drive application"
hx-get="/api/drive/list"
hx-get="drive/drive.html"
hx-target="#main-content"
hx-push-url="true"
>
@ -257,7 +257,7 @@
data-section="tasks"
role="menuitem"
aria-label="Tasks application"
hx-get="/api/tasks"
hx-get="tasks/tasks.html"
hx-target="#main-content"
hx-push-url="true"
>
@ -286,7 +286,7 @@
data-section="mail"
role="menuitem"
aria-label="Mail application"
hx-get="/api/email/latest"
hx-get="mail/mail.html"
hx-target="#main-content"
hx-push-url="true"
>
@ -413,10 +413,7 @@
</header>
<!-- Main content area -->
<main
id="main-content"
role="main"
>
<main id="main-content" role="main">
<!-- Sections will be loaded dynamically -->
</main>
@ -429,104 +426,132 @@
// Chat initialization function (called after HTMX loads chat content)
function initChatModule() {
if (window.chatModuleInitialized) return;
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const messageInput = document.getElementById("messageInput");
const sendBtn = document.getElementById("sendBtn");
if (!messageInput || !sendBtn) return;
window.chatModuleInitialized = true;
console.log("Initializing chat module...");
// 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}`;
// 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
let ws = null, currentSessionId = null, currentUserId = null, currentBotId = "default";
let isStreaming = false, streamingMessageId = null, currentStreamingContent = "";
let ws = null,
currentSessionId = null,
currentUserId = null,
currentBotId = "default";
let isStreaming = false,
streamingMessageId = null,
currentStreamingContent = "";
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
// Initialize auth and WebSocket
async function initChat() {
try {
updateConnectionStatus('connecting');
const response = await fetch(`/api/auth?bot_name=default`);
updateConnectionStatus("connecting");
const response = await fetch(
`/api/auth?bot_name=default`,
);
const auth = await response.json();
currentUserId = auth.user_id;
currentSessionId = auth.session_id;
currentBotId = auth.bot_id || "default";
console.log("Auth:", { currentUserId, currentSessionId, currentBotId });
console.log("Auth:", {
currentUserId,
currentSessionId,
currentBotId,
});
connectWebSocket();
} catch (e) {
console.error("Auth failed:", e);
updateConnectionStatus('disconnected');
updateConnectionStatus("disconnected");
setTimeout(initChat, 3000);
}
}
function connectWebSocket() {
if (ws) ws.close();
const url = `${WS_URL}/ws?session_id=${currentSessionId}&user_id=${currentUserId}`;
ws = new WebSocket(url);
ws.onopen = () => {
console.log("WebSocket connected");
updateConnectionStatus('connected');
updateConnectionStatus("connected");
reconnectAttempts = 0;
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'connected') return;
if (data.message_type === MessageType.BOT_RESPONSE) {
if (data.type === "connected") return;
if (
data.message_type === MessageType.BOT_RESPONSE
) {
processMessage(data);
}
} catch (e) {
console.error("WS message error:", e);
}
};
ws.onclose = () => {
updateConnectionStatus('disconnected');
updateConnectionStatus("disconnected");
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
setTimeout(connectWebSocket, 1000 * reconnectAttempts);
setTimeout(
connectWebSocket,
1000 * reconnectAttempts,
);
}
};
ws.onerror = (e) => console.error("WebSocket error:", e);
}
function processMessage(data) {
if (data.is_complete) {
if (isStreaming) finalizeStreaming();
else addMessage('bot', data.content);
else addMessage("bot", data.content);
isStreaming = false;
} else {
if (!isStreaming) {
isStreaming = true;
streamingMessageId = 'streaming-' + Date.now();
currentStreamingContent = data.content || '';
addMessage('bot', currentStreamingContent, streamingMessageId);
streamingMessageId = "streaming-" + Date.now();
currentStreamingContent = data.content || "";
addMessage(
"bot",
currentStreamingContent,
streamingMessageId,
);
} else {
currentStreamingContent += data.content || '';
currentStreamingContent += data.content || "";
updateStreaming(currentStreamingContent);
}
}
}
function addMessage(sender, content, msgId = null) {
const messages = document.getElementById('messages');
const messages = document.getElementById("messages");
if (!messages) return;
const div = document.createElement('div');
const div = document.createElement("div");
div.className = `message ${sender}`;
if (msgId) div.id = msgId;
if (sender === 'user') {
if (sender === "user") {
div.innerHTML = `<div class="message-content user-message">${escapeHtml(content)}</div>`;
} else {
div.innerHTML = `<div class="message-content bot-message">${marked.parse(content)}</div>`;
@ -534,72 +559,78 @@
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}
function updateStreaming(content) {
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() {
const el = document.getElementById(streamingMessageId);
if (el) {
el.querySelector('.message-content').innerHTML = marked.parse(currentStreamingContent);
el.removeAttribute('id');
el.querySelector(".message-content").innerHTML =
marked.parse(currentStreamingContent);
el.removeAttribute("id");
}
streamingMessageId = null;
currentStreamingContent = '';
currentStreamingContent = "";
}
function sendMessage() {
const input = document.getElementById('messageInput');
const input = document.getElementById("messageInput");
const content = input.value.trim();
if (!content || !ws || ws.readyState !== WebSocket.OPEN) return;
addMessage('user', content);
ws.send(JSON.stringify({
bot_id: currentBotId,
user_id: currentUserId,
session_id: currentSessionId,
channel: 'web',
content: content,
message_type: MessageType.USER,
timestamp: new Date().toISOString()
}));
input.value = '';
if (!content || !ws || ws.readyState !== WebSocket.OPEN)
return;
addMessage("user", content);
ws.send(
JSON.stringify({
bot_id: currentBotId,
user_id: currentUserId,
session_id: currentSessionId,
channel: "web",
content: content,
message_type: MessageType.USER,
timestamp: new Date().toISOString(),
}),
);
input.value = "";
input.focus();
}
function updateConnectionStatus(status) {
const el = document.getElementById('connectionStatus');
const el = document.getElementById("connectionStatus");
if (el) el.className = `connection-status ${status}`;
}
function escapeHtml(text) {
const div = document.createElement('div');
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
// Setup event handlers
sendBtn.onclick = sendMessage;
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
messageInput.addEventListener("keypress", (e) => {
if (e.key === "Enter") sendMessage();
});
// Start chat
initChat();
}
// Listen for HTMX content swaps to initialize chat
document.addEventListener('htmx:afterSettle', (e) => {
if (document.getElementById('messageInput')) {
document.addEventListener("htmx:afterSettle", (e) => {
if (document.getElementById("messageInput")) {
window.chatModuleInitialized = false; // Reset flag for reinitialization
initChatModule();
}
});
// Simple initialization for HTMX app
document.addEventListener("DOMContentLoaded", () => {
console.log("🚀 Initializing General Bots with HTMX...");