Browse Source

Feature/extend AI prompt API by mode (#4395)

* Extend AI prompt API by mode

* Update changelog
pull/4394/head
csehatt741 2 weeks ago
committed by GitHub
parent
commit
589eefaa76
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 11
      apps/api/src/app/endpoints/ai/ai.controller.ts
  3. 7
      apps/api/src/app/endpoints/ai/ai.service.ts
  4. 6
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  5. 22
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  6. 11
      apps/client/src/app/services/data.service.ts
  7. 1
      libs/common/src/lib/types/ai-prompt-mode.type.ts
  8. 2
      libs/common/src/lib/types/index.ts

6
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

11
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<AiPromptResponse> {
public async getPrompt(
@Param('mode') mode: AiPromptMode
): Promise<AiPromptResponse> {
const prompt = await this.aiService.getPrompt({
mode,
impersonationId: undefined,
languageCode:
this.request.user.Settings.settings.language ?? DEFAULT_LANGUAGE_CODE,

7
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,

6
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);

22
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -16,7 +16,7 @@
<button
mat-menu-item
[disabled]="!hasPermissionToReadAiPrompt"
(click)="onCopyPromptToClipboard()"
(click)="onCopyPromptToClipboard('portfolio')"
>
<span class="align-items-center d-flex">
@if (user?.subscription?.type === 'Basic') {
@ -24,7 +24,25 @@
} @else {
<ion-icon class="mr-2" name="copy-outline" />
}
<ng-container i18n>Copy AI prompt to clipboard</ng-container>
<ng-container i18n
>Copy portfolio data to clipboard for AI prompt</ng-container
>
</span>
</button>
<button
mat-menu-item
[disabled]="!hasPermissionToReadAiPrompt"
(click)="onCopyPromptToClipboard('analysis')"
>
<span class="align-items-center d-flex">
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="mr-2" />
} @else {
<ion-icon class="mr-2" name="copy-outline" />
}
<ng-container i18n
>Copy AI prompt to clipboard for analysis</ng-container
>
</span>
</button>
</mat-menu>

11
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<PortfolioReportResponse>('/api/v1/portfolio/report');
}
public fetchPrompt() {
return this.http.get<AiPromptResponse>('/api/v1/ai/prompt');
public fetchPrompt(mode: AiPromptMode) {
return this.http.get<AiPromptResponse>(`/api/v1/ai/prompt/${mode}`);
}
public fetchPublicPortfolio(aAccessId: string) {

1
libs/common/src/lib/types/ai-prompt-mode.type.ts

@ -0,0 +1 @@
export type AiPromptMode = 'analysis' | 'portfolio';

2
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,

Loading…
Cancel
Save