iameclyps0 commited on
Commit
32b833d
·
1 Parent(s): 3c970e3

fix: sidebar, welcome screen and UX

Browse files
webui/components/sidebar/bottom/preferences/preferences-panel.html CHANGED
@@ -9,13 +9,17 @@
9
  <template x-if="$store.preferences">
10
  <div class="pref-section">
11
  <span>
12
- <h3 class="pref-header" data-bs-toggle="collapse" data-bs-target="#pref-list">
 
 
 
13
  Preferences
14
  <svg class="arrow-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
15
  <path d="M8 4l8 8-8 8" />
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">
@@ -89,7 +93,7 @@
89
  flex-direction: column;
90
  gap: 1px;
91
  }
92
- #pref-list li {
93
  opacity: 0.8;
94
  }
95
  .pref-section .switch-label,
 
9
  <template x-if="$store.preferences">
10
  <div class="pref-section">
11
  <span>
12
+ <h3 class="pref-header"
13
+ data-bs-toggle="collapse"
14
+ @click="$store.sidebar.toggleSection('preferences')"
15
+ x-effect="!$store.sidebar.isSectionOpen('preferences') ? $el.classList.add('collapsed') : $el.classList.remove('collapsed')">
16
  Preferences
17
  <svg class="arrow-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
18
  <path d="M8 4l8 8-8 8" />
19
  </svg>
20
  </h3>
21
+ <ul class="config-list collapse" id="preferences-collapse"
22
+ x-effect="(() => { const c = bootstrap.Collapse.getOrCreateInstance($el, { toggle: false }); $store.sidebar.isSectionOpen('preferences') ? c.show() : c.hide(); })()">
23
  <li x-data>
24
  <span>Autoscroll</span>
25
  <label class="switch">
 
93
  flex-direction: column;
94
  gap: 1px;
95
  }
96
+ #preferences-collapse li {
97
  opacity: 0.8;
98
  }
99
  .pref-section .switch-label,
webui/components/sidebar/sidebar-store.js CHANGED
@@ -5,17 +5,57 @@ const model = {
5
  isOpen: true,
6
  _initialized: false,
7
 
 
 
 
 
 
 
8
  // Initialize the store by setting up a resize listener
9
  // Guard ensures this runs only once, even if called from multiple components
10
  init() {
11
  if (this._initialized) return;
12
  this._initialized = true;
13
 
 
14
  this.handleResize();
15
  this.resizeHandler = () => this.handleResize();
16
  window.addEventListener("resize", this.resizeHandler);
17
  },
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  // Cleanup method for lifecycle management
20
  destroy() {
21
  if (this.resizeHandler) {
 
5
  isOpen: true,
6
  _initialized: false,
7
 
8
+ // Centralized collapse state for all sidebar sections (persisted in localStorage)
9
+ sectionStates: {
10
+ tasks: false, // default: collapsed
11
+ preferences: false // default: collapsed
12
+ },
13
+
14
  // Initialize the store by setting up a resize listener
15
  // Guard ensures this runs only once, even if called from multiple components
16
  init() {
17
  if (this._initialized) return;
18
  this._initialized = true;
19
 
20
+ this.loadSectionStates();
21
  this.handleResize();
22
  this.resizeHandler = () => this.handleResize();
23
  window.addEventListener("resize", this.resizeHandler);
24
  },
25
 
26
+ // Load section collapse states from localStorage
27
+ loadSectionStates() {
28
+ try {
29
+ const stored = localStorage.getItem('sidebarSections');
30
+ if (stored) {
31
+ this.sectionStates = { ...this.sectionStates, ...JSON.parse(stored) };
32
+ }
33
+ } catch (e) {
34
+ console.error('Failed to load sidebar section states', e);
35
+ }
36
+ },
37
+
38
+ // Persist section states to localStorage
39
+ persistSectionStates() {
40
+ try {
41
+ localStorage.setItem('sidebarSections', JSON.stringify(this.sectionStates));
42
+ } catch (e) {
43
+ console.error('Failed to persist section states', e);
44
+ }
45
+ },
46
+
47
+ // Check if a section should be open (used by x-init in templates)
48
+ isSectionOpen(name) {
49
+ return this.sectionStates[name] === true;
50
+ },
51
+
52
+ // Toggle and persist a section's open state (drives Bootstrap programmatically via components)
53
+ toggleSection(name) {
54
+ if (!(name in this.sectionStates)) return;
55
+ this.sectionStates[name] = !this.sectionStates[name];
56
+ this.persistSectionStates();
57
+ },
58
+
59
  // Cleanup method for lifecycle management
60
  destroy() {
61
  if (this.resizeHandler) {
webui/components/sidebar/tasks/tasks-list.html CHANGED
@@ -8,13 +8,17 @@
8
  <div class="tasks-list-root">
9
  <template x-if="$store.tasks">
10
  <div x-data class="tasks-list-inner">
11
- <h3 class="section-header section-header-collapsible" data-bs-toggle="collapse" data-bs-target="#tasks-collapse">
 
 
 
12
  Tasks
13
  <svg class="arrow-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
14
  <path d="M8 4l8 8-8 8" />
15
  </svg>
16
  </h3>
17
- <div class="collapse show" id="tasks-collapse">
 
18
  <div class="tasks-list-body">
19
  <div class="tasks-list-container" x-data>
20
  <ul class="config-list tasks-config-list" x-show="$store.tasks.tasks.length > 0">
 
8
  <div class="tasks-list-root">
9
  <template x-if="$store.tasks">
10
  <div x-data class="tasks-list-inner">
11
+ <h3 class="section-header section-header-collapsible"
12
+ data-bs-toggle="collapse"
13
+ @click="$store.sidebar.toggleSection('tasks')"
14
+ x-effect="!$store.sidebar.isSectionOpen('tasks') ? $el.classList.add('collapsed') : $el.classList.remove('collapsed')">
15
  Tasks
16
  <svg class="arrow-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
17
  <path d="M8 4l8 8-8 8" />
18
  </svg>
19
  </h3>
20
+ <div class="collapse" id="tasks-collapse"
21
+ x-effect="(() => { const c = bootstrap.Collapse.getOrCreateInstance($el, { toggle: false }); $store.sidebar.isSectionOpen('tasks') ? c.show() : c.hide(); })()">
22
  <div class="tasks-list-body">
23
  <div class="tasks-list-container" x-data>
24
  <ul class="config-list tasks-config-list" x-show="$store.tasks.tasks.length > 0">
webui/components/sidebar/top-section/header-icons.html CHANGED
@@ -62,14 +62,12 @@
62
  }
63
  #logo-container a { color: inherit; text-decoration: none; }
64
  #logo-container img { border-radius: var(--spacing-xs); width: auto; height: 2.6rem; -webkit-transition: filter 0.3s ease; transition: filter 0.3s ease; }
65
- #right-panel.expanded #logo-container { margin-left: 4.6rem; }
66
 
67
  .light-mode #logo-container img { -webkit-filter: invert(100%) grayscale(100%); filter: invert(100%) grayscale(100%); }
68
 
69
  @media (max-width: 768px) {
70
  .toggle-sidebar-button { position: fixed; left: var(--spacing-md); z-index: 1004; }
71
- #logo-container { margin-left: 4.6rem; -webkit-transition: all 0.3s ease; transition: all 0.3s ease; z-index: 1004; }
72
- #right-panel.expanded #logo-container { margin-left: 4.6rem; }
73
  }
74
  </style>
75
  </body>
 
62
  }
63
  #logo-container a { color: inherit; text-decoration: none; }
64
  #logo-container img { border-radius: var(--spacing-xs); width: auto; height: 2.6rem; -webkit-transition: filter 0.3s ease; transition: filter 0.3s ease; }
 
65
 
66
  .light-mode #logo-container img { -webkit-filter: invert(100%) grayscale(100%); filter: invert(100%) grayscale(100%); }
67
 
68
  @media (max-width: 768px) {
69
  .toggle-sidebar-button { position: fixed; left: var(--spacing-md); z-index: 1004; }
70
+ #logo-container { -webkit-transition: all 0.3s ease; transition: all 0.3s ease; z-index: 1004; }
 
71
  }
72
  </style>
73
  </body>
webui/components/welcome/welcome-screen.html CHANGED
@@ -11,7 +11,7 @@
11
  <!-- Agent Zero Logo -->
12
  <div class="welcome-logo-container">
13
  <img
14
- src="/image_get?path=webui/public/lightSymbol.svg"
15
  alt="Agent Zero Logo"
16
  class="welcome-logo"
17
  />
@@ -78,22 +78,10 @@
78
  </p>
79
  </div>
80
  </div>
81
-
82
- <!-- Footer Info -->
83
- <div class="welcome-footer">
84
- <p>Agent Zero Framework • Open Source AI Assistant</p>
85
- </div>
86
  </div>
87
  </template>
88
  </div>
89
 
90
- <script>
91
- document.addEventListener("alpine:init", () => {
92
- const s = Alpine.store("welcomeStore");
93
- if (s && typeof s.init === "function") s.init();
94
- });
95
- </script>
96
-
97
  <style>
98
  /* Welcome Screen Styles */
99
  .welcome-container {
@@ -101,38 +89,43 @@
101
  flex-direction: column;
102
  align-items: center;
103
  justify-content: center;
104
- padding: 2rem;
105
  text-align: center;
106
  background: var(--color-background);
107
  color: var(--color-text);
108
- min-height: 400px;
109
  }
110
-
111
  .welcome-logo-container {
112
  display: flex;
113
  justify-content: center;
114
  align-items: center;
115
- margin-bottom: 1.5rem;
116
  }
117
 
118
  .welcome-logo {
119
- width: 120px;
120
- height: 120px;
121
- filter: brightness(1);
122
  }
123
 
124
- /* Dark mode adjustments for logo */
 
 
 
125
  .dark-mode .welcome-logo {
126
- filter: brightness(1.2);
127
  }
128
 
129
  .welcome-title {
130
  font-size: 2rem;
131
- font-weight: 300;
132
- margin-bottom: 0.8rem;
133
  color: var(--color-text);
134
  }
135
 
 
 
 
 
136
  .welcome-subtitle {
137
  font-size: 1rem;
138
  margin-bottom: 2rem;
@@ -163,32 +156,19 @@
163
  flex-direction: column;
164
  align-items: center;
165
  text-align: center;
166
- min-height: 120px;
167
  justify-content: center;
168
  }
169
 
170
  .welcome-action-card:hover {
171
- border-color: var(--color-accent);
172
  background: var(--color-message-bg);
173
- transform: translateY(-2px);
174
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
175
- }
176
-
177
- .dark-mode .welcome-action-card:hover {
178
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
179
  }
180
 
181
  .welcome-action-icon {
182
  font-size: 2rem;
183
- margin-bottom: 0.8rem;
184
- color: var(--color-accent);
185
- }
186
-
187
- .welcome-action-title {
188
- font-size: 1.1rem;
189
- font-weight: 500;
190
- margin-bottom: 0.4rem;
191
- color: var(--color-text);
192
  }
193
 
194
  .welcome-action-description {
@@ -197,12 +177,6 @@
197
  line-height: 1.3;
198
  }
199
 
200
- .welcome-footer {
201
- margin-top: 1.5rem;
202
- opacity: 0.7;
203
- font-size: 0.8rem;
204
- }
205
-
206
  /* Light mode adjustments */
207
  .light-mode .welcome-title {
208
  color: #2d2d2d;
@@ -226,37 +200,41 @@
226
 
227
  /* Responsive design */
228
  @media (max-width: 768px) {
229
- .welcome-container {
230
- padding: 1rem;
231
- }
232
-
233
- .welcome-title {
234
- font-size: 2rem;
235
- }
236
-
237
- .welcome-subtitle {
238
- font-size: 1rem;
239
- margin-bottom: 2rem;
240
- }
241
-
242
- .welcome-actions {
243
- grid-template-columns: 1fr;
244
- gap: 1rem;
245
- margin-bottom: 2rem;
246
- }
247
-
248
- .welcome-action-card {
249
- padding: 1.5rem;
250
- min-height: 120px;
251
- }
252
-
253
- .welcome-action-icon {
254
- font-size: 2rem;
255
- }
256
-
257
- .welcome-action-title {
258
- font-size: 1.1rem;
259
- }
 
 
 
 
260
  }
261
  </style>
262
  </body>
 
11
  <!-- Agent Zero Logo -->
12
  <div class="welcome-logo-container">
13
  <img
14
+ src="./public/darkSymbol.svg"
15
  alt="Agent Zero Logo"
16
  class="welcome-logo"
17
  />
 
78
  </p>
79
  </div>
80
  </div>
 
 
 
 
 
81
  </div>
82
  </template>
83
  </div>
84
 
 
 
 
 
 
 
 
85
  <style>
86
  /* Welcome Screen Styles */
87
  .welcome-container {
 
89
  flex-direction: column;
90
  align-items: center;
91
  justify-content: center;
92
+ padding: 3%;
93
  text-align: center;
94
  background: var(--color-background);
95
  color: var(--color-text);
96
+ min-height: 100vh;
97
  }
98
+ /* Welcome container styles */
99
  .welcome-logo-container {
100
  display: flex;
101
  justify-content: center;
102
  align-items: center;
 
103
  }
104
 
105
  .welcome-logo {
106
+ width: 7rem;
107
+ height: 7rem;
 
108
  }
109
 
110
+ /* Themes adjustments for logo */
111
+ .light-mode .welcome-logo {
112
+ filter: invert(0.5);
113
+ }
114
  .dark-mode .welcome-logo {
115
+ filter: invert(0.8);
116
  }
117
 
118
  .welcome-title {
119
  font-size: 2rem;
120
+ font-weight: 400;
121
+ margin: var(--spacing-xs) 0 0 0;
122
  color: var(--color-text);
123
  }
124
 
125
+ .welcome-action-title {
126
+ margin-bottom: 0;
127
+ }
128
+
129
  .welcome-subtitle {
130
  font-size: 1rem;
131
  margin-bottom: 2rem;
 
156
  flex-direction: column;
157
  align-items: center;
158
  text-align: center;
159
+ min-height: 10rem;
160
  justify-content: center;
161
  }
162
 
163
  .welcome-action-card:hover {
164
+ border-color: var(--color-accent-dark);
165
  background: var(--color-message-bg);
 
 
 
 
 
 
166
  }
167
 
168
  .welcome-action-icon {
169
  font-size: 2rem;
170
+ margin-bottom: 0;
171
+ color: var(--color-accent-dark);
 
 
 
 
 
 
 
172
  }
173
 
174
  .welcome-action-description {
 
177
  line-height: 1.3;
178
  }
179
 
 
 
 
 
 
 
180
  /* Light mode adjustments */
181
  .light-mode .welcome-title {
182
  color: #2d2d2d;
 
200
 
201
  /* Responsive design */
202
  @media (max-width: 768px) {
203
+ .welcome-container {
204
+ padding: 1rem;
205
+ padding-top: 3rem;
206
+ }
207
+ .welcome-logo {
208
+ width: 5rem;
209
+ height: 5rem;
210
+ }
211
+ .welcome-logo-container {
212
+ margin-top: 2rem;
213
+ }
214
+ .welcome-title {
215
+ font-size: 1.5rem;
216
+ margin: 0 !important;
217
+ }
218
+ .welcome-subtitle {
219
+ font-size: 1rem;
220
+ margin-top: var(--spacing-xs);
221
+ margin-bottom: 0;
222
+ }
223
+ .welcome-actions {
224
+ grid-template-columns: 1fr;
225
+ gap: 1rem;
226
+ margin-bottom: 2rem;
227
+ }
228
+ .welcome-action-card {
229
+ padding: 1.5rem;
230
+ min-height: 120px;
231
+ }
232
+ .welcome-action-icon {
233
+ font-size: 2rem;
234
+ }
235
+ .welcome-action-title {
236
+ font-size: 1.1rem;
237
+ }
238
  }
239
  </style>
240
  </body>
webui/index.css CHANGED
@@ -164,9 +164,23 @@ img {
164
  transition: margin-left var(--transition-speed) ease-in-out;
165
  }
166
 
 
167
  #right-panel.expanded {
168
  margin-left: 0;
169
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
  #time-date-container {
172
  z-index: 1000;
 
164
  transition: margin-left var(--transition-speed) ease-in-out;
165
  }
166
 
167
+ /* Scrollbar styles for right panel */
168
  #right-panel.expanded {
169
  margin-left: 0;
170
  }
171
+ div#right-panel::-webkit-scrollbar {
172
+ width: 6px;
173
+ }
174
+ div#right-panel::-webkit-scrollbar-track {
175
+ background: transparent;
176
+ }
177
+ div#right-panel::-webkit-scrollbar-thumb {
178
+ background-color: rgba(155, 155, 155, 0.3);
179
+ border-radius: 6px;
180
+ }
181
+ div#right-panel::-webkit-scrollbar-thumb:hover {
182
+ background-color: rgba(155, 155, 155, 0.5);
183
+ }
184
 
185
  #time-date-container {
186
  z-index: 1000;
webui/index.html CHANGED
@@ -128,8 +128,6 @@
128
 
129
  <!-- Non-module scripts after Alpine.js -->
130
  <script type="text/javascript" src="js/settings.js"></script>
131
- <script type="text/javascript" src="js/file_browser.js"></script>
132
- <script type="module" src="js/tunnel.js"></script>
133
  <script>
134
  // Expose git info for sidebar component
135
  globalThis.gitinfo = { version: "{{version_no}}", commit_time: "{{version_time}}" };
@@ -139,10 +137,11 @@
139
  <body class="dark-mode device-pointer" x-data>
140
  <div class="container">
141
  <!-- Sidebar Overlay -->
142
- <template x-if="$store.sidebar">
143
- <div id="sidebar-overlay" class="sidebar-overlay" x-data
144
- :class="{'visible': $store.sidebar.isOpen && $store.sidebar.isMobile()}"
145
- @click="$store.sidebar.close()"></div>
 
146
  </template>
147
  <!-- Left Sidebar (Header Icons, Quick Actions, Tabs, Chats, Tasks) -->
148
  <x-component path="sidebar/left-sidebar.html"></x-component>
@@ -190,10 +189,13 @@
190
  <button class="toast__copy" style="display: none;">Copy</button>
191
  <button class="toast__close">Close</button>
192
  </div>
193
- <!-- Progress Bar -->
194
- <x-component path="chat/input/progress.html"></x-component>
195
- <!-- Input Section -->
196
- <x-component path="chat/input/chat-bar.html"></x-component>
 
 
 
197
  </div>
198
  </div>
199
  <div id="settingsModal" x-data="settingsModalProxy">
 
128
 
129
  <!-- Non-module scripts after Alpine.js -->
130
  <script type="text/javascript" src="js/settings.js"></script>
 
 
131
  <script>
132
  // Expose git info for sidebar component
133
  globalThis.gitinfo = { version: "{{version_no}}", commit_time: "{{version_time}}" };
 
137
  <body class="dark-mode device-pointer" x-data>
138
  <div class="container">
139
  <!-- Sidebar Overlay -->
140
+ <div id="sidebar-overlay" x-data>
141
+ <template x-if="$store.sidebar">
142
+ <div class="sidebar-overlay" :class="{'visible': $store.sidebar.isOpen && $store.sidebar.isMobile()}" @click="$store.sidebar.close()"></div>
143
+ </template>
144
+ </div>
145
  </template>
146
  <!-- Left Sidebar (Header Icons, Quick Actions, Tabs, Chats, Tasks) -->
147
  <x-component path="sidebar/left-sidebar.html"></x-component>
 
189
  <button class="toast__copy" style="display: none;">Copy</button>
190
  <button class="toast__close">Close</button>
191
  </div>
192
+ <!-- Chat Input Area -->
193
+ <div x-show="!$store.welcomeStore || !$store.welcomeStore.isVisible">
194
+ <!-- Progress Bar -->
195
+ <x-component path="chat/input/progress.html"></x-component>
196
+ <!-- Input Section -->
197
+ <x-component path="chat/input/chat-bar.html"></x-component>
198
+ </div>
199
  </div>
200
  </div>
201
  <div id="settingsModal" x-data="settingsModalProxy">
webui/index.js CHANGED
@@ -331,26 +331,37 @@ async function poll() {
331
  // Check if this context exists in the chats list
332
  const contextExists = chatsStore.contains(context);
333
 
334
- // If it doesn't exist in the chats list, try to select the first chat
335
- if (!contextExists && chatsStore.contexts.length > 0) {
336
- const firstChatId = chatsStore.firstId();
337
- if (firstChatId) {
338
- setContext(firstChatId);
339
- chatsStore.setSelected(firstChatId);
 
 
 
 
 
340
  }
 
 
341
  }
342
-
343
- tasksStore.setSelected(context);
344
  } else {
345
- // No context selected, try to select the first available item
346
- if (contexts.length > 0) {
347
- const firstChatId = chatsStore.firstId();
348
- if (firstChatId) {
349
- setContext(firstChatId);
350
- chatsStore.setSelected(firstChatId);
351
- }
 
 
 
 
 
352
  }
353
  }
 
354
 
355
  lastLogVersion = response.log_version;
356
  lastLogGuid = response.log_guid;
@@ -617,6 +628,18 @@ document.addEventListener("DOMContentLoaded", function () {
617
  chatHistory.addEventListener("scroll", updateAfterScroll);
618
  }
619
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  // Start polling for updates
621
  startPolling();
622
  });
 
331
  // Check if this context exists in the chats list
332
  const contextExists = chatsStore.contains(context);
333
 
334
+ if (!contextExists) {
335
+ if (chatsStore.contexts.length > 0) {
336
+ // If it doesn't exist in the list but other contexts do, fall back to the first
337
+ const firstChatId = chatsStore.firstId();
338
+ if (firstChatId) {
339
+ setContext(firstChatId);
340
+ chatsStore.setSelected(firstChatId);
341
+ }
342
+ } else if (typeof deselectChat === "function") {
343
+ // No contexts remain – clear state so the welcome screen can surface
344
+ deselectChat();
345
  }
346
+ } else {
347
+ tasksStore.setSelected(context);
348
  }
 
 
349
  } else {
350
+ const welcomeStore =
351
+ globalThis.Alpine && typeof globalThis.Alpine.store === "function"
352
+ ? globalThis.Alpine.store("welcomeStore")
353
+ : null;
354
+ const welcomeVisible = Boolean(welcomeStore && welcomeStore.isVisible);
355
+
356
+ // No context selected, try to select the first available item unless welcome screen is active
357
+ if (!welcomeVisible && contexts.length > 0) {
358
+ const firstChatId = chatsStore.firstId();
359
+ if (firstChatId) {
360
+ setContext(firstChatId);
361
+ chatsStore.setSelected(firstChatId);
362
  }
363
  }
364
+ }
365
 
366
  lastLogVersion = response.log_version;
367
  lastLogGuid = response.log_guid;
 
628
  chatHistory.addEventListener("scroll", updateAfterScroll);
629
  }
630
 
631
+ // Restore previously selected context (if any) before polling begins
632
+ let storedContext = null;
633
+ try {
634
+ storedContext = localStorage.getItem("lastSelectedChat");
635
+ } catch (_e) {
636
+ storedContext = null;
637
+ }
638
+
639
+ if (storedContext && storedContext !== "null" && storedContext !== "") {
640
+ setContext(storedContext);
641
+ }
642
+
643
  // Start polling for updates
644
  startPolling();
645
  });