feat: added and fixed query and search interface

This commit is contained in:
Dhravya 2024-03-01 11:44:23 -07:00
parent b8e070c1ac
commit dc7e35e76a
7 changed files with 447 additions and 0 deletions

BIN
apps/web/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="299" height="186" fill="none"><g clip-path="url(#a)"><rect width="299" height="186" fill="#292B2B" rx="3"/><g stroke="#C9C9C9" opacity=".5"><path d="m71.594 205.646 224-224M95.173 205.646l224-224M118.752 205.646l224-224M142.331 205.646l224-224M165.91 205.646l224-224M189.489 205.646l224-224M213.067 205.646l224-224M236.646 205.646l224-224M260.226 205.646l224-224M283.804 205.646l224-224M-211.354 205.646l224-224M-187.774 205.646l223.999-224M-164.196 205.646l224-224M-140.617 205.646l224-224M-117.038 205.646l224-224M-93.459 205.646l224-224M-69.88 205.646l224-224M-46.301 205.646l224-224M-22.722 205.646l224-224M.857 205.646l224-224M24.436 205.646l224-224M48.015 205.646l224-224M272.723 205.646l-224-224M249.144 205.646l-224-224M225.564 205.646l-224-224M201.985 205.646l-224-224M178.407 205.646l-224-224M154.828 205.646l-224-224M131.249 205.646l-224-224M107.67 205.646l-224-224M84.09 205.646l-224-224M60.511 205.646l-224-224M36.933 205.646l-224-224M13.354 205.646l-224-224M508.512 205.646l-224-224M484.933 205.646l-224-224M461.354 205.646l-224-224M437.775 205.646l-224-224M414.196 205.646l-224-224M390.617 205.646l-224-224M367.038 205.646l-224-224M343.459 205.646l-224-224M319.88 205.646l-224-224M296.3 205.646l-224-224"/></g><g stroke="#C9C9C9" opacity=".5"><path d="m83.594 205.646 224-224M107.173 205.646l224-224M130.752 205.646l224-224M154.331 205.646l224-224M177.91 205.646l224-224M201.489 205.646l224-224M225.067 205.646l224-224M248.646 205.646l224-224M272.226 205.646l224-224M295.804 205.646l224-224M-199.354 205.646l224-224M-175.774 205.646l223.999-224M-152.196 205.646l224-224M-128.617 205.646l224-224M-105.038 205.646l224-224M-81.459 205.646l224-224M-57.88 205.646l224-224M-34.301 205.646l224-224M-10.722 205.646l224-224M12.857 205.646l224-224M36.436 205.646l224-224M60.015 205.646l224-224M284.723 205.646l-224-224M261.144 205.646l-224-224M237.564 205.646l-224-224M213.985 205.646l-224-224M190.407 205.646l-224-224M166.828 205.646l-224-224M143.249 205.646l-224-224M119.67 205.646l-224-224M96.09 205.646l-224-224M72.511 205.646l-224-224M48.933 205.646l-224-224M25.354 205.646l-224-224M520.512 205.646l-224-224M496.933 205.646l-224-224M473.354 205.646l-224-224M449.775 205.646l-224-224M426.196 205.646l-224-224M402.617 205.646l-224-224M379.038 205.646l-224-224M355.459 205.646l-224-224M331.88 205.646l-224-224M308.3 205.646l-224-224"/></g></g><defs><clipPath id="a"><rect width="299" height="186" fill="#fff" rx="3"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,48 @@
import { db } from "@/server/db";
import { eq } from "drizzle-orm";
import { sessions, users } from "@/server/db/schema";
import { type NextRequest, NextResponse } from "next/server";
import { env } from "@/env";
export const runtime = "edge";
export async function POST(req: NextRequest) {
const token = req.cookies.get("next-auth.session-token")?.value ?? req.cookies.get("__Secure-authjs.session-token")?.value ?? req.cookies.get("authjs.session-token")?.value ?? req.headers.get("Authorization")?.replace("Bearer ", "");
const sessionData = await db.select().from(sessions).where(eq(sessions.sessionToken, token!))
if (!sessionData || sessionData.length === 0) {
return new Response(JSON.stringify({ message: "Invalid Key, session not found." }), { status: 404 });
}
const user = await db.select().from(users).where(eq(users.id, sessionData[0].userId)).limit(1)
if (!user || user.length === 0) {
return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 });
}
const body = await req.json() as {
query: string;
}
const resp = await fetch(`https://cf-ai-backend.dhravya.workers.dev/ask`, {
headers: {
"X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY,
},
method: "POST",
body: JSON.stringify({
query: body.query,
}),
})
if (resp.status !== 200 || !resp.ok) {
const errorData = await resp.json();
return new Response(JSON.stringify({ message: "Error in CF function", error: errorData }), { status: resp.status });
}
// Stream the response back to the client
const { readable, writable } = new TransformStream();
resp && resp.body!.pipeTo(writable);
return new Response(readable, { status: 200 });
}

View file

@ -0,0 +1,134 @@
'use client';
import { Label } from './ui/label';
import React, { useEffect, useState } from 'react';
import { Input } from './ui/input';
import { Button } from './ui/button';
import SearchResults from './SearchResults';
function QueryAI() {
const [searchResults, setSearchResults] = useState<string[]>([]);
const [isAiLoading, setIsAiLoading] = useState(false);
const [aiResponse, setAIResponse] = useState('');
const [input, setInput] = useState('');
const [toBeParsed, setToBeParsed] = useState('');
const handleStreamData = (newChunk: string) => {
// Append the new chunk to the existing data to be parsed
setToBeParsed((prev) => prev + newChunk);
};
useEffect(() => {
// Define a function to try parsing the accumulated data
const tryParseAccumulatedData = () => {
// Attempt to parse the "toBeParsed" state as JSON
try {
// Split the accumulated data by the known delimiter "\n\n"
const parts = toBeParsed.split('\n\n');
let remainingData = '';
// Process each part to extract JSON objects
parts.forEach((part, index) => {
try {
const parsedPart = JSON.parse(part.replace('data: ', '')); // Try to parse the part as JSON
// If the part is the last one and couldn't be parsed, keep it to accumulate more data
if (index === parts.length - 1 && !parsedPart) {
remainingData = part;
} else if (parsedPart && parsedPart.response) {
// If the part is parsable and has the "response" field, update the AI response state
setAIResponse((prev) => prev + parsedPart.response);
}
} catch (error) {
// If parsing fails and it's not the last part, it's a malformed JSON
if (index !== parts.length - 1) {
console.error('Malformed JSON part: ', part);
} else {
// If it's the last part, it may be incomplete, so keep it
remainingData = part;
}
}
});
// Update the toBeParsed state to only contain the unparsed remainder
if (remainingData !== toBeParsed) {
setToBeParsed(remainingData);
}
} catch (error) {
console.error('Error parsing accumulated data: ', error);
}
};
// Call the parsing function if there's data to be parsed
if (toBeParsed) {
tryParseAccumulatedData();
}
}, [toBeParsed]);
const getSearchResults = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsAiLoading(true);
const sourcesResponse = await fetch(
`/api/query?sourcesOnly=true&q=${input}`,
);
const sourcesInJson = (await sourcesResponse.json()) as {
ids: string[];
};
setSearchResults(sourcesInJson.ids);
const response = await fetch(`/api/query?q=${input}`);
if (response.body) {
let reader = response.body.getReader();
let decoder = new TextDecoder('utf-8');
let result = '';
// @ts-ignore
reader.read().then(function processText({ done, value }) {
if (done) {
// setSearchResults(JSON.parse(result.replace('data: ', '')));
// setIsAiLoading(false);
return;
}
handleStreamData(decoder.decode(value));
return reader.read().then(processText);
});
}
};
return (
<div className="w-full max-w-2xl mx-auto">
<form onSubmit={async (e) => await getSearchResults(e)} className="mt-8">
<Label htmlFor="searchInput">Ask your SuperMemory</Label>
<div className="flex flex-col md:flex-row md:w-full md:items-center space-y-2 md:space-y-0 md:space-x-2">
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Search using AI... ✨"
id="searchInput"
/>
<Button
disabled={isAiLoading}
className="max-w-min md:w-full"
type="submit"
variant="default"
>
Ask AI
</Button>
</div>
</form>
{searchResults && (
<SearchResults aiResponse={aiResponse} sources={searchResults} />
)}
</div>
);
}
export default QueryAI;

View file

@ -0,0 +1,38 @@
'use client'
import React from 'react';
import { Card, CardContent } from './ui/card';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm'
function SearchResults({
aiResponse,
sources,
}: {
aiResponse: string;
sources: string[];
}) {
return (
<div
style={{
backgroundImage: `linear-gradient(to right, #E5D9F2, #CDC1FF)`,
}}
className="w-full max-w-2xl mx-auto px-4 py-6 space-y-6 border mt-4 rounded-xl"
>
<div className="text-start">
<div className="text-xl text-black">
<Markdown remarkPlugins={[remarkGfm]}>{aiResponse.replace('</s>', '')}</Markdown>
</div>
</div>
<div className="grid gap-6">
{sources.map((value, index) => (
<Card key={index}>
<CardContent className="space-y-2">{value}</CardContent>
</Card>
))}
</div>
</div>
);
}
export default SearchResults;

View file

@ -0,0 +1,200 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-gray-100 data-[state=open]:bg-gray-100 dark:focus:bg-gray-800 dark:data-[state=open]:bg-gray-800",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 text-gray-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 text-gray-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-gray-100 focus:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-800 dark:focus:text-gray-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-gray-100 focus:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-800 dark:focus:text-gray-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-gray-100 focus:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-800 dark:focus:text-gray-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-gray-100 dark:bg-gray-800", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View file

@ -0,0 +1,26 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }