Compare commits

..

3 Commits

Author SHA1 Message Date
ee361bf669 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 34m46s
2025-03-18 20:08:39 -07:00
csehatt741
efc0b1bf5a
Feature/support filters in AI prompt API (#4431)
* Support filters in AI prompt API

* Update changelog
2025-03-18 20:13:24 +01:00
Chang-Yen Tseng
235db72ade
Bugfix/change client-side dates to be sent in UTC format (#4402)
* Change client-side dates to be sent in UTC format

* Update changelog
2025-03-18 20:06:00 +01:00
9 changed files with 66 additions and 25 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added support for filtering in the _Copy AI prompt to clipboard_ actions on the analysis page (experimental)
### Changed
- Improved the symbol validation in the _Yahoo Finance_ service (get asset profiles)
@ -15,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed an issue in the activities import functionality related to the account balances
- Changed client-side dates to be sent in UTC format to ensure date consistency
- Benchmark endpoint
- Exchange rate endpoint
## 2.146.0 - 2025-03-15

View File

@ -1,5 +1,6 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE
@ -8,7 +9,14 @@ import { AiPromptResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { AiPromptMode, RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Get, Inject, Param, UseGuards } from '@nestjs/common';
import {
Controller,
Get,
Inject,
Param,
Query,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
@ -18,6 +26,7 @@ import { AiService } from './ai.service';
export class AiController {
public constructor(
private readonly aiService: AiService,
private readonly apiService: ApiService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@ -25,9 +34,23 @@ export class AiController {
@HasPermission(permissions.readAiPrompt)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getPrompt(
@Param('mode') mode: AiPromptMode
@Param('mode') mode: AiPromptMode,
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string,
@Query('tags') filterByTags?: string
): Promise<AiPromptResponse> {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByDataSource,
filterBySymbol,
filterByTags
});
const prompt = await this.aiService.getPrompt({
filters,
mode,
impersonationId: undefined,
languageCode:

View File

@ -7,6 +7,7 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.servic
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
@ -25,6 +26,7 @@ import { AiService } from './ai.service';
@Module({
controllers: [AiController],
imports: [
ApiModule,
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,

View File

@ -1,4 +1,5 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { Filter } from '@ghostfolio/common/interfaces';
import type { AiPromptMode } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
@ -8,12 +9,14 @@ export class AiService {
public constructor(private readonly portfolioService: PortfolioService) {}
public async getPrompt({
filters,
impersonationId,
languageCode,
mode,
userCurrency,
userId
}: {
filters?: Filter[];
impersonationId: string;
languageCode: string;
mode: AiPromptMode;
@ -21,6 +24,7 @@ export class AiService {
userId: string;
}) {
const { holdings } = await this.portfolioService.getDetails({
filters,
impersonationId,
userId
});

View File

@ -190,7 +190,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
}
this.dataService
.fetchPrompt(mode)
.fetchPrompt({
mode,
filters: this.userService.getFilters()
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ prompt }) => {
this.clipboard.copy(prompt);

View File

@ -8,7 +8,6 @@ import {
PROPERTY_API_KEY_GHOSTFOLIO
} from '@ghostfolio/common/config';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
AdminData,
@ -25,7 +24,6 @@ import { Injectable } from '@angular/core';
import { SortDirection } from '@angular/material/sort';
import { DataSource, MarketData, Platform } from '@prisma/client';
import { JobStatus } from 'bull';
import { format } from 'date-fns';
import { switchMap } from 'rxjs';
import { environment } from '../../environments/environment';
@ -186,19 +184,8 @@ export class AdminService {
);
}
public gatherSymbol({
dataSource,
date,
symbol
}: AssetProfileIdentifier & {
date?: Date;
}) {
let url = `/api/v1/admin/gather/${dataSource}/${symbol}`;
if (date) {
url = `${url}/${format(date, DATE_FORMAT)}`;
}
public gatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) {
const url = `/api/v1/admin/gather/${dataSource}/${symbol}`;
return this.http.post<MarketData | void>(url, {});
}

View File

@ -57,6 +57,7 @@ import { translate } from '@ghostfolio/ui/i18n';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { SortDirection } from '@angular/material/sort';
import { utc } from '@date-fns/utc';
import {
AccountBalance,
DataSource,
@ -281,7 +282,7 @@ export class DataService {
symbol: string;
}) {
return this.http.get<IDataProviderHistoricalResponse>(
`/api/v1/exchange-rate/${symbol}/${format(date, DATE_FORMAT)}`
`/api/v1/exchange-rate/${symbol}/${format(date, DATE_FORMAT, { in: utc })}`
);
}
@ -363,10 +364,7 @@ export class DataService {
}
return this.http.get<BenchmarkMarketDataDetails>(
`/api/v1/benchmarks/${dataSource}/${symbol}/${format(
startDate,
DATE_FORMAT
)}`,
`/api/v1/benchmarks/${dataSource}/${symbol}/${format(startDate, DATE_FORMAT, { in: utc })}`,
{ params }
);
}
@ -655,8 +653,18 @@ export class DataService {
return this.http.get<PortfolioReportResponse>('/api/v1/portfolio/report');
}
public fetchPrompt(mode: AiPromptMode) {
return this.http.get<AiPromptResponse>(`/api/v1/ai/prompt/${mode}`);
public fetchPrompt({
filters,
mode
}: {
filters?: Filter[];
mode: AiPromptMode;
}) {
const params = this.buildFiltersAsQueryParams({ filters });
return this.http.get<AiPromptResponse>(`/api/v1/ai/prompt/${mode}`, {
params
});
}
public fetchPublicPortfolio(aAccessId: string) {

6
package-lock.json generated
View File

@ -22,6 +22,7 @@
"@angular/router": "19.2.1",
"@angular/service-worker": "19.2.1",
"@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0",
"@dfinity/agent": "0.15.7",
"@dfinity/auth-client": "0.15.7",
"@dfinity/candid": "0.15.7",
@ -3113,6 +3114,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/@date-fns/utc": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.0.tgz",
"integrity": "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="
},
"node_modules/@dfinity/agent": {
"version": "0.15.7",
"resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.15.7.tgz",

View File

@ -68,6 +68,7 @@
"@angular/router": "19.2.1",
"@angular/service-worker": "19.2.1",
"@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0",
"@dfinity/agent": "0.15.7",
"@dfinity/auth-client": "0.15.7",
"@dfinity/candid": "0.15.7",