first try
This commit is contained in:
parent
b1005d7df5
commit
c4151f16a0
4 changed files with 135 additions and 7 deletions
|
|
@ -1,11 +1,12 @@
|
|||
import { v } from 'convex/values';
|
||||
import { api } from './_generated/api';
|
||||
import { query } from './_generated/server';
|
||||
import { type Id } from './_generated/dataModel';
|
||||
import { type SessionObj } from './betterAuth';
|
||||
import { messageRoleValidator } from './schema';
|
||||
import { mutation } from './functions';
|
||||
import { fuzzyMatchString } from '../../utils/fuzzy-search';
|
||||
import { getFirstSentence } from '../../utils/strings';
|
||||
import { api } from './_generated/api';
|
||||
import { Doc, type Id } from './_generated/dataModel';
|
||||
import { query } from './_generated/server';
|
||||
import { type SessionObj } from './betterAuth';
|
||||
import { mutation } from './functions';
|
||||
import { messageRoleValidator } from './schema';
|
||||
|
||||
export const get = query({
|
||||
args: {
|
||||
|
|
@ -278,3 +279,59 @@ export const remove = mutation({
|
|||
await ctx.db.delete(args.conversation_id);
|
||||
},
|
||||
});
|
||||
|
||||
export const search = query({
|
||||
args: {
|
||||
session_token: v.string(),
|
||||
search_term: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const session = await ctx.runQuery(api.betterAuth.publicGetSession, {
|
||||
session_token: args.session_token,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
type SearchResult = {
|
||||
conversation: Doc<'conversations'>;
|
||||
messages: Doc<'messages'>[];
|
||||
};
|
||||
const res: SearchResult[] = [];
|
||||
|
||||
if (!args.search_term.trim()) return res;
|
||||
|
||||
const convQuery = ctx.db
|
||||
.query('conversations')
|
||||
.withIndex('by_user', (q) => q.eq('user_id', session.userId));
|
||||
|
||||
for await (const conversation of convQuery) {
|
||||
const searchResult: SearchResult = {
|
||||
conversation,
|
||||
messages: [],
|
||||
};
|
||||
|
||||
const msgQuery = ctx.db
|
||||
.query('messages')
|
||||
.withIndex('by_conversation', (q) => q.eq('conversation_id', conversation._id))
|
||||
.order('asc');
|
||||
|
||||
for await (const message of msgQuery) {
|
||||
if (fuzzyMatchString(args.search_term, message.content)) {
|
||||
console.log('Found message for search');
|
||||
searchResult.messages.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
searchResult.messages.length > 0 ||
|
||||
fuzzyMatchString(args.search_term, conversation.title)
|
||||
) {
|
||||
res.push(searchResult);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default function fuzzysearch<T>(options: {
|
|||
/**
|
||||
* Internal helper function that performs the actual fuzzy string matching
|
||||
*/
|
||||
function fuzzyMatchString(needle: string, haystack: string): boolean {
|
||||
export function fuzzyMatchString(needle: string, haystack: string): boolean {
|
||||
const hlen = haystack.length;
|
||||
const nlen = needle.length;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
import ModelPicker from './model-picker.svelte';
|
||||
import AppSidebar from '$lib/components/app-sidebar.svelte';
|
||||
import { cn } from '$lib/utils/utils.js';
|
||||
import SearchModal from './search-modal.svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
|
|
@ -370,6 +371,7 @@
|
|||
|
||||
<!-- header -->
|
||||
<div class="md:bg-sidebar fixed top-2 right-2 z-50 flex rounded-bl-lg p-1 md:top-0 md:right-0">
|
||||
<SearchModal />
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<Button variant="ghost" size="icon" class="size-8" href="/account" {...tooltip.trigger}>
|
||||
|
|
|
|||
69
src/routes/chat/search-modal.svelte
Normal file
69
src/routes/chat/search-modal.svelte
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import { api } from '$lib/backend/convex/_generated/api';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import Modal from '$lib/components/ui/modal/modal.svelte';
|
||||
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
||||
import { session } from '$lib/state/session.svelte';
|
||||
import { useQuery } from 'convex-svelte';
|
||||
import { Debounced } from 'runed';
|
||||
import SearchIcon from '~icons/lucide/search';
|
||||
|
||||
let open = $state(true);
|
||||
|
||||
let input = $state('');
|
||||
let inputEl = $state<HTMLInputElement>();
|
||||
|
||||
const debouncedInput = new Debounced(() => input, 500);
|
||||
|
||||
const search = useQuery(api.conversations.search, () => ({
|
||||
search_term: debouncedInput.current,
|
||||
session_token: session.current?.session.token ?? '',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<Button
|
||||
onclick={() => (open = true)}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-8"
|
||||
{...tooltip.trigger}
|
||||
>
|
||||
<SearchIcon class="!size-4" />
|
||||
<span class="sr-only">Search</span>
|
||||
</Button>
|
||||
{/snippet}
|
||||
Search
|
||||
</Tooltip>
|
||||
|
||||
<Modal bind:open>
|
||||
<h2>Search</h2>
|
||||
<input bind:this={inputEl} bind:value={input} class="w-full border" placeholder="Search" />
|
||||
|
||||
{#if search.isLoading}
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
</div>
|
||||
{:else if search.data?.length}
|
||||
<div class="space-y-2">
|
||||
{#each search.data as { conversation, messages }}
|
||||
<div
|
||||
class="border-border flex items-center justify-between gap-2 rounded-lg border px-3 py-2 text-sm"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-muted-foreground text-xs">
|
||||
{conversation.title}
|
||||
</div>
|
||||
<div class="text-muted-foreground text-xs">
|
||||
{messages.length} message{messages.length > 1 ? 's' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Add message count to conversation -->
|
||||
<Button variant="secondary" size="sm" class="text-xs">View</Button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
Loading…
Add table
Reference in a new issue