Fix apps dropdown menu positioning near the grid button
- Changed apps-dropdown right position from 60px to 0 in app.css - Wrapped apps button and dropdown in a container with position:relative - Moved dropdown to be a sibling of the button inside the container - Removed duplicate dropdown from header-right section
This commit is contained in:
parent
2c852715f8
commit
b4c49314a6
35 changed files with 2800 additions and 1631 deletions
|
|
@ -67,7 +67,7 @@
|
|||
<aside class="metrics-sidebar">
|
||||
<div
|
||||
class="metric-card"
|
||||
hx-get="/api/analytics/messages/count"
|
||||
hx-get="/api/ui/analytics/messages/count"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
|
||||
<div
|
||||
class="metric-card"
|
||||
hx-get="/api/analytics/sessions/active"
|
||||
hx-get="/api/ui/analytics/sessions/active"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
<div
|
||||
class="metric-card"
|
||||
hx-get="/api/analytics/response/avg"
|
||||
hx-get="/api/ui/analytics/response/avg"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
|
||||
<div
|
||||
class="metric-card"
|
||||
hx-get="/api/analytics/llm/tokens"
|
||||
hx-get="/api/ui/analytics/llm/tokens"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
|
||||
<div
|
||||
class="metric-card"
|
||||
hx-get="/api/analytics/storage/usage"
|
||||
hx-get="/api/ui/analytics/storage/usage"
|
||||
hx-trigger="load, every 120s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -221,7 +221,7 @@
|
|||
|
||||
<div
|
||||
class="metric-card"
|
||||
hx-get="/api/analytics/errors/count"
|
||||
hx-get="/api/ui/analytics/errors/count"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -289,7 +289,7 @@
|
|||
<div
|
||||
class="chart-container"
|
||||
id="messagesChart"
|
||||
hx-get="/api/analytics/timeseries/messages"
|
||||
hx-get="/api/ui/analytics/timeseries/messages"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -324,7 +324,7 @@
|
|||
<div
|
||||
class="chart-container"
|
||||
id="responseChart"
|
||||
hx-get="/api/analytics/timeseries/response_time"
|
||||
hx-get="/api/ui/analytics/timeseries/response_time"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -343,7 +343,7 @@
|
|||
<div
|
||||
class="chart-container"
|
||||
id="channelChart"
|
||||
hx-get="/api/analytics/channels/distribution"
|
||||
hx-get="/api/ui/analytics/channels/distribution"
|
||||
hx-trigger="load, every 120s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -362,7 +362,7 @@
|
|||
<div
|
||||
class="chart-container"
|
||||
id="botChart"
|
||||
hx-get="/api/analytics/bots/performance"
|
||||
hx-get="/api/ui/analytics/bots/performance"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -504,7 +504,7 @@
|
|||
<div
|
||||
class="activity-feed"
|
||||
id="activityFeed"
|
||||
hx-get="/api/analytics/activity/recent"
|
||||
hx-get="/api/ui/analytics/activity/recent"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -514,7 +514,7 @@
|
|||
<h3 class="mt-4">Top Queries</h3>
|
||||
<div
|
||||
class="top-queries"
|
||||
hx-get="/api/analytics/queries/top"
|
||||
hx-get="/api/ui/analytics/queries/top"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -598,7 +598,7 @@
|
|||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/analytics/query", {
|
||||
const response = await fetch("/api/ui/analytics/chat", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ async function sendAnalyticsQuery() {
|
|||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/analytics/query", {
|
||||
const response = await fetch("/api/ui/analytics/chat", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<!-- Quick Actions -->
|
||||
<div class="sidebar-actions">
|
||||
<button class="btn-primary-full" id="new-event-btn"
|
||||
hx-get="/ui/calendar/event/new"
|
||||
hx-get="/api/ui/calendar/events/new"
|
||||
hx-target="#event-modal-content"
|
||||
hx-swap="innerHTML">
|
||||
<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">
|
||||
<h3>My Calendars</h3>
|
||||
<button class="btn-icon-sm" title="Add Calendar"
|
||||
hx-get="/ui/calendar/new"
|
||||
hx-get="/api/ui/calendar/calendars/new"
|
||||
hx-target="#calendar-modal-content">
|
||||
<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>
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="calendars-list" id="calendars-list"
|
||||
hx-get="/ui/calendar/list"
|
||||
hx-get="/api/ui/calendar/calendars"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Calendars loaded here -->
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
<div class="sidebar-section">
|
||||
<h3>Upcoming</h3>
|
||||
<div class="upcoming-events" id="upcoming-events"
|
||||
hx-get="/ui/calendar/upcoming"
|
||||
hx-get="/api/ui/calendar/events/upcoming"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Upcoming events loaded here -->
|
||||
|
|
@ -137,12 +137,6 @@
|
|||
<button class="view-btn active" data-view="week">Week</button>
|
||||
<button class="view-btn" data-view="month">Month</button>
|
||||
</div>
|
||||
<button class="btn-icon" id="calendar-settings" 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"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -423,7 +417,7 @@
|
|||
<div class="form-group">
|
||||
<label>Import to calendar</label>
|
||||
<select name="calendar_id"
|
||||
hx-get="/ui/calendar/list"
|
||||
hx-get="/api/ui/calendar/calendars"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<option value="">Default Calendar</option>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,84 @@
|
|||
/* Chat module styles - including chat layout and projector component */
|
||||
|
||||
/* Toast Notifications */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
z-index: 10002;
|
||||
}
|
||||
|
||||
.toast {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
background: var(--surface, #1a1a24);
|
||||
border: 1px solid var(--border, #2a2a2a);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
animation: toast-in 0.3s ease;
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
border-color: var(--success, #22c55e);
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
border-color: var(--error, #ef4444);
|
||||
}
|
||||
|
||||
.toast-warning {
|
||||
border-color: var(--warning, #f59e0b);
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
border-color: var(--accent, #3b82f6);
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.toast-message {
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #888888;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.toast-close:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.toast-fade-out {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes toast-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Chat Layout */
|
||||
.chat-layout {
|
||||
display: flex;
|
||||
|
|
@ -15,30 +94,7 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Connection Status */
|
||||
.connection-status {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
z-index: 1000;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.connection-status.connecting {
|
||||
background: #f59e0b;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.connection-status.connected {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.connection-status.disconnected {
|
||||
background: #ef4444;
|
||||
}
|
||||
/* Connection Status - use shared styles from app.css */
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
|
|
@ -96,21 +152,95 @@
|
|||
}
|
||||
|
||||
.user-message {
|
||||
background: var(--accent-color, #3b82f6);
|
||||
color: white;
|
||||
background: var(--accent, var(--primary, #3b82f6));
|
||||
color: #ffffff !important;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
/* Light accent themes need dark text on user messages */
|
||||
[data-theme="sentient"] .user-message,
|
||||
[data-theme="y2kglow"] .user-message,
|
||||
[data-theme="arcadeflash"] .user-message,
|
||||
[data-theme="green"] .user-message,
|
||||
[data-theme="jazzage"] .user-message,
|
||||
[data-theme="mellowgold"] .user-message,
|
||||
[data-theme="polaroidmemories"] .user-message,
|
||||
[data-theme="seasidepostcard"] .user-message,
|
||||
[data-theme="saturdaycartoons"] .user-message,
|
||||
[data-theme="light"] .user-message,
|
||||
[data-theme="typewriter"] .user-message,
|
||||
[data-theme="3dbevel"] .user-message {
|
||||
color: #000000 !important;
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
background: var(--secondary-bg, #f3f4f6);
|
||||
color: var(--text-primary, #1f2937);
|
||||
background: var(--surface, var(--card, #2a2a2a));
|
||||
color: #ffffff !important;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
/* Dark theme adjustments */
|
||||
[data-theme="dark"] .bot-message {
|
||||
background: #374151;
|
||||
color: #f9fafb;
|
||||
.bot-message,
|
||||
.bot-message p,
|
||||
.bot-message span,
|
||||
.bot-message li,
|
||||
.bot-message a,
|
||||
.bot-message strong,
|
||||
.bot-message em,
|
||||
.bot-message h1,
|
||||
.bot-message h2,
|
||||
.bot-message h3,
|
||||
.bot-message h4 {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Light background themes need dark bot message text */
|
||||
[data-theme="light"] .bot-message,
|
||||
[data-theme="light"] .bot-message p,
|
||||
[data-theme="light"] .bot-message span,
|
||||
[data-theme="light"] .bot-message li,
|
||||
[data-theme="light"] .bot-message a,
|
||||
[data-theme="light"] .bot-message strong,
|
||||
[data-theme="light"] .bot-message em,
|
||||
[data-theme="light"] .bot-message h1,
|
||||
[data-theme="light"] .bot-message h2,
|
||||
[data-theme="light"] .bot-message h3,
|
||||
[data-theme="light"] .bot-message h4,
|
||||
[data-theme="polaroidmemories"] .bot-message,
|
||||
[data-theme="polaroidmemories"] .bot-message p,
|
||||
[data-theme="polaroidmemories"] .bot-message span,
|
||||
[data-theme="polaroidmemories"] .bot-message li,
|
||||
[data-theme="polaroidmemories"] .bot-message a,
|
||||
[data-theme="polaroidmemories"] .bot-message h1,
|
||||
[data-theme="polaroidmemories"] .bot-message h2,
|
||||
[data-theme="seasidepostcard"] .bot-message,
|
||||
[data-theme="seasidepostcard"] .bot-message p,
|
||||
[data-theme="seasidepostcard"] .bot-message span,
|
||||
[data-theme="seasidepostcard"] .bot-message li,
|
||||
[data-theme="seasidepostcard"] .bot-message a,
|
||||
[data-theme="seasidepostcard"] .bot-message h1,
|
||||
[data-theme="seasidepostcard"] .bot-message h2,
|
||||
[data-theme="saturdaycartoons"] .bot-message,
|
||||
[data-theme="saturdaycartoons"] .bot-message p,
|
||||
[data-theme="saturdaycartoons"] .bot-message span,
|
||||
[data-theme="saturdaycartoons"] .bot-message li,
|
||||
[data-theme="saturdaycartoons"] .bot-message a,
|
||||
[data-theme="saturdaycartoons"] .bot-message h1,
|
||||
[data-theme="saturdaycartoons"] .bot-message h2,
|
||||
[data-theme="typewriter"] .bot-message,
|
||||
[data-theme="typewriter"] .bot-message p,
|
||||
[data-theme="typewriter"] .bot-message span,
|
||||
[data-theme="typewriter"] .bot-message li,
|
||||
[data-theme="typewriter"] .bot-message a,
|
||||
[data-theme="typewriter"] .bot-message h1,
|
||||
[data-theme="typewriter"] .bot-message h2,
|
||||
[data-theme="3dbevel"] .bot-message,
|
||||
[data-theme="3dbevel"] .bot-message p,
|
||||
[data-theme="3dbevel"] .bot-message span,
|
||||
[data-theme="3dbevel"] .bot-message li,
|
||||
[data-theme="3dbevel"] .bot-message a,
|
||||
[data-theme="3dbevel"] .bot-message h1,
|
||||
[data-theme="3dbevel"] .bot-message h2 {
|
||||
color: #000000 !important;
|
||||
}
|
||||
|
||||
/* Markdown content in bot messages */
|
||||
|
|
@ -123,7 +253,7 @@
|
|||
}
|
||||
|
||||
.bot-message code {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: "Monaco", "Menlo", monospace;
|
||||
|
|
@ -131,7 +261,7 @@
|
|||
}
|
||||
|
||||
.bot-message pre {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
|
|
@ -146,8 +276,8 @@
|
|||
/* Footer */
|
||||
footer {
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid var(--border-color, #e5e7eb);
|
||||
background: var(--primary-bg, #ffffff);
|
||||
border-top: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Suggestions */
|
||||
|
|
@ -176,20 +306,16 @@ footer {
|
|||
}
|
||||
|
||||
/* Input Container */
|
||||
.input-container {
|
||||
.input-container,
|
||||
form.input-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: var(--surface, var(--secondary-bg, #f9fafb));
|
||||
border: 1px solid var(--border, var(--border-color, #e5e7eb));
|
||||
background: var(--surface, var(--card, #1a1a24));
|
||||
border: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .input-container,
|
||||
[data-theme="sentient"] .input-container {
|
||||
background: var(--surface, #1a1a24);
|
||||
border-color: var(--border, #2a2a3a);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.input-container:focus-within {
|
||||
|
|
@ -203,23 +329,18 @@ footer {
|
|||
border-radius: 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary, #1f2937);
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #messageInput,
|
||||
[data-theme="sentient"] #messageInput {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
#messageInput:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#messageInput::placeholder {
|
||||
color: var(--text-muted, #9ca3af);
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
#voiceBtn,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,53 @@
|
|||
<link rel="stylesheet" href="chat/chat.css" />
|
||||
<script>
|
||||
// WebSocket URL - use relative path to go through botui proxy
|
||||
const WS_BASE_URL =
|
||||
window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
const WS_URL = `${WS_BASE_URL}${window.location.host}`;
|
||||
|
||||
// Message Type Constants
|
||||
const MessageType = {
|
||||
<div class="chat-layout" id="chat-app">
|
||||
<main id="messages"></main>
|
||||
|
||||
<footer>
|
||||
<div class="suggestions-container" id="suggestions"></div>
|
||||
<form class="input-container" id="chatForm">
|
||||
<input
|
||||
name="content"
|
||||
id="messageInput"
|
||||
type="text"
|
||||
placeholder="Message..."
|
||||
autofocus
|
||||
/>
|
||||
<button type="button" id="voiceBtn" title="Voice">🎤</button>
|
||||
<button type="submit" id="sendBtn" title="Send">↑</button>
|
||||
</form>
|
||||
</footer>
|
||||
<button class="scroll-to-bottom" id="scrollToBottom">↓</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// ==========================================================================
|
||||
// NOTIFICATION HELPER - Uses GBAlerts bell system
|
||||
// ==========================================================================
|
||||
function notify(message, type) {
|
||||
type = type || "info";
|
||||
if (window.GBAlerts) {
|
||||
if (type === "success") {
|
||||
window.GBAlerts.info("Chat", message);
|
||||
} else if (type === "error") {
|
||||
window.GBAlerts.warning("Chat", message);
|
||||
} else {
|
||||
window.GBAlerts.info("Chat", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// CONFIGURATION
|
||||
// ==========================================================================
|
||||
var WS_BASE_URL =
|
||||
window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
var WS_URL = WS_BASE_URL + window.location.host;
|
||||
|
||||
var MessageType = {
|
||||
EXTERNAL: 0,
|
||||
USER: 1,
|
||||
BOT_RESPONSE: 2,
|
||||
|
|
@ -15,76 +56,79 @@
|
|||
CONTEXT_CHANGE: 5,
|
||||
};
|
||||
|
||||
// State
|
||||
let ws = null,
|
||||
currentSessionId = null,
|
||||
currentUserId = null,
|
||||
currentBotId = "default";
|
||||
let isStreaming = false,
|
||||
streamingMessageId = null,
|
||||
// ==========================================================================
|
||||
// STATE
|
||||
// ==========================================================================
|
||||
var ws = null;
|
||||
var currentSessionId = null;
|
||||
var currentUserId = null;
|
||||
var currentBotId = "default";
|
||||
var isStreaming = false;
|
||||
var streamingMessageId = null;
|
||||
var currentStreamingContent = "";
|
||||
var reconnectAttempts = 0;
|
||||
var maxReconnectAttempts = 5;
|
||||
|
||||
// ==========================================================================
|
||||
// MESSAGE FUNCTIONS
|
||||
// ==========================================================================
|
||||
function escapeHtml(text) {
|
||||
var div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function addMessage(sender, content, msgId) {
|
||||
var messages = document.getElementById("messages");
|
||||
if (!messages) return;
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.className = "message " + sender;
|
||||
if (msgId) div.id = msgId;
|
||||
|
||||
if (sender === "user") {
|
||||
div.innerHTML =
|
||||
'<div class="message-content user-message">' +
|
||||
escapeHtml(content) +
|
||||
"</div>";
|
||||
} else {
|
||||
var parsed =
|
||||
typeof marked !== "undefined" && marked.parse
|
||||
? marked.parse(content)
|
||||
: escapeHtml(content);
|
||||
div.innerHTML =
|
||||
'<div class="message-content bot-message">' +
|
||||
parsed +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
messages.appendChild(div);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
}
|
||||
|
||||
function updateStreaming(content) {
|
||||
var el = document.getElementById(streamingMessageId);
|
||||
if (el) {
|
||||
var parsed =
|
||||
typeof marked !== "undefined" && marked.parse
|
||||
? marked.parse(content)
|
||||
: escapeHtml(content);
|
||||
el.querySelector(".message-content").innerHTML = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeStreaming() {
|
||||
var el = document.getElementById(streamingMessageId);
|
||||
if (el) {
|
||||
var parsed =
|
||||
typeof marked !== "undefined" && marked.parse
|
||||
? marked.parse(currentStreamingContent)
|
||||
: escapeHtml(currentStreamingContent);
|
||||
el.querySelector(".message-content").innerHTML = parsed;
|
||||
el.removeAttribute("id");
|
||||
}
|
||||
streamingMessageId = null;
|
||||
currentStreamingContent = "";
|
||||
let reconnectAttempts = 0;
|
||||
const maxReconnectAttempts = 5;
|
||||
|
||||
// Initialize auth and WebSocket
|
||||
async function initChat() {
|
||||
try {
|
||||
updateConnectionStatus("connecting");
|
||||
const botName = "default";
|
||||
// Use the botui proxy for auth (handles SSL cert issues)
|
||||
const response = await fetch(
|
||||
`/api/auth?bot_name=${encodeURIComponent(botName)}`,
|
||||
);
|
||||
const auth = await response.json();
|
||||
currentUserId = auth.user_id;
|
||||
currentSessionId = auth.session_id;
|
||||
currentBotId = auth.bot_id || "default";
|
||||
console.log("Auth:", {
|
||||
currentUserId,
|
||||
currentSessionId,
|
||||
currentBotId,
|
||||
});
|
||||
connectWebSocket();
|
||||
} catch (e) {
|
||||
console.error("Auth failed:", e);
|
||||
updateConnectionStatus("disconnected");
|
||||
setTimeout(initChat, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function connectWebSocket() {
|
||||
if (ws) ws.close();
|
||||
// Use the botui proxy for WebSocket (handles SSL cert issues)
|
||||
const url = `${WS_URL}/ws?session_id=${currentSessionId}&user_id=${currentUserId}`;
|
||||
ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket 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) {
|
||||
processMessage(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("WS message error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
updateConnectionStatus("disconnected");
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
reconnectAttempts++;
|
||||
setTimeout(connectWebSocket, 1000 * reconnectAttempts);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (e) => console.error("WebSocket error:", e);
|
||||
}
|
||||
|
||||
function processMessage(data) {
|
||||
|
|
@ -100,7 +144,11 @@
|
|||
isStreaming = true;
|
||||
streamingMessageId = "streaming-" + Date.now();
|
||||
currentStreamingContent = data.content || "";
|
||||
addMessage("bot", currentStreamingContent, streamingMessageId);
|
||||
addMessage(
|
||||
"bot",
|
||||
currentStreamingContent,
|
||||
streamingMessageId,
|
||||
);
|
||||
} else {
|
||||
currentStreamingContent += data.content || "";
|
||||
updateStreaming(currentStreamingContent);
|
||||
|
|
@ -108,47 +156,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
function addMessage(sender, content, msgId = null) {
|
||||
const messages = document.getElementById("messages");
|
||||
const div = document.createElement("div");
|
||||
div.className = `message ${sender}`;
|
||||
if (msgId) div.id = msgId;
|
||||
|
||||
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>`;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
function finalizeStreaming() {
|
||||
const el = document.getElementById(streamingMessageId);
|
||||
if (el) {
|
||||
el.querySelector(".message-content").innerHTML = marked.parse(
|
||||
currentStreamingContent,
|
||||
);
|
||||
el.removeAttribute("id");
|
||||
}
|
||||
streamingMessageId = null;
|
||||
currentStreamingContent = "";
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// SEND MESSAGE - GLOBAL FUNCTION
|
||||
// ==========================================================================
|
||||
function sendMessage() {
|
||||
const input = document.getElementById("messageInput");
|
||||
const content = input.value.trim();
|
||||
if (!content || !ws || ws.readyState !== WebSocket.OPEN) return;
|
||||
var input = document.getElementById("messageInput");
|
||||
if (!input) {
|
||||
console.error("Chat input not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var content = input.value.trim();
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always show user message locally
|
||||
addMessage("user", content);
|
||||
input.value = "";
|
||||
input.focus();
|
||||
|
||||
// Try to send via WebSocket if connected
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
bot_id: currentBotId,
|
||||
|
|
@ -160,78 +189,124 @@
|
|||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
|
||||
input.value = "";
|
||||
input.focus();
|
||||
} else {
|
||||
notify("Not connected to server. Message not sent.", "warning");
|
||||
}
|
||||
}
|
||||
|
||||
function updateConnectionStatus(status) {
|
||||
const el = document.getElementById("connectionStatus");
|
||||
if (el) el.className = `connection-status ${status}`;
|
||||
// Make sendMessage globally available immediately
|
||||
window.sendMessage = sendMessage;
|
||||
|
||||
// ==========================================================================
|
||||
// WEBSOCKET CONNECTION
|
||||
// ==========================================================================
|
||||
function connectWebSocket() {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
var url =
|
||||
WS_URL +
|
||||
"/ws?session_id=" +
|
||||
currentSessionId +
|
||||
"&user_id=" +
|
||||
currentUserId;
|
||||
ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = function () {
|
||||
console.log("WebSocket connected");
|
||||
reconnectAttempts = 0;
|
||||
};
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
if (data.type === "connected") return;
|
||||
if (data.message_type === MessageType.BOT_RESPONSE) {
|
||||
processMessage(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("WS message error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function () {
|
||||
notify("Disconnected from chat server", "error");
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
reconnectAttempts++;
|
||||
setTimeout(connectWebSocket, 1000 * reconnectAttempts);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = function (e) {
|
||||
console.error("WebSocket error:", e);
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize chat - runs immediately when script is executed
|
||||
// (works both on full page load and HTMX partial load)
|
||||
function setupChat() {
|
||||
const input = document.getElementById("messageInput");
|
||||
const sendBtn = document.getElementById("sendBtn");
|
||||
// ==========================================================================
|
||||
// INITIALIZATION
|
||||
// ==========================================================================
|
||||
function initChat() {
|
||||
var botName = "default";
|
||||
fetch("/api/auth?bot_name=" + encodeURIComponent(botName))
|
||||
.then(function (response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function (auth) {
|
||||
currentUserId = auth.user_id;
|
||||
currentSessionId = auth.session_id;
|
||||
currentBotId = auth.bot_id || "default";
|
||||
console.log("Auth:", {
|
||||
currentUserId: currentUserId,
|
||||
currentSessionId: currentSessionId,
|
||||
currentBotId: currentBotId,
|
||||
});
|
||||
connectWebSocket();
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error("Auth failed:", e);
|
||||
notify("Failed to connect to chat server", "error");
|
||||
setTimeout(initChat, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function setupEventHandlers() {
|
||||
var form = document.getElementById("chatForm");
|
||||
var input = document.getElementById("messageInput");
|
||||
var sendBtn = document.getElementById("sendBtn");
|
||||
|
||||
if (form) {
|
||||
form.onsubmit = function (e) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
if (sendBtn) sendBtn.onclick = sendMessage;
|
||||
if (input) {
|
||||
input.addEventListener("keypress", (e) => {
|
||||
if (e.key === "Enter") sendMessage();
|
||||
});
|
||||
input.onkeydown = function (e) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (sendBtn) {
|
||||
sendBtn.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Run setup immediately
|
||||
setupEventHandlers();
|
||||
initChat();
|
||||
}
|
||||
|
||||
// Initialize after a micro-delay to ensure DOM is ready
|
||||
// This works for both full page loads and HTMX partial loads
|
||||
setTimeout(() => {
|
||||
if (
|
||||
document.getElementById("messageInput") &&
|
||||
!window.chatInitialized
|
||||
) {
|
||||
window.chatInitialized = true;
|
||||
setupChat();
|
||||
}
|
||||
}, 0);
|
||||
|
||||
// Fallback for full page load
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (!window.chatInitialized) {
|
||||
window.chatInitialized = true;
|
||||
setupChat();
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
"Chat module initialized, sendMessage is:",
|
||||
typeof window.sendMessage,
|
||||
);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="chat-layout" id="chat-app">
|
||||
<div id="connectionStatus" class="connection-status disconnected"></div>
|
||||
<main id="messages"></main>
|
||||
|
||||
<footer>
|
||||
<div class="suggestions-container" id="suggestions"></div>
|
||||
<div class="input-container">
|
||||
<input
|
||||
name="content"
|
||||
id="messageInput"
|
||||
type="text"
|
||||
placeholder="Message..."
|
||||
autofocus
|
||||
/>
|
||||
<button type="button" id="voiceBtn" title="Voice">🎤</button>
|
||||
<button type="button" id="sendBtn" title="Send">↑</button>
|
||||
</div>
|
||||
</footer>
|
||||
<button class="scroll-to-bottom" id="scrollToBottom">↓</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@
|
|||
/* Header */
|
||||
--header-bg: hsla(var(--background) / 0.8);
|
||||
--header-border: hsla(var(--border) / 0.8);
|
||||
--header-height: 64px;
|
||||
--header-height: 40px;
|
||||
|
||||
/* Input Fields */
|
||||
--input-bg: hsl(var(--input));
|
||||
|
|
@ -296,11 +296,11 @@ body {
|
|||
/* LAYOUT STRUCTURE */
|
||||
/* ============================================ */
|
||||
#main-content {
|
||||
height: calc(100vh - 64px);
|
||||
height: calc(100vh - var(--header-height));
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin-top: 64px;
|
||||
margin-top: var(--header-height);
|
||||
}
|
||||
|
||||
.section {
|
||||
|
|
@ -323,14 +323,14 @@ body {
|
|||
left: 0;
|
||||
right: 0;
|
||||
height: var(--header-height);
|
||||
background: var(--header-bg);
|
||||
background: var(--bg, var(--surface, #0a0a0a));
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
border-bottom: 1px solid var(--header-border);
|
||||
border-bottom: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--space-lg);
|
||||
padding: 0 var(--space-md);
|
||||
z-index: var(--z-sticky);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all var(--transition-smooth);
|
||||
|
|
@ -370,29 +370,31 @@ body {
|
|||
.app-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
gap: 4px;
|
||||
padding: 6px 10px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-secondary, #888888);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: all var(--transition-fast);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.app-tab:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
background: var(--surface-hover, var(--bg-hover, rgba(255, 255, 255, 0.1)));
|
||||
color: var(--text, var(--text-primary, #ffffff));
|
||||
}
|
||||
|
||||
.app-tab.active {
|
||||
background: var(--accent-light);
|
||||
color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
background: var(--primary-light, rgba(212, 245, 5, 0.15));
|
||||
color: var(--primary, var(--accent-color, #d4f505));
|
||||
border-color: var(--primary, var(--accent-color, #d4f505));
|
||||
}
|
||||
|
||||
.app-tab svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
|
@ -406,10 +408,11 @@ body {
|
|||
.header-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: 8px 14px;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
gap: var(--space-xs);
|
||||
padding: 4px 12px;
|
||||
height: 28px;
|
||||
background: var(--surface, var(--glass-bg, rgba(255, 255, 255, 0.05)));
|
||||
border: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
border-radius: var(--radius-full);
|
||||
width: 100%;
|
||||
transition: all var(--transition-fast);
|
||||
|
|
@ -430,7 +433,7 @@ body {
|
|||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
|
@ -439,11 +442,11 @@ body {
|
|||
}
|
||||
|
||||
.search-shortcut {
|
||||
padding: 2px 6px;
|
||||
padding: 2px 5px;
|
||||
background: var(--bg-hover);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
|
@ -475,13 +478,13 @@ body {
|
|||
.logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
gap: var(--space-xs);
|
||||
cursor: pointer;
|
||||
padding: var(--space-sm);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition-fast);
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.logo-wrapper:hover {
|
||||
|
|
@ -491,11 +494,10 @@ body {
|
|||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: url("https://pragmatismo.com.br/icons/general-bots.svg")
|
||||
center/contain no-repeat;
|
||||
border-radius: var(--radius-sm);
|
||||
width: 32px;
|
||||
height: 20px;
|
||||
color: var(--text, var(--text-primary, #ffffff));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
|
|
@ -509,16 +511,16 @@ body {
|
|||
/* ICON BUTTONS (Apps, Theme, User) */
|
||||
/* ============================================ */
|
||||
.icon-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: var(--radius-full);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--glass-bg);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
background: var(--surface, var(--glass-bg, rgba(255, 255, 255, 0.05)));
|
||||
color: var(--text, var(--text-primary, #ffffff));
|
||||
transition: all var(--transition-fast);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
|
@ -534,9 +536,15 @@ body {
|
|||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.icon-button.active {
|
||||
background: var(--accent-dim, rgba(212, 245, 5, 0.15));
|
||||
border-color: var(--accent, var(--accent-color, #d4f505));
|
||||
color: var(--accent, var(--accent-color, #d4f505));
|
||||
}
|
||||
|
||||
.icon-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
|
|
@ -579,12 +587,12 @@ body {
|
|||
.apps-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 60px;
|
||||
right: 0;
|
||||
width: 280px;
|
||||
background: var(--glass-bg);
|
||||
background: var(--surface, var(--bg, #0a0a0a));
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
border: 1px solid var(--glass-border);
|
||||
border: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-xl);
|
||||
padding: var(--space-md);
|
||||
|
|
@ -625,21 +633,21 @@ body {
|
|||
padding: var(--space-md);
|
||||
border-radius: var(--radius-lg);
|
||||
text-decoration: none;
|
||||
color: var(--text-primary);
|
||||
color: var(--text, var(--text-primary, #ffffff));
|
||||
transition: all var(--transition-fast);
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.app-item:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--border-color);
|
||||
background: var(--surface-hover, var(--bg-hover, rgba(255, 255, 255, 0.1)));
|
||||
border-color: var(--border, var(--border-color, #2a2a2a));
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.app-item.active {
|
||||
background: var(--accent-light);
|
||||
border-color: var(--accent-color);
|
||||
background: var(--primary-light, rgba(212, 245, 5, 0.15));
|
||||
border-color: var(--primary, var(--accent-color, #d4f505));
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
|
|
@ -650,15 +658,53 @@ body {
|
|||
.app-item span {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
color: var(--text, var(--text-primary, #ffffff));
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* SETTINGS LINK */
|
||||
/* ============================================ */
|
||||
.settings-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: var(--space-md);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border-top: 1px solid var(--border, var(--border-color, #2a2a2a));
|
||||
color: var(--text, var(--text-primary, #ffffff));
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-md);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.settings-link:hover {
|
||||
background: var(--surface-hover, var(--bg-hover, rgba(255, 255, 255, 0.1)));
|
||||
}
|
||||
|
||||
.settings-link svg {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.settings-link:hover svg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* SETTINGS PANEL THEME FIX */
|
||||
/* ============================================ */
|
||||
.settings-panel {
|
||||
background: var(--surface, var(--bg, #0a0a0a)) !important;
|
||||
border-color: var(--border, var(--border-color, #2a2a2a)) !important;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* USER AVATAR */
|
||||
/* ============================================ */
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--accent-gradient);
|
||||
display: flex;
|
||||
|
|
@ -666,7 +712,7 @@ body {
|
|||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
box-shadow: var(--shadow-sm);
|
||||
|
|
@ -1032,16 +1078,107 @@ body {
|
|||
border-bottom: 1px solid var(--border, #2a2a2a);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notification-item:hover {
|
||||
background: var(--surface-hover, #1e1e1e);
|
||||
}
|
||||
|
||||
.notification-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.notification-item.unread {
|
||||
background: rgba(197, 248, 42, 0.05);
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text, #fff);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, #888);
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notification-time {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted, #666);
|
||||
}
|
||||
|
||||
.notification-action {
|
||||
background: var(--primary, #c5f82a);
|
||||
color: #000;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.notification-action:hover {
|
||||
background: var(--primary-hover, #b5e520);
|
||||
}
|
||||
|
||||
.notification-dismiss {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted, #666);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.notification-dismiss:hover {
|
||||
color: var(--text, #fff);
|
||||
}
|
||||
|
||||
.notification-item.notification-success {
|
||||
border-left: 3px solid var(--success, #22c55e);
|
||||
}
|
||||
|
||||
.notification-item.notification-error {
|
||||
border-left: 3px solid var(--error, #ef4444);
|
||||
}
|
||||
|
||||
.notification-item.notification-warning {
|
||||
border-left: 3px solid var(--warning, #f59e0b);
|
||||
}
|
||||
|
||||
.notification-item.notification-info {
|
||||
border-left: 3px solid var(--primary, #c5f82a);
|
||||
}
|
||||
|
||||
.notification-item.success .notification-item-icon {
|
||||
color: var(--success, #22c55e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,33 @@ body {
|
|||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
border-top: 1px solid var(--border, #2a2a2a);
|
||||
color: var(--text-primary, #ffffff);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-link:hover {
|
||||
background: var(--surface-hover, rgba(255, 255, 255, 0.05));
|
||||
}
|
||||
|
||||
.settings-link svg {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.settings-link:hover svg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* SETTINGS SHORTCUTS */
|
||||
/* ============================================ */
|
||||
|
|
|
|||
|
|
@ -154,6 +154,12 @@
|
|||
border-color: var(--border);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .icon-button.active {
|
||||
color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Notification Badge */
|
||||
[data-theme="sentient"] .notification-badge {
|
||||
background: var(--accent);
|
||||
|
|
@ -756,22 +762,48 @@
|
|||
}
|
||||
|
||||
[data-theme="sentient"] .app-item {
|
||||
color: var(--text-secondary);
|
||||
color: var(--text);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item span {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item .app-icon {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item .app-icon svg {
|
||||
stroke: var(--text);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item:hover {
|
||||
background: var(--surface-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item:hover span,
|
||||
[data-theme="sentient"] .app-item:hover .app-icon {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item.active {
|
||||
background: var(--accent-light);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item.active span,
|
||||
[data-theme="sentient"] .app-item.active .app-icon {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
[data-theme="sentient"] .app-item.active .app-icon svg {
|
||||
stroke: var(--accent);
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* FORMS & INPUTS */
|
||||
/* ============================================ */
|
||||
|
|
|
|||
|
|
@ -171,26 +171,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drive-toolbar-center">
|
||||
<div class="search-box">
|
||||
<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>
|
||||
<path d="m21 21-4.3-4.3"></path>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
placeholder="Search in Drive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drive-toolbar-center"></div>
|
||||
<div class="drive-toolbar-right">
|
||||
<button
|
||||
class="btn-icon view-toggle"
|
||||
|
|
|
|||
1223
ui/suite/index.html
1223
ui/suite/index.html
File diff suppressed because it is too large
Load diff
|
|
@ -85,11 +85,9 @@ document.addEventListener("keydown", (e) => {
|
|||
|
||||
document.body.addEventListener("htmx:afterSwap", (e) => {
|
||||
if (e.detail.target.id === "main-content") {
|
||||
const hash = window.location.hash || "#chat";
|
||||
document.querySelectorAll(".app-item").forEach((item) => {
|
||||
item.classList.toggle("active", item.getAttribute("href") === hash);
|
||||
});
|
||||
// Close settings panel on navigation
|
||||
if (settingsPanel) settingsPanel.classList.remove("show");
|
||||
// Note: Active class handling is done in htmx-app.js updateActiveNav()
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -214,22 +214,112 @@
|
|||
updateActiveNav(path);
|
||||
});
|
||||
|
||||
// Also listen for htmx:afterSwap to catch all navigation
|
||||
document.addEventListener("htmx:afterSwap", (event) => {
|
||||
setTimeout(() => {
|
||||
const path = window.location.hash || window.location.pathname;
|
||||
updateActiveNav(path);
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// Handle hash change
|
||||
window.addEventListener("hashchange", (event) => {
|
||||
updateActiveNav(window.location.hash);
|
||||
});
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener("popstate", (event) => {
|
||||
updateActiveNav(window.location.pathname);
|
||||
updateActiveNav(window.location.hash || window.location.pathname);
|
||||
});
|
||||
|
||||
// Handle direct clicks on app tabs and app items
|
||||
document.addEventListener("click", (event) => {
|
||||
const appTab = event.target.closest(".app-tab");
|
||||
const appItem = event.target.closest(".app-item");
|
||||
|
||||
if (appTab || appItem) {
|
||||
const element = appTab || appItem;
|
||||
const href = element.getAttribute("href");
|
||||
if (href) {
|
||||
// Immediately update active state on click
|
||||
updateActiveNav(href);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update active navigation item
|
||||
// Get current section from URL
|
||||
function getCurrentSection() {
|
||||
const hash = window.location.hash;
|
||||
if (hash && hash.length > 1) {
|
||||
// Handle both #section and /#section formats
|
||||
return hash.replace(/^#\/?/, "").split("/")[0].split("?")[0];
|
||||
}
|
||||
return "chat";
|
||||
}
|
||||
|
||||
// Update active navigation item and page title
|
||||
function updateActiveNav(path) {
|
||||
document.querySelectorAll(".nav-item, .app-item").forEach((item) => {
|
||||
const href = item.getAttribute("href");
|
||||
if (href === path || (path === "/" && href === "/chat")) {
|
||||
item.classList.add("active");
|
||||
} else {
|
||||
// Extract section name from path
|
||||
// Handles: "/#chat", "#chat", "/chat", "chat", "/#paper", "#paper"
|
||||
let section;
|
||||
if (path && path.length > 0) {
|
||||
// Remove leading /, #, or /# combinations
|
||||
section = path
|
||||
.replace(/^[/#]+/, "")
|
||||
.split("/")[0]
|
||||
.split("?")[0];
|
||||
}
|
||||
|
||||
// Fallback to current URL hash if section is empty
|
||||
if (!section) {
|
||||
section = getCurrentSection();
|
||||
}
|
||||
|
||||
// First, remove ALL active classes from all tabs, items, and apps button
|
||||
document.querySelectorAll(".app-tab.active").forEach((item) => {
|
||||
item.classList.remove("active");
|
||||
});
|
||||
document.querySelectorAll(".app-item.active").forEach((item) => {
|
||||
item.classList.remove("active");
|
||||
});
|
||||
|
||||
// Remove active from apps button
|
||||
const appsButton = document.getElementById("appsButton");
|
||||
if (appsButton) {
|
||||
appsButton.classList.remove("active");
|
||||
}
|
||||
|
||||
// Check if section exists in the main header tabs
|
||||
let foundInHeaderTabs = false;
|
||||
document.querySelectorAll(".app-tab").forEach((item) => {
|
||||
const dataSection = item.getAttribute("data-section");
|
||||
const href = item.getAttribute("href");
|
||||
const itemSection = dataSection || (href ? href.replace(/^#/, "") : "");
|
||||
if (itemSection === section) {
|
||||
item.classList.add("active");
|
||||
foundInHeaderTabs = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Update app items in launcher dropdown (always mark the current section)
|
||||
document.querySelectorAll(".app-item").forEach((item) => {
|
||||
const href = item.getAttribute("href");
|
||||
const dataSection = item.getAttribute("data-section");
|
||||
const itemSection = dataSection || (href ? href.replace(/^#/, "") : "");
|
||||
if (itemSection === section) {
|
||||
item.classList.add("active");
|
||||
}
|
||||
});
|
||||
|
||||
// If section is NOT in header tabs, select the apps button instead
|
||||
if (!foundInHeaderTabs && appsButton) {
|
||||
appsButton.classList.add("active");
|
||||
}
|
||||
|
||||
// Update page title
|
||||
const sectionName = section.charAt(0).toUpperCase() + section.slice(1);
|
||||
document.title = sectionName + " - General Bots";
|
||||
}
|
||||
|
||||
// Initialize keyboard shortcuts
|
||||
|
|
@ -314,8 +404,8 @@
|
|||
// Initialize theme
|
||||
initTheme();
|
||||
|
||||
// Set initial active nav
|
||||
updateActiveNav(window.location.pathname);
|
||||
// Set initial active nav based on hash or default to chat
|
||||
updateActiveNav(window.location.hash || "#chat");
|
||||
|
||||
console.log("HTMX application initialized");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<!-- Folder List -->
|
||||
<div class="folders-section">
|
||||
<div class="nav-item active" data-folder="inbox" hx-get="/ui/email/list?folder=inbox" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item active" data-folder="inbox" hx-get="/api/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">
|
||||
<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"/>
|
||||
|
|
@ -20,20 +20,20 @@
|
|||
<span>Inbox</span>
|
||||
<span class="folder-badge unread" id="inbox-count">0</span>
|
||||
</div>
|
||||
<div class="nav-item" data-folder="starred" hx-get="/ui/email/list?folder=starred" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item" data-folder="starred" hx-get="/api/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">
|
||||
<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>
|
||||
<span>Starred</span>
|
||||
</div>
|
||||
<div class="nav-item" data-folder="sent" hx-get="/ui/email/list?folder=sent" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item" data-folder="sent" hx-get="/api/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">
|
||||
<line x1="22" y1="2" x2="11" y2="13"/>
|
||||
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
||||
</svg>
|
||||
<span>Sent</span>
|
||||
</div>
|
||||
<div class="nav-item" data-folder="scheduled" hx-get="/ui/email/list?folder=scheduled" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item" data-folder="scheduled" hx-get="/api/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">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<polyline points="12 6 12 12 16 14"/>
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
<span>Scheduled</span>
|
||||
<span class="folder-badge" id="scheduled-count">0</span>
|
||||
</div>
|
||||
<div class="nav-item" data-folder="drafts" hx-get="/ui/email/list?folder=drafts" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item" data-folder="drafts" hx-get="/api/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">
|
||||
<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"/>
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
</svg>
|
||||
<span>Tracking</span>
|
||||
</div>
|
||||
<div class="nav-item" data-folder="spam" hx-get="/ui/email/list?folder=spam" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item" data-folder="spam" hx-get="/api/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">
|
||||
<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"/>
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
</svg>
|
||||
<span>Spam</span>
|
||||
</div>
|
||||
<div class="nav-item" data-folder="trash" hx-get="/ui/email/list?folder=trash" hx-target="#mail-list" hx-swap="innerHTML">
|
||||
<div class="nav-item" data-folder="trash" hx-get="/api/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">
|
||||
<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"/>
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<div id="accounts-list"
|
||||
hx-get="/ui/email/accounts"
|
||||
hx-get="/api/ui/email/accounts"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Accounts loaded here -->
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="labels-list" hx-get="/ui/email/labels" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div id="labels-list" hx-get="/api/ui/email/labels" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="label-item" style="--label-color: #ef4444;">
|
||||
<span class="label-dot"></span>
|
||||
<span>Important</span>
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
<input type="text" placeholder="Search emails..." id="email-search"
|
||||
hx-get="/ui/email/search" hx-trigger="keyup changed delay:300ms"
|
||||
hx-get="/api/ui/email/search" hx-trigger="keyup changed delay:300ms"
|
||||
hx-target="#mail-list" hx-include="this" name="q"/>
|
||||
</div>
|
||||
<button class="icon-btn" onclick="refreshMailList()" title="Refresh">
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div id="mail-list" hx-get="/ui/email/list?folder=inbox" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div id="mail-list" hx-get="/api/ui/email/list?folder=inbox" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading emails...</p>
|
||||
|
|
@ -421,7 +421,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="templates-list" id="templates-list" hx-get="/ui/email/templates" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="templates-list" id="templates-list" hx-get="/api/ui/email/templates" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="loading-state"><div class="spinner"></div></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
@ -443,7 +443,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="signatures-list" id="signatures-list" hx-get="/ui/email/signatures" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="signatures-list" id="signatures-list" hx-get="/api/ui/email/signatures" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="loading-state"><div class="spinner"></div></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
@ -465,7 +465,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="rules-list" id="rules-list" hx-get="/ui/email/rules" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="rules-list" id="rules-list" hx-get="/api/ui/email/rules" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="loading-state"><div class="spinner"></div></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
@ -487,7 +487,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="autoresponder-form" hx-post="/ui/email/auto-responder" hx-swap="none">
|
||||
<form id="autoresponder-form" hx-post="/api/ui/email/auto-responder" hx-swap="none">
|
||||
<div class="form-group">
|
||||
<label class="toggle-label">
|
||||
<input type="checkbox" name="enabled" id="autoresponder-enabled"/>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
Alert Configuration
|
||||
</h2>
|
||||
<span class="alert-summary"
|
||||
hx-get="/api/monitoring/alerts/summary"
|
||||
hx-get="/api/ui/monitoring/alerts/summary"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<span class="summary-item critical">0 Critical</span>
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
</div>
|
||||
|
||||
<div class="alerts-list" id="alerts-list"
|
||||
hx-get="/api/monitoring/alerts/active"
|
||||
hx-get="/api/ui/monitoring/alerts/active"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="alert-placeholder">
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
</div>
|
||||
|
||||
<div class="rules-grid" id="rules-grid"
|
||||
hx-get="/api/monitoring/alerts/rules"
|
||||
hx-get="/api/ui/monitoring/alerts/rules"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="rule-card skeleton">
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
</div>
|
||||
|
||||
<div class="history-list" id="history-list"
|
||||
hx-get="/api/monitoring/alerts/history"
|
||||
hx-get="/api/ui/monitoring/alerts/history"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="loading-state">
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<form id="create-alert-form"
|
||||
hx-post="/api/monitoring/alerts/rules"
|
||||
hx-post="/api/ui/monitoring/alerts/rules"
|
||||
hx-target="#rules-grid"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="closeCreateAlertModal()">
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ function acknowledgeAlert(alertId) {
|
|||
|
||||
function acknowledgeAllAlerts() {
|
||||
if (confirm('Are you sure you want to acknowledge all active alerts?')) {
|
||||
htmx.ajax('POST', '/api/monitoring/alerts/acknowledge-all', {
|
||||
htmx.ajax('POST', '/api/ui/monitoring/alerts/acknowledge-all', {
|
||||
swap: 'none'
|
||||
}).then(() => {
|
||||
htmx.trigger('#alerts-list', 'refresh');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="health-container">
|
||||
<!-- Health Overview -->
|
||||
<div class="health-overview"
|
||||
hx-get="/api/monitoring/health/overview"
|
||||
hx-get="/api/ui/monitoring/health/overview"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="health-status healthy">
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
<!-- Uptime Stats -->
|
||||
<div class="uptime-stats">
|
||||
<div class="stat-card"
|
||||
hx-get="/api/monitoring/health/uptime"
|
||||
hx-get="/api/ui/monitoring/health/uptime"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="stat-icon">
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="stat-card"
|
||||
hx-get="/api/monitoring/health/uptime-percent"
|
||||
hx-get="/api/ui/monitoring/health/uptime-percent"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="stat-icon success">
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="stat-card"
|
||||
hx-get="/api/monitoring/health/last-incident"
|
||||
hx-get="/api/ui/monitoring/health/last-incident"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="stat-icon warning">
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="stat-card"
|
||||
hx-get="/api/monitoring/health/response-time"
|
||||
hx-get="/api/ui/monitoring/health/response-time"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="stat-icon info">
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
</div>
|
||||
|
||||
<div class="health-checks-grid" id="health-checks"
|
||||
hx-get="/api/monitoring/health/checks"
|
||||
hx-get="/api/ui/monitoring/health/checks"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="health-check-card">
|
||||
|
|
@ -200,7 +200,7 @@
|
|||
</div>
|
||||
|
||||
<div class="dependencies-list" id="dependencies"
|
||||
hx-get="/api/monitoring/health/dependencies"
|
||||
hx-get="/api/ui/monitoring/health/dependencies"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="dependency-row">
|
||||
|
|
@ -277,7 +277,7 @@
|
|||
</div>
|
||||
|
||||
<div class="uptime-chart" id="uptime-chart"
|
||||
hx-get="/api/monitoring/health/uptime-history"
|
||||
hx-get="/api/ui/monitoring/health/uptime-history"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="uptime-bars">
|
||||
|
|
@ -319,7 +319,7 @@
|
|||
</div>
|
||||
|
||||
<div class="incidents-list" id="incidents"
|
||||
hx-get="/api/monitoring/health/incidents"
|
||||
hx-get="/api/ui/monitoring/health/incidents"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="incident-placeholder">
|
||||
|
|
|
|||
|
|
@ -246,11 +246,11 @@
|
|||
<rect x="0" y="0" width="160" height="85" rx="10" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<text x="12" y="24" fill="#64748b" font-family="system-ui" font-size="10" font-weight="500">ACTIVE SESSIONS</text>
|
||||
<text x="12" y="58" fill="#f8fafc" font-family="system-ui" font-size="32" font-weight="700"
|
||||
hx-get="/api/monitoring/metric/sessions"
|
||||
hx-get="/api/ui/monitoring/metric/sessions"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">--</text>
|
||||
<text x="148" y="70" fill="#10b981" font-family="system-ui" font-size="11" text-anchor="end"
|
||||
hx-get="/api/monitoring/trend/sessions"
|
||||
hx-get="/api/ui/monitoring/trend/sessions"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">↑ 0%</text>
|
||||
</g>
|
||||
|
|
@ -260,11 +260,11 @@
|
|||
<rect x="0" y="0" width="160" height="85" rx="10" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<text x="12" y="24" fill="#64748b" font-family="system-ui" font-size="10" font-weight="500">MESSAGES TODAY</text>
|
||||
<text x="12" y="58" fill="#f8fafc" font-family="system-ui" font-size="32" font-weight="700"
|
||||
hx-get="/api/monitoring/metric/messages"
|
||||
hx-get="/api/ui/monitoring/metric/messages"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">--</text>
|
||||
<text x="148" y="70" fill="#94a3b8" font-family="system-ui" font-size="11" text-anchor="end"
|
||||
hx-get="/api/monitoring/rate/messages"
|
||||
hx-get="/api/ui/monitoring/rate/messages"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">0/hr</text>
|
||||
</g>
|
||||
|
|
@ -274,7 +274,7 @@
|
|||
<rect x="0" y="0" width="160" height="85" rx="10" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<text x="12" y="24" fill="#64748b" font-family="system-ui" font-size="10" font-weight="500">AVG RESPONSE</text>
|
||||
<text x="12" y="58" fill="#f8fafc" font-family="system-ui" font-size="32" font-weight="700"
|
||||
hx-get="/api/monitoring/metric/response_time"
|
||||
hx-get="/api/ui/monitoring/metric/response_time"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">--</text>
|
||||
<text x="148" y="70" fill="#94a3b8" font-family="system-ui" font-size="11" text-anchor="end">ms</text>
|
||||
|
|
@ -283,7 +283,7 @@
|
|||
<!-- ==================== RESOURCE BARS ==================== -->
|
||||
|
||||
<g transform="translate(320, 580)" class="resource-bars"
|
||||
hx-get="/api/monitoring/resources/bars"
|
||||
hx-get="/api/ui/monitoring/resources/bars"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="innerHTML">
|
||||
<!-- CPU -->
|
||||
|
|
@ -325,7 +325,7 @@
|
|||
<rect x="0" y="0" width="520" height="40" rx="8" fill="#1e293b" stroke="#334155" stroke-width="1"/>
|
||||
<circle cx="18" cy="20" r="5" fill="#10b981" class="pulse-dot"/>
|
||||
<text x="34" y="25" fill="#94a3b8" font-family="system-ui" font-size="12"
|
||||
hx-get="/api/monitoring/activity/latest"
|
||||
hx-get="/api/ui/monitoring/activity/latest"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">System monitoring active...</text>
|
||||
</g>
|
||||
|
|
@ -335,7 +335,7 @@
|
|||
<g transform="translate(600, 45)">
|
||||
<text text-anchor="middle" fill="#f8fafc" font-family="system-ui" font-size="22" font-weight="600">Live System Monitor</text>
|
||||
<text y="24" text-anchor="middle" fill="#64748b" font-family="system-ui" font-size="12"
|
||||
hx-get="/api/monitoring/timestamp"
|
||||
hx-get="/api/ui/monitoring/timestamp"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">Last updated: --</text>
|
||||
</g>
|
||||
|
|
@ -361,7 +361,7 @@
|
|||
|
||||
<!-- Hidden service status updater -->
|
||||
<div id="service-status-container" style="display: none;"
|
||||
hx-get="/api/monitoring/services"
|
||||
hx-get="/api/ui/monitoring/services"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,23 @@
|
|||
<!-- Sidebar Navigation -->
|
||||
<aside class="monitoring-sidebar">
|
||||
<div class="monitoring-header">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" opacity="0.3"></circle>
|
||||
<circle cx="12" cy="12" r="6.5" opacity="0.6"></circle>
|
||||
<circle cx="12" cy="12" r="2" fill="currentColor" stroke="none"></circle>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="2"
|
||||
fill="currentColor"
|
||||
stroke="none"
|
||||
></circle>
|
||||
<line x1="12" y1="2" x2="12" y2="5"></line>
|
||||
<line x1="12" y1="19" x2="12" y2="22"></line>
|
||||
<line x1="2" y1="12" x2="5" y2="12"></line>
|
||||
|
|
@ -15,13 +28,23 @@
|
|||
</div>
|
||||
|
||||
<nav class="monitoring-nav">
|
||||
<a href="#dashboard" class="nav-item active"
|
||||
hx-get="/api/monitoring/dashboard"
|
||||
<a
|
||||
href="#dashboard"
|
||||
class="nav-item active"
|
||||
hx-get="/api/ui/monitoring/dashboard"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="3" width="7" height="9"></rect>
|
||||
<rect x="14" y="3" width="7" height="5"></rect>
|
||||
<rect x="14" y="12" width="7" height="9"></rect>
|
||||
|
|
@ -29,27 +52,61 @@
|
|||
</svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="#services" class="nav-item"
|
||||
hx-get="/api/monitoring/services"
|
||||
<a
|
||||
href="#services"
|
||||
class="nav-item"
|
||||
hx-get="/api/ui/monitoring/services"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect>
|
||||
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect>
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="2"
|
||||
width="20"
|
||||
height="8"
|
||||
rx="2"
|
||||
ry="2"
|
||||
></rect>
|
||||
<rect
|
||||
x="2"
|
||||
y="14"
|
||||
width="20"
|
||||
height="8"
|
||||
rx="2"
|
||||
ry="2"
|
||||
></rect>
|
||||
<line x1="6" y1="6" x2="6.01" y2="6"></line>
|
||||
<line x1="6" y1="18" x2="6.01" y2="18"></line>
|
||||
</svg>
|
||||
<span>Services</span>
|
||||
</a>
|
||||
<a href="#resources" class="nav-item"
|
||||
hx-get="/api/monitoring/resources"
|
||||
<a
|
||||
href="#resources"
|
||||
class="nav-item"
|
||||
hx-get="/api/ui/monitoring/resources"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"></rect>
|
||||
<rect x="9" y="9" width="6" height="6"></rect>
|
||||
<line x1="9" y1="1" x2="9" y2="4"></line>
|
||||
|
|
@ -63,14 +120,26 @@
|
|||
</svg>
|
||||
<span>Resources</span>
|
||||
</a>
|
||||
<a href="#logs" class="nav-item"
|
||||
hx-get="/api/monitoring/logs"
|
||||
<a
|
||||
href="#logs"
|
||||
class="nav-item"
|
||||
hx-get="/api/ui/monitoring/logs"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<svg width="18" height="18" 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>
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
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>
|
||||
<polyline points="14 2 14 8 20 8"></polyline>
|
||||
<line x1="16" y1="13" x2="8" y2="13"></line>
|
||||
<line x1="16" y1="17" x2="8" y2="17"></line>
|
||||
|
|
@ -78,55 +147,100 @@
|
|||
</svg>
|
||||
<span>Logs</span>
|
||||
</a>
|
||||
<a href="#metrics" class="nav-item"
|
||||
hx-get="/api/monitoring/metrics"
|
||||
<a
|
||||
href="#metrics"
|
||||
class="nav-item"
|
||||
hx-get="/api/ui/monitoring/metrics"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<line x1="18" y1="20" x2="18" y2="10"></line>
|
||||
<line x1="12" y1="20" x2="12" y2="4"></line>
|
||||
<line x1="6" y1="20" x2="6" y2="14"></line>
|
||||
</svg>
|
||||
<span>Metrics</span>
|
||||
</a>
|
||||
<a href="#alerts" class="nav-item"
|
||||
hx-get="/api/monitoring/alerts"
|
||||
<a
|
||||
href="#alerts"
|
||||
class="nav-item"
|
||||
hx-get="/api/ui/monitoring/alerts"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<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>
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<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>
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
||||
</svg>
|
||||
<span>Alerts</span>
|
||||
<span class="alert-badge" id="alert-count"
|
||||
hx-get="/api/monitoring/alerts/count"
|
||||
<span
|
||||
class="alert-badge"
|
||||
id="alert-count"
|
||||
hx-get="/api/ui/monitoring/alerts/count"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">0</span>
|
||||
hx-swap="innerHTML"
|
||||
>0</span
|
||||
>
|
||||
</a>
|
||||
<a href="#health" class="nav-item"
|
||||
hx-get="/api/monitoring/health"
|
||||
<a
|
||||
href="#health"
|
||||
class="nav-item"
|
||||
hx-get="/api/ui/monitoring/health"
|
||||
hx-target="#monitoring-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="false"
|
||||
onclick="setActiveNav(this)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
onclick="setActiveNav(this)"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
|
||||
</svg>
|
||||
<span>Health</span>
|
||||
<span class="health-indicator"
|
||||
hx-get="/api/monitoring/health/status"
|
||||
<span
|
||||
class="health-indicator"
|
||||
hx-get="/api/ui/monitoring/health/status"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="outerHTML"></span>
|
||||
hx-swap="outerHTML"
|
||||
></span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="monitoring-footer">
|
||||
<a href="/suite" class="back-link">
|
||||
<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="19" y1="12" x2="5" y2="12"></line>
|
||||
<polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
|
|
@ -134,10 +248,13 @@
|
|||
</a>
|
||||
<div class="system-status">
|
||||
<span class="status-indicator running"></span>
|
||||
<span class="status-text"
|
||||
hx-get="/api/monitoring/system/status"
|
||||
<span
|
||||
class="status-text"
|
||||
hx-get="/api/ui/monitoring/system/status"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="innerHTML">All Systems Operational</span>
|
||||
hx-swap="innerHTML"
|
||||
>All Systems Operational</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
|
@ -147,14 +264,20 @@
|
|||
<div class="monitoring-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<h1 id="page-title">Dashboard</h1>
|
||||
<span class="last-updated"
|
||||
hx-get="/api/monitoring/timestamp"
|
||||
<span
|
||||
class="last-updated"
|
||||
hx-get="/api/ui/monitoring/timestamp"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">--</span>
|
||||
hx-swap="innerHTML"
|
||||
>--</span
|
||||
>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="time-range-selector">
|
||||
<select id="time-range" onchange="updateTimeRange(this.value)">
|
||||
<select
|
||||
id="time-range"
|
||||
onchange="updateTimeRange(this.value)"
|
||||
>
|
||||
<option value="15m">Last 15 minutes</option>
|
||||
<option value="1h" selected>Last 1 hour</option>
|
||||
<option value="6h">Last 6 hours</option>
|
||||
|
|
@ -163,28 +286,78 @@
|
|||
<option value="30d">Last 30 days</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="toolbar-btn" onclick="refreshMonitoring()" title="Refresh">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
onclick="refreshMonitoring()"
|
||||
title="Refresh"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<polyline points="23 4 23 10 17 10"></polyline>
|
||||
<polyline points="1 20 1 14 7 14"></polyline>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||||
<path
|
||||
d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" onclick="toggleAutoRefresh()" id="auto-refresh-btn" title="Auto Refresh">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
onclick="toggleAutoRefresh()"
|
||||
id="auto-refresh-btn"
|
||||
title="Auto Refresh"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="toolbar-btn" onclick="exportData()" title="Export">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
onclick="exportData()"
|
||||
title="Export"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
|
||||
></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="/metrics" target="_blank" class="toolbar-btn" title="Prometheus Metrics">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<a
|
||||
href="/metrics"
|
||||
target="_blank"
|
||||
class="toolbar-btn"
|
||||
title="Prometheus Metrics"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||
|
|
@ -193,10 +366,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="monitoring-content" id="monitoring-content"
|
||||
hx-get="/api/monitoring/dashboard"
|
||||
<div
|
||||
class="monitoring-content"
|
||||
id="monitoring-content"
|
||||
hx-get="/api/ui/monitoring/dashboard"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<!-- Dashboard content loaded via HTMX -->
|
||||
<div class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
|
|
@ -208,22 +384,40 @@
|
|||
|
||||
<!-- Quick Stats Bar (always visible) -->
|
||||
<div class="quick-stats-bar">
|
||||
<div class="quick-stat"
|
||||
hx-get="/api/monitoring/quick/cpu"
|
||||
<div
|
||||
class="quick-stat"
|
||||
hx-get="/api/ui/monitoring/quick/cpu"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"></rect>
|
||||
<rect x="9" y="9" width="6" height="6"></rect>
|
||||
</svg>
|
||||
<span class="stat-label">CPU</span>
|
||||
<span class="stat-value">--%</span>
|
||||
</div>
|
||||
<div class="quick-stat"
|
||||
hx-get="/api/monitoring/quick/memory"
|
||||
<div
|
||||
class="quick-stat"
|
||||
hx-get="/api/ui/monitoring/quick/memory"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="2" y="6" width="20" height="12" rx="2"></rect>
|
||||
<line x1="6" y1="12" x2="6" y2="12"></line>
|
||||
<line x1="10" y1="12" x2="10" y2="12"></line>
|
||||
|
|
@ -233,11 +427,20 @@
|
|||
<span class="stat-label">Memory</span>
|
||||
<span class="stat-value">--%</span>
|
||||
</div>
|
||||
<div class="quick-stat"
|
||||
hx-get="/api/monitoring/quick/disk"
|
||||
<div
|
||||
class="quick-stat"
|
||||
hx-get="/api/ui/monitoring/quick/disk"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
||||
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
||||
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
||||
|
|
@ -245,11 +448,20 @@
|
|||
<span class="stat-label">Disk</span>
|
||||
<span class="stat-value">--%</span>
|
||||
</div>
|
||||
<div class="quick-stat"
|
||||
hx-get="/api/monitoring/quick/network"
|
||||
<div
|
||||
class="quick-stat"
|
||||
hx-get="/api/ui/monitoring/quick/network"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M5 12.55a11 11 0 0 1 14.08 0"></path>
|
||||
<path d="M1.42 9a16 16 0 0 1 21.16 0"></path>
|
||||
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path>
|
||||
|
|
@ -258,11 +470,20 @@
|
|||
<span class="stat-label">Network</span>
|
||||
<span class="stat-value">-- MB/s</span>
|
||||
</div>
|
||||
<div class="quick-stat"
|
||||
hx-get="/api/monitoring/quick/requests"
|
||||
<div
|
||||
class="quick-stat"
|
||||
hx-get="/api/ui/monitoring/quick/requests"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
|
||||
</svg>
|
||||
<span class="stat-label">Requests</span>
|
||||
|
|
@ -270,5 +491,5 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="/static/suite/monitoring/monitoring.css">
|
||||
<link rel="stylesheet" href="/static/suite/monitoring/monitoring.css" />
|
||||
<script src="/static/suite/monitoring/monitoring.js"></script>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<div class="filter-group">
|
||||
<label for="service-filter">Service</label>
|
||||
<select id="service-filter" onchange="applyLogFilters()"
|
||||
hx-get="/api/monitoring/logs/services"
|
||||
hx-get="/api/ui/monitoring/logs/services"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<option value="all">All Services</option>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
Metrics Dashboard
|
||||
</h2>
|
||||
<span class="last-sync"
|
||||
hx-get="/api/monitoring/metrics/last-sync"
|
||||
hx-get="/api/ui/monitoring/metrics/last-sync"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">Last sync: --</span>
|
||||
</div>
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
<!-- Key Metrics Overview -->
|
||||
<div class="key-metrics"
|
||||
hx-get="/api/analytics/dashboard"
|
||||
hx-get="/api/ui/analytics/dashboard"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="metric-card">
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="chart-body" id="requests-chart"
|
||||
hx-get="/api/analytics/metric?name=requests"
|
||||
hx-get="/api/ui/analytics/metric?name=requests"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<svg viewBox="0 0 600 200" class="chart-svg">
|
||||
|
|
@ -207,7 +207,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="chart-body" id="latency-chart"
|
||||
hx-get="/api/analytics/metric?name=latency"
|
||||
hx-get="/api/ui/analytics/metric?name=latency"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<svg viewBox="0 0 600 200" class="chart-svg">
|
||||
|
|
@ -247,7 +247,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="chart-body" id="errors-chart"
|
||||
hx-get="/api/analytics/metric?name=errors"
|
||||
hx-get="/api/ui/analytics/metric?name=errors"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<svg viewBox="0 0 600 200" class="chart-svg">
|
||||
|
|
@ -289,7 +289,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="chart-body" id="throughput-chart"
|
||||
hx-get="/api/analytics/metric?name=throughput"
|
||||
hx-get="/api/ui/analytics/metric?name=throughput"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<svg viewBox="0 0 600 200" class="chart-svg">
|
||||
|
|
@ -356,7 +356,7 @@
|
|||
</div>
|
||||
<div class="metrics-table-container">
|
||||
<table class="metrics-table" id="metrics-table"
|
||||
hx-get="/api/analytics/metrics/list"
|
||||
hx-get="/api/ui/analytics/metrics/list"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<thead>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
</button>
|
||||
<span
|
||||
class="last-updated"
|
||||
hx-get="/api/monitoring/timestamp"
|
||||
hx-get="/api/ui/monitoring/timestamp"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML"
|
||||
>--</span
|
||||
|
|
@ -778,7 +778,7 @@
|
|||
font-family="system-ui"
|
||||
font-size="28"
|
||||
font-weight="700"
|
||||
hx-get="/api/monitoring/metric/sessions"
|
||||
hx-get="/api/ui/monitoring/metric/sessions"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -791,7 +791,7 @@
|
|||
font-family="system-ui"
|
||||
font-size="10"
|
||||
text-anchor="end"
|
||||
hx-get="/api/monitoring/trend/sessions"
|
||||
hx-get="/api/ui/monitoring/trend/sessions"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -828,7 +828,7 @@
|
|||
font-family="system-ui"
|
||||
font-size="28"
|
||||
font-weight="700"
|
||||
hx-get="/api/monitoring/metric/messages"
|
||||
hx-get="/api/ui/monitoring/metric/messages"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -841,7 +841,7 @@
|
|||
font-family="system-ui"
|
||||
font-size="10"
|
||||
text-anchor="end"
|
||||
hx-get="/api/monitoring/rate/messages"
|
||||
hx-get="/api/ui/monitoring/rate/messages"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -878,7 +878,7 @@
|
|||
font-family="system-ui"
|
||||
font-size="28"
|
||||
font-weight="700"
|
||||
hx-get="/api/monitoring/metric/response_time"
|
||||
hx-get="/api/ui/monitoring/metric/response_time"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -900,7 +900,7 @@
|
|||
|
||||
<g
|
||||
transform="translate(330, 545)"
|
||||
hx-get="/api/monitoring/resources/bars"
|
||||
hx-get="/api/ui/monitoring/resources/bars"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1095,7 +1095,7 @@
|
|||
fill="#94a3b8"
|
||||
font-family="system-ui"
|
||||
font-size="11"
|
||||
hx-get="/api/monitoring/activity/latest"
|
||||
hx-get="/api/ui/monitoring/activity/latest"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1151,7 +1151,7 @@
|
|||
<!-- Sessions Panel -->
|
||||
<div
|
||||
class="monitor-panel"
|
||||
hx-get="/api/monitoring/sessions"
|
||||
hx-get="/api/ui/monitoring/sessions"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1189,7 +1189,7 @@
|
|||
<!-- Messages Panel -->
|
||||
<div
|
||||
class="monitor-panel"
|
||||
hx-get="/api/monitoring/messages"
|
||||
hx-get="/api/ui/monitoring/messages"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1220,7 +1220,7 @@
|
|||
<!-- Resources Panel -->
|
||||
<div
|
||||
class="monitor-panel resources-panel"
|
||||
hx-get="/api/monitoring/resources"
|
||||
hx-get="/api/ui/monitoring/resources"
|
||||
hx-trigger="load, every 15s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1271,7 +1271,7 @@
|
|||
<!-- Services Panel -->
|
||||
<div
|
||||
class="monitor-panel services-panel"
|
||||
hx-get="/api/monitoring/services"
|
||||
hx-get="/api/ui/monitoring/services"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1324,7 +1324,7 @@
|
|||
<!-- Bots Panel -->
|
||||
<div
|
||||
class="monitor-panel bots-panel"
|
||||
hx-get="/api/monitoring/bots"
|
||||
hx-get="/api/ui/monitoring/bots"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -1369,7 +1369,7 @@
|
|||
<div
|
||||
id="service-status-container"
|
||||
style="display: none"
|
||||
hx-get="/api/monitoring/services/status"
|
||||
hx-get="/api/ui/monitoring/services/status"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML"
|
||||
></div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div class="resource-cards">
|
||||
<!-- CPU Card -->
|
||||
<div class="resource-card cpu-card"
|
||||
hx-get="/api/monitoring/resources/cpu"
|
||||
hx-get="/api/ui/monitoring/resources/cpu"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="card-icon">
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
<!-- Memory Card -->
|
||||
<div class="resource-card memory-card"
|
||||
hx-get="/api/monitoring/resources/memory"
|
||||
hx-get="/api/ui/monitoring/resources/memory"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="card-icon">
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
|
||||
<!-- Disk Card -->
|
||||
<div class="resource-card disk-card"
|
||||
hx-get="/api/monitoring/resources/disk"
|
||||
hx-get="/api/ui/monitoring/resources/disk"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="card-icon">
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
|
||||
<!-- Network Card -->
|
||||
<div class="resource-card network-card"
|
||||
hx-get="/api/monitoring/resources/network"
|
||||
hx-get="/api/ui/monitoring/resources/network"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="card-icon">
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="chart-container" id="cpu-chart"
|
||||
hx-get="/api/monitoring/charts/cpu"
|
||||
hx-get="/api/ui/monitoring/charts/cpu"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="chart-placeholder">
|
||||
|
|
@ -173,7 +173,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="chart-container" id="memory-chart"
|
||||
hx-get="/api/monitoring/charts/memory"
|
||||
hx-get="/api/ui/monitoring/charts/memory"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="chart-placeholder">
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="detail-content" id="disk-partitions"
|
||||
hx-get="/api/monitoring/resources/disk/partitions"
|
||||
hx-get="/api/ui/monitoring/resources/disk/partitions"
|
||||
hx-trigger="load, every 60s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="partition-row">
|
||||
|
|
@ -259,7 +259,7 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="detail-content" id="process-list"
|
||||
hx-get="/api/monitoring/resources/processes"
|
||||
hx-get="/api/ui/monitoring/resources/processes"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<table class="process-table">
|
||||
|
|
@ -295,7 +295,7 @@
|
|||
</h3>
|
||||
</div>
|
||||
<div class="detail-content" id="network-interfaces"
|
||||
hx-get="/api/monitoring/resources/network/interfaces"
|
||||
hx-get="/api/ui/monitoring/resources/network/interfaces"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="interface-row">
|
||||
|
|
@ -334,7 +334,7 @@
|
|||
</h3>
|
||||
</div>
|
||||
<div class="detail-content system-info"
|
||||
hx-get="/api/monitoring/resources/system"
|
||||
hx-get="/api/ui/monitoring/resources/system"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="info-row">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div class="sidebar-header">
|
||||
<h2>Paper</h2>
|
||||
<button class="btn-icon" title="New Note"
|
||||
hx-post="/api/paper/new"
|
||||
hx-post="/api/ui/paper/new"
|
||||
hx-target="#paper-list"
|
||||
hx-swap="afterbegin">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<input type="text"
|
||||
placeholder="Search notes..."
|
||||
name="q"
|
||||
hx-get="/api/paper/search"
|
||||
hx-get="/api/ui/paper/search"
|
||||
hx-trigger="keyup changed delay:300ms"
|
||||
hx-target="#paper-list">
|
||||
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<!-- Notes List -->
|
||||
<div class="paper-list" id="paper-list"
|
||||
hx-get="/api/paper/list"
|
||||
hx-get="/api/ui/paper/list"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Notes loaded here -->
|
||||
|
|
@ -41,19 +41,19 @@
|
|||
<div class="sidebar-section">
|
||||
<h3>Quick Start</h3>
|
||||
<div class="template-grid">
|
||||
<button class="template-btn" hx-post="/api/paper/template/blank" hx-target="#editor-content">
|
||||
<button class="template-btn" hx-post="/api/ui/paper/template/blank" hx-target="#editor-content">
|
||||
<span class="template-icon">📄</span>
|
||||
<span>Blank</span>
|
||||
</button>
|
||||
<button class="template-btn" hx-post="/api/paper/template/meeting" hx-target="#editor-content">
|
||||
<button class="template-btn" hx-post="/api/ui/paper/template/meeting" hx-target="#editor-content">
|
||||
<span class="template-icon">📋</span>
|
||||
<span>Meeting</span>
|
||||
</button>
|
||||
<button class="template-btn" hx-post="/api/paper/template/todo" hx-target="#editor-content">
|
||||
<button class="template-btn" hx-post="/api/ui/paper/template/todo" hx-target="#editor-content">
|
||||
<span class="template-icon">✓</span>
|
||||
<span>To-Do</span>
|
||||
</button>
|
||||
<button class="template-btn" hx-post="/api/paper/template/research" hx-target="#editor-content">
|
||||
<button class="template-btn" hx-post="/api/ui/paper/template/research" hx-target="#editor-content">
|
||||
<span class="template-icon">🔬</span>
|
||||
<span>Research</span>
|
||||
</button>
|
||||
|
|
@ -239,7 +239,7 @@
|
|||
</svg>
|
||||
<span>AI</span>
|
||||
</button>
|
||||
<button class="btn-icon" hx-post="/api/paper/save" hx-include="#editor-content" title="Save">
|
||||
<button class="btn-icon" hx-post="/api/ui/paper/save" hx-include="#editor-content" title="Save">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"></path>
|
||||
<polyline points="17 21 17 13 7 13 7 21"></polyline>
|
||||
|
|
@ -270,7 +270,7 @@
|
|||
id="editor-content"
|
||||
contenteditable="true"
|
||||
data-placeholder="Start writing, or type / for commands..."
|
||||
hx-post="/api/paper/autosave"
|
||||
hx-post="/api/ui/paper/autosave"
|
||||
hx-trigger="keyup changed delay:2000ms"
|
||||
hx-swap="none"></div>
|
||||
</div>
|
||||
|
|
@ -426,16 +426,16 @@
|
|||
<div class="ai-panel-content">
|
||||
<!-- Quick Actions -->
|
||||
<div class="ai-quick-actions">
|
||||
<button class="ai-action-btn" hx-post="/api/paper/ai/summarize" hx-include="[name='selected-text']">
|
||||
<button class="ai-action-btn" hx-post="/api/ui/paper/ai/summarize" hx-include="[name='selected-text']">
|
||||
<span>📝</span> Summarize
|
||||
</button>
|
||||
<button class="ai-action-btn" hx-post="/api/paper/ai/expand" hx-include="[name='selected-text']">
|
||||
<button class="ai-action-btn" hx-post="/api/ui/paper/ai/expand" hx-include="[name='selected-text']">
|
||||
<span>📖</span> Expand
|
||||
</button>
|
||||
<button class="ai-action-btn" hx-post="/api/paper/ai/improve" hx-include="[name='selected-text']">
|
||||
<button class="ai-action-btn" hx-post="/api/ui/paper/ai/improve" hx-include="[name='selected-text']">
|
||||
<span>✏️</span> Improve
|
||||
</button>
|
||||
<button class="ai-action-btn" hx-post="/api/paper/ai/simplify" hx-include="[name='selected-text']">
|
||||
<button class="ai-action-btn" hx-post="/api/ui/paper/ai/simplify" hx-include="[name='selected-text']">
|
||||
<span>🎯</span> Simplify
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -463,7 +463,7 @@
|
|||
<option value="zh">Chinese</option>
|
||||
<option value="ja">Japanese</option>
|
||||
</select>
|
||||
<button class="ai-action-btn" hx-post="/api/paper/ai/translate" hx-include="#translate-lang, [name='selected-text']">
|
||||
<button class="ai-action-btn" hx-post="/api/ui/paper/ai/translate" hx-include="#translate-lang, [name='selected-text']">
|
||||
Translate
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -472,7 +472,7 @@
|
|||
<div class="ai-custom-section">
|
||||
<label>Custom</label> AI Command</label>
|
||||
<textarea id="ai-custom-prompt" placeholder="Tell AI what to do with selected text..."></textarea>
|
||||
<button class="btn-primary" hx-post="/api/paper/ai/custom" hx-include="#ai-custom-prompt, [name='selected-text']">
|
||||
<button class="btn-primary" hx-post="/api/ui/paper/ai/custom" hx-include="#ai-custom-prompt, [name='selected-text']">
|
||||
Run Command
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -543,23 +543,23 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="export-options">
|
||||
<button class="export-option" hx-get="/api/paper/export/pdf" hx-swap="none">
|
||||
<button class="export-option" hx-get="/api/ui/paper/export/pdf" hx-swap="none">
|
||||
<span class="export-icon">📄</span>
|
||||
<span class="export-label">PDF</span>
|
||||
</button>
|
||||
<button class="export-option" hx-get="/api/paper/export/docx" hx-swap="none">
|
||||
<button class="export-option" hx-get="/api/ui/paper/export/docx" hx-swap="none">
|
||||
<span class="export-icon">📝</span>
|
||||
<span class="export-label">Word (.docx)</span>
|
||||
</button>
|
||||
<button class="export-option" hx-get="/api/paper/export/md" hx-swap="none">
|
||||
<button class="export-option" hx-get="/api/ui/paper/export/md" hx-swap="none">
|
||||
<span class="export-icon">📑</span>
|
||||
<span class="export-label">Markdown</span>
|
||||
</button>
|
||||
<button class="export-option" hx-get="/api/paper/export/html" hx-swap="none">
|
||||
<button class="export-option" hx-get="/api/ui/paper/export/html" hx-swap="none">
|
||||
<span class="export-icon">🌐</span>
|
||||
<span class="export-label">HTML</span>
|
||||
</button>
|
||||
<button class="export-option" hx-get="/api/paper/export/txt" hx-swap="none">
|
||||
<button class="export-option" hx-get="/api/ui/paper/export/txt" hx-swap="none">
|
||||
<span class="export-icon">📋</span>
|
||||
<span class="export-label">Plain Text</span>
|
||||
</button>
|
||||
|
|
@ -1534,7 +1534,7 @@
|
|||
status.textContent = 'Saving...';
|
||||
status.className = 'status-item save-status saving';
|
||||
|
||||
htmx.ajax('POST', '/api/paper/save', {
|
||||
htmx.ajax('POST', '/api/ui/paper/save', {
|
||||
target: 'none',
|
||||
values: {
|
||||
title: title.innerText,
|
||||
|
|
@ -1681,7 +1681,7 @@
|
|||
const tone = this.dataset.tone;
|
||||
const selectedText = document.getElementById('selected-text-input').value;
|
||||
|
||||
htmx.ajax('POST', '/api/paper/ai/tone', {
|
||||
htmx.ajax('POST', '/api/ui/paper/ai/tone', {
|
||||
target: '#ai-response-content',
|
||||
values: {
|
||||
tone: tone,
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@
|
|||
const selectedText = document.getElementById('selected-text-input')?.value || '';
|
||||
|
||||
if (typeof htmx !== 'undefined') {
|
||||
htmx.ajax('POST', '/api/paper/ai/tone', {
|
||||
htmx.ajax('POST', '/api/ui/paper/ai/tone', {
|
||||
target: '#ai-response-content',
|
||||
values: { tone, text: selectedText }
|
||||
}).then(() => {
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
const content = elements.editor?.innerHTML || '';
|
||||
|
||||
if (typeof htmx !== 'undefined') {
|
||||
htmx.ajax('POST', '/api/paper/save', {
|
||||
htmx.ajax('POST', '/api/ui/paper/save', {
|
||||
swap: 'none',
|
||||
values: { title, content }
|
||||
}).then(() => {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
<div class="section-header">
|
||||
<h3>Collections</h3>
|
||||
<button class="btn-icon-sm" title="New Collection"
|
||||
hx-post="/api/research/collections/new"
|
||||
hx-post="/api/ui/research/collections/new"
|
||||
hx-target="#collections-list"
|
||||
hx-swap="afterbegin">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="collections-list" id="collections-list"
|
||||
hx-get="/api/research/collections"
|
||||
hx-get="/api/ui/research/collections"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Collections loaded here -->
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
<div class="sidebar-section">
|
||||
<h3>Recent</h3>
|
||||
<div class="recent-list" id="recent-searches"
|
||||
hx-get="/api/research/recent"
|
||||
hx-get="/api/ui/research/recent"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Recent searches loaded here -->
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
<div class="section-header">
|
||||
<h3>Prompts</h3>
|
||||
<button class="btn-icon-sm" title="Browse All"
|
||||
hx-get="/api/research/prompts"
|
||||
hx-get="/api/ui/research/prompts"
|
||||
hx-target="#main-results">
|
||||
<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>
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
<!-- Search Header -->
|
||||
<div class="search-header">
|
||||
<form class="search-form" id="research-form"
|
||||
hx-post="/api/research/search"
|
||||
hx-post="/api/ui/research/search"
|
||||
hx-target="#main-results"
|
||||
hx-indicator="#search-indicator">
|
||||
<div class="search-input-wrapper">
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
<div class="trending-section">
|
||||
<h3>Trending</h3>
|
||||
<div class="trending-tags" id="trending-tags"
|
||||
hx-get="/api/research/trending"
|
||||
hx-get="/api/ui/research/trending"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<!-- Trending tags loaded here -->
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
<h3>Sources</h3>
|
||||
<div class="sources-actions">
|
||||
<button class="btn-icon-sm" title="Export Citations"
|
||||
hx-get="/api/research/export-citations"
|
||||
hx-get="/api/ui/research/export-citations"
|
||||
hx-swap="none">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"></path>
|
||||
|
|
@ -1386,7 +1386,7 @@
|
|||
const content = document.getElementById('answer-content');
|
||||
if (content) {
|
||||
// Send to Paper via HTMX
|
||||
htmx.ajax('POST', '/api/paper/import', {
|
||||
htmx.ajax('POST', '/api/ui/paper/import', {
|
||||
values: {
|
||||
content: content.innerHTML,
|
||||
title: searchInput.value
|
||||
|
|
@ -1422,7 +1422,7 @@
|
|||
function updateSourceCounts() {
|
||||
// This would typically come from the API response
|
||||
// For now, we'll use placeholder logic
|
||||
htmx.ajax('GET', '/api/research/source-counts', {
|
||||
htmx.ajax('GET', '/api/ui/research/source-counts', {
|
||||
swap: 'none'
|
||||
}).then(response => {
|
||||
// Update counts in sidebar
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@
|
|||
if (exportBtn) {
|
||||
const content = document.getElementById('answer-content');
|
||||
if (content && typeof htmx !== 'undefined') {
|
||||
htmx.ajax('POST', '/api/paper/import', {
|
||||
htmx.ajax('POST', '/api/ui/paper/import', {
|
||||
values: {
|
||||
content: content.innerHTML,
|
||||
title: elements.searchInput?.value || 'Research Export'
|
||||
|
|
@ -287,7 +287,7 @@
|
|||
*/
|
||||
function updateSourceCounts() {
|
||||
if (typeof htmx !== 'undefined') {
|
||||
htmx.ajax('GET', '/api/research/source-counts', {
|
||||
htmx.ajax('GET', '/api/ui/research/source-counts', {
|
||||
swap: 'none'
|
||||
}).then(response => {
|
||||
// Update counts in sidebar if response contains them
|
||||
|
|
@ -304,7 +304,7 @@
|
|||
const collectionName = prompt('Enter collection name:');
|
||||
if (collectionName && typeof htmx !== 'undefined') {
|
||||
const content = document.getElementById('answer-content');
|
||||
htmx.ajax('POST', '/api/research/collections/save', {
|
||||
htmx.ajax('POST', '/api/ui/research/collections/save', {
|
||||
values: {
|
||||
collection: collectionName,
|
||||
content: content?.innerHTML || '',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
|
||||
<nav class="settings-nav">
|
||||
<a href="#profile" class="nav</span>-item active" onclick="showSection('profile', this)">
|
||||
<a href="#profile" class="nav-item active" onclick="showSection('profile', this)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
|
|
@ -683,7 +683,7 @@
|
|||
<div class="setting-card">
|
||||
<div class="card-header">
|
||||
<h2>Webhooks</h2>
|
||||
<button class="btn-primary btn-sm" onclick="document.getElementById('create-webhook-modal').showModal()">
|
||||
<button class="btn-primary btn-sm" onclick="document.getElementById('add-webhook-modal').showModal()">
|
||||
<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="5" y1="12" x2="19" y2="12"></line>
|
||||
|
|
@ -1701,22 +1701,43 @@
|
|||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
margin: 0;
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal[open] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal::backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--surface);
|
||||
background: var(--surface, #1a1a24);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border: 1px solid var(--border, #2a2a2a);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accounts-grid" id="accounts-list" hx-get="/api/sources/accounts/list" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="accounts-grid" id="accounts-list" hx-get="/api/ui/sources/accounts/list" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading accounts...</p>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -271,7 +271,7 @@
|
|||
const collectionName = prompt("Enter collection name:");
|
||||
if (collectionName && typeof htmx !== "undefined") {
|
||||
htmx
|
||||
.ajax("POST", "/api/sources/prompts/save", {
|
||||
.ajax("POST", "/api/ui/sources/prompts/save", {
|
||||
values: {
|
||||
promptId,
|
||||
collection: collectionName,
|
||||
|
|
@ -652,7 +652,7 @@
|
|||
.then(() => {
|
||||
showToast("Repository connected");
|
||||
// Refresh the repo card
|
||||
htmx.ajax("GET", "/api/sources/repositories", {
|
||||
htmx.ajax("GET", "/api/ui/sources/repositories", {
|
||||
target: "#content-area",
|
||||
swap: "innerHTML",
|
||||
});
|
||||
|
|
@ -672,7 +672,7 @@
|
|||
})
|
||||
.then(() => {
|
||||
showToast("Repository disconnected");
|
||||
htmx.ajax("GET", "/api/sources/repositories", {
|
||||
htmx.ajax("GET", "/api/ui/sources/repositories", {
|
||||
target: "#content-area",
|
||||
swap: "innerHTML",
|
||||
});
|
||||
|
|
@ -779,6 +779,46 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter MCP catalog by category
|
||||
*/
|
||||
function filterMcpCategory(btn, category) {
|
||||
document
|
||||
.querySelectorAll("#mcp-category-filter .category-btn")
|
||||
.forEach((b) => {
|
||||
b.classList.remove("active");
|
||||
b.style.background = "#f5f5f5";
|
||||
b.style.color = "#333";
|
||||
});
|
||||
btn.classList.add("active");
|
||||
btn.style.background = "#2196F3";
|
||||
btn.style.color = "white";
|
||||
|
||||
document.querySelectorAll(".server-card").forEach((card) => {
|
||||
if (category === "all" || card.dataset.category === category) {
|
||||
card.style.display = "";
|
||||
} else {
|
||||
card.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add MCP server from catalog
|
||||
*/
|
||||
function addCatalogServer(id, name) {
|
||||
if (confirm('Add "' + name + '" to your MCP configuration?')) {
|
||||
if (typeof htmx !== "undefined") {
|
||||
htmx.ajax("POST", "/api/ui/sources/mcp/add-from-catalog", {
|
||||
values: { server_id: id },
|
||||
target: "#mcp-grid",
|
||||
swap: "innerHTML",
|
||||
});
|
||||
showToast('Server "' + name + '" added to configuration');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show toast notification
|
||||
*/
|
||||
|
|
@ -823,7 +863,13 @@
|
|||
openApp,
|
||||
editApp,
|
||||
insertMention,
|
||||
filterMcpCategory,
|
||||
addCatalogServer,
|
||||
getTaskContext: window.getTaskContext,
|
||||
clearTaskContext: window.clearTaskContext,
|
||||
};
|
||||
|
||||
// Expose globally for inline onclick handlers
|
||||
window.filterMcpCategory = filterMcpCategory;
|
||||
window.addCatalogServer = addCatalogServer;
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@
|
|||
<div
|
||||
class="intent-list"
|
||||
id="intent-list"
|
||||
hx-get="/api/autotask/list"
|
||||
hx-get="/api/ui/autotask/list"
|
||||
hx-trigger="load, every 10s"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
<div class="modal-body">
|
||||
<form
|
||||
id="new-intent-form"
|
||||
hx-post="/api/autotask/create"
|
||||
hx-post="/api/ui/autotask/create"
|
||||
hx-ext="json-enc"
|
||||
hx-target="#intent-list"
|
||||
hx-swap="afterbegin"
|
||||
|
|
|
|||
|
|
@ -134,10 +134,11 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Status section - compact, single line */
|
||||
/* Status section - slightly taller */
|
||||
.taskmd-section-status {
|
||||
flex: 0 0 auto;
|
||||
max-height: 60px;
|
||||
min-height: 80px;
|
||||
max-height: 100px;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
|
|
@ -150,12 +151,12 @@
|
|||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Terminal section - fixed at bottom, compact */
|
||||
/* Terminal section - fixed at bottom, taller */
|
||||
.taskmd-section-terminal {
|
||||
flex: 0 0 100px;
|
||||
height: 100px;
|
||||
min-height: 100px;
|
||||
max-height: 100px;
|
||||
flex: 0 0 150px;
|
||||
height: 150px;
|
||||
min-height: 150px;
|
||||
max-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden !important;
|
||||
|
|
@ -173,10 +174,11 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* STATUS Section - single line */
|
||||
/* STATUS Section - better spacing */
|
||||
.taskmd-status-content {
|
||||
padding: 12px 24px;
|
||||
padding: 16px 24px;
|
||||
background: var(--surface, #111);
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
|
|
@ -993,8 +995,8 @@
|
|||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 20px 24px;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
border-top: 1px solid var(--border, #1a1a1a);
|
||||
background: var(--bg-secondary, #0d0d0d);
|
||||
}
|
||||
|
|
@ -1002,11 +1004,11 @@
|
|||
.taskmd-actions .btn-action-rich {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
|
@ -1017,14 +1019,14 @@
|
|||
background: var(--success, #22c55e);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
padding: 12px 24px;
|
||||
font-size: 15px;
|
||||
box-shadow: 0 4px 12px rgba(34, 197, 94, 0.4);
|
||||
padding: 8px 18px;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 2px 8px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
.taskmd-actions .btn-open-app:hover {
|
||||
background: #16a34a;
|
||||
box-shadow: 0 6px 20px rgba(34, 197, 94, 0.5);
|
||||
box-shadow: 0 4px 12px rgba(34, 197, 94, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
|
|
@ -1032,6 +1034,8 @@
|
|||
background: transparent;
|
||||
border: 1px solid var(--border, #333);
|
||||
color: var(--text-secondary, #888);
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.taskmd-actions .btn-cancel:hover {
|
||||
|
|
@ -1071,24 +1075,28 @@
|
|||
|
||||
/* Scrollbar */
|
||||
.task-detail-rich::-webkit-scrollbar,
|
||||
.taskmd-terminal-output::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
.taskmd-terminal-output::-webkit-scrollbar,
|
||||
.taskmd-progress-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.task-detail-rich::-webkit-scrollbar-track,
|
||||
.taskmd-terminal-output::-webkit-scrollbar-track {
|
||||
background: #0a0a0a;
|
||||
.taskmd-terminal-output::-webkit-scrollbar-track,
|
||||
.taskmd-progress-content::-webkit-scrollbar-track {
|
||||
background: var(--surface, #1a1a1a);
|
||||
}
|
||||
|
||||
.task-detail-rich::-webkit-scrollbar-thumb,
|
||||
.taskmd-terminal-output::-webkit-scrollbar-thumb {
|
||||
background: #222;
|
||||
border-radius: 3px;
|
||||
.taskmd-terminal-output::-webkit-scrollbar-thumb,
|
||||
.taskmd-progress-content::-webkit-scrollbar-thumb {
|
||||
background: var(--primary, #c5f82a);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.task-detail-rich::-webkit-scrollbar-thumb:hover,
|
||||
.taskmd-terminal-output::-webkit-scrollbar-thumb:hover {
|
||||
background: #333;
|
||||
.taskmd-terminal-output::-webkit-scrollbar-thumb:hover,
|
||||
.taskmd-progress-content::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary-hover, #d4ff4a);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ body:has(.tasks-app) {
|
|||
color: var(--error, #ef4444);
|
||||
}
|
||||
|
||||
.priority-badge.priority-normal {
|
||||
.priority-badge.priority-medium {
|
||||
background: var(--surface, #1a1a1a);
|
||||
color: var(--text-secondary, #888);
|
||||
}
|
||||
|
|
@ -1973,7 +1973,7 @@ body:has(.tasks-app) {
|
|||
.floating-progress-log {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
background: var(--bg, #0a0a0a);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border, #2a2a2a);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
|
|
@ -2008,7 +2008,7 @@ body:has(.tasks-app) {
|
|||
.floating-llm-terminal {
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
background: var(--bg, #0a0a0a);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border, #2a2a2a);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
|
|
@ -2703,7 +2703,7 @@ body:has(.tasks-app) {
|
|||
|
||||
/* Terminal Section Rich */
|
||||
.terminal-section-rich {
|
||||
background: #0a0a0a;
|
||||
background: var(--bg, var(--surface, #0a0a0a));
|
||||
border: 2px solid var(--border, #2a2a2a);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
|
@ -2747,7 +2747,7 @@ body:has(.tasks-app) {
|
|||
}
|
||||
|
||||
.terminal-output-rich {
|
||||
background: #000;
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
padding: 16px 20px;
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="tasks-app">
|
||||
<!-- Hidden element to load stats on page load -->
|
||||
<div
|
||||
hx-get="/api/tasks/stats"
|
||||
hx-get="/api/ui/tasks/stats"
|
||||
hx-trigger="load, taskCreated from:body"
|
||||
hx-swap="innerHTML"
|
||||
style="display: none"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<button
|
||||
class="filter-pill"
|
||||
data-filter="complete"
|
||||
hx-get="/api/tasks?filter=complete"
|
||||
hx-get="/api/ui/tasks?filter=complete"
|
||||
hx-target="#task-list"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<button
|
||||
class="filter-pill active"
|
||||
data-filter="all"
|
||||
hx-get="/api/tasks?filter=all"
|
||||
hx-get="/api/ui/tasks?filter=all"
|
||||
hx-target="#task-list"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<button
|
||||
class="filter-pill"
|
||||
data-filter="active"
|
||||
hx-get="/api/tasks?filter=active"
|
||||
hx-get="/api/ui/tasks?filter=active"
|
||||
hx-target="#task-list"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
<button
|
||||
class="filter-pill"
|
||||
data-filter="awaiting"
|
||||
hx-get="/api/tasks?filter=awaiting"
|
||||
hx-get="/api/ui/tasks?filter=awaiting"
|
||||
hx-target="#task-list"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
<button
|
||||
class="filter-pill"
|
||||
data-filter="paused"
|
||||
hx-get="/api/tasks?filter=paused"
|
||||
hx-get="/api/ui/tasks?filter=paused"
|
||||
hx-target="#task-list"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
<button
|
||||
class="filter-pill"
|
||||
data-filter="blocked"
|
||||
hx-get="/api/tasks?filter=blocked"
|
||||
hx-get="/api/ui/tasks?filter=blocked"
|
||||
hx-target="#task-list"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
<button
|
||||
id="quick-intent-btn"
|
||||
class="btn-create-run"
|
||||
hx-post="/api/autotask/create"
|
||||
hx-post="/api/ui/autotask/create"
|
||||
hx-ext="json-enc"
|
||||
hx-include="#quick-intent-input"
|
||||
hx-target="#intent-result-hidden"
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
<div
|
||||
class="tasks-list-scroll"
|
||||
id="task-list"
|
||||
hx-get="/api/tasks?filter=all"
|
||||
hx-get="/api/ui/tasks?filter=all"
|
||||
hx-trigger="load, taskCreated from:body throttle:2s"
|
||||
hx-swap="innerHTML transition:false"
|
||||
>
|
||||
|
|
@ -242,9 +242,10 @@
|
|||
<div class="form-group">
|
||||
<label for="intent-priority">Priority</label>
|
||||
<select id="intent-priority" name="priority">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="high">High</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium" selected>Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="urgent">Urgent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -2196,7 +2196,7 @@ function addTaskCardToList(taskId, title, status) {
|
|||
</div>
|
||||
<div class="task-card-meta">
|
||||
<span class="task-card-status ${statusClass}">${statusText}</span>
|
||||
<span class="task-card-priority">normal</span>
|
||||
<span class="task-card-priority">medium</span>
|
||||
</div>
|
||||
<div class="task-card-progress">
|
||||
<div class="task-progress-bar">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue