Skip to content

Commit

Permalink
feat(backend): add working summarize
Browse files Browse the repository at this point in the history
  • Loading branch information
Mati365 committed Dec 29, 2024
1 parent b428831 commit 2183e62
Show file tree
Hide file tree
Showing 35 changed files with 683 additions and 44 deletions.
1 change: 1 addition & 0 deletions apps/admin/src/i18n/packs/i18n-lang-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, {
description: {
label: 'Description',
placeholder: 'Enter project description',
generated: 'Generated by AI',
},
organization: {
label: 'Organization',
Expand Down
1 change: 1 addition & 0 deletions apps/admin/src/i18n/packs/i18n-lang-pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, {
description: {
label: 'Opis',
placeholder: 'Wpisz opis projektu',
generated: 'Wygenerowany przez AI',
},
organization: {
label: 'Wybierz organizację',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { controlled, useFormValidatorMessages, type ValidationErrorsListProps } from '@under-control/forms';

import type { SdkProjectT } from '@llm/sdk';
import type { SdkUpdateProjectInputT } from '@llm/sdk';

import { FormField, Input, TextArea } from '@llm/ui';
import { Checkbox, FormField, Input, TextArea } from '@llm/ui';
import { useI18n } from '~/i18n';

type Value = Pick<SdkProjectT, 'name' | 'description'>;
type Value = Pick<SdkUpdateProjectInputT, 'name' | 'summary'>;

type Props = ValidationErrorsListProps<Value>;

export const ProjectSharedFormFields = controlled<Value, Props>(({ errors, control: { bind } }) => {
export const ProjectSharedFormFields = controlled<Value, Props>(({ errors, control: { bind, value } }) => {
const t = useI18n().pack.modules.projects.form;
const validation = useFormValidatorMessages({ errors });

Expand All @@ -31,13 +31,33 @@ export const ProjectSharedFormFields = controlled<Value, Props>(({ errors, contr
<FormField
className="uk-margin"
label={t.fields.description.label}
{...validation.extract('description')}
{...validation.extract('summary.content.value')}
>
<TextArea
name="description"
placeholder={t.fields.description.placeholder}
{...bind.path('description')}
{...bind.path('summary.content.value', { input: value => value ?? '' })}
disabled={value.summary.content.generated}
/>

<Checkbox
className="block pt-2 uk-text-small"
{...bind.path('summary.content.generated', {
relatedInputs: ({ newGlobalValue, newControlValue }) => ({
...newGlobalValue,
summary: {
...newGlobalValue.summary,
content: (
newControlValue
? { generated: true, value: null }
: { generated: false, value: '' }
),
},
}),
})}
>
{t.fields.description.generated}
</Checkbox>
</FormField>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ export function ProjectUpdateFormModal(
) {
const t = useI18n().pack.modules.projects.form;
const { handleSubmitEvent, validator, submitState, bind } = useProjectUpdateForm({
defaultValue: project,
defaultValue: {
...project,
summary: {
content: (
project.summary.content.generated
? { generated: true, value: null }
: { generated: false, value: project.summary.content.value! }
),
},
},
onAfterSubmit,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export function ProjectsTableContainer() {
createModal.showAsOptional({
defaultValue: {
name: '',
description: null,
summary: {
content: { generated: true, value: null },
},
organization: createFakeSelectItem(),
},
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function ProjectsTableRow({ item, onUpdated }: Props) {
<tr>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.description || '-'}</td>
<td>{item.summary.content.value || '-'}</td>
<td>
<Link
className="uk-link"
Expand Down
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"db:migrate": "node dist/run-migrations.cli.mjs",
"es:reindex:all": "node dist/run-reindex-all.cli.mjs",
"chats:summarize:all": "node dist/run-summarize-chats.cli.mjs",
"projects:summarize:all": "node dist/run-summarize-projects.cli.mjs",
"dev": "npm run clean && concurrently --kill-others \"vite build --watch --mode production\" \"npm run build:wait && nodemon --stack-trace-limit=1000 --enable-source-maps ./dist/index.mjs\"",
"lint": "eslint . --config ./eslint.config.mjs",
"clean": "rm -rf ./dist 2>/dev/null",
Expand Down
21 changes: 21 additions & 0 deletions apps/backend/src/cli/run-summarize-projects.cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'reflect-metadata';

import { pipe } from 'fp-ts/lib/function';
import { container } from 'tsyringe';

import { runTaskAsVoid, tapTaskEitherTE, tryOrThrowTE } from '@llm/commons';
import { DatabaseConnectionRepo, ElasticsearchRepo } from '~/modules';
import { ProjectsSummariesService } from '~/modules/projects-summaries';

const databaseConnectionRepo = container.resolve(DatabaseConnectionRepo);
const esConnection = container.resolve(ElasticsearchRepo);

const summariesService = container.resolve(ProjectsSummariesService);

void pipe(
summariesService.summarizeAllProjects(),
tapTaskEitherTE(() => esConnection.close),
tapTaskEitherTE(() => databaseConnectionRepo.close),
tryOrThrowTE,
runTaskAsVoid,
);
9 changes: 9 additions & 0 deletions apps/backend/src/migrations/0028-add-projects-summarizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export async function up(db: Kysely<any>): Promise<void> {
.addColumn('project_id', 'integer', col => col.notNull().references('projects.id').onDelete('restrict'))
.addColumn('last_summarized_message_id', 'uuid', col => col.references('messages.id').onDelete('restrict'))
.execute();

await db
.insertInto('projects_summaries')
.columns(['project_id'])
.expression(
db.selectFrom('projects')
.select(['id as project_id']),
)
.execute();
}

export async function down(db: Kysely<any>): Promise<void> {
Expand Down
3 changes: 3 additions & 0 deletions apps/backend/src/modules/boot/boot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ElasticsearchRegistryBootService,
} from '../elasticsearch/boot';
import { LoggerService } from '../logger';
import { ProjectSummariesCronJob } from '../projects-summaries';
import { UsersBootService } from '../users';

@injectable()
Expand All @@ -32,6 +33,7 @@ export class BootService {
// Cron jobs
@inject(ElasticsearchAutoReindexJob) private readonly elasticsearchReindexJob: ElasticsearchAutoReindexJob,
@inject(ChatSummariesCronJob) private readonly chatSummariesCronJob: ChatSummariesCronJob,
@inject(ProjectSummariesCronJob) private readonly projectSummariesCronJob: ProjectSummariesCronJob,
) {}

/**
Expand Down Expand Up @@ -70,6 +72,7 @@ export class BootService {
private registerCronJobs = () => {
this.elasticsearchReindexJob.registerCronJob();
this.chatSummariesCronJob.registerCronJob();
this.projectSummariesCronJob.registerCronJob();

this.logger.info('Registered cron jobs!');
};
Expand Down
2 changes: 0 additions & 2 deletions apps/backend/src/modules/chats/chats.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,9 @@ export class ChatsRepo extends createProtectedDatabaseRepo('chats') {
'users.email as creator_email',

'chat_summaries.id as summary_id',

'chat_summaries.content as summary_content',
'chat_summaries.content_generated as summary_content_generated',
'chat_summaries.content_generated_at as summary_content_generated_at',

'chat_summaries.name as summary_name',
'chat_summaries.name_generated as summary_name_generated',
'chat_summaries.name_generated_at as summary_name_generated_at',
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/modules/config/helpers/config.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ChatsSummariesConfigV } from '~/modules/chats-summaries/chats-summaries
import { DatabaseConfigV } from '~/modules/database/database.config';
import { DatabaseMigrateConfigV } from '~/modules/database/migrate/database-migrate.config';
import { ElasticsearchConfigV } from '~/modules/elasticsearch/elasticsearch.config';
import { ProjectsSummariesConfigV } from '~/modules/projects-summaries/projects-summaries.config';
import { UsersConfigV } from '~/modules/users/users.config';

export const ConfigV = z.object({
Expand All @@ -20,6 +21,7 @@ export const ConfigV = z.object({
users: UsersConfigV,
auth: AuthConfigV,
chatsSummaries: ChatsSummariesConfigV,
projectsSummaries: ProjectsSummariesConfigV,
});

export type ConfigT = z.infer<typeof ConfigV>;
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ export function tryReadEnvOrPanic() {
JWT_SECRET,
JWT_EXPIRES_IN,

// Chats summaries
// Chats
CHATS_SUMMARIES_CRON,

// Projects
PROJECTS_SUMMARIES_CRON,
} = {
...envFile.parsed,
...process.env,
Expand Down Expand Up @@ -90,6 +93,9 @@ export function tryReadEnvOrPanic() {
chatsSummaries: {
cron: CHATS_SUMMARIES_CRON,
},
projectsSummaries: {
cron: PROJECTS_SUMMARIES_CRON,
},
users: {
root: {
email: USER_ROOT_EMAIL,
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/modules/database/database.tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
import type { ProjectsEmbeddingsTable } from '../projects-embeddings';
import type { ProjectsFilesTable } from '../projects-files';
import type { ProjectsPoliciesTable } from '../projects-policies';
import type { ProjectsSummariesTable } from '../projects-summaries';
import type {
S3ResourcesBucketsTable,
S3ResourcesTable,
Expand Down Expand Up @@ -61,6 +62,7 @@ export type DatabaseTables = {
projects_files: ProjectsFilesTable;
projects_embeddings: ProjectsEmbeddingsTable;
projects_policies: ProjectsPoliciesTable;
projects_summaries: ProjectsSummariesTable;

// Apps
apps: AppsTable;
Expand Down
6 changes: 6 additions & 0 deletions apps/backend/src/modules/projects-summaries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './projects-summaries.config';
export * from './projects-summaries.cron';
export * from './projects-summaries.errors';
export * from './projects-summaries.repo';
export * from './projects-summaries.service';
export * from './projects-summaries.tables';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from 'zod';

export const ProjectsSummariesConfigV = z.object({
cron: z.string().default('*/20 * * * * *'),
});

export type ProjectsSummariesConfigT = z.infer<typeof ProjectsSummariesConfigV>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { flow } from 'fp-ts/lib/function';
import { inject, injectable } from 'tsyringe';

import { runTaskAsVoid, tryOrThrowTE } from '@llm/commons';
import { ConfigService } from '~/modules/config';

import { CronJob, CronService } from '../cron';
import { LoggerService } from '../logger';
import { ProjectsSummariesService } from './projects-summaries.service';

@injectable()
export class ProjectSummariesCronJob extends CronJob {
private readonly logger = LoggerService.of('ProjectSummariesCronJob');

constructor(
@inject(CronService) cronService: CronService,
@inject(ConfigService) configService: ConfigService,
@inject(ProjectsSummariesService) private readonly projectsSummariesService: ProjectsSummariesService,
) {
super(cronService, configService);
}

registerCronJob(): void {
const { logger } = this;
const { cron } = this.config.projectsSummaries;

if (!cron) {
logger.warn('ProjectSummariesCronJob cron job is disabled!');
return;
}

this.cronService.tryRegister(
{
expression: cron,
skipIfPreviousJobNotDone: true,
},
flow(
this.projectsSummariesService.summarizeAllProjects,
tryOrThrowTE,
runTaskAsVoid,
),
);

logger.info('Registered ProjectSummariesCronJob cron job!');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { SdKSearchMessagesOutputT } from '@llm/sdk';

import { TaggedError } from '@llm/commons';

export class MissingAIModelInProjectError
extends TaggedError.ofLiteral<SdKSearchMessagesOutputT>()('MissingAIModelInProject') {
}
Loading

0 comments on commit 2183e62

Please sign in to comment.