Skip to content

Commit d344daf

Browse files
authored
feat(models-ui): minor visual enhancements (#2109)
Show image if present, URL, tags, and better display buttons Signed-off-by: Ettore Di Giacinto <[email protected]>
1 parent 3411e07 commit d344daf

File tree

3 files changed

+96
-28
lines changed

3 files changed

+96
-28
lines changed

core/http/elements/gallery.go

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ func StartProgressBar(uid, progress string) string {
8686
).Render()
8787
}
8888

89+
func cardSpan(text, icon string) elem.Node {
90+
return elem.Span(
91+
attrs.Props{
92+
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2",
93+
},
94+
elem.I(attrs.Props{
95+
"class": icon + " pr-2",
96+
}),
97+
elem.Text(text),
98+
)
99+
}
100+
89101
func ListModels(models []*gallery.GalleryModel) string {
90102
modelsElements := []elem.Node{}
91103
span := func(s string) elem.Node {
@@ -99,10 +111,17 @@ func ListModels(models []*gallery.GalleryModel) string {
99111
installButton := func(m *gallery.GalleryModel) elem.Node {
100112
return elem.Button(
101113
attrs.Props{
102-
"class": "float-right inline-block rounded bg-primary px-6 pb-2 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",
114+
"data-twe-ripple-init": "",
115+
"data-twe-ripple-color": "light",
116+
"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",
103117
// post the Model ID as param
104118
"hx-post": "/browse/install/model/" + fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name),
105119
},
120+
elem.I(
121+
attrs.Props{
122+
"class": "fa-solid fa-download pr-2",
123+
},
124+
),
106125
elem.Text("Install"),
107126
)
108127
}
@@ -111,7 +130,7 @@ func ListModels(models []*gallery.GalleryModel) string {
111130

112131
return elem.Div(
113132
attrs.Props{
114-
"class": "p-6",
133+
"class": "p-6 text-surface dark:text-white",
115134
},
116135
elem.H5(
117136
attrs.Props{
@@ -129,42 +148,93 @@ func ListModels(models []*gallery.GalleryModel) string {
129148
}
130149

131150
actionDiv := func(m *gallery.GalleryModel) elem.Node {
151+
nodes := []elem.Node{
152+
cardSpan("Repository: "+m.Gallery.Name, "fa-brands fa-git-alt"),
153+
}
154+
155+
if m.License != "" {
156+
nodes = append(nodes,
157+
cardSpan("License: "+m.License, "fas fa-book"),
158+
)
159+
}
160+
161+
for _, tag := range m.Tags {
162+
nodes = append(nodes,
163+
cardSpan(tag, "fas fa-tag"),
164+
)
165+
}
166+
167+
for i, url := range m.URLs {
168+
nodes = append(nodes,
169+
elem.A(
170+
attrs.Props{
171+
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2",
172+
"href": url,
173+
"target": "_blank",
174+
},
175+
elem.I(attrs.Props{
176+
"class": "fas fa-link pr-2",
177+
}),
178+
elem.Text("Link #"+fmt.Sprintf("%d", i+1)),
179+
))
180+
}
181+
132182
return elem.Div(
133183
attrs.Props{
134184
"class": "px-6 pt-4 pb-2",
135185
},
136-
elem.Span(
186+
elem.P(
137187
attrs.Props{
138-
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2",
188+
"class": "mb-4 text-base",
139189
},
140-
elem.Text("Repository: "+m.Gallery.Name),
190+
nodes...,
141191
),
142192
elem.If(m.Installed, span("Installed"), installButton(m)),
143193
)
144194
}
145195

146196
for _, m := range models {
197+
198+
elems := []elem.Node{}
199+
200+
if m.Icon != "" {
201+
elems = append(elems,
202+
203+
elem.Div(attrs.Props{
204+
"class": "flex justify-center items-center",
205+
},
206+
elem.A(attrs.Props{
207+
"href": "#!",
208+
// "class": "justify-center items-center",
209+
},
210+
elem.Img(attrs.Props{
211+
// "class": "rounded-t-lg object-fit object-center h-96",
212+
"class": "rounded-t-lg max-h-48 max-w-96 object-cover",
213+
"src": m.Icon,
214+
}),
215+
),
216+
))
217+
}
218+
219+
elems = append(elems, descriptionDiv(m), actionDiv(m))
147220
modelsElements = append(modelsElements,
148221
elem.Div(
149222
attrs.Props{
150-
"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 p-2",
223+
"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",
151224
},
152225
elem.Div(
153226
attrs.Props{
154-
"class": "p-6",
227+
// "class": "p-6",
155228
},
156-
descriptionDiv(m),
157-
actionDiv(m),
158-
// elem.If(m.Installed, span("Installed"), installButton(m)),
159-
160-
// elem.If(m.Installed, span("Installed"), span("Not Installed")),
229+
elems...,
161230
),
162231
),
163232
)
164233
}
165234

166235
wrapper := elem.Div(attrs.Props{
167-
"class": "dark grid grid-cols-1 grid-rows-1 md:grid-cols-2 ",
236+
"class": "dark grid grid-cols-1 grid-rows-1 md:grid-cols-3 block rounded-lg shadow-secondary-1 dark:bg-surface-dark",
237+
//"class": "block rounded-lg bg-white shadow-secondary-1 dark:bg-surface-dark",
168238
}, modelsElements...)
169239

170240
return wrapper.Render()

core/http/routes/ui.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ func RegisterUIRoutes(app *fiber.App,
2626
models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
2727

2828
summary := fiber.Map{
29-
"Title": "LocalAI API - Models",
30-
"Models": template.HTML(elements.ListModels(models)),
29+
"Title": "LocalAI - Models",
30+
"Models": template.HTML(elements.ListModels(models)),
31+
"Repositories": appConfig.Galleries,
3132
// "ApplicationConfig": appConfig,
3233
}
3334

@@ -49,7 +50,10 @@ func RegisterUIRoutes(app *fiber.App,
4950

5051
filteredModels := []*gallery.GalleryModel{}
5152
for _, m := range models {
52-
if strings.Contains(m.Name, form.Search) {
53+
if strings.Contains(m.Name, form.Search) ||
54+
strings.Contains(m.Description, form.Search) ||
55+
strings.Contains(m.Gallery.Name, form.Search) ||
56+
strings.Contains(strings.Join(m.Tags, ","), form.Search) {
5357
filteredModels = append(filteredModels, m)
5458
}
5559
}

core/http/views/models.html

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,14 @@
77

88
{{template "views/partials/navbar" .}}
99
<div class="container mx-auto px-4 flex-grow">
10-
<div class="header text-center py-12">
11-
<h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1>
12-
<div class="mt-6">
13-
<!-- Logo can be uncommented and updated with a valid URL -->
14-
</div>
15-
<p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p>
16-
<a href="https://localai.io" target="_blank" class="mt-4 inline-block bg-blue-500 text-white py-2 px-4 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg">
17-
<i class="fas fa-book-reader pr-2"></i>Documentation
18-
</a>
19-
</div>
2010

2111
<div class="models mt-12">
22-
<h2 class="text-center text-3xl font-semibold text-gray-100">Available models from repositories</h2>
23-
12+
<h2 class="text-center text-3xl font-semibold text-gray-100">
13+
🖼️ Available models from <i>{{ len .Repositories }}</i> repositories <a href="https://localai.io/models/" target="_blank" >
14+
<i class="fas fa-circle-info pr-2"></i>
15+
</a></h2>
16+
17+
2418
<span class="htmx-indicator loader"></span>
2519
<input class="form-control appearance-none block w-full px-3 py-2 text-base font-normal text-gray-300 pb-2 mb-5 bg-gray-800 bg-clip-padding border border-solid border-gray-600 rounded transition ease-in-out m-0 focus:text-gray-300 focus:bg-gray-900 focus:border-blue-500 focus:outline-none" type="search"
2620
name="search" placeholder="Begin Typing To Search models..."

0 commit comments

Comments
 (0)