From b260c4f450e126d656d8b81bdf3cc4377046e448 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 7 Mar 2025 19:48:03 +0100 Subject: [PATCH 1/2] Feature/extend personal finance tools 20250306 (#4406) * Extend personal finance tools * CoinStats * Fincake * Koinly * Nansen * Simply Wall St * Tradervue --- libs/common/src/lib/personal-finance-tools.ts | 57 +++++++++++++++++++ libs/ui/src/lib/i18n.ts | 3 + 2 files changed, 60 insertions(+) diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index e4c820fb..936f3c6f 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -82,6 +82,16 @@ export const personalFinanceTools: Product[] = [ regions: ['Global'], slogan: 'Take control of your financial future' }, + { + founded: 2017, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'coinstats', + name: 'CoinStats', + origin: 'Armenia', + pricingPerYear: '$168', + slogan: 'Manage All Your Wallets & Exchanges From One Place' + }, { founded: 2013, hasFreePlan: true, @@ -154,6 +164,7 @@ export const personalFinanceTools: Product[] = [ name: 'Delta Investment Tracker', note: 'Acquired by eToro', origin: 'Belgium', + pricingPerYear: '$150', slogan: 'The app to track all your investments. Make smart moves only.' }, { @@ -252,6 +263,13 @@ export const personalFinanceTools: Product[] = [ slogan: 'The most convenient mobile application for personal finance accounting' }, + { + founded: 2022, + key: 'fincake', + name: 'Fincake', + origin: 'British Virgin Islands', + slogan: 'Easy-to-use Portfolio Tracker' + }, { founded: 2023, hasFreePlan: true, @@ -340,6 +358,15 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '€119', slogan: 'ETF portfolios made simple' }, + { + founded: 2018, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'koinly', + name: 'Koinly', + origin: 'Singapore', + slogan: 'Track all your crypto wallets in one place' + }, { founded: 2016, hasFreePlan: true, @@ -469,6 +496,16 @@ export const personalFinanceTools: Product[] = [ slogan: 'Track your equity, fund, investment trust, ETF and pension investments in one place.' }, + { + founded: 2020, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'nansen', + name: 'Crypto Portfolio Tracker by Nansen', + origin: 'Singapore', + pricingPerYear: '$1188', + slogan: 'Your Complete Crypto Portfolio, Reimagined' + }, { founded: 2017, hasFreePlan: false, @@ -634,6 +671,16 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '€80', slogan: 'Stock Portfolio Tracker' }, + { + founded: 2014, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'simply-wallstreet', + name: 'Stock Portfolio Tracker & Visualizer by Simply Wall St', + origin: 'Australia', + pricingPerYear: '$120', + slogan: 'Smart portfolio tracker for informed investors' + }, { founded: 2021, hasFreePlan: true, @@ -706,6 +753,16 @@ export const personalFinanceTools: Product[] = [ slogan: 'Your financial life in a spreadsheet, automatically updated each day' }, + { + founded: 2011, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'tradervue', + name: 'Tradervue', + origin: 'United States', + pricingPerYear: '$360', + slogan: 'The Trading Journal to Improve Your Trading Performance' + }, { hasFreePlan: true, hasSelfHostingAbility: false, diff --git a/libs/ui/src/lib/i18n.ts b/libs/ui/src/lib/i18n.ts index 7ea36123..4c09a4fc 100644 --- a/libs/ui/src/lib/i18n.ts +++ b/libs/ui/src/lib/i18n.ts @@ -70,9 +70,11 @@ const locales = { 'South America': $localize`South America`, // Countries + Armenia: $localize`Armenia`, Australia: $localize`Australia`, Austria: $localize`Austria`, Belgium: $localize`Belgium`, + 'British Virgin Islands': $localize`British Virgin Islands`, Bulgaria: $localize`Bulgaria`, Canada: $localize`Canada`, 'Czech Republic': $localize`Czech Republic`, @@ -86,6 +88,7 @@ const locales = { 'New Zealand': $localize`New Zealand`, Poland: $localize`Poland`, Romania: $localize`Romania`, + Singapore: $localize`Singapore`, 'South Africa': $localize`South Africa`, Switzerland: $localize`Switzerland`, Thailand: $localize`Thailand`, From 589eefaa764090516f55a16d70899df7fe05ff4a Mon Sep 17 00:00:00 2001 From: csehatt741 <77381875+csehatt741@users.noreply.github.com> Date: Fri, 7 Mar 2025 19:48:52 +0100 Subject: [PATCH 2/2] Feature/extend AI prompt API by mode (#4395) * Extend AI prompt API by mode * Update changelog --- CHANGELOG.md | 6 +++++ .../api/src/app/endpoints/ai/ai.controller.ts | 11 ++++++---- apps/api/src/app/endpoints/ai/ai.service.ts | 7 ++++++ .../analysis/analysis-page.component.ts | 6 ++--- .../portfolio/analysis/analysis-page.html | 22 +++++++++++++++++-- apps/client/src/app/services/data.service.ts | 11 +++++++--- .../src/lib/types/ai-prompt-mode.type.ts | 1 + libs/common/src/lib/types/index.ts | 2 ++ 8 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 libs/common/src/lib/types/ai-prompt-mode.type.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 432a953c..88b90c94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added a _Copy portfolio data to clipboard for AI prompt_ action to the analysis page (experimental) + ## 2.144.0 - 2025-03-06 ### Fixed diff --git a/apps/api/src/app/endpoints/ai/ai.controller.ts b/apps/api/src/app/endpoints/ai/ai.controller.ts index 981b26aa..910abbf9 100644 --- a/apps/api/src/app/endpoints/ai/ai.controller.ts +++ b/apps/api/src/app/endpoints/ai/ai.controller.ts @@ -6,9 +6,9 @@ import { } from '@ghostfolio/common/config'; import { AiPromptResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; -import type { RequestWithUser } from '@ghostfolio/common/types'; +import type { AiPromptMode, RequestWithUser } from '@ghostfolio/common/types'; -import { Controller, Get, Inject, UseGuards } from '@nestjs/common'; +import { Controller, Get, Inject, Param, UseGuards } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; @@ -21,11 +21,14 @@ export class AiController { @Inject(REQUEST) private readonly request: RequestWithUser ) {} - @Get('prompt') + @Get('prompt/:mode') @HasPermission(permissions.readAiPrompt) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async getPrompt(): Promise { + public async getPrompt( + @Param('mode') mode: AiPromptMode + ): Promise { const prompt = await this.aiService.getPrompt({ + mode, impersonationId: undefined, languageCode: this.request.user.Settings.settings.language ?? DEFAULT_LANGUAGE_CODE, diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index 59dec6ad..d9090d77 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -1,4 +1,5 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; +import type { AiPromptMode } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; @@ -9,11 +10,13 @@ export class AiService { public async getPrompt({ impersonationId, languageCode, + mode, userCurrency, userId }: { impersonationId: string; languageCode: string; + mode: AiPromptMode; userCurrency: string; userId: string; }) { @@ -43,6 +46,10 @@ export class AiService { ) ]; + if (mode === 'portfolio') { + return holdingsTable.join('\n'); + } + return [ `You are a neutral financial assistant. Please analyze the following investment portfolio (base currency being ${userCurrency}) in simple words.`, ...holdingsTable, diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 5eebb42e..28c0b9c0 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -12,7 +12,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { GroupBy } from '@ghostfolio/common/types'; +import type { AiPromptMode, GroupBy } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { Clipboard } from '@angular/cdk/clipboard'; @@ -142,9 +142,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { this.fetchDividendsAndInvestments(); } - public onCopyPromptToClipboard() { + public onCopyPromptToClipboard(mode: AiPromptMode) { this.dataService - .fetchPrompt() + .fetchPrompt(mode) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ prompt }) => { this.clipboard.copy(prompt); diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html index 07ffa705..84ca54e0 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -16,7 +16,7 @@ + diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 0bc4ebcc..e8d4b782 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -46,7 +46,12 @@ import { User } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; -import { AccountWithValue, DateRange, GroupBy } from '@ghostfolio/common/types'; +import type { + AccountWithValue, + AiPromptMode, + DateRange, + GroupBy +} from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { HttpClient, HttpParams } from '@angular/common/http'; @@ -650,8 +655,8 @@ export class DataService { return this.http.get('/api/v1/portfolio/report'); } - public fetchPrompt() { - return this.http.get('/api/v1/ai/prompt'); + public fetchPrompt(mode: AiPromptMode) { + return this.http.get(`/api/v1/ai/prompt/${mode}`); } public fetchPublicPortfolio(aAccessId: string) { diff --git a/libs/common/src/lib/types/ai-prompt-mode.type.ts b/libs/common/src/lib/types/ai-prompt-mode.type.ts new file mode 100644 index 00000000..00a031df --- /dev/null +++ b/libs/common/src/lib/types/ai-prompt-mode.type.ts @@ -0,0 +1 @@ +export type AiPromptMode = 'analysis' | 'portfolio'; diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index 9e8178d3..668486a9 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -2,6 +2,7 @@ import type { AccessType } from './access-type.type'; import type { AccessWithGranteeUser } from './access-with-grantee-user.type'; import type { AccountWithPlatform } from './account-with-platform.type'; import type { AccountWithValue } from './account-with-value.type'; +import type { AiPromptMode } from './ai-prompt-mode.type'; import type { BenchmarkTrend } from './benchmark-trend.type'; import type { ColorScheme } from './color-scheme.type'; import type { DateRange } from './date-range.type'; @@ -24,6 +25,7 @@ export type { AccessWithGranteeUser, AccountWithPlatform, AccountWithValue, + AiPromptMode, BenchmarkTrend, ColorScheme, DateRange,