From ed07a23e63f34b81ba5d774f0805091078da854b Mon Sep 17 00:00:00 2001
From: Philipinho <16838612+Philipinho@users.noreply.github.com>
Date: Tue, 2 Sep 2025 14:49:01 -0700
Subject: [PATCH] WIP
---
.../search/components/ai-search-result.tsx | 91 ++++++++++++++++++
.../components/search-spotlight-filters.tsx | 29 ++++++
.../search/components/search-spotlight.tsx | 93 ++++++++++++++++---
.../features/search/hooks/use-ai-search.ts | 21 +++++
.../search/hooks/use-unified-search.ts | 3 +-
.../search/services/ai-search-service.ts | 23 +++++
apps/server/src/ee | 2 +-
7 files changed, 247 insertions(+), 15 deletions(-)
create mode 100644 apps/client/src/features/search/components/ai-search-result.tsx
create mode 100644 apps/client/src/features/search/hooks/use-ai-search.ts
create mode 100644 apps/client/src/features/search/services/ai-search-service.ts
diff --git a/apps/client/src/features/search/components/ai-search-result.tsx b/apps/client/src/features/search/components/ai-search-result.tsx
new file mode 100644
index 00000000..cf67fb0b
--- /dev/null
+++ b/apps/client/src/features/search/components/ai-search-result.tsx
@@ -0,0 +1,91 @@
+import React, { useMemo } from "react";
+import { Paper, Text, Group, Stack, Loader, Box } from "@mantine/core";
+import { IconSparkles, IconFileText } from "@tabler/icons-react";
+import { Link } from "react-router-dom";
+import { IAiSearchResponse } from "../services/ai-search-service";
+import { buildPageUrl } from "@/features/page/page.utils";
+
+interface AiSearchResultProps {
+ result: IAiSearchResponse;
+ isLoading?: boolean;
+}
+
+export function AiSearchResult({ result, isLoading }: AiSearchResultProps) {
+ // Deduplicate sources by pageId, keeping the one with highest similarity
+ const deduplicatedSources = useMemo(() => {
+ if (!result?.sources) return [];
+
+ const pageMap = new Map();
+ result.sources.forEach((source) => {
+ const existing = pageMap.get(source.pageId);
+ if (!existing || source.similarity > existing.similarity) {
+ pageMap.set(source.pageId, source);
+ }
+ });
+
+ return Array.from(pageMap.values());
+ }, [result?.sources]);
+
+ if (isLoading) {
+ return (
+
+
+
+ AI is thinking...
+
+
+ );
+ }
+
+ if (!result) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ AI Answer
+
+
+ {result.answer}
+
+
+
+ {deduplicatedSources.length > 0 && (
+
+
+ Sources
+
+ {deduplicatedSources.map((source) => (
+
+
+
+
+
+ {source.title}
+
+
+
+
+ ))}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/apps/client/src/features/search/components/search-spotlight-filters.tsx b/apps/client/src/features/search/components/search-spotlight-filters.tsx
index d8f770e7..6c3a2477 100644
--- a/apps/client/src/features/search/components/search-spotlight-filters.tsx
+++ b/apps/client/src/features/search/components/search-spotlight-filters.tsx
@@ -9,6 +9,7 @@ import {
ScrollArea,
Avatar,
Group,
+ Switch,
getDefaultZIndex,
} from "@mantine/core";
import {
@@ -17,6 +18,7 @@ import {
IconFileDescription,
IconSearch,
IconCheck,
+ IconSparkles,
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { useDebouncedValue } from "@mantine/hooks";
@@ -26,12 +28,16 @@ import classes from "./search-spotlight-filters.module.css";
interface SearchSpotlightFiltersProps {
onFiltersChange?: (filters: any) => void;
+ onAskClick?: () => void;
spaceId?: string;
+ isAiMode?: boolean;
}
export function SearchSpotlightFilters({
onFiltersChange,
+ onAskClick,
spaceId,
+ isAiMode = false,
}: SearchSpotlightFiltersProps) {
const { t } = useTranslation();
const { hasLicenseKey } = useLicense();
@@ -119,6 +125,29 @@ export function SearchSpotlightFilters({
return (
+ {hasLicenseKey && (
+
+ onAskClick()}
+ label="Ask AI"
+ size="sm"
+ color="blue"
+ labelPosition="left"
+ styles={{
+ root: { display: "flex", alignItems: "center" },
+ label: { paddingRight: "8px", fontSize: "13px", fontWeight: 500 }
+ }}
+ />
+
+ )}
+