@shival99/z-ui 2.2.0 → 2.2.2
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/fesm2022/shival99-z-ui-components-z-chat.mjs +102 -47
- package/fesm2022/shival99-z-ui-components-z-chat.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-table.mjs +353 -40
- package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
- package/package.json +1 -1
- package/types/shival99-z-ui-components-z-chat.d.ts +26 -3
- package/types/shival99-z-ui-components-z-table.d.ts +99 -3
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, model, output, signal, viewChild, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { input, model, output, signal, viewChild, viewChildren, afterRenderEffect, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormsModule } from '@angular/forms';
|
|
5
5
|
import { TranslatePipe } from '@ngx-translate/core';
|
|
6
6
|
import { ZIconComponent } from '@shival99/z-ui/components/z-icon';
|
|
7
7
|
import { ZModalComponent } from '@shival99/z-ui/components/z-modal';
|
|
8
8
|
import { zTransform, zMergeClasses, zUuid } from '@shival99/z-ui/utils';
|
|
9
|
+
import { injectVirtualizer, elementScroll } from '@tanstack/angular-virtual';
|
|
9
10
|
import { NgScrollbar } from 'ngx-scrollbar';
|
|
10
11
|
import { cva } from 'class-variance-authority';
|
|
11
12
|
|
|
13
|
+
/** Renders a pin gets to land the list on the newest message before it gives up. */
|
|
14
|
+
const Z_CHAT_PIN_TO_NEWEST_ATTEMPTS = 12;
|
|
15
|
+
/** Gợi ý mặc định hiển thị khi khung chat chưa có tin nhắn nào. */
|
|
16
|
+
const Z_CHAT_DEFAULT_SUGGESTIONS = [
|
|
17
|
+
{
|
|
18
|
+
label: 'i18n_z_ui_chat_ai_suggestion_1',
|
|
19
|
+
prompt: 'Create release notes from recent commits',
|
|
20
|
+
icon: 'lucideSparkles',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: 'i18n_z_ui_chat_ai_suggestion_2',
|
|
24
|
+
prompt: 'Suggest test cases for a new feature',
|
|
25
|
+
icon: 'lucideFlaskConical',
|
|
26
|
+
},
|
|
27
|
+
{ label: 'i18n_z_ui_chat_ai_suggestion_3', prompt: 'Generate a concise PR summary', icon: 'lucideGitPullRequest' },
|
|
28
|
+
];
|
|
29
|
+
|
|
12
30
|
const zChatWrapperVariants = cva('z-chat fixed z-[95] pointer-events-none', {
|
|
13
31
|
variants: {
|
|
14
32
|
zPosition: {
|
|
@@ -54,19 +72,6 @@ const zChatFabVariants = cva('relative inline-flex size-14 items-center justify-
|
|
|
54
72
|
},
|
|
55
73
|
});
|
|
56
74
|
|
|
57
|
-
const DEFAULT_SUGGESTIONS = [
|
|
58
|
-
{
|
|
59
|
-
label: 'i18n_z_ui_chat_ai_suggestion_1',
|
|
60
|
-
prompt: 'Create release notes from recent commits',
|
|
61
|
-
icon: 'lucideSparkles',
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
label: 'i18n_z_ui_chat_ai_suggestion_2',
|
|
65
|
-
prompt: 'Suggest test cases for a new feature',
|
|
66
|
-
icon: 'lucideFlaskConical',
|
|
67
|
-
},
|
|
68
|
-
{ label: 'i18n_z_ui_chat_ai_suggestion_3', prompt: 'Generate a concise PR summary', icon: 'lucideGitPullRequest' },
|
|
69
|
-
];
|
|
70
75
|
class ZChatComponent {
|
|
71
76
|
class = input('', /* @ts-ignore */
|
|
72
77
|
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
@@ -86,7 +91,7 @@ class ZChatComponent {
|
|
|
86
91
|
...(ngDevMode ? [{ debugName: "zOffset" }] : /* istanbul ignore next */ []));
|
|
87
92
|
zSize = input('default', /* @ts-ignore */
|
|
88
93
|
...(ngDevMode ? [{ debugName: "zSize" }] : /* istanbul ignore next */ []));
|
|
89
|
-
zSuggestions = input(
|
|
94
|
+
zSuggestions = input(Z_CHAT_DEFAULT_SUGGESTIONS, /* @ts-ignore */
|
|
90
95
|
...(ngDevMode ? [{ debugName: "zSuggestions" }] : /* istanbul ignore next */ []));
|
|
91
96
|
zShowSuggestions = input(true, { ...(ngDevMode ? { debugName: "zShowSuggestions" } : /* istanbul ignore next */ {}), transform: zTransform });
|
|
92
97
|
zShowPulse = input(true, { ...(ngDevMode ? { debugName: "zShowPulse" } : /* istanbul ignore next */ {}), transform: zTransform });
|
|
@@ -130,9 +135,69 @@ class ZChatComponent {
|
|
|
130
135
|
...(ngDevMode ? [{ debugName: "_chatInputRef" }] : /* istanbul ignore next */ []));
|
|
131
136
|
_fileInputRef = viewChild('fileInput', /* @ts-ignore */
|
|
132
137
|
...(ngDevMode ? [{ debugName: "_fileInputRef" }] : /* istanbul ignore next */ []));
|
|
138
|
+
_virtualMessageRefs = viewChildren('virtualMessage', /* @ts-ignore */
|
|
139
|
+
...(ngDevMode ? [{ debugName: "_virtualMessageRefs" }] : /* istanbul ignore next */ []));
|
|
140
|
+
/** Signal, not a field: requesting a pin must itself wake `_pinToNewest`. */
|
|
141
|
+
_pinAttemptsLeft = signal(0, /* @ts-ignore */
|
|
142
|
+
...(ngDevMode ? [{ debugName: "_pinAttemptsLeft" }] : /* istanbul ignore next */ []));
|
|
133
143
|
_mockReplyTimer = null;
|
|
134
144
|
_rippleTimer = null;
|
|
135
|
-
|
|
145
|
+
// Declared before the virtualizer so measurement runs before its post-render scroll
|
|
146
|
+
// reconciliation — otherwise anchoring settles against stale sizes.
|
|
147
|
+
_measureMessages = afterRenderEffect({
|
|
148
|
+
mixedReadWrite: () => {
|
|
149
|
+
for (const ref of this._virtualMessageRefs()) {
|
|
150
|
+
this.messageVirtualizer.measureElement(ref.nativeElement);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
messageVirtualizer = injectVirtualizer(() => ({
|
|
155
|
+
scrollElement: this._getMessagesViewportElement(),
|
|
156
|
+
count: this.zMessages().length,
|
|
157
|
+
// Seed only: every row is measured, so this just sizes rows not yet rendered.
|
|
158
|
+
estimateSize: () => 74,
|
|
159
|
+
getItemKey: (index) => this.zMessages()[index]?.id ?? index,
|
|
160
|
+
// Chat semantics: pin to the bottom, and follow new messages only when already pinned
|
|
161
|
+
// there, so a reader scrolled up into history is never yanked down by an incoming reply.
|
|
162
|
+
anchorTo: 'end',
|
|
163
|
+
followOnAppend: true,
|
|
164
|
+
scrollEndThreshold: 80,
|
|
165
|
+
overscan: 10,
|
|
166
|
+
useApplicationRefTick: false,
|
|
167
|
+
// ng-scrollbar does not emit a scroll event for a programmatic scrollTo, so the virtualizer
|
|
168
|
+
// would keep rendering the old offset while the box sits somewhere else — a viewport stuck on
|
|
169
|
+
// stale rows. Push the offset in by hand, exactly as z-table does over the same scroller.
|
|
170
|
+
scrollToFn: (offset, options, instance) => {
|
|
171
|
+
instance.scrollOffset = offset;
|
|
172
|
+
elementScroll(offset, options, instance);
|
|
173
|
+
},
|
|
174
|
+
}));
|
|
175
|
+
/**
|
|
176
|
+
* Drives a requested pin to completion across renders.
|
|
177
|
+
*
|
|
178
|
+
* One `scrollToEnd()` is not enough on a cold list: the end is computed from estimates, and it
|
|
179
|
+
* moves as rows are measured. The scroll box also arrives late — ng-scrollbar reports its
|
|
180
|
+
* viewport only after init, and a scroll issued before then leaves the virtualizer at offset 0
|
|
181
|
+
* with no event to resync it. Re-asserting each render until `isAtEnd` holds covers both.
|
|
182
|
+
*/
|
|
183
|
+
_pinToNewest = afterRenderEffect({
|
|
184
|
+
mixedReadWrite: () => {
|
|
185
|
+
// Tracked so this re-runs as rows mount and measurements land.
|
|
186
|
+
this.messageVirtualizer.getVirtualItems();
|
|
187
|
+
const attemptsLeft = this._pinAttemptsLeft();
|
|
188
|
+
const hasRows = this.zMessages().length > 0;
|
|
189
|
+
if (attemptsLeft <= 0 || !this.zOpen() || !hasRows || !this._getMessagesViewportElement()) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (this.messageVirtualizer.isAtEnd(1)) {
|
|
193
|
+
this._pinAttemptsLeft.set(0);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// Decrementing re-triggers this effect, so the pin keeps re-asserting until it lands.
|
|
197
|
+
this._pinAttemptsLeft.set(attemptsLeft - 1);
|
|
198
|
+
this.messageVirtualizer.scrollToEnd();
|
|
199
|
+
},
|
|
200
|
+
});
|
|
136
201
|
wrapperClasses = computed(() => zMergeClasses(zChatWrapperVariants({ zPosition: this.zPosition() }), this.class()), /* @ts-ignore */
|
|
137
202
|
...(ngDevMode ? [{ debugName: "wrapperClasses" }] : /* istanbul ignore next */ []));
|
|
138
203
|
wrapperTop = computed(() => this._resolveOffsetValue('top'), /* @ts-ignore */
|
|
@@ -159,6 +224,12 @@ class ZChatComponent {
|
|
|
159
224
|
...(ngDevMode ? [{ debugName: "canSend" }] : /* istanbul ignore next */ []));
|
|
160
225
|
hasMessages = computed(() => this.zMessages().length > 0, /* @ts-ignore */
|
|
161
226
|
...(ngDevMode ? [{ debugName: "hasMessages" }] : /* istanbul ignore next */ []));
|
|
227
|
+
/** Only the newest bubble plays the push-in animation; recycled rows must not replay it. */
|
|
228
|
+
newestMessageId = computed(() => {
|
|
229
|
+
const messages = this.zMessages();
|
|
230
|
+
return messages.length > 0 ? messages[messages.length - 1].id : null;
|
|
231
|
+
}, /* @ts-ignore */
|
|
232
|
+
...(ngDevMode ? [{ debugName: "newestMessageId" }] : /* istanbul ignore next */ []));
|
|
162
233
|
showEmptyState = computed(() => !this.hasMessages() && this.zShowSuggestions(), /* @ts-ignore */
|
|
163
234
|
...(ngDevMode ? [{ debugName: "showEmptyState" }] : /* istanbul ignore next */ []));
|
|
164
235
|
previewImageSizeLabel = computed(() => {
|
|
@@ -173,9 +244,6 @@ class ZChatComponent {
|
|
|
173
244
|
if (this._rippleTimer) {
|
|
174
245
|
clearTimeout(this._rippleTimer);
|
|
175
246
|
}
|
|
176
|
-
if (this._scrollTimer) {
|
|
177
|
-
clearTimeout(this._scrollTimer);
|
|
178
|
-
}
|
|
179
247
|
const filesInMessages = this.zMessages().flatMap(item => item.files ?? []);
|
|
180
248
|
this._releaseAttachmentUrls(filesInMessages);
|
|
181
249
|
this._releaseAttachmentUrls(this.attachments());
|
|
@@ -334,9 +402,7 @@ class ZChatComponent {
|
|
|
334
402
|
this._triggerRipple();
|
|
335
403
|
if (isOpen) {
|
|
336
404
|
this._focusInputSoon();
|
|
337
|
-
|
|
338
|
-
this._scheduleScrollToBottom(250);
|
|
339
|
-
});
|
|
405
|
+
this._ensureBottomVisible();
|
|
340
406
|
}
|
|
341
407
|
}
|
|
342
408
|
_createMessage(role, content, files) {
|
|
@@ -393,31 +459,20 @@ class ZChatComponent {
|
|
|
393
459
|
this.closeImagePreview();
|
|
394
460
|
}
|
|
395
461
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
462
|
+
/**
|
|
463
|
+
* Requests a pin to the newest message; `_pinToNewest` performs it once rendering can honour it.
|
|
464
|
+
*
|
|
465
|
+
* Never scroll synchronously from here. Callers fire right after mutating `zMessages` or
|
|
466
|
+
* `zOpen`, before change detection has fed the new length into the virtualizer, so the scroll
|
|
467
|
+
* would target the previous last index and strand the list mid-history.
|
|
468
|
+
*/
|
|
404
469
|
_ensureBottomVisible() {
|
|
405
|
-
this.
|
|
406
|
-
requestAnimationFrame(() => {
|
|
407
|
-
this._scrollToBottom(0);
|
|
408
|
-
this._scheduleScrollToBottom(90, 0);
|
|
409
|
-
});
|
|
470
|
+
this._pinAttemptsLeft.set(Z_CHAT_PIN_TO_NEWEST_ATTEMPTS);
|
|
410
471
|
}
|
|
411
|
-
|
|
472
|
+
/** ng-scrollbar owns the real scrolling box; the virtualizer must observe that, not the host. */
|
|
473
|
+
_getMessagesViewportElement() {
|
|
412
474
|
const scrollbar = this._messagesScrollbarRef();
|
|
413
|
-
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
void scrollbar.scrollTo({
|
|
417
|
-
bottom: 0,
|
|
418
|
-
duration,
|
|
419
|
-
easing: { x1: 0.25, y1: 0.1, x2: 0.25, y2: 1 },
|
|
420
|
-
});
|
|
475
|
+
return scrollbar?.adapter.initialized() ? scrollbar.adapter.viewportElement : undefined;
|
|
421
476
|
}
|
|
422
477
|
_focusInputSoon() {
|
|
423
478
|
setTimeout(() => this._chatInputRef()?.nativeElement.focus(), 50);
|
|
@@ -460,15 +515,15 @@ class ZChatComponent {
|
|
|
460
515
|
return defaults[side];
|
|
461
516
|
}
|
|
462
517
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ZChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
463
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ZChatComponent, isStandalone: true, selector: "z-chat", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, zOpen: { classPropertyName: "zOpen", publicName: "zOpen", isSignal: true, isRequired: false, transformFunction: null }, zMessages: { classPropertyName: "zMessages", publicName: "zMessages", isSignal: true, isRequired: false, transformFunction: null }, zTitle: { classPropertyName: "zTitle", publicName: "zTitle", isSignal: true, isRequired: false, transformFunction: null }, zSubtitle: { classPropertyName: "zSubtitle", publicName: "zSubtitle", isSignal: true, isRequired: false, transformFunction: null }, zPlaceholder: { classPropertyName: "zPlaceholder", publicName: "zPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, zPosition: { classPropertyName: "zPosition", publicName: "zPosition", isSignal: true, isRequired: false, transformFunction: null }, zOffset: { classPropertyName: "zOffset", publicName: "zOffset", isSignal: true, isRequired: false, transformFunction: null }, zSize: { classPropertyName: "zSize", publicName: "zSize", isSignal: true, isRequired: false, transformFunction: null }, zSuggestions: { classPropertyName: "zSuggestions", publicName: "zSuggestions", isSignal: true, isRequired: false, transformFunction: null }, zShowSuggestions: { classPropertyName: "zShowSuggestions", publicName: "zShowSuggestions", isSignal: true, isRequired: false, transformFunction: null }, zShowPulse: { classPropertyName: "zShowPulse", publicName: "zShowPulse", isSignal: true, isRequired: false, transformFunction: null }, zDisabled: { classPropertyName: "zDisabled", publicName: "zDisabled", isSignal: true, isRequired: false, transformFunction: null }, zLoading: { classPropertyName: "zLoading", publicName: "zLoading", isSignal: true, isRequired: false, transformFunction: null }, zAllowAttachments: { classPropertyName: "zAllowAttachments", publicName: "zAllowAttachments", isSignal: true, isRequired: false, transformFunction: null }, zAcceptedFiles: { classPropertyName: "zAcceptedFiles", publicName: "zAcceptedFiles", isSignal: true, isRequired: false, transformFunction: null }, zAutoReply: { classPropertyName: "zAutoReply", publicName: "zAutoReply", isSignal: true, isRequired: false, transformFunction: null }, zAutoReplyDelay: { classPropertyName: "zAutoReplyDelay", publicName: "zAutoReplyDelay", isSignal: true, isRequired: false, transformFunction: null }, zFabIcon: { classPropertyName: "zFabIcon", publicName: "zFabIcon", isSignal: true, isRequired: false, transformFunction: null }, zBotIcon: { classPropertyName: "zBotIcon", publicName: "zBotIcon", isSignal: true, isRequired: false, transformFunction: null }, zSendIcon: { classPropertyName: "zSendIcon", publicName: "zSendIcon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zOpen: "zOpenChange", zMessages: "zMessagesChange", zSend: "zSend", zResponse: "zResponse", zToggle: "zToggle", zClear: "zClear" }, host: { listeners: { "document:keydown.escape": "onEscapeKey()" }, classAttribute: "z-chat block" }, viewQueries: [{ propertyName: "_messagesScrollbarRef", first: true, predicate: ["messagesScrollbar"], descendants: true, isSignal: true }, { propertyName: "_chatInputRef", first: true, predicate: ["chatInput"], descendants: true, isSignal: true }, { propertyName: "_fileInputRef", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], exportAs: ["zChat"], ngImport: i0, template: "<div\n [class]=\"wrapperClasses()\"\n [style.top]=\"wrapperTop()\"\n [style.right]=\"wrapperRight()\"\n [style.bottom]=\"wrapperBottom()\"\n [style.left]=\"wrapperLeft()\">\n <div class=\"relative\">\n <section class=\"z-chat-panel-shell\" [class]=\"panelClasses()\">\n <header class=\"from-primary/95 to-primary/75 text-primary-foreground bg-gradient-to-r px-4 py-3.5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <span\n class=\"bg-primary-foreground/15 relative inline-flex size-8 shrink-0 items-center justify-center rounded-full\">\n <z-icon [zType]=\"zBotIcon()\" zSize=\"16\" class=\"text-primary-foreground\" />\n <span\n class=\"ring-primary inline-block size-2.5 rounded-full bg-emerald-300 ring-2\"\n style=\"position: absolute; right: -0.0625rem; bottom: -0.0625rem\"></span>\n </span>\n <h3 class=\"truncate text-sm font-semibold\">{{ zTitle() | translate }}</h3>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"clearMessages()\"\n title=\"{{ 'i18n_z_ui_chat_ai_new_chat' | translate }}\">\n <z-icon zType=\"lucideRefreshCcw\" zSize=\"17\" />\n </button>\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"close()\"\n title=\"{{ 'i18n_z_ui_chat_ai_close' | translate }}\">\n <z-icon zType=\"lucideChevronDown\" zSize=\"18\" />\n </button>\n </div>\n </div>\n </header>\n\n <div class=\"z-chat-lower-shell\">\n <ng-scrollbar\n #messagesScrollbar\n (afterInit)=\"onMessagesScrollbarInit()\"\n class=\"z-chat-scrollbar z-chat-content-surface flex-1 overflow-x-hidden\"\n track=\"vertical\"\n orientation=\"vertical\"\n appearance=\"compact\"\n visibility=\"hover\">\n <div class=\"z-chat-messages\">\n @if (hasMessages()) {\n @for (item of zMessages(); track item.id) {\n <div\n class=\"z-chat-message z-chat-message--enter\"\n [class.justify-end]=\"item.role === 'user'\"\n [class.justify-start]=\"item.role === 'assistant'\">\n <div\n class=\"z-chat-message-bubble max-w-[88%] min-w-0 rounded-2xl px-3 py-2.5 text-sm leading-5 [overflow-wrap:anywhere] break-all shadow-xs\"\n [class.bg-primary]=\"item.role === 'user'\"\n [class.text-primary-foreground]=\"item.role === 'user'\"\n [class.rounded-br-md]=\"item.role === 'user'\"\n [class.bg-background]=\"item.role === 'assistant'\"\n [class.border-border/60]=\"item.role === 'assistant'\"\n [class.text-foreground]=\"item.role === 'assistant'\"\n [class.rounded-bl-md]=\"item.role === 'assistant'\"\n [class.border]=\"item.role === 'assistant'\">\n @if (item.files && item.files.length > 0) {\n <div class=\"mb-2 flex flex-col gap-1.5\">\n @for (file of item.files; track file.id) {\n <div\n class=\"flex items-center gap-2 rounded-[0.5rem] px-2 py-1.5 text-xs\"\n [class.bg-white/15]=\"item.role === 'user'\"\n [class.bg-muted/40]=\"item.role === 'assistant'\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background/80 text-muted-foreground inline-flex size-8 items-center justify-center rounded-[0.5rem]\"\n [class.bg-white/20]=\"item.role === 'user'\"\n [class.text-white]=\"item.role === 'user'\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"truncate text-[0.625rem] opacity-80\">{{ formatFileSize(file.size) }}</p>\n </div>\n </div>\n }\n </div>\n }\n {{ item.content }}\n </div>\n </div>\n }\n } @else if (showEmptyState()) {\n <div class=\"flex h-full min-h-60 flex-col items-center justify-center px-2 text-center\">\n <div\n class=\"from-primary/20 to-primary/5 mb-3 inline-flex size-14 items-center justify-center rounded-full bg-gradient-to-br\">\n <z-icon zType=\"lucideBot\" zSize=\"28\" class=\"text-primary\" />\n </div>\n <h4 class=\"text-foreground text-sm font-semibold\">{{ 'i18n_z_ui_chat_ai_empty_title' | translate }}</h4>\n <p class=\"text-muted-foreground mt-1 text-xs\">{{ 'i18n_z_ui_chat_ai_empty_desc' | translate }}</p>\n\n @if (zSuggestions().length > 0) {\n <div class=\"mt-4 flex w-full flex-col gap-2\">\n @for (item of zSuggestions(); track item.label) {\n <button\n type=\"button\"\n class=\"z-chat-suggestion-item bg-background/95 hover:bg-background border-border/60 text-foreground flex w-full cursor-pointer items-center gap-2 rounded-xl border px-3 py-2 text-left text-xs transition hover:shadow-sm\"\n [style.animation-delay.ms]=\"$index * 70\"\n (click)=\"onSuggestionSelect(item)\">\n @if (item.icon) {\n <z-icon [zType]=\"item.icon!\" zSize=\"14\" class=\"text-primary shrink-0\" />\n }\n <span class=\"line-clamp-2\">{{ item.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (isTyping()) {\n <div class=\"z-chat-message z-chat-message--enter justify-start\">\n <div class=\"bg-background border-border/60 rounded-2xl rounded-bl-md border px-3 py-2\">\n <div class=\"flex items-center gap-1\">\n <span class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.15s\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.3s\"></span>\n </div>\n </div>\n </div>\n }\n </div>\n </ng-scrollbar>\n\n <footer class=\"z-chat-footer-surface p-3\">\n @if (attachments().length > 0) {\n <div class=\"mb-2 flex max-h-32 flex-col gap-2 overflow-y-auto pr-1\">\n @for (file of attachments(); track file.id) {\n <div\n class=\"bg-muted/45 border-border/55 relative flex w-full items-center gap-2 rounded-[0.5rem] border px-2 py-1.5\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 shrink-0 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background text-muted-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-[0.5rem]\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"text-muted-foreground truncate text-[0.625rem]\">{{ formatFileSize(file.size) }}</p>\n </div>\n <button\n type=\"button\"\n class=\"bg-background/80 hover:bg-background text-muted-foreground inline-flex size-5 cursor-pointer items-center justify-center rounded-full transition\"\n (click)=\"removeAttachment(file.id)\">\n <z-icon zType=\"lucideX\" zSize=\"12\" />\n </button>\n </div>\n }\n </div>\n }\n\n <div class=\"z-chat-composer-shell\">\n <div class=\"z-chat-composer-main\" [class.z-chat-composer__field--overflow]=\"isComposerOverflowing()\">\n <textarea\n #chatInput\n class=\"z-chat-composer-textarea\"\n [placeholder]=\"zPlaceholder() | translate\"\n [disabled]=\"zDisabled()\"\n [ngModel]=\"draftMessage()\"\n (ngModelChange)=\"draftMessage.set($event)\"\n (input)=\"onTextareaInput($event)\"\n (paste)=\"onTextareaPaste($event)\"\n (keydown)=\"onInputKeydown($event)\"\n rows=\"3\"></textarea>\n </div>\n\n <div class=\"z-chat-composer-toolbar\">\n <div class=\"z-chat-composer-toolbar-left\">\n @if (zAllowAttachments()) {\n <input\n #fileInput\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"zAcceptedFiles()\"\n multiple\n (change)=\"onFileChange($event)\" />\n <button\n type=\"button\"\n class=\"z-chat-composer-tool-btn\"\n [disabled]=\"zDisabled()\"\n (click)=\"onClickAttach()\"\n title=\"{{ 'i18n_z_ui_chat_ai_attach' | translate }}\">\n <z-icon zType=\"lucidePlus\" zSize=\"18\" />\n </button>\n }\n </div>\n\n <div class=\"z-chat-composer-toolbar-right\">\n <button\n type=\"button\"\n class=\"z-chat-composer-send-btn\"\n [disabled]=\"!canSend()\"\n (click)=\"onSendMessage()\"\n title=\"{{ 'i18n_z_ui_chat_ai_send' | translate }}\">\n <z-icon\n [zType]=\"isTyping() ? 'lucideLoaderCircle' : zSendIcon()\"\n zSize=\"16\"\n [class]=\"isTyping() ? 'animate-spin' : ''\" />\n </button>\n </div>\n </div>\n </div>\n </footer>\n </div>\n\n <z-modal\n [zVisible]=\"isPreviewVisible()\"\n [zTitle]=\"previewImage()?.name\"\n [zDescription]=\"previewImageSizeLabel()\"\n zWidth=\"min(92vw, 720px)\"\n [zHideFooter]=\"true\"\n [zMaskClosable]=\"true\"\n zOverlay=\"blur\"\n (zCancel)=\"closeImagePreview()\">\n <div class=\"z-chat-preview-modal-body\">\n <img [src]=\"previewImage()?.url\" [alt]=\"previewImage()?.name\" class=\"z-chat-preview-image\" />\n </div>\n </z-modal>\n </section>\n\n <button\n type=\"button\"\n class=\"pointer-events-auto cursor-pointer\"\n [class]=\"fabClasses()\"\n (click)=\"onToggle()\"\n [attr.aria-expanded]=\"zOpen()\"\n [attr.aria-label]=\"'i18n_z_ui_chat_ai_toggle' | translate\"\n title=\"{{ 'i18n_z_ui_chat_ai_toggle' | translate }}\">\n @if (zShowPulse() && !zOpen()) {\n <span class=\"bg-primary/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n @if (showRipple()) {\n <span class=\"bg-primary-foreground/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n <z-icon\n [zType]=\"zOpen() ? 'lucideX' : zFabIcon()\"\n zSize=\"24\"\n class=\"relative z-1 transition-transform duration-200\"\n [class.rotate-180]=\"zOpen()\" />\n </button>\n </div>\n</div>\n", styles: [".z-chat textarea{scrollbar-width:thin}.z-chat-panel-shell{box-shadow:0 0 28px #00000024!important}:is(.dark,[data-theme=dark]) .z-chat-panel-shell{box-shadow:0 0 28px #00000057!important}.z-chat-composer__field--overflow:after{position:absolute;right:0;bottom:0;left:0;height:1rem;pointer-events:none;content:\"\";background:linear-gradient(to top,var(--background) 22%,transparent)}.z-chat-composer-shell{position:relative;display:flex;width:100%;flex-direction:column;border:1px solid color-mix(in oklab,var(--border) 82%,transparent);border-radius:1rem;background:linear-gradient(180deg,color-mix(in oklab,var(--card) 98%,var(--background) 2%),color-mix(in oklab,var(--card) 92%,var(--background) 8%));box-shadow:0 6px 18px #00000014;transition:box-shadow .2s ease}.z-chat-composer-shell:focus-within{box-shadow:0 6px 18px #00000014}:is(.dark,[data-theme=dark]) .z-chat-composer-shell{border-color:var(--border);background:linear-gradient(180deg,color-mix(in oklab,var(--card) 96%,var(--background) 4%),color-mix(in oklab,var(--card) 88%,var(--background) 12%));box-shadow:0 10px 24px #00000057}.z-chat-composer-main{position:relative;overflow:hidden;padding:.7rem .35rem .45rem .85rem}.z-chat-composer-textarea{width:100%;height:4.125rem;max-height:4.125rem;border:none;background:transparent;resize:none;overflow-x:hidden;font-size:.875rem;line-height:1.4rem;color:var(--foreground);outline:none;padding-right:.2rem;scrollbar-width:thin;overflow-wrap:anywhere;word-break:break-word}.z-chat-composer-textarea::placeholder{color:var(--muted-foreground)}.z-chat-composer-textarea::-webkit-scrollbar{width:.375rem}.z-chat-composer-textarea::-webkit-scrollbar-track{background:transparent}.z-chat-composer-textarea::-webkit-scrollbar-thumb{border-radius:62.4375rem;background:#64748b73}:is(.dark,[data-theme=dark]) .z-chat-composer-textarea::-webkit-scrollbar-thumb{background:#94a3b88c}.z-chat-composer-toolbar{display:flex;align-items:center;justify-content:space-between;border-top:.0625rem solid rgba(219,219,219,.6);padding:.55rem .7rem .65rem}:is(.dark,[data-theme=dark]) .z-chat-composer-toolbar{border-top-color:#00000038}.z-chat-composer-toolbar-left,.z-chat-composer-toolbar-right{display:flex;align-items:center}.z-chat-composer-tool-btn{display:inline-flex;height:1.75rem;width:1.75rem;cursor:pointer;align-items:center;justify-content:center;border-radius:.6rem;border:none;background:transparent;color:#374151;transition:background-color .2s ease}.z-chat-composer-tool-btn:hover{background:#0000000f}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn{color:#e5e7ebe6}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn:hover{background:#ffffff1a}.z-chat-composer-tool-btn:disabled{cursor:not-allowed;opacity:.5}.z-chat-composer-send-btn{display:inline-flex;height:2rem;width:2rem;cursor:pointer;align-items:center;justify-content:center;border-radius:62.4375rem;border:1px solid rgba(0,0,0,.1);background:var(--primary);color:var(--primary-foreground);box-shadow:0 2px 8px #0000002e,inset 0 1px #ffffff2e;transition:transform .15s ease,opacity .2s ease,box-shadow .2s ease}.z-chat-composer-send-btn:hover{transform:translateY(-.0625rem);box-shadow:0 4px 12px #00000038,inset 0 1px #ffffff38}.z-chat-composer-send-btn:disabled{cursor:not-allowed;opacity:.45;box-shadow:none}.z-chat-scrollbar{contain:layout}.z-chat-content-surface{background:var(--card)}.z-chat-lower-shell{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden;background:var(--card);color:var(--card-foreground);border:1px solid transparent;border-top:0;border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}:is(.dark,[data-theme=dark]) .z-chat-lower-shell{border-color:var(--border);border-top:0}.z-chat-footer-surface{background:transparent;border-top:.0625rem solid color-mix(in oklab,var(--border) 70%,transparent)}:is(.dark,[data-theme=dark]) .z-chat-footer-surface{background:transparent;border-top-color:var(--border)}.z-chat-messages{min-height:100%;overflow-x:hidden;padding:.875rem .75rem;contain:content}.z-chat-message{display:flex;margin:.5rem 0;padding:0 2px;transform-origin:bottom center;contain:layout style}.z-chat-message-bubble{transform:translateZ(0);backface-visibility:hidden}.z-chat-message--enter{animation:z-chat-message-push-in .3s ease-out;will-change:transform,opacity}.z-chat-suggestion-item{animation:z-chat-suggestion-reveal .28s ease-out both;will-change:transform,opacity}.z-chat-preview-modal-body{display:flex;align-items:center;justify-content:center;max-height:min(70vh,40rem);padding:.25rem}.z-chat-preview-image{max-width:100%;max-height:min(66vh,37.5rem);border-radius:.5rem;object-fit:contain}@keyframes z-chat-message-push-in{0%{opacity:0;transform:translateY(.9375rem) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes z-chat-suggestion-reveal{0%{opacity:0;transform:translateY(.5rem) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ZIconComponent, selector: "z-icon, [z-icon]", inputs: ["class", "zType", "zAnimatedType", "zAnimate", "zAnimationTrigger", "zSize", "zStrokeWidth", "zSvg"] }, { kind: "component", type: ZModalComponent, selector: "z-modal", inputs: ["class", "zBodyClass", "zVisible", "zTitle", "zDescription", "zWidth", "zClosable", "zMaskClosable", "zDisableShadow", "zHeaderBorder", "zFooterBorder", "zHideHeader", "zHideFooter", "zOkText", "zCancelText", "zOkDestructive", "zOkDisabled", "zLoading", "zContentLoading", "zSkeletonRows", "zOverlay"], outputs: ["zOk", "zCancel", "zAfterClose", "zScrollbar", "zVisibleChange"], exportAs: ["zModal"] }, { kind: "component", type: NgScrollbar, selector: "ng-scrollbar:not([externalViewport]), [ngScrollbar]", exportAs: ["ngScrollbar"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
518
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ZChatComponent, isStandalone: true, selector: "z-chat", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, zOpen: { classPropertyName: "zOpen", publicName: "zOpen", isSignal: true, isRequired: false, transformFunction: null }, zMessages: { classPropertyName: "zMessages", publicName: "zMessages", isSignal: true, isRequired: false, transformFunction: null }, zTitle: { classPropertyName: "zTitle", publicName: "zTitle", isSignal: true, isRequired: false, transformFunction: null }, zSubtitle: { classPropertyName: "zSubtitle", publicName: "zSubtitle", isSignal: true, isRequired: false, transformFunction: null }, zPlaceholder: { classPropertyName: "zPlaceholder", publicName: "zPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, zPosition: { classPropertyName: "zPosition", publicName: "zPosition", isSignal: true, isRequired: false, transformFunction: null }, zOffset: { classPropertyName: "zOffset", publicName: "zOffset", isSignal: true, isRequired: false, transformFunction: null }, zSize: { classPropertyName: "zSize", publicName: "zSize", isSignal: true, isRequired: false, transformFunction: null }, zSuggestions: { classPropertyName: "zSuggestions", publicName: "zSuggestions", isSignal: true, isRequired: false, transformFunction: null }, zShowSuggestions: { classPropertyName: "zShowSuggestions", publicName: "zShowSuggestions", isSignal: true, isRequired: false, transformFunction: null }, zShowPulse: { classPropertyName: "zShowPulse", publicName: "zShowPulse", isSignal: true, isRequired: false, transformFunction: null }, zDisabled: { classPropertyName: "zDisabled", publicName: "zDisabled", isSignal: true, isRequired: false, transformFunction: null }, zLoading: { classPropertyName: "zLoading", publicName: "zLoading", isSignal: true, isRequired: false, transformFunction: null }, zAllowAttachments: { classPropertyName: "zAllowAttachments", publicName: "zAllowAttachments", isSignal: true, isRequired: false, transformFunction: null }, zAcceptedFiles: { classPropertyName: "zAcceptedFiles", publicName: "zAcceptedFiles", isSignal: true, isRequired: false, transformFunction: null }, zAutoReply: { classPropertyName: "zAutoReply", publicName: "zAutoReply", isSignal: true, isRequired: false, transformFunction: null }, zAutoReplyDelay: { classPropertyName: "zAutoReplyDelay", publicName: "zAutoReplyDelay", isSignal: true, isRequired: false, transformFunction: null }, zFabIcon: { classPropertyName: "zFabIcon", publicName: "zFabIcon", isSignal: true, isRequired: false, transformFunction: null }, zBotIcon: { classPropertyName: "zBotIcon", publicName: "zBotIcon", isSignal: true, isRequired: false, transformFunction: null }, zSendIcon: { classPropertyName: "zSendIcon", publicName: "zSendIcon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zOpen: "zOpenChange", zMessages: "zMessagesChange", zSend: "zSend", zResponse: "zResponse", zToggle: "zToggle", zClear: "zClear" }, host: { listeners: { "document:keydown.escape": "onEscapeKey()" }, classAttribute: "z-chat block" }, viewQueries: [{ propertyName: "_messagesScrollbarRef", first: true, predicate: ["messagesScrollbar"], descendants: true, isSignal: true }, { propertyName: "_chatInputRef", first: true, predicate: ["chatInput"], descendants: true, isSignal: true }, { propertyName: "_fileInputRef", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "_virtualMessageRefs", predicate: ["virtualMessage"], descendants: true, isSignal: true }], exportAs: ["zChat"], ngImport: i0, template: "<div\n [class]=\"wrapperClasses()\"\n [style.top]=\"wrapperTop()\"\n [style.right]=\"wrapperRight()\"\n [style.bottom]=\"wrapperBottom()\"\n [style.left]=\"wrapperLeft()\">\n <div class=\"relative\">\n <section class=\"z-chat-panel-shell\" [class]=\"panelClasses()\">\n <header class=\"from-primary/95 to-primary/75 text-primary-foreground bg-gradient-to-r px-4 py-3.5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <span\n class=\"bg-primary-foreground/15 relative inline-flex size-8 shrink-0 items-center justify-center rounded-full\">\n <z-icon [zType]=\"zBotIcon()\" zSize=\"16\" class=\"text-primary-foreground\" />\n <span\n class=\"ring-primary inline-block size-2.5 rounded-full bg-emerald-300 ring-2\"\n style=\"position: absolute; right: -0.0625rem; bottom: -0.0625rem\"></span>\n </span>\n <h3 class=\"truncate text-sm font-semibold\">{{ zTitle() | translate }}</h3>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"clearMessages()\"\n title=\"{{ 'i18n_z_ui_chat_ai_new_chat' | translate }}\">\n <z-icon zType=\"lucideRefreshCcw\" zSize=\"17\" />\n </button>\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"close()\"\n title=\"{{ 'i18n_z_ui_chat_ai_close' | translate }}\">\n <z-icon zType=\"lucideChevronDown\" zSize=\"18\" />\n </button>\n </div>\n </div>\n </header>\n\n <div class=\"z-chat-lower-shell\">\n <ng-scrollbar\n #messagesScrollbar\n (afterInit)=\"onMessagesScrollbarInit()\"\n class=\"z-chat-scrollbar z-chat-content-surface flex-1 overflow-x-hidden\"\n track=\"vertical\"\n orientation=\"vertical\"\n appearance=\"compact\"\n visibility=\"hover\">\n <div class=\"z-chat-messages\">\n @if (hasMessages()) {\n <div class=\"z-chat-messages-sizer\" [style.height.px]=\"messageVirtualizer.getTotalSize()\">\n @for (row of messageVirtualizer.getVirtualItems(); track row.key) {\n @let item = zMessages()[row.index];\n <div\n #virtualMessage\n class=\"z-chat-virtual-row\"\n [attr.data-index]=\"row.index\"\n [style.transform]=\"'translateY(' + row.start + 'px)'\">\n <div\n class=\"z-chat-message\"\n [class.z-chat-message--enter]=\"item.id === newestMessageId()\"\n [class.justify-end]=\"item.role === 'user'\"\n [class.justify-start]=\"item.role === 'assistant'\">\n <div\n class=\"z-chat-message-bubble max-w-[88%] min-w-0 rounded-2xl px-3 py-2.5 text-sm leading-5 [overflow-wrap:anywhere] break-all shadow-xs\"\n [class.bg-primary]=\"item.role === 'user'\"\n [class.text-primary-foreground]=\"item.role === 'user'\"\n [class.rounded-br-md]=\"item.role === 'user'\"\n [class.bg-background]=\"item.role === 'assistant'\"\n [class.border-border/60]=\"item.role === 'assistant'\"\n [class.text-foreground]=\"item.role === 'assistant'\"\n [class.rounded-bl-md]=\"item.role === 'assistant'\"\n [class.border]=\"item.role === 'assistant'\">\n @if (item.files && item.files.length > 0) {\n <div class=\"mb-2 flex flex-col gap-1.5\">\n @for (file of item.files; track file.id) {\n <div\n class=\"flex items-center gap-2 rounded-[0.5rem] px-2 py-1.5 text-xs\"\n [class.bg-white/15]=\"item.role === 'user'\"\n [class.bg-muted/40]=\"item.role === 'assistant'\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background/80 text-muted-foreground inline-flex size-8 items-center justify-center rounded-[0.5rem]\"\n [class.bg-white/20]=\"item.role === 'user'\"\n [class.text-white]=\"item.role === 'user'\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"truncate text-[0.625rem] opacity-80\">{{ formatFileSize(file.size) }}</p>\n </div>\n </div>\n }\n </div>\n }\n {{ item.content }}\n </div>\n </div>\n </div>\n }\n </div>\n } @else if (showEmptyState()) {\n <div class=\"flex h-full min-h-60 flex-col items-center justify-center px-2 text-center\">\n <div\n class=\"from-primary/20 to-primary/5 mb-3 inline-flex size-14 items-center justify-center rounded-full bg-gradient-to-br\">\n <z-icon zType=\"lucideBot\" zSize=\"28\" class=\"text-primary\" />\n </div>\n <h4 class=\"text-foreground text-sm font-semibold\">{{ 'i18n_z_ui_chat_ai_empty_title' | translate }}</h4>\n <p class=\"text-muted-foreground mt-1 text-xs\">{{ 'i18n_z_ui_chat_ai_empty_desc' | translate }}</p>\n\n @if (zSuggestions().length > 0) {\n <div class=\"mt-4 flex w-full flex-col gap-2\">\n @for (item of zSuggestions(); track item.label) {\n <button\n type=\"button\"\n class=\"z-chat-suggestion-item bg-background/95 hover:bg-background border-border/60 text-foreground flex w-full cursor-pointer items-center gap-2 rounded-xl border px-3 py-2 text-left text-xs transition hover:shadow-sm\"\n [style.animation-delay.ms]=\"$index * 70\"\n (click)=\"onSuggestionSelect(item)\">\n @if (item.icon) {\n <z-icon [zType]=\"item.icon!\" zSize=\"14\" class=\"text-primary shrink-0\" />\n }\n <span class=\"line-clamp-2\">{{ item.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (isTyping()) {\n <div class=\"z-chat-message z-chat-message--enter justify-start\">\n <div class=\"bg-background border-border/60 rounded-2xl rounded-bl-md border px-3 py-2\">\n <div class=\"flex items-center gap-1\">\n <span class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.15s\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.3s\"></span>\n </div>\n </div>\n </div>\n }\n </div>\n </ng-scrollbar>\n\n <footer class=\"z-chat-footer-surface p-3\">\n @if (attachments().length > 0) {\n <div class=\"mb-2 flex max-h-32 flex-col gap-2 overflow-y-auto pr-1\">\n @for (file of attachments(); track file.id) {\n <div\n class=\"bg-muted/45 border-border/55 relative flex w-full items-center gap-2 rounded-[0.5rem] border px-2 py-1.5\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 shrink-0 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background text-muted-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-[0.5rem]\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"text-muted-foreground truncate text-[0.625rem]\">{{ formatFileSize(file.size) }}</p>\n </div>\n <button\n type=\"button\"\n class=\"bg-background/80 hover:bg-background text-muted-foreground inline-flex size-5 cursor-pointer items-center justify-center rounded-full transition\"\n (click)=\"removeAttachment(file.id)\">\n <z-icon zType=\"lucideX\" zSize=\"12\" />\n </button>\n </div>\n }\n </div>\n }\n\n <div class=\"z-chat-composer-shell\">\n <div class=\"z-chat-composer-main\" [class.z-chat-composer__field--overflow]=\"isComposerOverflowing()\">\n <textarea\n #chatInput\n class=\"z-chat-composer-textarea\"\n [placeholder]=\"zPlaceholder() | translate\"\n [disabled]=\"zDisabled()\"\n [ngModel]=\"draftMessage()\"\n (ngModelChange)=\"draftMessage.set($event)\"\n (input)=\"onTextareaInput($event)\"\n (paste)=\"onTextareaPaste($event)\"\n (keydown)=\"onInputKeydown($event)\"\n rows=\"3\"></textarea>\n </div>\n\n <div class=\"z-chat-composer-toolbar\">\n <div class=\"z-chat-composer-toolbar-left\">\n @if (zAllowAttachments()) {\n <input\n #fileInput\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"zAcceptedFiles()\"\n multiple\n (change)=\"onFileChange($event)\" />\n <button\n type=\"button\"\n class=\"z-chat-composer-tool-btn\"\n [disabled]=\"zDisabled()\"\n (click)=\"onClickAttach()\"\n title=\"{{ 'i18n_z_ui_chat_ai_attach' | translate }}\">\n <z-icon zType=\"lucidePlus\" zSize=\"18\" />\n </button>\n }\n </div>\n\n <div class=\"z-chat-composer-toolbar-right\">\n <button\n type=\"button\"\n class=\"z-chat-composer-send-btn\"\n [disabled]=\"!canSend()\"\n (click)=\"onSendMessage()\"\n title=\"{{ 'i18n_z_ui_chat_ai_send' | translate }}\">\n <z-icon\n [zType]=\"isTyping() ? 'lucideLoaderCircle' : zSendIcon()\"\n zSize=\"16\"\n [class]=\"isTyping() ? 'animate-spin' : ''\" />\n </button>\n </div>\n </div>\n </div>\n </footer>\n </div>\n\n <z-modal\n [zVisible]=\"isPreviewVisible()\"\n [zTitle]=\"previewImage()?.name\"\n [zDescription]=\"previewImageSizeLabel()\"\n zWidth=\"min(92vw, 720px)\"\n [zHideFooter]=\"true\"\n [zMaskClosable]=\"true\"\n zOverlay=\"blur\"\n (zCancel)=\"closeImagePreview()\">\n <div class=\"z-chat-preview-modal-body\">\n <img [src]=\"previewImage()?.url\" [alt]=\"previewImage()?.name\" class=\"z-chat-preview-image\" />\n </div>\n </z-modal>\n </section>\n\n <button\n type=\"button\"\n class=\"pointer-events-auto cursor-pointer\"\n [class]=\"fabClasses()\"\n (click)=\"onToggle()\"\n [attr.aria-expanded]=\"zOpen()\"\n [attr.aria-label]=\"'i18n_z_ui_chat_ai_toggle' | translate\"\n title=\"{{ 'i18n_z_ui_chat_ai_toggle' | translate }}\">\n @if (zShowPulse() && !zOpen()) {\n <span class=\"bg-primary/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n @if (showRipple()) {\n <span class=\"bg-primary-foreground/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n <z-icon\n [zType]=\"zOpen() ? 'lucideX' : zFabIcon()\"\n zSize=\"24\"\n class=\"relative z-1 transition-transform duration-200\"\n [class.rotate-180]=\"zOpen()\" />\n </button>\n </div>\n</div>\n", styles: [".z-chat textarea{scrollbar-width:thin}.z-chat-panel-shell{box-shadow:0 0 28px #00000024!important}:is(.dark,[data-theme=dark]) .z-chat-panel-shell{box-shadow:0 0 28px #00000057!important}.z-chat-composer__field--overflow:after{position:absolute;right:0;bottom:0;left:0;height:1rem;pointer-events:none;content:\"\";background:linear-gradient(to top,var(--background) 22%,transparent)}.z-chat-composer-shell{position:relative;display:flex;width:100%;flex-direction:column;border:1px solid color-mix(in oklab,var(--border) 82%,transparent);border-radius:1rem;background:linear-gradient(180deg,color-mix(in oklab,var(--card) 98%,var(--background) 2%),color-mix(in oklab,var(--card) 92%,var(--background) 8%));box-shadow:0 6px 18px #00000014;transition:box-shadow .2s ease}.z-chat-composer-shell:focus-within{box-shadow:0 6px 18px #00000014}:is(.dark,[data-theme=dark]) .z-chat-composer-shell{border-color:var(--border);background:linear-gradient(180deg,color-mix(in oklab,var(--card) 96%,var(--background) 4%),color-mix(in oklab,var(--card) 88%,var(--background) 12%));box-shadow:0 10px 24px #00000057}.z-chat-composer-main{position:relative;overflow:hidden;padding:.7rem .35rem .45rem .85rem}.z-chat-composer-textarea{width:100%;height:4.125rem;max-height:4.125rem;border:none;background:transparent;resize:none;overflow-x:hidden;font-size:.875rem;line-height:1.4rem;color:var(--foreground);outline:none;padding-right:.2rem;scrollbar-width:thin;overflow-wrap:anywhere;word-break:break-word}.z-chat-composer-textarea::placeholder{color:var(--muted-foreground)}.z-chat-composer-textarea::-webkit-scrollbar{width:.375rem}.z-chat-composer-textarea::-webkit-scrollbar-track{background:transparent}.z-chat-composer-textarea::-webkit-scrollbar-thumb{border-radius:62.4375rem;background:#64748b73}:is(.dark,[data-theme=dark]) .z-chat-composer-textarea::-webkit-scrollbar-thumb{background:#94a3b88c}.z-chat-composer-toolbar{display:flex;align-items:center;justify-content:space-between;border-top:.0625rem solid rgba(219,219,219,.6);padding:.55rem .7rem .65rem}:is(.dark,[data-theme=dark]) .z-chat-composer-toolbar{border-top-color:#00000038}.z-chat-composer-toolbar-left,.z-chat-composer-toolbar-right{display:flex;align-items:center}.z-chat-composer-tool-btn{display:inline-flex;height:1.75rem;width:1.75rem;cursor:pointer;align-items:center;justify-content:center;border-radius:.6rem;border:none;background:transparent;color:#374151;transition:background-color .2s ease}.z-chat-composer-tool-btn:hover{background:#0000000f}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn{color:#e5e7ebe6}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn:hover{background:#ffffff1a}.z-chat-composer-tool-btn:disabled{cursor:not-allowed;opacity:.5}.z-chat-composer-send-btn{display:inline-flex;height:2rem;width:2rem;cursor:pointer;align-items:center;justify-content:center;border-radius:62.4375rem;border:1px solid rgba(0,0,0,.1);background:var(--primary);color:var(--primary-foreground);box-shadow:0 2px 8px #0000002e,inset 0 1px #ffffff2e;transition:transform .15s ease,opacity .2s ease,box-shadow .2s ease}.z-chat-composer-send-btn:hover{transform:translateY(-.0625rem);box-shadow:0 4px 12px #00000038,inset 0 1px #ffffff38}.z-chat-composer-send-btn:disabled{cursor:not-allowed;opacity:.45;box-shadow:none}.z-chat-scrollbar{contain:layout}.z-chat-content-surface{background:var(--card)}.z-chat-lower-shell{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden;background:var(--card);color:var(--card-foreground);border:1px solid transparent;border-top:0;border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}:is(.dark,[data-theme=dark]) .z-chat-lower-shell{border-color:var(--border);border-top:0}.z-chat-footer-surface{background:transparent;border-top:.0625rem solid color-mix(in oklab,var(--border) 70%,transparent)}:is(.dark,[data-theme=dark]) .z-chat-footer-surface{background:transparent;border-top-color:var(--border)}.z-chat-messages{min-height:100%;overflow-x:hidden;padding:.875rem .75rem;contain:content}.z-chat-messages-sizer{position:relative;width:100%}.z-chat-virtual-row{position:absolute;top:0;left:0;width:100%;padding:.5rem 0}.z-chat-virtual-row>.z-chat-message{margin:0}.z-chat-message{display:flex;margin:.5rem 0;padding:0 2px;transform-origin:bottom center;contain:layout style}.z-chat-message-bubble{transform:translateZ(0);backface-visibility:hidden}.z-chat-message--enter{animation:z-chat-message-push-in .3s ease-out;will-change:transform,opacity}.z-chat-suggestion-item{animation:z-chat-suggestion-reveal .28s ease-out both;will-change:transform,opacity}.z-chat-preview-modal-body{display:flex;align-items:center;justify-content:center;max-height:min(70vh,40rem);padding:.25rem}.z-chat-preview-image{max-width:100%;max-height:min(66vh,37.5rem);border-radius:.5rem;object-fit:contain}@keyframes z-chat-message-push-in{0%{opacity:0;transform:translateY(.9375rem) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes z-chat-suggestion-reveal{0%{opacity:0;transform:translateY(.5rem) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ZIconComponent, selector: "z-icon, [z-icon]", inputs: ["class", "zType", "zAnimatedType", "zAnimate", "zAnimationTrigger", "zSize", "zStrokeWidth", "zSvg"] }, { kind: "component", type: ZModalComponent, selector: "z-modal", inputs: ["class", "zBodyClass", "zVisible", "zTitle", "zDescription", "zWidth", "zClosable", "zMaskClosable", "zDisableShadow", "zHeaderBorder", "zFooterBorder", "zHideHeader", "zHideFooter", "zOkText", "zCancelText", "zOkDestructive", "zOkDisabled", "zLoading", "zContentLoading", "zSkeletonRows", "zOverlay"], outputs: ["zOk", "zCancel", "zAfterClose", "zScrollbar", "zVisibleChange"], exportAs: ["zModal"] }, { kind: "component", type: NgScrollbar, selector: "ng-scrollbar:not([externalViewport]), [ngScrollbar]", exportAs: ["ngScrollbar"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
464
519
|
}
|
|
465
520
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ZChatComponent, decorators: [{
|
|
466
521
|
type: Component,
|
|
467
522
|
args: [{ selector: 'z-chat', imports: [FormsModule, TranslatePipe, ZIconComponent, ZModalComponent, NgScrollbar], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
468
523
|
class: 'z-chat block',
|
|
469
524
|
'(document:keydown.escape)': 'onEscapeKey()',
|
|
470
|
-
}, exportAs: 'zChat', template: "<div\n [class]=\"wrapperClasses()\"\n [style.top]=\"wrapperTop()\"\n [style.right]=\"wrapperRight()\"\n [style.bottom]=\"wrapperBottom()\"\n [style.left]=\"wrapperLeft()\">\n <div class=\"relative\">\n <section class=\"z-chat-panel-shell\" [class]=\"panelClasses()\">\n <header class=\"from-primary/95 to-primary/75 text-primary-foreground bg-gradient-to-r px-4 py-3.5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <span\n class=\"bg-primary-foreground/15 relative inline-flex size-8 shrink-0 items-center justify-center rounded-full\">\n <z-icon [zType]=\"zBotIcon()\" zSize=\"16\" class=\"text-primary-foreground\" />\n <span\n class=\"ring-primary inline-block size-2.5 rounded-full bg-emerald-300 ring-2\"\n style=\"position: absolute; right: -0.0625rem; bottom: -0.0625rem\"></span>\n </span>\n <h3 class=\"truncate text-sm font-semibold\">{{ zTitle() | translate }}</h3>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"clearMessages()\"\n title=\"{{ 'i18n_z_ui_chat_ai_new_chat' | translate }}\">\n <z-icon zType=\"lucideRefreshCcw\" zSize=\"17\" />\n </button>\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"close()\"\n title=\"{{ 'i18n_z_ui_chat_ai_close' | translate }}\">\n <z-icon zType=\"lucideChevronDown\" zSize=\"18\" />\n </button>\n </div>\n </div>\n </header>\n\n <div class=\"z-chat-lower-shell\">\n <ng-scrollbar\n #messagesScrollbar\n (afterInit)=\"onMessagesScrollbarInit()\"\n class=\"z-chat-scrollbar z-chat-content-surface flex-1 overflow-x-hidden\"\n track=\"vertical\"\n orientation=\"vertical\"\n appearance=\"compact\"\n visibility=\"hover\">\n <div class=\"z-chat-messages\">\n @if (hasMessages()) {\n @for (item of zMessages(); track item.id) {\n <div\n class=\"z-chat-message z-chat-message--enter\"\n [class.justify-end]=\"item.role === 'user'\"\n [class.justify-start]=\"item.role === 'assistant'\">\n <div\n class=\"z-chat-message-bubble max-w-[88%] min-w-0 rounded-2xl px-3 py-2.5 text-sm leading-5 [overflow-wrap:anywhere] break-all shadow-xs\"\n [class.bg-primary]=\"item.role === 'user'\"\n [class.text-primary-foreground]=\"item.role === 'user'\"\n [class.rounded-br-md]=\"item.role === 'user'\"\n [class.bg-background]=\"item.role === 'assistant'\"\n [class.border-border/60]=\"item.role === 'assistant'\"\n [class.text-foreground]=\"item.role === 'assistant'\"\n [class.rounded-bl-md]=\"item.role === 'assistant'\"\n [class.border]=\"item.role === 'assistant'\">\n @if (item.files && item.files.length > 0) {\n <div class=\"mb-2 flex flex-col gap-1.5\">\n @for (file of item.files; track file.id) {\n <div\n class=\"flex items-center gap-2 rounded-[0.5rem] px-2 py-1.5 text-xs\"\n [class.bg-white/15]=\"item.role === 'user'\"\n [class.bg-muted/40]=\"item.role === 'assistant'\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background/80 text-muted-foreground inline-flex size-8 items-center justify-center rounded-[0.5rem]\"\n [class.bg-white/20]=\"item.role === 'user'\"\n [class.text-white]=\"item.role === 'user'\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"truncate text-[0.625rem] opacity-80\">{{ formatFileSize(file.size) }}</p>\n </div>\n </div>\n }\n </div>\n }\n {{ item.content }}\n </div>\n </div>\n }\n } @else if (showEmptyState()) {\n <div class=\"flex h-full min-h-60 flex-col items-center justify-center px-2 text-center\">\n <div\n class=\"from-primary/20 to-primary/5 mb-3 inline-flex size-14 items-center justify-center rounded-full bg-gradient-to-br\">\n <z-icon zType=\"lucideBot\" zSize=\"28\" class=\"text-primary\" />\n </div>\n <h4 class=\"text-foreground text-sm font-semibold\">{{ 'i18n_z_ui_chat_ai_empty_title' | translate }}</h4>\n <p class=\"text-muted-foreground mt-1 text-xs\">{{ 'i18n_z_ui_chat_ai_empty_desc' | translate }}</p>\n\n @if (zSuggestions().length > 0) {\n <div class=\"mt-4 flex w-full flex-col gap-2\">\n @for (item of zSuggestions(); track item.label) {\n <button\n type=\"button\"\n class=\"z-chat-suggestion-item bg-background/95 hover:bg-background border-border/60 text-foreground flex w-full cursor-pointer items-center gap-2 rounded-xl border px-3 py-2 text-left text-xs transition hover:shadow-sm\"\n [style.animation-delay.ms]=\"$index * 70\"\n (click)=\"onSuggestionSelect(item)\">\n @if (item.icon) {\n <z-icon [zType]=\"item.icon!\" zSize=\"14\" class=\"text-primary shrink-0\" />\n }\n <span class=\"line-clamp-2\">{{ item.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (isTyping()) {\n <div class=\"z-chat-message z-chat-message--enter justify-start\">\n <div class=\"bg-background border-border/60 rounded-2xl rounded-bl-md border px-3 py-2\">\n <div class=\"flex items-center gap-1\">\n <span class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.15s\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.3s\"></span>\n </div>\n </div>\n </div>\n }\n </div>\n </ng-scrollbar>\n\n <footer class=\"z-chat-footer-surface p-3\">\n @if (attachments().length > 0) {\n <div class=\"mb-2 flex max-h-32 flex-col gap-2 overflow-y-auto pr-1\">\n @for (file of attachments(); track file.id) {\n <div\n class=\"bg-muted/45 border-border/55 relative flex w-full items-center gap-2 rounded-[0.5rem] border px-2 py-1.5\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 shrink-0 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background text-muted-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-[0.5rem]\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"text-muted-foreground truncate text-[0.625rem]\">{{ formatFileSize(file.size) }}</p>\n </div>\n <button\n type=\"button\"\n class=\"bg-background/80 hover:bg-background text-muted-foreground inline-flex size-5 cursor-pointer items-center justify-center rounded-full transition\"\n (click)=\"removeAttachment(file.id)\">\n <z-icon zType=\"lucideX\" zSize=\"12\" />\n </button>\n </div>\n }\n </div>\n }\n\n <div class=\"z-chat-composer-shell\">\n <div class=\"z-chat-composer-main\" [class.z-chat-composer__field--overflow]=\"isComposerOverflowing()\">\n <textarea\n #chatInput\n class=\"z-chat-composer-textarea\"\n [placeholder]=\"zPlaceholder() | translate\"\n [disabled]=\"zDisabled()\"\n [ngModel]=\"draftMessage()\"\n (ngModelChange)=\"draftMessage.set($event)\"\n (input)=\"onTextareaInput($event)\"\n (paste)=\"onTextareaPaste($event)\"\n (keydown)=\"onInputKeydown($event)\"\n rows=\"3\"></textarea>\n </div>\n\n <div class=\"z-chat-composer-toolbar\">\n <div class=\"z-chat-composer-toolbar-left\">\n @if (zAllowAttachments()) {\n <input\n #fileInput\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"zAcceptedFiles()\"\n multiple\n (change)=\"onFileChange($event)\" />\n <button\n type=\"button\"\n class=\"z-chat-composer-tool-btn\"\n [disabled]=\"zDisabled()\"\n (click)=\"onClickAttach()\"\n title=\"{{ 'i18n_z_ui_chat_ai_attach' | translate }}\">\n <z-icon zType=\"lucidePlus\" zSize=\"18\" />\n </button>\n }\n </div>\n\n <div class=\"z-chat-composer-toolbar-right\">\n <button\n type=\"button\"\n class=\"z-chat-composer-send-btn\"\n [disabled]=\"!canSend()\"\n (click)=\"onSendMessage()\"\n title=\"{{ 'i18n_z_ui_chat_ai_send' | translate }}\">\n <z-icon\n [zType]=\"isTyping() ? 'lucideLoaderCircle' : zSendIcon()\"\n zSize=\"16\"\n [class]=\"isTyping() ? 'animate-spin' : ''\" />\n </button>\n </div>\n </div>\n </div>\n </footer>\n </div>\n\n <z-modal\n [zVisible]=\"isPreviewVisible()\"\n [zTitle]=\"previewImage()?.name\"\n [zDescription]=\"previewImageSizeLabel()\"\n zWidth=\"min(92vw, 720px)\"\n [zHideFooter]=\"true\"\n [zMaskClosable]=\"true\"\n zOverlay=\"blur\"\n (zCancel)=\"closeImagePreview()\">\n <div class=\"z-chat-preview-modal-body\">\n <img [src]=\"previewImage()?.url\" [alt]=\"previewImage()?.name\" class=\"z-chat-preview-image\" />\n </div>\n </z-modal>\n </section>\n\n <button\n type=\"button\"\n class=\"pointer-events-auto cursor-pointer\"\n [class]=\"fabClasses()\"\n (click)=\"onToggle()\"\n [attr.aria-expanded]=\"zOpen()\"\n [attr.aria-label]=\"'i18n_z_ui_chat_ai_toggle' | translate\"\n title=\"{{ 'i18n_z_ui_chat_ai_toggle' | translate }}\">\n @if (zShowPulse() && !zOpen()) {\n <span class=\"bg-primary/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n @if (showRipple()) {\n <span class=\"bg-primary-foreground/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n <z-icon\n [zType]=\"zOpen() ? 'lucideX' : zFabIcon()\"\n zSize=\"24\"\n class=\"relative z-1 transition-transform duration-200\"\n [class.rotate-180]=\"zOpen()\" />\n </button>\n </div>\n</div>\n", styles: [".z-chat textarea{scrollbar-width:thin}.z-chat-panel-shell{box-shadow:0 0 28px #00000024!important}:is(.dark,[data-theme=dark]) .z-chat-panel-shell{box-shadow:0 0 28px #00000057!important}.z-chat-composer__field--overflow:after{position:absolute;right:0;bottom:0;left:0;height:1rem;pointer-events:none;content:\"\";background:linear-gradient(to top,var(--background) 22%,transparent)}.z-chat-composer-shell{position:relative;display:flex;width:100%;flex-direction:column;border:1px solid color-mix(in oklab,var(--border) 82%,transparent);border-radius:1rem;background:linear-gradient(180deg,color-mix(in oklab,var(--card) 98%,var(--background) 2%),color-mix(in oklab,var(--card) 92%,var(--background) 8%));box-shadow:0 6px 18px #00000014;transition:box-shadow .2s ease}.z-chat-composer-shell:focus-within{box-shadow:0 6px 18px #00000014}:is(.dark,[data-theme=dark]) .z-chat-composer-shell{border-color:var(--border);background:linear-gradient(180deg,color-mix(in oklab,var(--card) 96%,var(--background) 4%),color-mix(in oklab,var(--card) 88%,var(--background) 12%));box-shadow:0 10px 24px #00000057}.z-chat-composer-main{position:relative;overflow:hidden;padding:.7rem .35rem .45rem .85rem}.z-chat-composer-textarea{width:100%;height:4.125rem;max-height:4.125rem;border:none;background:transparent;resize:none;overflow-x:hidden;font-size:.875rem;line-height:1.4rem;color:var(--foreground);outline:none;padding-right:.2rem;scrollbar-width:thin;overflow-wrap:anywhere;word-break:break-word}.z-chat-composer-textarea::placeholder{color:var(--muted-foreground)}.z-chat-composer-textarea::-webkit-scrollbar{width:.375rem}.z-chat-composer-textarea::-webkit-scrollbar-track{background:transparent}.z-chat-composer-textarea::-webkit-scrollbar-thumb{border-radius:62.4375rem;background:#64748b73}:is(.dark,[data-theme=dark]) .z-chat-composer-textarea::-webkit-scrollbar-thumb{background:#94a3b88c}.z-chat-composer-toolbar{display:flex;align-items:center;justify-content:space-between;border-top:.0625rem solid rgba(219,219,219,.6);padding:.55rem .7rem .65rem}:is(.dark,[data-theme=dark]) .z-chat-composer-toolbar{border-top-color:#00000038}.z-chat-composer-toolbar-left,.z-chat-composer-toolbar-right{display:flex;align-items:center}.z-chat-composer-tool-btn{display:inline-flex;height:1.75rem;width:1.75rem;cursor:pointer;align-items:center;justify-content:center;border-radius:.6rem;border:none;background:transparent;color:#374151;transition:background-color .2s ease}.z-chat-composer-tool-btn:hover{background:#0000000f}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn{color:#e5e7ebe6}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn:hover{background:#ffffff1a}.z-chat-composer-tool-btn:disabled{cursor:not-allowed;opacity:.5}.z-chat-composer-send-btn{display:inline-flex;height:2rem;width:2rem;cursor:pointer;align-items:center;justify-content:center;border-radius:62.4375rem;border:1px solid rgba(0,0,0,.1);background:var(--primary);color:var(--primary-foreground);box-shadow:0 2px 8px #0000002e,inset 0 1px #ffffff2e;transition:transform .15s ease,opacity .2s ease,box-shadow .2s ease}.z-chat-composer-send-btn:hover{transform:translateY(-.0625rem);box-shadow:0 4px 12px #00000038,inset 0 1px #ffffff38}.z-chat-composer-send-btn:disabled{cursor:not-allowed;opacity:.45;box-shadow:none}.z-chat-scrollbar{contain:layout}.z-chat-content-surface{background:var(--card)}.z-chat-lower-shell{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden;background:var(--card);color:var(--card-foreground);border:1px solid transparent;border-top:0;border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}:is(.dark,[data-theme=dark]) .z-chat-lower-shell{border-color:var(--border);border-top:0}.z-chat-footer-surface{background:transparent;border-top:.0625rem solid color-mix(in oklab,var(--border) 70%,transparent)}:is(.dark,[data-theme=dark]) .z-chat-footer-surface{background:transparent;border-top-color:var(--border)}.z-chat-messages{min-height:100%;overflow-x:hidden;padding:.875rem .75rem;contain:content}.z-chat-message{display:flex;margin:.5rem 0;padding:0 2px;transform-origin:bottom center;contain:layout style}.z-chat-message-bubble{transform:translateZ(0);backface-visibility:hidden}.z-chat-message--enter{animation:z-chat-message-push-in .3s ease-out;will-change:transform,opacity}.z-chat-suggestion-item{animation:z-chat-suggestion-reveal .28s ease-out both;will-change:transform,opacity}.z-chat-preview-modal-body{display:flex;align-items:center;justify-content:center;max-height:min(70vh,40rem);padding:.25rem}.z-chat-preview-image{max-width:100%;max-height:min(66vh,37.5rem);border-radius:.5rem;object-fit:contain}@keyframes z-chat-message-push-in{0%{opacity:0;transform:translateY(.9375rem) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes z-chat-suggestion-reveal{0%{opacity:0;transform:translateY(.5rem) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"] }]
|
|
471
|
-
}], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], zOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "zOpen", required: false }] }, { type: i0.Output, args: ["zOpenChange"] }], zMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "zMessages", required: false }] }, { type: i0.Output, args: ["zMessagesChange"] }], zTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "zTitle", required: false }] }], zSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSubtitle", required: false }] }], zPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "zPlaceholder", required: false }] }], zPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "zPosition", required: false }] }], zOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "zOffset", required: false }] }], zSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSize", required: false }] }], zSuggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSuggestions", required: false }] }], zShowSuggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "zShowSuggestions", required: false }] }], zShowPulse: [{ type: i0.Input, args: [{ isSignal: true, alias: "zShowPulse", required: false }] }], zDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "zDisabled", required: false }] }], zLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "zLoading", required: false }] }], zAllowAttachments: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAllowAttachments", required: false }] }], zAcceptedFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAcceptedFiles", required: false }] }], zAutoReply: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAutoReply", required: false }] }], zAutoReplyDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAutoReplyDelay", required: false }] }], zFabIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "zFabIcon", required: false }] }], zBotIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "zBotIcon", required: false }] }], zSendIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSendIcon", required: false }] }], zSend: [{ type: i0.Output, args: ["zSend"] }], zResponse: [{ type: i0.Output, args: ["zResponse"] }], zToggle: [{ type: i0.Output, args: ["zToggle"] }], zClear: [{ type: i0.Output, args: ["zClear"] }], _messagesScrollbarRef: [{ type: i0.ViewChild, args: ['messagesScrollbar', { isSignal: true }] }], _chatInputRef: [{ type: i0.ViewChild, args: ['chatInput', { isSignal: true }] }], _fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
525
|
+
}, exportAs: 'zChat', template: "<div\n [class]=\"wrapperClasses()\"\n [style.top]=\"wrapperTop()\"\n [style.right]=\"wrapperRight()\"\n [style.bottom]=\"wrapperBottom()\"\n [style.left]=\"wrapperLeft()\">\n <div class=\"relative\">\n <section class=\"z-chat-panel-shell\" [class]=\"panelClasses()\">\n <header class=\"from-primary/95 to-primary/75 text-primary-foreground bg-gradient-to-r px-4 py-3.5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"flex items-center gap-2\">\n <span\n class=\"bg-primary-foreground/15 relative inline-flex size-8 shrink-0 items-center justify-center rounded-full\">\n <z-icon [zType]=\"zBotIcon()\" zSize=\"16\" class=\"text-primary-foreground\" />\n <span\n class=\"ring-primary inline-block size-2.5 rounded-full bg-emerald-300 ring-2\"\n style=\"position: absolute; right: -0.0625rem; bottom: -0.0625rem\"></span>\n </span>\n <h3 class=\"truncate text-sm font-semibold\">{{ zTitle() | translate }}</h3>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"clearMessages()\"\n title=\"{{ 'i18n_z_ui_chat_ai_new_chat' | translate }}\">\n <z-icon zType=\"lucideRefreshCcw\" zSize=\"17\" />\n </button>\n <button\n type=\"button\"\n class=\"text-primary-foreground/90 hover:bg-primary-foreground/15 inline-flex size-8 cursor-pointer items-center justify-center rounded-md transition\"\n (click)=\"close()\"\n title=\"{{ 'i18n_z_ui_chat_ai_close' | translate }}\">\n <z-icon zType=\"lucideChevronDown\" zSize=\"18\" />\n </button>\n </div>\n </div>\n </header>\n\n <div class=\"z-chat-lower-shell\">\n <ng-scrollbar\n #messagesScrollbar\n (afterInit)=\"onMessagesScrollbarInit()\"\n class=\"z-chat-scrollbar z-chat-content-surface flex-1 overflow-x-hidden\"\n track=\"vertical\"\n orientation=\"vertical\"\n appearance=\"compact\"\n visibility=\"hover\">\n <div class=\"z-chat-messages\">\n @if (hasMessages()) {\n <div class=\"z-chat-messages-sizer\" [style.height.px]=\"messageVirtualizer.getTotalSize()\">\n @for (row of messageVirtualizer.getVirtualItems(); track row.key) {\n @let item = zMessages()[row.index];\n <div\n #virtualMessage\n class=\"z-chat-virtual-row\"\n [attr.data-index]=\"row.index\"\n [style.transform]=\"'translateY(' + row.start + 'px)'\">\n <div\n class=\"z-chat-message\"\n [class.z-chat-message--enter]=\"item.id === newestMessageId()\"\n [class.justify-end]=\"item.role === 'user'\"\n [class.justify-start]=\"item.role === 'assistant'\">\n <div\n class=\"z-chat-message-bubble max-w-[88%] min-w-0 rounded-2xl px-3 py-2.5 text-sm leading-5 [overflow-wrap:anywhere] break-all shadow-xs\"\n [class.bg-primary]=\"item.role === 'user'\"\n [class.text-primary-foreground]=\"item.role === 'user'\"\n [class.rounded-br-md]=\"item.role === 'user'\"\n [class.bg-background]=\"item.role === 'assistant'\"\n [class.border-border/60]=\"item.role === 'assistant'\"\n [class.text-foreground]=\"item.role === 'assistant'\"\n [class.rounded-bl-md]=\"item.role === 'assistant'\"\n [class.border]=\"item.role === 'assistant'\">\n @if (item.files && item.files.length > 0) {\n <div class=\"mb-2 flex flex-col gap-1.5\">\n @for (file of item.files; track file.id) {\n <div\n class=\"flex items-center gap-2 rounded-[0.5rem] px-2 py-1.5 text-xs\"\n [class.bg-white/15]=\"item.role === 'user'\"\n [class.bg-muted/40]=\"item.role === 'assistant'\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background/80 text-muted-foreground inline-flex size-8 items-center justify-center rounded-[0.5rem]\"\n [class.bg-white/20]=\"item.role === 'user'\"\n [class.text-white]=\"item.role === 'user'\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"truncate text-[0.625rem] opacity-80\">{{ formatFileSize(file.size) }}</p>\n </div>\n </div>\n }\n </div>\n }\n {{ item.content }}\n </div>\n </div>\n </div>\n }\n </div>\n } @else if (showEmptyState()) {\n <div class=\"flex h-full min-h-60 flex-col items-center justify-center px-2 text-center\">\n <div\n class=\"from-primary/20 to-primary/5 mb-3 inline-flex size-14 items-center justify-center rounded-full bg-gradient-to-br\">\n <z-icon zType=\"lucideBot\" zSize=\"28\" class=\"text-primary\" />\n </div>\n <h4 class=\"text-foreground text-sm font-semibold\">{{ 'i18n_z_ui_chat_ai_empty_title' | translate }}</h4>\n <p class=\"text-muted-foreground mt-1 text-xs\">{{ 'i18n_z_ui_chat_ai_empty_desc' | translate }}</p>\n\n @if (zSuggestions().length > 0) {\n <div class=\"mt-4 flex w-full flex-col gap-2\">\n @for (item of zSuggestions(); track item.label) {\n <button\n type=\"button\"\n class=\"z-chat-suggestion-item bg-background/95 hover:bg-background border-border/60 text-foreground flex w-full cursor-pointer items-center gap-2 rounded-xl border px-3 py-2 text-left text-xs transition hover:shadow-sm\"\n [style.animation-delay.ms]=\"$index * 70\"\n (click)=\"onSuggestionSelect(item)\">\n @if (item.icon) {\n <z-icon [zType]=\"item.icon!\" zSize=\"14\" class=\"text-primary shrink-0\" />\n }\n <span class=\"line-clamp-2\">{{ item.label | translate }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (isTyping()) {\n <div class=\"z-chat-message z-chat-message--enter justify-start\">\n <div class=\"bg-background border-border/60 rounded-2xl rounded-bl-md border px-3 py-2\">\n <div class=\"flex items-center gap-1\">\n <span class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.15s\"></span>\n <span\n class=\"bg-muted-foreground/60 inline-block size-1.5 animate-bounce rounded-full\"\n style=\"animation-delay: 0.3s\"></span>\n </div>\n </div>\n </div>\n }\n </div>\n </ng-scrollbar>\n\n <footer class=\"z-chat-footer-surface p-3\">\n @if (attachments().length > 0) {\n <div class=\"mb-2 flex max-h-32 flex-col gap-2 overflow-y-auto pr-1\">\n @for (file of attachments(); track file.id) {\n <div\n class=\"bg-muted/45 border-border/55 relative flex w-full items-center gap-2 rounded-[0.5rem] border px-2 py-1.5\">\n @if (isImageAttachment(file) && file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n [alt]=\"file.name\"\n class=\"size-8 shrink-0 cursor-pointer rounded-[0.5rem] object-cover\"\n (click)=\"openImagePreview(file)\"\n title=\"Click to preview\" />\n } @else {\n <span\n class=\"bg-background text-muted-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-[0.5rem]\">\n <z-icon zType=\"lucideFileText\" zSize=\"14\" />\n </span>\n }\n <div class=\"min-w-0 flex-1\">\n <p class=\"truncate text-[0.6875rem] font-medium\">{{ file.name }}</p>\n <p class=\"text-muted-foreground truncate text-[0.625rem]\">{{ formatFileSize(file.size) }}</p>\n </div>\n <button\n type=\"button\"\n class=\"bg-background/80 hover:bg-background text-muted-foreground inline-flex size-5 cursor-pointer items-center justify-center rounded-full transition\"\n (click)=\"removeAttachment(file.id)\">\n <z-icon zType=\"lucideX\" zSize=\"12\" />\n </button>\n </div>\n }\n </div>\n }\n\n <div class=\"z-chat-composer-shell\">\n <div class=\"z-chat-composer-main\" [class.z-chat-composer__field--overflow]=\"isComposerOverflowing()\">\n <textarea\n #chatInput\n class=\"z-chat-composer-textarea\"\n [placeholder]=\"zPlaceholder() | translate\"\n [disabled]=\"zDisabled()\"\n [ngModel]=\"draftMessage()\"\n (ngModelChange)=\"draftMessage.set($event)\"\n (input)=\"onTextareaInput($event)\"\n (paste)=\"onTextareaPaste($event)\"\n (keydown)=\"onInputKeydown($event)\"\n rows=\"3\"></textarea>\n </div>\n\n <div class=\"z-chat-composer-toolbar\">\n <div class=\"z-chat-composer-toolbar-left\">\n @if (zAllowAttachments()) {\n <input\n #fileInput\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"zAcceptedFiles()\"\n multiple\n (change)=\"onFileChange($event)\" />\n <button\n type=\"button\"\n class=\"z-chat-composer-tool-btn\"\n [disabled]=\"zDisabled()\"\n (click)=\"onClickAttach()\"\n title=\"{{ 'i18n_z_ui_chat_ai_attach' | translate }}\">\n <z-icon zType=\"lucidePlus\" zSize=\"18\" />\n </button>\n }\n </div>\n\n <div class=\"z-chat-composer-toolbar-right\">\n <button\n type=\"button\"\n class=\"z-chat-composer-send-btn\"\n [disabled]=\"!canSend()\"\n (click)=\"onSendMessage()\"\n title=\"{{ 'i18n_z_ui_chat_ai_send' | translate }}\">\n <z-icon\n [zType]=\"isTyping() ? 'lucideLoaderCircle' : zSendIcon()\"\n zSize=\"16\"\n [class]=\"isTyping() ? 'animate-spin' : ''\" />\n </button>\n </div>\n </div>\n </div>\n </footer>\n </div>\n\n <z-modal\n [zVisible]=\"isPreviewVisible()\"\n [zTitle]=\"previewImage()?.name\"\n [zDescription]=\"previewImageSizeLabel()\"\n zWidth=\"min(92vw, 720px)\"\n [zHideFooter]=\"true\"\n [zMaskClosable]=\"true\"\n zOverlay=\"blur\"\n (zCancel)=\"closeImagePreview()\">\n <div class=\"z-chat-preview-modal-body\">\n <img [src]=\"previewImage()?.url\" [alt]=\"previewImage()?.name\" class=\"z-chat-preview-image\" />\n </div>\n </z-modal>\n </section>\n\n <button\n type=\"button\"\n class=\"pointer-events-auto cursor-pointer\"\n [class]=\"fabClasses()\"\n (click)=\"onToggle()\"\n [attr.aria-expanded]=\"zOpen()\"\n [attr.aria-label]=\"'i18n_z_ui_chat_ai_toggle' | translate\"\n title=\"{{ 'i18n_z_ui_chat_ai_toggle' | translate }}\">\n @if (zShowPulse() && !zOpen()) {\n <span class=\"bg-primary/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n @if (showRipple()) {\n <span class=\"bg-primary-foreground/30 absolute inset-0 animate-ping rounded-full\"></span>\n }\n\n <z-icon\n [zType]=\"zOpen() ? 'lucideX' : zFabIcon()\"\n zSize=\"24\"\n class=\"relative z-1 transition-transform duration-200\"\n [class.rotate-180]=\"zOpen()\" />\n </button>\n </div>\n</div>\n", styles: [".z-chat textarea{scrollbar-width:thin}.z-chat-panel-shell{box-shadow:0 0 28px #00000024!important}:is(.dark,[data-theme=dark]) .z-chat-panel-shell{box-shadow:0 0 28px #00000057!important}.z-chat-composer__field--overflow:after{position:absolute;right:0;bottom:0;left:0;height:1rem;pointer-events:none;content:\"\";background:linear-gradient(to top,var(--background) 22%,transparent)}.z-chat-composer-shell{position:relative;display:flex;width:100%;flex-direction:column;border:1px solid color-mix(in oklab,var(--border) 82%,transparent);border-radius:1rem;background:linear-gradient(180deg,color-mix(in oklab,var(--card) 98%,var(--background) 2%),color-mix(in oklab,var(--card) 92%,var(--background) 8%));box-shadow:0 6px 18px #00000014;transition:box-shadow .2s ease}.z-chat-composer-shell:focus-within{box-shadow:0 6px 18px #00000014}:is(.dark,[data-theme=dark]) .z-chat-composer-shell{border-color:var(--border);background:linear-gradient(180deg,color-mix(in oklab,var(--card) 96%,var(--background) 4%),color-mix(in oklab,var(--card) 88%,var(--background) 12%));box-shadow:0 10px 24px #00000057}.z-chat-composer-main{position:relative;overflow:hidden;padding:.7rem .35rem .45rem .85rem}.z-chat-composer-textarea{width:100%;height:4.125rem;max-height:4.125rem;border:none;background:transparent;resize:none;overflow-x:hidden;font-size:.875rem;line-height:1.4rem;color:var(--foreground);outline:none;padding-right:.2rem;scrollbar-width:thin;overflow-wrap:anywhere;word-break:break-word}.z-chat-composer-textarea::placeholder{color:var(--muted-foreground)}.z-chat-composer-textarea::-webkit-scrollbar{width:.375rem}.z-chat-composer-textarea::-webkit-scrollbar-track{background:transparent}.z-chat-composer-textarea::-webkit-scrollbar-thumb{border-radius:62.4375rem;background:#64748b73}:is(.dark,[data-theme=dark]) .z-chat-composer-textarea::-webkit-scrollbar-thumb{background:#94a3b88c}.z-chat-composer-toolbar{display:flex;align-items:center;justify-content:space-between;border-top:.0625rem solid rgba(219,219,219,.6);padding:.55rem .7rem .65rem}:is(.dark,[data-theme=dark]) .z-chat-composer-toolbar{border-top-color:#00000038}.z-chat-composer-toolbar-left,.z-chat-composer-toolbar-right{display:flex;align-items:center}.z-chat-composer-tool-btn{display:inline-flex;height:1.75rem;width:1.75rem;cursor:pointer;align-items:center;justify-content:center;border-radius:.6rem;border:none;background:transparent;color:#374151;transition:background-color .2s ease}.z-chat-composer-tool-btn:hover{background:#0000000f}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn{color:#e5e7ebe6}:is(.dark,[data-theme=dark]) .z-chat-composer-tool-btn:hover{background:#ffffff1a}.z-chat-composer-tool-btn:disabled{cursor:not-allowed;opacity:.5}.z-chat-composer-send-btn{display:inline-flex;height:2rem;width:2rem;cursor:pointer;align-items:center;justify-content:center;border-radius:62.4375rem;border:1px solid rgba(0,0,0,.1);background:var(--primary);color:var(--primary-foreground);box-shadow:0 2px 8px #0000002e,inset 0 1px #ffffff2e;transition:transform .15s ease,opacity .2s ease,box-shadow .2s ease}.z-chat-composer-send-btn:hover{transform:translateY(-.0625rem);box-shadow:0 4px 12px #00000038,inset 0 1px #ffffff38}.z-chat-composer-send-btn:disabled{cursor:not-allowed;opacity:.45;box-shadow:none}.z-chat-scrollbar{contain:layout}.z-chat-content-surface{background:var(--card)}.z-chat-lower-shell{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden;background:var(--card);color:var(--card-foreground);border:1px solid transparent;border-top:0;border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}:is(.dark,[data-theme=dark]) .z-chat-lower-shell{border-color:var(--border);border-top:0}.z-chat-footer-surface{background:transparent;border-top:.0625rem solid color-mix(in oklab,var(--border) 70%,transparent)}:is(.dark,[data-theme=dark]) .z-chat-footer-surface{background:transparent;border-top-color:var(--border)}.z-chat-messages{min-height:100%;overflow-x:hidden;padding:.875rem .75rem;contain:content}.z-chat-messages-sizer{position:relative;width:100%}.z-chat-virtual-row{position:absolute;top:0;left:0;width:100%;padding:.5rem 0}.z-chat-virtual-row>.z-chat-message{margin:0}.z-chat-message{display:flex;margin:.5rem 0;padding:0 2px;transform-origin:bottom center;contain:layout style}.z-chat-message-bubble{transform:translateZ(0);backface-visibility:hidden}.z-chat-message--enter{animation:z-chat-message-push-in .3s ease-out;will-change:transform,opacity}.z-chat-suggestion-item{animation:z-chat-suggestion-reveal .28s ease-out both;will-change:transform,opacity}.z-chat-preview-modal-body{display:flex;align-items:center;justify-content:center;max-height:min(70vh,40rem);padding:.25rem}.z-chat-preview-image{max-width:100%;max-height:min(66vh,37.5rem);border-radius:.5rem;object-fit:contain}@keyframes z-chat-message-push-in{0%{opacity:0;transform:translateY(.9375rem) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes z-chat-suggestion-reveal{0%{opacity:0;transform:translateY(.5rem) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"] }]
|
|
526
|
+
}], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], zOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "zOpen", required: false }] }, { type: i0.Output, args: ["zOpenChange"] }], zMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "zMessages", required: false }] }, { type: i0.Output, args: ["zMessagesChange"] }], zTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "zTitle", required: false }] }], zSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSubtitle", required: false }] }], zPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "zPlaceholder", required: false }] }], zPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "zPosition", required: false }] }], zOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "zOffset", required: false }] }], zSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSize", required: false }] }], zSuggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSuggestions", required: false }] }], zShowSuggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "zShowSuggestions", required: false }] }], zShowPulse: [{ type: i0.Input, args: [{ isSignal: true, alias: "zShowPulse", required: false }] }], zDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "zDisabled", required: false }] }], zLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "zLoading", required: false }] }], zAllowAttachments: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAllowAttachments", required: false }] }], zAcceptedFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAcceptedFiles", required: false }] }], zAutoReply: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAutoReply", required: false }] }], zAutoReplyDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "zAutoReplyDelay", required: false }] }], zFabIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "zFabIcon", required: false }] }], zBotIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "zBotIcon", required: false }] }], zSendIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "zSendIcon", required: false }] }], zSend: [{ type: i0.Output, args: ["zSend"] }], zResponse: [{ type: i0.Output, args: ["zResponse"] }], zToggle: [{ type: i0.Output, args: ["zToggle"] }], zClear: [{ type: i0.Output, args: ["zClear"] }], _messagesScrollbarRef: [{ type: i0.ViewChild, args: ['messagesScrollbar', { isSignal: true }] }], _chatInputRef: [{ type: i0.ViewChild, args: ['chatInput', { isSignal: true }] }], _fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], _virtualMessageRefs: [{ type: i0.ViewChildren, args: ['virtualMessage', { isSignal: true }] }] } });
|
|
472
527
|
|
|
473
528
|
/**
|
|
474
529
|
* Generated bundle index. Do not edit.
|