8e2afb93f3
- NCWF-5: scaffold /analysis route with ChessBoard viewer and navigation - NCWF-6: FEN / PGN / Game-ID input form with depth selector - NCWF-7: extend GameApiService with analyzePosition(); add AnalysisService with game-wide annotation pipeline; proxy /api/analysis -> :8087 - NCWF-8: EvalTimelineComponent — SVG win-chance chart per ply - NCWF-9: AnnotatedMoveListComponent — quality labels (!! ! ?! ? ??) derived from win-chance delta Also fix pre-existing app.spec.ts failure (missing provideHttpClient). Apply project-wide prettier formatting pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
124 lines
3.1 KiB
TypeScript
124 lines
3.1 KiB
TypeScript
import { Component, DestroyRef, OnInit, inject } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { RouterLink } from '@angular/router';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
import { BotService } from '../../services/bot.service';
|
|
import { Bot, BotWithToken } from '../../models/bot.models';
|
|
|
|
@Component({
|
|
selector: 'app-bots',
|
|
standalone: true,
|
|
imports: [CommonModule, RouterLink, FormsModule],
|
|
templateUrl: './bots.component.html',
|
|
styleUrl: './bots.component.css',
|
|
})
|
|
export class BotsComponent implements OnInit {
|
|
private readonly destroyRef = inject(DestroyRef);
|
|
private readonly botService = inject(BotService);
|
|
|
|
bots: Bot[] = [];
|
|
loading = true;
|
|
|
|
showCreate = false;
|
|
newBotName = '';
|
|
creating = false;
|
|
createError: string | null = null;
|
|
|
|
revealedTokens: Record<string, string> = {};
|
|
revealingId: string | null = null;
|
|
copiedId: string | null = null;
|
|
deletingId: string | null = null;
|
|
|
|
ngOnInit(): void {
|
|
this.loadBots();
|
|
}
|
|
|
|
loadBots(): void {
|
|
this.loading = true;
|
|
this.botService
|
|
.list()
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe({
|
|
next: (bots) => {
|
|
this.bots = bots;
|
|
this.loading = false;
|
|
},
|
|
error: () => {
|
|
this.loading = false;
|
|
},
|
|
});
|
|
}
|
|
|
|
openCreate(): void {
|
|
this.newBotName = '';
|
|
this.createError = null;
|
|
this.showCreate = true;
|
|
}
|
|
|
|
cancelCreate(): void {
|
|
this.showCreate = false;
|
|
}
|
|
|
|
submitCreate(): void {
|
|
const name = this.newBotName.trim();
|
|
if (!name) return;
|
|
this.creating = true;
|
|
this.createError = null;
|
|
this.botService.create(name).subscribe({
|
|
next: (bot: BotWithToken) => {
|
|
this.creating = false;
|
|
this.showCreate = false;
|
|
this.bots = [bot, ...this.bots];
|
|
this.revealedTokens[bot.id] = bot.token;
|
|
},
|
|
error: (err) => {
|
|
this.creating = false;
|
|
this.createError = err.error?.message ?? err.error?.error ?? 'Failed to create bot.';
|
|
},
|
|
});
|
|
}
|
|
|
|
revealToken(botId: string): void {
|
|
if (this.revealedTokens[botId]) {
|
|
delete this.revealedTokens[botId];
|
|
return;
|
|
}
|
|
this.revealingId = botId;
|
|
this.botService.rotateToken(botId).subscribe({
|
|
next: (token) => {
|
|
this.revealingId = null;
|
|
this.revealedTokens[botId] = token;
|
|
},
|
|
error: () => {
|
|
this.revealingId = null;
|
|
},
|
|
});
|
|
}
|
|
|
|
copyToken(botId: string): void {
|
|
const token = this.revealedTokens[botId];
|
|
if (!token) return;
|
|
navigator.clipboard.writeText(token).then(() => {
|
|
this.copiedId = botId;
|
|
setTimeout(() => {
|
|
this.copiedId = null;
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
deleteBot(botId: string): void {
|
|
this.deletingId = botId;
|
|
this.botService.delete(botId).subscribe({
|
|
next: () => {
|
|
this.deletingId = null;
|
|
this.bots = this.bots.filter((b) => b.id !== botId);
|
|
delete this.revealedTokens[botId];
|
|
},
|
|
error: () => {
|
|
this.deletingId = null;
|
|
},
|
|
});
|
|
}
|
|
}
|