mirror of
https://github.com/docmost/docmost.git
synced 2026-08-20 02:54:11 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f5a7d76d9 |
@@ -15,14 +15,6 @@ export interface IAiSearchResponse {
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function hintVectorCache(): Promise<void> {
|
||||
try {
|
||||
await api.post("/ai/vector-cache-hint");
|
||||
} catch {
|
||||
// best-effort cache hint
|
||||
}
|
||||
}
|
||||
|
||||
export async function aiAnswers(
|
||||
params: IPageSearchParams,
|
||||
onChunk?: (chunk: { content?: string; sources?: any[] }) => void,
|
||||
|
||||
@@ -13,16 +13,11 @@ import { SearchResultItem } from "./search-result-item.tsx";
|
||||
import { AiSearchResult } from "../../../ee/ai/components/ai-search-result.tsx";
|
||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
||||
import { Feature } from "@/ee/features";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||
import { hintVectorCache } from "@/ee/ai/services/ai-search-service.ts";
|
||||
import { getAiVectorDriver } from "@/lib/config.ts";
|
||||
|
||||
interface SearchSpotlightProps {
|
||||
spaceId?: string;
|
||||
}
|
||||
export function SearchSpotlight({ spaceId }: SearchSpotlightProps) {
|
||||
const workspace = useAtomValue(workspaceAtom);
|
||||
const { t } = useTranslation();
|
||||
const hasAiFeature = useHasFeature(Feature.AI);
|
||||
const hasAttachmentIndexing = useHasFeature(Feature.ATTACHMENT_INDEXING);
|
||||
@@ -101,15 +96,6 @@ export function SearchSpotlight({ spaceId }: SearchSpotlightProps) {
|
||||
/>
|
||||
));
|
||||
|
||||
const handleSpotlightOpen = () => {
|
||||
if (
|
||||
workspace?.settings?.ai?.search === true &&
|
||||
getAiVectorDriver() === "turbopuffer"
|
||||
) {
|
||||
hintVectorCache();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFiltersChange = (newFilters: any) => {
|
||||
setFilters(newFilters);
|
||||
};
|
||||
@@ -129,7 +115,6 @@ export function SearchSpotlight({ spaceId }: SearchSpotlightProps) {
|
||||
<Spotlight.Root
|
||||
size="xl"
|
||||
maxHeight={600}
|
||||
onSpotlightOpen={handleSpotlightOpen}
|
||||
store={searchSpotlightStore}
|
||||
query={query}
|
||||
onQueryChange={setQuery}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { Group, Select, SelectProps, Text } from "@mantine/core";
|
||||
import { useGetSpacesQuery } from "@/features/space/queries/space-query.ts";
|
||||
@@ -14,7 +14,6 @@ interface SpaceSelectProps {
|
||||
width?: number;
|
||||
opened?: boolean;
|
||||
clearable?: boolean;
|
||||
withinPortal?: boolean;
|
||||
}
|
||||
|
||||
const renderSelectOption: SelectProps["renderOption"] = ({ option }) => (
|
||||
@@ -42,7 +41,6 @@ export function SpaceSelect({
|
||||
width,
|
||||
opened,
|
||||
clearable,
|
||||
withinPortal = true,
|
||||
}: SpaceSelectProps) {
|
||||
const { t } = useTranslation();
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
@@ -52,13 +50,9 @@ export function SpaceSelect({
|
||||
limit: 50,
|
||||
});
|
||||
const [data, setData] = useState([]);
|
||||
const fetchedSpaces = useRef(new Map<string, ISpace>());
|
||||
|
||||
useEffect(() => {
|
||||
if (spaces) {
|
||||
spaces.items.forEach((space: ISpace) =>
|
||||
fetchedSpaces.current.set(space.slug, space),
|
||||
);
|
||||
const spaceData = spaces?.items
|
||||
.filter((space: ISpace) => space.slug !== value)
|
||||
.map((space: ISpace) => {
|
||||
@@ -89,19 +83,14 @@ export function SpaceSelect({
|
||||
onSearchChange={setSearchValue}
|
||||
clearable={clearable}
|
||||
variant="filled"
|
||||
onChange={(slug) => {
|
||||
// options accumulate across fetches; resolve against everything
|
||||
// fetched, not just the latest query result
|
||||
const space = slug && fetchedSpaces.current.get(slug);
|
||||
if (space) {
|
||||
onChange(space);
|
||||
}
|
||||
}}
|
||||
onChange={(slug) =>
|
||||
onChange(spaces.items?.find((item) => item.slug === slug))
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
nothingFoundMessage={t("No space found")}
|
||||
limit={50}
|
||||
checkIconPosition="right"
|
||||
comboboxProps={{ width, withinPortal, position: "bottom", keepMounted: false, dropdownPadding: 0 }}
|
||||
comboboxProps={{ width, withinPortal: true, position: "bottom", keepMounted: false, dropdownPadding: 0 }}
|
||||
dropdownOpened={opened}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,6 @@ export function SwitchSpace({
|
||||
onChange={(space) => handleSelect(space.slug)}
|
||||
width={300}
|
||||
opened={true}
|
||||
withinPortal={false}
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
@@ -43,10 +43,6 @@ export function isCloud(): boolean {
|
||||
return castToBoolean(getConfigValue("CLOUD"));
|
||||
}
|
||||
|
||||
export function getAiVectorDriver(): string {
|
||||
return getConfigValue("AI_VECTOR_DRIVER");
|
||||
}
|
||||
|
||||
export function getAvatarUrl(
|
||||
avatarUrl: string,
|
||||
type: AvatarIconType = AvatarIconType.AVATAR,
|
||||
|
||||
@@ -16,7 +16,6 @@ export default defineConfig(({ mode }) => {
|
||||
BILLING_TRIAL_DAYS,
|
||||
POSTHOG_HOST,
|
||||
POSTHOG_KEY,
|
||||
AI_VECTOR_DRIVER,
|
||||
} = loadEnv(mode, envPath, "");
|
||||
|
||||
return {
|
||||
@@ -32,7 +31,6 @@ export default defineConfig(({ mode }) => {
|
||||
BILLING_TRIAL_DAYS,
|
||||
POSTHOG_HOST,
|
||||
POSTHOG_KEY,
|
||||
AI_VECTOR_DRIVER,
|
||||
},
|
||||
APP_VERSION: JSON.stringify(process.env.npm_package_version),
|
||||
},
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
"@nestjs/websockets": "11.1.28",
|
||||
"@node-saml/passport-saml": "5.1.0",
|
||||
"@socket.io/redis-adapter": "8.3.0",
|
||||
"@turbopuffer/turbopuffer": "^2.8.0",
|
||||
"ai": "6.0.134",
|
||||
"ai-sdk-ollama": "3.8.1",
|
||||
"bcrypt": "6.0.0",
|
||||
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
JSONContent,
|
||||
} from '@tiptap/core';
|
||||
import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
||||
import { collapseBlankLines } from '../common/helpers';
|
||||
// @tiptap/html library works best for generating prosemirror json state but not HTML
|
||||
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
||||
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
||||
@@ -147,7 +146,7 @@ export function htmlToJson(html: string) {
|
||||
}
|
||||
|
||||
export function jsonToText(tiptapJson: JSONContent) {
|
||||
return collapseBlankLines(generateText(tiptapJson, tiptapExtensions));
|
||||
return generateText(tiptapJson, tiptapExtensions);
|
||||
}
|
||||
|
||||
export function jsonToNode(tiptapJson: JSONContent) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './utils';
|
||||
export * from './text.utils';
|
||||
export * from './nanoid.utils';
|
||||
export * from './file.helper';
|
||||
export * from './constants';
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { collapseBlankLines } from './text.utils';
|
||||
|
||||
describe('collapseBlankLines', () => {
|
||||
it.each([
|
||||
['a\n\n\n\nb', 'a\n\nb'],
|
||||
['a\n\nb', 'a\n\nb'],
|
||||
['a\nb', 'a\nb'],
|
||||
['\n\n\n\na\n\n\n', '\n\na\n\n'],
|
||||
['no newlines', 'no newlines'],
|
||||
['', ''],
|
||||
])('collapses %j to %j', (input, expected) => {
|
||||
expect(collapseBlankLines(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
export function collapseBlankLines(text: string): string {
|
||||
return text.replace(/\n{2,}/g, '\n\n');
|
||||
}
|
||||
@@ -496,21 +496,10 @@ export class PageService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.aiQueue.add(
|
||||
QueueJob.PAGE_MOVED_TO_SPACE,
|
||||
{
|
||||
pageIds: pageIdsToMove,
|
||||
spaceId,
|
||||
workspaceId: rootPage.workspaceId,
|
||||
},
|
||||
{
|
||||
attempts: 2,
|
||||
backoff: {
|
||||
type: 'fixed',
|
||||
delay: 2 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
);
|
||||
await this.aiQueue.add(QueueJob.PAGE_MOVED_TO_SPACE, {
|
||||
pageIds: pageIdsToMove,
|
||||
workspaceId: rootPage.workspaceId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { EnvironmentService } from '../../integrations/environment/environment.s
|
||||
|
||||
export class SpaceEvent {
|
||||
spaceId: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -23,12 +22,12 @@ export class SpaceListener {
|
||||
|
||||
@OnEvent(EventName.SPACE_DELETED)
|
||||
async handleSpaceDeleted(event: SpaceEvent) {
|
||||
const { spaceId, workspaceId } = event;
|
||||
const { spaceId } = event;
|
||||
if (this.isTypesense()) {
|
||||
await this.searchQueue.add(QueueJob.SPACE_DELETED, { spaceId });
|
||||
}
|
||||
|
||||
await this.aiQueue.add(QueueJob.SPACE_DELETED, { spaceId, workspaceId });
|
||||
await this.aiQueue.add(QueueJob.SPACE_DELETED, { spaceId });
|
||||
}
|
||||
|
||||
isTypesense(): boolean {
|
||||
|
||||
@@ -230,7 +230,6 @@ export class SpaceRepo {
|
||||
|
||||
this.eventEmitter.emit(EventName.SPACE_DELETED, {
|
||||
spaceId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,24 +211,6 @@ export class WorkspaceRepo {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async updateAiEmbeddingFingerprint(
|
||||
workspaceId: string,
|
||||
fingerprint: { driver: string; model: string; dimensions: number },
|
||||
trx?: KyselyTransaction,
|
||||
) {
|
||||
const db = dbOrTx(this.db, trx);
|
||||
return db
|
||||
.updateTable('workspaces')
|
||||
.set({
|
||||
settings: sql`COALESCE(settings, '{}'::jsonb)
|
||||
|| jsonb_build_object('ai', COALESCE(settings->'ai', '{}'::jsonb)
|
||||
|| jsonb_build_object('embedding', ${JSON.stringify(fingerprint)}::text::jsonb))`,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where('id', '=', workspaceId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async updateSharingSettings(
|
||||
workspaceId: string,
|
||||
prefKey: string,
|
||||
|
||||
+1
-1
Submodule apps/server/src/ee updated: 660418ac2c...b38dc5ecd1
@@ -310,31 +310,6 @@ export class EnvironmentService {
|
||||
return val === 'true';
|
||||
}
|
||||
|
||||
getAiVectorDriver(): string {
|
||||
return this.configService
|
||||
.get<string>('AI_VECTOR_DRIVER', 'pgvector')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
getTurbopufferApiKey(): string {
|
||||
return this.configService.get<string>('TURBOPUFFER_API_KEY');
|
||||
}
|
||||
|
||||
getTurbopufferRegion(): string {
|
||||
return this.configService.get<string>('TURBOPUFFER_REGION');
|
||||
}
|
||||
|
||||
getTurbopufferBaseUrl(): string {
|
||||
return this.configService.get<string>('TURBOPUFFER_BASE_URL');
|
||||
}
|
||||
|
||||
getTurbopufferNamespacePrefix(): string {
|
||||
return this.configService.get<string>(
|
||||
'TURBOPUFFER_NAMESPACE_PREFIX',
|
||||
'docmost',
|
||||
);
|
||||
}
|
||||
|
||||
getOpenAiApiKey(): string {
|
||||
return this.configService.get<string>('OPENAI_API_KEY');
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
Matches,
|
||||
MinLength,
|
||||
ValidateIf,
|
||||
validateSync,
|
||||
@@ -109,41 +108,6 @@ export class EnvironmentVariables {
|
||||
@IsString()
|
||||
AI_DRIVER: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((obj) => obj.AI_VECTOR_DRIVER)
|
||||
@IsIn(['pgvector', 'turbopuffer'])
|
||||
@IsString()
|
||||
AI_VECTOR_DRIVER: string;
|
||||
|
||||
@ValidateIf((obj) => obj.AI_VECTOR_DRIVER === 'turbopuffer')
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
TURBOPUFFER_API_KEY: string;
|
||||
|
||||
@ValidateIf(
|
||||
(obj) =>
|
||||
obj.AI_VECTOR_DRIVER === 'turbopuffer' && !obj.TURBOPUFFER_BASE_URL,
|
||||
)
|
||||
@IsNotEmpty({
|
||||
message:
|
||||
'TURBOPUFFER_REGION is required when AI_VECTOR_DRIVER is turbopuffer, unless TURBOPUFFER_BASE_URL is set',
|
||||
})
|
||||
@IsString()
|
||||
TURBOPUFFER_REGION: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((obj) => obj.TURBOPUFFER_BASE_URL != '' && obj.TURBOPUFFER_BASE_URL != null)
|
||||
@IsUrl({ protocols: ['http', 'https'], require_tld: false })
|
||||
TURBOPUFFER_BASE_URL: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Matches(/^[A-Za-z0-9\-_.]{1,90}$/, {
|
||||
message:
|
||||
'TURBOPUFFER_NAMESPACE_PREFIX may only contain letters, digits, dot, dash, underscore (max 90 chars)',
|
||||
})
|
||||
TURBOPUFFER_NAMESPACE_PREFIX: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
AI_EMBEDDING_MODEL: string;
|
||||
|
||||
@@ -61,7 +61,6 @@ export enum QueueJob {
|
||||
WORKSPACE_DELETED = 'workspace-deleted',
|
||||
WORKSPACE_CREATE_EMBEDDINGS = 'workspace-create-embeddings',
|
||||
WORKSPACE_DELETE_EMBEDDINGS = 'workspace-delete-embeddings',
|
||||
WORKSPACE_RESET_EMBEDDINGS = 'workspace-reset-embeddings',
|
||||
|
||||
GENERATE_PAGE_EMBEDDINGS = 'generate-page-embeddings',
|
||||
DELETE_PAGE_EMBEDDINGS = 'delete-page-embeddings',
|
||||
|
||||
@@ -49,10 +49,6 @@ export class StaticModule implements OnModuleInit {
|
||||
: undefined,
|
||||
POSTHOG_HOST: this.environmentService.getPostHogHost(),
|
||||
POSTHOG_KEY: this.environmentService.getPostHogKey(),
|
||||
AI_VECTOR_DRIVER:
|
||||
this.environmentService.getAiVectorDriver() === 'turbopuffer'
|
||||
? 'turbopuffer'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const windowScriptContent = `<script>window.CONFIG=${JSON.stringify(configString)};</script>`;
|
||||
|
||||
Generated
-17
@@ -593,9 +593,6 @@ importers:
|
||||
'@socket.io/redis-adapter':
|
||||
specifier: 8.3.0
|
||||
version: 8.3.0(socket.io-adapter@2.5.4(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@turbopuffer/turbopuffer':
|
||||
specifier: ^2.8.0
|
||||
version: 2.8.0
|
||||
ai:
|
||||
specifier: 6.0.134
|
||||
version: 6.0.134(zod@4.3.6)
|
||||
@@ -4385,9 +4382,6 @@ packages:
|
||||
'@tsconfig/node16@1.0.4':
|
||||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||
|
||||
'@turbopuffer/turbopuffer@2.8.0':
|
||||
resolution: {integrity: sha512-lurmVByLynuWtPtVl7SBEJ0bNakb8bs4KsafKzeLWnXA6BsQiGS9obT/7ExQVb9/yecyRn/grj8VgwUzi2xMyg==}
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
|
||||
|
||||
@@ -5704,7 +5698,6 @@ packages:
|
||||
cron-parser@4.9.0:
|
||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
deprecated: v4 is no longer maintained, upgrade to v5
|
||||
|
||||
cron@4.4.0:
|
||||
resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==}
|
||||
@@ -8175,9 +8168,6 @@ packages:
|
||||
pako@2.0.3:
|
||||
resolution: {integrity: sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw==}
|
||||
|
||||
pako@2.2.0:
|
||||
resolution: {integrity: sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==}
|
||||
|
||||
parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -14351,11 +14341,6 @@ snapshots:
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
|
||||
'@turbopuffer/turbopuffer@2.8.0':
|
||||
dependencies:
|
||||
pako: 2.2.0
|
||||
undici: 7.29.0
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -18764,8 +18749,6 @@ snapshots:
|
||||
|
||||
pako@2.0.3: {}
|
||||
|
||||
pako@2.2.0: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
Reference in New Issue
Block a user