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,58 +1,31 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed } from '@angular/core';
import { LegacyChatComponent } from '@cacheplane/chat';
import { ChatDebugComponent } from '@cacheplane/chat';
import { streamResource } from '@cacheplane/stream-resource';
import { environment } from '../environments/environment';

interface ToolCallEntry {
name: string;
args: string;
result?: string;
}

/**
* FilesystemComponent demonstrates agent file operations.
*
* The agent can read and write files using tool calls. The sidebar
* shows a real-time log of each file operation as it happens.
*
* Key integration points:
* - `stream.messages()` contains all messages including tool call results
* - `computed()` derives tool call entries from AI messages
* - Tool calls update reactively as the agent performs file operations
*/
@Component({
selector: 'app-filesystem',
standalone: true,
imports: [LegacyChatComponent],
imports: [ChatDebugComponent],
template: `
<cp-chat
[messages]="stream.messages()"
[isLoading]="stream.isLoading()"
[error]="stream.error()"
(sendMessage)="send($event)">
<ng-template #sidebar>
<h3 style="font-size: 0.8rem; font-weight: 600; margin-bottom: 0.75rem; color: #1a1a2e;">File Operations</h3>
@for (entry of toolCallEntries(); track $index) {
<div style="display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; font-size: 0.8rem; border-bottom: 1px solid #e5e7eb;">
<span style="flex-shrink: 0; font-size: 1rem; line-height: 1.2;">
{{ entry.name === 'read_file' ? '📖' : '✏️' }}
</span>
<div style="min-width: 0;">
<div style="font-weight: 500; color: #1a1a2e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ getFilePath(entry.args) }}
</div>
<div style="color: #8b8fa3; font-size: 0.75rem; margin-top: 2px;">
{{ entry.name === 'read_file' ? 'read' : 'write' }}
{{ entry.result ? ' · done' : ' · running…' }}
</div>
<div class="flex h-screen">
<chat-debug [ref]="stream" class="flex-1 min-w-0" />
@if (fileOps().length > 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 mb-3"
style="color: var(--chat-text-muted, #777);">File Operations</h3>
@for (op of fileOps(); track $index) {
<div class="flex items-start gap-2 text-sm py-1">
<span class="shrink-0">{{ op.name === 'read_file' ? '📖' : '✏️' }}</span>
<span class="font-mono text-xs break-all"
style="color: var(--chat-text, #e0e0e0);">{{ op.path }}</span>
</div>
</div>
}
@empty {
<p style="color: #8b8fa3; font-size: 0.8rem;">Ask the agent to read or write a file.</p>
}
</ng-template>
</cp-chat>
}
</aside>
}
</div>
`,
})
export class FilesystemComponent {
Expand All @@ -61,29 +34,18 @@ export class FilesystemComponent {
assistantId: environment.streamingAssistantId,
});

toolCallEntries = computed(() => {
const msg = this.stream.messages();
const calls: ToolCallEntry[] = [];
for (const m of msg) {
if ((m as any).tool_calls) {
for (const tc of (m as any).tool_calls) {
calls.push({ name: tc.name, args: JSON.stringify(tc.args), result: tc.output });
protected readonly fileOps = computed(() => {
const messages = this.stream.messages();
const ops: { name: string; path: string }[] = [];
for (const msg of messages) {
if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) {
for (const tc of (msg as any).tool_calls) {
if (tc.name === 'read_file' || tc.name === 'write_file') {
ops.push({ name: tc.name, path: tc.args?.path ?? '' });
}
}
}
}
return calls;
return ops;
});

getFilePath(args: string): string {
try {
const parsed = JSON.parse(args);
return parsed.path ?? args;
} catch {
return args;
}
}

send(text: string): void {
this.stream.submit({ messages: [{ role: 'human', content: text }] });
}
}
63 changes: 24 additions & 39 deletions cockpit/deep-agents/memory/angular/src/app/memory.component.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,30 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed } from '@angular/core';
import { LegacyChatComponent } from '@cacheplane/chat';
import { ChatDebugComponent } from '@cacheplane/chat';
import { streamResource } from '@cacheplane/stream-resource';
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. The sidebar shows all learned
* facts in real time as the agent updates its memory.
*
* Key integration points:
* - `stream.value()` contains the agent state including `agent_memory`
* - `computed()` derives key/value pairs for the sidebar
* - Memory entries update reactively as the agent learns new facts
*/
@Component({
selector: 'app-da-memory',
standalone: true,
imports: [LegacyChatComponent],
imports: [ChatDebugComponent],
template: `
<cp-chat
[messages]="stream.messages()"
[isLoading]="stream.isLoading()"
[error]="stream.error()"
(sendMessage)="send($event)">
<ng-template #sidebar>
<h3 style="font-size: 0.8rem; font-weight: 600; margin-bottom: 0.75rem; color: #1a1a2e;">Learned Facts</h3>
@for (entry of memoryEntries(); track entry[0]) {
<div style="padding: 6px 0; font-size: 0.8rem; border-bottom: 1px solid #e5e7eb;">
<div style="font-weight: 600; color: #1a1a2e; margin-bottom: 2px;">{{ entry[0] }}</div>
<div style="color: #555770;">{{ entry[1] }}</div>
</div>
}
@empty {
<p style="color: #8b8fa3; font-size: 0.8rem;">Tell the agent something about yourself to see it remember.</p>
}
</ng-template>
</cp-chat>
<div class="flex h-screen">
<chat-debug [ref]="stream" class="flex-1 min-w-0" />
@if (memoryEntries().length > 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 mb-3"
style="color: var(--chat-text-muted, #777);">Agent Memory</h3>
@for (entry of memoryEntries(); track $index) {
<div class="text-sm py-1">
<span class="font-semibold" style="color: var(--chat-text-muted, #777);">{{ entry[0] }}:</span>
<span class="ml-1" style="color: var(--chat-text, #e0e0e0);">{{ entry[1] }}</span>
</div>
}
</aside>
}
</div>
`,
})
export class MemoryComponent {
Expand All @@ -46,12 +33,10 @@ export class MemoryComponent {
assistantId: environment.streamingAssistantId,
});

memoryEntries = computed(() => {
const val = this.stream.value() as { agent_memory?: Record<string, string> } | undefined;
return Object.entries(val?.agent_memory ?? {});
protected readonly memoryEntries = computed(() => {
const val = this.stream.value() as Record<string, unknown>;
const mem = val?.['agent_memory'];
if (!mem || typeof mem !== 'object') return [];
return Object.entries(mem as Record<string, string>);
});

send(text: string): void {
this.stream.submit({ messages: [{ role: 'human', content: text }] });
}
}
76 changes: 29 additions & 47 deletions cockpit/deep-agents/planning/angular/src/app/planning.component.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,36 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed } from '@angular/core';
import { LegacyChatComponent } from '@cacheplane/chat';
import { ChatDebugComponent } from '@cacheplane/chat';
import { streamResource } from '@cacheplane/stream-resource';
import { environment } from '../environments/environment';

interface PlanStep {
title: string;
status: 'pending' | 'running' | 'complete';
}

/**
* PlanningComponent demonstrates agent task decomposition.
*
* The agent receives a complex task, breaks it into ordered steps,
* and executes them. The sidebar shows each step's status in real time.
*
* Key integration points:
* - `stream.value()` contains the plan state with step list
* - `computed()` derives the plan steps for the sidebar
* - Steps update reactively as the agent works through them
*/
@Component({
selector: 'app-planning',
standalone: true,
imports: [LegacyChatComponent],
imports: [ChatDebugComponent],
template: `
<cp-chat
[messages]="stream.messages()"
[isLoading]="stream.isLoading()"
[error]="stream.error()"
(sendMessage)="send($event)">
<ng-template #sidebar>
<h3 style="font-size: 0.8rem; font-weight: 600; margin-bottom: 0.75rem; color: #1a1a2e;">Task Plan</h3>
@for (step of planSteps(); track $index) {
<div style="display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 0.8rem;">
<span style="width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;"
[style.background]="step.status === 'complete' ? '#10b981' : step.status === 'running' ? '#004090' : '#d1d5db'"></span>
<span [style.color]="step.status === 'complete' ? '#555770' : '#1a1a2e'"
[style.textDecoration]="step.status === 'complete' ? 'line-through' : 'none'">
{{ step.title }}
</span>
</div>
}
@empty {
<p style="color: #8b8fa3; font-size: 0.8rem;">Ask a complex question to see the plan.</p>
}
</ng-template>
</cp-chat>
<div class="flex h-screen">
<chat-debug [ref]="stream" class="flex-1 min-w-0" />
@if (planSteps().length > 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 mb-3"
style="color: var(--chat-text-muted, #777);">Plan Steps</h3>
@for (step of planSteps(); track $index) {
<div class="flex items-start gap-2 text-sm py-1">
<span class="shrink-0 mt-0.5"
style="color: {{ step.status === 'complete' ? 'var(--chat-accent, #4ade80)' : 'var(--chat-text-muted, #777)' }};">
{{ step.status === 'complete' ? '' : '○' }}
</span>
<span [style.text-decoration]="step.status === 'complete' ? 'line-through' : 'none'"
style="color: var(--chat-text, #e0e0e0);">
{{ step.title }}
</span>
</div>
}
</aside>
}
</div>
`,
})
export class PlanningComponent {
Expand All @@ -54,12 +39,9 @@ export class PlanningComponent {
assistantId: environment.streamingAssistantId,
});

planSteps = computed(() => {
const val = this.stream.value() as { plan?: PlanStep[] } | undefined;
return val?.plan ?? [];
protected readonly planSteps = computed(() => {
const val = this.stream.value() as Record<string, unknown>;
const plan = val?.['plan'];
return Array.isArray(plan) ? plan as { title: string; status: string }[] : [];
});

send(text: string): void {
this.stream.submit({ messages: [{ role: 'human', content: text }] });
}
}
Loading
Loading