diff --git a/apps/api/src/app/endpoints/ai/ai.controller.spec.ts b/apps/api/src/app/endpoints/ai/ai.controller.spec.ts index ad6dfa8cf1..55a7870ba0 100644 --- a/apps/api/src/app/endpoints/ai/ai.controller.spec.ts +++ b/apps/api/src/app/endpoints/ai/ai.controller.spec.ts @@ -4,7 +4,7 @@ import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.gu import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { - MCP_MAX_ACTIVITIES, + AI_MAX_ACTIVITIES, THROTTLE_AI_ACTIVITIES_LIMIT, THROTTLE_DAILY_TTL } from '@ghostfolio/common/config'; @@ -132,23 +132,23 @@ describe('AiController', () => { expect(aiService.getActivitiesFromFile).not.toHaveBeenCalled(); }); - it(`Refuses a CSV file with more than ${MCP_MAX_ACTIVITIES} rows before a model call`, async () => { + it(`Refuses a CSV file with more than ${AI_MAX_ACTIVITIES} rows before a model call`, async () => { await expect( aiController.getActivitiesFromFile( createResponse(), - createCsvFile(MCP_MAX_ACTIVITIES + 1) + createCsvFile(AI_MAX_ACTIVITIES + 1) ) ).rejects.toThrow(HttpException); expect(aiService.getActivitiesFromFile).not.toHaveBeenCalled(); expect(Logger.prototype.warn).toHaveBeenCalledWith( - `Rejected a CSV file with ${MCP_MAX_ACTIVITIES + 1} rows` + `Rejected a CSV file with ${AI_MAX_ACTIVITIES + 1} rows` ); }); - it(`Accepts a CSV file with ${MCP_MAX_ACTIVITIES} rows`, async () => { - const file = createCsvFile(MCP_MAX_ACTIVITIES); + it(`Accepts a CSV file with ${AI_MAX_ACTIVITIES} rows`, async () => { + const file = createCsvFile(AI_MAX_ACTIVITIES); await aiController.getActivitiesFromFile(createResponse(), file); diff --git a/apps/api/src/app/endpoints/ai/ai.controller.ts b/apps/api/src/app/endpoints/ai/ai.controller.ts index acf2c93789..5dadd1409d 100644 --- a/apps/api/src/app/endpoints/ai/ai.controller.ts +++ b/apps/api/src/app/endpoints/ai/ai.controller.ts @@ -10,7 +10,7 @@ import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { AI_ACTIVITIES_FILE_MAXIMUM_SIZE, AI_ACTIVITIES_TIMEOUT, - MCP_MAX_ACTIVITIES, + AI_MAX_ACTIVITIES, THROTTLE_AI_ACTIVITIES_LIMIT, THROTTLE_DAILY_TTL } from '@ghostfolio/common/config'; @@ -116,14 +116,14 @@ export class AiController { if (mediaType === 'text/csv') { const dataRowCount = getDataRowCountOfCsvFile(file.buffer); - if (dataRowCount > MCP_MAX_ACTIVITIES) { + if (dataRowCount > AI_MAX_ACTIVITIES) { this.logger.warn(`Rejected a CSV file with ${dataRowCount} rows`); throw new HttpException( { error: getReasonPhrase(StatusCodes.BAD_REQUEST), message: [ - `The CSV file has more than ${MCP_MAX_ACTIVITIES} rows of activities` + `The CSV file has more than ${AI_MAX_ACTIVITIES} rows of activities` ] }, StatusCodes.BAD_REQUEST diff --git a/apps/api/src/app/endpoints/ai/ai.schemas.spec.ts b/apps/api/src/app/endpoints/ai/ai.schemas.spec.ts index 1e7474b7df..bdf57e914e 100644 --- a/apps/api/src/app/endpoints/ai/ai.schemas.spec.ts +++ b/apps/api/src/app/endpoints/ai/ai.schemas.spec.ts @@ -1,5 +1,5 @@ import { - MCP_MAX_ACTIVITIES, + AI_MAX_ACTIVITIES, SEARCH_QUERY_MAXIMUM_LENGTH } from '@ghostfolio/common/config'; @@ -44,7 +44,7 @@ describe('ACTIVITY_ROWS_OUTPUT', () => { name: 'A'.repeat(SEARCH_QUERY_MAXIMUM_LENGTH + 1) }), createActivityRow({ fee: -1, quantity: -1, unitPrice: -1 }), - ...Array.from({ length: MCP_MAX_ACTIVITIES }, () => { + ...Array.from({ length: AI_MAX_ACTIVITIES }, () => { return createActivityRow(); }) ] diff --git a/apps/api/src/app/endpoints/ai/ai.schemas.ts b/apps/api/src/app/endpoints/ai/ai.schemas.ts index a2cae1d5da..88597b1e0b 100644 --- a/apps/api/src/app/endpoints/ai/ai.schemas.ts +++ b/apps/api/src/app/endpoints/ai/ai.schemas.ts @@ -1,4 +1,4 @@ -import { MCP_MAX_ACTIVITIES } from '@ghostfolio/common/config'; +import { AI_MAX_ACTIVITIES } from '@ghostfolio/common/config'; import { Type as ActivityType } from '@prisma/client'; import { z } from 'zod'; @@ -39,7 +39,7 @@ export const ACTIVITY_ROWS_OUTPUT = z.object({ unitPrice: z.number().describe('The unit price of the activity') }) ) - .describe(`The activities of the file, at most ${MCP_MAX_ACTIVITIES}`) + .describe(`The activities of the file, at most ${AI_MAX_ACTIVITIES}`) }); export const ROW_MATCHES_OUTPUT = z.object({ diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index 02ebc93aae..432a7f1c6a 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -9,8 +9,8 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con import { PortfolioTableService } from '@ghostfolio/api/services/portfolio-table/portfolio-table.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { + AI_MAX_ACTIVITIES, DEFAULT_LOCALE, - MCP_MAX_ACTIVITIES, NON_INVESTMENT_ACTIVITY_TYPES, PROPERTY_API_KEY_OPENROUTER, PROPERTY_OPENROUTER_MODEL, @@ -299,7 +299,7 @@ export class AiService { 'For an interest or a liability, give 1 as quantity and the amount as unit price.', 'For a fee which is not part of a buy or a sell, give 0 as quantity, 0 as unit price and the amount as fee.', `If the format of a date or of a number is ambiguous and the file gives no other hint, read it as in the locale ${locale}.`, - `Give at most ${MCP_MAX_ACTIVITIES} rows.` + `Give at most ${AI_MAX_ACTIVITIES} rows.` ].join('\n'), maxOutputTokens: AiService.VISION_MAXIMUM_OUTPUT_TOKEN_COUNT, messages: [{ content, role: 'user' }], @@ -333,7 +333,7 @@ export class AiService { `Read ${output.rows.length} rows from a file in ${this.getSecondsSince(startTime)} seconds with ${totalUsage.totalTokens} tokens for ${this.getCost(steps)} USD` ); - return output.rows.slice(0, MCP_MAX_ACTIVITIES); + return output.rows.slice(0, AI_MAX_ACTIVITIES); } public async getPrompt({ diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index dd2ccafe78..644fe18d5a 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -2,8 +2,8 @@ import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/fil import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; import { AI_ACTIVITIES_FILE_MAXIMUM_SIZE, - DEFAULT_DATE_RANGE, - MCP_MAX_ACTIVITIES + AI_MAX_ACTIVITIES, + DEFAULT_DATE_RANGE } from '@ghostfolio/common/config'; import { CreateAccountWithBalancesDto, @@ -133,7 +133,7 @@ export class GfImportActivitiesDialogComponent { protected isGhostfolioAiEnabled = false; protected isLoading = false; protected isMaxActivitiesOfGhostfolioAiReached = false; - protected readonly maxActivitiesOfGhostfolioAi = MCP_MAX_ACTIVITIES; + protected readonly maxActivitiesOfGhostfolioAi = AI_MAX_ACTIVITIES; protected mode: 'DIVIDEND'; protected pageIndex = 0; protected readonly pageSize = 8; @@ -387,9 +387,9 @@ export class GfImportActivitiesDialogComponent { if ( fileExtension === 'csv' && this.importActivitiesService.getDataRowCountOfCsv(await file.text()) > - MCP_MAX_ACTIVITIES + AI_MAX_ACTIVITIES ) { - return $localize`The CSV file has more than ${MCP_MAX_ACTIVITIES}:maxActivities: rows of activities`; + return $localize`The CSV file has more than ${AI_MAX_ACTIVITIES}:maxActivities: rows of activities`; } return undefined; @@ -587,7 +587,7 @@ export class GfImportActivitiesDialogComponent { activitiesOfFile = activities; this.assetProfiles = assetProfiles; this.isMaxActivitiesOfGhostfolioAiReached = - activities.length + rowsWithoutMatch.length >= MCP_MAX_ACTIVITIES; + activities.length + rowsWithoutMatch.length >= AI_MAX_ACTIVITIES; this.rowsWithoutMatch = rowsWithoutMatch; return from( diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss index 5aaa0c42ed..7704de61bd 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss @@ -43,13 +43,6 @@ font-size: 2.5rem; } - .mat-mdc-form-field { - .mat-mdc-progress-spinner { - right: 1.5rem; - top: calc(50% - 10px); - } - } - .mat-mdc-progress-spinner { @include mat.progress-spinner-overrides( ( @@ -61,6 +54,13 @@ } } + .mat-mdc-form-field { + .mat-mdc-progress-spinner { + right: 1.5rem; + top: calc(50% - 10px); + } + } + .mat-expansion-panel { @include mat.expansion-overrides( ( diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 14e25d0549..f8c834afbd 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -40,6 +40,7 @@ export const warnColorRgb = { export const AI_ACTIVITIES_FILE_MAXIMUM_SIZE = 10 * 1024 * 1024; export const AI_ACTIVITIES_TIMEOUT = ms('2 minutes'); +export const AI_MAX_ACTIVITIES = 25; export const ASSET_CLASS_MAPPING = new Map([ [AssetClass.ALTERNATIVE_INVESTMENT, [AssetSubClass.COLLECTIBLE]],