Spaces:
Paused
Paused
frdel commited on
Commit ·
06c8c46
1
Parent(s): 3167019
frontend components refactor cleanup
Browse files- webui/components/messages/action-buttons/simple-action-buttons.js +2 -2
- webui/components/projects/project-selector.html +2 -2
- webui/components/sidebar/bottom/preferences/preferences-panel.html +6 -6
- webui/components/sidebar/bottom/preferences/preferences-store.js +92 -42
- webui/components/sidebar/left-sidebar.html +76 -46
- webui/index.html +11 -5
- webui/index.js +1 -59
webui/components/messages/action-buttons/simple-action-buttons.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import { store as speechStore } from "/components/chat/speech/speech-store.js";
|
| 3 |
|
| 4 |
// Extract text content from different message types
|
| 5 |
-
function getTextContent(element) {
|
| 6 |
// Get all children except action buttons
|
| 7 |
const textParts = [];
|
| 8 |
// Loop through all child elements
|
|
@@ -15,7 +15,7 @@ function getTextContent(element) {
|
|
| 15 |
continue;
|
| 16 |
}
|
| 17 |
// Get text content from the child
|
| 18 |
-
const text = child.innerText || "";
|
| 19 |
if (text.trim()) {
|
| 20 |
textParts.push(text.trim());
|
| 21 |
}
|
|
|
|
| 2 |
import { store as speechStore } from "/components/chat/speech/speech-store.js";
|
| 3 |
|
| 4 |
// Extract text content from different message types
|
| 5 |
+
function getTextContent(element,html=false) {
|
| 6 |
// Get all children except action buttons
|
| 7 |
const textParts = [];
|
| 8 |
// Loop through all child elements
|
|
|
|
| 15 |
continue;
|
| 16 |
}
|
| 17 |
// Get text content from the child
|
| 18 |
+
const text = (html ? child.innerHTML : child.innerText) || "";
|
| 19 |
if (text.trim()) {
|
| 20 |
textParts.push(text.trim());
|
| 21 |
}
|
webui/components/projects/project-selector.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
| 7 |
</head>
|
| 8 |
|
| 9 |
<body>
|
| 10 |
-
<div x-data>
|
| 11 |
<template x-if="$store.projects">
|
| 12 |
<div>
|
| 13 |
<template x-if="$store.selectedProject">
|
|
@@ -18,7 +18,7 @@
|
|
| 18 |
</template>
|
| 19 |
</div>
|
| 20 |
</template>
|
| 21 |
-
</div>
|
| 22 |
</body>
|
| 23 |
|
| 24 |
</html>
|
|
|
|
| 7 |
</head>
|
| 8 |
|
| 9 |
<body>
|
| 10 |
+
<!-- <div x-data>
|
| 11 |
<template x-if="$store.projects">
|
| 12 |
<div>
|
| 13 |
<template x-if="$store.selectedProject">
|
|
|
|
| 18 |
</template>
|
| 19 |
</div>
|
| 20 |
</template>
|
| 21 |
+
</div> -->
|
| 22 |
</body>
|
| 23 |
|
| 24 |
</html>
|
webui/components/sidebar/bottom/preferences/preferences-panel.html
CHANGED
|
@@ -16,42 +16,42 @@
|
|
| 16 |
</svg>
|
| 17 |
</h3>
|
| 18 |
<ul class="config-list collapse show" id="pref-list">
|
| 19 |
-
<li x-data
|
| 20 |
<span>Autoscroll</span>
|
| 21 |
<label class="switch">
|
| 22 |
<input id="auto-scroll-switch" type="checkbox" x-model="$store.preferences.autoScroll">
|
| 23 |
<span class="slider"></span>
|
| 24 |
</label>
|
| 25 |
</li>
|
| 26 |
-
<li x-data
|
| 27 |
<span class="switch-label">Dark mode</span>
|
| 28 |
<label class="switch">
|
| 29 |
<input type="checkbox" x-model="$store.preferences.darkMode">
|
| 30 |
<span class="slider"></span>
|
| 31 |
</label>
|
| 32 |
</li>
|
| 33 |
-
<li x-data
|
| 34 |
<span class="switch-label">Speech</span>
|
| 35 |
<label class="switch">
|
| 36 |
<input type="checkbox" x-model="$store.preferences.speech">
|
| 37 |
<span class="slider"></span>
|
| 38 |
</label>
|
| 39 |
</li>
|
| 40 |
-
<li x-data
|
| 41 |
<span>Show thoughts</span>
|
| 42 |
<label class="switch">
|
| 43 |
<input type="checkbox" x-model="$store.preferences.showThoughts">
|
| 44 |
<span class="slider"></span>
|
| 45 |
</label>
|
| 46 |
</li>
|
| 47 |
-
<li x-data
|
| 48 |
<span>Show JSON</span>
|
| 49 |
<label class="switch">
|
| 50 |
<input type="checkbox" x-model="$store.preferences.showJson">
|
| 51 |
<span class="slider"></span>
|
| 52 |
</label>
|
| 53 |
</li>
|
| 54 |
-
<li x-data
|
| 55 |
<span>Show utility messages</span>
|
| 56 |
<label class="switch">
|
| 57 |
<input type="checkbox" x-model="$store.preferences.showUtils">
|
|
|
|
| 16 |
</svg>
|
| 17 |
</h3>
|
| 18 |
<ul class="config-list collapse show" id="pref-list">
|
| 19 |
+
<li x-data>
|
| 20 |
<span>Autoscroll</span>
|
| 21 |
<label class="switch">
|
| 22 |
<input id="auto-scroll-switch" type="checkbox" x-model="$store.preferences.autoScroll">
|
| 23 |
<span class="slider"></span>
|
| 24 |
</label>
|
| 25 |
</li>
|
| 26 |
+
<li x-data>
|
| 27 |
<span class="switch-label">Dark mode</span>
|
| 28 |
<label class="switch">
|
| 29 |
<input type="checkbox" x-model="$store.preferences.darkMode">
|
| 30 |
<span class="slider"></span>
|
| 31 |
</label>
|
| 32 |
</li>
|
| 33 |
+
<li x-data>
|
| 34 |
<span class="switch-label">Speech</span>
|
| 35 |
<label class="switch">
|
| 36 |
<input type="checkbox" x-model="$store.preferences.speech">
|
| 37 |
<span class="slider"></span>
|
| 38 |
</label>
|
| 39 |
</li>
|
| 40 |
+
<li x-data>
|
| 41 |
<span>Show thoughts</span>
|
| 42 |
<label class="switch">
|
| 43 |
<input type="checkbox" x-model="$store.preferences.showThoughts">
|
| 44 |
<span class="slider"></span>
|
| 45 |
</label>
|
| 46 |
</li>
|
| 47 |
+
<li x-data>
|
| 48 |
<span>Show JSON</span>
|
| 49 |
<label class="switch">
|
| 50 |
<input type="checkbox" x-model="$store.preferences.showJson">
|
| 51 |
<span class="slider"></span>
|
| 52 |
</label>
|
| 53 |
</li>
|
| 54 |
+
<li x-data>
|
| 55 |
<span>Show utility messages</span>
|
| 56 |
<label class="switch">
|
| 57 |
<input type="checkbox" x-model="$store.preferences.showUtils">
|
webui/components/sidebar/bottom/preferences/preferences-store.js
CHANGED
|
@@ -1,14 +1,63 @@
|
|
| 1 |
import { createStore } from "/js/AlpineStore.js";
|
|
|
|
|
|
|
| 2 |
|
| 3 |
// Preferences store centralizes user preference toggles and side-effects
|
| 4 |
const model = {
|
| 5 |
// UI toggles (initialized with safe defaults, loaded from localStorage in init)
|
| 6 |
-
autoScroll
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
// Initialize preferences and apply current state
|
| 14 |
init() {
|
|
@@ -16,68 +65,69 @@ const model = {
|
|
| 16 |
// Load persisted preferences with safe fallbacks
|
| 17 |
try {
|
| 18 |
const storedDarkMode = localStorage.getItem("darkMode");
|
| 19 |
-
this.
|
| 20 |
} catch {
|
| 21 |
-
this.
|
| 22 |
}
|
| 23 |
|
| 24 |
try {
|
| 25 |
const storedSpeech = localStorage.getItem("speech");
|
| 26 |
-
this.
|
| 27 |
} catch {
|
| 28 |
-
this.
|
| 29 |
}
|
| 30 |
|
| 31 |
// Apply all preferences
|
| 32 |
-
this.
|
| 33 |
-
this.
|
| 34 |
-
this.
|
| 35 |
-
this.
|
| 36 |
-
this.
|
| 37 |
-
this.
|
| 38 |
} catch (e) {
|
| 39 |
console.error("Failed to initialize preferences store", e);
|
| 40 |
}
|
| 41 |
},
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
if (typeof globalThis.toggleAutoScroll === "function") {
|
| 46 |
-
globalThis.toggleAutoScroll(value);
|
| 47 |
-
}
|
| 48 |
},
|
| 49 |
|
| 50 |
-
|
| 51 |
-
if (
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
|
|
|
| 54 |
},
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
}
|
| 60 |
},
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
| 66 |
},
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
globalThis.toggleJson(value);
|
| 71 |
-
}
|
| 72 |
},
|
| 73 |
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
| 78 |
},
|
| 79 |
};
|
| 80 |
|
| 81 |
export const store = createStore("preferences", model);
|
| 82 |
-
|
| 83 |
-
|
|
|
|
| 1 |
import { createStore } from "/js/AlpineStore.js";
|
| 2 |
+
import * as css from "/js/css.js";
|
| 3 |
+
import { store as speechStore } from "/components/chat/speech/speech-store.js";
|
| 4 |
|
| 5 |
// Preferences store centralizes user preference toggles and side-effects
|
| 6 |
const model = {
|
| 7 |
// UI toggles (initialized with safe defaults, loaded from localStorage in init)
|
| 8 |
+
get autoScroll() {
|
| 9 |
+
return this._autoScroll;
|
| 10 |
+
},
|
| 11 |
+
set autoScroll(value) {
|
| 12 |
+
this._autoScroll = value;
|
| 13 |
+
this._applyAutoScroll(value);
|
| 14 |
+
},
|
| 15 |
+
_autoScroll: true,
|
| 16 |
+
|
| 17 |
+
get darkMode() {
|
| 18 |
+
return this._darkMode;
|
| 19 |
+
},
|
| 20 |
+
set darkMode(value) {
|
| 21 |
+
this._darkMode = value;
|
| 22 |
+
this._applyDarkMode(value);
|
| 23 |
+
},
|
| 24 |
+
_darkMode: true,
|
| 25 |
+
|
| 26 |
+
get speech() {
|
| 27 |
+
return this._speech;
|
| 28 |
+
},
|
| 29 |
+
set speech(value) {
|
| 30 |
+
this._speech = value;
|
| 31 |
+
this._applySpeech(value);
|
| 32 |
+
},
|
| 33 |
+
_speech: false,
|
| 34 |
+
|
| 35 |
+
get showThoughts() {
|
| 36 |
+
return this._showThoughts;
|
| 37 |
+
},
|
| 38 |
+
set showThoughts(value) {
|
| 39 |
+
this._showThoughts = value;
|
| 40 |
+
this._applyShowThoughts(value);
|
| 41 |
+
},
|
| 42 |
+
_showThoughts: true,
|
| 43 |
+
|
| 44 |
+
get showJson() {
|
| 45 |
+
return this._showJson;
|
| 46 |
+
},
|
| 47 |
+
set showJson(value) {
|
| 48 |
+
this._showJson = value;
|
| 49 |
+
this._applyShowJson(value);
|
| 50 |
+
},
|
| 51 |
+
_showJson: false,
|
| 52 |
+
|
| 53 |
+
get showUtils() {
|
| 54 |
+
return this._showUtils;
|
| 55 |
+
},
|
| 56 |
+
set showUtils(value) {
|
| 57 |
+
this._showUtils = value;
|
| 58 |
+
this._applyShowUtils(value);
|
| 59 |
+
},
|
| 60 |
+
_showUtils: false,
|
| 61 |
|
| 62 |
// Initialize preferences and apply current state
|
| 63 |
init() {
|
|
|
|
| 65 |
// Load persisted preferences with safe fallbacks
|
| 66 |
try {
|
| 67 |
const storedDarkMode = localStorage.getItem("darkMode");
|
| 68 |
+
this._darkMode = storedDarkMode !== "false";
|
| 69 |
} catch {
|
| 70 |
+
this._darkMode = true; // Default to dark mode if localStorage is unavailable
|
| 71 |
}
|
| 72 |
|
| 73 |
try {
|
| 74 |
const storedSpeech = localStorage.getItem("speech");
|
| 75 |
+
this._speech = storedSpeech === "true";
|
| 76 |
} catch {
|
| 77 |
+
this._speech = false; // Default to speech off if localStorage is unavailable
|
| 78 |
}
|
| 79 |
|
| 80 |
// Apply all preferences
|
| 81 |
+
this._applyDarkMode(this._darkMode);
|
| 82 |
+
this._applyAutoScroll(this._autoScroll);
|
| 83 |
+
this._applySpeech(this._speech);
|
| 84 |
+
this._applyShowThoughts(this._showThoughts);
|
| 85 |
+
this._applyShowJson(this._showJson);
|
| 86 |
+
this._applyShowUtils(this._showUtils);
|
| 87 |
} catch (e) {
|
| 88 |
console.error("Failed to initialize preferences store", e);
|
| 89 |
}
|
| 90 |
},
|
| 91 |
|
| 92 |
+
_applyAutoScroll(value) {
|
| 93 |
+
// nothing for now
|
|
|
|
|
|
|
|
|
|
| 94 |
},
|
| 95 |
|
| 96 |
+
_applyDarkMode(value) {
|
| 97 |
+
if (value) {
|
| 98 |
+
document.body.classList.remove("light-mode");
|
| 99 |
+
document.body.classList.add("dark-mode");
|
| 100 |
+
} else {
|
| 101 |
+
document.body.classList.remove("dark-mode");
|
| 102 |
+
document.body.classList.add("light-mode");
|
| 103 |
}
|
| 104 |
+
localStorage.setItem("darkMode", value);
|
| 105 |
},
|
| 106 |
|
| 107 |
+
_applySpeech(value) {
|
| 108 |
+
localStorage.setItem("speech", value);
|
| 109 |
+
if (!value) speechStore.stopAudio();
|
|
|
|
| 110 |
},
|
| 111 |
|
| 112 |
+
_applyShowThoughts(value) {
|
| 113 |
+
css.toggleCssProperty(
|
| 114 |
+
".msg-thoughts",
|
| 115 |
+
"display",
|
| 116 |
+
value ? undefined : "none"
|
| 117 |
+
);
|
| 118 |
},
|
| 119 |
|
| 120 |
+
_applyShowJson(value) {
|
| 121 |
+
css.toggleCssProperty(".msg-json", "display", value ? "block" : "none");
|
|
|
|
|
|
|
| 122 |
},
|
| 123 |
|
| 124 |
+
_applyShowUtils(value) {
|
| 125 |
+
css.toggleCssProperty(
|
| 126 |
+
".message-util",
|
| 127 |
+
"display",
|
| 128 |
+
value ? undefined : "none"
|
| 129 |
+
);
|
| 130 |
},
|
| 131 |
};
|
| 132 |
|
| 133 |
export const store = createStore("preferences", model);
|
|
|
|
|
|
webui/components/sidebar/left-sidebar.html
CHANGED
|
@@ -1,30 +1,36 @@
|
|
| 1 |
<html>
|
|
|
|
| 2 |
<head>
|
| 3 |
<script type="module">
|
| 4 |
import { store as sidebarStore } from "/components/sidebar/sidebar-store.js";
|
| 5 |
</script>
|
| 6 |
<!-- Wrapper composes existing sidebar components -->
|
| 7 |
</head>
|
|
|
|
| 8 |
<body>
|
| 9 |
-
<div
|
| 10 |
-
<
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
</div>
|
| 23 |
-
</
|
| 24 |
-
<!--Sidebar lower elements-->
|
| 25 |
-
<div class="left-panel-bottom">
|
| 26 |
-
<x-component path="sidebar/bottom/sidebar-bottom.html"></x-component>
|
| 27 |
-
</div>
|
| 28 |
</div>
|
| 29 |
|
| 30 |
<style>
|
|
@@ -42,21 +48,25 @@
|
|
| 42 |
color: var(--color-text);
|
| 43 |
box-shadow: 1px 0 5px rgba(0, 0, 0, 0.3);
|
| 44 |
user-select: none;
|
| 45 |
-
-webkit-user-select: none;
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
/* Ensure component host behaves like the original direct children for flex layout */
|
| 51 |
-
#left-panel
|
| 52 |
-
#left-panel
|
| 53 |
-
|
| 54 |
-
|
| 55 |
}
|
| 56 |
|
| 57 |
-
#left-panel
|
| 58 |
-
|
| 59 |
-
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
#left-panel.hidden {
|
|
@@ -64,11 +74,13 @@
|
|
| 64 |
}
|
| 65 |
|
| 66 |
.left-panel-top {
|
| 67 |
-
flex: 1 1 auto;
|
|
|
|
| 68 |
display: -webkit-flex;
|
| 69 |
display: flex;
|
| 70 |
flex-direction: column;
|
| 71 |
-
min-height: 0;
|
|
|
|
| 72 |
overflow: hidden;
|
| 73 |
justify-content: space-between;
|
| 74 |
margin-top: 3.5rem;
|
|
@@ -76,13 +88,16 @@
|
|
| 76 |
scrollbar-width: none;
|
| 77 |
-ms-overflow-style: none;
|
| 78 |
}
|
|
|
|
| 79 |
.left-panel-top::-webkit-scrollbar {
|
| 80 |
width: 0px;
|
| 81 |
}
|
|
|
|
| 82 |
.left-panel-bottom {
|
| 83 |
position: relative;
|
| 84 |
flex-shrink: 0;
|
| 85 |
-
flex-grow: 0;
|
|
|
|
| 86 |
}
|
| 87 |
|
| 88 |
/* Chats section container inside sidebar */
|
|
@@ -91,7 +106,8 @@
|
|
| 91 |
display: flex;
|
| 92 |
flex-direction: column;
|
| 93 |
min-height: 0;
|
| 94 |
-
flex: 1 1 auto;
|
|
|
|
| 95 |
max-height: 100%;
|
| 96 |
overflow: hidden;
|
| 97 |
}
|
|
@@ -102,19 +118,21 @@
|
|
| 102 |
display: flex;
|
| 103 |
flex-direction: column;
|
| 104 |
min-height: 0;
|
| 105 |
-
flex: 0 0 auto;
|
| 106 |
-
|
|
|
|
|
|
|
| 107 |
margin-top: 0;
|
| 108 |
padding-top: var(--spacing-md);
|
| 109 |
overflow: hidden;
|
| 110 |
}
|
| 111 |
-
|
| 112 |
/* Flatten wrapper elements to maintain flex chain for inner scroll containers */
|
| 113 |
-
#chats-section
|
| 114 |
-
#chats-section
|
| 115 |
-
#tasks-section
|
| 116 |
-
#tasks-section
|
| 117 |
-
#tasks-section
|
| 118 |
display: contents;
|
| 119 |
}
|
| 120 |
|
|
@@ -125,10 +143,12 @@
|
|
| 125 |
flex: 1;
|
| 126 |
min-height: 0;
|
| 127 |
}
|
|
|
|
| 128 |
/* During the collapsing transition, allow height animation to control size */
|
| 129 |
#tasks-section .collapsing {
|
| 130 |
flex: 0 0 auto !important;
|
| 131 |
}
|
|
|
|
| 132 |
/* Common section header styling (Chats, Tasks) - matching Preferences style */
|
| 133 |
.section-header {
|
| 134 |
display: flex;
|
|
@@ -137,23 +157,25 @@
|
|
| 137 |
gap: 6px;
|
| 138 |
margin: 0 0 0.5rem 0;
|
| 139 |
padding: 0;
|
| 140 |
-
-webkit-user-select: none;
|
|
|
|
| 141 |
user-select: none;
|
| 142 |
font-size: var(--font-size-normal);
|
| 143 |
}
|
| 144 |
-
|
| 145 |
/* Collapsible section headers (Tasks) */
|
| 146 |
.section-header-collapsible {
|
| 147 |
cursor: pointer;
|
| 148 |
}
|
| 149 |
-
|
| 150 |
@media (max-width: 768px) {
|
| 151 |
#left-panel {
|
| 152 |
position: fixed;
|
| 153 |
left: 0;
|
| 154 |
top: 0;
|
| 155 |
bottom: 0;
|
| 156 |
-
width: 250px !important;
|
|
|
|
| 157 |
min-width: 250px;
|
| 158 |
z-index: 1003;
|
| 159 |
transition: all var(--transition-speed) ease-in-out;
|
|
@@ -165,9 +187,17 @@
|
|
| 165 |
}
|
| 166 |
|
| 167 |
@media (max-height: 600px) {
|
| 168 |
-
#chats-section {
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
}
|
| 171 |
</style>
|
| 172 |
</body>
|
| 173 |
-
|
|
|
|
|
|
| 1 |
<html>
|
| 2 |
+
|
| 3 |
<head>
|
| 4 |
<script type="module">
|
| 5 |
import { store as sidebarStore } from "/components/sidebar/sidebar-store.js";
|
| 6 |
</script>
|
| 7 |
<!-- Wrapper composes existing sidebar components -->
|
| 8 |
</head>
|
| 9 |
+
|
| 10 |
<body>
|
| 11 |
+
<div x-data>
|
| 12 |
+
<template x-if="$store.sidebar">
|
| 13 |
+
<div id="left-panel" class="panel" x-data :class="{'hidden': !$store.sidebar.isOpen}">
|
| 14 |
+
<!--Sidebar upper elements-->
|
| 15 |
+
<div class="left-panel-top">
|
| 16 |
+
<!-- Top Section: Header Icons + Quick Actions -->
|
| 17 |
+
<x-component path="sidebar/top-section/sidebar-top.html"></x-component>
|
| 18 |
+
<!-- Chats List -->
|
| 19 |
+
<div class="config-section" id="chats-section">
|
| 20 |
+
<x-component path="sidebar/chats/chats-list.html"></x-component>
|
| 21 |
+
</div>
|
| 22 |
|
| 23 |
+
<!-- Tasks List -->
|
| 24 |
+
<div class="config-section" id="tasks-section">
|
| 25 |
+
<x-component path="sidebar/tasks/tasks-list.html"></x-component>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
<!--Sidebar lower elements-->
|
| 29 |
+
<div class="left-panel-bottom">
|
| 30 |
+
<x-component path="sidebar/bottom/sidebar-bottom.html"></x-component>
|
| 31 |
+
</div>
|
| 32 |
</div>
|
| 33 |
+
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
</div>
|
| 35 |
|
| 36 |
<style>
|
|
|
|
| 48 |
color: var(--color-text);
|
| 49 |
box-shadow: 1px 0 5px rgba(0, 0, 0, 0.3);
|
| 50 |
user-select: none;
|
| 51 |
+
-webkit-user-select: none;
|
| 52 |
+
/* Safari compatibility */
|
| 53 |
+
height: 100vh;
|
| 54 |
+
/* Ensure full height */
|
| 55 |
+
overflow: hidden;
|
| 56 |
+
/* Prevent overall panel overflow */
|
| 57 |
}
|
| 58 |
|
| 59 |
/* Ensure component host behaves like the original direct children for flex layout */
|
| 60 |
+
#left-panel>.left-panel-top,
|
| 61 |
+
#left-panel>.left-panel-bottom {
|
| 62 |
+
display: flex;
|
| 63 |
+
flex-direction: column;
|
| 64 |
}
|
| 65 |
|
| 66 |
+
#left-panel>.left-panel-top {
|
| 67 |
+
flex: 1;
|
| 68 |
+
min-height: 0;
|
| 69 |
+
/* allow inner flex children to shrink properly */
|
| 70 |
}
|
| 71 |
|
| 72 |
#left-panel.hidden {
|
|
|
|
| 74 |
}
|
| 75 |
|
| 76 |
.left-panel-top {
|
| 77 |
+
flex: 1 1 auto;
|
| 78 |
+
/* Take available space */
|
| 79 |
display: -webkit-flex;
|
| 80 |
display: flex;
|
| 81 |
flex-direction: column;
|
| 82 |
+
min-height: 0;
|
| 83 |
+
/* Critical for allowing flex children to shrink */
|
| 84 |
overflow: hidden;
|
| 85 |
justify-content: space-between;
|
| 86 |
margin-top: 3.5rem;
|
|
|
|
| 88 |
scrollbar-width: none;
|
| 89 |
-ms-overflow-style: none;
|
| 90 |
}
|
| 91 |
+
|
| 92 |
.left-panel-top::-webkit-scrollbar {
|
| 93 |
width: 0px;
|
| 94 |
}
|
| 95 |
+
|
| 96 |
.left-panel-bottom {
|
| 97 |
position: relative;
|
| 98 |
flex-shrink: 0;
|
| 99 |
+
flex-grow: 0;
|
| 100 |
+
/* Prevent bottom from growing */
|
| 101 |
}
|
| 102 |
|
| 103 |
/* Chats section container inside sidebar */
|
|
|
|
| 106 |
display: flex;
|
| 107 |
flex-direction: column;
|
| 108 |
min-height: 0;
|
| 109 |
+
flex: 1 1 auto;
|
| 110 |
+
/* Take all available space */
|
| 111 |
max-height: 100%;
|
| 112 |
overflow: hidden;
|
| 113 |
}
|
|
|
|
| 118 |
display: flex;
|
| 119 |
flex-direction: column;
|
| 120 |
min-height: 0;
|
| 121 |
+
flex: 0 0 auto;
|
| 122 |
+
/* Shrink to content, no reserved space */
|
| 123 |
+
max-height: 40%;
|
| 124 |
+
/* Limit to 40% of viewport height when expanded */
|
| 125 |
margin-top: 0;
|
| 126 |
padding-top: var(--spacing-md);
|
| 127 |
overflow: hidden;
|
| 128 |
}
|
| 129 |
+
|
| 130 |
/* Flatten wrapper elements to maintain flex chain for inner scroll containers */
|
| 131 |
+
#chats-section>x-component,
|
| 132 |
+
#chats-section>x-component>div[x-data],
|
| 133 |
+
#tasks-section>x-component,
|
| 134 |
+
#tasks-section>x-component>div[x-data],
|
| 135 |
+
#tasks-section>x-component>div[x-data]>div[x-data] {
|
| 136 |
display: contents;
|
| 137 |
}
|
| 138 |
|
|
|
|
| 143 |
flex: 1;
|
| 144 |
min-height: 0;
|
| 145 |
}
|
| 146 |
+
|
| 147 |
/* During the collapsing transition, allow height animation to control size */
|
| 148 |
#tasks-section .collapsing {
|
| 149 |
flex: 0 0 auto !important;
|
| 150 |
}
|
| 151 |
+
|
| 152 |
/* Common section header styling (Chats, Tasks) - matching Preferences style */
|
| 153 |
.section-header {
|
| 154 |
display: flex;
|
|
|
|
| 157 |
gap: 6px;
|
| 158 |
margin: 0 0 0.5rem 0;
|
| 159 |
padding: 0;
|
| 160 |
+
-webkit-user-select: none;
|
| 161 |
+
/* Safari compatibility */
|
| 162 |
user-select: none;
|
| 163 |
font-size: var(--font-size-normal);
|
| 164 |
}
|
| 165 |
+
|
| 166 |
/* Collapsible section headers (Tasks) */
|
| 167 |
.section-header-collapsible {
|
| 168 |
cursor: pointer;
|
| 169 |
}
|
| 170 |
+
|
| 171 |
@media (max-width: 768px) {
|
| 172 |
#left-panel {
|
| 173 |
position: fixed;
|
| 174 |
left: 0;
|
| 175 |
top: 0;
|
| 176 |
bottom: 0;
|
| 177 |
+
width: 250px !important;
|
| 178 |
+
/* Force width */
|
| 179 |
min-width: 250px;
|
| 180 |
z-index: 1003;
|
| 181 |
transition: all var(--transition-speed) ease-in-out;
|
|
|
|
| 187 |
}
|
| 188 |
|
| 189 |
@media (max-height: 600px) {
|
| 190 |
+
#chats-section {
|
| 191 |
+
min-height: 100%;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.left-panel-top {
|
| 195 |
+
overflow-y: auto;
|
| 196 |
+
-webkit-scroll-behavior: smooth;
|
| 197 |
+
scroll-behavior: smooth;
|
| 198 |
+
}
|
| 199 |
}
|
| 200 |
</style>
|
| 201 |
</body>
|
| 202 |
+
|
| 203 |
+
</html>
|
webui/index.html
CHANGED
|
@@ -99,7 +99,9 @@
|
|
| 99 |
<script type="module" src="index.js"></script>
|
| 100 |
|
| 101 |
<!-- Bootstrap JS (only for logic, importing bundled CSS => UI conflicts) -->
|
| 102 |
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
|
|
|
|
|
|
| 103 |
|
| 104 |
<!-- Then load Alpine.js -->
|
| 105 |
<!-- <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"></script> -->
|
|
@@ -133,15 +135,19 @@
|
|
| 133 |
</script>
|
| 134 |
</head>
|
| 135 |
|
| 136 |
-
<body class="dark-mode device-pointer">
|
| 137 |
<div class="container">
|
| 138 |
<!-- Sidebar Overlay -->
|
| 139 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
<!-- Left Sidebar (Header Icons, Quick Actions, Tabs, Chats, Tasks) -->
|
| 141 |
<x-component path="sidebar/left-sidebar.html"></x-component>
|
| 142 |
-
|
| 143 |
<!-- Right Panel (Message History and Input Section) -->
|
| 144 |
-
<div id="right-panel" class="panel"
|
| 145 |
<!-- Time and Date -->
|
| 146 |
<div id="time-date-container">
|
| 147 |
<div id="time-date"></div>
|
|
|
|
| 99 |
<script type="module" src="index.js"></script>
|
| 100 |
|
| 101 |
<!-- Bootstrap JS (only for logic, importing bundled CSS => UI conflicts) -->
|
| 102 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
| 103 |
+
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
|
| 104 |
+
crossorigin="anonymous"></script>
|
| 105 |
|
| 106 |
<!-- Then load Alpine.js -->
|
| 107 |
<!-- <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"></script> -->
|
|
|
|
| 135 |
</script>
|
| 136 |
</head>
|
| 137 |
|
| 138 |
+
<body class="dark-mode device-pointer" x-data>
|
| 139 |
<div class="container">
|
| 140 |
<!-- Sidebar Overlay -->
|
| 141 |
+
<template x-if="$store.sidebar">
|
| 142 |
+
<div id="sidebar-overlay" class="sidebar-overlay" x-data
|
| 143 |
+
:class="{'visible': $store.sidebar.isOpen && $store.sidebar.isMobile()}"
|
| 144 |
+
@click="$store.sidebar.close()"></div>
|
| 145 |
+
</template>
|
| 146 |
<!-- Left Sidebar (Header Icons, Quick Actions, Tabs, Chats, Tasks) -->
|
| 147 |
<x-component path="sidebar/left-sidebar.html"></x-component>
|
| 148 |
+
|
| 149 |
<!-- Right Panel (Message History and Input Section) -->
|
| 150 |
+
<div id="right-panel" class="panel" >
|
| 151 |
<!-- Time and Date -->
|
| 152 |
<div id="time-date-container">
|
| 153 |
<div id="time-date"></div>
|
webui/index.js
CHANGED
|
@@ -21,9 +21,6 @@ globalThis.resetCounter = 0; // Used by stores and getChatBasedId
|
|
| 21 |
let skipOneSpeech = false;
|
| 22 |
let connectionStatus = undefined; // undefined = not checked yet, true = connected, false = disconnected
|
| 23 |
|
| 24 |
-
export function getAutoScroll() {
|
| 25 |
-
return autoScroll;
|
| 26 |
-
}
|
| 27 |
|
| 28 |
// Sidebar toggle logic is now handled by sidebar-store.js
|
| 29 |
|
|
@@ -175,7 +172,7 @@ setInterval(updateUserTime, 1000);
|
|
| 175 |
function setMessage(id, type, heading, content, temp, kvps = null) {
|
| 176 |
const result = msgs.setMessage(id, type, heading, content, temp, kvps);
|
| 177 |
const chatHistoryEl = document.getElementById("chat-history");
|
| 178 |
-
if (autoScroll && chatHistoryEl) {
|
| 179 |
chatHistoryEl.scrollTop = chatHistoryEl.scrollHeight;
|
| 180 |
}
|
| 181 |
return result;
|
|
@@ -480,61 +477,6 @@ export const getChatBasedId = function (id) {
|
|
| 480 |
return context + "-" + globalThis.resetCounter + "-" + id;
|
| 481 |
};
|
| 482 |
|
| 483 |
-
globalThis.toggleAutoScroll = async function (_autoScroll) {
|
| 484 |
-
autoScroll = _autoScroll;
|
| 485 |
-
};
|
| 486 |
-
|
| 487 |
-
globalThis.toggleJson = async function (showJson) {
|
| 488 |
-
css.toggleCssProperty(".msg-json", "display", showJson ? "block" : "none");
|
| 489 |
-
};
|
| 490 |
-
|
| 491 |
-
globalThis.toggleThoughts = async function (showThoughts) {
|
| 492 |
-
css.toggleCssProperty(
|
| 493 |
-
".msg-thoughts",
|
| 494 |
-
"display",
|
| 495 |
-
showThoughts ? undefined : "none"
|
| 496 |
-
);
|
| 497 |
-
};
|
| 498 |
-
|
| 499 |
-
globalThis.toggleUtils = async function (showUtils) {
|
| 500 |
-
css.toggleCssProperty(
|
| 501 |
-
".message-util",
|
| 502 |
-
"display",
|
| 503 |
-
showUtils ? undefined : "none"
|
| 504 |
-
);
|
| 505 |
-
};
|
| 506 |
-
|
| 507 |
-
globalThis.toggleDarkMode = function (isDark) {
|
| 508 |
-
if (isDark) {
|
| 509 |
-
document.body.classList.remove("light-mode");
|
| 510 |
-
document.body.classList.add("dark-mode");
|
| 511 |
-
} else {
|
| 512 |
-
document.body.classList.remove("dark-mode");
|
| 513 |
-
document.body.classList.add("light-mode");
|
| 514 |
-
}
|
| 515 |
-
console.log("Dark mode:", isDark);
|
| 516 |
-
localStorage.setItem("darkMode", isDark);
|
| 517 |
-
};
|
| 518 |
-
|
| 519 |
-
globalThis.toggleSpeech = function (isOn) {
|
| 520 |
-
console.log("Speech:", isOn);
|
| 521 |
-
localStorage.setItem("speech", isOn);
|
| 522 |
-
if (!isOn) speechStore.stopAudio();
|
| 523 |
-
};
|
| 524 |
-
|
| 525 |
-
globalThis.nudge = async function () {
|
| 526 |
-
await inputStore.nudge();
|
| 527 |
-
};
|
| 528 |
-
|
| 529 |
-
globalThis.restart = async function () {
|
| 530 |
-
await chatsStore.restart();
|
| 531 |
-
};
|
| 532 |
-
|
| 533 |
-
// Modify this part
|
| 534 |
-
document.addEventListener("DOMContentLoaded", () => {
|
| 535 |
-
const isDarkMode = localStorage.getItem("darkMode") !== "false";
|
| 536 |
-
toggleDarkMode(isDarkMode);
|
| 537 |
-
});
|
| 538 |
|
| 539 |
globalThis.loadChats = async function () {
|
| 540 |
await chatsStore.loadChats();
|
|
|
|
| 21 |
let skipOneSpeech = false;
|
| 22 |
let connectionStatus = undefined; // undefined = not checked yet, true = connected, false = disconnected
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
// Sidebar toggle logic is now handled by sidebar-store.js
|
| 26 |
|
|
|
|
| 172 |
function setMessage(id, type, heading, content, temp, kvps = null) {
|
| 173 |
const result = msgs.setMessage(id, type, heading, content, temp, kvps);
|
| 174 |
const chatHistoryEl = document.getElementById("chat-history");
|
| 175 |
+
if (preferencesStore.autoScroll && chatHistoryEl) {
|
| 176 |
chatHistoryEl.scrollTop = chatHistoryEl.scrollHeight;
|
| 177 |
}
|
| 178 |
return result;
|
|
|
|
| 477 |
return context + "-" + globalThis.resetCounter + "-" + id;
|
| 478 |
};
|
| 479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
|
| 481 |
globalThis.loadChats = async function () {
|
| 482 |
await chatsStore.loadChats();
|