Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Assistants] Filter on names #841

Merged
merged 17 commits into from Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/server/database.ts
Expand Up @@ -85,6 +85,7 @@ client.on("open", () => {
assistants.createIndex({ userCount: 1 }).catch(console.error);
assistants.createIndex({ featured: 1, userCount: -1 }).catch(console.error);
assistants.createIndex({ modelId: 1, userCount: -1 }).catch(console.error);
assistants.createIndex({ name: "text" }).catch(console.error);
reports.createIndex({ assistantId: 1 }).catch(console.error);
reports.createIndex({ createdBy: 1, assistantId: 1 }).catch(console.error);
});
17 changes: 17 additions & 0 deletions src/lib/utils/debounce.ts
@@ -0,0 +1,17 @@
/**
* A debounce function that works in both browser and Nodejs.
* For pure Nodejs work, prefer the `Debouncer` class.
*/
export function debounce<T extends unknown[]>(
callback: (...rest: T) => unknown,
limit: number
): (...rest: T) => void {
let timer: ReturnType<typeof setTimeout>;

return function (...rest) {
clearTimeout(timer);
timer = setTimeout(() => {
callback(...rest);
}, limit);
};
}
5 changes: 4 additions & 1 deletion src/routes/assistants/+page.server.ts
Expand Up @@ -16,6 +16,7 @@ export const load = async ({ url, locals }) => {
const modelId = url.searchParams.get("modelId");
const pageIndex = parseInt(url.searchParams.get("p") ?? "0");
const username = url.searchParams.get("user");
const query = url.searchParams.get("q")?.trim();
const createdByCurrentUser = locals.user?.username && locals.user.username === username;

let user: Pick<User, "_id"> | null = null;
Expand All @@ -34,11 +35,12 @@ export const load = async ({ url, locals }) => {
...(modelId && { modelId }),
...(!createdByCurrentUser && { userCount: { $gt: 1 } }),
...(user ? { createdById: user._id } : { featured: true }),
...(query && { $text: { $search: query } }),
};
const assistants = await collections.assistants
.find(filter)
.skip(NUM_PER_PAGE * pageIndex)
.sort({ userCount: -1 })
.sort({ ...(query && { score: { $meta: "textScore" } }), userCount: -1 })
.limit(NUM_PER_PAGE)
.toArray();

Expand All @@ -49,5 +51,6 @@ export const load = async ({ url, locals }) => {
selectedModel: modelId ?? "",
numTotalItems,
numItemsPerPage: NUM_PER_PAGE,
query,
};
};
43 changes: 39 additions & 4 deletions src/routes/assistants/+page.svelte
Expand Up @@ -4,6 +4,7 @@
import { PUBLIC_APP_ASSETS, PUBLIC_ORIGIN } from "$env/static/public";
import { isHuggingChat } from "$lib/utils/isHuggingChat";

import { tick } from "svelte";
import { goto } from "$app/navigation";
import { base } from "$app/paths";
import { page } from "$app/stores";
Expand All @@ -14,22 +15,40 @@
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
import CarbonEarthAmerica from "~icons/carbon/earth-americas-filled";
import CarbonUserMultiple from "~icons/carbon/user-multiple";
import CarbonSearch from "~icons/carbon/search";
import Pagination from "$lib/components/Pagination.svelte";
import { formatUserCount } from "$lib/utils/formatUserCount";
import { getHref } from "$lib/utils/getHref";
import { debounce } from "$lib/utils/debounce";

export let data: PageData;

$: assistantsCreator = $page.url.searchParams.get("user");
$: createdByMe = data.user?.username && data.user.username === assistantsCreator;

const SEARCH_DEBOUNCE_DELAY = 400;
let filterInputEl: HTMLInputElement;
let searchDisabled = false;

const onModelChange = (e: Event) => {
const newUrl = getHref($page.url, {
newKeys: { modelId: (e.target as HTMLSelectElement).value },
existingKeys: { behaviour: "delete_except", keys: ["user"] },
});
goto(newUrl);
};

const filterOnName = debounce(async (e: Event) => {
searchDisabled = true;
const value = (e.target as HTMLInputElement).value;
const newUrl = getHref($page.url, { newKeys: { q: value } });
await goto(newUrl);
Copy link
Collaborator Author

@mishig25 mishig25 Feb 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taking advantage of svelte partial hydration without creating explicit API routes for now

setTimeout(async () => {
searchDisabled = false;
await tick();
filterInputEl.focus();
}, 0);
}, SEARCH_DEBOUNCE_DELAY);
</script>

<svelte:head>
Expand Down Expand Up @@ -96,7 +115,7 @@
{assistantsCreator}'s Assistants
<a
href={getHref($page.url, {
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p"] },
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p", "q"] },
})}
class="group"
><CarbonClose
Expand All @@ -116,7 +135,7 @@
{:else}
<a
href={getHref($page.url, {
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p"] },
existingKeys: { behaviour: "delete", keys: ["user", "modelId", "p", "q"] },
})}
class="flex items-center gap-1.5 rounded-full border px-3 py-1 {!assistantsCreator
? 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white'
Expand All @@ -129,16 +148,32 @@
<a
href={getHref($page.url, {
newKeys: { user: data.user.username },
existingKeys: { behaviour: "delete", keys: ["modelId", "p"] },
existingKeys: { behaviour: "delete", keys: ["modelId", "p", "q"] },
})}
class="flex items-center gap-1.5 rounded-full border px-3 py-1 {assistantsCreator &&
class="flex items-center gap-1.5 truncate rounded-full border px-3 py-1 {assistantsCreator &&
createdByMe
? 'border-gray-300 bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white'
: 'border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-300'}"
>{data.user.username}
</a>
{/if}
{/if}
<div
class="ml-auto mr-2 flex h-9 w-9 items-center gap-x-2 rounded-full border border-gray-300 px-2 transition-all duration-300 ease-out hover:w-64 md:w-64 dark:border-gray-500"
>
<button class="flex-shrink-0 cursor-default" on:click={() => filterInputEl.focus()}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this filterInputEl.focus() trick is needed for the small/mobiel screen

><CarbonSearch class="text-gray-400" /></button
>
<input
Copy link
Collaborator

@gary149 gary149 Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get weird behaviour (undefined is the default value then it's not obvious that there're results)

Screen.Recording.2024-02-20.at.14.07.59.mov

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c0387ce trimming the value was turning null into undefined

shoud be fixed by here d65f7c1

class="h-9 w-full rounded-full bg-transparent focus:outline-none"
placeholder="Filter by name"
value={data.query}
on:input={filterOnName}
bind:this={filterInputEl}
maxlength="150"
disabled={searchDisabled}
/>
</div>
</div>

<div class="mt-8 grid grid-cols-2 gap-3 sm:gap-5 md:grid-cols-3 lg:grid-cols-4">
Expand Down