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 all 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
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,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({ searchTokens: 1 }).catch(console.error);
reports.createIndex({ assistantId: 1 }).catch(console.error);
reports.createIndex({ createdBy: 1, assistantId: 1 }).catch(console.error);
});
1 change: 1 addition & 0 deletions src/lib/types/Assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface Assistant extends Timestamps {
preprompt: string;
userCount?: number;
featured?: boolean;
searchTokens: string[];
}
17 changes: 17 additions & 0 deletions src/lib/utils/debounce.ts
Original file line number Diff line number Diff line change
@@ -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);
};
}
33 changes: 33 additions & 0 deletions src/lib/utils/searchTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const PUNCTUATION_REGEX = /\p{P}/gu;

function removeDiacritics(s: string, form: "NFD" | "NFKD" = "NFD"): string {
return s.normalize(form).replace(/[\u0300-\u036f]/g, "");
}

export function generateSearchTokens(value: string): string[] {
const fullTitleToken = removeDiacritics(value)
.replace(PUNCTUATION_REGEX, "")
.replaceAll(/\s+/g, "")
.toLowerCase();
return [
...new Set([
...removeDiacritics(value)
.split(/\s+/)
.map((word) => word.replace(PUNCTUATION_REGEX, "").toLowerCase())
.filter((word) => word.length),
...(fullTitleToken.length ? [fullTitleToken] : []),
]),
];
}

function escapeForRegExp(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}

export function generateQueryTokens(query: string): RegExp[] {
return removeDiacritics(query)
.split(/\s+/)
.map((word) => word.replace(PUNCTUATION_REGEX, "").toLowerCase())
.filter((word) => word.length)
.map((token) => new RegExp(`^${escapeForRegExp(token)}`));
}
4 changes: 4 additions & 0 deletions src/routes/assistants/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ENABLE_ASSISTANTS } from "$env/static/private";
import { collections } from "$lib/server/database.js";
import type { Assistant } from "$lib/types/Assistant";
import type { User } from "$lib/types/User";
import { generateQueryTokens } from "$lib/utils/searchTokens.js";
import { error, redirect } from "@sveltejs/kit";
import type { Filter } from "mongodb";

Expand All @@ -16,6 +17,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() ?? null;
const createdByCurrentUser = locals.user?.username && locals.user.username === username;

let user: Pick<User, "_id"> | null = null;
Expand All @@ -34,6 +36,7 @@ export const load = async ({ url, locals }) => {
...(modelId && { modelId }),
...(!createdByCurrentUser && { userCount: { $gt: 1 } }),
...(user ? { createdById: user._id } : { featured: true }),
...(query && { searchTokens: { $all: generateQueryTokens(query) } }),
};
const assistants = await collections.assistants
.find(filter)
Expand All @@ -49,5 +52,6 @@ export const load = async ({ url, locals }) => {
selectedModel: modelId ?? "",
numTotalItems,
numItemsPerPage: NUM_PER_PAGE,
query,
};
};
42 changes: 38 additions & 4 deletions src/routes/assistants/+page.svelte
Original file line number Diff line number Diff line change
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,16 +15,22 @@
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";
import { useSettingsStore } from "$lib/stores/settings";

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 },
Expand All @@ -32,6 +39,18 @@
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);

const settings = useSettingsStore();
</script>

Expand Down Expand Up @@ -99,7 +118,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 @@ -119,7 +138,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 @@ -132,16 +151,31 @@
<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="relative ml-auto flex hidden h-[30px] w-40 items-center rounded-full border px-2 has-[:focus]:border-gray-400 sm:w-64 dark:border-gray-600"
>
<CarbonSearch class="pointer-events-none absolute left-2 text-xs text-gray-400" />
<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-[30px] w-full bg-transparent pl-5 focus:outline-none"
placeholder="Filter by name"
value={data.query}
on:input={filterOnName}
bind:this={filterInputEl}
maxlength="150"
type="search"
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { z } from "zod";
import { sha256 } from "$lib/utils/sha256";

import sharp from "sharp";
import { generateSearchTokens } from "$lib/utils/searchTokens";

const newAsssistantSchema = z.object({
name: z.string().min(1),
Expand Down Expand Up @@ -130,6 +131,7 @@ export const actions: Actions = {
exampleInputs,
avatar: deleteAvatar ? undefined : hash ?? assistant.avatar,
updatedAt: new Date(),
searchTokens: generateSearchTokens(parse.data.name),
},
}
);
Expand Down
2 changes: 2 additions & 0 deletions src/routes/settings/assistants/new/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ObjectId } from "mongodb";
import { z } from "zod";
import { sha256 } from "$lib/utils/sha256";
import sharp from "sharp";
import { generateSearchTokens } from "$lib/utils/searchTokens";

const newAsssistantSchema = z.object({
name: z.string().min(1),
Expand Down Expand Up @@ -99,6 +100,7 @@ export const actions: Actions = {
updatedAt: new Date(),
userCount: 1,
featured: false,
searchTokens: generateSearchTokens(parse.data.name),
});

// add insertedId to user settings
Expand Down