@c8y/tutorial 1023.0.0 → 1023.4.1
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/cumulocity.config.ts
CHANGED
|
@@ -885,6 +885,13 @@ export default {
|
|
|
885
885
|
description: 'This is an example for the bottom drawer service.',
|
|
886
886
|
scope: 'self'
|
|
887
887
|
},
|
|
888
|
+
{
|
|
889
|
+
name: 'AI Components',
|
|
890
|
+
module: 'aiComponentsProviders',
|
|
891
|
+
path: './src/ai/ai-components.providers.ts',
|
|
892
|
+
description: 'Showcasing the most common AI components.',
|
|
893
|
+
scope: 'self'
|
|
894
|
+
},
|
|
888
895
|
{
|
|
889
896
|
name: 'Computed asset properties',
|
|
890
897
|
module: 'computedAssetPropertiesProviders',
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1023.
|
|
3
|
+
"version": "1023.4.1",
|
|
4
4
|
"description": "This package is used to scaffold a tutorial for Cumulocity IoT Web SDK which explains a lot of concepts.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@c8y/style": "1023.
|
|
7
|
-
"@c8y/ngx-components": "1023.
|
|
8
|
-
"@c8y/client": "1023.
|
|
9
|
-
"@c8y/bootstrap": "1023.
|
|
6
|
+
"@c8y/style": "1023.4.1",
|
|
7
|
+
"@c8y/ngx-components": "1023.4.1",
|
|
8
|
+
"@c8y/client": "1023.4.1",
|
|
9
|
+
"@c8y/bootstrap": "1023.4.1",
|
|
10
10
|
"@angular/cdk": "^20.2.7",
|
|
11
11
|
"monaco-editor": "~0.53.0",
|
|
12
12
|
"ngx-bootstrap": "20.0.2",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"rxjs": "7.8.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@c8y/options": "1023.
|
|
18
|
-
"@c8y/devkit": "1023.
|
|
17
|
+
"@c8y/options": "1023.4.1",
|
|
18
|
+
"@c8y/devkit": "1023.4.1"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@angular/common": ">=20 <21"
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<c8y-title>Chat component example</c8y-title>
|
|
2
|
+
<div class="row d-flex-sm">
|
|
3
|
+
<div class="col-md-4 p-0">
|
|
4
|
+
<div class="d-col p-16 form-group-sm bg-level-1">
|
|
5
|
+
<p class="text-medium p-b-16">Config options</p>
|
|
6
|
+
|
|
7
|
+
<label>title</label>
|
|
8
|
+
<div class="input-group input-group-sm m-b-8">
|
|
9
|
+
<input
|
|
10
|
+
class="form-control"
|
|
11
|
+
type="text"
|
|
12
|
+
[(ngModel)]="config.title"
|
|
13
|
+
/>
|
|
14
|
+
<div class="input-group-btn">
|
|
15
|
+
<button
|
|
16
|
+
class="btn btn-default"
|
|
17
|
+
(click)="reloadComponent()"
|
|
18
|
+
>
|
|
19
|
+
{{ 'Apply' | translate }}
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<label>headline</label>
|
|
25
|
+
<div class="input-group input-group-sm m-b-8">
|
|
26
|
+
<input
|
|
27
|
+
class="form-control"
|
|
28
|
+
type="text"
|
|
29
|
+
[(ngModel)]="config.headline"
|
|
30
|
+
/>
|
|
31
|
+
<div class="input-group-btn">
|
|
32
|
+
<button
|
|
33
|
+
class="btn btn-default"
|
|
34
|
+
(click)="reloadComponent()"
|
|
35
|
+
>
|
|
36
|
+
{{ 'Apply' | translate }}
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<label>welcomeText</label>
|
|
42
|
+
<div class="input-group input-group-sm m-b-8">
|
|
43
|
+
<input
|
|
44
|
+
class="form-control"
|
|
45
|
+
type="text"
|
|
46
|
+
[(ngModel)]="config.welcomeText"
|
|
47
|
+
/>
|
|
48
|
+
<div class="input-group-btn">
|
|
49
|
+
<button
|
|
50
|
+
class="btn btn-default"
|
|
51
|
+
(click)="reloadComponent()"
|
|
52
|
+
>
|
|
53
|
+
{{ 'Apply' | translate }}
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<label>placeholder</label>
|
|
59
|
+
<div class="input-group input-group-sm m-b-8">
|
|
60
|
+
<input
|
|
61
|
+
class="form-control"
|
|
62
|
+
type="text"
|
|
63
|
+
[(ngModel)]="config.placeholder"
|
|
64
|
+
/>
|
|
65
|
+
<div class="input-group-btn">
|
|
66
|
+
<button
|
|
67
|
+
class="btn btn-default"
|
|
68
|
+
(click)="reloadComponent()"
|
|
69
|
+
>
|
|
70
|
+
{{ 'Apply' | translate }}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<label>sendButtonText (displayed as a tooltip)</label>
|
|
76
|
+
<div class="input-group input-group-sm m-b-8">
|
|
77
|
+
<input
|
|
78
|
+
class="form-control"
|
|
79
|
+
type="text"
|
|
80
|
+
[(ngModel)]="config.sendButtonText"
|
|
81
|
+
/>
|
|
82
|
+
<div class="input-group-btn">
|
|
83
|
+
<button
|
|
84
|
+
class="btn btn-default"
|
|
85
|
+
(click)="reloadComponent()"
|
|
86
|
+
>
|
|
87
|
+
{{ 'Apply' | translate }}
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<label>cancelButtonText (displayed as a tooltip)</label>
|
|
93
|
+
<div class="input-group input-group-sm m-b-8">
|
|
94
|
+
<input
|
|
95
|
+
class="form-control"
|
|
96
|
+
type="text"
|
|
97
|
+
[(ngModel)]="config.cancelButtonText"
|
|
98
|
+
/>
|
|
99
|
+
<div class="input-group-btn">
|
|
100
|
+
<button
|
|
101
|
+
class="btn btn-default"
|
|
102
|
+
(click)="reloadComponent()"
|
|
103
|
+
>
|
|
104
|
+
{{ 'Apply' | translate }}
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<label>disclaimerText</label>
|
|
110
|
+
<div class="input-group input-group-sm m-b-8">
|
|
111
|
+
<input
|
|
112
|
+
class="form-control"
|
|
113
|
+
type="text"
|
|
114
|
+
[(ngModel)]="config.disclaimerText"
|
|
115
|
+
/>
|
|
116
|
+
<div class="input-group-btn">
|
|
117
|
+
<button
|
|
118
|
+
class="btn btn-default"
|
|
119
|
+
(click)="reloadComponent()"
|
|
120
|
+
>
|
|
121
|
+
{{ 'Apply' | translate }}
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<pre class="m-t-16 inner-scroll small">{{ config | json }}</pre>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="col-md-8">
|
|
130
|
+
@if (showComponent) {
|
|
131
|
+
<label
|
|
132
|
+
class="c8y-switch m-t-8"
|
|
133
|
+
title="showInitialMessage"
|
|
134
|
+
>
|
|
135
|
+
<input
|
|
136
|
+
class="form-control"
|
|
137
|
+
type="checkbox"
|
|
138
|
+
[(ngModel)]="showInitialMessage"
|
|
139
|
+
(ngModelChange)="reloadComponent()"
|
|
140
|
+
/>
|
|
141
|
+
<span></span>
|
|
142
|
+
<span>Show example message with content projection</span>
|
|
143
|
+
</label>
|
|
144
|
+
<div
|
|
145
|
+
class="card m-t-16"
|
|
146
|
+
style="height: 530px"
|
|
147
|
+
>
|
|
148
|
+
<c8y-ai-chat
|
|
149
|
+
[config]="config"
|
|
150
|
+
(onMessage)="sendMessage($event)"
|
|
151
|
+
[isLoading]="isLoading"
|
|
152
|
+
>
|
|
153
|
+
@if (showInitialMessage) {
|
|
154
|
+
<c8y-ai-chat-message>
|
|
155
|
+
You can also use content projection to add any dynamic content to the chat bubble.
|
|
156
|
+
<div class="d-flex justify-content-end">
|
|
157
|
+
<button class="btn btn-default btn-sm m-t-8">Get device details</button>
|
|
158
|
+
<button class="btn btn-default btn-sm m-t-8 m-l-4">Get all measurements</button>
|
|
159
|
+
</div>
|
|
160
|
+
<c8y-ai-chat-message-action
|
|
161
|
+
icon="rocket"
|
|
162
|
+
tooltip="Get device details"
|
|
163
|
+
></c8y-ai-chat-message-action>
|
|
164
|
+
</c8y-ai-chat-message>
|
|
165
|
+
}
|
|
166
|
+
@for (message of displayMessages; track message) {
|
|
167
|
+
<c8y-ai-chat-message [message]="message">
|
|
168
|
+
@if (message.role === 'assistant') {
|
|
169
|
+
<c8y-ai-chat-message-action
|
|
170
|
+
icon="thumbs-up"
|
|
171
|
+
tooltip="This is useful"
|
|
172
|
+
></c8y-ai-chat-message-action>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@if (message.role === 'assistant') {
|
|
176
|
+
<c8y-ai-chat-message-action
|
|
177
|
+
icon="thumbs-down"
|
|
178
|
+
tooltip="This is not useful"
|
|
179
|
+
></c8y-ai-chat-message-action>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@if (message.role === 'assistant') {
|
|
183
|
+
<c8y-ai-chat-message-action
|
|
184
|
+
icon="undo"
|
|
185
|
+
tooltip="Revert"
|
|
186
|
+
(click)="revertMessage(message)"
|
|
187
|
+
></c8y-ai-chat-message-action>
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@if (message.role === 'user') {
|
|
191
|
+
<c8y-ai-chat-message-action
|
|
192
|
+
icon="edit1"
|
|
193
|
+
tooltip="Edit"
|
|
194
|
+
></c8y-ai-chat-message-action>
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@if (message.role === 'user') {
|
|
198
|
+
<c8y-ai-chat-message-action
|
|
199
|
+
icon="clone"
|
|
200
|
+
tooltip="Copy"
|
|
201
|
+
></c8y-ai-chat-message-action>
|
|
202
|
+
}
|
|
203
|
+
</c8y-ai-chat-message>
|
|
204
|
+
}
|
|
205
|
+
@if (isLoading) {
|
|
206
|
+
<c8y-ai-chat-message>
|
|
207
|
+
<c8y-loading [message]="'Loading...'"></c8y-loading>
|
|
208
|
+
</c8y-ai-chat-message>
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
<c8y-ai-chat-suggestion
|
|
212
|
+
[label]="'Create device '"
|
|
213
|
+
[prompt]="'AHOI, create a new device'"
|
|
214
|
+
(suggestionClicked)="sendMessage($event)"
|
|
215
|
+
></c8y-ai-chat-suggestion>
|
|
216
|
+
|
|
217
|
+
<c8y-ai-chat-suggestion
|
|
218
|
+
[icon]="'forward'"
|
|
219
|
+
[label]="'Mix and match'"
|
|
220
|
+
[useAiButtons]="true"
|
|
221
|
+
[prompt]="'Create a device with a measurement and an event'"
|
|
222
|
+
(suggestionClicked)="sendMessage($event)"
|
|
223
|
+
></c8y-ai-chat-suggestion>
|
|
224
|
+
</c8y-ai-chat>
|
|
225
|
+
</div>
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
<div class="p-16 bg-level-1">
|
|
229
|
+
<p class="text-medium">Show timestamps on chat messages</p>
|
|
230
|
+
<p class="text-muted text-12 m-b-16">
|
|
231
|
+
The option controls the AIMessage objects passed to the component, it's not part of the
|
|
232
|
+
configuration.
|
|
233
|
+
</p>
|
|
234
|
+
<label
|
|
235
|
+
class="c8y-checkbox m-t-8"
|
|
236
|
+
title="showTimestamps"
|
|
237
|
+
>
|
|
238
|
+
<input
|
|
239
|
+
class="form-control"
|
|
240
|
+
type="checkbox"
|
|
241
|
+
[(ngModel)]="showTimestamps"
|
|
242
|
+
(ngModelChange)="reloadComponent()"
|
|
243
|
+
/>
|
|
244
|
+
<span></span>
|
|
245
|
+
<span>showTimestamps</span>
|
|
246
|
+
</label>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { JsonPipe } from '@angular/common';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { CoreModule, LoadingComponent } from '@c8y/ngx-components';
|
|
5
|
+
import { AIMessage } from '@c8y/ngx-components/ai';
|
|
6
|
+
import {
|
|
7
|
+
AiChatComponent,
|
|
8
|
+
AiChatMessageActionComponent,
|
|
9
|
+
AiChatMessageComponent,
|
|
10
|
+
AiChatSuggestionComponent
|
|
11
|
+
} from '@c8y/ngx-components/ai/ai-chat';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'c8y-ai-chat-example',
|
|
15
|
+
templateUrl: './ai-chat-example.component.html',
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [
|
|
18
|
+
AiChatComponent,
|
|
19
|
+
AiChatMessageComponent,
|
|
20
|
+
AiChatMessageActionComponent,
|
|
21
|
+
AiChatSuggestionComponent,
|
|
22
|
+
LoadingComponent,
|
|
23
|
+
CoreModule,
|
|
24
|
+
FormsModule,
|
|
25
|
+
JsonPipe
|
|
26
|
+
]
|
|
27
|
+
})
|
|
28
|
+
export class ChatExampleComponent {
|
|
29
|
+
private _showTimestamps = false;
|
|
30
|
+
showInitialMessage = false;
|
|
31
|
+
|
|
32
|
+
get showTimestamps(): boolean {
|
|
33
|
+
return this._showTimestamps;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set showTimestamps(value: boolean) {
|
|
37
|
+
this._showTimestamps = value;
|
|
38
|
+
this.updateDisplayMessages();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
config = {
|
|
42
|
+
headline: 'Welcome!',
|
|
43
|
+
welcomeText: 'Type a message below to get started.',
|
|
44
|
+
title: 'What can I help you with?',
|
|
45
|
+
placeholder: 'Type your message here...',
|
|
46
|
+
sendButtonText: 'Send',
|
|
47
|
+
cancelButtonText: 'Cancel',
|
|
48
|
+
disclaimerText: 'AI-generated responses can contain errors. Verify the details before use.',
|
|
49
|
+
userInterfaceIcons: { send: 'arrow-circle-up', cancel: 'stop-circle' }
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
messages: AIMessage[] = [];
|
|
53
|
+
displayMessages: AIMessage[] = [];
|
|
54
|
+
showComponent = true;
|
|
55
|
+
|
|
56
|
+
isLoading = false;
|
|
57
|
+
|
|
58
|
+
async sendMessage(message: AIMessage): Promise<void> {
|
|
59
|
+
this.isLoading = true;
|
|
60
|
+
this.messages.push(message);
|
|
61
|
+
this.updateDisplayMessages();
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
// Simulate a delay for the response
|
|
64
|
+
this.messages.push({
|
|
65
|
+
content: 'Thank you for your message!',
|
|
66
|
+
role: 'assistant',
|
|
67
|
+
timestamp: new Date().toISOString()
|
|
68
|
+
});
|
|
69
|
+
this.updateDisplayMessages();
|
|
70
|
+
this.isLoading = false;
|
|
71
|
+
}, 1000);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
revertMessage(message: AIMessage) {
|
|
75
|
+
this.messages = this.messages.filter(m => m !== message);
|
|
76
|
+
this.updateDisplayMessages();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
reloadComponent() {
|
|
80
|
+
// Reload the component by toggling the flag
|
|
81
|
+
this.showComponent = false;
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
this.showComponent = true;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private updateDisplayMessages() {
|
|
88
|
+
this.displayMessages = this._showTimestamps
|
|
89
|
+
? this.messages
|
|
90
|
+
: this.messages.map(({ timestamp: _timestamp, ...msg }) => msg);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { hookNavigator, hookRoute, NavigatorNode } from '@c8y/ngx-components';
|
|
2
|
+
|
|
3
|
+
export const aiComponentsProviders = [
|
|
4
|
+
hookRoute({
|
|
5
|
+
path: 'ai/chat',
|
|
6
|
+
loadComponent: () => import('./ai-chat-example.component').then(m => m.ChatExampleComponent)
|
|
7
|
+
}),
|
|
8
|
+
hookNavigator(
|
|
9
|
+
new NavigatorNode({
|
|
10
|
+
priority: 75,
|
|
11
|
+
path: 'ai/chat',
|
|
12
|
+
icon: 'chat',
|
|
13
|
+
label: 'AI Chat',
|
|
14
|
+
parent: 'AI'
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
];
|