Browse Source

Feature/improve usability of AI prompt actions (#4426)

* Improve usability of AI prompt actions

* Update changelog
pull/4438/head
Tobias Kugel 2 weeks ago
committed by GitHub
parent
commit
9e44023f86
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 2
      apps/client/src/app/components/header/header.component.html
  3. 27
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  4. 90
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  5. 2
      apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts
  6. 2
      apps/client/src/styles.scss

6
CHANGELOG.md

@ -7,12 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Improved the usability of the user account registration
### Changed ### Changed
- Improved the usability of the user account registration
- Improved the usability of the _Copy AI prompt to clipboard_ actions on the analysis page (experimental)
- Improved the language localization for German (`de`) - Improved the language localization for German (`de`)
- Upgraded `angular` from version `19.0.5` to `19.2.1` - Upgraded `angular` from version `19.0.5` to `19.2.1`
- Upgraded `Nx` from version `20.3.2` to `20.5.0` - Upgraded `Nx` from version `20.3.2` to `20.5.0`

2
apps/client/src/app/components/header/header.component.html

@ -134,7 +134,7 @@
</button> </button>
<mat-menu <mat-menu
#assistantMenu="matMenu" #assistantMenu="matMenu"
class="assistant" class="no-max-width"
xPosition="before" xPosition="before"
[overlapTrigger]="true" [overlapTrigger]="true"
(closed)="assistantElement?.setIsOpen(false)" (closed)="assistantElement?.setIsOpen(false)"

27
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -16,7 +16,14 @@ import type { AiPromptMode, GroupBy } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import {
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
ViewChild
} from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { SymbolProfile } from '@prisma/client'; import { SymbolProfile } from '@prisma/client';
import { isNumber, sortBy } from 'lodash'; import { isNumber, sortBy } from 'lodash';
@ -32,6 +39,8 @@ import { takeUntil } from 'rxjs/operators';
standalone: false standalone: false
}) })
export class AnalysisPageComponent implements OnDestroy, OnInit { export class AnalysisPageComponent implements OnDestroy, OnInit {
@ViewChild(MatMenuTrigger) actionsMenuButton!: MatMenuTrigger;
public benchmark: Partial<SymbolProfile>; public benchmark: Partial<SymbolProfile>;
public benchmarkDataItems: HistoricalDataItem[] = []; public benchmarkDataItems: HistoricalDataItem[] = [];
public benchmarks: Partial<SymbolProfile>[]; public benchmarks: Partial<SymbolProfile>[];
@ -46,10 +55,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public investments: InvestmentItem[]; public investments: InvestmentItem[];
public investmentTimelineDataLabel = $localize`Investment`; public investmentTimelineDataLabel = $localize`Investment`;
public investmentsByGroup: InvestmentItem[]; public investmentsByGroup: InvestmentItem[];
public isLoadingAnalysisPrompt: boolean;
public isLoadingBenchmarkComparator: boolean; public isLoadingBenchmarkComparator: boolean;
public isLoadingDividendTimelineChart: boolean; public isLoadingDividendTimelineChart: boolean;
public isLoadingInvestmentChart: boolean; public isLoadingInvestmentChart: boolean;
public isLoadingInvestmentTimelineChart: boolean; public isLoadingInvestmentTimelineChart: boolean;
public isLoadingPortfolioPrompt: boolean;
public mode: GroupBy = 'month'; public mode: GroupBy = 'month';
public modeOptions: ToggleOption[] = [ public modeOptions: ToggleOption[] = [
{ label: $localize`Monthly`, value: 'month' }, { label: $localize`Monthly`, value: 'month' },
@ -143,6 +154,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
} }
public onCopyPromptToClipboard(mode: AiPromptMode) { public onCopyPromptToClipboard(mode: AiPromptMode) {
if (mode === 'analysis') {
this.isLoadingAnalysisPrompt = true;
} else if (mode === 'portfolio') {
this.isLoadingPortfolioPrompt = true;
}
this.dataService this.dataService
.fetchPrompt(mode) .fetchPrompt(mode)
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
@ -163,6 +180,14 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
.subscribe(() => { .subscribe(() => {
window.open('https://duck.ai', '_blank'); window.open('https://duck.ai', '_blank');
}); });
this.actionsMenuButton.closeMenu();
if (mode === 'analysis') {
this.isLoadingAnalysisPrompt = false;
} else if (mode === 'portfolio') {
this.isLoadingPortfolioPrompt = false;
}
}); });
} }

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

@ -5,6 +5,7 @@
<div class="col-lg"> <div class="col-lg">
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button <button
#actionsMenuButton
class="mx-1 no-min-width px-2" class="mx-1 no-min-width px-2"
mat-stroked-button mat-stroked-button
[matMenuTriggerFor]="actionsMenu" [matMenuTriggerFor]="actionsMenu"
@ -12,39 +13,62 @@
> >
<ion-icon name="ellipsis-vertical" /> <ion-icon name="ellipsis-vertical" />
</button> </button>
<mat-menu #actionsMenu="matMenu" xPosition="before"> <mat-menu
<button #actionsMenu="matMenu"
mat-menu-item class="no-max-width"
[disabled]="!hasPermissionToReadAiPrompt" xPosition="before"
(click)="onCopyPromptToClipboard('portfolio')" >
> <div (click)="$event.stopPropagation()">
<span class="align-items-center d-flex"> <button
@if (user?.subscription?.type === 'Basic') { mat-menu-item
<gf-premium-indicator class="mr-2" /> [disabled]="!hasPermissionToReadAiPrompt"
} @else { (click)="onCopyPromptToClipboard('portfolio')"
<ion-icon class="mr-2" name="copy-outline" /> >
} <span class="align-items-center d-flex">
<ng-container i18n @if (user?.subscription?.type === 'Basic') {
>Copy portfolio data to clipboard for AI prompt</ng-container <gf-premium-indicator class="mr-2" />
> } @else {
</span> @if (isLoadingPortfolioPrompt) {
</button> <mat-spinner
<button class="mr-2"
mat-menu-item color="accent"
[disabled]="!hasPermissionToReadAiPrompt" [diameter]="16"
(click)="onCopyPromptToClipboard('analysis')" />
> } @else {
<span class="align-items-center d-flex"> <ion-icon class="mr-2" name="copy-outline" />
@if (user?.subscription?.type === 'Basic') { }
<gf-premium-indicator class="mr-2" /> }
} @else { <ng-container i18n
<ion-icon class="mr-2" name="copy-outline" /> >Copy portfolio data to clipboard for AI
} prompt</ng-container
<ng-container i18n >
>Copy AI prompt to clipboard for analysis</ng-container </span>
> </button>
</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 {
@if (isLoadingAnalysisPrompt) {
<mat-spinner
class="mr-2"
color="accent"
[diameter]="16"
/>
} @else {
<ion-icon class="mr-2" name="copy-outline" />
}
}
<ng-container i18n
>Copy AI prompt to clipboard for analysis</ng-container
>
</span>
</button>
</div>
</mat-menu> </mat-menu>
</div> </div>
</div> </div>

2
apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts

@ -10,6 +10,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AnalysisPageRoutingModule } from './analysis-page-routing.module'; import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
@ -29,6 +30,7 @@ import { AnalysisPageComponent } from './analysis-page.component';
MatButtonModule, MatButtonModule,
MatCardModule, MatCardModule,
MatMenuModule, MatMenuModule,
MatProgressSpinnerModule,
NgxSkeletonLoaderModule NgxSkeletonLoaderModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]

2
apps/client/src/styles.scss

@ -451,7 +451,7 @@ ngx-skeleton-loader {
} }
.mat-mdc-menu-panel { .mat-mdc-menu-panel {
&.assistant { &.no-max-width {
max-width: unset !important; max-width: unset !important;
.mat-mdc-menu-content { .mat-mdc-menu-content {

Loading…
Cancel
Save