Skip to content

Commit 768e76a

Browse files
committed
[UI] add images, tags, descriptions to projects & templates (#873)
1 parent 5b4ff25 commit 768e76a

File tree

19 files changed

+887
-89
lines changed

19 files changed

+887
-89
lines changed

services/app/src/lib/components/preset/ModelSelect.svelte

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,21 +172,24 @@
172172
<Command.Root shouldFilter={false}>
173173
<Command.Input bind:value={searchQuery} placeholder="Search models..." />
174174
<Command.List>
175-
<Command.Empty>No models found.</Command.Empty>
175+
{@const results = (
176+
searchQuery.trim() !== '' ? fuse.search(searchQuery).map((result) => result.item) : models
177+
)
178+
.filter((model) => {
179+
if (!capabilityFilter || model.capabilities.includes(capabilityFilter)) {
180+
return true;
181+
}
182+
})
183+
.sort((a, b) => {
184+
if (a.id === selectedModel) return -1;
185+
if (b.id === selectedModel) return 1;
186+
return 0;
187+
})}
188+
{#if results.length === 0}
189+
<Command.Empty forceMount>No models found.</Command.Empty>
190+
{/if}
176191
<Command.Group value="models">
177-
{#each (searchQuery.trim() !== '' ? fuse
178-
.search(searchQuery)
179-
.map((result) => result.item) : models)
180-
.filter((model) => {
181-
if (!capabilityFilter || model.capabilities.includes(capabilityFilter)) {
182-
return true;
183-
}
184-
})
185-
.sort((a, b) => {
186-
if (a.id === selectedModel) return -1;
187-
if (b.id === selectedModel) return 1;
188-
return 0;
189-
}) as { id, name, languages, capabilities, owned_by }}
192+
{#each results as { id, name, languages, capabilities, owned_by }}
190193
<!-- TODO: simplify this -->
191194
{@const isDisabled =
192195
(owned_by !== 'ellm' &&

services/app/src/lib/components/tables/(sub)/HowToUseTab.svelte

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script lang="ts">
22
import { guideConverter as converter } from '$lib/showdown';
33
import type { GenTableCol } from '$lib/types';
4-
import '$lib/showdown/showdown-theme.css';
54
65
import llmHowToUse from './guides/llm.md?raw';
76
import pythonHowToUse from './guides/python.md?raw';

services/app/src/lib/components/tables/ActionTable.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@
470470
</p>
471471
{/if}
472472

473-
{#if column.gen_config?.object === 'gen_config.llm' && column.dtype === 'str'}
473+
{#if column.dtype === 'str'}
474474
<div
475475
class="absolute right-2 top-2 flex gap-1 opacity-0 transition-opacity group-focus-within/cell:opacity-100 group-hover/cell:opacity-100"
476476
>

services/app/src/lib/components/tables/ChatTable.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@
468468
</p>
469469
{/if}
470470

471-
{#if column.gen_config?.object === 'gen_config.llm' && column.dtype === 'str'}
471+
{#if column.dtype === 'str'}
472472
<div
473473
class="absolute right-2 top-2 flex gap-1 opacity-0 transition-opacity group-focus-within/cell:opacity-100 group-hover/cell:opacity-100"
474474
>

services/app/src/lib/components/tables/KnowledgeTable.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@
457457
</p>
458458
{/if}
459459

460-
{#if column.gen_config?.object === 'gen_config.llm' && column.dtype === 'str'}
460+
{#if column.dtype === 'str'}
461461
<div
462462
class="absolute right-2 top-2 flex gap-1 opacity-0 transition-opacity group-focus-within/cell:opacity-100 group-hover/cell:opacity-100"
463463
>

services/app/src/lib/constants.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,18 @@ export const fileColumnFiletypes = [
237237
{ ext: '.jsonl', type: 'document' }
238238
];
239239

240+
export const tagColors = [
241+
'#e74d73',
242+
'#4db2d6',
243+
'#4d69e8',
244+
'#e8b04d',
245+
'#9b4de8',
246+
'#e84d4d',
247+
'#4de8e1',
248+
'#6f4de8',
249+
'#e84db8'
250+
];
251+
240252
export const agentColors: { bg: string; text: string }[] = [
241253
{
242254
bg: '#FFD9E4',

services/app/src/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ export type Project = {
495495
updated_at: string;
496496
id: string;
497497
name: string;
498+
description: string;
498499
quotas: Record<string, unknown>;
499500
tags: string[];
500501
profile_picture_url: string | null;

services/app/src/routes/(main)/SideDock.svelte

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,8 @@
4545
4646
const items = [
4747
{
48-
type: 'link',
49-
title: m['left_dock.analytics'](),
50-
href: '/analytics',
51-
iconClass: '[&_path]:stroke-[1.7]',
52-
Icon: ChartLine
48+
type: 'category',
49+
title: 'ORGANIZATION'
5350
},
5451
{
5552
type: 'link',
@@ -61,9 +58,10 @@
6158
},
6259
{
6360
type: 'link',
64-
title: m['left_dock.organization'](),
65-
href: '/organization',
66-
Icon: PeopleIcon
61+
title: m['left_dock.analytics'](),
62+
href: '/analytics',
63+
iconClass: '[&_path]:stroke-[1.7]',
64+
Icon: ChartLine
6765
},
6866
{
6967
type: 'link',
@@ -72,9 +70,15 @@
7270
Icon: Compass,
7371
exclude: page.data.ossMode
7472
},
73+
{
74+
type: 'link',
75+
title: m['left_dock.organization'](),
76+
href: '/organization',
77+
Icon: PeopleIcon
78+
},
7579
{
7680
type: 'category',
77-
title: ''
81+
title: 'SUPPORT'
7882
},
7983
{
8084
type: 'link',
@@ -234,19 +238,14 @@
234238
</div>
235239
</a>
236240

237-
<hr
238-
class:opacity-0={isInSystemPages}
239-
class="my-1 border-[#E5E5E5] transition-[margin] data-dark:border-[#484C55]"
240-
/>
241-
242241
{#each isInSystemPages ? systemItems : items as item}
243242
{#if !item.exclude}
244243
{#if item.type === 'category'}
245244
{#if item.title}
246245
<div
247246
class="{$showDock || !bigScreen.current
248-
? 'pl-5 [&:not(:first-child)]:mt-3 [&:not(:first-child)]:pt-3'
249-
: 'pl-3 [&:not(:first-child)]:mt-0 [&:not(:first-child)]:pt-0'} mb-1 cursor-default border-[#E5E5E5] text-sm font-medium text-[#999] transition-[padding,margin] duration-200 data-dark:border-[#484C55] [&:not(:first-child)]:border-t"
247+
? 'pl-3 [&:not(:first-child)]:mt-3 [&:not(:first-child)]:pt-3'
248+
: 'pl-3 [&:not(:first-child)]:mt-0 [&:not(:first-child)]:pt-0'} mb-1 cursor-default border-[#E5E5E5] text-sm font-medium text-[#98A2B3] transition-[padding,margin] duration-200 data-dark:border-[#484C55]"
250249
>
251250
<span class="{$showDock ? 'opacity-100' : 'opacity-0'} transition-opacity">
252251
{item.title}

services/app/src/routes/(main)/project/+page.svelte

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
projectSort as sortOptions,
1212
uploadQueue
1313
} from '$globalStore';
14+
import { tagColors } from '$lib/constants';
1415
import logger from '$lib/logger';
1516
import type { Project } from '$lib/types';
1617
17-
import ProjectDialogs from './ProjectDialogs.svelte';
1818
import ExportProjectButton from './ExportProjectButton.svelte';
19+
import ProjectDialogs from './ProjectDialogs.svelte';
20+
import ProjectsThumbsFetch from './ProjectsThumbsFetch.svelte';
1921
import { m } from '$lib/paraglide/messages';
2022
import { getLocale } from '$lib/paraglide/runtime';
2123
import InputText from '$lib/components/InputText.svelte';
@@ -53,6 +55,8 @@
5355
{ id: 'updated_at', title: m['sortable.updated_at'](), Icon: SortByIcon }
5456
];
5557
58+
let projectsThumbs = $state<{ [projectID: string]: string }>({});
59+
5660
let searchQuery = $state('');
5761
let isLoadingSearch = $state(false);
5862
@@ -206,7 +210,13 @@
206210
const offset = target.scrollHeight - target.clientHeight - target.scrollTop;
207211
const LOAD_THRESHOLD = 1000;
208212
209-
if (offset < LOAD_THRESHOLD && !isLoadingProjects && !moreProjectsFinished) {
213+
if (
214+
orgProjects.length > 0 &&
215+
offset < LOAD_THRESHOLD &&
216+
!isLoadingProjects &&
217+
!moreProjectsFinished
218+
) {
219+
fetchController?.abort('Duplicate');
210220
await getProjects();
211221
}
212222
};
@@ -330,7 +340,7 @@
330340

331341
{#if !loadingProjectsError}
332342
<div
333-
style="grid-auto-rows: 128px;"
343+
style="grid-auto-rows: 240px;"
334344
class="grid grow grid-flow-row grid-cols-[minmax(15rem,1fr)] gap-4 px-1 pb-4 pt-1 sm:grid-cols-[repeat(auto-fill,_minmax(300px,_1fr))]"
335345
>
336346
{#if isLoadingProjects}
@@ -347,14 +357,53 @@
347357
title={project.id}
348358
class="flex flex-col rounded-lg border border-[#E5E5E5] bg-white transition-[transform,box-shadow] hover:-translate-y-0.5 hover:shadow-float data-dark:border-[#333] data-dark:bg-[#42464E]"
349359
>
350-
<div class="flex grow items-start justify-between p-3">
351-
<div class="flex items-start gap-1.5">
352-
<span class="rounded bg-[#FFEFF2] p-1">
353-
<Clipboard class="h-4 w-4 flex-[0_0_auto] text-[#950048]" />
354-
</span>
360+
<div class="relative p-2">
361+
{#if projectsThumbs[project.id]}
362+
<!-- Temp fix for url -->
363+
<img
364+
src={projectsThumbs[project.id].replace('http://', 'https://')}
365+
class="h-28 w-full rounded-md object-cover"
366+
alt=""
367+
/>
368+
{:else if !project.cover_picture_url}
369+
<div class="flex h-28 items-center justify-center rounded-md bg-secondary">
370+
<Clipboard class="h-10 w-10 text-white" />
371+
</div>
372+
{:else}
373+
<div class="flex h-28 items-center justify-center">
374+
<LoadingSpinner class="h-4 w-4 text-secondary" />
375+
</div>
376+
{/if}
377+
378+
<div class="absolute right-0 top-0 flex flex-wrap justify-end gap-1 p-3">
379+
{#each (project.tags ?? []).slice(0, 5) as tag, index}
380+
<span
381+
style="background-color: {tagColors[index % tagColors.length]};"
382+
class="select-none rounded-md px-1.5 py-[3px] text-xs text-white"
383+
>
384+
{tag}
385+
</span>
386+
{/each}
387+
388+
{#if (project.tags ?? []).slice(5).length > 0}
389+
<span
390+
class="select-none rounded-md bg-black px-1.5 py-[3px] text-xs text-white"
391+
>
392+
+{(project.tags ?? []).slice(5).length}
393+
</span>
394+
{/if}
395+
</div>
396+
</div>
397+
398+
<div class="flex grow items-start justify-between px-3 pb-1 pt-1">
399+
<div class="flex h-full flex-col items-start gap-1">
355400
<span class="line-clamp-2 text-[#475467] [word-break:break-word]">
356401
{project.name}
357402
</span>
403+
404+
<p class="h-1 grow overflow-auto text-sm text-[#667085]">
405+
{project.description ?? ''}
406+
</p>
358407
</div>
359408

360409
<DropdownMenu.Root>
@@ -456,3 +505,4 @@
456505
{orgProjects}
457506
{refetchProjects}
458507
/>
508+
<ProjectsThumbsFetch {orgProjects} bind:projectsThumbs />
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<script lang="ts">
2+
import { PUBLIC_JAMAI_URL } from '$env/static/public';
3+
import debounce from 'lodash/debounce';
4+
import { page } from '$app/state';
5+
import { isValidUri } from '$lib/utils';
6+
import type { Project } from '$lib/types';
7+
8+
import { toast, CustomToastDesc } from '$lib/components/ui/sonner';
9+
10+
let {
11+
orgProjects,
12+
projectsThumbs = $bindable()
13+
}: {
14+
orgProjects: Project[];
15+
projectsThumbs: { [projectID: string]: string };
16+
} = $props();
17+
18+
const debouncedFetchThumbs = debounce(fetchThumbs, 250);
19+
async function fetchThumbs() {
20+
const cacheProjects = $state.snapshot(orgProjects);
21+
22+
const urlResponse = await fetch(`${PUBLIC_JAMAI_URL}/api/owl/files/url/thumb`, {
23+
method: 'POST',
24+
headers: {
25+
'Content-Type': 'application/json',
26+
'x-project-id': page.params.project_id ?? ''
27+
},
28+
body: JSON.stringify({
29+
uris: cacheProjects.map((proj) =>
30+
isValidUri(proj.cover_picture_url ?? '') ? proj.cover_picture_url : 's3://placeholder/'
31+
)
32+
})
33+
});
34+
const urlBody = await urlResponse.json();
35+
36+
if (urlResponse.ok) {
37+
projectsThumbs = (urlBody.urls as string[]).reduce(
38+
(acc, url, index) => {
39+
acc[cacheProjects[index].id] = url;
40+
return acc;
41+
},
42+
{} as { [projectID: string]: string }
43+
);
44+
} else {
45+
toast.error('Failed to retrieve thumbnails', {
46+
id: urlBody.message || JSON.stringify(urlBody),
47+
description: CustomToastDesc as any,
48+
componentProps: {
49+
description: urlBody.message || JSON.stringify(urlBody),
50+
requestID: urlBody.request_id
51+
}
52+
});
53+
}
54+
}
55+
$effect(() => {
56+
if (orgProjects.length) {
57+
debouncedFetchThumbs();
58+
}
59+
});
60+
</script>

0 commit comments

Comments
 (0)