Skip to content

Commit 755e4fb

Browse files
authored
feat(ui): improvements to index and models page (mudler#4918)
- mobile-friendly index - adjust color palette - improve search experience Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent e4fdde1 commit 755e4fb

File tree

8 files changed

+519
-385
lines changed

8 files changed

+519
-385
lines changed

core/http/elements/buttons.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func installButton(galleryName string) elem.Node {
1313
attrs.Props{
1414
"data-twe-ripple-init": "",
1515
"data-twe-ripple-color": "light",
16-
"class": "float-right inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
16+
"class": "float-right inline-flex items-center rounded-lg bg-blue-600 hover:bg-blue-700 px-4 py-2 text-sm font-medium text-white transition duration-300 ease-in-out shadow hover:shadow-lg",
1717
"hx-swap": "outerHTML",
1818
// post the Model ID as param
1919
"hx-post": "browse/install/model/" + galleryName,
@@ -52,7 +52,7 @@ func infoButton(m *gallery.GalleryModel) elem.Node {
5252
attrs.Props{
5353
"data-twe-ripple-init": "",
5454
"data-twe-ripple-color": "light",
55-
"class": "float-left inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
55+
"class": "inline-flex items-center rounded-lg bg-gray-700 hover:bg-gray-600 px-4 py-2 text-sm font-medium text-white transition duration-300 ease-in-out",
5656
"data-modal-target": modalName(m),
5757
"data-modal-toggle": modalName(m),
5858
},

core/http/elements/gallery.go

+111-106
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717
func cardSpan(text, icon string) elem.Node {
1818
return elem.Span(
1919
attrs.Props{
20-
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2",
20+
"class": "inline-flex items-center px-3 py-1 rounded-lg text-xs font-medium bg-gray-700/70 text-gray-300 border border-gray-600/50 mr-2 mb-2",
2121
},
2222
elem.I(attrs.Props{
2323
"class": icon + " pr-2",
@@ -39,19 +39,20 @@ func searchableElement(text, icon string) elem.Node {
3939
),
4040
elem.Span(
4141
attrs.Props{
42-
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2",
42+
"class": "inline-flex items-center text-xs px-3 py-1 rounded-full bg-gray-700/60 text-gray-300 border border-gray-600/50 hover:bg-gray-600 hover:text-gray-100 transition duration-200 ease-in-out",
4343
},
4444
elem.A(
4545
attrs.Props{
4646
// "name": "search",
4747
// "value": text,
4848
//"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2",
49-
"href": "#!",
50-
"hx-post": "browse/search/models",
51-
"hx-target": "#search-results",
49+
//"href": "#!",
50+
"href": "browse?term=" + text,
51+
//"hx-post": "browse/search/models",
52+
//"hx-target": "#search-results",
5253
// TODO: this doesn't work
5354
// "hx-vals": `{ \"search\": \"` + text + `\" }`,
54-
"hx-indicator": ".htmx-indicator",
55+
//"hx-indicator": ".htmx-indicator",
5556
},
5657
elem.I(attrs.Props{
5758
"class": icon + " pr-2",
@@ -101,7 +102,7 @@ func modalName(m *gallery.GalleryModel) string {
101102
return m.Name + "-modal"
102103
}
103104

104-
func modelDescription(m *gallery.GalleryModel) elem.Node {
105+
func modelModal(m *gallery.GalleryModel) elem.Node {
105106
urls := []elem.Node{}
106107
for _, url := range m.URLs {
107108
urls = append(urls,
@@ -118,137 +119,140 @@ func modelDescription(m *gallery.GalleryModel) elem.Node {
118119

119120
return elem.Div(
120121
attrs.Props{
121-
"class": "p-6 text-surface dark:text-white",
122+
"id": modalName(m),
123+
"tabindex": "-1",
124+
"aria-hidden": "true",
125+
"class": "hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full",
122126
},
123-
elem.H5(
124-
attrs.Props{
125-
"class": "mb-2 text-xl font-bold leading-tight",
126-
},
127-
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
128-
),
129-
elem.Div( // small description
130-
attrs.Props{
131-
"class": "mb-4 text-sm truncate text-base",
132-
},
133-
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
134-
),
135-
136127
elem.Div(
137128
attrs.Props{
138-
"id": modalName(m),
139-
"tabindex": "-1",
140-
"aria-hidden": "true",
141-
"class": "hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full",
129+
"class": "relative p-4 w-full max-w-2xl max-h-full",
142130
},
143131
elem.Div(
144132
attrs.Props{
145-
"class": "relative p-4 w-full max-w-2xl max-h-full",
133+
"class": "relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700",
146134
},
135+
// header
147136
elem.Div(
148137
attrs.Props{
149-
"class": "relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700",
138+
"class": "flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600",
150139
},
151-
// header
152-
elem.Div(
140+
elem.H3(
153141
attrs.Props{
154-
"class": "flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600",
142+
"class": "text-xl font-semibold text-gray-900 dark:text-white",
155143
},
156-
elem.H3(
157-
attrs.Props{
158-
"class": "text-xl font-semibold text-gray-900 dark:text-white",
159-
},
160-
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
144+
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
145+
),
146+
elem.Button( // close button
147+
attrs.Props{
148+
"class": "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white",
149+
"data-modal-hide": modalName(m),
150+
},
151+
elem.Raw(
152+
`<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
153+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
154+
</svg>`,
161155
),
162-
elem.Button( // close button
156+
elem.Span(
163157
attrs.Props{
164-
"class": "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white",
165-
"data-modal-hide": modalName(m),
158+
"class": "sr-only",
166159
},
167-
elem.Raw(
168-
`<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
169-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
170-
</svg>`,
171-
),
172-
elem.Span(
173-
attrs.Props{
174-
"class": "sr-only",
175-
},
176-
elem.Text("Close modal"),
177-
),
160+
elem.Text("Close modal"),
178161
),
179162
),
180-
// body
163+
),
164+
// body
165+
elem.Div(
166+
attrs.Props{
167+
"class": "p-4 md:p-5 space-y-4",
168+
},
181169
elem.Div(
182170
attrs.Props{
183-
"class": "p-4 md:p-5 space-y-4",
171+
"class": "flex justify-center items-center",
172+
},
173+
elem.Img(attrs.Props{
174+
// "class": "rounded-t-lg object-fit object-center h-96",
175+
"class": "lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded",
176+
"src": m.Icon,
177+
"loading": "lazy",
178+
}),
179+
),
180+
elem.P(
181+
attrs.Props{
182+
"class": "text-base leading-relaxed text-gray-500 dark:text-gray-400",
184183
},
184+
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
185+
),
186+
elem.Hr(
187+
attrs.Props{},
188+
),
189+
elem.P(
190+
attrs.Props{
191+
"class": "text-sm font-semibold text-gray-900 dark:text-white",
192+
},
193+
elem.Text("Links"),
194+
),
195+
elem.Ul(
196+
attrs.Props{},
197+
urls...,
198+
),
199+
elem.If(
200+
len(m.Tags) > 0,
185201
elem.Div(
186-
attrs.Props{
187-
"class": "flex justify-center items-center",
188-
},
189-
elem.Img(attrs.Props{
190-
// "class": "rounded-t-lg object-fit object-center h-96",
191-
"class": "lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded",
192-
"src": m.Icon,
193-
"loading": "lazy",
194-
}),
195-
),
196-
elem.P(
197-
attrs.Props{
198-
"class": "text-base leading-relaxed text-gray-500 dark:text-gray-400",
199-
},
200-
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
201-
),
202-
elem.Hr(
203202
attrs.Props{},
204-
),
205-
elem.P(
206-
attrs.Props{
207-
"class": "text-sm font-semibold text-gray-900 dark:text-white",
208-
},
209-
elem.Text("Links"),
210-
),
211-
elem.Ul(
212-
attrs.Props{},
213-
urls...,
214-
),
215-
elem.If(
216-
len(m.Tags) > 0,
203+
elem.P(
204+
attrs.Props{
205+
"class": "text-sm mb-5 font-semibold text-gray-900 dark:text-white",
206+
},
207+
elem.Text("Tags"),
208+
),
217209
elem.Div(
218-
attrs.Props{},
219-
elem.P(
220-
attrs.Props{
221-
"class": "text-sm mb-5 font-semibold text-gray-900 dark:text-white",
222-
},
223-
elem.Text("Tags"),
224-
),
225-
elem.Div(
226-
attrs.Props{
227-
"class": "flex flex-row flex-wrap content-center",
228-
},
229-
tagsNodes...,
230-
),
210+
attrs.Props{
211+
"class": "flex flex-row flex-wrap content-center",
212+
},
213+
tagsNodes...,
231214
),
232-
elem.Div(attrs.Props{}),
233215
),
216+
elem.Div(attrs.Props{}),
234217
),
235-
// Footer
236-
elem.Div(
218+
),
219+
// Footer
220+
elem.Div(
221+
attrs.Props{
222+
"class": "flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600",
223+
},
224+
elem.Button(
237225
attrs.Props{
238-
"class": "flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600",
226+
"data-modal-hide": modalName(m),
227+
"class": "py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700",
239228
},
240-
elem.Button(
241-
attrs.Props{
242-
"data-modal-hide": modalName(m),
243-
"class": "py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700",
244-
},
245-
elem.Text("Close"),
246-
),
229+
elem.Text("Close"),
247230
),
248231
),
249232
),
250233
),
251234
)
235+
236+
}
237+
238+
func modelDescription(m *gallery.GalleryModel) elem.Node {
239+
return elem.Div(
240+
attrs.Props{
241+
"class": "p-6 text-surface dark:text-white",
242+
},
243+
elem.H5(
244+
attrs.Props{
245+
"class": "mb-2 text-xl font-bold leading-tight",
246+
},
247+
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
248+
),
249+
elem.Div( // small description
250+
attrs.Props{
251+
"class": "mb-4 text-sm truncate text-base",
252+
},
253+
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
254+
),
255+
)
252256
}
253257

254258
func modelActionItems(m *gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) elem.Node {
@@ -397,7 +401,7 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g
397401
modelsElements = append(modelsElements,
398402
elem.Div(
399403
attrs.Props{
400-
"class": " me-4 mb-2 block rounded-lg bg-white shadow-secondary-1 dark:bg-gray-800 dark:bg-surface-dark dark:text-white text-surface pb-2",
404+
"class": " me-4 mb-2 block rounded-lg bg-white shadow-secondary-1 dark:bg-gray-800 dark:bg-surface-dark dark:text-white text-surface pb-2 bg-gray-800/90 border border-gray-700/50 rounded-xl overflow-hidden transition-all duration-300 hover:shadow-lg hover:shadow-blue-900/20 hover:-translate-y-1 hover:border-blue-700/50",
401405
},
402406
elem.Div(
403407
attrs.Props{
@@ -406,6 +410,7 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g
406410
elems...,
407411
),
408412
),
413+
modelModal(m),
409414
)
410415
}
411416

core/http/static/chat.js

+19-6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ SOFTWARE.
2727
2828
*/
2929

30+
function toggleLoader(show) {
31+
const loader = document.getElementById('loader');
32+
const sendButton = document.getElementById('send-button');
33+
34+
if (show) {
35+
loader.style.display = 'block';
36+
sendButton.style.display = 'none';
37+
document.getElementById("input").disabled = true;
38+
} else {
39+
document.getElementById("input").disabled = false;
40+
loader.style.display = 'none';
41+
sendButton.style.display = 'block';
42+
}
43+
}
44+
3045
function submitKey(event) {
3146
event.preventDefault();
3247
localStorage.setItem("key", document.getElementById("apiKey").value);
@@ -72,8 +87,8 @@ function readInputImage() {
7287
// Set class "loader" to the element with "loader" id
7388
//document.getElementById("loader").classList.add("loader");
7489
// Make the "loader" visible
75-
document.getElementById("loader").style.display = "block";
76-
document.getElementById("input").disabled = true;
90+
toggleLoader(true);
91+
7792

7893
messages = Alpine.store("chat").messages();
7994

@@ -243,10 +258,8 @@ function readInputImage() {
243258
}
244259

245260
// Remove class "loader" from the element with "loader" id
246-
//document.getElementById("loader").classList.remove("loader");
247-
document.getElementById("loader").style.display = "none";
248-
// enable input
249-
document.getElementById("input").disabled = false;
261+
toggleLoader(false);
262+
250263
// scroll to the bottom of the chat
251264
document.getElementById('messages').scrollIntoView(false)
252265
// set focus to the input

core/http/static/general.css

-12
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,6 @@ body {
1010
.htmx-request .htmx-indicator{
1111
opacity:1
1212
}
13-
/* Loader (https://cssloaders.github.io/) */
14-
.loader {
15-
width: 12px;
16-
height: 12px;
17-
border-radius: 50%;
18-
display: block;
19-
margin:15px auto;
20-
position: relative;
21-
color: #FFF;
22-
box-sizing: border-box;
23-
animation: animloader 2s linear infinite;
24-
}
2513

2614
@keyframes animloader {
2715
0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }

0 commit comments

Comments
 (0)