@pillar-ai/angular 0.1.11
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/LICENSE +21 -0
- package/README.md +357 -0
- package/dist/LICENSE +21 -0
- package/dist/README.md +357 -0
- package/dist/esm2022/lib/inject-help-panel.mjs +59 -0
- package/dist/esm2022/lib/inject-pillar-tool.mjs +111 -0
- package/dist/esm2022/lib/inject-pillar.mjs +79 -0
- package/dist/esm2022/lib/pillar-panel.component.mjs +80 -0
- package/dist/esm2022/lib/pillar.service.mjs +308 -0
- package/dist/esm2022/lib/types.mjs +5 -0
- package/dist/esm2022/pillar-ai-angular.mjs +5 -0
- package/dist/esm2022/public-api.mjs +67 -0
- package/dist/fesm2022/pillar-ai-angular.mjs +695 -0
- package/dist/fesm2022/pillar-ai-angular.mjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/lib/inject-help-panel.d.ts +34 -0
- package/dist/lib/inject-pillar-tool.d.ts +89 -0
- package/dist/lib/inject-pillar.d.ts +96 -0
- package/dist/lib/pillar-panel.component.d.ts +58 -0
- package/dist/lib/pillar.service.d.ts +126 -0
- package/dist/lib/types.d.ts +119 -0
- package/dist/public-api.d.ts +77 -0
- package/package.json +73 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, NgZone, ApplicationRef, EnvironmentInjector, signal, computed, createComponent, Injectable, input, effect, ViewChild, ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
|
|
3
|
+
import { Pillar } from '@pillar-ai/sdk';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* PillarService
|
|
7
|
+
* Angular service that initializes and manages the Pillar SDK
|
|
8
|
+
*/
|
|
9
|
+
class PillarService {
|
|
10
|
+
ngZone = inject(NgZone);
|
|
11
|
+
appRef = inject(ApplicationRef);
|
|
12
|
+
environmentInjector = inject(EnvironmentInjector);
|
|
13
|
+
// Internal state
|
|
14
|
+
_pillar = signal(null);
|
|
15
|
+
cleanupFns = [];
|
|
16
|
+
cardRefs = new Map();
|
|
17
|
+
onTaskCallback = null;
|
|
18
|
+
registeredCards = null;
|
|
19
|
+
// Public signals
|
|
20
|
+
state = signal('uninitialized');
|
|
21
|
+
isReady = computed(() => this.state() === 'ready');
|
|
22
|
+
isPanelOpen = signal(false);
|
|
23
|
+
/**
|
|
24
|
+
* Get the Pillar SDK instance.
|
|
25
|
+
*/
|
|
26
|
+
getInstance() {
|
|
27
|
+
return this._pillar();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the Pillar SDK.
|
|
31
|
+
* Call this in your app's initialization (e.g., APP_INITIALIZER).
|
|
32
|
+
*
|
|
33
|
+
* @param initConfig - Configuration options
|
|
34
|
+
*/
|
|
35
|
+
async init(initConfig) {
|
|
36
|
+
const { productKey, helpCenter, config, onTask, cards } = initConfig;
|
|
37
|
+
// Support both productKey (new) and helpCenter (deprecated)
|
|
38
|
+
const resolvedKey = productKey ?? helpCenter;
|
|
39
|
+
if (helpCenter && !productKey) {
|
|
40
|
+
console.warn('[Pillar Angular] "helpCenter" is deprecated. Use "productKey" instead.');
|
|
41
|
+
}
|
|
42
|
+
// Store callbacks for later use
|
|
43
|
+
this.onTaskCallback = onTask ?? null;
|
|
44
|
+
this.registeredCards = cards ?? null;
|
|
45
|
+
try {
|
|
46
|
+
// Pillar is a singleton - check if already initialized
|
|
47
|
+
const existingInstance = Pillar.getInstance();
|
|
48
|
+
if (existingInstance) {
|
|
49
|
+
// Reuse existing instance (preserves chat history, panel state, etc.)
|
|
50
|
+
this._pillar.set(existingInstance);
|
|
51
|
+
this.state.set(existingInstance.state);
|
|
52
|
+
this.subscribeToEvents(existingInstance);
|
|
53
|
+
this.registerCards(existingInstance);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Initialize new instance
|
|
57
|
+
const instance = await Pillar.init({
|
|
58
|
+
productKey: resolvedKey,
|
|
59
|
+
...config,
|
|
60
|
+
});
|
|
61
|
+
this.ngZone.run(() => {
|
|
62
|
+
this._pillar.set(instance);
|
|
63
|
+
this.state.set(instance.state);
|
|
64
|
+
});
|
|
65
|
+
this.subscribeToEvents(instance);
|
|
66
|
+
this.registerCards(instance);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('[Pillar Angular] Failed to initialize:', error);
|
|
70
|
+
this.ngZone.run(() => {
|
|
71
|
+
this.state.set('error');
|
|
72
|
+
});
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to SDK events and sync to Angular signals.
|
|
78
|
+
*/
|
|
79
|
+
subscribeToEvents(instance) {
|
|
80
|
+
// Panel open/close events
|
|
81
|
+
const unsubOpen = instance.on('panel:open', () => {
|
|
82
|
+
this.ngZone.run(() => {
|
|
83
|
+
this.isPanelOpen.set(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
this.cleanupFns.push(unsubOpen);
|
|
87
|
+
const unsubClose = instance.on('panel:close', () => {
|
|
88
|
+
this.ngZone.run(() => {
|
|
89
|
+
this.isPanelOpen.set(false);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
this.cleanupFns.push(unsubClose);
|
|
93
|
+
// State change events
|
|
94
|
+
const unsubReady = instance.on('ready', () => {
|
|
95
|
+
this.ngZone.run(() => {
|
|
96
|
+
this.state.set('ready');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
this.cleanupFns.push(unsubReady);
|
|
100
|
+
const unsubError = instance.on('error', () => {
|
|
101
|
+
this.ngZone.run(() => {
|
|
102
|
+
this.state.set('error');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
this.cleanupFns.push(unsubError);
|
|
106
|
+
// Task execution events
|
|
107
|
+
if (this.onTaskCallback) {
|
|
108
|
+
const callback = this.onTaskCallback;
|
|
109
|
+
const unsubTask = instance.on('task:execute', (task) => {
|
|
110
|
+
this.ngZone.run(() => {
|
|
111
|
+
callback(task);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
this.cleanupFns.push(unsubTask);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Register custom card components.
|
|
119
|
+
*/
|
|
120
|
+
registerCards(instance) {
|
|
121
|
+
if (!this.registeredCards)
|
|
122
|
+
return;
|
|
123
|
+
Object.entries(this.registeredCards).forEach(([cardType, CardComponent]) => {
|
|
124
|
+
const unsubscribe = instance.registerCard(cardType, (container, data, callbacks) => {
|
|
125
|
+
// Create an Angular component dynamically
|
|
126
|
+
const componentRef = createComponent(CardComponent, {
|
|
127
|
+
environmentInjector: this.environmentInjector,
|
|
128
|
+
});
|
|
129
|
+
// Set inputs
|
|
130
|
+
componentRef.setInput('data', data);
|
|
131
|
+
componentRef.setInput('onConfirm', callbacks.onConfirm);
|
|
132
|
+
componentRef.setInput('onCancel', callbacks.onCancel);
|
|
133
|
+
componentRef.setInput('onStateChange', callbacks.onStateChange);
|
|
134
|
+
// Attach to the application
|
|
135
|
+
this.appRef.attachView(componentRef.hostView);
|
|
136
|
+
// Append to container
|
|
137
|
+
container.appendChild(componentRef.location.nativeElement);
|
|
138
|
+
this.cardRefs.set(container, componentRef);
|
|
139
|
+
// Return cleanup function
|
|
140
|
+
return () => {
|
|
141
|
+
const ref = this.cardRefs.get(container);
|
|
142
|
+
if (ref) {
|
|
143
|
+
this.appRef.detachView(ref.hostView);
|
|
144
|
+
ref.destroy();
|
|
145
|
+
this.cardRefs.delete(container);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
this.cleanupFns.push(unsubscribe);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Open the help panel.
|
|
154
|
+
*/
|
|
155
|
+
open(options) {
|
|
156
|
+
this._pillar()?.open(options);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Close the help panel.
|
|
160
|
+
*/
|
|
161
|
+
close() {
|
|
162
|
+
this._pillar()?.close();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Toggle the help panel.
|
|
166
|
+
*/
|
|
167
|
+
toggle() {
|
|
168
|
+
this._pillar()?.toggle();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Open a specific article.
|
|
172
|
+
*/
|
|
173
|
+
openArticle(slug) {
|
|
174
|
+
this._pillar()?.open({ article: slug });
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Open a specific category.
|
|
178
|
+
*/
|
|
179
|
+
async openCategory(slug) {
|
|
180
|
+
this._pillar()?.navigate('category', { slug });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Perform a search.
|
|
184
|
+
*/
|
|
185
|
+
search(query) {
|
|
186
|
+
this._pillar()?.open({ search: query });
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Navigate to a specific view.
|
|
190
|
+
*/
|
|
191
|
+
navigate(view, params) {
|
|
192
|
+
this._pillar()?.navigate(view, params);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Update the panel theme at runtime.
|
|
196
|
+
*/
|
|
197
|
+
setTheme(theme) {
|
|
198
|
+
this._pillar()?.setTheme(theme);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Enable or disable the text selection "Ask AI" popover.
|
|
202
|
+
*/
|
|
203
|
+
setTextSelectionEnabled(enabled) {
|
|
204
|
+
this._pillar()?.setTextSelectionEnabled(enabled);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Subscribe to SDK events.
|
|
208
|
+
*/
|
|
209
|
+
on(event, callback) {
|
|
210
|
+
const pillar = this._pillar();
|
|
211
|
+
if (!pillar) {
|
|
212
|
+
return () => { };
|
|
213
|
+
}
|
|
214
|
+
// Wrap callback to run in Angular zone
|
|
215
|
+
const wrappedCallback = (data) => {
|
|
216
|
+
this.ngZone.run(() => callback(data));
|
|
217
|
+
};
|
|
218
|
+
return pillar.on(event, wrappedCallback);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Register a task handler.
|
|
222
|
+
*/
|
|
223
|
+
onTask(taskName, handler) {
|
|
224
|
+
const pillar = this._pillar();
|
|
225
|
+
if (!pillar) {
|
|
226
|
+
return () => { };
|
|
227
|
+
}
|
|
228
|
+
// Wrap handler to run in Angular zone
|
|
229
|
+
const wrappedHandler = (data) => {
|
|
230
|
+
this.ngZone.run(() => handler(data));
|
|
231
|
+
};
|
|
232
|
+
return pillar.onTask(taskName, wrappedHandler);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Define a tool with metadata and handler.
|
|
236
|
+
* Returns an unsubscribe function to remove the tool.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```typescript
|
|
240
|
+
* const unsubscribe = pillarService.defineTool({
|
|
241
|
+
* name: 'add_to_cart',
|
|
242
|
+
* description: 'Add a product to the shopping cart',
|
|
243
|
+
* inputSchema: {
|
|
244
|
+
* type: 'object',
|
|
245
|
+
* properties: {
|
|
246
|
+
* productId: { type: 'string' },
|
|
247
|
+
* quantity: { type: 'number' },
|
|
248
|
+
* },
|
|
249
|
+
* required: ['productId', 'quantity'],
|
|
250
|
+
* },
|
|
251
|
+
* execute: async ({ productId, quantity }) => {
|
|
252
|
+
* await this.cartService.add(productId, quantity);
|
|
253
|
+
* return { content: [{ type: 'text', text: 'Added to cart' }] };
|
|
254
|
+
* },
|
|
255
|
+
* });
|
|
256
|
+
*
|
|
257
|
+
* // Later, to unregister:
|
|
258
|
+
* unsubscribe();
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
262
|
+
defineTool(schema) {
|
|
263
|
+
const pillar = this._pillar();
|
|
264
|
+
if (!pillar) {
|
|
265
|
+
console.warn('[Pillar Angular] Cannot define tool - SDK not initialized');
|
|
266
|
+
return () => { };
|
|
267
|
+
}
|
|
268
|
+
// Wrap execute to run in Angular zone
|
|
269
|
+
const wrappedSchema = {
|
|
270
|
+
...schema,
|
|
271
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
272
|
+
execute: async (input) => {
|
|
273
|
+
return this.ngZone.run(() => schema.execute(input));
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
return pillar.defineTool(wrappedSchema);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Mount the panel to a specific container element.
|
|
280
|
+
* Used for manual panel placement with PillarPanelComponent.
|
|
281
|
+
*/
|
|
282
|
+
mountPanelTo(container) {
|
|
283
|
+
this._pillar()?.mountPanelTo(container);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Cleanup resources.
|
|
287
|
+
* Note: We intentionally don't call Pillar.destroy() here to preserve
|
|
288
|
+
* conversation history across route changes. Call Pillar.destroy()
|
|
289
|
+
* explicitly if you need to fully reset the SDK.
|
|
290
|
+
*/
|
|
291
|
+
ngOnDestroy() {
|
|
292
|
+
// Run all cleanup functions
|
|
293
|
+
this.cleanupFns.forEach((cleanup) => cleanup());
|
|
294
|
+
this.cleanupFns.length = 0;
|
|
295
|
+
// Destroy all card component refs
|
|
296
|
+
this.cardRefs.forEach((ref) => {
|
|
297
|
+
this.appRef.detachView(ref.hostView);
|
|
298
|
+
ref.destroy();
|
|
299
|
+
});
|
|
300
|
+
this.cardRefs.clear();
|
|
301
|
+
}
|
|
302
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PillarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
303
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PillarService, providedIn: 'root' });
|
|
304
|
+
}
|
|
305
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PillarService, decorators: [{
|
|
306
|
+
type: Injectable,
|
|
307
|
+
args: [{ providedIn: 'root' }]
|
|
308
|
+
}] });
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* PillarPanelComponent
|
|
312
|
+
* Renders the Pillar help panel at a custom location in the DOM
|
|
313
|
+
*/
|
|
314
|
+
/**
|
|
315
|
+
* Renders the Pillar help panel at a custom location in the DOM.
|
|
316
|
+
* Use this when you want to control where the panel is rendered instead of
|
|
317
|
+
* having it automatically appended to document.body.
|
|
318
|
+
*
|
|
319
|
+
* **Important**: When using this component, set `panel.container: 'manual'` in your
|
|
320
|
+
* Pillar configuration to prevent automatic mounting.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* // app.config.ts
|
|
325
|
+
* function initPillar() {
|
|
326
|
+
* const pillar = inject(PillarService);
|
|
327
|
+
* return () => pillar.init({
|
|
328
|
+
* productKey: 'your-product-key',
|
|
329
|
+
* config: { panel: { container: 'manual' } }
|
|
330
|
+
* });
|
|
331
|
+
* }
|
|
332
|
+
*
|
|
333
|
+
* // layout.component.ts
|
|
334
|
+
* @Component({
|
|
335
|
+
* selector: 'app-layout',
|
|
336
|
+
* standalone: true,
|
|
337
|
+
* imports: [PillarPanelComponent],
|
|
338
|
+
* template: `
|
|
339
|
+
* <div class="layout">
|
|
340
|
+
* <app-sidebar />
|
|
341
|
+
* <pillar-panel class="help-panel-container" />
|
|
342
|
+
* <main>
|
|
343
|
+
* <router-outlet />
|
|
344
|
+
* </main>
|
|
345
|
+
* </div>
|
|
346
|
+
* `,
|
|
347
|
+
* })
|
|
348
|
+
* export class LayoutComponent {}
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
class PillarPanelComponent {
|
|
352
|
+
containerRef;
|
|
353
|
+
pillarService = inject(PillarService);
|
|
354
|
+
hasMounted = false;
|
|
355
|
+
effectRef = null;
|
|
356
|
+
/**
|
|
357
|
+
* Optional class to add to the container element.
|
|
358
|
+
* Use host binding for styling instead if possible.
|
|
359
|
+
*/
|
|
360
|
+
containerClass = input('');
|
|
361
|
+
ngAfterViewInit() {
|
|
362
|
+
// Use effect to react to isReady signal changes
|
|
363
|
+
this.effectRef = effect(() => {
|
|
364
|
+
const isReady = this.pillarService.isReady();
|
|
365
|
+
if (isReady && !this.hasMounted && this.containerRef?.nativeElement) {
|
|
366
|
+
this.pillarService.mountPanelTo(this.containerRef.nativeElement);
|
|
367
|
+
this.hasMounted = true;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
ngOnDestroy() {
|
|
372
|
+
// Effect is automatically cleaned up when component is destroyed
|
|
373
|
+
// Panel cleanup is handled by PillarService
|
|
374
|
+
this.effectRef?.destroy();
|
|
375
|
+
}
|
|
376
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PillarPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
377
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.12", type: PillarPanelComponent, isStandalone: true, selector: "pillar-panel", inputs: { containerClass: { classPropertyName: "containerClass", publicName: "containerClass", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], ngImport: i0, template: '<div #container data-pillar-panel-container></div>', isInline: true, styles: [":host{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
378
|
+
}
|
|
379
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PillarPanelComponent, decorators: [{
|
|
380
|
+
type: Component,
|
|
381
|
+
args: [{ selector: 'pillar-panel', standalone: true, template: '<div #container data-pillar-panel-container></div>', changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}\n"] }]
|
|
382
|
+
}], propDecorators: { containerRef: [{
|
|
383
|
+
type: ViewChild,
|
|
384
|
+
args: ['container', { static: true }]
|
|
385
|
+
}] } });
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* injectPillar Function
|
|
389
|
+
* Angular-idiomatic injection helper for accessing Pillar SDK
|
|
390
|
+
*/
|
|
391
|
+
/**
|
|
392
|
+
* Angular injection function to access the Pillar SDK.
|
|
393
|
+
* Use this in components, directives, or services to interact with Pillar.
|
|
394
|
+
*
|
|
395
|
+
* Must be called within an injection context (constructor, field initializer, or inject()).
|
|
396
|
+
*
|
|
397
|
+
* @example Basic usage (untyped)
|
|
398
|
+
* ```typescript
|
|
399
|
+
* @Component({
|
|
400
|
+
* selector: 'app-help-button',
|
|
401
|
+
* standalone: true,
|
|
402
|
+
* template: `
|
|
403
|
+
* <button (click)="toggle()">
|
|
404
|
+
* {{ isPanelOpen() ? 'Close Help' : 'Get Help' }}
|
|
405
|
+
* </button>
|
|
406
|
+
* `,
|
|
407
|
+
* })
|
|
408
|
+
* export class HelpButtonComponent {
|
|
409
|
+
* private pillar = injectPillar();
|
|
410
|
+
* isPanelOpen = this.pillar.isPanelOpen;
|
|
411
|
+
* toggle = this.pillar.toggle;
|
|
412
|
+
* }
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* @example Type-safe onTask with action definitions
|
|
416
|
+
* ```typescript
|
|
417
|
+
* import { actions } from '@/lib/pillar/actions';
|
|
418
|
+
*
|
|
419
|
+
* @Component({...})
|
|
420
|
+
* export class MyComponent implements OnInit, OnDestroy {
|
|
421
|
+
* private pillar = injectPillar<typeof actions>();
|
|
422
|
+
* private unsubscribe?: () => void;
|
|
423
|
+
*
|
|
424
|
+
* ngOnInit() {
|
|
425
|
+
* // TypeScript knows data has the correct shape
|
|
426
|
+
* this.unsubscribe = this.pillar.onTask('add_new_source', (data) => {
|
|
427
|
+
* console.log(data.url); // ✓ Typed!
|
|
428
|
+
* });
|
|
429
|
+
* }
|
|
430
|
+
*
|
|
431
|
+
* ngOnDestroy() {
|
|
432
|
+
* this.unsubscribe?.();
|
|
433
|
+
* }
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
function injectPillar() {
|
|
438
|
+
const service = inject(PillarService);
|
|
439
|
+
// Create a type-safe wrapper around pillar.onTask
|
|
440
|
+
const onTask = (taskName, handler) => {
|
|
441
|
+
// Cast handler to match the SDK's expected type
|
|
442
|
+
// The runtime behavior is the same, this is just for type narrowing
|
|
443
|
+
return service.onTask(taskName, handler);
|
|
444
|
+
};
|
|
445
|
+
return {
|
|
446
|
+
pillar: () => service.getInstance(),
|
|
447
|
+
state: service.state,
|
|
448
|
+
isReady: service.isReady,
|
|
449
|
+
isPanelOpen: service.isPanelOpen,
|
|
450
|
+
open: service.open.bind(service),
|
|
451
|
+
close: service.close.bind(service),
|
|
452
|
+
toggle: service.toggle.bind(service),
|
|
453
|
+
openArticle: service.openArticle.bind(service),
|
|
454
|
+
openCategory: service.openCategory.bind(service),
|
|
455
|
+
search: service.search.bind(service),
|
|
456
|
+
navigate: service.navigate.bind(service),
|
|
457
|
+
setTheme: service.setTheme.bind(service),
|
|
458
|
+
setTextSelectionEnabled: service.setTextSelectionEnabled.bind(service),
|
|
459
|
+
on: service.on.bind(service),
|
|
460
|
+
onTask,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* injectHelpPanel Function
|
|
466
|
+
* Angular injection helper for panel-specific controls
|
|
467
|
+
*/
|
|
468
|
+
/**
|
|
469
|
+
* Angular injection function for panel-specific controls.
|
|
470
|
+
* Provides a simplified API focused on panel operations.
|
|
471
|
+
*
|
|
472
|
+
* Must be called within an injection context (constructor, field initializer, or inject()).
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* @Component({
|
|
477
|
+
* selector: 'app-help-menu',
|
|
478
|
+
* standalone: true,
|
|
479
|
+
* template: `
|
|
480
|
+
* <div>
|
|
481
|
+
* <button (click)="toggle()">
|
|
482
|
+
* {{ isOpen() ? 'Close' : 'Help' }}
|
|
483
|
+
* </button>
|
|
484
|
+
* <button (click)="openChat()">Ask AI</button>
|
|
485
|
+
* </div>
|
|
486
|
+
* `,
|
|
487
|
+
* })
|
|
488
|
+
* export class HelpMenuComponent {
|
|
489
|
+
* private panel = injectHelpPanel();
|
|
490
|
+
* isOpen = this.panel.isOpen;
|
|
491
|
+
* toggle = this.panel.toggle;
|
|
492
|
+
* openChat = this.panel.openChat;
|
|
493
|
+
* }
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
function injectHelpPanel() {
|
|
497
|
+
const service = inject(PillarService);
|
|
498
|
+
const openSearch = (query) => {
|
|
499
|
+
if (query) {
|
|
500
|
+
service.search(query);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
service.open({ view: 'search' });
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
const openChat = () => {
|
|
507
|
+
service.navigate('chat');
|
|
508
|
+
};
|
|
509
|
+
return {
|
|
510
|
+
isOpen: computed(() => service.isPanelOpen()),
|
|
511
|
+
open: service.open.bind(service),
|
|
512
|
+
close: service.close.bind(service),
|
|
513
|
+
toggle: service.toggle.bind(service),
|
|
514
|
+
openArticle: service.openArticle.bind(service),
|
|
515
|
+
openCategory: service.openCategory.bind(service),
|
|
516
|
+
openSearch,
|
|
517
|
+
openChat,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* injectPillarTool Function
|
|
523
|
+
* Angular-idiomatic injection helper for registering Pillar tools
|
|
524
|
+
*
|
|
525
|
+
* Register one or more tools with co-located metadata and handlers.
|
|
526
|
+
* Tools are registered when called and automatically unregistered
|
|
527
|
+
* when the component is destroyed.
|
|
528
|
+
*
|
|
529
|
+
* @example Single tool
|
|
530
|
+
* ```typescript
|
|
531
|
+
* import { Component } from '@angular/core';
|
|
532
|
+
* import { injectPillarTool } from '@pillar-ai/angular';
|
|
533
|
+
*
|
|
534
|
+
* @Component({
|
|
535
|
+
* selector: 'app-cart-button',
|
|
536
|
+
* standalone: true,
|
|
537
|
+
* template: `<button>Cart</button>`,
|
|
538
|
+
* })
|
|
539
|
+
* export class CartButtonComponent {
|
|
540
|
+
* constructor() {
|
|
541
|
+
* injectPillarTool({
|
|
542
|
+
* name: 'add_to_cart',
|
|
543
|
+
* description: 'Add a product to the shopping cart',
|
|
544
|
+
* inputSchema: {
|
|
545
|
+
* type: 'object',
|
|
546
|
+
* properties: {
|
|
547
|
+
* productId: { type: 'string', description: 'Product ID' },
|
|
548
|
+
* quantity: { type: 'number', description: 'Quantity to add' },
|
|
549
|
+
* },
|
|
550
|
+
* required: ['productId', 'quantity'],
|
|
551
|
+
* },
|
|
552
|
+
* execute: async ({ productId, quantity }) => {
|
|
553
|
+
* await cartApi.add(productId, quantity);
|
|
554
|
+
* return { content: [{ type: 'text', text: 'Added to cart' }] };
|
|
555
|
+
* },
|
|
556
|
+
* });
|
|
557
|
+
* }
|
|
558
|
+
* }
|
|
559
|
+
* ```
|
|
560
|
+
*
|
|
561
|
+
* @example Multiple tools
|
|
562
|
+
* ```typescript
|
|
563
|
+
* import { Component } from '@angular/core';
|
|
564
|
+
* import { injectPillarTool } from '@pillar-ai/angular';
|
|
565
|
+
*
|
|
566
|
+
* @Component({
|
|
567
|
+
* selector: 'app-billing-page',
|
|
568
|
+
* standalone: true,
|
|
569
|
+
* template: `<div>Billing Content</div>`,
|
|
570
|
+
* })
|
|
571
|
+
* export class BillingPageComponent {
|
|
572
|
+
* constructor() {
|
|
573
|
+
* injectPillarTool([
|
|
574
|
+
* {
|
|
575
|
+
* name: 'get_current_plan',
|
|
576
|
+
* description: 'Get the current billing plan',
|
|
577
|
+
* execute: async () => ({ plan: 'pro', price: 29 }),
|
|
578
|
+
* },
|
|
579
|
+
* {
|
|
580
|
+
* name: 'upgrade_plan',
|
|
581
|
+
* description: 'Upgrade to a higher plan',
|
|
582
|
+
* inputSchema: {
|
|
583
|
+
* type: 'object',
|
|
584
|
+
* properties: { planId: { type: 'string' } },
|
|
585
|
+
* required: ['planId'],
|
|
586
|
+
* },
|
|
587
|
+
* execute: async ({ planId }) => {
|
|
588
|
+
* await billingApi.upgrade(planId);
|
|
589
|
+
* return { content: [{ type: 'text', text: 'Upgraded!' }] };
|
|
590
|
+
* },
|
|
591
|
+
* },
|
|
592
|
+
* ]);
|
|
593
|
+
* }
|
|
594
|
+
* }
|
|
595
|
+
* ```
|
|
596
|
+
*/
|
|
597
|
+
/**
|
|
598
|
+
* Register one or more Pillar tools with co-located metadata and handlers.
|
|
599
|
+
*
|
|
600
|
+
* The tools are registered when this function is called and automatically
|
|
601
|
+
* unregistered when the component is destroyed. Must be called within
|
|
602
|
+
* an injection context (constructor, field initializer, or inject()).
|
|
603
|
+
*
|
|
604
|
+
* @param schemaOrSchemas - Single tool schema or array of tool schemas
|
|
605
|
+
*/
|
|
606
|
+
function injectPillarTool(
|
|
607
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
608
|
+
schemaOrSchemas) {
|
|
609
|
+
const service = inject(PillarService);
|
|
610
|
+
const destroyRef = inject(DestroyRef);
|
|
611
|
+
// Normalize to array for consistent handling
|
|
612
|
+
const schemas = Array.isArray(schemaOrSchemas)
|
|
613
|
+
? schemaOrSchemas
|
|
614
|
+
: [schemaOrSchemas];
|
|
615
|
+
// Track unsubscribe functions
|
|
616
|
+
const unsubscribes = [];
|
|
617
|
+
// Register all tools
|
|
618
|
+
schemas.forEach((schema) => {
|
|
619
|
+
const unsub = service.defineTool(schema);
|
|
620
|
+
unsubscribes.push(unsub);
|
|
621
|
+
});
|
|
622
|
+
// Cleanup on destroy
|
|
623
|
+
destroyRef.onDestroy(() => {
|
|
624
|
+
unsubscribes.forEach((unsub) => unsub());
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/** @deprecated Use injectPillarTool instead */
|
|
628
|
+
const injectPillarAction = injectPillarTool;
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* @pillar-ai/angular - Angular bindings for Pillar SDK
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```typescript
|
|
635
|
+
* // app.config.ts
|
|
636
|
+
* import { ApplicationConfig, APP_INITIALIZER, inject } from '@angular/core';
|
|
637
|
+
* import { PillarService } from '@pillar-ai/angular';
|
|
638
|
+
*
|
|
639
|
+
* function initPillar() {
|
|
640
|
+
* const pillar = inject(PillarService);
|
|
641
|
+
* return () => pillar.init({ productKey: 'your-product-key' });
|
|
642
|
+
* }
|
|
643
|
+
*
|
|
644
|
+
* export const appConfig: ApplicationConfig = {
|
|
645
|
+
* providers: [
|
|
646
|
+
* { provide: APP_INITIALIZER, useFactory: initPillar, multi: true },
|
|
647
|
+
* ],
|
|
648
|
+
* };
|
|
649
|
+
*
|
|
650
|
+
* // help-button.component.ts
|
|
651
|
+
* import { Component } from '@angular/core';
|
|
652
|
+
* import { injectPillar } from '@pillar-ai/angular';
|
|
653
|
+
*
|
|
654
|
+
* @Component({
|
|
655
|
+
* selector: 'app-help-button',
|
|
656
|
+
* standalone: true,
|
|
657
|
+
* template: `
|
|
658
|
+
* <button (click)="toggle()">
|
|
659
|
+
* {{ isPanelOpen() ? 'Close Help' : 'Get Help' }}
|
|
660
|
+
* </button>
|
|
661
|
+
* `,
|
|
662
|
+
* })
|
|
663
|
+
* export class HelpButtonComponent {
|
|
664
|
+
* private pillar = injectPillar();
|
|
665
|
+
* isPanelOpen = this.pillar.isPanelOpen;
|
|
666
|
+
* toggle = this.pillar.toggle;
|
|
667
|
+
* }
|
|
668
|
+
*
|
|
669
|
+
* // Custom panel placement example:
|
|
670
|
+
* // layout.component.ts
|
|
671
|
+
* import { Component } from '@angular/core';
|
|
672
|
+
* import { PillarPanelComponent } from '@pillar-ai/angular';
|
|
673
|
+
*
|
|
674
|
+
* @Component({
|
|
675
|
+
* selector: 'app-layout',
|
|
676
|
+
* standalone: true,
|
|
677
|
+
* imports: [PillarPanelComponent],
|
|
678
|
+
* template: `
|
|
679
|
+
* <div class="layout">
|
|
680
|
+
* <pillar-panel class="custom-panel" />
|
|
681
|
+
* <main>Your content</main>
|
|
682
|
+
* </div>
|
|
683
|
+
* `,
|
|
684
|
+
* })
|
|
685
|
+
* export class LayoutComponent {}
|
|
686
|
+
* ```
|
|
687
|
+
*/
|
|
688
|
+
// Service
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Generated bundle index. Do not edit.
|
|
692
|
+
*/
|
|
693
|
+
|
|
694
|
+
export { PillarPanelComponent, PillarService, injectHelpPanel, injectPillar, injectPillarAction, injectPillarTool };
|
|
695
|
+
//# sourceMappingURL=pillar-ai-angular.mjs.map
|