Fix suite app routing - use HTML files instead of API endpoints, add drive.html
This commit is contained in:
parent
4451cffdb5
commit
45b23af166
2 changed files with 429 additions and 79 deletions
319
ui/suite/drive/drive.html
Normal file
319
ui/suite/drive/drive.html
Normal 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>
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<script src="js/vendor/htmx-ws.js"></script>
|
<script src="js/vendor/htmx-ws.js"></script>
|
||||||
<script src="js/vendor/htmx-json-enc.js"></script>
|
<script src="js/vendor/htmx-json-enc.js"></script>
|
||||||
<script src="js/vendor/marked.min.js"></script>
|
<script src="js/vendor/marked.min.js"></script>
|
||||||
|
|
||||||
<!-- Enable HTMX to process inline scripts in swapped content -->
|
<!-- Enable HTMX to process inline scripts in swapped content -->
|
||||||
<script>
|
<script>
|
||||||
htmx.config.allowEval = true;
|
htmx.config.allowEval = true;
|
||||||
|
|
@ -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,104 +426,132 @@
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
if (ws) ws.close();
|
if (ws) ws.close();
|
||||||
const url = `${WS_URL}/ws?session_id=${currentSessionId}&user_id=${currentUserId}`;
|
const url = `${WS_URL}/ws?session_id=${currentSessionId}&user_id=${currentUserId}`;
|
||||||
ws = new WebSocket(url);
|
ws = new WebSocket(url);
|
||||||
|
|
||||||
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) {
|
||||||
console.error("WS message error:", e);
|
console.error("WS message error:", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
updateConnectionStatus('disconnected');
|
updateConnectionStatus("disconnected");
|
||||||
if (reconnectAttempts < maxReconnectAttempts) {
|
if (reconnectAttempts < maxReconnectAttempts) {
|
||||||
reconnectAttempts++;
|
reconnectAttempts++;
|
||||||
setTimeout(connectWebSocket, 1000 * reconnectAttempts);
|
setTimeout(
|
||||||
|
connectWebSocket,
|
||||||
|
1000 * reconnectAttempts,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = (e) => console.error("WebSocket error:", e);
|
ws.onerror = (e) => console.error("WebSocket error:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
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>`;
|
||||||
|
|
@ -534,72 +559,78 @@
|
||||||
messages.appendChild(div);
|
messages.appendChild(div);
|
||||||
messages.scrollTop = messages.scrollHeight;
|
messages.scrollTop = messages.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
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({
|
|
||||||
bot_id: currentBotId,
|
ws.send(
|
||||||
user_id: currentUserId,
|
JSON.stringify({
|
||||||
session_id: currentSessionId,
|
bot_id: currentBotId,
|
||||||
channel: 'web',
|
user_id: currentUserId,
|
||||||
content: content,
|
session_id: currentSessionId,
|
||||||
message_type: MessageType.USER,
|
channel: "web",
|
||||||
timestamp: new Date().toISOString()
|
content: content,
|
||||||
}));
|
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
|
||||||
initChat();
|
initChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Simple initialization for HTMX app
|
// Simple initialization for HTMX app
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
console.log("🚀 Initializing General Bots with HTMX...");
|
console.log("🚀 Initializing General Bots with HTMX...");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue