From 64e69d9bffe24e66d9dce2d13f1b585dd6e42051 Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sun, 2 Mar 2025 12:32:39 +0100 Subject: [PATCH 1/5] feat(backend): add organization scope projects --- ...d-projects-to-organizations-ai-settings.ts | 77 +++++++++++++++++++ apps/backend/src/migrations/index.ts | 2 + .../organizations-ai-settings.tables.ts | 8 +- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts diff --git a/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts b/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts new file mode 100644 index 00000000..516cbb89 --- /dev/null +++ b/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts @@ -0,0 +1,77 @@ +import type { Kysely } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('organizations_ai_settings') + .addColumn('project_id', 'integer', col => col.references('projects.id').onDelete('restrict')) + .execute(); + + const organizations = await db + .selectFrom('organizations_ai_settings') + .select(['organization_id as id']) + .execute(); + + const rootUser = await db + .selectFrom('users') + .select(['id']) + .where('role', '=', 'root') + .executeTakeFirst(); + + if (rootUser) { + for (const [index, organization] of organizations.entries()) { + const project = await db + .insertInto('projects') + .values({ + name: `Migrated Organization Project - ${Date.now()}-${index}`, + organization_id: organization.id, + internal: true, + creator_user_id: rootUser.id, + }) + .returning(['id']) + .executeTakeFirstOrThrow(); + + await db + .insertInto('projects_summaries') + .values({ + project_id: project.id, + }) + .execute(); + + await db + .updateTable('organizations_ai_settings') + .set({ project_id: project.id }) + .where('organization_id', '=', organization.id) + .execute(); + } + } + + await db.schema + .alterTable('organizations_ai_settings') + .alterColumn('project_id', col => col.setNotNull()) + .execute(); +} + +export async function down(db: Kysely): Promise { + const projects = await db + .selectFrom('organizations_ai_settings') + .select(['project_id']) + .where('project_id', 'is not', null) + .execute(); + + await db.schema + .alterTable('organizations_ai_settings') + .dropColumn('project_id') + .execute(); + + if (projects.length > 0) { + await db + .deleteFrom('projects_summaries') + .where('project_id', 'in', projects.map(app => app.project_id)) + .execute(); + + await db + .deleteFrom('projects') + .where('id', 'in', projects.map(app => app.project_id)) + .execute(); + } +} diff --git a/apps/backend/src/migrations/index.ts b/apps/backend/src/migrations/index.ts index aa93240b..960d4329 100644 --- a/apps/backend/src/migrations/index.ts +++ b/apps/backend/src/migrations/index.ts @@ -44,6 +44,7 @@ import * as addSearchEnginesTable from './0042-add-search-engines-table'; import * as addWebSearchToMessages from './0043-add-websearch-flag-to-messages'; import * as addPinnedMessages from './0044-add-pinned-messages'; import * as addOrganizationsAISettings from './0045-add-organizations-ai-settings-table'; +import * as addProjectsToOrganizationsAISettings from './0046-add-projects-to-organizations-ai-settings'; export const DB_MIGRATIONS = { '0000-add-users-tables': addUsersTables, @@ -92,4 +93,5 @@ export const DB_MIGRATIONS = { '0043-add-websearch-flag-to-messages': addWebSearchToMessages, '0044-add-pinned-messages': addPinnedMessages, '0045-add-organizations-ai-settings-table': addOrganizationsAISettings, + '0046-add-projects-to-organizations-ai-settings': addProjectsToOrganizationsAISettings, }; diff --git a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.tables.ts b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.tables.ts index 54db6118..f135736c 100644 --- a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.tables.ts +++ b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.tables.ts @@ -4,10 +4,12 @@ import type { NormalizeInsertTableRow, NormalizeSelectTableRow, TableId, + TableRowWithIdName, } from '../database'; export type OrganizationsAISettingsTable = { organization_id: ColumnType; + project_id: ColumnType; chat_context: string | null; }; @@ -15,4 +17,8 @@ export type OrganizationsAISettingsTableRow = NormalizeSelectTableRow; -export type OrganizationsAISettingsTableRelationRow = Omit; +export type OrganizationsAISettingsTableRelationRow = + & Omit + & { + project: TableRowWithIdName; + }; From 248671735d012b20f6777a78370a8a5deb46a9ac Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sun, 2 Mar 2025 13:45:25 +0100 Subject: [PATCH 2/5] feat(backend): add automatic creation of organization projects --- ...d-projects-to-organizations-ai-settings.ts | 19 +++-- .../organizations-ai-settings.repo.ts | 33 +++++++- .../organizations-ai-settings.service.ts | 36 +++++++- .../organizations/organizations.repo.ts | 82 ++++--------------- .../organizations/organizations.service.ts | 25 +++++- .../src/modules/projects/projects.service.ts | 38 ++++++--- apps/backend/src/modules/users/users.repo.ts | 16 ++++ .../src/modules/users/users.service.ts | 2 + .../organizations-my/my-organization-form.tsx | 2 +- 9 files changed, 161 insertions(+), 92 deletions(-) diff --git a/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts b/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts index 516cbb89..6b2455a8 100644 --- a/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts +++ b/apps/backend/src/migrations/0046-add-projects-to-organizations-ai-settings.ts @@ -7,8 +7,8 @@ export async function up(db: Kysely): Promise { .execute(); const organizations = await db - .selectFrom('organizations_ai_settings') - .select(['organization_id as id']) + .selectFrom('organizations') + .select(['id']) .execute(); const rootUser = await db @@ -38,9 +38,18 @@ export async function up(db: Kysely): Promise { .execute(); await db - .updateTable('organizations_ai_settings') - .set({ project_id: project.id }) - .where('organization_id', '=', organization.id) + .insertInto('organizations_ai_settings') + .values({ + organization_id: organization.id, + project_id: project.id, + chat_context: null, + }) + .onConflict(oc => oc + .column('organization_id') + .doUpdateSet(eb => ({ + project_id: eb.ref('excluded.project_id'), + })), + ) .execute(); } } diff --git a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts index 755e51be..bf840701 100644 --- a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts +++ b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts @@ -5,8 +5,8 @@ import { injectable } from 'tsyringe'; import { AbstractDatabaseRepo, DatabaseError, - TableId, - TransactionalAttrs, + type TableId, + type TransactionalAttrs, tryReuseTransactionOrSkip, } from '~/modules/database'; @@ -14,6 +14,30 @@ import type { OrganizationsAISettingsTableInsertRow } from './organizations-ai-s @injectable() export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo { + getChatProjectIdByOrganizationIdOrNil = ( + { + forwardTransaction, + organizationId, + }: TransactionalAttrs<{ organizationId: TableId; }>, + ) => { + const transaction = tryReuseTransactionOrSkip({ + db: this.db, + forwardTransaction, + }); + + return pipe( + transaction(trx => + trx + .selectFrom('organizations_ai_settings') + .select('project_id as id') + .where('organization_id', '=', organizationId) + .executeTakeFirst(), + ), + DatabaseError.tryTask, + TE.map(row => row?.id ?? null), + ); + }; + getChatContextByOrganizationIdOrNil = ( { forwardTransaction, @@ -42,7 +66,9 @@ export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo { { forwardTransaction, value, - }: TransactionalAttrs<{ value: OrganizationsAISettingsTableInsertRow; }>, + }: TransactionalAttrs<{ + value: OrganizationsAISettingsTableInsertRow; + }>, ) => { const transaction = tryReuseTransactionOrSkip({ db: this.db, @@ -56,6 +82,7 @@ export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo { .values({ organization_id: value.organizationId, chat_context: value.chatContext || null, + project_id: value.projectId, }) .onConflict(oc => oc .column('organization_id') diff --git a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts index 4b443bfd..a7814e00 100644 --- a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts +++ b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts @@ -1,12 +1,44 @@ +import { taskEither as TE } from 'fp-ts'; +import { pipe } from 'fp-ts/lib/function'; import { inject, injectable } from 'tsyringe'; +import { SdkUpsertOrganizationAISettingsInputT } from '@llm/sdk'; + +import { TableRowWithId } from '../database'; +import { ProjectsService } from '../projects'; import { OrganizationsAISettingsRepo } from './organizations-ai-settings.repo'; @injectable() export class OrganizationsAISettingsService { constructor( - @inject(OrganizationsAISettingsRepo) private readonly organizationsAISettingsRepo: OrganizationsAISettingsRepo, + @inject(OrganizationsAISettingsRepo) private readonly repo: OrganizationsAISettingsRepo, + @inject(ProjectsService) private readonly projectsService: ProjectsService, ) {} - getChatContextByOrganizationIdOrNil = this.organizationsAISettingsRepo.getChatContextByOrganizationIdOrNil; + getChatContextByOrganizationIdOrNil = this.repo.getChatContextByOrganizationIdOrNil; + + upsert = ({ chatContext, organization }: SdkUpsertOrganizationAISettingsInputT & { organization: TableRowWithId; }) => pipe( + TE.Do, + TE.bind('project', () => pipe( + this.repo.getChatProjectIdByOrganizationIdOrNil({ + organizationId: organization.id, + }), + TE.chainW((projectId) => { + if (projectId) { + return TE.right({ id: projectId }); + } + + return this.projectsService.createInternal({ + organization, + }); + }), + )), + TE.chainW(({ project }) => this.repo.upsert({ + value: { + projectId: project.id, + organizationId: organization.id, + chatContext, + }, + })), + ); } diff --git a/apps/backend/src/modules/organizations/organizations.repo.ts b/apps/backend/src/modules/organizations/organizations.repo.ts index 3c196f25..6d3e1d54 100644 --- a/apps/backend/src/modules/organizations/organizations.repo.ts +++ b/apps/backend/src/modules/organizations/organizations.repo.ts @@ -3,95 +3,36 @@ import { array as A, taskEither as TE } from 'fp-ts'; import { pipe } from 'fp-ts/lib/function'; import { inject, injectable } from 'tsyringe'; -import type { SdkCreateOrganizationInputT } from '@llm/sdk'; - import { createArchiveRecordQuery, createArchiveRecordsQuery, - createProtectedDatabaseRepo, + createDatabaseRepo, createUnarchiveRecordQuery, createUnarchiveRecordsQuery, DatabaseConnectionRepo, DatabaseError, - TableId, + type TableId, type TransactionalAttrs, - tryReuseOrCreateTransaction, tryReuseTransactionOrSkip, } from '~/modules/database'; -import { OrganizationsAISettingsRepo } from '../organizations-ai-settings'; import { OrganizationTableRowWithRelations } from './organizations.tables'; @injectable() -export class OrganizationsRepo extends createProtectedDatabaseRepo('organizations') { +export class OrganizationsRepo extends createDatabaseRepo('organizations') { constructor( @inject(DatabaseConnectionRepo) databaseConnectionRepo: DatabaseConnectionRepo, - @inject(OrganizationsAISettingsRepo) private readonly organizationsAISettingsRepo: OrganizationsAISettingsRepo, ) { super(databaseConnectionRepo); } - createIdsIterator = this.baseRepo.createIdsIterator; - - archive = createArchiveRecordQuery(this.baseRepo.queryFactoryAttrs); - - archiveRecords = createArchiveRecordsQuery(this.baseRepo.queryFactoryAttrs); - - unarchive = createUnarchiveRecordQuery(this.baseRepo.queryFactoryAttrs); + archive = createArchiveRecordQuery(this.queryFactoryAttrs); - unarchiveRecords = createUnarchiveRecordsQuery(this.baseRepo.queryFactoryAttrs); + archiveRecords = createArchiveRecordsQuery(this.queryFactoryAttrs); - create = ({ value, forwardTransaction }: TransactionalAttrs<{ value: SdkCreateOrganizationInputT; }>) => { - const transaction = tryReuseOrCreateTransaction({ - db: this.db, - forwardTransaction, - }); + unarchive = createUnarchiveRecordQuery(this.queryFactoryAttrs); - return transaction(trx => pipe( - this.baseRepo.create({ - forwardTransaction: trx, - value: { - name: value.name, - maxNumberOfUsers: value.maxNumberOfUsers, - }, - }), - TE.tap(({ id }) => this.organizationsAISettingsRepo.upsert({ - forwardTransaction: trx, - value: { - organizationId: id, - chatContext: value.aiSettings.chatContext, - }, - })), - )); - }; - - update = ({ id, value, forwardTransaction }: TransactionalAttrs<{ - id: TableId; - value: SdkCreateOrganizationInputT; - }>) => { - const transaction = tryReuseOrCreateTransaction({ - db: this.db, - forwardTransaction, - }); - - return transaction(trx => pipe( - this.baseRepo.update({ - id, - forwardTransaction: trx, - value: { - name: value.name, - maxNumberOfUsers: value.maxNumberOfUsers, - }, - }), - TE.tap(() => this.organizationsAISettingsRepo.upsert({ - forwardTransaction: trx, - value: { - organizationId: id, - chatContext: value.aiSettings.chatContext, - }, - })), - )); - }; + unarchiveRecords = createUnarchiveRecordsQuery(this.queryFactoryAttrs); findWithRelationsByIds = ({ forwardTransaction, ids }: TransactionalAttrs<{ ids: TableId[]; }>) => { const transaction = tryReuseTransactionOrSkip({ db: this.db, forwardTransaction }); @@ -103,9 +44,12 @@ export class OrganizationsRepo extends createProtectedDatabaseRepo('organization .selectFrom(this.table) .where('organizations.id', 'in', ids) .leftJoin('organizations_ai_settings', 'organizations_ai_settings.organization_id', 'organizations.id') + .innerJoin('projects', 'projects.id', 'organizations_ai_settings.project_id') .selectAll('organizations') .select([ 'organizations_ai_settings.chat_context as ai_settings_chat_context', + 'projects.id as ai_settings_project_id', + 'projects.name as ai_settings_project_name', ]) .limit(ids.length) .execute(), @@ -114,11 +58,17 @@ export class OrganizationsRepo extends createProtectedDatabaseRepo('organization TE.map( A.map(({ ai_settings_chat_context: aiSettingsChatContext, + ai_settings_project_id: projectId, + ai_settings_project_name: projectName, ...organization }): OrganizationTableRowWithRelations => ({ ...camelcaseKeys(organization), aiSettings: { chatContext: aiSettingsChatContext, + project: { + id: projectId, + name: projectName, + }, }, })), ), diff --git a/apps/backend/src/modules/organizations/organizations.service.ts b/apps/backend/src/modules/organizations/organizations.service.ts index dd962a42..34ece9f6 100644 --- a/apps/backend/src/modules/organizations/organizations.service.ts +++ b/apps/backend/src/modules/organizations/organizations.service.ts @@ -14,6 +14,7 @@ import type { TableRowWithId } from '../database'; import { AIModelsService } from '../ai-models'; import { AppsService } from '../apps'; +import { OrganizationsAISettingsService } from '../organizations-ai-settings/organizations-ai-settings.service'; import { PermissionsService } from '../permissions'; import { ProjectsService } from '../projects'; import { UsersService } from '../users'; @@ -31,6 +32,7 @@ export class OrganizationsService implements WithAuthFirewall UsersService)) private readonly usersService: Readonly, @inject(delay(() => ProjectsService)) private readonly projectsService: Readonly, @@ -70,13 +72,32 @@ export class OrganizationsService implements WithAuthFirewall pipe( + create = ( + { + aiSettings: { chatContext }, + ...value + }: SdkCreateOrganizationInputT, + ) => pipe( this.repo.create({ value }), + TE.tap(organization => this.organizationsAISettingsService.upsert({ + organization, + chatContext, + })), TE.tap(({ id }) => this.esIndexRepo.findAndIndexDocumentById(id)), ); - update = ({ id, ...value }: SdkUpdateOrganizationInputT & TableRowWithId) => pipe( + update = ( + { + id, + aiSettings: { chatContext }, + ...value + }: SdkUpdateOrganizationInputT & TableRowWithId, + ) => pipe( this.repo.update({ id, value }), + TE.tap(organization => this.organizationsAISettingsService.upsert({ + organization, + chatContext, + })), TE.tap(() => this.esIndexRepo.findAndIndexDocumentById(id)), ); } diff --git a/apps/backend/src/modules/projects/projects.service.ts b/apps/backend/src/modules/projects/projects.service.ts index 2f2d866d..f2efdb7f 100644 --- a/apps/backend/src/modules/projects/projects.service.ts +++ b/apps/backend/src/modules/projects/projects.service.ts @@ -22,6 +22,7 @@ import type { TableId, TableRowWithId, TableRowWithUuid } from '../database'; import { ChatsService } from '../chats/chats.service'; import { PermissionsService } from '../permissions'; +import { UsersService } from '../users'; import { ProjectsEsIndexRepo, ProjectsEsSearchRepo } from './elasticsearch'; import { ProjectsFirewall } from './projects.firewall'; import { ProjectsRepo } from './projects.repo'; @@ -33,6 +34,7 @@ export class ProjectsService implements WithAuthFirewall { @inject(ProjectsEsSearchRepo) private readonly esSearchRepo: ProjectsEsSearchRepo, @inject(ProjectsEsIndexRepo) private readonly esIndexRepo: ProjectsEsIndexRepo, @inject(PermissionsService) private readonly permissionsService: PermissionsService, + @inject(delay(() => UsersService)) private readonly usersService: Readonly, @inject(delay(() => ChatsService)) private readonly chatsService: Readonly, ) {} @@ -157,23 +159,33 @@ export class ProjectsService implements WithAuthFirewall { permissions, }: { organization: TableRowWithId; - creator: TableRowWithId; + creator?: TableRowWithId; permissions?: SdkPermissionT[]; }, ) => - this.create({ - internal: true, - organization, - name: `Unnamed Project - ${Date.now()}`, - summary: { - content: { - value: '', - generated: false, + pipe( + TE.Do, + TE.bind('safeCreator', () => { + if (!creator) { + return this.usersService.getFirstRootUser(); + } + + return TE.right(creator); + }), + TE.chainW(({ safeCreator }) => this.create({ + internal: true, + organization, + name: `Unnamed Project - ${Date.now()}`, + summary: { + content: { + value: '', + generated: false, + }, }, - }, - permissions, - creator, - }); + permissions, + creator: safeCreator, + })), + ); update = ({ id, permissions, ...value }: SdkUpdateProjectInputT & TableRowWithId) => pipe( this.repo.update({ id, value }), diff --git a/apps/backend/src/modules/users/users.repo.ts b/apps/backend/src/modules/users/users.repo.ts index 84bb94ea..56d4dd19 100644 --- a/apps/backend/src/modules/users/users.repo.ts +++ b/apps/backend/src/modules/users/users.repo.ts @@ -42,6 +42,22 @@ export class UsersRepo extends createProtectedDatabaseRepo('users') { super(databaseConnectionRepo); } + getFirstRootUser = () => + pipe( + this.baseRepo.findOne({ + select: ['id', 'email'], + where: [ + ['role', '=', 'root'], + ['archived', '=', false], + ['active', '=', true], + ], + }), + TE.map(({ id, email }) => ({ + id, + email, + })), + ); + archive = (attrs: TransactionalAttrs) => createArchiveRecordQuery(this.baseRepo.queryFactoryAttrs)({ ...attrs, diff --git a/apps/backend/src/modules/users/users.service.ts b/apps/backend/src/modules/users/users.service.ts index 3f4fbec5..3195b697 100644 --- a/apps/backend/src/modules/users/users.service.ts +++ b/apps/backend/src/modules/users/users.service.ts @@ -39,6 +39,8 @@ export class UsersService implements WithAuthFirewall { get = this.esSearchRepo.get; + getFirstRootUser = this.repo.getFirstRootUser; + search = this.esSearchRepo.search; create = ({ avatar, ...value }: InternalCreateUserInputT) => pipe( diff --git a/apps/chat/src/modules/organizations-my/my-organization-form.tsx b/apps/chat/src/modules/organizations-my/my-organization-form.tsx index 1df61355..ba284b85 100644 --- a/apps/chat/src/modules/organizations-my/my-organization-form.tsx +++ b/apps/chat/src/modules/organizations-my/my-organization-form.tsx @@ -11,7 +11,7 @@ type Props = { }; export function MyOrganizationForm({ defaultValue }: Props) { - const t = useI18n().pack.users.form; + const t = useI18n().pack.organizations.form; const { handleSubmitEvent, validator, bind, submitState } = useOrganizationUpdateForm({ defaultValue, }); From 7513e60ef4bc696005e4c719b962c94c60e3da68 Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sun, 2 Mar 2025 15:04:59 +0100 Subject: [PATCH 3/5] feat(chat): add knowledge files --- .../organizations-es-search.repo.ts | 1 + apps/chat/src/i18n/packs/i18n-lang-en.ts | 1 + apps/chat/src/i18n/packs/i18n-lang-pl.ts | 1 + .../organizations-my/my-organization-form.tsx | 61 ++++++++++++------- .../organization-ai-settings-form-field.tsx | 4 +- .../pages/my-organization-settings.route.tsx | 7 ++- .../sdk-organization-ai-settings.dto.ts | 3 + .../dto/sdk-create-organization.dto.ts | 7 ++- .../dto/sdk-update-organization.dto.ts | 7 ++- 9 files changed, 65 insertions(+), 27 deletions(-) diff --git a/apps/backend/src/modules/organizations/elasticsearch/organizations-es-search.repo.ts b/apps/backend/src/modules/organizations/elasticsearch/organizations-es-search.repo.ts index badb6a59..0d56146d 100644 --- a/apps/backend/src/modules/organizations/elasticsearch/organizations-es-search.repo.ts +++ b/apps/backend/src/modules/organizations/elasticsearch/organizations-es-search.repo.ts @@ -76,6 +76,7 @@ export class OrganizationsEsSearchRepo { maxNumberOfUsers: source.max_number_of_users ?? 0, aiSettings: { chatContext: source.ai_settings?.chat_context ?? null, + project: source.ai_settings?.project, }, }); } diff --git a/apps/chat/src/i18n/packs/i18n-lang-en.ts b/apps/chat/src/i18n/packs/i18n-lang-en.ts index 6398c8cc..4b816d51 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-en.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-en.ts @@ -839,6 +839,7 @@ export const I18N_PACK_EN = { create: 'Create organization', edit: 'Edit organization', }, + knowledgeFiles: 'Knowledge files', fields: { name: { label: 'Name', diff --git a/apps/chat/src/i18n/packs/i18n-lang-pl.ts b/apps/chat/src/i18n/packs/i18n-lang-pl.ts index 21321fb1..f891c72a 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-pl.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-pl.ts @@ -841,6 +841,7 @@ export const I18N_PACK_PL: I18nLangPack = { edit: 'Edytuj organizację', create: 'Utwórz organizację', }, + knowledgeFiles: 'Pliki wiedzy', fields: { name: { label: 'Nazwa', diff --git a/apps/chat/src/modules/organizations-my/my-organization-form.tsx b/apps/chat/src/modules/organizations-my/my-organization-form.tsx index ba284b85..385ee552 100644 --- a/apps/chat/src/modules/organizations-my/my-organization-form.tsx +++ b/apps/chat/src/modules/organizations-my/my-organization-form.tsx @@ -5,6 +5,7 @@ import { FormErrorAlert, FormField, Input, SaveButton } from '~/ui'; import { useOrganizationUpdateForm } from '../organizations/form'; import { OrganizationAISettingsFormField } from '../organizations/form/shared/organization-ai-settings-form-field'; +import { ProjectFilesListContainer } from '../projects'; type Props = { defaultValue: SdkOrganizationT; @@ -17,32 +18,48 @@ export function MyOrganizationForm({ defaultValue }: Props) { }); return ( -
- - +
+ + + + + - - + + +
+ +
+
- +
+

+ {t.knowledgeFiles} +

-
-
diff --git a/apps/chat/src/modules/organizations/form/shared/organization-ai-settings-form-field.tsx b/apps/chat/src/modules/organizations/form/shared/organization-ai-settings-form-field.tsx index 99114e5b..ffbfe1cd 100644 --- a/apps/chat/src/modules/organizations/form/shared/organization-ai-settings-form-field.tsx +++ b/apps/chat/src/modules/organizations/form/shared/organization-ai-settings-form-field.tsx @@ -1,11 +1,11 @@ import { controlled, useFormValidatorMessages, type ValidationErrorsListProps } from '@under-control/forms'; -import type { SdkOrganizationAISettingsT } from '@llm/sdk'; +import type { SdkUpsertOrganizationAISettingsInputT } from '@llm/sdk'; import { useI18n } from '~/i18n'; import { FormField, TextArea } from '~/ui'; -type Value = SdkOrganizationAISettingsT; +type Value = SdkUpsertOrganizationAISettingsInputT; type Props = ValidationErrorsListProps; diff --git a/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx b/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx index 7d4f7f19..ca8217e1 100644 --- a/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx +++ b/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx @@ -34,7 +34,12 @@ export function MyOrganizationSettingsRoute() { - {() => result.status === 'success' && } + {() => { + if (result.status === 'success') { + return ; + } + return null; + }} diff --git a/packages/sdk/src/modules/dashboard/organizations/dto/ai-settings/sdk-organization-ai-settings.dto.ts b/packages/sdk/src/modules/dashboard/organizations/dto/ai-settings/sdk-organization-ai-settings.dto.ts index 32f6c188..7b901041 100644 --- a/packages/sdk/src/modules/dashboard/organizations/dto/ai-settings/sdk-organization-ai-settings.dto.ts +++ b/packages/sdk/src/modules/dashboard/organizations/dto/ai-settings/sdk-organization-ai-settings.dto.ts @@ -1,7 +1,10 @@ import { z } from 'zod'; +import { SdkTableRowWithIdV } from '~/shared'; + export const SdkOrganizationAISettingsV = z.object({ chatContext: z.string().nullable(), + project: SdkTableRowWithIdV, }); export type SdkOrganizationAISettingsT = z.infer; diff --git a/packages/sdk/src/modules/dashboard/organizations/dto/sdk-create-organization.dto.ts b/packages/sdk/src/modules/dashboard/organizations/dto/sdk-create-organization.dto.ts index b378da9c..34e5106e 100644 --- a/packages/sdk/src/modules/dashboard/organizations/dto/sdk-create-organization.dto.ts +++ b/packages/sdk/src/modules/dashboard/organizations/dto/sdk-create-organization.dto.ts @@ -2,13 +2,18 @@ import type { z } from 'zod'; import { SdkTableRowWithIdV, ZodOmitArchivedFields, ZodOmitDateFields } from '~/shared'; +import { SdkUpsertOrganizationAISettingsInputV } from './ai-settings'; import { SdkOrganizationV } from './sdk-organization.dto'; export const SdkCreateOrganizationInputV = SdkOrganizationV.omit({ ...ZodOmitDateFields, ...ZodOmitArchivedFields, id: true, -}); + aiSettings: true, +}) + .extend({ + aiSettings: SdkUpsertOrganizationAISettingsInputV, + }); export type SdkCreateOrganizationInputT = z.infer; diff --git a/packages/sdk/src/modules/dashboard/organizations/dto/sdk-update-organization.dto.ts b/packages/sdk/src/modules/dashboard/organizations/dto/sdk-update-organization.dto.ts index 5b2de637..bde3660b 100644 --- a/packages/sdk/src/modules/dashboard/organizations/dto/sdk-update-organization.dto.ts +++ b/packages/sdk/src/modules/dashboard/organizations/dto/sdk-update-organization.dto.ts @@ -2,13 +2,18 @@ import type { z } from 'zod'; import { SdkTableRowWithIdV, ZodOmitArchivedFields, ZodOmitDateFields } from '~/shared'; +import { SdkUpsertOrganizationAISettingsInputV } from './ai-settings'; import { SdkOrganizationV } from './sdk-organization.dto'; export const SdkUpdateOrganizationInputV = SdkOrganizationV.omit({ ...ZodOmitDateFields, ...ZodOmitArchivedFields, id: true, -}); + aiSettings: true, +}) + .extend({ + aiSettings: SdkUpsertOrganizationAISettingsInputV, + }); export type SdkUpdateOrganizationInputT = z.infer; From e5735be4ad505ebefbf0d432b771b43943fdcab0 Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sun, 2 Mar 2025 15:10:06 +0100 Subject: [PATCH 4/5] fix(chat): improve typings --- .../form/shared/organization-shared-form-fields.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/chat/src/modules/organizations/form/shared/organization-shared-form-fields.tsx b/apps/chat/src/modules/organizations/form/shared/organization-shared-form-fields.tsx index a76c17cb..6f14f378 100644 --- a/apps/chat/src/modules/organizations/form/shared/organization-shared-form-fields.tsx +++ b/apps/chat/src/modules/organizations/form/shared/organization-shared-form-fields.tsx @@ -1,13 +1,15 @@ import { controlled, useFormValidatorMessages, type ValidationErrorsListProps } from '@under-control/forms'; -import type { SdkOrganizationT } from '@llm/sdk'; +import type { SdkOrganizationT, SdkUpsertUserAISettingsInputT } from '@llm/sdk'; import { useI18n } from '~/i18n'; import { FormField, Input, NumericInput } from '~/ui'; import { OrganizationAISettingsFormField } from './organization-ai-settings-form-field'; -type Value = Pick; +type Value = Pick & { + aiSettings: SdkUpsertUserAISettingsInputT; +}; type Props = ValidationErrorsListProps; From 772b078294573aa8aa7132c6d3ea3cee9bef4dce Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sun, 2 Mar 2025 15:46:38 +0100 Subject: [PATCH 5/5] feat(backend): make knowledge files prototype --- .../src/modules/messages/messages.service.ts | 4 +- .../organizations-ai-settings/index.ts | 1 + .../organizations-ai-settings.repo.ts | 2 +- .../organizations-ai-settings.service.ts | 4 +- .../projects-embeddings.service.ts | 21 +++++++-- .../create-relevant-embeddings-prompt.ts | 21 +++++---- .../modules/prompts/embeddings/utils/types.ts | 2 +- apps/chat/src/i18n/packs/i18n-lang-en.ts | 3 ++ apps/chat/src/i18n/packs/i18n-lang-pl.ts | 3 ++ apps/chat/src/modules/management/.gitkeep | 0 .../organizations-my/my-organization-form.tsx | 2 +- apps/chat/src/router.tsx | 3 ++ .../management/layout/management.layout.tsx | 23 ++++++++- .../chat/src/routes/management/pages/index.ts | 1 + .../pages/organization-management.route.tsx | 47 +++++++++++++++++++ .../pages/my-organization-settings.route.tsx | 1 + apps/chat/src/routes/use-sitemap.tsx | 1 + 17 files changed, 117 insertions(+), 22 deletions(-) delete mode 100644 apps/chat/src/modules/management/.gitkeep create mode 100644 apps/chat/src/routes/management/pages/organization-management.route.tsx diff --git a/apps/backend/src/modules/messages/messages.service.ts b/apps/backend/src/modules/messages/messages.service.ts index e94f98a9..6f06dc0e 100644 --- a/apps/backend/src/modules/messages/messages.service.ts +++ b/apps/backend/src/modules/messages/messages.service.ts @@ -166,7 +166,7 @@ export class MessagesService implements WithAuthFirewall { ); }), )), - TE.bindW('mappedContent', ({ message, history }) => pipe( + TE.bindW('mappedContent', ({ message, chat, history }) => pipe( message.repliedMessage ? createReplyAiMessagePrefix(message.repliedMessage, message.content) : message.content, @@ -176,7 +176,7 @@ export class MessagesService implements WithAuthFirewall { prefixedMessage => this.projectsEmbeddingsService.wrapWithEmbeddingContextPrompt({ history, message: prefixedMessage, - chat: { id: message.chat.id }, + chat, }), )), TE.chainW(({ mappedContent, history, message, chat, personalities }) => diff --git a/apps/backend/src/modules/organizations-ai-settings/index.ts b/apps/backend/src/modules/organizations-ai-settings/index.ts index 4b3b253d..af800dc8 100644 --- a/apps/backend/src/modules/organizations-ai-settings/index.ts +++ b/apps/backend/src/modules/organizations-ai-settings/index.ts @@ -1,2 +1,3 @@ export * from './organizations-ai-settings.repo'; +export * from './organizations-ai-settings.service'; export * from './organizations-ai-settings.tables'; diff --git a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts index bf840701..30ae704e 100644 --- a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts +++ b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.repo.ts @@ -14,7 +14,7 @@ import type { OrganizationsAISettingsTableInsertRow } from './organizations-ai-s @injectable() export class OrganizationsAISettingsRepo extends AbstractDatabaseRepo { - getChatProjectIdByOrganizationIdOrNil = ( + getProjectIdByOrganizationIdOrNil = ( { forwardTransaction, organizationId, diff --git a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts index a7814e00..ff435507 100644 --- a/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts +++ b/apps/backend/src/modules/organizations-ai-settings/organizations-ai-settings.service.ts @@ -15,12 +15,14 @@ export class OrganizationsAISettingsService { @inject(ProjectsService) private readonly projectsService: ProjectsService, ) {} + getProjectIdByOrganizationIdOrNil = this.repo.getProjectIdByOrganizationIdOrNil; + getChatContextByOrganizationIdOrNil = this.repo.getChatContextByOrganizationIdOrNil; upsert = ({ chatContext, organization }: SdkUpsertOrganizationAISettingsInputT & { organization: TableRowWithId; }) => pipe( TE.Do, TE.bind('project', () => pipe( - this.repo.getChatProjectIdByOrganizationIdOrNil({ + this.repo.getProjectIdByOrganizationIdOrNil({ organizationId: organization.id, }), TE.chainW((projectId) => { diff --git a/apps/backend/src/modules/projects-embeddings/projects-embeddings.service.ts b/apps/backend/src/modules/projects-embeddings/projects-embeddings.service.ts index d973fbce..8b2c26ea 100644 --- a/apps/backend/src/modules/projects-embeddings/projects-embeddings.service.ts +++ b/apps/backend/src/modules/projects-embeddings/projects-embeddings.service.ts @@ -17,13 +17,14 @@ import { } from '@llm/commons'; import type { WithAuthFirewall } from '../auth'; -import type { TableId, TableRowWithUuid } from '../database'; +import type { TableId, TableRowWithId, TableRowWithUuid } from '../database'; import type { UploadFilePayload } from '../s3'; import { AIConnectorService } from '../ai-connector'; import { AIModelsService } from '../ai-models'; import { AppsService } from '../apps'; import { ChatsRepo } from '../chats/chats.repo'; +import { OrganizationsAISettingsService } from '../organizations-ai-settings'; import { PermissionsService } from '../permissions'; import { ProjectsFilesRepo } from '../projects-files/projects-files.repo'; import { createRelevantEmbeddingsPrompt } from '../prompts'; @@ -70,6 +71,7 @@ export class ProjectsEmbeddingsService implements WithAuthFirewall OrganizationsAISettingsService)) private readonly organizationsAISettingsService: OrganizationsAISettingsService, @inject(delay(() => PermissionsService)) private readonly permissionsService: Readonly, @inject(delay(() => AppsService)) private readonly appsService: Readonly, ) {} @@ -87,7 +89,9 @@ export class ProjectsEmbeddingsService implements WithAuthFirewall>; }, ) => { @@ -96,6 +100,12 @@ export class ProjectsEmbeddingsService implements WithAuthFirewall { - if (isNil(projectId) && !apps.items.length) { + TE.chainW(({ apps, fileEmbeddingModel: { id, projectId }, organizationProjectId }) => { + if (isNil(projectId) && !apps.items.length && isNil(organizationProjectId)) { return TE.of(message); } const projectsIds = rejectFalsyItems([ projectId, + organizationProjectId, ...apps.items.map(({ project }) => project.id), ]); @@ -128,7 +139,7 @@ export class ProjectsEmbeddingsService implements WithAuthFirewall searchResults.map(result => ({ ...result, - isAppKnowledge: appsProjectIds.has(result.project.id), + isInternalKnowledge: appsProjectIds.has(result.project.id) || result.project.id === organizationProjectId, }))), TE.map(searchResults => createRelevantEmbeddingsPrompt(message, searchResults)), ); diff --git a/apps/backend/src/modules/prompts/embeddings/create-relevant-embeddings-prompt.ts b/apps/backend/src/modules/prompts/embeddings/create-relevant-embeddings-prompt.ts index ed85affa..1061cd73 100644 --- a/apps/backend/src/modules/prompts/embeddings/create-relevant-embeddings-prompt.ts +++ b/apps/backend/src/modules/prompts/embeddings/create-relevant-embeddings-prompt.ts @@ -24,11 +24,11 @@ export function createRelevantEmbeddingsPrompt( children: fragments .slice(0, 10) - .map(({ text, id, isAppKnowledge }) => + .map(({ text, id, isInternalKnowledge }) => xml('embedding', { attributes: { id, - isAppKnowledge, + isInternalKnowledge, }, children: [text], }), @@ -58,14 +58,14 @@ export function createRelevantEmbeddingsPrompt( xml('point', { children: ['First mention: cite source using footnote [^1] and provide detailed explanation'] }), xml('point', { children: ['Subsequent mentions: refer back to earlier explanation without repeating the source'] }), xml('point', { children: ['Only cite new, unique information from files'] }), - xml('point', { children: ['For app knowledge content (isAppKnowledge=true), mention it comes from assistant\'s knowledge base without referencing specific files'] }), + xml('point', { children: ['For internal knowledge content (isInternalKnowledge), ALWAYS state it comes from "knowledge provided to the assistant" and cite with footnote'] }), ], }), xml('instruction', { - children: ['When using app knowledge, refer to it as "based on assistant\'s knowledge" or "from app\'s knowledge base" without specific file citations'], + children: ['When using internal knowledge, ALWAYS refer to it as "based on knowledge provided to the assistant" without referencing specific files'], }), - xml('instruction', { children: ['For app knowledge content, always make it clear this comes from the app\'s own knowledge base'] }), - xml('instruction', { children: ['Keep footnotes short (one sentence) and include embedding ID when citing sources'] }), + xml('instruction', { children: ['For internal knowledge content, make it explicit this comes from the knowledge provided to the assistant'] }), + xml('instruction', { children: ['Keep footnotes short (one sentence) and include embedding ID when citing sources, EXCEPT for app knowledge'] }), xml('instruction', { attributes: { description: 'When user specifically asks about files or content', @@ -73,7 +73,7 @@ export function createRelevantEmbeddingsPrompt( children: [ xml('point', { children: ['Prioritize information from attached files in the current chat'] }), xml('point', { children: ['Use other available context to provide comprehensive answers'] }), - xml('point', { children: ['Make it clear which information comes from files vs general knowledge'] }), + xml('point', { children: ['Make it clear which information comes from files vs knowledge provided to the assistant'] }), ], }), xml('instruction', { children: ['Don\'t use ✅ or ❌ in responses unless the user uses them first'] }), @@ -92,10 +92,10 @@ export function createRelevantEmbeddingsPrompt( children: [ xml('rule', { children: ['Add footnotes only for first mention of specific information'] }), xml('rule', { children: ['For regular files - Format: [^1]: Fragment #embedding:123 contains relevant details.'] }), - xml('rule', { children: ['For app knowledge - Format: [^1]: Based on assistant\'s knowledge base'] }), + xml('rule', { children: ['For internal knowledge - Format: [^1]: Based on knowledge provided to the assistant'] }), xml('rule', { children: ['Keep references natural and only when meaningful'] }), - xml('rule', { children: ['For app knowledge, NEVER reference specific files or embedding IDs'] }), - xml('rule', { children: ['Citations are MANDATORY for app knowledge but should only mention "assistant\'s knowledge" or "app\'s knowledge base"'] }), + xml('rule', { children: ['For internal knowledge, NEVER reference specific files or embedding IDs'] }), + xml('rule', { children: ['Citations are MANDATORY for internal knowledge and should ALWAYS mention "knowledge provided to the assistant"'] }), xml('rule', { children: ['Citations are optional for other content unless specifically referencing new file content'] }), ], }), @@ -116,6 +116,7 @@ export function createRelevantEmbeddingsPrompt( xml('scenario', { children: ['When answering specific questions about files'] }), xml('scenario', { children: ['When providing technical details from documentation'] }), xml('scenario', { children: ['When user asks for source information'] }), + xml('scenario', { children: ['ALWAYS when using internal knowledge (isInternalKnowledge)'] }), ], }), ], diff --git a/apps/backend/src/modules/prompts/embeddings/utils/types.ts b/apps/backend/src/modules/prompts/embeddings/utils/types.ts index 42be99fc..c0626d03 100644 --- a/apps/backend/src/modules/prompts/embeddings/utils/types.ts +++ b/apps/backend/src/modules/prompts/embeddings/utils/types.ts @@ -3,5 +3,5 @@ import type { TableRowWithId, TableRowWithIdName } from '~/modules/database'; export type MatchingEmbedding = TableRowWithId & { projectFile: TableRowWithIdName; text: string; - isAppKnowledge: boolean; + isInternalKnowledge: boolean; }; diff --git a/apps/chat/src/i18n/packs/i18n-lang-en.ts b/apps/chat/src/i18n/packs/i18n-lang-en.ts index 4b816d51..6f51b077 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-en.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-en.ts @@ -380,6 +380,9 @@ export const I18N_PACK_EN = { users: { title: 'Users', }, + organization: { + title: 'Organization', + }, usersGroups: { title: 'Users Groups', }, diff --git a/apps/chat/src/i18n/packs/i18n-lang-pl.ts b/apps/chat/src/i18n/packs/i18n-lang-pl.ts index f891c72a..7361ebf7 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-pl.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-pl.ts @@ -382,6 +382,9 @@ export const I18N_PACK_PL: I18nLangPack = { users: { title: 'Użytkownicy', }, + organization: { + title: 'Organizacja', + }, usersGroups: { title: 'Grupy użytkowników', }, diff --git a/apps/chat/src/modules/management/.gitkeep b/apps/chat/src/modules/management/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/chat/src/modules/organizations-my/my-organization-form.tsx b/apps/chat/src/modules/organizations-my/my-organization-form.tsx index 385ee552..2f1690ee 100644 --- a/apps/chat/src/modules/organizations-my/my-organization-form.tsx +++ b/apps/chat/src/modules/organizations-my/my-organization-form.tsx @@ -53,7 +53,7 @@ export function MyOrganizationForm({ defaultValue }: Props) {
-

+

{t.knowledgeFiles}

diff --git a/apps/chat/src/router.tsx b/apps/chat/src/router.tsx index 12a14e5b..deb08eb2 100644 --- a/apps/chat/src/router.tsx +++ b/apps/chat/src/router.tsx @@ -14,6 +14,7 @@ import { ManagementRoute, MeSettingsRoute, MyOrganizationSettingsRoute, + OrganizationManagementRoute, PinnedMessagesRoute, ProjectRoute, ProjectsRoute, @@ -85,6 +86,8 @@ function LoggedInRouter() { + + diff --git a/apps/chat/src/routes/management/layout/management.layout.tsx b/apps/chat/src/routes/management/layout/management.layout.tsx index 0a1e7b60..e2a03eaa 100644 --- a/apps/chat/src/routes/management/layout/management.layout.tsx +++ b/apps/chat/src/routes/management/layout/management.layout.tsx @@ -1,9 +1,18 @@ import type { PropsWithChildren } from 'react'; -import { BotIcon, FolderOpen, SearchIcon, UserCircleIcon, UsersIcon } from 'lucide-react'; +import { + BotIcon, + BuildingIcon, + FolderOpen, + SearchIcon, + UserCircleIcon, + UsersIcon, +} from 'lucide-react'; +import { useSdkForLoggedIn } from '@llm/sdk'; import { useI18n } from '~/i18n'; import { LayoutHeader, PageWithNavigationLayout } from '~/layouts'; +import { useHasWorkspaceOrganization } from '~/modules'; import { RouteMetaTags, useSitemap } from '~/routes'; import { SideLayout, SideNav, SideNavItem } from '~/ui'; @@ -15,6 +24,9 @@ export function ManagementLayout({ title, children }: Props) { const t = useI18n().pack.routes.management; const sitemap = useSitemap(); + const { guard } = useSdkForLoggedIn(); + const hasOrganization = useHasWorkspaceOrganization(); + return ( + {hasOrganization && guard.is.minimum.techUser && ( + } + href={sitemap.management.organization} + > + {t.pages.organization.title} + + )} +

diff --git a/apps/chat/src/routes/management/pages/index.ts b/apps/chat/src/routes/management/pages/index.ts index 3a23b132..fe6f203e 100644 --- a/apps/chat/src/routes/management/pages/index.ts +++ b/apps/chat/src/routes/management/pages/index.ts @@ -1,4 +1,5 @@ export * from './ai-models-management.route'; +export * from './organization-management.route'; export * from './s3-buckets-management.route'; export * from './search-engines-management.route'; export * from './users-groups-management.route'; diff --git a/apps/chat/src/routes/management/pages/organization-management.route.tsx b/apps/chat/src/routes/management/pages/organization-management.route.tsx new file mode 100644 index 00000000..4630d653 --- /dev/null +++ b/apps/chat/src/routes/management/pages/organization-management.route.tsx @@ -0,0 +1,47 @@ +import { pipe } from 'fp-ts/lib/function'; +import { Redirect } from 'wouter'; + +import { tryOrThrowTE } from '@llm/commons'; +import { useAsyncValue } from '@llm/commons-front'; +import { useSdkForLoggedIn } from '@llm/sdk'; +import { useI18n } from '~/i18n'; +import { MyOrganizationForm, useWorkspaceOrganizationOrThrow } from '~/modules'; +import { useSitemap } from '~/routes/use-sitemap'; +import { ContentCard, SpinnerContainer } from '~/ui'; + +import { ManagementLayout } from '../layout'; + +export function OrganizationManagementRoute() { + const sitemap = useSitemap(); + const t = useI18n().pack.routes.management.pages.organization; + + const { sdks } = useSdkForLoggedIn(); + const { organization } = useWorkspaceOrganizationOrThrow(); + + const result = useAsyncValue( + pipe( + sdks.dashboard.organizations.get(organization.id), + tryOrThrowTE, + ), + [], + ); + + if (result.status === 'error') { + return ; + } + + return ( + + + + {() => { + if (result.status === 'success') { + return ; + } + return null; + }} + + + + ); +} diff --git a/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx b/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx index ca8217e1..684912cd 100644 --- a/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx +++ b/apps/chat/src/routes/settings/pages/my-organization-settings.route.tsx @@ -38,6 +38,7 @@ export function MyOrganizationSettingsRoute() { if (result.status === 'success') { return ; } + return null; }} diff --git a/apps/chat/src/routes/use-sitemap.tsx b/apps/chat/src/routes/use-sitemap.tsx index 7c0276bd..5177893f 100644 --- a/apps/chat/src/routes/use-sitemap.tsx +++ b/apps/chat/src/routes/use-sitemap.tsx @@ -28,6 +28,7 @@ export function useSitemap() { }, management: { index: prefixWithBaseRoute('/management'), + organization: prefixWithBaseRoute('/management/organization'), users: prefixWithBaseRoute('/management/users'), usersGroups: prefixWithBaseRoute('/management/users-groups'), aiModels: prefixWithBaseRoute('/management/ai-models'),