Skip to content

Commit fa43671

Browse files
committed
feat(cockpit): add planning example with generative UI views
1 parent c0661bc commit fa43671

5 files changed

Lines changed: 57 additions & 74 deletions

File tree

Lines changed: 12 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,27 @@
1-
import { Component, computed } from '@angular/core';
2-
import { ChatComponent } from '@cacheplane/chat';
1+
import { Component } from '@angular/core';
2+
import { ChatComponent, views } from '@cacheplane/chat';
3+
import { signalStateStore } from '@cacheplane/render';
34
import { agent } from '@cacheplane/angular';
45
import { environment } from '../environments/environment';
6+
import { PlanChecklistComponent } from './views/plan-checklist.component';
7+
import { CheckboxRowComponent } from './views/checkbox-row.component';
58

6-
/**
7-
* Represents a single step in an agent-generated plan.
8-
*/
9-
interface PlanStep {
10-
title: string;
11-
status: 'pending' | 'in_progress' | 'complete';
12-
}
13-
14-
/**
15-
* PlanningComponent demonstrates agent task decomposition.
16-
*
17-
* The agent receives a complex task, breaks it into ordered steps,
18-
* and executes them sequentially. The sidebar displays a live checklist
19-
* of plan steps derived from the `plan` array in the graph state.
20-
*
21-
* Key integration points:
22-
* - `stream.value()` exposes the full graph state, including the `plan` array
23-
* - `planSteps` is derived from `stream.value()` for reactive sidebar rendering
24-
* - Step status icons update in real time as the agent progresses
25-
*/
269
@Component({
2710
selector: 'app-planning',
2811
standalone: true,
2912
imports: [ChatComponent],
30-
template: `
31-
<div class="flex h-screen">
32-
<chat [ref]="stream" class="flex-1 min-w-0" />
33-
<aside class="w-72 shrink-0 border-l overflow-y-auto p-4 space-y-2"
34-
style="border-color: var(--chat-border, #333); background: var(--chat-bg, #171717); color: var(--chat-text, #e0e0e0);">
35-
<h3 class="text-xs font-semibold uppercase tracking-wide"
36-
style="color: var(--chat-text-muted, #777);">Plan</h3>
37-
@if (planSteps().length === 0) {
38-
<p class="text-sm italic" style="color: var(--chat-text-muted, #777);">No plan yet</p>
39-
}
40-
@for (step of planSteps(); track step.title) {
41-
<div class="flex items-start gap-2 rounded-md px-2 py-1.5 text-sm"
42-
style="background: var(--chat-bg-hover, #222);">
43-
<span class="mt-0.5 shrink-0 text-base leading-none">
44-
@if (step.status === 'complete') {
45-
<span style="color: #22c55e;">&#10003;</span>
46-
} @else if (step.status === 'in_progress') {
47-
<span class="inline-block animate-spin" style="color: var(--chat-text-muted, #777);">&#9696;</span>
48-
} @else {
49-
<span style="color: var(--chat-border-light, #444);">&#9675;</span>
50-
}
51-
</span>
52-
<span style="color: var(--chat-text, #e0e0e0);">{{ step.title }}</span>
53-
</div>
54-
}
55-
</aside>
56-
</div>
57-
`,
13+
template: `<chat [ref]="stream" [views]="ui" [store]="uiStore" class="block h-screen" />`,
5814
})
5915
export class PlanningComponent {
60-
/**
61-
* The streaming resource connected to the planning graph.
62-
*
63-
* The graph returns a `plan` array alongside messages in its state.
64-
* Each plan entry has a `title` and `status` that drive the sidebar checklist.
65-
*/
6616
protected readonly stream = agent({
6717
apiUrl: environment.langGraphApiUrl,
68-
assistantId: environment.streamingAssistantId,
18+
assistantId: environment.planningAssistantId,
6919
});
7020

71-
/**
72-
* Reactive list of plan steps derived from the graph state.
73-
*
74-
* The Python graph stores the plan as `state.plan` — an array of objects
75-
* with `title` and `status` fields. This signal re-computes whenever
76-
* the stream state changes.
77-
*/
78-
protected readonly planSteps = computed<PlanStep[]>(() => {
79-
const val = this.stream.value() as Record<string, unknown>;
80-
const plan = val?.['plan'];
81-
if (!Array.isArray(plan)) return [];
82-
return plan.map((step: Record<string, unknown>) => ({
83-
title: String(step['title'] ?? ''),
84-
status: (['pending', 'in_progress', 'complete'].includes(step['status'] as string)
85-
? step['status']
86-
: 'pending') as PlanStep['status'],
87-
}));
21+
readonly ui = views({
22+
'plan-checklist': PlanChecklistComponent,
23+
'checkbox-row': CheckboxRowComponent,
8824
});
25+
26+
readonly uiStore = signalStateStore({});
8927
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, input } from '@angular/core';
2+
3+
@Component({
4+
selector: 'checkbox-row',
5+
standalone: true,
6+
template: `
7+
<label class="flex items-center gap-2 py-1 cursor-pointer text-sm" style="color: var(--chat-text);">
8+
<input
9+
type="checkbox"
10+
[checked]="checked()"
11+
(change)="toggle()"
12+
class="w-4 h-4"
13+
/>
14+
<span [class.line-through]="checked()" [style.opacity]="checked() ? '0.5' : '1'">
15+
{{ label() }}
16+
</span>
17+
</label>
18+
`,
19+
})
20+
export class CheckboxRowComponent {
21+
readonly label = input<string>('');
22+
readonly checked = input<boolean>(false);
23+
readonly emit = input<(event: string) => void>(() => {});
24+
25+
toggle(): void {
26+
this.emit()('toggle');
27+
}
28+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, input } from '@angular/core';
2+
3+
@Component({
4+
selector: 'plan-checklist',
5+
standalone: true,
6+
template: `
7+
<div class="border rounded-xl p-4 my-2" style="border-color: var(--chat-border); background: var(--chat-bg-alt);">
8+
<h4 class="text-sm font-semibold mb-2" style="color: var(--chat-text);">{{ title() }}</h4>
9+
<ng-content />
10+
</div>
11+
`,
12+
})
13+
export class PlanChecklistComponent {
14+
readonly title = input<string>('Plan');
15+
}

cockpit/deep-agents/planning/angular/src/environments/environment.development.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export const environment = {
88
production: false,
99
langGraphApiUrl: 'http://localhost:4310/api',
1010
streamingAssistantId: 'planning',
11+
planningAssistantId: 'planning',
1112
};

cockpit/deep-agents/planning/angular/src/environments/environment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export const environment = {
88
production: true,
99
langGraphApiUrl: '/api',
1010
streamingAssistantId: 'planning',
11+
planningAssistantId: 'planning',
1112
};

0 commit comments

Comments
 (0)