diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index 09ead314b..6be9976ed 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -10,26 +10,25 @@ import type { AiPromptMode } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { generateText } from 'ai'; -import { ColumnDescriptor } from 'tablemark'; - -const tablemark = require('tablemark').default; - -// Column definitions for holdings table -const HOLDINGS_TABLE_COLUMNS: ({ key: string } & ColumnDescriptor)[] = [ - { key: 'CURRENCY', name: 'Currency' }, - { key: 'NAME', name: 'Name' }, - { key: 'SYMBOL', name: 'Symbol' }, - { key: 'ASSET_CLASS', name: 'Asset Class' }, - { - align: 'right', - key: 'ALLOCATION_PERCENTAGE', - name: 'Allocation in Percentage' - }, - { key: 'ASSET_SUB_CLASS', name: 'Asset Sub Class' } -]; +import type { ColumnDescriptor } from 'tablemark'; @Injectable() export class AiService { + private readonly HOLDINGS_TABLE_COLUMNS: ({ + key: string; + } & ColumnDescriptor)[] = [ + { key: 'NAME', name: 'Name' }, + { key: 'SYMBOL', name: 'Symbol' }, + { key: 'CURRENCY', name: 'Currency' }, + { key: 'ASSET_CLASS', name: 'Asset Class' }, + { key: 'ASSET_SUB_CLASS', name: 'Asset Sub Class' }, + { + align: 'right', + key: 'ALLOCATION_PERCENTAGE', + name: 'Allocation in Percentage' + } + ]; + public constructor( private readonly portfolioService: PortfolioService, private readonly propertyService: PropertyService @@ -75,50 +74,71 @@ export class AiService { userId }); - const holdingsTableColumns: ColumnDescriptor[] = HOLDINGS_TABLE_COLUMNS.map( - ({ align, name }) => { + const holdingsTableColumns: ColumnDescriptor[] = + this.HOLDINGS_TABLE_COLUMNS.map(({ align, name }) => { return { name, align: align ?? 'left' }; - } - ); + }); const holdingsTableRows = Object.values(holdings) .sort((a, b) => { return b.allocationInPercentage - a.allocationInPercentage; }) - .map((holding) => { - return HOLDINGS_TABLE_COLUMNS.reduce( - (row, { key, name }) => { - switch (key) { - case 'CURRENCY': - row[name] = holding.currency; - break; - case 'NAME': - row[name] = holding.name; - break; - case 'SYMBOL': - row[name] = holding.symbol; - break; - case 'ASSET_CLASS': - row[name] = holding.assetClass ?? ''; - break; - case 'ALLOCATION_PERCENTAGE': - row[name] = - `${(holding.allocationInPercentage * 100).toFixed(3)}%`; - break; - case 'ASSET_SUB_CLASS': - row[name] = holding.assetSubClass ?? ''; - break; - default: - row[name] = ''; - break; - } - return row; - }, - {} as Record - ); - }); + .map( + ({ + allocationInPercentage, + assetClass, + assetSubClass, + currency, + name: label, + symbol + }) => { + return this.HOLDINGS_TABLE_COLUMNS.reduce( + (row, { key, name }) => { + switch (key) { + case 'ALLOCATION_PERCENTAGE': + row[name] = `${(allocationInPercentage * 100).toFixed(3)}%`; + break; + + case 'ASSET_CLASS': + row[name] = assetClass ?? ''; + break; + + case 'ASSET_SUB_CLASS': + row[name] = assetSubClass ?? ''; + break; + + case 'CURRENCY': + row[name] = currency; + break; + + case 'NAME': + row[name] = label; + break; + + case 'SYMBOL': + row[name] = symbol; + break; + + default: + row[name] = ''; + break; + } + + return row; + }, + {} as Record + ); + } + ); + + // Dynamic import to load ESM module from CommonJS context + // eslint-disable-next-line @typescript-eslint/no-implied-eval + const dynamicImport = new Function('s', 'return import(s)') as ( + s: string + ) => Promise; + const { tablemark } = await dynamicImport('tablemark'); - const holdingsTableString = tablemark.default(holdingsTableRows, { + const holdingsTableString = tablemark(holdingsTableRows, { columns: holdingsTableColumns });