|
1 | | -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 |
2 | | -import { Component } from '@angular/core'; |
3 | | -import { ChatDebugComponent } from '@cacheplane/chat'; |
| 1 | +import { Component, computed } from '@angular/core'; |
| 2 | +import { LegacyChatComponent } from '@cacheplane/chat'; |
4 | 3 | import { streamResource } from '@cacheplane/stream-resource'; |
5 | 4 | import { environment } from '../environments/environment'; |
6 | 5 |
|
| 6 | +interface ToolCallEntry { |
| 7 | + name: string; |
| 8 | + args: string; |
| 9 | + result?: string; |
| 10 | +} |
| 11 | + |
| 12 | +/** |
| 13 | + * FilesystemComponent demonstrates agent file operations. |
| 14 | + * |
| 15 | + * The agent can read and write files using tool calls. The sidebar |
| 16 | + * shows a real-time log of each file operation as it happens. |
| 17 | + * |
| 18 | + * Key integration points: |
| 19 | + * - `stream.messages()` contains all messages including tool call results |
| 20 | + * - `computed()` derives tool call entries from AI messages |
| 21 | + * - Tool calls update reactively as the agent performs file operations |
| 22 | + */ |
7 | 23 | @Component({ |
8 | 24 | selector: 'app-filesystem', |
9 | 25 | standalone: true, |
10 | | - imports: [ChatDebugComponent], |
11 | | - template: `<chat-debug [ref]="stream" class="block h-screen" />`, |
| 26 | + imports: [LegacyChatComponent], |
| 27 | + template: ` |
| 28 | + <cp-chat |
| 29 | + [messages]="stream.messages()" |
| 30 | + [isLoading]="stream.isLoading()" |
| 31 | + [error]="stream.error()" |
| 32 | + (sendMessage)="send($event)"> |
| 33 | + <ng-template #sidebar> |
| 34 | + <h3 style="font-size: 0.8rem; font-weight: 600; margin-bottom: 0.75rem; color: #1a1a2e;">File Operations</h3> |
| 35 | + @for (entry of toolCallEntries(); track $index) { |
| 36 | + <div style="display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; font-size: 0.8rem; border-bottom: 1px solid #e5e7eb;"> |
| 37 | + <span style="flex-shrink: 0; font-size: 1rem; line-height: 1.2;"> |
| 38 | + {{ entry.name === 'read_file' ? '📖' : '✏️' }} |
| 39 | + </span> |
| 40 | + <div style="min-width: 0;"> |
| 41 | + <div style="font-weight: 500; color: #1a1a2e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> |
| 42 | + {{ getFilePath(entry.args) }} |
| 43 | + </div> |
| 44 | + <div style="color: #8b8fa3; font-size: 0.75rem; margin-top: 2px;"> |
| 45 | + {{ entry.name === 'read_file' ? 'read' : 'write' }} |
| 46 | + {{ entry.result ? ' · done' : ' · running…' }} |
| 47 | + </div> |
| 48 | + </div> |
| 49 | + </div> |
| 50 | + } |
| 51 | + @empty { |
| 52 | + <p style="color: #8b8fa3; font-size: 0.8rem;">Ask the agent to read or write a file.</p> |
| 53 | + } |
| 54 | + </ng-template> |
| 55 | + </cp-chat> |
| 56 | + `, |
12 | 57 | }) |
13 | 58 | export class FilesystemComponent { |
14 | 59 | protected readonly stream = streamResource({ |
15 | 60 | apiUrl: environment.langGraphApiUrl, |
16 | 61 | assistantId: environment.streamingAssistantId, |
17 | 62 | }); |
| 63 | + |
| 64 | + toolCallEntries = computed(() => { |
| 65 | + const msg = this.stream.messages(); |
| 66 | + const calls: ToolCallEntry[] = []; |
| 67 | + for (const m of msg) { |
| 68 | + if ((m as any).tool_calls) { |
| 69 | + for (const tc of (m as any).tool_calls) { |
| 70 | + calls.push({ name: tc.name, args: JSON.stringify(tc.args), result: tc.output }); |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | + return calls; |
| 75 | + }); |
| 76 | + |
| 77 | + getFilePath(args: string): string { |
| 78 | + try { |
| 79 | + const parsed = JSON.parse(args); |
| 80 | + return parsed.path ?? args; |
| 81 | + } catch { |
| 82 | + return args; |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + send(text: string): void { |
| 87 | + this.stream.submit({ messages: [{ role: 'human', content: text }] }); |
| 88 | + } |
18 | 89 | } |
0 commit comments