@ai-sdk/angular 0.0.0-02dba89b-20251009204516
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/CHANGELOG.md +911 -0
- package/LICENSE +13 -0
- package/README.md +643 -0
- package/dist/index.d.mts +116 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +325 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +309 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2023 Vercel, Inc.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
# AI SDK Angular
|
|
2
|
+
|
|
3
|
+
Angular UI components for the [AI SDK v5](https://ai-sdk.dev/docs).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@ai-sdk/angular` package provides Angular-specific implementations using Angular signals for reactive state management:
|
|
8
|
+
|
|
9
|
+
- **Chat** - Multi-turn conversations with streaming responses
|
|
10
|
+
- **Completion** - Single-turn text generation
|
|
11
|
+
- **StructuredObject** - Type-safe object generation with Zod schemas
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @ai-sdk/angular ai
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Peer Dependencies
|
|
20
|
+
|
|
21
|
+
- Angular 16+ (`@angular/core`)
|
|
22
|
+
- Zod v3+ (optional, for structured objects)
|
|
23
|
+
|
|
24
|
+
## Chat
|
|
25
|
+
|
|
26
|
+
Real-time conversation interface with streaming support.
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Component, inject } from '@angular/core';
|
|
32
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
33
|
+
import { Chat } from '@ai-sdk/angular';
|
|
34
|
+
import { CommonModule } from '@angular/common';
|
|
35
|
+
|
|
36
|
+
@Component({
|
|
37
|
+
selector: 'app-chat',
|
|
38
|
+
imports: [CommonModule, ReactiveFormsModule],
|
|
39
|
+
template: `
|
|
40
|
+
<div class="chat-container">
|
|
41
|
+
<div class="messages">
|
|
42
|
+
@for (message of chat.messages; track message.id) {
|
|
43
|
+
<div class="message" [ngClass]="message.role">
|
|
44
|
+
@for (part of message.parts; track $index) {
|
|
45
|
+
@switch (part.type) {
|
|
46
|
+
@case ('text') {
|
|
47
|
+
<div style="white-space: pre-wrap">
|
|
48
|
+
{{ part.text }}
|
|
49
|
+
@if (part.state === 'streaming') {
|
|
50
|
+
<span class="cursor">▮</span>
|
|
51
|
+
}
|
|
52
|
+
</div>
|
|
53
|
+
}
|
|
54
|
+
@case ('reasoning') {
|
|
55
|
+
<details>
|
|
56
|
+
<summary>Reasoning</summary>
|
|
57
|
+
<div style="white-space: pre-wrap; opacity: 80%">
|
|
58
|
+
{{ part.text }}
|
|
59
|
+
</div>
|
|
60
|
+
</details>
|
|
61
|
+
}
|
|
62
|
+
@default {
|
|
63
|
+
<code>{{ part | json }}</code>
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
</div>
|
|
68
|
+
}
|
|
69
|
+
@if (chat.status === 'submitted') {
|
|
70
|
+
<div><em>Waiting...</em></div>
|
|
71
|
+
}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<form [formGroup]="chatForm" (ngSubmit)="sendMessage()">
|
|
75
|
+
<input formControlName="userInput" placeholder="Type your message..." />
|
|
76
|
+
@if (chat.status === 'ready') {
|
|
77
|
+
<button type="submit" [disabled]="!chatForm.valid">Send</button>
|
|
78
|
+
} @else {
|
|
79
|
+
<button [disabled]="chat.status === 'error'" (click)="chat.stop()">
|
|
80
|
+
Stop
|
|
81
|
+
</button>
|
|
82
|
+
}
|
|
83
|
+
</form>
|
|
84
|
+
</div>
|
|
85
|
+
`,
|
|
86
|
+
})
|
|
87
|
+
export class ChatComponent {
|
|
88
|
+
private fb = inject(FormBuilder);
|
|
89
|
+
|
|
90
|
+
public chat = new Chat({});
|
|
91
|
+
|
|
92
|
+
chatForm = this.fb.group({
|
|
93
|
+
userInput: ['', Validators.required],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
sendMessage() {
|
|
97
|
+
if (this.chatForm.invalid) return;
|
|
98
|
+
|
|
99
|
+
const userInput = this.chatForm.value.userInput;
|
|
100
|
+
this.chatForm.reset();
|
|
101
|
+
|
|
102
|
+
this.chat.sendMessage(
|
|
103
|
+
{ text: userInput },
|
|
104
|
+
{
|
|
105
|
+
body: {
|
|
106
|
+
selectedModel: 'gpt-4o',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Constructor Options
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
interface ChatInit<UI_MESSAGE extends UIMessage = UIMessage> {
|
|
118
|
+
/** Initial messages */
|
|
119
|
+
messages?: UI_MESSAGE[];
|
|
120
|
+
|
|
121
|
+
/** Custom ID generator */
|
|
122
|
+
generateId?: () => string;
|
|
123
|
+
|
|
124
|
+
/** Maximum conversation steps */
|
|
125
|
+
maxSteps?: number;
|
|
126
|
+
|
|
127
|
+
/** Tool call handler */
|
|
128
|
+
onToolCall?: (params: { toolCall: ToolCall }) => Promise<string>;
|
|
129
|
+
|
|
130
|
+
/** Completion callback */
|
|
131
|
+
onFinish?: (params: { message: UI_MESSAGE }) => void;
|
|
132
|
+
|
|
133
|
+
/** Error handler */
|
|
134
|
+
onError?: (error: Error) => void;
|
|
135
|
+
|
|
136
|
+
/** Custom transport */
|
|
137
|
+
transport?: ChatTransport;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Properties (Reactive)
|
|
142
|
+
|
|
143
|
+
- `messages: UIMessage[]` - Array of conversation messages
|
|
144
|
+
- `status: 'ready' | 'submitted' | 'streaming' | 'error'` - Current status
|
|
145
|
+
- `error: Error | undefined` - Current error state
|
|
146
|
+
|
|
147
|
+
### Methods
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Send a message
|
|
151
|
+
await chat.sendMessage(
|
|
152
|
+
message: UIMessageInput,
|
|
153
|
+
options?: {
|
|
154
|
+
body?: Record<string, any>;
|
|
155
|
+
headers?: Record<string, string>;
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Regenerate last assistant message
|
|
160
|
+
await chat.regenerate(options?: {
|
|
161
|
+
body?: Record<string, any>;
|
|
162
|
+
headers?: Record<string, string>;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Add tool execution result
|
|
166
|
+
chat.addToolResult({
|
|
167
|
+
toolCallId: string;
|
|
168
|
+
output: string;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Stop current generation
|
|
172
|
+
chat.stop();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### File Attachments
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// HTML template
|
|
179
|
+
<input type="file" multiple (change)="onFileSelect($event)" />
|
|
180
|
+
|
|
181
|
+
// Component
|
|
182
|
+
onFileSelect(event: Event) {
|
|
183
|
+
const files = (event.target as HTMLInputElement).files;
|
|
184
|
+
if (files) {
|
|
185
|
+
this.chat.sendMessage({
|
|
186
|
+
text: "Analyze these files",
|
|
187
|
+
files: files
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Client-side Tool Calls
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const chat = new Chat({
|
|
197
|
+
async onToolCall({ toolCall }) {
|
|
198
|
+
switch (toolCall.toolName) {
|
|
199
|
+
case 'get_weather':
|
|
200
|
+
return await getWeather(toolCall.input.location);
|
|
201
|
+
case 'search':
|
|
202
|
+
return await search(toolCall.input.query);
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`Unknown tool: ${toolCall.toolName}`);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Completion
|
|
211
|
+
|
|
212
|
+
Single-turn text generation with streaming.
|
|
213
|
+
|
|
214
|
+
### Basic Usage
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { Component } from '@angular/core';
|
|
218
|
+
import { Completion } from '@ai-sdk/angular';
|
|
219
|
+
|
|
220
|
+
@Component({
|
|
221
|
+
selector: 'app-completion',
|
|
222
|
+
template: `
|
|
223
|
+
<div>
|
|
224
|
+
<textarea
|
|
225
|
+
[(ngModel)]="completion.input"
|
|
226
|
+
placeholder="Enter your prompt..."
|
|
227
|
+
rows="4"
|
|
228
|
+
>
|
|
229
|
+
</textarea>
|
|
230
|
+
|
|
231
|
+
<button
|
|
232
|
+
(click)="completion.complete(completion.input)"
|
|
233
|
+
[disabled]="completion.loading"
|
|
234
|
+
>
|
|
235
|
+
{{ completion.loading ? 'Generating...' : 'Generate' }}
|
|
236
|
+
</button>
|
|
237
|
+
|
|
238
|
+
@if (completion.loading) {
|
|
239
|
+
<button (click)="completion.stop()">Stop</button>
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
<div class="result">
|
|
243
|
+
<h3>Result:</h3>
|
|
244
|
+
<pre>{{ completion.completion }}</pre>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
@if (completion.error) {
|
|
248
|
+
<div class="error">{{ completion.error.message }}</div>
|
|
249
|
+
}
|
|
250
|
+
</div>
|
|
251
|
+
`,
|
|
252
|
+
})
|
|
253
|
+
export class CompletionComponent {
|
|
254
|
+
completion = new Completion({
|
|
255
|
+
api: '/api/completion',
|
|
256
|
+
onFinish: (prompt, completion) => {
|
|
257
|
+
console.log('Completed:', { prompt, completion });
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Constructor Options
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
interface CompletionOptions {
|
|
267
|
+
/** API endpoint (default: '/api/completion') */
|
|
268
|
+
api?: string;
|
|
269
|
+
|
|
270
|
+
/** Unique identifier */
|
|
271
|
+
id?: string;
|
|
272
|
+
|
|
273
|
+
/** Initial completion text */
|
|
274
|
+
initialCompletion?: string;
|
|
275
|
+
|
|
276
|
+
/** Initial input text */
|
|
277
|
+
initialInput?: string;
|
|
278
|
+
|
|
279
|
+
/** Stream protocol: 'data' (default) | 'text' */
|
|
280
|
+
streamProtocol?: 'data' | 'text';
|
|
281
|
+
|
|
282
|
+
/** Completion callback */
|
|
283
|
+
onFinish?: (prompt: string, completion: string) => void;
|
|
284
|
+
|
|
285
|
+
/** Error handler */
|
|
286
|
+
onError?: (error: Error) => void;
|
|
287
|
+
|
|
288
|
+
/** Custom fetch function */
|
|
289
|
+
fetch?: FetchFunction;
|
|
290
|
+
|
|
291
|
+
/** Request headers */
|
|
292
|
+
headers?: Record<string, string>;
|
|
293
|
+
|
|
294
|
+
/** Request body */
|
|
295
|
+
body?: Record<string, any>;
|
|
296
|
+
|
|
297
|
+
/** Request credentials */
|
|
298
|
+
credentials?: RequestCredentials;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Properties (Reactive)
|
|
303
|
+
|
|
304
|
+
- `completion: string` - Generated text (writable)
|
|
305
|
+
- `input: string` - Current input (writable)
|
|
306
|
+
- `loading: boolean` - Generation state
|
|
307
|
+
- `error: Error | undefined` - Error state
|
|
308
|
+
- `id: string` - Completion ID
|
|
309
|
+
- `api: string` - API endpoint
|
|
310
|
+
- `streamProtocol: 'data' | 'text'` - Stream type
|
|
311
|
+
|
|
312
|
+
### Methods
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// Generate completion
|
|
316
|
+
await completion.complete(
|
|
317
|
+
prompt: string,
|
|
318
|
+
options?: {
|
|
319
|
+
headers?: Record<string, string>;
|
|
320
|
+
body?: Record<string, any>;
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
// Form submission handler
|
|
325
|
+
await completion.handleSubmit(event?: { preventDefault?: () => void });
|
|
326
|
+
|
|
327
|
+
// Stop generation
|
|
328
|
+
completion.stop();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## StructuredObject
|
|
332
|
+
|
|
333
|
+
Generate structured data with Zod schemas and streaming.
|
|
334
|
+
|
|
335
|
+
### Basic Usage
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { Component } from '@angular/core';
|
|
339
|
+
import { StructuredObject } from '@ai-sdk/angular';
|
|
340
|
+
import { z } from 'zod';
|
|
341
|
+
|
|
342
|
+
const schema = z.object({
|
|
343
|
+
title: z.string(),
|
|
344
|
+
summary: z.string(),
|
|
345
|
+
tags: z.array(z.string()),
|
|
346
|
+
sentiment: z.enum(['positive', 'negative', 'neutral']),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
@Component({
|
|
350
|
+
selector: 'app-structured-object',
|
|
351
|
+
template: `
|
|
352
|
+
<div>
|
|
353
|
+
<textarea
|
|
354
|
+
[(ngModel)]="input"
|
|
355
|
+
placeholder="Enter content to analyze..."
|
|
356
|
+
rows="4"
|
|
357
|
+
>
|
|
358
|
+
</textarea>
|
|
359
|
+
|
|
360
|
+
<button (click)="analyze()" [disabled]="structuredObject.loading">
|
|
361
|
+
{{ structuredObject.loading ? 'Analyzing...' : 'Analyze' }}
|
|
362
|
+
</button>
|
|
363
|
+
|
|
364
|
+
@if (structuredObject.object) {
|
|
365
|
+
<div class="result">
|
|
366
|
+
<h3>Analysis:</h3>
|
|
367
|
+
<div><strong>Title:</strong> {{ structuredObject.object.title }}</div>
|
|
368
|
+
<div>
|
|
369
|
+
<strong>Summary:</strong> {{ structuredObject.object.summary }}
|
|
370
|
+
</div>
|
|
371
|
+
<div>
|
|
372
|
+
<strong>Tags:</strong>
|
|
373
|
+
{{ structuredObject.object.tags?.join(', ') }}
|
|
374
|
+
</div>
|
|
375
|
+
<div>
|
|
376
|
+
<strong>Sentiment:</strong> {{ structuredObject.object.sentiment }}
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
@if (structuredObject.error) {
|
|
382
|
+
<div class="error">{{ structuredObject.error.message }}</div>
|
|
383
|
+
}
|
|
384
|
+
</div>
|
|
385
|
+
`,
|
|
386
|
+
})
|
|
387
|
+
export class StructuredObjectComponent {
|
|
388
|
+
input = '';
|
|
389
|
+
|
|
390
|
+
structuredObject = new StructuredObject({
|
|
391
|
+
api: '/api/analyze',
|
|
392
|
+
schema,
|
|
393
|
+
onFinish: ({ object, error }) => {
|
|
394
|
+
if (error) {
|
|
395
|
+
console.error('Schema validation failed:', error);
|
|
396
|
+
} else {
|
|
397
|
+
console.log('Generated object:', object);
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
async analyze() {
|
|
403
|
+
if (!this.input.trim()) return;
|
|
404
|
+
await this.structuredObject.submit(this.input);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Constructor Options
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
interface StructuredObjectOptions<SCHEMA, RESULT> {
|
|
413
|
+
/** API endpoint */
|
|
414
|
+
api: string;
|
|
415
|
+
|
|
416
|
+
/** Zod schema */
|
|
417
|
+
schema: SCHEMA;
|
|
418
|
+
|
|
419
|
+
/** Unique identifier */
|
|
420
|
+
id?: string;
|
|
421
|
+
|
|
422
|
+
/** Initial object value */
|
|
423
|
+
initialValue?: DeepPartial<RESULT>;
|
|
424
|
+
|
|
425
|
+
/** Completion callback */
|
|
426
|
+
onFinish?: (event: {
|
|
427
|
+
object: RESULT | undefined;
|
|
428
|
+
error: Error | undefined;
|
|
429
|
+
}) => void;
|
|
430
|
+
|
|
431
|
+
/** Error handler */
|
|
432
|
+
onError?: (error: Error) => void;
|
|
433
|
+
|
|
434
|
+
/** Custom fetch function */
|
|
435
|
+
fetch?: FetchFunction;
|
|
436
|
+
|
|
437
|
+
/** Request headers */
|
|
438
|
+
headers?: Record<string, string>;
|
|
439
|
+
|
|
440
|
+
/** Request credentials */
|
|
441
|
+
credentials?: RequestCredentials;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Properties (Reactive)
|
|
446
|
+
|
|
447
|
+
- `object: DeepPartial<RESULT> | undefined` - Generated object
|
|
448
|
+
- `loading: boolean` - Generation state
|
|
449
|
+
- `error: Error | undefined` - Error state
|
|
450
|
+
|
|
451
|
+
### Methods
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// Submit input for generation
|
|
455
|
+
await structuredObject.submit(input: unknown);
|
|
456
|
+
|
|
457
|
+
// Stop generation
|
|
458
|
+
structuredObject.stop();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Server Implementation
|
|
462
|
+
|
|
463
|
+
### Express.js Chat Endpoint
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { openai } from '@ai-sdk/openai';
|
|
467
|
+
import { convertToModelMessages, streamText } from 'ai';
|
|
468
|
+
import express from 'express';
|
|
469
|
+
|
|
470
|
+
const app = express();
|
|
471
|
+
app.use(express.json());
|
|
472
|
+
|
|
473
|
+
app.post('/api/chat', async (req, res) => {
|
|
474
|
+
const { messages, selectedModel } = req.body;
|
|
475
|
+
|
|
476
|
+
const result = streamText({
|
|
477
|
+
model: openai(selectedModel || 'gpt-4o'),
|
|
478
|
+
messages: convertToModelMessages(messages),
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
result.pipeUIMessageStreamToResponse(res);
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Express.js Completion Endpoint
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
app.post('/api/completion', async (req, res) => {
|
|
489
|
+
const { prompt } = req.body;
|
|
490
|
+
|
|
491
|
+
const result = streamText({
|
|
492
|
+
model: openai('gpt-4o'),
|
|
493
|
+
prompt,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
result.pipeTextStreamToResponse(res);
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Express.js Structured Object Endpoint
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { streamObject } from 'ai';
|
|
504
|
+
import { z } from 'zod';
|
|
505
|
+
|
|
506
|
+
app.post('/api/analyze', async (req, res) => {
|
|
507
|
+
const input = req.body;
|
|
508
|
+
|
|
509
|
+
const result = streamObject({
|
|
510
|
+
model: openai('gpt-4o'),
|
|
511
|
+
schema: z.object({
|
|
512
|
+
title: z.string(),
|
|
513
|
+
summary: z.string(),
|
|
514
|
+
tags: z.array(z.string()),
|
|
515
|
+
sentiment: z.enum(['positive', 'negative', 'neutral']),
|
|
516
|
+
}),
|
|
517
|
+
prompt: `Analyze this content: ${JSON.stringify(input)}`,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
result.pipeTextStreamToResponse(res);
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Development Setup
|
|
525
|
+
|
|
526
|
+
### Building the Library
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
# Install dependencies
|
|
530
|
+
pnpm install
|
|
531
|
+
|
|
532
|
+
# Build library
|
|
533
|
+
pnpm build
|
|
534
|
+
|
|
535
|
+
# Watch mode
|
|
536
|
+
pnpm build:watch
|
|
537
|
+
|
|
538
|
+
# Run tests
|
|
539
|
+
pnpm test
|
|
540
|
+
|
|
541
|
+
# Test watch mode
|
|
542
|
+
pnpm test:watch
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Running the Example
|
|
546
|
+
|
|
547
|
+
```bash
|
|
548
|
+
# Navigate to example
|
|
549
|
+
cd examples/angular-chat
|
|
550
|
+
|
|
551
|
+
# Set up environment
|
|
552
|
+
echo "OPENAI_API_KEY=your_key_here" > .env
|
|
553
|
+
|
|
554
|
+
# Start development (Angular + Express)
|
|
555
|
+
pnpm start
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Starts:
|
|
559
|
+
|
|
560
|
+
- Angular dev server: `http://localhost:4200`
|
|
561
|
+
- Express API server: `http://localhost:3000`
|
|
562
|
+
- Proxy routes `/api/*` to Express
|
|
563
|
+
|
|
564
|
+
## Testing
|
|
565
|
+
|
|
566
|
+
### Running Tests
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
pnpm test # Run all tests
|
|
570
|
+
pnpm test:watch # Watch mode
|
|
571
|
+
pnpm test:update # Update snapshots
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## TypeScript Support
|
|
575
|
+
|
|
576
|
+
Full type safety with automatic type inference:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { Chat, UIMessage, StructuredObject } from '@ai-sdk/angular';
|
|
580
|
+
import { z } from 'zod';
|
|
581
|
+
|
|
582
|
+
// Custom message types
|
|
583
|
+
interface CustomMessage extends UIMessage {
|
|
584
|
+
customData?: string;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const chat = new Chat<CustomMessage>({});
|
|
588
|
+
|
|
589
|
+
// Schema-typed objects
|
|
590
|
+
const schema = z.object({
|
|
591
|
+
name: z.string(),
|
|
592
|
+
age: z.number(),
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const obj = new StructuredObject({
|
|
596
|
+
api: '/api/object',
|
|
597
|
+
schema, // Type automatically inferred
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// obj.object has type: { name?: string; age?: number } | undefined
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## Error Handling
|
|
604
|
+
|
|
605
|
+
All components provide reactive error states:
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
const chat = new Chat({
|
|
609
|
+
onError: (error) => {
|
|
610
|
+
console.error('Chat error:', error);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Template
|
|
615
|
+
@if (chat.error) {
|
|
616
|
+
<div class="error">{{ chat.error.message }}</div>
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Performance
|
|
621
|
+
|
|
622
|
+
### Stop on-going requests
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
chat.stop();
|
|
626
|
+
completion.stop();
|
|
627
|
+
structuredObject.stop();
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Change Detection
|
|
631
|
+
|
|
632
|
+
Uses Angular signals for efficient reactivity:
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
// These trigger minimal change detection
|
|
636
|
+
chat.messages; // Signal<UIMessage[]>
|
|
637
|
+
chat.status; // Signal<ChatStatus>
|
|
638
|
+
chat.error; // Signal<Error | undefined>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## License
|
|
642
|
+
|
|
643
|
+
Apache-2.0
|