Skip to content
Merged
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
@@ -1,22 +1,100 @@
import { Component } from '@angular/core';
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
import { streamResource } from '@cacheplane/stream-resource';
import { environment } from '../environments/environment';

/**
* Represents a file operation extracted from agent tool calls.
*/
interface FileOperation {
type: 'read' | 'write';
path: string;
preview: string;
}

/**
* FilesystemComponent demonstrates agent file operations.
*
* The agent can read and write files using tool calls.
* The agent can read and write files using tool calls. The sidebar displays
* a live log of file operations derived from `stream.messages()`, filtering
* for `read_file` and `write_file` tool calls.
*
* Key integration points:
* - `stream.messages()` exposes the full message history including tool calls
* - `fileOps` filters messages for file-related tool calls and extracts metadata
* - Operations appear in the sidebar in real time as the agent works
*/
@Component({
selector: 'app-filesystem',
standalone: true,
imports: [ChatComponent],
template: `<chat [ref]="stream" class="block h-screen" />`,
template: `
<div class="flex h-screen">
<chat [ref]="stream" class="flex-1 min-w-0" />
<aside class="w-72 shrink-0 border-l overflow-y-auto p-4 space-y-2"
style="border-color: var(--chat-border, #333); background: var(--chat-bg, #171717); color: var(--chat-text, #e0e0e0);">
<h3 class="text-xs font-semibold uppercase tracking-wide"
style="color: var(--chat-text-muted, #777);">File Operations</h3>
@if (fileOps().length === 0) {
<p class="text-sm italic" style="color: var(--chat-text-muted, #777);">No file operations yet</p>
}
@for (op of fileOps(); track $index) {
<div class="rounded-md px-2 py-1.5 text-sm space-y-1"
style="background: var(--chat-bg-hover, #222);">
<div class="flex items-center gap-2">
<span class="inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium"
[style.background]="op.type === 'read' ? 'rgba(59,130,246,0.15)' : 'rgba(234,179,8,0.15)'"
[style.color]="op.type === 'read' ? '#60a5fa' : '#facc15'">
{{ op.type === 'read' ? 'READ' : 'WRITE' }}
</span>
<span class="truncate text-xs" style="color: var(--chat-text, #e0e0e0);">{{ op.path }}</span>
</div>
@if (op.preview) {
<p class="truncate text-xs" style="color: var(--chat-text-muted, #777);">{{ op.preview }}</p>
}
</div>
}
</aside>
</div>
`,
})
export class FilesystemComponent {
/**
* The streaming resource connected to the filesystem graph.
*
* The graph uses `read_file` and `write_file` tool calls that appear
* in `stream.messages()`. We filter and display them in the sidebar.
*/
protected readonly stream = streamResource({
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});

/**
* Reactive list of file operations derived from the message history.
*
* Scans all messages for tool calls named `read_file` or `write_file`,
* extracts the file path and a short result preview for sidebar display.
*/
protected readonly fileOps = computed<FileOperation[]>(() => {
const msgs = this.stream.messages();
const ops: FileOperation[] = [];
for (const msg of msgs) {
const m = msg as unknown as Record<string, unknown>;
if (!('tool_calls' in m) || !Array.isArray(m['tool_calls'])) continue;
for (const tc of m['tool_calls'] as Array<Record<string, unknown>>) {
const name = tc['name'] as string | undefined;
if (name !== 'read_file' && name !== 'write_file') continue;
const args = (tc['args'] ?? {}) as Record<string, unknown>;
const path = String(args['path'] ?? args['file_path'] ?? '');
const content = String(args['content'] ?? args['result'] ?? '');
ops.push({
type: name === 'read_file' ? 'read' : 'write',
path,
preview: content.slice(0, 80),
});
}
}
return ops;
});
}
54 changes: 51 additions & 3 deletions cockpit/deep-agents/memory/angular/src/app/memory.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
import { streamResource } from '@cacheplane/stream-resource';
import { environment } from '../environments/environment';
Expand All @@ -7,17 +7,65 @@ import { environment } from '../environments/environment';
* MemoryComponent demonstrates persistent agent memory across sessions.
*
* The agent extracts facts about the user from each conversation turn
* and stores them in `agent_memory` state.
* and stores them in `agent_memory` (or `memory`) state. The sidebar
* displays all learned facts as key-value pairs with a live count.
*
* Key integration points:
* - `stream.value()` exposes the full graph state including the memory dict
* - `memoryEntries` is derived reactively for sidebar rendering
* - Facts appear as the agent learns them during conversation
*/
@Component({
selector: 'app-da-memory',
standalone: true,
imports: [ChatComponent],
template: `<chat [ref]="stream" class="block h-screen" />`,
template: `
<div class="flex h-screen">
<chat [ref]="stream" class="flex-1 min-w-0" />
<aside class="w-72 shrink-0 border-l overflow-y-auto p-4 space-y-2"
style="border-color: var(--chat-border, #333); background: var(--chat-bg, #171717); color: var(--chat-text, #e0e0e0);">
<h3 class="text-xs font-semibold uppercase tracking-wide"
style="color: var(--chat-text-muted, #777);">
Learned Facts
@if (memoryEntries().length > 0) {
<span class="ml-1 tabular-nums">({{ memoryEntries().length }})</span>
}
</h3>
@if (memoryEntries().length === 0) {
<p class="text-sm italic" style="color: var(--chat-text-muted, #777);">No facts learned yet</p>
}
@for (entry of memoryEntries(); track entry[0]) {
<div class="text-sm py-1">
<span class="font-medium" style="color: var(--chat-text, #e0e0e0);">{{ entry[0] }}:</span>
<span style="color: var(--chat-text-muted, #777);"> {{ entry[1] }}</span>
</div>
}
</aside>
</div>
`,
})
export class MemoryComponent {
/**
* The streaming resource connected to the memory graph.
*
* The graph returns an `agent_memory` (or `memory`) dict alongside messages
* in its state. We derive a reactive signal from `stream.value()` for display.
*/
protected readonly stream = streamResource({
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});

/**
* Reactive list of [key, value] memory entries derived from the graph state.
*
* Checks for `agent_memory` first, then falls back to `memory`.
* This signal re-computes whenever the stream state changes.
*/
protected readonly memoryEntries = computed(() => {
const val = this.stream.value() as Record<string, unknown>;
const mem = val?.['agent_memory'] ?? val?.['memory'];
if (!mem || typeof mem !== 'object') return [];
return Object.entries(mem as Record<string, string>);
});
}
72 changes: 69 additions & 3 deletions cockpit/deep-agents/planning/angular/src/app/planning.component.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,89 @@
import { Component } from '@angular/core';
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
import { streamResource } from '@cacheplane/stream-resource';
import { environment } from '../environments/environment';

/**
* Represents a single step in an agent-generated plan.
*/
interface PlanStep {
title: string;
status: 'pending' | 'in_progress' | 'complete';
}

/**
* PlanningComponent demonstrates agent task decomposition.
*
* The agent receives a complex task, breaks it into ordered steps,
* and executes them sequentially.
* and executes them sequentially. The sidebar displays a live checklist
* of plan steps derived from the `plan` array in the graph state.
*
* Key integration points:
* - `stream.value()` exposes the full graph state, including the `plan` array
* - `planSteps` is derived from `stream.value()` for reactive sidebar rendering
* - Step status icons update in real time as the agent progresses
*/
@Component({
selector: 'app-planning',
standalone: true,
imports: [ChatComponent],
template: `<chat [ref]="stream" class="block h-screen" />`,
template: `
<div class="flex h-screen">
<chat [ref]="stream" class="flex-1 min-w-0" />
<aside class="w-72 shrink-0 border-l overflow-y-auto p-4 space-y-2"
style="border-color: var(--chat-border, #333); background: var(--chat-bg, #171717); color: var(--chat-text, #e0e0e0);">
<h3 class="text-xs font-semibold uppercase tracking-wide"
style="color: var(--chat-text-muted, #777);">Plan</h3>
@if (planSteps().length === 0) {
<p class="text-sm italic" style="color: var(--chat-text-muted, #777);">No plan yet</p>
}
@for (step of planSteps(); track step.title) {
<div class="flex items-start gap-2 rounded-md px-2 py-1.5 text-sm"
style="background: var(--chat-bg-hover, #222);">
<span class="mt-0.5 shrink-0 text-base leading-none">
@if (step.status === 'complete') {
<span style="color: #22c55e;">&#10003;</span>
} @else if (step.status === 'in_progress') {
<span class="inline-block animate-spin" style="color: var(--chat-text-muted, #777);">&#9696;</span>
} @else {
<span style="color: var(--chat-border-light, #444);">&#9675;</span>
}
</span>
<span style="color: var(--chat-text, #e0e0e0);">{{ step.title }}</span>
</div>
}
</aside>
</div>
`,
})
export class PlanningComponent {
/**
* The streaming resource connected to the planning graph.
*
* The graph returns a `plan` array alongside messages in its state.
* Each plan entry has a `title` and `status` that drive the sidebar checklist.
*/
protected readonly stream = streamResource({
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});

/**
* Reactive list of plan steps derived from the graph state.
*
* The Python graph stores the plan as `state.plan` — an array of objects
* with `title` and `status` fields. This signal re-computes whenever
* the stream state changes.
*/
protected readonly planSteps = computed<PlanStep[]>(() => {
const val = this.stream.value() as Record<string, unknown>;
const plan = val?.['plan'];
if (!Array.isArray(plan)) return [];
return plan.map((step: Record<string, unknown>) => ({
title: String(step['title'] ?? ''),
status: (['pending', 'in_progress', 'complete'].includes(step['status'] as string)
? step['status']
: 'pending') as PlanStep['status'],
}));
});
}
Loading