import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ChooseNextChallenge, ChooseNextOption } from '@ask-me-mcp/askme-shared';
/**
* Component for responding to choose-next decision requests
*
* @remarks
* This component displays decision options in an appealing, animated grid layout
* where users can click to select their preferred next step. Includes special
* action buttons for abort and requesting new ideas.
*
* @example
* ```html
* <app-choose-next-response
* [requestId]="request.id"
* [challengeInput]="chooseNextChallenge"
* (responseSubmitted)="handleResponse($event)">
* </app-choose-next-response>
* ```
*/
@Component({
selector: 'app-choose-next-response',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="choose-next-response">
<!-- Bail-out buttons at the top as big buttons -->
<div class="top-bail-out-buttons">
<button
type="button"
class="big-bail-out-btn"
(click)="bailOutToTool('ask-one-question')"
[disabled]="isSubmitting()"
title="Use open-ended question instead">
🤔 Use Open Question Instead
</button>
<button
type="button"
class="big-bail-out-btn"
(click)="bailOutToTool('ask-multiple-choice')"
[disabled]="isSubmitting()"
title="Use structured multiple choice instead">
📝 Use Multiple Choice Instead
</button>
<button
type="button"
class="big-bail-out-btn"
(click)="bailOutToTool('challenge-hypothesis')"
[disabled]="isSubmitting()"
title="Use hypothesis validation instead">
🔍 Use Hypothesis Challenge Instead
</button>
</div>
<!-- Challenge Title and Description -->
<div class="challenge-header">
<h2 class="challenge-title">{{ challenge().title }}</h2>
<div class="challenge-description" [innerHTML]="formattedDescription()"></div>
</div>
<!-- Options Grid -->
<div class="options-grid">
@for (option of challenge().options; track option.id) {
<div
class="option-card"
[class.disabled]="isSubmitting()"
[class.selected]="selectedOption()?.id === option.id"
(click)="selectOption(option)"
[attr.tabindex]="isSubmitting() ? -1 : 0"
(keydown.enter)="selectOption(option)"
(keydown.space)="selectOption(option)"
title="Click to choose: {{ option.title }}">
<div class="option-icon" *ngIf="option.icon">
{{ option.icon }}
</div>
<div class="option-content">
<h3 class="option-title">{{ option.title }}</h3>
<p class="option-description">{{ option.description }}</p>
</div>
<div class="click-indicator">
<span class="click-text">Click to Choose</span>
</div>
</div>
}
</div>
<!-- Secondary Action Buttons -->
<div class="secondary-actions">
<button
type="button"
class="action-btn abort"
(click)="submitAbort()"
[disabled]="isSubmitting()"
title="Don't proceed with any of these options">
🛑 Abort
</button>
<button
type="button"
class="action-btn new-ideas"
(click)="submitNewIdeas()"
[disabled]="isSubmitting()"
title="Request different or alternative options">
💡 New Ideas
</button>
</div>
</div>
`,
styles: [`
.choose-next-response {
display: flex;
flex-direction: column;
gap: 2rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(4px);
border: 1px solid rgba(64, 164, 223, 0.3);
}
/* Bail-out buttons */
.top-bail-out-buttons {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 1rem;
}
.big-bail-out-btn {
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
border: 2px solid #ffc107;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 0.9rem;
font-weight: 600;
color: #856404;
cursor: pointer;
min-width: 280px;
transition: all 0.3s ease;
text-align: center;
}
.big-bail-out-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
border-color: #e0a800;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(255, 193, 7, 0.3);
}
.big-bail-out-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
@media (max-width: 768px) {
.top-bail-out-buttons {
flex-direction: column;
align-items: center;
}
.big-bail-out-btn {
min-width: auto;
width: 100%;
max-width: 300px;
}
}
/* Challenge Header */
.challenge-header {
text-align: center;
margin-bottom: 1rem;
}
.challenge-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--text-primary, #333);
margin: 0 0 1rem 0;
}
.challenge-description {
font-size: 1rem;
color: var(--text-secondary, #666);
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
}
.challenge-description h1,
.challenge-description h2,
.challenge-description h3 {
color: var(--text-primary, #333);
margin: 1rem 0 0.5rem 0;
}
.challenge-description ul,
.challenge-description ol {
text-align: left;
display: inline-block;
}
.challenge-description code {
background: rgba(64, 164, 223, 0.1);
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
}
.challenge-description pre {
background: rgba(64, 164, 223, 0.1);
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
text-align: left;
}
/* Options Grid */
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.option-card {
background: rgba(255, 255, 255, 0.9);
border: 2px solid rgba(64, 164, 223, 0.2);
border-radius: 12px;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-height: 200px;
backdrop-filter: blur(2px);
}
.option-card:hover:not(.disabled) {
transform: translateY(-12px) scale(1.05);
box-shadow: 0 16px 32px rgba(64, 164, 223, 0.35);
border-color: #40a4df;
background: linear-gradient(135deg, rgba(64, 164, 223, 0.15) 0%, rgba(64, 164, 223, 0.05) 100%);
}
.option-card:hover:not(.disabled) .click-indicator {
opacity: 1;
transform: translateY(0);
}
.option-card.disabled {
opacity: 0.6;
cursor: not-allowed;
}
.option-card.selected {
border-color: #28a745;
background: linear-gradient(135deg, rgba(40, 167, 69, 0.15) 0%, rgba(40, 167, 69, 0.05) 100%);
box-shadow: 0 8px 16px rgba(40, 167, 69, 0.3);
}
.option-card.selected .click-indicator {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.option-card.selected .click-text::before {
content: "✓ ";
font-weight: bold;
}
.click-indicator {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%) translateY(10px);
opacity: 0;
transition: all 0.3s ease;
background: linear-gradient(135deg, #40a4df 0%, #2980b9 100%);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
white-space: nowrap;
box-shadow: 0 4px 12px rgba(64, 164, 223, 0.4);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.click-text {
display: flex;
align-items: center;
gap: 0.5rem;
}
.click-text::after {
content: '→';
font-weight: bold;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 50%, 100% { opacity: 1; transform: translateX(0); }
25% { opacity: 0.7; transform: translateX(2px); }
}
.option-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
line-height: 1;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
60% {
transform: translateY(-3px);
}
}
.option-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.option-title {
font-size: 1.3rem;
font-weight: 600;
color: var(--text-primary, #333);
margin: 0 0 0.75rem 0;
}
.option-description {
font-size: 0.95rem;
color: var(--text-secondary, #666);
line-height: 1.5;
margin: 0;
}
.secondary-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.action-btn {
background: rgba(255, 255, 255, 0.9);
border: 2px solid;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
min-width: 140px;
}
.action-btn.abort {
border-color: #dc3545;
color: #dc3545;
}
.action-btn.abort:hover:not(:disabled) {
background: #dc3545;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
.action-btn.new-ideas {
border-color: #6c757d;
color: #6c757d;
}
.action-btn.new-ideas:hover:not(:disabled) {
background: #6c757d;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3);
}
.action-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Responsive Design */
@media (max-width: 768px) {
.choose-next-response {
padding: 0.75rem;
gap: 1.5rem;
}
.challenge-title {
font-size: 1.5rem;
}
.options-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.option-card {
min-height: 180px;
padding: 1.25rem;
}
.option-title {
font-size: 1.2rem;
}
.secondary-actions {
flex-direction: column;
width: 100%;
}
.action-btn {
width: 100%;
max-width: 250px;
}
}
/* Focus styles for accessibility */
.option-card:focus {
outline: 3px solid rgba(64, 164, 223, 0.5);
outline-offset: 2px;
}
.action-btn:focus,
.big-bail-out-btn:focus {
outline: 3px solid rgba(64, 164, 223, 0.5);
outline-offset: 2px;
}
`]
})
export class ChooseNextResponseComponent {
@Input() requestId = '';
@Input() challengeInput: ChooseNextChallenge | null = null;
@Output() responseSubmitted = new EventEmitter<{
requestId: string;
type: 'choose-next';
action: 'selected' | 'abort' | 'new-ideas';
selectedOption?: ChooseNextOption;
message?: string;
}>();
@Output() toolRedirect = new EventEmitter<{
requestId: string;
toolName: string;
message: string;
}>();
// Reactive state
private _additionalMessage = signal<string>('');
private _isSubmitting = signal<boolean>(false);
private _selectedOption = signal<ChooseNextOption | null>(null);
// Computed signals
isSubmitting = this._isSubmitting.asReadonly();
selectedOption = this._selectedOption.asReadonly();
challenge = computed(() => this.challengeInput || {
id: '',
title: '',
description: '',
options: []
});
formattedDescription = computed(() => {
const description = this.challenge().description;
if (!description) return '';
// Simple markdown-to-HTML conversion for basic formatting
return description
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>');
});
selectOption(option: ChooseNextOption): void {
if (this.isSubmitting()) return;
console.log('ChooseNext: Option selected:', option);
this._selectedOption.set(option);
// Give a moment to show selection state before submitting
setTimeout(() => {
this._isSubmitting.set(true);
const response = {
requestId: this.requestId,
type: 'choose-next' as const,
action: 'selected' as const,
selectedOption: option
};
this.responseSubmitted.emit(response);
}, 500); // 500ms delay to show selection
}
submitAbort(): void {
if (this.isSubmitting()) return;
console.log('ChooseNext: Submitting abort');
this._isSubmitting.set(true);
const response = {
requestId: this.requestId,
type: 'choose-next' as const,
action: 'abort' as const,
message: this._additionalMessage().trim() || undefined
};
this.responseSubmitted.emit(response);
}
submitNewIdeas(): void {
if (this.isSubmitting()) return;
console.log('ChooseNext: Submitting new ideas request');
this._isSubmitting.set(true);
const response = {
requestId: this.requestId,
type: 'choose-next' as const,
action: 'new-ideas' as const,
message: this._additionalMessage().trim() || undefined
};
this.responseSubmitted.emit(response);
}
bailOutToTool(toolName: string): void {
if (this.isSubmitting()) return;
console.log('ChooseNext: Bailing out to tool:', toolName);
let message = '';
const challenge = this.challenge();
switch (toolName) {
case 'ask-one-question':
message = `STOP! User REFUSES the choose-next format and DEMANDS an open-ended question instead. IMMEDIATELY reformulate this decision point "${challenge.title}" as a single open question using ask-one-question tool. Do NOT continue with the current approach.`;
break;
case 'ask-multiple-choice':
message = `STOP! User REFUSES the choose-next format and DEMANDS structured multiple choice questions instead. IMMEDIATELY reformulate this decision point "${challenge.title}" as multiple choice questions using ask-multiple-choice tool. Do NOT continue with the current approach.`;
break;
case 'challenge-hypothesis':
message = `STOP! User REFUSES the choose-next format and DEMANDS hypothesis validation instead. IMMEDIATELY reformulate this decision point "${challenge.title}" as hypothesis statements to validate using challenge-hypothesis tool. Do NOT continue with the current approach.`;
break;
}
this.toolRedirect.emit({
requestId: this.requestId,
toolName,
message
});
}
// Getter for ngModel binding
get additionalMessage(): string {
return this._additionalMessage();
}
// Setter for ngModel binding
set additionalMessage(value: string) {
this._additionalMessage.set(value);
}
}