feat: ai chat

This commit is contained in:
Philipinho
2026-03-05 22:56:09 +00:00
parent 66c26af34b
commit 9d9e389bbf
33 changed files with 2114 additions and 7 deletions
@@ -46,6 +46,10 @@ export class UpdateWorkspaceDto extends PartialType(CreateWorkspaceDto) {
@IsBoolean()
mcpEnabled: boolean;
@IsOptional()
@IsBoolean()
aiChat: boolean;
@IsOptional()
@IsInt()
@Min(1)
@@ -440,11 +440,26 @@ export class WorkspaceService {
);
}
if (typeof updateWorkspaceDto.aiChat !== 'undefined') {
const prev = settingsBefore?.ai?.chat ?? false;
if (prev !== updateWorkspaceDto.aiChat) {
before.aiChat = prev;
after.aiChat = updateWorkspaceDto.aiChat;
}
await this.workspaceRepo.updateAiSettings(
workspaceId,
'chat',
updateWorkspaceDto.aiChat,
trx,
);
}
delete updateWorkspaceDto.restrictApiToAdmins;
delete updateWorkspaceDto.aiSearch;
delete updateWorkspaceDto.generativeAi;
delete updateWorkspaceDto.disablePublicSharing;
delete updateWorkspaceDto.mcpEnabled;
delete updateWorkspaceDto.aiChat;
await this.workspaceRepo.updateWorkspace(
updateWorkspaceDto,
@@ -0,0 +1,60 @@
import { type Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('ai_chats')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('creator_id', 'uuid', (col) =>
col.references('users.id').notNull(),
)
.addColumn('title', 'varchar', (col) => col)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.execute();
await db.schema
.createIndex('idx_ai_chats_workspace_creator')
.on('ai_chats')
.columns(['workspace_id', 'creator_id', 'id'])
.execute();
await db.schema
.createTable('ai_chat_messages')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
)
.addColumn('chat_id', 'uuid', (col) =>
col.references('ai_chats.id').onDelete('cascade').notNull(),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('role', 'varchar', (col) => col.notNull())
.addColumn('content', 'text', (col) => col)
.addColumn('tool_calls', 'jsonb', (col) => col)
.addColumn('metadata', 'jsonb', (col) => col)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.execute();
await db.schema
.createIndex('idx_ai_chat_messages_chat_id')
.on('ai_chat_messages')
.columns(['chat_id', 'id'])
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('ai_chat_messages').execute();
await db.schema.dropTable('ai_chats').execute();
}
@@ -44,6 +44,21 @@ export class AttachmentRepo {
.executeTakeFirst();
}
async findByIdWithContent(
attachmentId: string,
opts?: {
trx?: KyselyTransaction;
},
): Promise<Attachment> {
const db = dbOrTx(this.db, opts?.trx);
return db
.selectFrom('attachments')
.select([...this.baseFields, 'textContent'])
.where('id', '=', attachmentId)
.executeTakeFirst();
}
async insertAttachment(
insertableAttachment: InsertableAttachment,
trx?: KyselyTransaction,
+22
View File
@@ -429,7 +429,29 @@ export interface PagePermissions {
updatedAt: Generated<Timestamp>;
}
export interface AiChats {
id: Generated<string>;
workspaceId: string;
creatorId: string;
title: string | null;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
}
export interface AiChatMessages {
id: Generated<string>;
chatId: string;
workspaceId: string;
role: string;
content: string | null;
toolCalls: Json | null;
metadata: Json | null;
createdAt: Generated<Timestamp>;
}
export interface DB {
aiChats: AiChats;
aiChatMessages: AiChatMessages;
apiKeys: ApiKeys;
attachments: Attachments;
audit: Audit;
@@ -1,5 +1,7 @@
import { Insertable, Selectable, Updateable } from 'kysely';
import {
AiChats,
AiChatMessages,
Attachments,
Comments,
Groups,
@@ -28,6 +30,15 @@ import {
} from './db';
import { PageEmbeddings } from '@docmost/db/types/embeddings.types';
// AI Chat
export type AiChat = Selectable<AiChats>;
export type InsertableAiChat = Insertable<AiChats>;
export type UpdatableAiChat = Updateable<Omit<AiChats, 'id'>>;
// AI Chat Message
export type AiChatMessage = Selectable<AiChatMessages>;
export type InsertableAiChatMessage = Insertable<AiChatMessages>;
// Workspace
export type Workspace = Selectable<Workspaces>;
export type InsertableWorkspace = Insertable<Workspaces>;
@@ -252,6 +252,13 @@ export class EnvironmentService {
return this.configService.get<string>('AI_COMPLETION_MODEL');
}
getAiChatModel(): string {
return (
this.configService.get<string>('AI_CHAT_MODEL') ||
this.configService.get<string>('AI_COMPLETION_MODEL')
);
}
getAiEmbeddingDimension(): number {
return parseInt(
this.configService.get<string>('AI_EMBEDDING_DIMENSION'),