Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { WelcomeSuggestionsComponent } from './welcome-suggestions.component';
[selectedModel]="shell.model()"
(selectedModelChange)="shell.onModelChange($event)"
>
<welcome-suggestions chatWelcomeSuggestions (selected)="send($event)" />
<welcome-suggestions chatWelcomeSuggestions [appModeOn]="shell.appMode() === 'on'" (selected)="send($event)" />
</chat>
`,
styles: [`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { AppModePromoComponent } from './app-mode-promo.component';
[showModelPicker]="false"
(selectedModelChange)="shell.onModelChange($event)"
>
<welcome-suggestions chatWelcomeSuggestions (selected)="send($event)" />
<welcome-suggestions chatWelcomeSuggestions [appModeOn]="shell.appMode() === 'on'" (selected)="send($event)" />
</chat-popup>
`,
styles: [`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { WelcomeSuggestionsComponent } from './welcome-suggestions.component';
/>
}
</div>
<welcome-suggestions chatWelcomeSuggestions (selected)="send($event)" />
<welcome-suggestions chatWelcomeSuggestions [appModeOn]="shell.appMode() === 'on'" (selected)="send($event)" />
</chat-sidebar>
`,
styles: [`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
import { ChangeDetectionStrategy, Component, output } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import {
ChatWelcomeSuggestionComponent,
ChatSelectComponent,
type ChatSelectOption,
} from '@threadplane/chat';
import { FEATURED_SUGGESTIONS, MORE_SUGGESTIONS } from './welcome-suggestions';
import { suggestionsForAppMode } from './welcome-suggestions';

/**
* Demo-side composition that renders the welcome-state suggestion surface
Expand All @@ -26,13 +26,13 @@ import { FEATURED_SUGGESTIONS, MORE_SUGGESTIONS } from './welcome-suggestions';
<div class="welcome-suggestions__row">
<chat-welcome-suggestion
class="welcome-suggestions__featured"
[label]="featuredOne.label"
[value]="featuredOne.value"
[description]="featuredOne.description"
[label]="featuredOne().label"
[value]="featuredOne().value"
[description]="featuredOne().description"
(selected)="selected.emit($event)"
/>
<chat-select
[options]="moreOptions"
[options]="moreOptions()"
placeholder="More prompts"
menuLabel="More demo prompts"
panelClass="welcome-suggestions__menu-panel"
Expand Down Expand Up @@ -115,9 +115,16 @@ import { FEATURED_SUGGESTIONS, MORE_SUGGESTIONS } from './welcome-suggestions';
})
export class WelcomeSuggestionsComponent {
readonly selected = output<string>();
protected readonly featuredOne = FEATURED_SUGGESTIONS[0];
protected readonly moreOptions: readonly ChatSelectOption[] = [
...FEATURED_SUGGESTIONS.slice(1),
...MORE_SUGGESTIONS,
].map((s) => ({ value: s.value, label: s.label, description: s.description }));
/**
* When true, lead with the itinerary prompts (App mode / map cockpit on
* screen). When false, lead with the broad capability prompts (plain chat).
* Passed by each mode from `shell.appMode()`.
*/
readonly appModeOn = input<boolean>(false);

private readonly suggestions = computed(() => suggestionsForAppMode(this.appModeOn()));
protected readonly featuredOne = computed(() => this.suggestions().featured);
protected readonly moreOptions = computed<readonly ChatSelectOption[]>(() =>
this.suggestions().more.map((s) => ({ value: s.value, label: s.label, description: s.description })),
);
}
66 changes: 54 additions & 12 deletions examples/ag-ui/angular/src/app/modes/welcome-suggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,102 @@
* frontend-owned itinerary client tools, and the research subagent
* (list introduced in #655; presented here via the canonical two-tier
* featured + "More prompts" UI).
*
* Each prompt is tagged `kind` so the surface can lead with the prompts
* that match the current context: in App mode (the map cockpit is on
* screen) the itinerary prompts are featured + ordered first; in plain
* chat the broad capability prompts lead. See `suggestionsForAppMode`.
*/
export interface WelcomeSuggestion {
readonly label: string;
readonly value: string;
readonly description: string;
/** `itinerary` prompts drive the map cockpit; `capability` prompts show
* the transport's general features. Used to order by App mode. */
readonly kind: 'capability' | 'itinerary';
}

export const FEATURED_SUGGESTIONS: readonly WelcomeSuggestion[] = [
/** Broad transport-capability prompts (relevant in any mode). */
const CAPABILITY_SUGGESTIONS: readonly WelcomeSuggestion[] = [
{
kind: 'capability',
label: 'Search docs and cite sources',
value: 'What do the docs say about streaming?',
description: 'Calls a search tool and weaves inline citations into the reply.',
},
{
kind: 'capability',
label: 'Generative UI: revenue dashboard',
value: 'Build me a revenue dashboard',
description: 'Streams a composed UI surface the model builds on the fly.',
},
{
kind: 'capability',
label: 'Approve before a refund',
value: 'Issue me a $50 refund',
description: 'Pauses mid-run and asks for your explicit approval before proceeding.',
},
{
kind: 'capability',
label: 'Delegate to a research subagent',
value: 'Research AG-UI and give me the highlights',
description: 'Parent spawns a subagent and streams its summary back to you.',
},
];

export const MORE_SUGGESTIONS: readonly WelcomeSuggestion[] = [
/** Prompts that drive the itinerary panel / map cockpit (App mode). */
const ITINERARY_SUGGESTIONS: readonly WelcomeSuggestion[] = [
{
kind: 'itinerary',
label: 'Read my itinerary',
value: "What's on my itinerary?",
description: 'Agent calls a browser-side tool to read your trip data.',
},
{
kind: 'itinerary',
label: 'Agent adds a stop to day 2',
value: 'Add the Louvre to day 2 of my trip',
description: 'Agent mutates the itinerary panel directly via a client tool.',
},
{
kind: 'itinerary',
label: 'Clear a day (asks first)',
value: 'Clear my day 2 plans',
description: 'Client tool requires your confirmation before clearing the day.',
},
{
label: 'Delegate to a research subagent',
value: 'Research AG-UI and give me the highlights',
description: 'Parent spawns a subagent and streams its summary back to you.',
},
];

/**
* Back-compat: unified array combining featured + more in the original
* order. Kept so existing imports don't break. Prefer FEATURED_SUGGESTIONS
* + MORE_SUGGESTIONS for the two-tier UI.
* Pick the featured prompt + "More prompts" order for the current context.
*
* - App mode ON (map cockpit visible): feature "What's on my itinerary?" and
* list the itinerary prompts first — they match what the user is looking at.
* - App mode OFF (plain chat): feature "Search docs…" and lead with the broad
* capability prompts; itinerary prompts trail (they still work, just less
* prominent without the map).
*/
export function suggestionsForAppMode(appModeOn: boolean): {
readonly featured: WelcomeSuggestion;
readonly more: readonly WelcomeSuggestion[];
} {
if (appModeOn) {
const [featured, ...restItinerary] = ITINERARY_SUGGESTIONS;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ITINERARY_SUGGESTIONS is ever empty, featured will be undefined and the template will throw. The arrays are module-level constants so this can't happen today, but an explicit guard or a compile-time assertion (ITINERARY_SUGGESTIONS.length > 0 as true) would make the invariant explicit.

This is a very low-risk theoretical concern given the fixed constants — mentioning it only because the pattern is easy to break later if someone removes a suggestion.

return { featured, more: [...restItinerary, ...CAPABILITY_SUGGESTIONS] };
}
const [featured, ...restCapability] = CAPABILITY_SUGGESTIONS;
return { featured, more: [...restCapability, ...ITINERARY_SUGGESTIONS] };
}

/**
* Back-compat: the original two-tier split + unified array, in the plain-chat
* order. Prefer `suggestionsForAppMode` for context-aware ordering.
*/
export const FEATURED_SUGGESTIONS: readonly WelcomeSuggestion[] = CAPABILITY_SUGGESTIONS.slice(0, 3);
export const MORE_SUGGESTIONS: readonly WelcomeSuggestion[] = [
...ITINERARY_SUGGESTIONS,
...CAPABILITY_SUGGESTIONS.slice(3),
];
export const WELCOME_SUGGESTIONS: readonly WelcomeSuggestion[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WELCOME_SUGGESTIONS silently changed order: previously [...FEATURED_SUGGESTIONS, ...MORE_SUGGESTIONS] put "Delegate to a research subagent" last; now [...CAPABILITY_SUGGESTIONS, ...ITINERARY_SUGGESTIONS] promotes it to 4th (after the other capability prompts, before the itinerary ones). The comment says there are no in-tree consumers today, so this is low-risk — just worth noting if anyone has an external dependency on this export's ordering.

...FEATURED_SUGGESTIONS,
...MORE_SUGGESTIONS,
...CAPABILITY_SUGGESTIONS,
...ITINERARY_SUGGESTIONS,
];
Loading