+ {workspace?.settings?.ai?.search === true && (
+
onAskClick()}
- label="Ask AI"
+ label={t("Ask AI")}
size="sm"
color="blue"
labelPosition="left"
styles={{
root: { display: "flex", alignItems: "center" },
- label: { paddingRight: "8px", fontSize: "13px", fontWeight: 500 }
+ label: { paddingRight: "8px", fontSize: "13px", fontWeight: 500 },
}}
/>
@@ -260,7 +265,7 @@ export function SearchSpotlightFilters({
contentType !== option.value &&
handleFilterChange("contentType", option.value)
}
- disabled={option.disabled}
+ disabled={option.disabled || (isAiMode && option.value === "attachment")}
>
@@ -270,6 +275,11 @@ export function SearchSpotlightFilters({
{t("Enterprise")}
)}
+ {!option.disabled && isAiMode && option.value === "attachment" && (
+
+ {t("Ask AI not available for attachments")}
+
+ )}
{contentType === option.value && }
diff --git a/apps/client/src/features/search/components/search-spotlight.tsx b/apps/client/src/features/search/components/search-spotlight.tsx
index b5079879..9350e189 100644
--- a/apps/client/src/features/search/components/search-spotlight.tsx
+++ b/apps/client/src/features/search/components/search-spotlight.tsx
@@ -7,9 +7,9 @@ import { useTranslation } from "react-i18next";
import { searchSpotlightStore } from "../constants.ts";
import { SearchSpotlightFilters } from "./search-spotlight-filters.tsx";
import { useUnifiedSearch } from "../hooks/use-unified-search.ts";
-import { useAiSearch } from "../hooks/use-ai-search.ts";
+import { useAiSearch } from "../../../ee/ai/hooks/use-ai-search.ts";
import { SearchResultItem } from "./search-result-item.tsx";
-import { AiSearchResult } from "./ai-search-result.tsx";
+import { AiSearchResult } from "../../../ee/ai/components/ai-search-result.tsx";
import { useLicense } from "@/ee/hooks/use-license.tsx";
import { isCloud } from "@/lib/config.ts";
diff --git a/apps/client/src/features/workspace/services/workspace-service.ts b/apps/client/src/features/workspace/services/workspace-service.ts
index c8ac84a3..e47e2972 100644
--- a/apps/client/src/features/workspace/services/workspace-service.ts
+++ b/apps/client/src/features/workspace/services/workspace-service.ts
@@ -42,7 +42,7 @@ export async function deleteWorkspaceMember(data: {
await api.post("/workspace/members/delete", data);
}
-export async function updateWorkspace(data: Partial
) {
+export async function updateWorkspace(data: Partial & { aiSearch?: boolean }) {
const req = await api.post("/workspace/update", data);
return req.data;
}
diff --git a/apps/client/src/features/workspace/types/workspace.types.ts b/apps/client/src/features/workspace/types/workspace.types.ts
index 600641c9..f7d0b964 100644
--- a/apps/client/src/features/workspace/types/workspace.types.ts
+++ b/apps/client/src/features/workspace/types/workspace.types.ts
@@ -9,7 +9,7 @@ export interface IWorkspace {
defaultSpaceId: string;
customDomain: string;
enableInvite: boolean;
- settings: any;
+ settings: IWorkspaceSettings;
status: string;
enforceSso: boolean;
stripeCustomerId: string;
@@ -24,6 +24,14 @@ export interface IWorkspace {
enforceMfa?: boolean;
}
+export interface IWorkspaceSettings {
+ ai?: IWorkspaceAiSettings;
+}
+
+export interface IWorkspaceAiSettings {
+ search?: boolean;
+}
+
export interface ICreateInvite {
role: string;
emails: string[];
diff --git a/apps/server/src/core/workspace/services/workspace.service.ts b/apps/server/src/core/workspace/services/workspace.service.ts
index ae7f556b..1a5e7f8d 100644
--- a/apps/server/src/core/workspace/services/workspace.service.ts
+++ b/apps/server/src/core/workspace/services/workspace.service.ts
@@ -33,6 +33,7 @@ import { InjectQueue } from '@nestjs/bullmq';
import { QueueJob, QueueName } from '../../../integrations/queue/constants';
import { Queue } from 'bullmq';
import { generateRandomSuffixNumbers } from '../../../common/helpers';
+import { isPageEmbeddingsTableExists } from '@docmost/db/helpers/helpers';
@Injectable()
export class WorkspaceService {
@@ -321,6 +322,13 @@ export class WorkspaceService {
);
if (updateWorkspaceDto.aiSearch) {
+ const tableExists = await isPageEmbeddingsTableExists(this.db);
+ if (!tableExists) {
+ throw new BadRequestException(
+ 'Failed to activate. Make sure pgvector postgres extension is installed.',
+ );
+ }
+
await this.aiQueue.add(QueueJob.WORKSPACE_CREATE_EMBEDDINGS, {
workspaceId,
});
diff --git a/apps/server/src/database/helpers/helpers.ts b/apps/server/src/database/helpers/helpers.ts
new file mode 100644
index 00000000..076fc5de
--- /dev/null
+++ b/apps/server/src/database/helpers/helpers.ts
@@ -0,0 +1,22 @@
+import { sql } from 'kysely';
+import { KyselyDB } from '@docmost/db/types/kysely.types';
+
+export async function isPageEmbeddingsTableExists(db: KyselyDB) {
+ return tableExists({ db, tableName: 'page_embeddings' });
+}
+
+export async function tableExists(opts: {
+ db: KyselyDB;
+ tableName: string;
+}): Promise {
+ const { db, tableName } = opts;
+ const result = await sql<{ exists: boolean }>`
+ SELECT EXISTS (
+ SELECT 1 FROM information_schema.tables
+ WHERE table_schema = COALESCE(current_schema(), 'public')
+ AND table_name = ${tableName}
+ ) as exists
+ `.execute(db);
+
+ return result.rows[0]?.exists ?? false;
+}
diff --git a/apps/server/src/ee b/apps/server/src/ee
index 4de95306..d20586e3 160000
--- a/apps/server/src/ee
+++ b/apps/server/src/ee
@@ -1 +1 @@
-Subproject commit 4de95306c7a8d1393f6fa8952c137d52cd28fdfa
+Subproject commit d20586e3eb741e5c51d7e92a8f20b6e7189e47ca