Skip to content

Commit

Permalink
feat: Add bot search functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
n4ze3m committed Sep 5, 2024
1 parent 6bc983b commit 54c9eda
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 16 deletions.
9 changes: 9 additions & 0 deletions app/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import SettingsTeamsRoot from "./routes/settings/teams";
import BotIntegrationAPIRoot from "./routes/bot/api";
import SettingsModelRoot from "./routes/settings/model";
import { useDarkMode } from "./hooks/useDarkmode";
import BotSearchRoot from "./routes/bot/serach";

const router = createHashRouter([
{
Expand Down Expand Up @@ -58,6 +59,14 @@ const router = createHashRouter([
</BotLayout>
),
},
{
path: "/bot/:id/search",
element: (
<BotLayout>
<BotSearchRoot />
</BotLayout>
),
},
{
path: "/bot/:id/playground/:history_id",
element: (
Expand Down
18 changes: 11 additions & 7 deletions app/ui/src/Layout/BotLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import {
SparklesIcon,
PuzzlePieceIcon,
EyeDropperIcon,
ChatBubbleLeftRightIcon
ChatBubbleLeftRightIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/24/outline";

import { Link, useParams, useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
import { Tooltip } from "antd";
import { Tooltip } from "antd";
import { ApplicationMenu } from "./ApplicationMenu";

const navigation = [
Expand All @@ -22,6 +23,11 @@ const navigation = [
href: "/bot/:id",
icon: SparklesIcon,
},
{
name: "Search (Beta)",
href: "/bot/:id/search",
icon: MagnifyingGlassIcon,
},
{
name: "Data Sources",
href: "/bot/:id/data-sources",
Expand Down Expand Up @@ -65,7 +71,7 @@ export default function BotLayout({
const location = useLocation();
const navigate = useNavigate();

const { isLogged, } = useAuth();
const { isLogged } = useAuth();

React.useEffect(() => {
if (!isLogged) {
Expand Down Expand Up @@ -183,11 +189,9 @@ export default function BotLayout({
</Dialog>
</Transition.Root>



<div className="flex flex-col">
<div className="sticky top-0 z-[999] flex h-14 bg-white border-b border-gray-200 dark:bg-[#171717] dark:border-gray-600">
<button
<button
type="button"
className="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 md:hidden dark:border-gray-600 dark:text-gray-200"
onClick={() => setSidebarOpen(true)}
Expand All @@ -209,7 +213,7 @@ export default function BotLayout({

<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<ApplicationMenu />
<ApplicationMenu />
</div>
</div>
</div>
Expand Down
13 changes: 9 additions & 4 deletions app/ui/src/Layout/BotPlaygroundLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EyeDropperIcon,
SparklesIcon,
ChatBubbleLeftRightIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/24/outline";

import { Link, useParams, useLocation, useNavigate } from "react-router-dom";
Expand All @@ -22,6 +23,11 @@ const navigation = [
href: "/bot/:id",
icon: SparklesIcon,
},
{
name: "Search (Beta)",
href: "/bot/:id/search",
icon: MagnifyingGlassIcon,
},
{
name: "Data Sources",
href: "/bot/:id/data-sources",
Expand Down Expand Up @@ -160,7 +166,7 @@ export default function BotPlaygroundLayout({
)}
>
<item.icon
className={classNames(
className={classNames(
location.pathname ===
item.href.replace(":id", params.id!)
? "text-gray-500"
Expand All @@ -187,7 +193,7 @@ export default function BotPlaygroundLayout({
<div className="flex flex-grow flex-col overflow-y-auto border-r border-gray-200 bg-white pt-5 dark:bg-[#171717] dark:border-gray-600">
<div className="mt-14 flex flex-grow flex-col">
<nav className="flex-1 space-y-1 px-2 pb-4">
{navigation.map((item) => (
{navigation.map((item) => (
<Tooltip placement="right" key={item.name} title={item.name}>
<Link
to={{
Expand Down Expand Up @@ -222,7 +228,7 @@ export default function BotPlaygroundLayout({

<div className="flex flex-col min-h-screen">
<div className="sticky top-0 z-[9999] flex h-14 bg-white border-b border-gray-200 dark:bg-[#171717] dark:border-gray-600">
<button
<button
type="button"
className="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 md:hidden dark:border-gray-600 dark:text-gray-200"
onClick={() => setSidebarOpen(true)}
Expand All @@ -242,7 +248,6 @@ export default function BotPlaygroundLayout({
</span>
</Link>


<div className="flex flex-1 justify-end px-4">
<div className="ml-4 flex items-center md:ml-6">
<ApplicationMenu />
Expand Down
68 changes: 68 additions & 0 deletions app/ui/src/components/Bot/Search/AISearchEngine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { motion } from "framer-motion";
import { NoResult, SearchResult, SkeletonSearchResult } from "./SearchResult";
import { ISearchResult } from "./types";

type Props = {
onSubmit: (query: string) => void;
searchQuery: string;
setSearchQuery: (query: string) => void;
isLoading: boolean;
data: ISearchResult;
};

export default function AISearchEngine({
onSubmit,
searchQuery,
setSearchQuery,
isLoading,
data,
}: Props) {
return (
<div className="max-w-5xl mt-12 mx-auto p-4 min-h-screen">
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit(searchQuery);
}}
className="flex mb-8"
>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Enter your search query..."
className="flex-grow px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-400 bg-white dark:bg-[#1e1e1e] text-gray-900 dark:text-gray-100"
/>
<button
type="submit"
className="px-6 py-2 bg-indigo-600 dark:bg-white text-white dark:text-indigo-600 rounded-r-lg hover:bg-indigo-700 dark:hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-400"
>
<MagnifyingGlassIcon className="w-5 h-5" />
</button>
</form>

<motion.div className="space-y-6">
{isLoading && (
<>
<SkeletonSearchResult />
<SkeletonSearchResult />
<SkeletonSearchResult />
</>
)}
{!isLoading && data.length === 0 && <NoResult />}
{!isLoading &&
data.length > 0 &&
data.map((result, idx) => (
<SearchResult
key={idx}
context={result?.result?.pageContent}
source={result?.result?.source}
icon={<MagnifyingGlassIcon className="size-4" />}
score={+result?.score}
/>
))}
</motion.div>
</div>
);
}
43 changes: 43 additions & 0 deletions app/ui/src/components/Bot/Search/DefaultSearchBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";

interface SearchBoxProps {
onSubmit: (query: string) => void;
placeholder?: string;
searchQuery: string;
setSearchQuery: (query: string) => void;
}

export default function DefaultSearchBox({
onSubmit,
placeholder = "Find that TPS report, Bob's extension, or the elusive stapler...",
searchQuery,
setSearchQuery,
}: SearchBoxProps) {
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(searchQuery);
};

return (
<div className="h-[70vh] flex items-center justify-center">
<div className="w-full max-w-3xl px-4">
<form onSubmit={handleSearch} className="relative">
<textarea
placeholder={placeholder}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full py-3 pr-16 text-gray-900 border rounded-md outline-none dark:bg-[#1e1e1e] dark:border-gray-700 dark:text-gray-100 bg-white border-gray-300 transition-colors duration-300 resize-none ring-0"
rows={3}
/>
<button
type="submit"
className="absolute right-2 top-1/2 -translate-y-1/2 size-10 bg-indigo-600 dark:bg-white text-white dark:text-indigo-600 rounded-full hover:bg-indigo-700 dark:hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-white focus:ring-opacity-50 transition-all duration-300 transform hover:scale-105"
aria-label="Search"
>
<MagnifyingGlassIcon className="size-5 mx-auto" />
</button>{" "}
</form>
</div>
</div>
);
}
85 changes: 85 additions & 0 deletions app/ui/src/components/Bot/Search/SearchResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { motion } from "framer-motion";
import { useState } from "react";

export interface SearchResultProps {
context: string;
source: string;
icon: React.ReactNode;
score: number;
}

export function SearchResult({
context,
source,
icon,
score,
}: SearchResultProps) {
const [isExpanded, setIsExpanded] = useState(false);
const maxLength = 250;

const toggleReadMore = () => {
setIsExpanded(!isExpanded);
};

const truncatedContext = isExpanded ? context : context.slice(0, maxLength);

return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:shadow-md transition-shadow bg-white dark:bg-[#1e1e1e]"
>
<p className="text-gray-600 dark:text-gray-300 mb-3">
{truncatedContext}
{context.length > maxLength && (
<>
{!isExpanded && "..."}
<button
onClick={toggleReadMore}
className="text-indigo-600 dark:text-indigo-400 ml-2 hover:underline focus:outline-none"
>
{isExpanded ? "Read less" : "Read more"}
</button>
</>
)}
</p>
<div className="flex items-center text-sm text-gray-500 dark:text-gray-400">
{icon}
<span className="ml-2">{source}</span>
</div>
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between bg-gray-50 dark:bg-[#2a2a2a] rounded-b-lg -mx-4 -mb-4 px-4 py-2">
<span className="text-sm text-gray-600 dark:text-gray-300">
Similarity: {`${score}%`}
</span>
</div>
</motion.div>
);
}
export function SkeletonSearchResult() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:shadow-md transition-shadow bg-white dark:bg-[#1e1e1e]"
>
<div className="h-4 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2"></div>
<div className="h-4 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2"></div>
<div className="h-4 w-1/2 bg-gray-200 dark:bg-gray-700 rounded mb-3"></div>
<div className="flex items-center">
<div className="h-5 w-5 bg-gray-200 dark:bg-gray-700 rounded-full mr-2"></div>
<div className="h-4 w-1/4 bg-gray-200 dark:bg-gray-700 rounded"></div>
</div>
</motion.div>
);
}

export const NoResult = () => (
<div className="text-center py-8">
<p className="text-gray-600 dark:text-gray-400 text-lg">No results found</p>
<p className="text-gray-500 dark:text-gray-500 text-sm mt-2">
Try adjusting your search query
</p>
</div>
);
Loading

0 comments on commit 54c9eda

Please sign in to comment.