Skip to content

Commit 8072283

Browse files
bloveclaude
andauthored
feat(gtm): Spec 4 — cockpit activation recipes (try-this-prompt suggestions) (#388)
* docs(gtm): spec for cockpit-activation-recipes (Spec 4) Pre-baked "Try this prompt" suggestions on the four capability examples that map to activation signals (streaming, persistence, interrupts, generative-ui), plus a ChatComponent enhancement so firstMessageSent flips on the first stream-start regardless of submit path (input, suggestion-click, programmatic). A developer landing in the cockpit gets a 1-click path to firing each activation signal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(gtm): implementation plan for cockpit-activation-recipes (Spec 4) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(chat): flip firstMessageSent on agent stream-start (any submit path) ChatComponent now subscribes to agent.lifecycle.streamStartedAt and flips CHAT_LIFECYCLE.firstMessageSent on its first transition to a non-null value. This makes the lifecycle robust to programmatic agent.submit() calls, including the <chat-welcome-suggestion> click handler pattern in cockpit examples. messageCount and inputSubmittedAt remain input-bound by design — they measure typing engagement, not stream initiation. Two new lifecycle tests cover the agent-driven flip + stickiness across multiple stream-starts. The chat mock-agent gained a minimal lifecycle stub (streamStartedAt only, with a writable _internal handle) so the tests can drive the signal directly without a full adapter. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(cockpit-streaming): pre-baked welcome suggestions Two <chat-welcome-suggestion> rows in the empty-state — "Stream a long answer" and "Walk me through agent tool calls". Clicking either calls agent.submit({ message: ... }) which fires AGENT_LIFECYCLE.streamStartedAt on first chunk arrival → cockpit:transport_connected. ChatComponent's new effect (Phase 0) then flips firstMessageSent → cockpit:chat_first_message. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(cockpit-persistence): pre-baked welcome suggestion One <chat-welcome-suggestion> row — "Save this thread for later" — that prompts a project-brief conversation. After the user reloads the page, AGENT_LIFECYCLE.threadPersistedAt fires → cockpit:thread_persisted activation signal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(cockpit-interrupts): pre-baked welcome suggestion One <chat-welcome-suggestion> row — "Approve a tool call" — that prompts a flight-booking conversation. The graph pauses at an interrupt; when the user approves via the ChatInterruptPanelComponent, AGENT_LIFECYCLE.interruptResolvedAt fires → cockpit:interrupt_handled activation signal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(cockpit-generative-ui): pre-baked welcome suggestions Two <chat-welcome-suggestion> rows — "Render a dashboard" and "Render a form". Clicking either prompts the agent to emit a generative-UI payload that RenderSpecComponent mounts, firing RENDER_LIFECYCLE.firstMountAt → cockpit:generative_component_rendered activation signal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 4ed6453 commit 8072283

9 files changed

Lines changed: 1092 additions & 13 deletions

File tree

cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT
22
import { Component } from '@angular/core';
3-
import { ChatComponent, views } from '@ngaf/chat';
3+
import { ChatComponent, ChatWelcomeSuggestionComponent, views } from '@ngaf/chat';
44
import { agent } from '@ngaf/langgraph';
55
import { ExampleChatLayoutComponent } from '@ngaf/example-layouts';
66
import { environment } from '../environments/environment';
@@ -21,13 +21,28 @@ const dashboardViews = views({
2121
data_grid: DataGridComponent,
2222
});
2323

24+
const WELCOME_SUGGESTIONS = [
25+
{ label: 'Render a dashboard', value: 'Show me a Q3 sales dashboard with three metrics.' },
26+
{ label: 'Render a form', value: 'Create a contact form with name, email, and message.' },
27+
] as const;
28+
2429
@Component({
2530
selector: 'app-generative-ui',
2631
standalone: true,
27-
imports: [ChatComponent, ExampleChatLayoutComponent],
32+
imports: [ChatComponent, ChatWelcomeSuggestionComponent, ExampleChatLayoutComponent],
2833
template: `
2934
<example-chat-layout>
30-
<chat main [agent]="agent" [views]="dashboardViews" class="flex-1 min-w-0" />
35+
<chat main [agent]="agent" [views]="dashboardViews" class="flex-1 min-w-0">
36+
<div chatWelcomeSuggestions>
37+
@for (s of suggestions; track s.value) {
38+
<chat-welcome-suggestion
39+
[label]="s.label"
40+
[value]="s.value"
41+
(selected)="send($event)"
42+
/>
43+
}
44+
</div>
45+
</chat>
3146
</example-chat-layout>
3247
`,
3348
})
@@ -37,4 +52,9 @@ export class GenerativeUiComponent {
3752
assistantId: environment.generativeUiAssistantId,
3853
});
3954
protected readonly dashboardViews = dashboardViews;
55+
protected readonly suggestions = WELCOME_SUGGESTIONS;
56+
57+
protected send(text: string): void {
58+
void this.agent.submit({ message: text });
59+
}
4060
}

cockpit/langgraph/interrupts/angular/src/app/interrupts.component.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
// SPDX-License-Identifier: MIT
22
import { Component } from '@angular/core';
3-
import { ChatComponent, ChatInterruptPanelComponent, views, type InterruptAction } from '@ngaf/chat';
3+
import { ChatComponent, ChatInterruptPanelComponent, ChatWelcomeSuggestionComponent, views, type InterruptAction } from '@ngaf/chat';
44
import { agent } from '@ngaf/langgraph';
55
import { ExampleChatLayoutComponent } from '@ngaf/example-layouts';
66
import { signalStateStore } from '@ngaf/render';
77
import { environment } from '../environments/environment';
88
import { ApprovalCardComponent } from './views/approval-card.component';
99

10+
const WELCOME_SUGGESTIONS = [
11+
{ label: 'Approve a tool call', value: 'Book a flight to Paris for next Tuesday.' },
12+
] as const;
13+
1014
/**
1115
* InterruptsComponent demonstrates human-in-the-loop with `agent()`.
1216
*
@@ -22,11 +26,21 @@ import { ApprovalCardComponent } from './views/approval-card.component';
2226
@Component({
2327
selector: 'app-interrupts',
2428
standalone: true,
25-
imports: [ChatComponent, ChatInterruptPanelComponent, ExampleChatLayoutComponent],
29+
imports: [ChatComponent, ChatInterruptPanelComponent, ChatWelcomeSuggestionComponent, ExampleChatLayoutComponent],
2630
template: `
2731
<example-chat-layout>
2832
<div main class="flex flex-col h-full">
29-
<chat [agent]="agent" [views]="ui" [store]="uiStore" class="flex-1 min-w-0" />
33+
<chat [agent]="agent" [views]="ui" [store]="uiStore" class="flex-1 min-w-0">
34+
<div chatWelcomeSuggestions>
35+
@for (s of suggestions; track s.value) {
36+
<chat-welcome-suggestion
37+
[label]="s.label"
38+
[value]="s.value"
39+
(selected)="send($event)"
40+
/>
41+
}
42+
</div>
43+
</chat>
3044
@if (agent.interrupt()) {
3145
<div class="p-4" style="border-top: 1px solid var(--ngaf-chat-separator);">
3246
<chat-interrupt-panel [agent]="agent" (action)="onInterruptAction($event)" />
@@ -39,6 +53,11 @@ import { ApprovalCardComponent } from './views/approval-card.component';
3953
export class InterruptsComponent {
4054
readonly ui = views({ 'approval-card': ApprovalCardComponent });
4155
readonly uiStore = signalStateStore({});
56+
protected readonly suggestions = WELCOME_SUGGESTIONS;
57+
58+
protected send(text: string): void {
59+
void this.agent.submit({ message: text });
60+
}
4261

4362
/**
4463
* The streaming resource with interrupt support.

cockpit/langgraph/persistence/angular/src/app/persistence.component.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
// SPDX-License-Identifier: MIT
12
import { Component, signal } from '@angular/core';
2-
import { ChatComponent } from '@ngaf/chat';
3+
import { ChatComponent, ChatWelcomeSuggestionComponent } from '@ngaf/chat';
34
import { agent } from '@ngaf/langgraph';
45
import { ExampleChatLayoutComponent } from '@ngaf/example-layouts';
56
import { environment } from '../environments/environment';
67

8+
const WELCOME_SUGGESTIONS = [
9+
{ label: 'Save this thread for later', value: 'Help me draft a project brief I can revisit.' },
10+
] as const;
11+
712
interface Thread {
813
id: string;
914
label: string;
@@ -23,10 +28,20 @@ interface Thread {
2328
@Component({
2429
selector: 'app-persistence',
2530
standalone: true,
26-
imports: [ChatComponent, ExampleChatLayoutComponent],
31+
imports: [ChatComponent, ChatWelcomeSuggestionComponent, ExampleChatLayoutComponent],
2732
template: `
2833
<example-chat-layout sidebarWidth="w-56">
29-
<chat main [agent]="agent" class="block flex-1 min-w-0" />
34+
<chat main [agent]="agent" class="block flex-1 min-w-0">
35+
<div chatWelcomeSuggestions>
36+
@for (s of suggestions; track s.value) {
37+
<chat-welcome-suggestion
38+
[label]="s.label"
39+
[value]="s.value"
40+
(selected)="send($event)"
41+
/>
42+
}
43+
</div>
44+
</chat>
3045
3146
<div sidebar
3247
class="flex flex-col"
@@ -72,9 +87,14 @@ interface Thread {
7287
export class PersistenceComponent {
7388
protected readonly threads = signal<Thread[]>([]);
7489
protected readonly activeThreadId = signal<string | null>(null);
90+
protected readonly suggestions = WELCOME_SUGGESTIONS;
7591

7692
private threadCounter = 0;
7793

94+
protected send(text: string): void {
95+
void this.agent.submit({ message: text });
96+
}
97+
7898
/**
7999
* The streaming resource with thread persistence.
80100
*

cockpit/langgraph/streaming/angular/src/app/streaming.component.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
// SPDX-License-Identifier: MIT
22
import { Component } from '@angular/core';
3-
import { ChatComponent } from '@ngaf/chat';
3+
import { ChatComponent, ChatWelcomeSuggestionComponent } from '@ngaf/chat';
44
import { agent } from '@ngaf/langgraph';
55
import { ExampleChatLayoutComponent } from '@ngaf/example-layouts';
66
import { environment } from '../environments/environment';
77

8+
const WELCOME_SUGGESTIONS = [
9+
{ label: 'Stream a long answer', value: 'Explain LangGraph checkpointing in 200 words.' },
10+
{ label: 'Walk me through agent tool calls', value: 'Show me how an agent decides which tool to use.' },
11+
] as const;
12+
813
/**
914
* Streaming demo — simplest possible @ngaf/chat integration.
1015
*
@@ -15,10 +20,20 @@ import { environment } from '../environments/environment';
1520
@Component({
1621
selector: 'app-streaming',
1722
standalone: true,
18-
imports: [ChatComponent, ExampleChatLayoutComponent],
23+
imports: [ChatComponent, ChatWelcomeSuggestionComponent, ExampleChatLayoutComponent],
1924
template: `
2025
<example-chat-layout>
21-
<chat main [agent]="agent" class="flex-1 min-w-0" />
26+
<chat main [agent]="agent" class="flex-1 min-w-0">
27+
<div chatWelcomeSuggestions>
28+
@for (s of suggestions; track s.value) {
29+
<chat-welcome-suggestion
30+
[label]="s.label"
31+
[value]="s.value"
32+
(selected)="send($event)"
33+
/>
34+
}
35+
</div>
36+
</chat>
2237
</example-chat-layout>
2338
`,
2439
})
@@ -27,4 +42,9 @@ export class StreamingComponent {
2742
apiUrl: environment.langGraphApiUrl,
2843
assistantId: environment.streamingAssistantId,
2944
});
45+
protected readonly suggestions = WELCOME_SUGGESTIONS;
46+
47+
protected send(text: string): void {
48+
void this.agent.submit({ message: text });
49+
}
3050
}

0 commit comments

Comments
 (0)