Skip to content

Commit 1274ad6

Browse files
authored
Merge pull request #295 from n4ze3m/next
v1.11.0
2 parents dd79a51 + f597cfe commit 1274ad6

37 files changed

+1126
-148
lines changed

app/ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "app",
33
"private": true,
4-
"version": "1.10.0",
4+
"version": "1.11.0",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

app/ui/src/App.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import SettingsTeamsRoot from "./routes/settings/teams";
2424
import BotIntegrationAPIRoot from "./routes/bot/api";
2525
import SettingsModelRoot from "./routes/settings/model";
2626
import { useDarkMode } from "./hooks/useDarkmode";
27+
import BotSearchRoot from "./routes/bot/serach";
2728

2829
const router = createHashRouter([
2930
{
@@ -58,6 +59,14 @@ const router = createHashRouter([
5859
</BotLayout>
5960
),
6061
},
62+
{
63+
path: "/bot/:id/search",
64+
element: (
65+
<BotLayout>
66+
<BotSearchRoot />
67+
</BotLayout>
68+
),
69+
},
6170
{
6271
path: "/bot/:id/playground/:history_id",
6372
element: (

app/ui/src/Layout/BotLayout.tsx

+11-7
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import {
88
SparklesIcon,
99
PuzzlePieceIcon,
1010
EyeDropperIcon,
11-
ChatBubbleLeftRightIcon
11+
ChatBubbleLeftRightIcon,
12+
//MagnifyingGlassIcon,
1213
} from "@heroicons/react/24/outline";
1314

1415
import { Link, useParams, useLocation, useNavigate } from "react-router-dom";
1516
import { useAuth } from "../context/AuthContext";
16-
import { Tooltip } from "antd";
17+
import { Tooltip } from "antd";
1718
import { ApplicationMenu } from "./ApplicationMenu";
1819

1920
const navigation = [
@@ -22,6 +23,11 @@ const navigation = [
2223
href: "/bot/:id",
2324
icon: SparklesIcon,
2425
},
26+
// {
27+
// name: "Search (Beta)",
28+
// href: "/bot/:id/search",
29+
// icon: MagnifyingGlassIcon,
30+
// },
2531
{
2632
name: "Data Sources",
2733
href: "/bot/:id/data-sources",
@@ -65,7 +71,7 @@ export default function BotLayout({
6571
const location = useLocation();
6672
const navigate = useNavigate();
6773

68-
const { isLogged, } = useAuth();
74+
const { isLogged } = useAuth();
6975

7076
React.useEffect(() => {
7177
if (!isLogged) {
@@ -183,11 +189,9 @@ export default function BotLayout({
183189
</Dialog>
184190
</Transition.Root>
185191

186-
187-
188192
<div className="flex flex-col">
189193
<div className="sticky top-0 z-[999] flex h-14 bg-white border-b border-gray-200 dark:bg-[#171717] dark:border-gray-600">
190-
<button
194+
<button
191195
type="button"
192196
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"
193197
onClick={() => setSidebarOpen(true)}
@@ -209,7 +213,7 @@ export default function BotLayout({
209213

210214
<div className="flex flex-1 justify-end px-4">
211215
<div className="ml-4 flex items-center md:ml-6">
212-
<ApplicationMenu />
216+
<ApplicationMenu />
213217
</div>
214218
</div>
215219
</div>

app/ui/src/Layout/BotPlaygroundLayout.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
EyeDropperIcon,
1010
SparklesIcon,
1111
ChatBubbleLeftRightIcon,
12+
// MagnifyingGlassIcon,
1213
} from "@heroicons/react/24/outline";
1314

1415
import { Link, useParams, useLocation, useNavigate } from "react-router-dom";
@@ -22,6 +23,11 @@ const navigation = [
2223
href: "/bot/:id",
2324
icon: SparklesIcon,
2425
},
26+
// {
27+
// name: "Search (Beta)",
28+
// href: "/bot/:id/search",
29+
// icon: MagnifyingGlassIcon,
30+
// },
2531
{
2632
name: "Data Sources",
2733
href: "/bot/:id/data-sources",
@@ -160,7 +166,7 @@ export default function BotPlaygroundLayout({
160166
)}
161167
>
162168
<item.icon
163-
className={classNames(
169+
className={classNames(
164170
location.pathname ===
165171
item.href.replace(":id", params.id!)
166172
? "text-gray-500"
@@ -187,7 +193,7 @@ export default function BotPlaygroundLayout({
187193
<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">
188194
<div className="mt-14 flex flex-grow flex-col">
189195
<nav className="flex-1 space-y-1 px-2 pb-4">
190-
{navigation.map((item) => (
196+
{navigation.map((item) => (
191197
<Tooltip placement="right" key={item.name} title={item.name}>
192198
<Link
193199
to={{
@@ -222,7 +228,7 @@ export default function BotPlaygroundLayout({
222228

223229
<div className="flex flex-col min-h-screen">
224230
<div className="sticky top-0 z-[9999] flex h-14 bg-white border-b border-gray-200 dark:bg-[#171717] dark:border-gray-600">
225-
<button
231+
<button
226232
type="button"
227233
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"
228234
onClick={() => setSidebarOpen(true)}
@@ -242,7 +248,6 @@ export default function BotPlaygroundLayout({
242248
</span>
243249
</Link>
244250

245-
246251
<div className="flex flex-1 justify-end px-4">
247252
<div className="ml-4 flex items-center md:ml-6">
248253
<ApplicationMenu />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
2+
import { motion } from "framer-motion";
3+
import { NoResult, SearchResult, SkeletonSearchResult } from "./SearchResult";
4+
import { ISearchResult } from "./types";
5+
6+
type Props = {
7+
onSubmit: (query: string) => void;
8+
searchQuery: string;
9+
setSearchQuery: (query: string) => void;
10+
isLoading: boolean;
11+
data: ISearchResult;
12+
};
13+
14+
export default function AISearchEngine({
15+
onSubmit,
16+
searchQuery,
17+
setSearchQuery,
18+
isLoading,
19+
data,
20+
}: Props) {
21+
return (
22+
<div className="max-w-5xl mt-12 mx-auto p-4 min-h-screen">
23+
<form
24+
onSubmit={(e) => {
25+
e.preventDefault();
26+
onSubmit(searchQuery);
27+
}}
28+
className="flex mb-8"
29+
>
30+
<input
31+
type="text"
32+
value={searchQuery}
33+
onChange={(e) => setSearchQuery(e.target.value)}
34+
placeholder="Enter your search query..."
35+
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"
36+
/>
37+
<button
38+
type="submit"
39+
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"
40+
>
41+
<MagnifyingGlassIcon className="w-5 h-5" />
42+
</button>
43+
</form>
44+
45+
<motion.div className="space-y-6">
46+
{isLoading && (
47+
<>
48+
<SkeletonSearchResult />
49+
<SkeletonSearchResult />
50+
<SkeletonSearchResult />
51+
</>
52+
)}
53+
{!isLoading && data.length === 0 && <NoResult />}
54+
{!isLoading &&
55+
data.length > 0 &&
56+
data.map((result, idx) => (
57+
<SearchResult
58+
key={idx}
59+
context={result?.result?.pageContent}
60+
source={result?.result?.source}
61+
icon={<MagnifyingGlassIcon className="size-4" />}
62+
score={+result?.score}
63+
/>
64+
))}
65+
</motion.div>
66+
</div>
67+
);
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
2+
3+
interface SearchBoxProps {
4+
onSubmit: (query: string) => void;
5+
placeholder?: string;
6+
searchQuery: string;
7+
setSearchQuery: (query: string) => void;
8+
}
9+
10+
export default function DefaultSearchBox({
11+
onSubmit,
12+
placeholder = "Find that TPS report, Bob's extension, or the elusive stapler...",
13+
searchQuery,
14+
setSearchQuery,
15+
}: SearchBoxProps) {
16+
const handleSearch = (e: React.FormEvent) => {
17+
e.preventDefault();
18+
onSubmit(searchQuery);
19+
};
20+
21+
return (
22+
<div className="h-[70vh] flex items-center justify-center">
23+
<div className="w-full max-w-3xl px-4">
24+
<form onSubmit={handleSearch} className="relative">
25+
<textarea
26+
placeholder={placeholder}
27+
value={searchQuery}
28+
onChange={(e) => setSearchQuery(e.target.value)}
29+
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"
30+
rows={3}
31+
/>
32+
<button
33+
type="submit"
34+
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"
35+
aria-label="Search"
36+
>
37+
<MagnifyingGlassIcon className="size-5 mx-auto" />
38+
</button>{" "}
39+
</form>
40+
</div>
41+
</div>
42+
);
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { motion } from "framer-motion";
2+
import { useState } from "react";
3+
4+
export interface SearchResultProps {
5+
context: string;
6+
source: string;
7+
icon: React.ReactNode;
8+
score: number;
9+
}
10+
11+
export function SearchResult({
12+
context,
13+
source,
14+
icon,
15+
score,
16+
}: SearchResultProps) {
17+
const [isExpanded, setIsExpanded] = useState(false);
18+
const maxLength = 300;
19+
20+
const toggleReadMore = () => {
21+
setIsExpanded(!isExpanded);
22+
};
23+
24+
const truncatedContext = isExpanded ? context : context.slice(0, maxLength);
25+
26+
return (
27+
<motion.div
28+
initial={{ opacity: 0, y: 20 }}
29+
animate={{ opacity: 1, y: 0 }}
30+
transition={{ duration: 0.3 }}
31+
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:shadow-md transition-shadow bg-white dark:bg-[#1e1e1e]"
32+
>
33+
<p className="text-gray-600 dark:text-gray-300 mb-3">
34+
{truncatedContext}
35+
{context.length > maxLength && (
36+
<>
37+
{!isExpanded && "..."}
38+
<button
39+
onClick={toggleReadMore}
40+
className="text-indigo-600 dark:text-indigo-400 ml-2 hover:underline focus:outline-none"
41+
>
42+
{isExpanded ? "Read less" : "Read more"}
43+
</button>
44+
</>
45+
)}
46+
</p>
47+
<div className="flex items-center text-sm text-gray-500 dark:text-gray-400">
48+
{icon}
49+
<span className="ml-2">{source}</span>
50+
</div>
51+
<div className="mt-2 pt-2 border-t border-gray-200 dark:border-gray-700 flex items-center justify-end bg-gray-50 dark:bg-[#2a2a2a] rounded-b-lg -mx-4 -mb-4 px-4 py-2">
52+
<span className="text-xs text-gray-600 dark:text-gray-300">
53+
Similarity: {`${score}%`}
54+
</span>
55+
</div>
56+
</motion.div>
57+
);
58+
}
59+
export function SkeletonSearchResult() {
60+
return (
61+
<motion.div
62+
initial={{ opacity: 0, y: 20 }}
63+
animate={{ opacity: 1, y: 0 }}
64+
transition={{ duration: 0.3 }}
65+
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:shadow-md transition-shadow bg-white dark:bg-[#1e1e1e]"
66+
>
67+
<div className="h-4 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2"></div>
68+
<div className="h-4 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2"></div>
69+
<div className="h-4 w-1/2 bg-gray-200 dark:bg-gray-700 rounded mb-3"></div>
70+
<div className="flex items-center">
71+
<div className="h-5 w-5 bg-gray-200 dark:bg-gray-700 rounded-full mr-2"></div>
72+
<div className="h-4 w-1/4 bg-gray-200 dark:bg-gray-700 rounded"></div>
73+
</div>
74+
</motion.div>
75+
);
76+
}
77+
78+
export const NoResult = () => (
79+
<div className="text-center py-8">
80+
<p className="text-gray-600 dark:text-gray-400 text-lg">No results found</p>
81+
<p className="text-gray-500 dark:text-gray-500 text-sm mt-2">
82+
Try adjusting your search query
83+
</p>
84+
</div>
85+
);

0 commit comments

Comments
 (0)