Feature/improve usability of AI prompt actions (#4426)
* Improve usability of AI prompt actions * Update changelog
This commit is contained in:
parent
a4c78739bb
commit
9e44023f86
@ -7,12 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Improved the usability of the user account registration
|
||||
|
||||
### 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`)
|
||||
- Upgraded `angular` from version `19.0.5` to `19.2.1`
|
||||
- Upgraded `Nx` from version `20.3.2` to `20.5.0`
|
||||
|
@ -134,7 +134,7 @@
|
||||
</button>
|
||||
<mat-menu
|
||||
#assistantMenu="matMenu"
|
||||
class="assistant"
|
||||
class="no-max-width"
|
||||
xPosition="before"
|
||||
[overlapTrigger]="true"
|
||||
(closed)="assistantElement?.setIsOpen(false)"
|
||||
|
@ -16,7 +16,14 @@ import type { AiPromptMode, GroupBy } from '@ghostfolio/common/types';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
|
||||
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 { SymbolProfile } from '@prisma/client';
|
||||
import { isNumber, sortBy } from 'lodash';
|
||||
@ -32,6 +39,8 @@ import { takeUntil } from 'rxjs/operators';
|
||||
standalone: false
|
||||
})
|
||||
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
@ViewChild(MatMenuTrigger) actionsMenuButton!: MatMenuTrigger;
|
||||
|
||||
public benchmark: Partial<SymbolProfile>;
|
||||
public benchmarkDataItems: HistoricalDataItem[] = [];
|
||||
public benchmarks: Partial<SymbolProfile>[];
|
||||
@ -46,10 +55,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
public investments: InvestmentItem[];
|
||||
public investmentTimelineDataLabel = $localize`Investment`;
|
||||
public investmentsByGroup: InvestmentItem[];
|
||||
public isLoadingAnalysisPrompt: boolean;
|
||||
public isLoadingBenchmarkComparator: boolean;
|
||||
public isLoadingDividendTimelineChart: boolean;
|
||||
public isLoadingInvestmentChart: boolean;
|
||||
public isLoadingInvestmentTimelineChart: boolean;
|
||||
public isLoadingPortfolioPrompt: boolean;
|
||||
public mode: GroupBy = 'month';
|
||||
public modeOptions: ToggleOption[] = [
|
||||
{ label: $localize`Monthly`, value: 'month' },
|
||||
@ -143,6 +154,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onCopyPromptToClipboard(mode: AiPromptMode) {
|
||||
if (mode === 'analysis') {
|
||||
this.isLoadingAnalysisPrompt = true;
|
||||
} else if (mode === 'portfolio') {
|
||||
this.isLoadingPortfolioPrompt = true;
|
||||
}
|
||||
|
||||
this.dataService
|
||||
.fetchPrompt(mode)
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
@ -163,6 +180,14 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
.subscribe(() => {
|
||||
window.open('https://duck.ai', '_blank');
|
||||
});
|
||||
|
||||
this.actionsMenuButton.closeMenu();
|
||||
|
||||
if (mode === 'analysis') {
|
||||
this.isLoadingAnalysisPrompt = false;
|
||||
} else if (mode === 'portfolio') {
|
||||
this.isLoadingPortfolioPrompt = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
<div class="col-lg">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button
|
||||
#actionsMenuButton
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-stroked-button
|
||||
[matMenuTriggerFor]="actionsMenu"
|
||||
@ -12,39 +13,62 @@
|
||||
>
|
||||
<ion-icon name="ellipsis-vertical" />
|
||||
</button>
|
||||
<mat-menu #actionsMenu="matMenu" xPosition="before">
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="!hasPermissionToReadAiPrompt"
|
||||
(click)="onCopyPromptToClipboard('portfolio')"
|
||||
>
|
||||
<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 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
|
||||
#actionsMenu="matMenu"
|
||||
class="no-max-width"
|
||||
xPosition="before"
|
||||
>
|
||||
<div (click)="$event.stopPropagation()">
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="!hasPermissionToReadAiPrompt"
|
||||
(click)="onCopyPromptToClipboard('portfolio')"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="mr-2" />
|
||||
} @else {
|
||||
@if (isLoadingPortfolioPrompt) {
|
||||
<mat-spinner
|
||||
class="mr-2"
|
||||
color="accent"
|
||||
[diameter]="16"
|
||||
/>
|
||||
} @else {
|
||||
<ion-icon class="mr-2" name="copy-outline" />
|
||||
}
|
||||
}
|
||||
<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 {
|
||||
@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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,6 +10,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
|
||||
@ -29,6 +30,7 @@ import { AnalysisPageComponent } from './analysis-page.component';
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatMenuModule,
|
||||
MatProgressSpinnerModule,
|
||||
NgxSkeletonLoaderModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
|
@ -451,7 +451,7 @@ ngx-skeleton-loader {
|
||||
}
|
||||
|
||||
.mat-mdc-menu-panel {
|
||||
&.assistant {
|
||||
&.no-max-width {
|
||||
max-width: unset !important;
|
||||
|
||||
.mat-mdc-menu-content {
|
||||
|
Loading…
x
Reference in New Issue
Block a user