@theseam/ui-common 1.0.2-beta.57 → 1.0.2-beta.59

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.
@@ -1,6 +1,997 @@
1
- var publicApi = {};
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injector, Input, ChangeDetectionStrategy, Component, EventEmitter, Output, ChangeDetectorRef, NgZone, ViewChild } from '@angular/core';
3
+ import { NgForOf, NgIf, NgComponentOutlet, AsyncPipe, JsonPipe } from '@angular/common';
4
+ import { BehaviorSubject, switchMap, of, startWith, map, shareReplay } from 'rxjs';
5
+ import { TheSeamOverlayScrollbarDirective } from '@theseam/ui-common/scrollbar';
6
+ import { MarkdownComponent } from 'ngx-markdown';
7
+ import * as i1 from '@angular/forms';
8
+ import { FormControl, Validators, ReactiveFormsModule, FormGroup } from '@angular/forms';
9
+ import * as i2 from '@theseam/ui-common/rich-text';
10
+ import { TheSeamRichTextModule } from '@theseam/ui-common/rich-text';
11
+ import * as i4 from '@theseam/ui-common/form-field';
12
+ import { TheSeamFormFieldModule } from '@theseam/ui-common/form-field';
13
+ import * as i4$1 from '@theseam/ui-common/buttons';
14
+ import { TheSeamButtonsModule } from '@theseam/ui-common/buttons';
15
+ import { ComponentHarness } from '@angular/cdk/testing';
16
+ import { THESEAM_DATATABLE_PREFERENCES_ACCESSOR, DatatablePreferencesService, EMPTY_DATATABLE_PREFERENCES, mapColumnsAlterationsStates } from '@theseam/ui-common/datatable';
17
+ import * as i2$1 from '@theseam/ui-common/loading';
18
+ import { TheSeamLoadingModule } from '@theseam/ui-common/loading';
19
+ import { AlterationsDiffComponent } from '@theseam/ui-common/datatable-alterations-display';
20
+
21
+ class LmStudioAiProvider {
22
+ async chat(messages) {
23
+ const url = 'http://localhost:1234/v1/chat/completions';
24
+ const headers = {
25
+ 'Content-Type': 'application/json',
26
+ };
27
+ const model = 'model-identifier';
28
+ const response = await fetch(url, {
29
+ method: 'POST',
30
+ headers,
31
+ body: JSON.stringify({
32
+ model,
33
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
34
+ }),
35
+ });
36
+ const data = await response.json();
37
+ console.log('Response from AI:', data);
38
+ const content = data.choices[0].message.content;
39
+ console.log(`%cResponse from AI. content:\n${content}`, 'color: limegreen;');
40
+ return { content };
41
+ }
42
+ }
43
+
44
+ class OpenRouterAiProvider {
45
+ async chat(messages) {
46
+ const defaultApiKey = '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';
47
+ const url = 'https://openrouter.ai/api/v1/chat/completions';
48
+ const apiKey = localStorage.getItem('openrouter-api-key') || defaultApiKey;
49
+ const headers = {
50
+ Authorization: `Bearer ${apiKey}`,
51
+ 'Content-Type': 'application/json',
52
+ };
53
+ const model = 'google/gemini-2.5-flash';
54
+ const response = await fetch(url, {
55
+ method: 'POST',
56
+ headers,
57
+ body: JSON.stringify({
58
+ model,
59
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
60
+ response_format: { type: 'json_object' },
61
+ }),
62
+ });
63
+ const data = await response.json();
64
+ console.log('Response from AI:', data);
65
+ const content = data.choices[0].message.content;
66
+ console.log(`%cResponse from AI. content:\n${content}`, 'color: limegreen;');
67
+ return { content };
68
+ }
69
+ }
70
+
71
+ class MockAiProvider {
72
+ _response;
73
+ constructor(_response = 'Mock response') {
74
+ this._response = _response;
75
+ }
76
+ async chat(messages) {
77
+ const content = typeof this._response === 'function'
78
+ ? this._response(messages)
79
+ : this._response;
80
+ return { content };
81
+ }
82
+ }
83
+
84
+ const THESEAM_CHAT_PROVIDER = new InjectionToken('TheSeamChatProvider');
85
+
86
+ /**
87
+ * Splits a raw AI response string into an ordered array of segments.
88
+ * Fenced code blocks with `seam-` prefixed language tags become custom-block
89
+ * segments; everything else stays as markdown.
90
+ */
91
+ function parseChatResponse(input) {
92
+ if (!input) {
93
+ return [];
94
+ }
95
+ const segments = [];
96
+ const pattern = /^```(seam-[\w-]+)\n([\s\S]*?)^```/gm;
97
+ let lastIndex = 0;
98
+ let match;
99
+ while ((match = pattern.exec(input)) !== null) {
100
+ if (match.index > lastIndex) {
101
+ segments.push({
102
+ type: 'markdown',
103
+ content: input.slice(lastIndex, match.index),
104
+ });
105
+ }
106
+ segments.push({
107
+ type: 'custom-block',
108
+ tag: match[1],
109
+ content: match[2].replace(/\n$/, ''),
110
+ });
111
+ lastIndex = match.index + match[0].length;
112
+ }
113
+ if (lastIndex < input.length) {
114
+ segments.push({ type: 'markdown', content: input.slice(lastIndex) });
115
+ }
116
+ return segments;
117
+ }
118
+
119
+ const THESEAM_CHAT_BLOCK_REGISTRY = new InjectionToken('TheSeamChatBlockRegistry');
120
+
121
+ class SeamChatMessageComponent {
122
+ message;
123
+ _blockRegistry = inject(THESEAM_CHAT_BLOCK_REGISTRY, {
124
+ optional: true,
125
+ });
126
+ _injector = inject(Injector);
127
+ _getBlockComponent(tag) {
128
+ return this._blockRegistry?.get(tag) ?? null;
129
+ }
130
+ _createBlockInjector(content) {
131
+ return Injector.create({
132
+ providers: [{ provide: 'CHAT_BLOCK_CONTENT', useValue: content }],
133
+ parent: this._injector,
134
+ });
135
+ }
136
+ _buildFallbackMarkdown(tag, content) {
137
+ return '```' + tag + '\n' + content + '\n```';
138
+ }
139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SeamChatMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
140
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SeamChatMessageComponent, isStandalone: true, selector: "seam-chat-message", inputs: { message: "message" }, ngImport: i0, template: `
141
+ <div
142
+ class="seam-chat-message"
143
+ [class.seam-chat-message--user]="message.role === 'user'"
144
+ [class.seam-chat-message--assistant]="message.role === 'assistant'"
145
+ >
146
+ <div class="seam-chat-message__role">
147
+ {{ message.role === 'user' ? 'You' : 'Assistant' }}
148
+ </div>
149
+ <div class="seam-chat-message__content">
150
+ <ng-container *ngFor="let segment of message.segments">
151
+ <markdown
152
+ *ngIf="segment.type === 'markdown'"
153
+ [data]="segment.content"
154
+ ></markdown>
155
+ <ng-container *ngIf="segment.type === 'custom-block'">
156
+ <ng-container
157
+ *ngIf="
158
+ _getBlockComponent(segment.tag) as blockComponent;
159
+ else fallbackBlock
160
+ "
161
+ >
162
+ <ng-container
163
+ *ngComponentOutlet="
164
+ blockComponent;
165
+ injector: _createBlockInjector(segment.content)
166
+ "
167
+ ></ng-container>
168
+ </ng-container>
169
+ <ng-template #fallbackBlock>
170
+ <markdown
171
+ [data]="_buildFallbackMarkdown(segment.tag, segment.content)"
172
+ ></markdown>
173
+ </ng-template>
174
+ </ng-container>
175
+ </ng-container>
176
+ </div>
177
+ </div>
178
+ `, isInline: true, styles: [".seam-chat-message{display:flex;flex-direction:column;padding:8px 12px;margin-bottom:8px}.seam-chat-message--user{align-items:flex-end}.seam-chat-message--user .seam-chat-message__content{background-color:#e8f5e9;border-radius:8px;padding:8px 12px;max-width:80%}.seam-chat-message--assistant .seam-chat-message__content{background-color:#f1f3f5;border-radius:8px;padding:8px 12px;max-width:80%}.seam-chat-message__role{font-size:.75rem;color:#6c757d;margin-bottom:4px}\n"], dependencies: [{ kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
179
+ }
180
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SeamChatMessageComponent, decorators: [{
181
+ type: Component,
182
+ args: [{ selector: 'seam-chat-message', imports: [NgForOf, NgIf, MarkdownComponent, NgComponentOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: `
183
+ <div
184
+ class="seam-chat-message"
185
+ [class.seam-chat-message--user]="message.role === 'user'"
186
+ [class.seam-chat-message--assistant]="message.role === 'assistant'"
187
+ >
188
+ <div class="seam-chat-message__role">
189
+ {{ message.role === 'user' ? 'You' : 'Assistant' }}
190
+ </div>
191
+ <div class="seam-chat-message__content">
192
+ <ng-container *ngFor="let segment of message.segments">
193
+ <markdown
194
+ *ngIf="segment.type === 'markdown'"
195
+ [data]="segment.content"
196
+ ></markdown>
197
+ <ng-container *ngIf="segment.type === 'custom-block'">
198
+ <ng-container
199
+ *ngIf="
200
+ _getBlockComponent(segment.tag) as blockComponent;
201
+ else fallbackBlock
202
+ "
203
+ >
204
+ <ng-container
205
+ *ngComponentOutlet="
206
+ blockComponent;
207
+ injector: _createBlockInjector(segment.content)
208
+ "
209
+ ></ng-container>
210
+ </ng-container>
211
+ <ng-template #fallbackBlock>
212
+ <markdown
213
+ [data]="_buildFallbackMarkdown(segment.tag, segment.content)"
214
+ ></markdown>
215
+ </ng-template>
216
+ </ng-container>
217
+ </ng-container>
218
+ </div>
219
+ </div>
220
+ `, styles: [".seam-chat-message{display:flex;flex-direction:column;padding:8px 12px;margin-bottom:8px}.seam-chat-message--user{align-items:flex-end}.seam-chat-message--user .seam-chat-message__content{background-color:#e8f5e9;border-radius:8px;padding:8px 12px;max-width:80%}.seam-chat-message--assistant .seam-chat-message__content{background-color:#f1f3f5;border-radius:8px;padding:8px 12px;max-width:80%}.seam-chat-message__role{font-size:.75rem;color:#6c757d;margin-bottom:4px}\n"] }]
221
+ }], propDecorators: { message: [{
222
+ type: Input,
223
+ args: [{ required: true }]
224
+ }] } });
225
+
226
+ class SeamChatInputComponent {
227
+ placeholder = 'Type a message...';
228
+ disabled = false;
229
+ messageSent = new EventEmitter();
230
+ _control = new FormControl('', [Validators.required]);
231
+ _onEnterKey(event) {
232
+ const keyEvent = event;
233
+ if (keyEvent.shiftKey) {
234
+ return;
235
+ }
236
+ keyEvent.preventDefault();
237
+ this._onSend();
238
+ }
239
+ _onSend() {
240
+ const value = this._control.value?.trim();
241
+ if (!value || this.disabled) {
242
+ return;
243
+ }
244
+ this.messageSent.emit(value);
245
+ this._control.reset();
246
+ }
247
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SeamChatInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
248
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SeamChatInputComponent, isStandalone: true, selector: "seam-chat-input", inputs: { placeholder: "placeholder", disabled: "disabled" }, outputs: { messageSent: "messageSent" }, ngImport: i0, template: `
249
+ <div class="seam-chat-input">
250
+ <seam-form-field>
251
+ <seam-rich-text
252
+ [formControl]="_control"
253
+ [placeholder]="placeholder"
254
+ [disableRichText]="true"
255
+ [rows]="2.7"
256
+ [resizable]="false"
257
+ (keydown.enter)="_onEnterKey($event)"
258
+ ></seam-rich-text>
259
+ </seam-form-field>
260
+ <button
261
+ seamButton
262
+ theme="primary"
263
+ class="seam-chat-send-btn"
264
+ [disabled]="disabled || _control.invalid"
265
+ (click)="_onSend()"
266
+ >
267
+ Send
268
+ </button>
269
+ </div>
270
+ `, isInline: true, styles: [".seam-chat-input{display:flex;align-items:flex-end;gap:8px;padding:8px;border-top:1px solid #dee2e6}seam-form-field{flex:1}.seam-chat-send-btn{flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TheSeamRichTextModule }, { kind: "component", type: i2.RichTextComponent, selector: "seam-rich-text", inputs: ["value", "required", "placeholder", "rows", "resizable", "disableRichText", "displayCharacterCounter", "minLength", "maxLength", "characterCounterTpl", "characterCounterFn", "useMentions", "mentionItems", "mentionSearchFn", "mentionRenderListFn", "mentionListLoadingText", "mentionListEmptyText"], outputs: ["quillEditorCreated", "quillEditorChanged", "quillContentChanged", "quillSelectionChanged", "quillFocus", "quillBlur", "mentionsUpdated"] }, { kind: "ngmodule", type: TheSeamFormFieldModule }, { kind: "component", type: i4.TheSeamFormFieldComponent, selector: "seam-form-field", inputs: ["inline", "label", "labelPosition", "labelClass", "maxErrors", "numPaddingErrors", "labelId", "helpText", "helpTextId"] }, { kind: "ngmodule", type: TheSeamButtonsModule }, { kind: "component", type: i4$1.TheSeamButtonComponent, selector: "button[seamButton]", inputs: ["disabled", "theme", "size", "type"], exportAs: ["seamButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
271
+ }
272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SeamChatInputComponent, decorators: [{
273
+ type: Component,
274
+ args: [{ selector: 'seam-chat-input', imports: [
275
+ ReactiveFormsModule,
276
+ TheSeamRichTextModule,
277
+ TheSeamFormFieldModule,
278
+ TheSeamButtonsModule,
279
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
280
+ <div class="seam-chat-input">
281
+ <seam-form-field>
282
+ <seam-rich-text
283
+ [formControl]="_control"
284
+ [placeholder]="placeholder"
285
+ [disableRichText]="true"
286
+ [rows]="2.7"
287
+ [resizable]="false"
288
+ (keydown.enter)="_onEnterKey($event)"
289
+ ></seam-rich-text>
290
+ </seam-form-field>
291
+ <button
292
+ seamButton
293
+ theme="primary"
294
+ class="seam-chat-send-btn"
295
+ [disabled]="disabled || _control.invalid"
296
+ (click)="_onSend()"
297
+ >
298
+ Send
299
+ </button>
300
+ </div>
301
+ `, styles: [".seam-chat-input{display:flex;align-items:flex-end;gap:8px;padding:8px;border-top:1px solid #dee2e6}seam-form-field{flex:1}.seam-chat-send-btn{flex-shrink:0}\n"] }]
302
+ }], propDecorators: { placeholder: [{
303
+ type: Input
304
+ }], disabled: [{
305
+ type: Input
306
+ }], messageSent: [{
307
+ type: Output
308
+ }] } });
309
+
310
+ class TheSeamChatComponent {
311
+ _provider = inject(THESEAM_CHAT_PROVIDER, { optional: true });
312
+ _cdr = inject(ChangeDetectorRef);
313
+ _ngZone = inject(NgZone);
314
+ systemPrompt = '';
315
+ placeholder = 'Type a message...';
316
+ _messageList;
317
+ _messageListScrollbar;
318
+ _loadingSubject = new BehaviorSubject(false);
319
+ _messages = [];
320
+ _displayMessages = [];
321
+ // Pixels of slack allowed when deciding if the viewport is "at the bottom".
322
+ // Small enough that scrolling up a line unpins, but forgiving of sub-pixel drift.
323
+ _pinnedThreshold = 32;
324
+ // True when the viewport is (or should be) tracking the latest content. Starts
325
+ // true so the first messages scroll into view. Updated on every scroll event.
326
+ _isPinnedToBottom = true;
327
+ // Set when a user action (e.g. sending a message) requires the view to jump
328
+ // to the bottom on the next content-size change, regardless of pinned state.
329
+ // Cleared as soon as it's consumed.
330
+ _forceScrollOnNextResize = false;
331
+ ngAfterViewInit() {
332
+ const scrollInstance = this._messageListScrollbar?.instance;
333
+ if (!scrollInstance) {
334
+ return;
335
+ }
336
+ // OverlayScrollbars callbacks run outside Angular already (the directive's
337
+ // service initializes with runOutsideAngular). These handlers only update
338
+ // local flags and call scroll() — no change detection needed.
339
+ this._ngZone.runOutsideAngular(() => {
340
+ scrollInstance.options({
341
+ callbacks: {
342
+ onScroll: () => this._updatePinnedState(),
343
+ // Fires when the content's scrollable size changes: a new message
344
+ // appended, a custom block finishing its async render, an image
345
+ // finishing loading, etc. This removes the need for a setTimeout
346
+ // hack because we react to actual size changes rather than guessing
347
+ // when rendering has settled.
348
+ onContentSizeChanged: () => this._maybeScrollToBottom(),
349
+ },
350
+ });
351
+ });
352
+ }
353
+ async _onMessageSent(text) {
354
+ if (this._loadingSubject.value || !this._provider) {
355
+ if (!this._provider) {
356
+ console.error('No chat provider configured.');
357
+ }
358
+ return;
359
+ }
360
+ const userMessage = { role: 'user', content: text };
361
+ this._messages.push(userMessage);
362
+ this._displayMessages = [
363
+ ...this._displayMessages,
364
+ {
365
+ role: 'user',
366
+ segments: [{ type: 'markdown', content: text }],
367
+ timestamp: new Date(),
368
+ },
369
+ ];
370
+ // The user just sent a message — jump to the bottom even if they had
371
+ // scrolled up previously. Consumed by the next onContentSizeChanged.
372
+ this._forceScrollOnNextResize = true;
373
+ this._cdr.markForCheck();
374
+ this._loadingSubject.next(true);
375
+ try {
376
+ const messagesToSend = [];
377
+ if (this.systemPrompt) {
378
+ messagesToSend.push({ role: 'system', content: this.systemPrompt });
379
+ }
380
+ messagesToSend.push(...this._messages);
381
+ const response = await this._provider.chat(messagesToSend);
382
+ const assistantMessage = {
383
+ role: 'assistant',
384
+ content: response.content,
385
+ };
386
+ this._messages.push(assistantMessage);
387
+ this._displayMessages = [
388
+ ...this._displayMessages,
389
+ {
390
+ role: 'assistant',
391
+ segments: parseChatResponse(response.content),
392
+ timestamp: new Date(),
393
+ },
394
+ ];
395
+ }
396
+ catch (err) {
397
+ console.error('Chat provider error:', err);
398
+ }
399
+ finally {
400
+ this._loadingSubject.next(false);
401
+ this._cdr.markForCheck();
402
+ }
403
+ }
404
+ _updatePinnedState() {
405
+ const scrollInstance = this._messageListScrollbar?.instance;
406
+ if (!scrollInstance) {
407
+ return;
408
+ }
409
+ const info = scrollInstance.scroll();
410
+ this._isPinnedToBottom =
411
+ info.position.y >= info.max.y - this._pinnedThreshold;
412
+ }
413
+ _maybeScrollToBottom() {
414
+ // Auto-scroll only when the user expects it:
415
+ // - They just sent a message (forceScroll), OR
416
+ // - They were already following the latest content (pinnedToBottom).
417
+ // If they scrolled up to read an earlier message, we leave their viewport
418
+ // alone so continued content growth (e.g. a streaming answer or an
419
+ // async-rendered block) doesn't keep yanking it back.
420
+ if (this._forceScrollOnNextResize || this._isPinnedToBottom) {
421
+ this._scrollToBottom();
422
+ this._forceScrollOnNextResize = false;
423
+ }
424
+ }
425
+ _scrollToBottom() {
426
+ const scrollInstance = this._messageListScrollbar?.instance;
427
+ if (scrollInstance) {
428
+ const state = scrollInstance.getState();
429
+ scrollInstance.scroll({ y: state.contentScrollSize.height });
430
+ }
431
+ }
432
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
433
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TheSeamChatComponent, isStandalone: true, selector: "seam-chat", inputs: { systemPrompt: "systemPrompt", placeholder: "placeholder" }, viewQueries: [{ propertyName: "_messageList", first: true, predicate: ["messageList"], descendants: true }, { propertyName: "_messageListScrollbar", first: true, predicate: TheSeamOverlayScrollbarDirective, descendants: true }], ngImport: i0, template: "<div class=\"seam-chat\">\n <div class=\"seam-chat__messages\" #messageList seamOverlayScrollbar>\n <seam-chat-message\n *ngFor=\"let msg of _displayMessages\"\n [message]=\"msg\"\n ></seam-chat-message>\n\n <div *ngIf=\"_loadingSubject | async\" class=\"seam-chat__loading\">\n <span>Thinking...</span>\n </div>\n </div>\n\n <seam-chat-input\n [placeholder]=\"placeholder\"\n [disabled]=\"!!(_loadingSubject | async)\"\n (messageSent)=\"_onMessageSent($event)\"\n ></seam-chat-input>\n</div>\n", styles: [":host{display:flex;flex-direction:column;height:100%;overflow:hidden}.seam-chat{display:flex;flex-direction:column;height:100%;overflow:hidden}.seam-chat__messages{flex:1;overflow-y:auto;padding:12px}.seam-chat__loading{padding:8px 12px;color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: SeamChatMessageComponent, selector: "seam-chat-message", inputs: ["message"] }, { kind: "component", type: SeamChatInputComponent, selector: "seam-chat-input", inputs: ["placeholder", "disabled"], outputs: ["messageSent"] }, { kind: "directive", type: TheSeamOverlayScrollbarDirective, selector: "[seamOverlayScrollbar]", inputs: ["seamOverlayScrollbar", "overlayScrollbarEnabled"], exportAs: ["seamOverlayScrollbar"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
434
+ }
435
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamChatComponent, decorators: [{
436
+ type: Component,
437
+ args: [{ selector: 'seam-chat', imports: [
438
+ AsyncPipe,
439
+ NgForOf,
440
+ NgIf,
441
+ SeamChatMessageComponent,
442
+ SeamChatInputComponent,
443
+ TheSeamOverlayScrollbarDirective,
444
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"seam-chat\">\n <div class=\"seam-chat__messages\" #messageList seamOverlayScrollbar>\n <seam-chat-message\n *ngFor=\"let msg of _displayMessages\"\n [message]=\"msg\"\n ></seam-chat-message>\n\n <div *ngIf=\"_loadingSubject | async\" class=\"seam-chat__loading\">\n <span>Thinking...</span>\n </div>\n </div>\n\n <seam-chat-input\n [placeholder]=\"placeholder\"\n [disabled]=\"!!(_loadingSubject | async)\"\n (messageSent)=\"_onMessageSent($event)\"\n ></seam-chat-input>\n</div>\n", styles: [":host{display:flex;flex-direction:column;height:100%;overflow:hidden}.seam-chat{display:flex;flex-direction:column;height:100%;overflow:hidden}.seam-chat__messages{flex:1;overflow-y:auto;padding:12px}.seam-chat__loading{padding:8px 12px;color:#6c757d;font-style:italic}\n"] }]
445
+ }], propDecorators: { systemPrompt: [{
446
+ type: Input
447
+ }], placeholder: [{
448
+ type: Input
449
+ }], _messageList: [{
450
+ type: ViewChild,
451
+ args: ['messageList']
452
+ }], _messageListScrollbar: [{
453
+ type: ViewChild,
454
+ args: [TheSeamOverlayScrollbarDirective]
455
+ }] } });
456
+
457
+ class TheSeamChatMessageHarness extends ComponentHarness {
458
+ static hostSelector = 'seam-chat-message';
459
+ _role = this.locatorForOptional('.seam-chat-message__role');
460
+ _content = this.locatorForOptional('.seam-chat-message__content');
461
+ async getRole() {
462
+ const roleEl = await this._role();
463
+ const text = await roleEl?.text();
464
+ return text?.trim().toLowerCase() ?? '';
465
+ }
466
+ async getText() {
467
+ const contentEl = await this._content();
468
+ return (await contentEl?.text())?.trim() ?? '';
469
+ }
470
+ }
471
+ class TheSeamChatInputHarness extends ComponentHarness {
472
+ static hostSelector = 'seam-chat-input';
473
+ async getSendButton() {
474
+ return this.locatorFor('button')();
475
+ }
476
+ async isSendDisabled() {
477
+ const btn = await this.getSendButton();
478
+ return (await btn.getAttribute('disabled')) !== null;
479
+ }
480
+ }
481
+ class TheSeamChatHarness extends ComponentHarness {
482
+ static hostSelector = 'seam-chat';
483
+ _messages = this.locatorForAll(TheSeamChatMessageHarness);
484
+ _input = this.locatorFor(TheSeamChatInputHarness);
485
+ _loading = this.locatorForOptional('.seam-chat__loading');
486
+ async getMessages() {
487
+ return this._messages();
488
+ }
489
+ async getInput() {
490
+ return this._input();
491
+ }
492
+ async isLoading() {
493
+ return (await this._loading()) !== null;
494
+ }
495
+ }
496
+
497
+ const assistantPrompt = `You are a helpful assistant that provides formatting json code for a datatable.
498
+ A datatable is a table that displays data in rows and columns, similar to a spreadsheet, with column sorting and data filtering.
499
+
500
+ Your job is not to provide a descriptive analysis of the request or any additional information. The user will ignore anything that is not a JSON object.
501
+
502
+ The user will provide a request, and you will respond with a JSON object that contains an array of table alterations.
503
+ The following is the typescript interface for a datatable column and the alterations you can make to it:
504
+
505
+ \`\`\`typescript
506
+ interface TableColumn {
507
+ /** Column property */
508
+ prop: string,
509
+ /** Column name */
510
+ name: string,
511
+ /** Column cell type - determines filter type */
512
+ cellType?: 'string' | 'integer' | 'decimal' | 'currency' | 'date' | 'phone',
513
+ /** Whether the column is sortable */
514
+ sortable?: boolean,
515
+ /** Whether the column is filterable */
516
+ filterable?: boolean,
517
+ /** Whether the column is visible */
518
+ visible?: boolean,
519
+ /** Whether the column is resizable */
520
+ resizable?: boolean,
521
+ /** Whether the column is draggable */
522
+ draggable?: boolean,
523
+ /** Column width */
524
+ width?: number,
525
+ /** Column index */
526
+ index?: number,
527
+ }
528
+
529
+ interface SortItem {
530
+ /** Column property */
531
+ prop: string,
532
+ /** Sort direction */
533
+ dir: 'asc' | 'desc'
534
+ }
535
+
536
+ interface SortState {
537
+ /** The list of sorts */
538
+ sorts: SortItem[]
539
+ }
540
+
541
+ interface OrderRecord {
542
+ /** Column property */
543
+ columnProp: string,
544
+ /** Column order, which is the index that it will be placed in the columns array. */
545
+ index: number
546
+ }
547
+
548
+ interface OrderState {
549
+ /** The list of column order records */
550
+ columns: OrderRecord[]
551
+ }
552
+
553
+ interface WidthState {
554
+ /** The column property that this width alteration applies to */
555
+ columnProp: string
556
+ /** The width of the column. Number is in pixels. */
557
+ width?: number
558
+ /** Whether the column can auto resize. Needs to be false to guarantee a specific width. */
559
+ canAutoResize: boolean
560
+ }
561
+
562
+ interface HideColumnState {
563
+ /** The column property that this alteration applies to */
564
+ columnProp: string
565
+ /** Whether the column is hidden */
566
+ hidden: boolean
567
+ }
568
+
569
+ interface FilterState {
570
+ /** The column property that this filter applies to */
571
+ columnProp: string,
572
+ /** The filter type based on column cellType */
573
+ filterType: 'text' | 'numeric' | 'date',
574
+ /** The filter operation */
575
+ operation: string,
576
+ /** The filter value (for single value operations) */
577
+ value?: any,
578
+ /** The from value (for range operations like 'between') */
579
+ fromValue?: any,
580
+ /** The to value (for range operations like 'between') */
581
+ toValue?: any
582
+ }
583
+
584
+ interface TableAlteration<TType extends string, TState> {
585
+ /**
586
+ * Unique identifier for the alteration.
587
+ */
588
+ id: string
589
+ /**
590
+ * The type of alteration.
591
+ */
592
+ type: TType
593
+ /** The alteration state */
594
+ state: TState
595
+ }
596
+
597
+ /**
598
+ * Sort alteration for a datatable.
599
+ * "id" should always be "sort" for this alteration.
600
+ */
601
+ type SortAlteration = TableAlteration<'sort', SortState>
602
+
603
+ /**
604
+ * Order alteration for a datatable column.
605
+ *
606
+ * "id" should always be "order" for this alteration.
607
+ */
608
+ type OrderAlteration = TableAlteration<'order', OrderState>
609
+
610
+ /**
611
+ * Width alteration for a datatable column.
612
+ *
613
+ * "id" should always be "width-<prop>" for this alteration. So, for example, if the column property is "name", the id would be "width-name".
614
+ */
615
+ type WidthAlteration = TableAlteration<'width', WidthState>
616
+
617
+ /**
618
+ * Hide column alteration for a datatable column.
619
+ *
620
+ * "id" should always be "hide-column-<prop>" for this alteration. So, for example, if the column property is "name", the id would be "hide-column-name".
621
+ */
622
+ type HideColumnAlteration = TableAlteration<'hide-column', HideColumnState>
623
+
624
+ /**
625
+ * Filter alteration for a datatable column.
626
+ * "id" should be "filter--<columnProp>" for this alteration.
627
+ * For example, if filtering the "age" column, the id would be "filter--age".
628
+ */
629
+ type FilterAlteration = TableAlteration<'filter', FilterState>
630
+ \`\`\`
631
+
632
+ ## Filter Operations by Type
633
+
634
+ ### Text Filters (cellType: 'string', 'phone')
635
+ - 'contains': Text contains the value (case-insensitive)
636
+ - 'eq': Text equals the value exactly
637
+ - 'neq': Text does not equal the value
638
+ - 'ncontains': Text does not contain the value
639
+ - 'blank': Field is empty/null
640
+ - 'not-blank': Field is not empty/null
641
+
642
+ ### Numeric Filters (cellType: 'integer', 'decimal', 'currency')
643
+ - 'eq': Equals the value
644
+ - 'gt': Greater than the value
645
+ - 'gte': Greater than or equal to the value
646
+ - 'lt': Less than the value
647
+ - 'lte': Less than or equal to the value
648
+ - 'between': Between fromValue and toValue (inclusive)
649
+ - 'not-between': Not between fromValue and toValue
650
+ - 'blank': Field is empty/null
651
+ - 'not-blank': Field is not empty/null
652
+
653
+ ### Date Filters (cellType: 'date')
654
+ - 'eq': Date equals the value
655
+ - 'gt': Date is after the value
656
+ - 'gte': Date is on or after the value
657
+ - 'lt': Date is before the value
658
+ - 'lte': Date is on or before the value
659
+ - 'between': Date is between fromValue and toValue (inclusive)
660
+ - 'not-between': Date is not between fromValue and toValue
661
+ - 'blank': Field is empty/null
662
+ - 'not-blank': Field is not empty/null
663
+
664
+ ## Examples
665
+
666
+ Filter age greater than 30:
667
+ \`\`\`json
668
+ {
669
+ "id": "filter--age",
670
+ "type": "filter",
671
+ "state": {
672
+ "columnProp": "age",
673
+ "filterType": "numeric",
674
+ "operation": "gt",
675
+ "value": 30
676
+ }
677
+ }
678
+ \`\`\`
679
+
680
+ Filter color contains "red":
681
+ \`\`\`json
682
+ {
683
+ "id": "filter--color",
684
+ "type": "filter",
685
+ "state": {
686
+ "columnProp": "color",
687
+ "filterType": "text",
688
+ "operation": "contains",
689
+ "value": "red"
690
+ }
691
+ }
692
+ \`\`\`
693
+
694
+ Filter age between 25 and 65:
695
+ \`\`\`json
696
+ {
697
+ "id": "filter--age",
698
+ "type": "filter",
699
+ "state": {
700
+ "columnProp": "age",
701
+ "filterType": "numeric",
702
+ "operation": "between",
703
+ "fromValue": 25,
704
+ "toValue": 65
705
+ }
706
+ }
707
+ \`\`\`
708
+
709
+ Sort by name ascending:
710
+ \`\`\`json
711
+ {
712
+ "id": "sort",
713
+ "type": "sort",
714
+ "state": {
715
+ "sorts": [
716
+ {
717
+ "prop": "name",
718
+ "dir": "asc"
719
+ }
720
+ ]
721
+ }
722
+ }
723
+ \`\`\`
724
+
725
+ Hide the age column:
726
+ \`\`\`json
727
+ {
728
+ "id": "hide-column-age",
729
+ "type": "hide-column",
730
+ "state": {
731
+ "columnProp": "age",
732
+ "hidden": true
733
+ }
734
+ }
735
+ \`\`\`
736
+
737
+ Set name column width to 300 pixels:
738
+ \`\`\`json
739
+ {
740
+ "id": "width-name",
741
+ "type": "width",
742
+ "state": {
743
+ "columnProp": "name",
744
+ "width": 300,
745
+ "canAutoResize": false
746
+ }
747
+ }
748
+ \`\`\`
749
+
750
+ Reorder columns (name first, age second, color third):
751
+ \`\`\`json
752
+ {
753
+ "id": "order",
754
+ "type": "order",
755
+ "state": {
756
+ "columns": [
757
+ { "columnProp": "name", "index": 0 },
758
+ { "columnProp": "age", "index": 1 },
759
+ { "columnProp": "color", "index": 2 }
760
+ ]
761
+ }
762
+ }
763
+ \`\`\`
764
+ `;
765
+ const getUserPrompt = (columns, request) => `
766
+ Columns:
767
+ \`\`\`json
768
+ ${JSON.stringify(columns, null, 2)}
769
+ \`\`\`
770
+ Request: "${request}"
771
+ `;
772
+ function parseResponse(responseContent, responseFormat) {
773
+ if (responseFormat?.type === 'json_object') {
774
+ return JSON.parse(responseContent);
775
+ }
776
+ // Parse the JSON string to an object, which is in the string between the code blocks.
777
+ // So, need to find the first and last code block markers.
778
+ const startIndex = responseContent.indexOf('```json') + '```json'.length;
779
+ const endIndex = responseContent.lastIndexOf('```');
780
+ const alterations = responseContent.substring(startIndex, endIndex).trim();
781
+ // console.log('Alterations:', alterations)
782
+ return JSON.parse(alterations);
783
+ }
784
+ const THESEAM_DATATABLE_PROMPTER_PROVIDER = new InjectionToken('TheSeamDatatablePrompterProvider');
785
+
786
+ class TheSeamDatatablePrompterComponent {
787
+ // cdr = inject(ChangeDetectorRef)
788
+ _prefsAccessor = inject(THESEAM_DATATABLE_PREFERENCES_ACCESSOR, { optional: true });
789
+ _dtPrefsService = inject(DatatablePreferencesService);
790
+ _aiProvider = inject(THESEAM_DATATABLE_PROMPTER_PROVIDER, {
791
+ optional: true,
792
+ });
793
+ _loadingSubject = new BehaviorSubject(false);
794
+ _altsDataSubject = new BehaviorSubject(undefined);
795
+ loading$ = this._loadingSubject.asObservable();
796
+ diffMode = 'auto';
797
+ compact = true;
798
+ set prompt(value) {
799
+ if (value) {
800
+ this._form.controls.prompt.setValue(value);
801
+ }
802
+ else {
803
+ this._form.controls.prompt.setValue('Sort color descending order');
804
+ }
805
+ }
806
+ set datatable(value) {
807
+ this._datatableSubject.next(value);
808
+ }
809
+ get datatable() {
810
+ return this._datatableSubject.value;
811
+ }
812
+ _datatableSubject = new BehaviorSubject(null);
813
+ showAlts = true;
814
+ _form = new FormGroup({
815
+ prompt: new FormControl('Sort color descending order', [
816
+ Validators.required,
817
+ ]),
818
+ });
819
+ _alterations$ = this._datatableSubject
820
+ .asObservable()
821
+ .pipe(switchMap((dt) => {
822
+ if (!dt) {
823
+ return of(dt);
824
+ }
825
+ return dt._columnsAlterationsManager.changes.pipe(startWith(undefined), map(() => dt));
826
+ }), switchMap((datatable) => {
827
+ if (!datatable) {
828
+ return of([]);
829
+ }
830
+ const key = datatable.preferencesKey;
831
+ if (!key) {
832
+ // eslint-disable-next-line no-console
833
+ console.warn('No preferences key set on datatable, returning empty alterations.');
834
+ return of([]);
835
+ }
836
+ return (this._dtPrefsService.preferences(key).pipe(switchMap((prefs) => {
837
+ // console.log('~~~~Current preferences:', prefs)
838
+ if (!prefs) {
839
+ return of(JSON.parse(JSON.stringify(EMPTY_DATATABLE_PREFERENCES))
840
+ .alterations);
841
+ }
842
+ // return of(JSON.parse(prefs).alterations as ColumnsAlterationState[])
843
+ return of(prefs.alterations);
844
+ })) ?? of([]));
845
+ }));
846
+ _alterationsDisplayItems$ = this._alterations$.pipe(switchMap((alterations) => {
847
+ console.log('~~~~~Current alterations:', alterations);
848
+ if (!alterations || alterations.length === 0) {
849
+ return of([]);
850
+ }
851
+ const alts = mapColumnsAlterationsStates(alterations);
852
+ console.log('~~~~~Mapped alterations:', alts);
853
+ return of(alts.map((a) => a.toDisplayItem()));
854
+ }), shareReplay({ bufferSize: 1, refCount: true }));
855
+ _pendingAlterationsSubject = new BehaviorSubject([]);
856
+ _pendingAlterationsDisplayItems$ = this._pendingAlterationsSubject.asObservable().pipe(switchMap((pending) => {
857
+ if (!pending || pending.length === 0) {
858
+ return of([]);
859
+ }
860
+ const alts = mapColumnsAlterationsStates(pending);
861
+ console.log('~~~~~Mapped alterations2:', alts);
862
+ return of(alts.map((a) => a.toDisplayItem()));
863
+ }), shareReplay({ bufferSize: 1, refCount: true }));
864
+ _onSubmit() {
865
+ console.log('Submitting prompt:', this._form.value);
866
+ if (this._form.invalid) {
867
+ return;
868
+ }
869
+ if (this._loadingSubject.value) {
870
+ console.warn('Already loading, ignoring submit.');
871
+ return;
872
+ }
873
+ const prompt = this._form.value.prompt;
874
+ if (!prompt) {
875
+ return;
876
+ }
877
+ console.log('datatable', this._datatableSubject.value);
878
+ const columns = (this._datatableSubject.value?.ngxDatatable?.columns || []).map((col) => ({
879
+ prop: col.prop,
880
+ name: col.name,
881
+ cellType: col.cellType || 'string',
882
+ sortable: col.sortable,
883
+ filterable: true,
884
+ visible: true,
885
+ resizable: col.resizeable,
886
+ draggable: col.draggable,
887
+ }));
888
+ console.log('columns', columns);
889
+ const userPrompt = getUserPrompt(columns, prompt);
890
+ console.log('userPrompt', userPrompt);
891
+ this._loadingSubject.next(true);
892
+ if (!this._aiProvider) {
893
+ console.error('No AI provider configured, cannot submit prompt.');
894
+ this._loadingSubject.next(false);
895
+ return;
896
+ }
897
+ this._aiProvider
898
+ .chat([
899
+ { role: 'system', content: assistantPrompt },
900
+ { role: 'user', content: userPrompt },
901
+ ])
902
+ .then(async (response) => {
903
+ const alterations = parseResponse(response.content, undefined);
904
+ // this._form.reset()
905
+ console.log('Received alterations:', alterations);
906
+ const datatable = this._datatableSubject.value;
907
+ if (!datatable) {
908
+ console.error('No datatable found to apply alterations to.');
909
+ return;
910
+ }
911
+ const key = this.datatable.preferencesKey;
912
+ const before = await this._prefsAccessor?.get(key).toPromise();
913
+ console.log('Current preferences before update:', before);
914
+ const _apply = async () => {
915
+ console.log('Preferences updated successfully.');
916
+ const _cols = this.datatable.ngxDatatable.columns;
917
+ const cols = [..._cols];
918
+ console.log('this.datatable!.columns', cols);
919
+ const after = await this._prefsAccessor?.get(key).toPromise();
920
+ let _after = (JSON.parse(after || '{}').alterations ||
921
+ []);
922
+ if (!Array.isArray(_after)) {
923
+ _after = [_after];
924
+ }
925
+ const mgr = this.datatable._columnsAlterationsManager;
926
+ console.log('_columnsAlterationsManager', mgr, mgr.get());
927
+ const alts = mapColumnsAlterationsStates(_after);
928
+ console.log('Mapped alterations:', alts);
929
+ const columnsBefore = JSON.parse(JSON.stringify(this.datatable.ngxDatatable.columns.map((x) => x.prop)));
930
+ console.log('Columns before applying alterations:', columnsBefore);
931
+ for (const a of alts) {
932
+ console.log('Applying alteration:', a);
933
+ a.apply(cols, this.datatable);
934
+ }
935
+ console.log('Current preferences after update:', after);
936
+ console.log(_after);
937
+ this.datatable.columns = [...cols];
938
+ const columnsAfter = JSON.parse(JSON.stringify(this.datatable.ngxDatatable.columns.map((x) => x.prop)));
939
+ console.log('Columns after applying alterations:', columnsAfter);
940
+ mgr.add(alts);
941
+ datatable._cdr.detectChanges();
942
+ this._pendingAlterationsSubject.next(_after);
943
+ };
944
+ this._prefsAccessor
945
+ ?.update(key, JSON.stringify({
946
+ version: 2,
947
+ alterations,
948
+ }))
949
+ .subscribe(async () => {
950
+ // TODO: Cleanup. This is a hack to ensure the datatable updates after the preferences are set.
951
+ await _apply();
952
+ datatable.rows = [...datatable.rows];
953
+ datatable._cdr.detectChanges();
954
+ await _apply();
955
+ this._loadingSubject.next(false);
956
+ });
957
+ })
958
+ .catch((err) => {
959
+ console.error('Error submitting prompt:', err);
960
+ this._loadingSubject.next(false);
961
+ });
962
+ }
963
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamDatatablePrompterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
964
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TheSeamDatatablePrompterComponent, isStandalone: true, selector: "seam-datatable-prompter", inputs: { diffMode: "diffMode", compact: "compact", prompt: "prompt", datatable: "datatable", showAlts: "showAlts" }, ngImport: i0, template: "<div class=\"d-block border rounded border-lightgray\">\n <div\n [formGroup]=\"_form\"\n (ngSubmit)=\"_onSubmit()\"\n class=\"d-block position-relative\"\n >\n <div>\n <div class=\"p-1\">\n <!-- <textarea formControlName=\"prompt\" style=\"width: 800px; height: 100px;\"></textarea> -->\n <seam-form-field [numPaddingErrors]=\"0\">\n <seam-rich-text\n seamInput\n formControlName=\"prompt\"\n placeholder=\"Describe what you want to apply to the datatable.\"\n [rows]=\"3\"\n [resizable]=\"false\"\n [disableRichText]=\"true\"\n ></seam-rich-text>\n </seam-form-field>\n </div>\n <div class=\"p-1\">\n <button\n class=\"mr-1\"\n type=\"submit\"\n seamButton\n theme=\"primary\"\n size=\"sm\"\n (click)=\"_onSubmit()\"\n >\n Submit\n </button>\n <button seamButton theme=\"lightgray\" size=\"sm\" (click)=\"_form.reset()\">\n Reset\n </button>\n </div>\n </div>\n <div\n *ngIf=\"loading$ | async\"\n class=\"d-block position-absolute\"\n style=\"top: 0; left: 0; right: 0; bottom: 0\"\n >\n <seam-loading></seam-loading>\n </div>\n </div>\n\n <div class=\"p-2\" *ngIf=\"showAlts\">\n <div>Active Alterations</div>\n <div class=\"p-2 border rounded bg-lightgray\">\n <!-- {{ _alterations$ | async | json }} -->\n <div *ngFor=\"let alteration of _alterations$ | async; let last = last\">\n <div\n class=\"d-flex align-items-center border border-dark rounded p-1\"\n [class.mb-1]=\"!last\"\n >\n <span class=\"badge bg-secondary\">{{ alteration.type }}</span>\n <!-- <span class=\"badge bg-primary\">{{ alteration.label }}</span> -->\n <!-- <div>\n {{ alteration.value | json }}\n </div> -->\n <div *ngIf=\"alteration.type === 'sort'\">\n <div class=\"d-flex flex-column\">\n <div\n *ngFor=\"let sort of alteration.state.sorts; let last = last\"\n [class.mb-1]=\"!last\"\n >\n <span class=\"badge bg-lightgray\"\n >{{ sort.prop }} ({{ sort.dir }})</span\n >\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <seam-alterations-diff\n [currentItems]=\"(_alterationsDisplayItems$ | async) ?? []\"\n [pendingItems]=\"(_pendingAlterationsDisplayItems$ | async) ?? []\"\n [compact]=\"compact\"\n [diffMode]=\"diffMode\"\n ></seam-alterations-diff>\n </div>\n</div>\n", styles: [":host{display:block;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TheSeamLoadingModule }, { kind: "component", type: i2$1.TheSeamLoadingComponent, selector: "seam-loading", inputs: ["theme"] }, { kind: "ngmodule", type: TheSeamRichTextModule }, { kind: "component", type: i2.RichTextComponent, selector: "seam-rich-text", inputs: ["value", "required", "placeholder", "rows", "resizable", "disableRichText", "displayCharacterCounter", "minLength", "maxLength", "characterCounterTpl", "characterCounterFn", "useMentions", "mentionItems", "mentionSearchFn", "mentionRenderListFn", "mentionListLoadingText", "mentionListEmptyText"], outputs: ["quillEditorCreated", "quillEditorChanged", "quillContentChanged", "quillSelectionChanged", "quillFocus", "quillBlur", "mentionsUpdated"] }, { kind: "ngmodule", type: TheSeamFormFieldModule }, { kind: "component", type: i4.TheSeamFormFieldComponent, selector: "seam-form-field", inputs: ["inline", "label", "labelPosition", "labelClass", "maxErrors", "numPaddingErrors", "labelId", "helpText", "helpTextId"] }, { kind: "directive", type: i4.InputDirective, selector: "input[seamInput], textarea[seamInput], ng-select[seamInput], seam-tel-input[seamInput], quill-editor[seamInput], seam-google-maps[seamInput], seam-rich-text[seamInput]", inputs: ["seamInputSize", "id", "type", "placeholder", "required", "disabled", "readonly"], exportAs: ["seamInput"] }, { kind: "ngmodule", type: TheSeamButtonsModule }, { kind: "component", type: i4$1.TheSeamButtonComponent, selector: "button[seamButton]", inputs: ["disabled", "theme", "size", "type"], exportAs: ["seamButton"] }, { kind: "component", type: AlterationsDiffComponent, selector: "seam-alterations-diff", inputs: ["currentItems", "pendingItems", "diffMode", "initialDiffState", "compact"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
965
+ }
966
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TheSeamDatatablePrompterComponent, decorators: [{
967
+ type: Component,
968
+ args: [{ selector: 'seam-datatable-prompter', imports: [
969
+ ReactiveFormsModule,
970
+ AsyncPipe,
971
+ JsonPipe,
972
+ NgForOf,
973
+ NgIf,
974
+ TheSeamLoadingModule,
975
+ TheSeamRichTextModule,
976
+ TheSeamFormFieldModule,
977
+ TheSeamButtonsModule,
978
+ AlterationsDiffComponent,
979
+ ], template: "<div class=\"d-block border rounded border-lightgray\">\n <div\n [formGroup]=\"_form\"\n (ngSubmit)=\"_onSubmit()\"\n class=\"d-block position-relative\"\n >\n <div>\n <div class=\"p-1\">\n <!-- <textarea formControlName=\"prompt\" style=\"width: 800px; height: 100px;\"></textarea> -->\n <seam-form-field [numPaddingErrors]=\"0\">\n <seam-rich-text\n seamInput\n formControlName=\"prompt\"\n placeholder=\"Describe what you want to apply to the datatable.\"\n [rows]=\"3\"\n [resizable]=\"false\"\n [disableRichText]=\"true\"\n ></seam-rich-text>\n </seam-form-field>\n </div>\n <div class=\"p-1\">\n <button\n class=\"mr-1\"\n type=\"submit\"\n seamButton\n theme=\"primary\"\n size=\"sm\"\n (click)=\"_onSubmit()\"\n >\n Submit\n </button>\n <button seamButton theme=\"lightgray\" size=\"sm\" (click)=\"_form.reset()\">\n Reset\n </button>\n </div>\n </div>\n <div\n *ngIf=\"loading$ | async\"\n class=\"d-block position-absolute\"\n style=\"top: 0; left: 0; right: 0; bottom: 0\"\n >\n <seam-loading></seam-loading>\n </div>\n </div>\n\n <div class=\"p-2\" *ngIf=\"showAlts\">\n <div>Active Alterations</div>\n <div class=\"p-2 border rounded bg-lightgray\">\n <!-- {{ _alterations$ | async | json }} -->\n <div *ngFor=\"let alteration of _alterations$ | async; let last = last\">\n <div\n class=\"d-flex align-items-center border border-dark rounded p-1\"\n [class.mb-1]=\"!last\"\n >\n <span class=\"badge bg-secondary\">{{ alteration.type }}</span>\n <!-- <span class=\"badge bg-primary\">{{ alteration.label }}</span> -->\n <!-- <div>\n {{ alteration.value | json }}\n </div> -->\n <div *ngIf=\"alteration.type === 'sort'\">\n <div class=\"d-flex flex-column\">\n <div\n *ngFor=\"let sort of alteration.state.sorts; let last = last\"\n [class.mb-1]=\"!last\"\n >\n <span class=\"badge bg-lightgray\"\n >{{ sort.prop }} ({{ sort.dir }})</span\n >\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <seam-alterations-diff\n [currentItems]=\"(_alterationsDisplayItems$ | async) ?? []\"\n [pendingItems]=\"(_pendingAlterationsDisplayItems$ | async) ?? []\"\n [compact]=\"compact\"\n [diffMode]=\"diffMode\"\n ></seam-alterations-diff>\n </div>\n</div>\n", styles: [":host{display:block;overflow:hidden}\n"] }]
980
+ }], propDecorators: { diffMode: [{
981
+ type: Input
982
+ }], compact: [{
983
+ type: Input
984
+ }], prompt: [{
985
+ type: Input
986
+ }], datatable: [{
987
+ type: Input
988
+ }], showAlts: [{
989
+ type: Input
990
+ }] } });
2
991
 
3
992
  /**
4
993
  * Generated bundle index. Do not edit.
5
994
  */
995
+
996
+ export { LmStudioAiProvider, MockAiProvider, OpenRouterAiProvider, THESEAM_CHAT_BLOCK_REGISTRY, THESEAM_CHAT_PROVIDER, THESEAM_DATATABLE_PROMPTER_PROVIDER, TheSeamChatComponent, TheSeamChatHarness, TheSeamDatatablePrompterComponent, assistantPrompt, getUserPrompt, parseChatResponse, parseResponse };
6
997
  //# sourceMappingURL=theseam-ui-common-ai.mjs.map