@copilotkitnext/angular 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -0
- package/dist/README.md +282 -0
- package/dist/components/chat/copilot-chat.component.d.ts +5 -6
- package/dist/esm2022/components/chat/copilot-chat-input.component.mjs +2 -2
- package/dist/esm2022/components/chat/copilot-chat.component.mjs +24 -26
- package/dist/esm2022/index.mjs +2 -2
- package/dist/esm2022/utils/agent.utils.mjs +28 -20
- package/dist/fesm2022/copilotkitnext-angular.mjs +50 -44
- package/dist/fesm2022/copilotkitnext-angular.mjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/utils/agent.utils.d.ts +27 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -124,6 +124,104 @@ export class AppComponent {
|
|
|
124
124
|
|
|
125
125
|
- If `agentId` is omitted, the component uses the default agent (ID: `default`)
|
|
126
126
|
|
|
127
|
+
## Custom Input Components (Angular)
|
|
128
|
+
|
|
129
|
+
When building custom input components for CopilotKit Angular, use the service-based pattern with `CopilotChatConfigurationService` for message submission. This is the idiomatic Angular approach leveraging dependency injection.
|
|
130
|
+
|
|
131
|
+
### Service-Based Custom Input Example:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { Component } from '@angular/core';
|
|
135
|
+
import { FormsModule } from '@angular/forms';
|
|
136
|
+
import { CopilotChatConfigurationService } from '@copilotkitnext/angular';
|
|
137
|
+
|
|
138
|
+
@Component({
|
|
139
|
+
selector: 'my-custom-input',
|
|
140
|
+
standalone: true,
|
|
141
|
+
imports: [FormsModule],
|
|
142
|
+
template: `
|
|
143
|
+
<div class="custom-input-wrapper">
|
|
144
|
+
<input
|
|
145
|
+
[(ngModel)]="inputValue"
|
|
146
|
+
(keyup.enter)="submitMessage()"
|
|
147
|
+
placeholder="Type your message..."
|
|
148
|
+
/>
|
|
149
|
+
<button (click)="submitMessage()">Send</button>
|
|
150
|
+
</div>
|
|
151
|
+
`
|
|
152
|
+
})
|
|
153
|
+
export class MyCustomInputComponent {
|
|
154
|
+
inputValue = '';
|
|
155
|
+
|
|
156
|
+
constructor(private chat: CopilotChatConfigurationService) {}
|
|
157
|
+
|
|
158
|
+
submitMessage() {
|
|
159
|
+
const value = this.inputValue.trim();
|
|
160
|
+
if (value) {
|
|
161
|
+
// Use the service to submit the message
|
|
162
|
+
this.chat.submitInput(value);
|
|
163
|
+
this.inputValue = '';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Using the Custom Input Component:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { Component } from '@angular/core';
|
|
173
|
+
import { CopilotChatViewComponent } from '@copilotkitnext/angular';
|
|
174
|
+
import { MyCustomInputComponent } from './my-custom-input.component';
|
|
175
|
+
|
|
176
|
+
@Component({
|
|
177
|
+
selector: 'app-chat',
|
|
178
|
+
standalone: true,
|
|
179
|
+
imports: [CopilotChatViewComponent],
|
|
180
|
+
template: `
|
|
181
|
+
<copilot-chat-view
|
|
182
|
+
[messages]="messages"
|
|
183
|
+
[inputComponent]="customInputComponent">
|
|
184
|
+
</copilot-chat-view>
|
|
185
|
+
`
|
|
186
|
+
})
|
|
187
|
+
export class ChatComponent {
|
|
188
|
+
messages = [];
|
|
189
|
+
customInputComponent = MyCustomInputComponent;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Key Points:
|
|
194
|
+
|
|
195
|
+
- **No callback props**: Unlike React which uses `onSubmitMessage` callbacks, Angular uses dependency injection
|
|
196
|
+
- **Service injection**: Inject `CopilotChatConfigurationService` to access `submitInput()`
|
|
197
|
+
- **Cross-component communication**: The service handles message submission internally
|
|
198
|
+
- **Type safety**: Full TypeScript support with proper type inference
|
|
199
|
+
|
|
200
|
+
### Alternative: Using the Chat Config Directive
|
|
201
|
+
|
|
202
|
+
For template-level hooks, you can also use the `copilotkitChatConfig` directive:
|
|
203
|
+
|
|
204
|
+
```html
|
|
205
|
+
<div [copilotkitChatConfig]="{
|
|
206
|
+
onSubmitInput: handleSubmit,
|
|
207
|
+
onChangeInput: handleChange
|
|
208
|
+
}">
|
|
209
|
+
<copilot-chat></copilot-chat>
|
|
210
|
+
</div>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
export class ChatComponent {
|
|
215
|
+
handleSubmit = (value: string) => {
|
|
216
|
+
console.log('Message submitted:', value);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
handleChange = (value: string) => {
|
|
220
|
+
console.log('Input changed:', value);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
127
225
|
## Agents 101 (AG-UI)
|
|
128
226
|
|
|
129
227
|
- **Agent model**: CopilotKit uses AG-UI's `AbstractAgent` interface (package `@ag-ui/client`)
|
|
@@ -205,6 +303,190 @@ console.log(
|
|
|
205
303
|
- **`provideCopilotKit(...)`**: Set runtime URL, headers, properties, agents, tools, human-in-the-loop handlers
|
|
206
304
|
- **`provideCopilotChatConfiguration(...)`**: Set UI labels and behavior for chat input/view
|
|
207
305
|
|
|
306
|
+
## Headless Usage: Building Custom Chat UIs
|
|
307
|
+
|
|
308
|
+
For advanced use cases where you need full control over the chat UI, you can use the `watchAgent` utility directly to build a custom chat component.
|
|
309
|
+
|
|
310
|
+
### Using `watchAgent` for Custom Components
|
|
311
|
+
|
|
312
|
+
The `watchAgent` function provides reactive signals for agent state, making it easy to build custom chat interfaces:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { Component, effect } from "@angular/core";
|
|
316
|
+
import { watchAgent } from "@copilotkitnext/angular";
|
|
317
|
+
|
|
318
|
+
@Component({
|
|
319
|
+
selector: "my-custom-chat",
|
|
320
|
+
template: `
|
|
321
|
+
<div class="custom-chat">
|
|
322
|
+
<div *ngFor="let msg of messages()" class="message">
|
|
323
|
+
{{ msg.content }}
|
|
324
|
+
</div>
|
|
325
|
+
<input
|
|
326
|
+
[disabled]="isRunning()"
|
|
327
|
+
(keyup.enter)="sendMessage($event)"
|
|
328
|
+
/>
|
|
329
|
+
</div>
|
|
330
|
+
`,
|
|
331
|
+
})
|
|
332
|
+
export class MyCustomChatComponent {
|
|
333
|
+
protected agent!: ReturnType<typeof watchAgent>["agent"];
|
|
334
|
+
protected messages!: ReturnType<typeof watchAgent>["messages"];
|
|
335
|
+
protected isRunning!: ReturnType<typeof watchAgent>["isRunning"];
|
|
336
|
+
|
|
337
|
+
constructor() {
|
|
338
|
+
const w = watchAgent({ agentId: "custom" });
|
|
339
|
+
this.agent = w.agent;
|
|
340
|
+
this.messages = w.messages;
|
|
341
|
+
this.isRunning = w.isRunning;
|
|
342
|
+
|
|
343
|
+
// React to agent changes
|
|
344
|
+
effect(() => {
|
|
345
|
+
const currentAgent = this.agent();
|
|
346
|
+
if (currentAgent) {
|
|
347
|
+
console.log("Agent ready:", currentAgent.id);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async sendMessage(event: Event) {
|
|
353
|
+
const input = event.target as HTMLInputElement;
|
|
354
|
+
const content = input.value.trim();
|
|
355
|
+
if (!content || !this.agent()) return;
|
|
356
|
+
|
|
357
|
+
// Add user message and run agent
|
|
358
|
+
this.agent()!.addMessage({ role: "user", content });
|
|
359
|
+
input.value = "";
|
|
360
|
+
await this.agent()!.runAgent();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Switching Agents at Runtime
|
|
366
|
+
|
|
367
|
+
Use `watchAgentWith` when you need to switch agents dynamically outside of the constructor:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { Component, Injector } from "@angular/core";
|
|
371
|
+
import { watchAgent, watchAgentWith } from "@copilotkitnext/angular";
|
|
372
|
+
|
|
373
|
+
@Component({
|
|
374
|
+
selector: "agent-switcher",
|
|
375
|
+
template: `
|
|
376
|
+
<button (click)="switchToAgent('sales')">Sales Agent</button>
|
|
377
|
+
<button (click)="switchToAgent('support')">Support Agent</button>
|
|
378
|
+
<div>Current Agent: {{ agent()?.id || 'None' }}</div>
|
|
379
|
+
`,
|
|
380
|
+
})
|
|
381
|
+
export class AgentSwitcherComponent {
|
|
382
|
+
protected agent!: ReturnType<typeof watchAgent>["agent"];
|
|
383
|
+
protected messages!: ReturnType<typeof watchAgent>["messages"];
|
|
384
|
+
protected isRunning!: ReturnType<typeof watchAgent>["isRunning"];
|
|
385
|
+
private watcher?: ReturnType<typeof watchAgent>;
|
|
386
|
+
|
|
387
|
+
constructor(private injector: Injector) {
|
|
388
|
+
// Initialize with default agent
|
|
389
|
+
this.switchToAgent("default");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
switchToAgent(agentId: string) {
|
|
393
|
+
// Clean up previous watcher
|
|
394
|
+
this.watcher?.unsubscribe();
|
|
395
|
+
|
|
396
|
+
// Create new watcher with the ergonomic helper
|
|
397
|
+
const w = watchAgentWith(this.injector, { agentId });
|
|
398
|
+
|
|
399
|
+
// Update component signals
|
|
400
|
+
this.agent = w.agent;
|
|
401
|
+
this.messages = w.messages;
|
|
402
|
+
this.isRunning = w.isRunning;
|
|
403
|
+
this.watcher = w;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Rendering Tool Calls (Headless)
|
|
409
|
+
|
|
410
|
+
To render tool calls in a headless UI, register renderers in your providers and drop the lightweight view in your template.
|
|
411
|
+
|
|
412
|
+
1) Register tool renderers (e.g., a wildcard that renders any tool):
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
|
|
416
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
417
|
+
import { provideCopilotKit } from '@copilotkitnext/angular';
|
|
418
|
+
|
|
419
|
+
// Simple demo renderer (Component or TemplateRef accepted)
|
|
420
|
+
@Component({
|
|
421
|
+
standalone: true,
|
|
422
|
+
template: `
|
|
423
|
+
<div style="padding:12px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;margin:8px 0;">
|
|
424
|
+
<div style="font-weight:600;margin-bottom:6px;">Tool: {{ name }}</div>
|
|
425
|
+
<pre style="margin:0;white-space:pre-wrap;">{{ args | json }}</pre>
|
|
426
|
+
<div *ngIf="result" style="margin-top:6px;">Result: {{ result }}</div>
|
|
427
|
+
</div>
|
|
428
|
+
`,
|
|
429
|
+
})
|
|
430
|
+
export class WildcardToolRenderComponent {
|
|
431
|
+
@Input() name!: string;
|
|
432
|
+
@Input() args: any;
|
|
433
|
+
@Input() status: any;
|
|
434
|
+
@Input() result?: string;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export const appConfig: ApplicationConfig = {
|
|
438
|
+
providers: [
|
|
439
|
+
importProvidersFrom(BrowserModule),
|
|
440
|
+
...provideCopilotKit({
|
|
441
|
+
renderToolCalls: [
|
|
442
|
+
{ name: '*', render: WildcardToolRenderComponent },
|
|
443
|
+
],
|
|
444
|
+
}),
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
2) Render tool calls under assistant messages using the headless view component:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { Component } from '@angular/core';
|
|
453
|
+
import { watchAgent, CopilotChatToolCallsViewComponent } from '@copilotkitnext/angular';
|
|
454
|
+
|
|
455
|
+
@Component({
|
|
456
|
+
standalone: true,
|
|
457
|
+
imports: [CopilotChatToolCallsViewComponent],
|
|
458
|
+
template: `
|
|
459
|
+
<div *ngFor="let m of messages()">
|
|
460
|
+
<div>{{ m.role }}: {{ m.content }}</div>
|
|
461
|
+
<ng-container *ngIf="m.role === 'assistant'">
|
|
462
|
+
<copilot-chat-tool-calls-view
|
|
463
|
+
[message]="m"
|
|
464
|
+
[messages]="messages()"
|
|
465
|
+
[isLoading]="isRunning()"
|
|
466
|
+
/>
|
|
467
|
+
</ng-container>
|
|
468
|
+
</div>
|
|
469
|
+
`,
|
|
470
|
+
})
|
|
471
|
+
export class HeadlessWithToolsComponent {
|
|
472
|
+
agent = watchAgent().agent;
|
|
473
|
+
messages = watchAgent().messages;
|
|
474
|
+
isRunning = watchAgent().isRunning;
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Notes:
|
|
479
|
+
- If you prefer full manual control, you can render a specific tool call with `CopilotKitToolRenderComponent` and pass `toolName`, `args`, `status`, and `result` yourself.
|
|
480
|
+
- You can also register tool renders declaratively via the `CopilotKitFrontendToolDirective` by using `[copilotkitFrontendTool]` in templates.
|
|
481
|
+
|
|
482
|
+
### Key Benefits of Headless Usage
|
|
483
|
+
|
|
484
|
+
- **Full control**: Build any UI you need without constraints
|
|
485
|
+
- **Reactive signals**: Automatically update UI when agent state changes
|
|
486
|
+
- **Type safety**: Full TypeScript support with AG-UI types
|
|
487
|
+
- **Memory efficient**: Automatic cleanup via Angular's DestroyRef
|
|
488
|
+
- **Framework agnostic**: Works with any AG-UI compatible agent
|
|
489
|
+
|
|
208
490
|
## End-to-End: Running the Demo
|
|
209
491
|
|
|
210
492
|
From the repo root:
|
package/dist/README.md
CHANGED
|
@@ -124,6 +124,104 @@ export class AppComponent {
|
|
|
124
124
|
|
|
125
125
|
- If `agentId` is omitted, the component uses the default agent (ID: `default`)
|
|
126
126
|
|
|
127
|
+
## Custom Input Components (Angular)
|
|
128
|
+
|
|
129
|
+
When building custom input components for CopilotKit Angular, use the service-based pattern with `CopilotChatConfigurationService` for message submission. This is the idiomatic Angular approach leveraging dependency injection.
|
|
130
|
+
|
|
131
|
+
### Service-Based Custom Input Example:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { Component } from '@angular/core';
|
|
135
|
+
import { FormsModule } from '@angular/forms';
|
|
136
|
+
import { CopilotChatConfigurationService } from '@copilotkitnext/angular';
|
|
137
|
+
|
|
138
|
+
@Component({
|
|
139
|
+
selector: 'my-custom-input',
|
|
140
|
+
standalone: true,
|
|
141
|
+
imports: [FormsModule],
|
|
142
|
+
template: `
|
|
143
|
+
<div class="custom-input-wrapper">
|
|
144
|
+
<input
|
|
145
|
+
[(ngModel)]="inputValue"
|
|
146
|
+
(keyup.enter)="submitMessage()"
|
|
147
|
+
placeholder="Type your message..."
|
|
148
|
+
/>
|
|
149
|
+
<button (click)="submitMessage()">Send</button>
|
|
150
|
+
</div>
|
|
151
|
+
`
|
|
152
|
+
})
|
|
153
|
+
export class MyCustomInputComponent {
|
|
154
|
+
inputValue = '';
|
|
155
|
+
|
|
156
|
+
constructor(private chat: CopilotChatConfigurationService) {}
|
|
157
|
+
|
|
158
|
+
submitMessage() {
|
|
159
|
+
const value = this.inputValue.trim();
|
|
160
|
+
if (value) {
|
|
161
|
+
// Use the service to submit the message
|
|
162
|
+
this.chat.submitInput(value);
|
|
163
|
+
this.inputValue = '';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Using the Custom Input Component:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { Component } from '@angular/core';
|
|
173
|
+
import { CopilotChatViewComponent } from '@copilotkitnext/angular';
|
|
174
|
+
import { MyCustomInputComponent } from './my-custom-input.component';
|
|
175
|
+
|
|
176
|
+
@Component({
|
|
177
|
+
selector: 'app-chat',
|
|
178
|
+
standalone: true,
|
|
179
|
+
imports: [CopilotChatViewComponent],
|
|
180
|
+
template: `
|
|
181
|
+
<copilot-chat-view
|
|
182
|
+
[messages]="messages"
|
|
183
|
+
[inputComponent]="customInputComponent">
|
|
184
|
+
</copilot-chat-view>
|
|
185
|
+
`
|
|
186
|
+
})
|
|
187
|
+
export class ChatComponent {
|
|
188
|
+
messages = [];
|
|
189
|
+
customInputComponent = MyCustomInputComponent;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Key Points:
|
|
194
|
+
|
|
195
|
+
- **No callback props**: Unlike React which uses `onSubmitMessage` callbacks, Angular uses dependency injection
|
|
196
|
+
- **Service injection**: Inject `CopilotChatConfigurationService` to access `submitInput()`
|
|
197
|
+
- **Cross-component communication**: The service handles message submission internally
|
|
198
|
+
- **Type safety**: Full TypeScript support with proper type inference
|
|
199
|
+
|
|
200
|
+
### Alternative: Using the Chat Config Directive
|
|
201
|
+
|
|
202
|
+
For template-level hooks, you can also use the `copilotkitChatConfig` directive:
|
|
203
|
+
|
|
204
|
+
```html
|
|
205
|
+
<div [copilotkitChatConfig]="{
|
|
206
|
+
onSubmitInput: handleSubmit,
|
|
207
|
+
onChangeInput: handleChange
|
|
208
|
+
}">
|
|
209
|
+
<copilot-chat></copilot-chat>
|
|
210
|
+
</div>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
export class ChatComponent {
|
|
215
|
+
handleSubmit = (value: string) => {
|
|
216
|
+
console.log('Message submitted:', value);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
handleChange = (value: string) => {
|
|
220
|
+
console.log('Input changed:', value);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
127
225
|
## Agents 101 (AG-UI)
|
|
128
226
|
|
|
129
227
|
- **Agent model**: CopilotKit uses AG-UI's `AbstractAgent` interface (package `@ag-ui/client`)
|
|
@@ -205,6 +303,190 @@ console.log(
|
|
|
205
303
|
- **`provideCopilotKit(...)`**: Set runtime URL, headers, properties, agents, tools, human-in-the-loop handlers
|
|
206
304
|
- **`provideCopilotChatConfiguration(...)`**: Set UI labels and behavior for chat input/view
|
|
207
305
|
|
|
306
|
+
## Headless Usage: Building Custom Chat UIs
|
|
307
|
+
|
|
308
|
+
For advanced use cases where you need full control over the chat UI, you can use the `watchAgent` utility directly to build a custom chat component.
|
|
309
|
+
|
|
310
|
+
### Using `watchAgent` for Custom Components
|
|
311
|
+
|
|
312
|
+
The `watchAgent` function provides reactive signals for agent state, making it easy to build custom chat interfaces:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { Component, effect } from "@angular/core";
|
|
316
|
+
import { watchAgent } from "@copilotkitnext/angular";
|
|
317
|
+
|
|
318
|
+
@Component({
|
|
319
|
+
selector: "my-custom-chat",
|
|
320
|
+
template: `
|
|
321
|
+
<div class="custom-chat">
|
|
322
|
+
<div *ngFor="let msg of messages()" class="message">
|
|
323
|
+
{{ msg.content }}
|
|
324
|
+
</div>
|
|
325
|
+
<input
|
|
326
|
+
[disabled]="isRunning()"
|
|
327
|
+
(keyup.enter)="sendMessage($event)"
|
|
328
|
+
/>
|
|
329
|
+
</div>
|
|
330
|
+
`,
|
|
331
|
+
})
|
|
332
|
+
export class MyCustomChatComponent {
|
|
333
|
+
protected agent!: ReturnType<typeof watchAgent>["agent"];
|
|
334
|
+
protected messages!: ReturnType<typeof watchAgent>["messages"];
|
|
335
|
+
protected isRunning!: ReturnType<typeof watchAgent>["isRunning"];
|
|
336
|
+
|
|
337
|
+
constructor() {
|
|
338
|
+
const w = watchAgent({ agentId: "custom" });
|
|
339
|
+
this.agent = w.agent;
|
|
340
|
+
this.messages = w.messages;
|
|
341
|
+
this.isRunning = w.isRunning;
|
|
342
|
+
|
|
343
|
+
// React to agent changes
|
|
344
|
+
effect(() => {
|
|
345
|
+
const currentAgent = this.agent();
|
|
346
|
+
if (currentAgent) {
|
|
347
|
+
console.log("Agent ready:", currentAgent.id);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async sendMessage(event: Event) {
|
|
353
|
+
const input = event.target as HTMLInputElement;
|
|
354
|
+
const content = input.value.trim();
|
|
355
|
+
if (!content || !this.agent()) return;
|
|
356
|
+
|
|
357
|
+
// Add user message and run agent
|
|
358
|
+
this.agent()!.addMessage({ role: "user", content });
|
|
359
|
+
input.value = "";
|
|
360
|
+
await this.agent()!.runAgent();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Switching Agents at Runtime
|
|
366
|
+
|
|
367
|
+
Use `watchAgentWith` when you need to switch agents dynamically outside of the constructor:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { Component, Injector } from "@angular/core";
|
|
371
|
+
import { watchAgent, watchAgentWith } from "@copilotkitnext/angular";
|
|
372
|
+
|
|
373
|
+
@Component({
|
|
374
|
+
selector: "agent-switcher",
|
|
375
|
+
template: `
|
|
376
|
+
<button (click)="switchToAgent('sales')">Sales Agent</button>
|
|
377
|
+
<button (click)="switchToAgent('support')">Support Agent</button>
|
|
378
|
+
<div>Current Agent: {{ agent()?.id || 'None' }}</div>
|
|
379
|
+
`,
|
|
380
|
+
})
|
|
381
|
+
export class AgentSwitcherComponent {
|
|
382
|
+
protected agent!: ReturnType<typeof watchAgent>["agent"];
|
|
383
|
+
protected messages!: ReturnType<typeof watchAgent>["messages"];
|
|
384
|
+
protected isRunning!: ReturnType<typeof watchAgent>["isRunning"];
|
|
385
|
+
private watcher?: ReturnType<typeof watchAgent>;
|
|
386
|
+
|
|
387
|
+
constructor(private injector: Injector) {
|
|
388
|
+
// Initialize with default agent
|
|
389
|
+
this.switchToAgent("default");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
switchToAgent(agentId: string) {
|
|
393
|
+
// Clean up previous watcher
|
|
394
|
+
this.watcher?.unsubscribe();
|
|
395
|
+
|
|
396
|
+
// Create new watcher with the ergonomic helper
|
|
397
|
+
const w = watchAgentWith(this.injector, { agentId });
|
|
398
|
+
|
|
399
|
+
// Update component signals
|
|
400
|
+
this.agent = w.agent;
|
|
401
|
+
this.messages = w.messages;
|
|
402
|
+
this.isRunning = w.isRunning;
|
|
403
|
+
this.watcher = w;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Rendering Tool Calls (Headless)
|
|
409
|
+
|
|
410
|
+
To render tool calls in a headless UI, register renderers in your providers and drop the lightweight view in your template.
|
|
411
|
+
|
|
412
|
+
1) Register tool renderers (e.g., a wildcard that renders any tool):
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
|
|
416
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
417
|
+
import { provideCopilotKit } from '@copilotkitnext/angular';
|
|
418
|
+
|
|
419
|
+
// Simple demo renderer (Component or TemplateRef accepted)
|
|
420
|
+
@Component({
|
|
421
|
+
standalone: true,
|
|
422
|
+
template: `
|
|
423
|
+
<div style="padding:12px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;margin:8px 0;">
|
|
424
|
+
<div style="font-weight:600;margin-bottom:6px;">Tool: {{ name }}</div>
|
|
425
|
+
<pre style="margin:0;white-space:pre-wrap;">{{ args | json }}</pre>
|
|
426
|
+
<div *ngIf="result" style="margin-top:6px;">Result: {{ result }}</div>
|
|
427
|
+
</div>
|
|
428
|
+
`,
|
|
429
|
+
})
|
|
430
|
+
export class WildcardToolRenderComponent {
|
|
431
|
+
@Input() name!: string;
|
|
432
|
+
@Input() args: any;
|
|
433
|
+
@Input() status: any;
|
|
434
|
+
@Input() result?: string;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export const appConfig: ApplicationConfig = {
|
|
438
|
+
providers: [
|
|
439
|
+
importProvidersFrom(BrowserModule),
|
|
440
|
+
...provideCopilotKit({
|
|
441
|
+
renderToolCalls: [
|
|
442
|
+
{ name: '*', render: WildcardToolRenderComponent },
|
|
443
|
+
],
|
|
444
|
+
}),
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
2) Render tool calls under assistant messages using the headless view component:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { Component } from '@angular/core';
|
|
453
|
+
import { watchAgent, CopilotChatToolCallsViewComponent } from '@copilotkitnext/angular';
|
|
454
|
+
|
|
455
|
+
@Component({
|
|
456
|
+
standalone: true,
|
|
457
|
+
imports: [CopilotChatToolCallsViewComponent],
|
|
458
|
+
template: `
|
|
459
|
+
<div *ngFor="let m of messages()">
|
|
460
|
+
<div>{{ m.role }}: {{ m.content }}</div>
|
|
461
|
+
<ng-container *ngIf="m.role === 'assistant'">
|
|
462
|
+
<copilot-chat-tool-calls-view
|
|
463
|
+
[message]="m"
|
|
464
|
+
[messages]="messages()"
|
|
465
|
+
[isLoading]="isRunning()"
|
|
466
|
+
/>
|
|
467
|
+
</ng-container>
|
|
468
|
+
</div>
|
|
469
|
+
`,
|
|
470
|
+
})
|
|
471
|
+
export class HeadlessWithToolsComponent {
|
|
472
|
+
agent = watchAgent().agent;
|
|
473
|
+
messages = watchAgent().messages;
|
|
474
|
+
isRunning = watchAgent().isRunning;
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Notes:
|
|
479
|
+
- If you prefer full manual control, you can render a specific tool call with `CopilotKitToolRenderComponent` and pass `toolName`, `args`, `status`, and `result` yourself.
|
|
480
|
+
- You can also register tool renders declaratively via the `CopilotKitFrontendToolDirective` by using `[copilotkitFrontendTool]` in templates.
|
|
481
|
+
|
|
482
|
+
### Key Benefits of Headless Usage
|
|
483
|
+
|
|
484
|
+
- **Full control**: Build any UI you need without constraints
|
|
485
|
+
- **Reactive signals**: Automatically update UI when agent state changes
|
|
486
|
+
- **Type safety**: Full TypeScript support with AG-UI types
|
|
487
|
+
- **Memory efficient**: Automatic cleanup via Angular's DestroyRef
|
|
488
|
+
- **Framework agnostic**: Works with any AG-UI compatible agent
|
|
489
|
+
|
|
208
490
|
## End-to-End: Running the Demo
|
|
209
491
|
|
|
210
492
|
From the repo root:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OnInit, OnChanges,
|
|
1
|
+
import { OnInit, OnChanges, SimpleChanges, ChangeDetectorRef, Signal, Injector, Type } from "@angular/core";
|
|
2
2
|
import { CopilotChatConfigurationService } from "../../core/chat-configuration/chat-configuration.service";
|
|
3
3
|
import { Message, AbstractAgent } from "@ag-ui/client";
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
@@ -11,27 +11,26 @@ import * as i0 from "@angular/core";
|
|
|
11
11
|
* <copilot-chat [agentId]="'default'" [threadId]="'abc123'"></copilot-chat>
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
|
-
export declare class CopilotChatComponent implements OnInit, OnChanges
|
|
14
|
+
export declare class CopilotChatComponent implements OnInit, OnChanges {
|
|
15
15
|
private chatConfig;
|
|
16
16
|
private cdr;
|
|
17
17
|
private injector;
|
|
18
18
|
agentId?: string;
|
|
19
19
|
threadId?: string;
|
|
20
|
+
inputComponent?: Type<any>;
|
|
20
21
|
constructor(chatConfig: CopilotChatConfigurationService | null, cdr: ChangeDetectorRef, injector: Injector);
|
|
21
22
|
protected agent: Signal<AbstractAgent | undefined>;
|
|
22
23
|
protected messages: Signal<Message[]>;
|
|
23
24
|
protected isRunning: Signal<boolean>;
|
|
24
25
|
protected showCursor: import("@angular/core").WritableSignal<boolean>;
|
|
25
26
|
private generatedThreadId;
|
|
26
|
-
private
|
|
27
|
+
private watcher?;
|
|
27
28
|
private hasConnectedOnce;
|
|
28
|
-
private lastAgentId?;
|
|
29
29
|
ngOnInit(): void;
|
|
30
30
|
ngOnChanges(changes: SimpleChanges): void;
|
|
31
31
|
private connectToAgent;
|
|
32
32
|
private setupChatHandlers;
|
|
33
|
-
ngOnDestroy(): void;
|
|
34
33
|
private createWatcher;
|
|
35
34
|
static ɵfac: i0.ɵɵFactoryDeclaration<CopilotChatComponent, [{ optional: true; }, null, null]>;
|
|
36
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<CopilotChatComponent, "copilot-chat", never, { "agentId": { "alias": "agentId"; "required": false; }; "threadId": { "alias": "threadId"; "required": false; }; }, {}, never, never, true, never>;
|
|
35
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CopilotChatComponent, "copilot-chat", never, { "agentId": { "alias": "agentId"; "required": false; }; "threadId": { "alias": "threadId"; "required": false; }; "inputComponent": { "alias": "inputComponent"; "required": false; }; }, {}, never, never, true, never>;
|
|
37
36
|
}
|