mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 15:47:35 +00:00
LLM reccomendation service
This commit is contained in:
+12
-1
@@ -1,4 +1,15 @@
|
||||
<app-modal-shell [title]="modalTitle" (closed)="closed.emit()">
|
||||
@if (!isEditMode) {
|
||||
<div class="ai-suggest-row">
|
||||
<button class="btn-ai" (click)="onGetRecommendation()" [disabled]="recommending() || saving()">
|
||||
{{ recommending() ? 'Generating...' : '✦ Suggest with AI' }}
|
||||
</button>
|
||||
@if (recommendError()) {
|
||||
<p class="recommend-error">{{ recommendError() }}</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="field">
|
||||
<label class="field-label">Benchmark</label>
|
||||
<textarea class="field-input field-textarea" [(ngModel)]="benchmarkText"
|
||||
@@ -16,7 +27,7 @@
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="btn-secondary" (click)="closed.emit()">Cancel</button>
|
||||
<button class="btn-primary" (click)="onSave()" [disabled]="saving() || !benchmarkText.trim()">
|
||||
<button class="btn-primary" (click)="onSave()" [disabled]="saving() || recommending() || !benchmarkText.trim()">
|
||||
{{ saving() ? 'Saving...' : submitLabel }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+32
-1
@@ -1 +1,32 @@
|
||||
/* Inherits all styles from modal-shell via ::ng-deep */
|
||||
:host ::ng-deep {
|
||||
.ai-suggest-row {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.btn-ai {
|
||||
padding: 7px 14px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid #c4b5fd;
|
||||
background: #f5f3ff;
|
||||
color: #6d28d9;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #ede9fe;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.recommend-error {
|
||||
font-size: 12px;
|
||||
color: #dc2626;
|
||||
margin: 6px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -22,7 +22,9 @@ export class EditBenchmarkModal {
|
||||
readonly closed = output<void>();
|
||||
|
||||
protected readonly saving = signal(false);
|
||||
protected readonly recommending = signal(false);
|
||||
protected readonly errorMessage = signal<string | null>(null);
|
||||
protected readonly recommendError = signal<string | null>(null);
|
||||
|
||||
protected shortName = '';
|
||||
protected benchmarkText = '';
|
||||
@@ -47,6 +49,22 @@ export class EditBenchmarkModal {
|
||||
}
|
||||
}
|
||||
|
||||
async onGetRecommendation() {
|
||||
this.recommending.set(true);
|
||||
this.recommendError.set(null);
|
||||
|
||||
const result = await this.studentService.getBenchmarkRecommendation(this.studentId(), this.goalId());
|
||||
|
||||
this.recommending.set(false);
|
||||
|
||||
if (result.success && result.payload) {
|
||||
this.benchmarkText = result.payload.benchmark;
|
||||
this.shortName = result.payload.shortName;
|
||||
} else {
|
||||
this.recommendError.set(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
async onSave() {
|
||||
if (!this.benchmarkText.trim()) return;
|
||||
this.saving.set(true);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export interface BenchmarkRecommendationDto {
|
||||
benchmark: string;
|
||||
shortName: string;
|
||||
}
|
||||
|
||||
export interface StudentBenchmarkSummary {
|
||||
studentIdentifier: string;
|
||||
benchmarks: BenchmarkDto[];
|
||||
|
||||
@@ -24,7 +24,7 @@ export function describeHttpError(error: HttpErrorResponse): string {
|
||||
case 500:
|
||||
return 'Server error (500). The API encountered an internal failure (possibly a database issue).';
|
||||
case 503:
|
||||
return 'Server unavailable (503). The API may be starting up or overwhelmed.';
|
||||
return serverMessage ?? 'Service unavailable (503). The API or a required provider may be starting up or unreachable.';
|
||||
default:
|
||||
return `Unexpected error (${error.status}).`;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { CreateStudentDto } from '../classes/create-student.dto';
|
||||
import { CreateGoalDto } from '../classes/create-goal.dto';
|
||||
import { StudentCardDto } from '../classes/student-card.dto';
|
||||
import { StudentGoalSummary, StudentGoalItem } from '../classes/student-goal';
|
||||
import { StudentBenchmarkSummary } from '../classes/benchmark.dto';
|
||||
import { BenchmarkRecommendationDto, StudentBenchmarkSummary } from '../classes/benchmark.dto';
|
||||
import { StudentProgressReportDto } from '../classes/student-progress-report.dto';
|
||||
import { StudentFullProfileDto } from '../classes/student-full-profile.dto';
|
||||
|
||||
@@ -269,6 +269,24 @@ export class StudentService {
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Requests an AI-generated benchmark recommendation for a goal.
|
||||
// *****************************************************************
|
||||
async getBenchmarkRecommendation(studentId: string, goalId: string): Promise<ApiResult<BenchmarkRecommendationDto>> {
|
||||
try {
|
||||
const result = await firstValueFrom(
|
||||
this.http.get<ResponseResult<BenchmarkRecommendationDto>>(
|
||||
`${this.base}/api/Student/${studentId}/goals/${goalId}/benchmark-recommendation`
|
||||
)
|
||||
);
|
||||
return result.success && result.data
|
||||
? ApiResult.ok(result.data)
|
||||
: ApiResult.fail(result.message);
|
||||
} catch (error) {
|
||||
return ApiResult.fail(describeHttpError(error as HttpErrorResponse));
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Updates a goal's description, category, and baseline.
|
||||
// *****************************************************************
|
||||
|
||||
Reference in New Issue
Block a user