@threadplane/chat 0.0.55 → 0.0.56
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/README.md +1 -1
- package/fesm2022/threadplane-chat.mjs +2395 -1768
- package/fesm2022/threadplane-chat.mjs.map +1 -1
- package/package.json +2 -2
- package/types/threadplane-chat.d.ts +101 -14
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, ViewEncapsulation,
|
|
2
|
+
import { InjectionToken, input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, ViewEncapsulation, ElementRef, ViewContainerRef, DestroyRef, viewChild, model, contentChild, untracked, makeEnvironmentProviders, Injector, runInInjectionContext, SecurityContext } from '@angular/core';
|
|
3
3
|
import { NgTemplateOutlet, NgComponentOutlet, KeyValuePipe } from '@angular/common';
|
|
4
4
|
import { createPartialMarkdownParser, materialize } from '@cacheplane/partial-markdown';
|
|
5
5
|
import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
|
|
@@ -305,6 +305,14 @@ const LIGHT_TOKENS = `
|
|
|
305
305
|
--a2ui-elevation-3: 0 4px 8px rgba(0, 0, 0, 0.10);
|
|
306
306
|
--a2ui-elevation-4: 0 8px 16px rgba(0, 0, 0, 0.14);
|
|
307
307
|
--a2ui-elevation-5: 0 16px 32px rgba(0, 0, 0, 0.18);
|
|
308
|
+
|
|
309
|
+
/* --tplane-chat-citation-* — inline markers, preview card, sources panel */
|
|
310
|
+
--tplane-chat-citation-accent: #2f6fe0;
|
|
311
|
+
--tplane-chat-citation-accent-soft: #eaf1fd;
|
|
312
|
+
--tplane-chat-citation-accent-border: #c9def8;
|
|
313
|
+
--tplane-chat-citation-marker-bg: #f1f2f4;
|
|
314
|
+
--tplane-chat-citation-marker-border: var(--tplane-chat-separator);
|
|
315
|
+
--tplane-chat-citation-marker-fg: #4b5563;
|
|
308
316
|
`;
|
|
309
317
|
const DARK_TOKENS = `
|
|
310
318
|
--tplane-chat-bg: rgb(17, 17, 17);
|
|
@@ -345,6 +353,14 @@ const DARK_TOKENS = `
|
|
|
345
353
|
--a2ui-elevation-3: 0 4px 8px rgba(0, 0, 0, 0.4);
|
|
346
354
|
--a2ui-elevation-4: 0 8px 16px rgba(0, 0, 0, 0.45);
|
|
347
355
|
--a2ui-elevation-5: 0 16px 32px rgba(0, 0, 0, 0.5);
|
|
356
|
+
|
|
357
|
+
/* --tplane-chat-citation-* dark variant */
|
|
358
|
+
--tplane-chat-citation-accent: #6ea8ff;
|
|
359
|
+
--tplane-chat-citation-accent-soft: rgba(79, 141, 245, 0.16);
|
|
360
|
+
--tplane-chat-citation-accent-border: rgba(79, 141, 245, 0.38);
|
|
361
|
+
--tplane-chat-citation-marker-bg: rgba(255, 255, 255, 0.08);
|
|
362
|
+
--tplane-chat-citation-marker-border: var(--tplane-chat-separator);
|
|
363
|
+
--tplane-chat-citation-marker-fg: #c9ccd1;
|
|
348
364
|
`;
|
|
349
365
|
const GEOMETRY_TOKENS = `
|
|
350
366
|
--tplane-chat-radius-bubble: 15px;
|
|
@@ -353,6 +369,7 @@ const GEOMETRY_TOKENS = `
|
|
|
353
369
|
--tplane-chat-radius-button: 8px;
|
|
354
370
|
--tplane-chat-radius-launcher: 9999px;
|
|
355
371
|
--tplane-chat-max-width: 48rem;
|
|
372
|
+
--tplane-chat-citation-radius: 6px;
|
|
356
373
|
`;
|
|
357
374
|
const TYPOGRAPHY_TOKENS = `
|
|
358
375
|
--tplane-chat-font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
@@ -837,53 +854,364 @@ const CHAT_MESSAGE_STYLES = `
|
|
|
837
854
|
.chat-message__control-btn svg { width: 16px; height: 16px; pointer-events: none; }
|
|
838
855
|
`;
|
|
839
856
|
|
|
857
|
+
/** Hostname of `url` with a leading `www.` removed; null if absent/malformed. */
|
|
858
|
+
function deriveDomain(url) {
|
|
859
|
+
if (!url)
|
|
860
|
+
return null;
|
|
861
|
+
try {
|
|
862
|
+
return new URL(url).hostname.replace(/^www\./, '');
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
/** Explicit `sourceType`, else 'web' inferred from a url, else 'unknown'. */
|
|
869
|
+
function deriveSourceType(c) {
|
|
870
|
+
if (c.sourceType)
|
|
871
|
+
return c.sourceType;
|
|
872
|
+
return c.url ? 'web' : 'unknown';
|
|
873
|
+
}
|
|
874
|
+
/** Uppercased first letter of the domain (or title) for the monogram chip. */
|
|
875
|
+
function deriveMonogram(c) {
|
|
876
|
+
const seed = deriveDomain(c.url) ?? c.title ?? '';
|
|
877
|
+
const ch = seed.trim().charAt(0);
|
|
878
|
+
return ch ? ch.toUpperCase() : '?';
|
|
879
|
+
}
|
|
880
|
+
/** Deterministic hue in [0,360) from a seed string (stable monogram color). */
|
|
881
|
+
function monogramHue(seed) {
|
|
882
|
+
let h = 0;
|
|
883
|
+
for (let i = 0; i < seed.length; i++) {
|
|
884
|
+
h = (h * 31 + seed.charCodeAt(i)) % 360;
|
|
885
|
+
}
|
|
886
|
+
return h;
|
|
887
|
+
}
|
|
888
|
+
/** Short freshness label (e.g. "Apr 2024"); null when absent or unparseable. */
|
|
889
|
+
function formatPublished(value) {
|
|
890
|
+
if (value == null)
|
|
891
|
+
return null;
|
|
892
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
893
|
+
if (Number.isNaN(d.getTime()))
|
|
894
|
+
return null;
|
|
895
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short' });
|
|
896
|
+
}
|
|
897
|
+
/** Deterministic monogram-chip background color (stable per source). */
|
|
898
|
+
function monogramColor(c) {
|
|
899
|
+
const seed = deriveDomain(c.url) ?? c.title ?? '?';
|
|
900
|
+
return `hsl(${monogramHue(seed)} 60% 45%)`;
|
|
901
|
+
}
|
|
902
|
+
/** Human label for the source-type badge; null when type is 'unknown'. */
|
|
903
|
+
function citationTypeLabel(c) {
|
|
904
|
+
const t = deriveSourceType(c);
|
|
905
|
+
if (t === 'unknown')
|
|
906
|
+
return null;
|
|
907
|
+
return t === 'web' ? 'Web' : t.charAt(0).toUpperCase() + t.slice(1);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// libs/chat/src/lib/styles/chat-citations.styles.ts
|
|
911
|
+
// SPDX-License-Identifier: MIT
|
|
912
|
+
/** Inline pill marker (chat-md-citation-reference). */
|
|
913
|
+
const CHAT_CITATION_MARKER_STYLES = `
|
|
914
|
+
:host { display: inline; }
|
|
915
|
+
.chat-citation-marker {
|
|
916
|
+
display: inline-flex;
|
|
917
|
+
align-items: center;
|
|
918
|
+
justify-content: center;
|
|
919
|
+
min-width: 17px;
|
|
920
|
+
height: 17px;
|
|
921
|
+
padding: 0 5px;
|
|
922
|
+
margin: 0 1px;
|
|
923
|
+
font-size: 11px;
|
|
924
|
+
font-weight: 600;
|
|
925
|
+
line-height: 1;
|
|
926
|
+
color: var(--tplane-chat-citation-marker-fg);
|
|
927
|
+
background: var(--tplane-chat-citation-marker-bg);
|
|
928
|
+
border: 1px solid var(--tplane-chat-citation-marker-border);
|
|
929
|
+
border-radius: var(--tplane-chat-citation-radius);
|
|
930
|
+
translate: 0 -1px;
|
|
931
|
+
text-decoration: none;
|
|
932
|
+
cursor: pointer;
|
|
933
|
+
white-space: nowrap;
|
|
934
|
+
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
|
|
935
|
+
}
|
|
936
|
+
.chat-citation-marker:hover,
|
|
937
|
+
.chat-citation-marker:focus-visible {
|
|
938
|
+
background: var(--tplane-chat-citation-accent-soft);
|
|
939
|
+
border-color: var(--tplane-chat-citation-accent-border);
|
|
940
|
+
color: var(--tplane-chat-citation-accent);
|
|
941
|
+
outline: none;
|
|
942
|
+
}
|
|
943
|
+
.chat-citation-marker:focus-visible {
|
|
944
|
+
box-shadow: 0 0 0 2px var(--tplane-chat-citation-accent-border);
|
|
945
|
+
}
|
|
946
|
+
.chat-citation-marker--unresolved {
|
|
947
|
+
color: var(--tplane-chat-text-muted);
|
|
948
|
+
background: transparent;
|
|
949
|
+
border-style: dashed;
|
|
950
|
+
cursor: default;
|
|
951
|
+
}
|
|
952
|
+
.chat-citation-marker--unresolved:hover {
|
|
953
|
+
background: transparent;
|
|
954
|
+
border-color: var(--tplane-chat-citation-marker-border);
|
|
955
|
+
color: var(--tplane-chat-text-muted);
|
|
956
|
+
}
|
|
957
|
+
.chat-citation-marker--no-url {
|
|
958
|
+
cursor: help;
|
|
959
|
+
}
|
|
960
|
+
`;
|
|
961
|
+
/** Provenance preview card (chat-citation-preview), portaled into the overlay pane. */
|
|
962
|
+
const CHAT_CITATION_PREVIEW_STYLES = `
|
|
963
|
+
:host { display: block; }
|
|
964
|
+
.chat-citation-preview {
|
|
965
|
+
width: 320px;
|
|
966
|
+
max-width: calc(100vw - 24px);
|
|
967
|
+
box-sizing: border-box;
|
|
968
|
+
background: var(--tplane-chat-surface);
|
|
969
|
+
border: 1px solid var(--tplane-chat-separator);
|
|
970
|
+
border-radius: var(--tplane-chat-radius-card);
|
|
971
|
+
box-shadow: var(--tplane-chat-shadow-md);
|
|
972
|
+
padding: 12px 13px;
|
|
973
|
+
text-align: left;
|
|
974
|
+
color: var(--tplane-chat-text);
|
|
975
|
+
}
|
|
976
|
+
.chat-citation-preview__head {
|
|
977
|
+
display: flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
gap: 7px;
|
|
980
|
+
margin-bottom: 7px;
|
|
981
|
+
}
|
|
982
|
+
.chat-citation-preview__fav {
|
|
983
|
+
width: 16px; height: 16px;
|
|
984
|
+
border-radius: 4px;
|
|
985
|
+
flex: 0 0 auto;
|
|
986
|
+
object-fit: cover;
|
|
987
|
+
}
|
|
988
|
+
.chat-citation-preview__fav--mono {
|
|
989
|
+
display: flex; align-items: center; justify-content: center;
|
|
990
|
+
color: #fff; font-size: 10px; font-weight: 700;
|
|
991
|
+
}
|
|
992
|
+
.chat-citation-preview__domain {
|
|
993
|
+
font-size: 12px;
|
|
994
|
+
color: var(--tplane-chat-text-muted);
|
|
995
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
996
|
+
}
|
|
997
|
+
.chat-citation-preview__type {
|
|
998
|
+
margin-left: auto;
|
|
999
|
+
font-size: 11px;
|
|
1000
|
+
color: var(--tplane-chat-text-muted);
|
|
1001
|
+
flex: 0 0 auto;
|
|
1002
|
+
}
|
|
1003
|
+
.chat-citation-preview__title {
|
|
1004
|
+
font-size: 14px; font-weight: 600; line-height: 1.35;
|
|
1005
|
+
margin: 0 0 5px;
|
|
1006
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
1007
|
+
}
|
|
1008
|
+
.chat-citation-preview__snippet {
|
|
1009
|
+
font-size: 12.5px; color: var(--tplane-chat-text-muted); line-height: 1.5;
|
|
1010
|
+
margin: 0;
|
|
1011
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
1012
|
+
}
|
|
1013
|
+
.chat-citation-preview__foot {
|
|
1014
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1015
|
+
margin-top: 10px; padding-top: 9px;
|
|
1016
|
+
border-top: 1px solid var(--tplane-chat-separator);
|
|
1017
|
+
}
|
|
1018
|
+
.chat-citation-preview__open {
|
|
1019
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
1020
|
+
font-size: 12px; font-weight: 600;
|
|
1021
|
+
color: var(--tplane-chat-citation-accent);
|
|
1022
|
+
text-decoration: none;
|
|
1023
|
+
}
|
|
1024
|
+
.chat-citation-preview__open:hover { text-decoration: underline; }
|
|
1025
|
+
.chat-citation-preview__meta { font-size: 11px; color: var(--tplane-chat-text-muted); }
|
|
1026
|
+
`;
|
|
1027
|
+
/** Sources panel (chat-citations) + detail card (chat-citations-card). */
|
|
1028
|
+
const CHAT_CITATIONS_PANEL_STYLES = `
|
|
1029
|
+
:host { display: block; }
|
|
1030
|
+
.chat-citations {
|
|
1031
|
+
margin-top: var(--tplane-chat-space-5);
|
|
1032
|
+
padding-top: var(--tplane-chat-space-4);
|
|
1033
|
+
border-top: 1px solid var(--tplane-chat-separator);
|
|
1034
|
+
}
|
|
1035
|
+
.chat-citations__header {
|
|
1036
|
+
display: flex; align-items: center; gap: 9px;
|
|
1037
|
+
width: 100%;
|
|
1038
|
+
padding: 0 0 11px;
|
|
1039
|
+
background: none; border: 0;
|
|
1040
|
+
font: inherit; color: inherit;
|
|
1041
|
+
cursor: pointer; text-align: left;
|
|
1042
|
+
}
|
|
1043
|
+
.chat-citations__heading { font-size: 13px; font-weight: 600; }
|
|
1044
|
+
.chat-citations__count {
|
|
1045
|
+
font-size: 11px; font-weight: 600;
|
|
1046
|
+
color: var(--tplane-chat-text-muted);
|
|
1047
|
+
background: var(--tplane-chat-surface-alt);
|
|
1048
|
+
border: 1px solid var(--tplane-chat-separator);
|
|
1049
|
+
border-radius: 20px;
|
|
1050
|
+
padding: 1px 7px;
|
|
1051
|
+
}
|
|
1052
|
+
.chat-citations__favstack { display: flex; margin-left: 2px; }
|
|
1053
|
+
.chat-citations__fav {
|
|
1054
|
+
width: 16px; height: 16px;
|
|
1055
|
+
border-radius: 4px;
|
|
1056
|
+
margin-left: -5px;
|
|
1057
|
+
border: 1.5px solid var(--tplane-chat-surface);
|
|
1058
|
+
object-fit: cover;
|
|
1059
|
+
}
|
|
1060
|
+
.chat-citations__fav:first-child { margin-left: 0; }
|
|
1061
|
+
.chat-citations__fav--mono {
|
|
1062
|
+
display: flex; align-items: center; justify-content: center;
|
|
1063
|
+
color: #fff; font-size: 9px; font-weight: 700;
|
|
1064
|
+
}
|
|
1065
|
+
.chat-citations__chevron {
|
|
1066
|
+
margin-left: auto;
|
|
1067
|
+
color: var(--tplane-chat-text-muted);
|
|
1068
|
+
transition: transform 120ms ease;
|
|
1069
|
+
}
|
|
1070
|
+
.chat-citations__chevron.is-open { transform: rotate(180deg); }
|
|
1071
|
+
.chat-citations__list {
|
|
1072
|
+
list-style: none; margin: 0; padding: 0;
|
|
1073
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
1074
|
+
}
|
|
1075
|
+
.chat-citations__item { margin: 0; }
|
|
1076
|
+
|
|
1077
|
+
.chat-citations-card {
|
|
1078
|
+
display: flex; gap: 10px;
|
|
1079
|
+
padding: 10px 11px;
|
|
1080
|
+
background: var(--tplane-chat-surface);
|
|
1081
|
+
border: 1px solid var(--tplane-chat-separator);
|
|
1082
|
+
border-radius: var(--tplane-chat-radius-card);
|
|
1083
|
+
text-decoration: none; color: inherit;
|
|
1084
|
+
cursor: pointer;
|
|
1085
|
+
transition: border-color 120ms ease, background 120ms ease;
|
|
1086
|
+
}
|
|
1087
|
+
.chat-citations-card:hover,
|
|
1088
|
+
.chat-citations-card:focus-visible {
|
|
1089
|
+
border-color: var(--tplane-chat-citation-accent-border);
|
|
1090
|
+
outline: none;
|
|
1091
|
+
}
|
|
1092
|
+
.chat-citations-card__index {
|
|
1093
|
+
flex: 0 0 auto;
|
|
1094
|
+
width: 18px; height: 18px;
|
|
1095
|
+
border-radius: 5px;
|
|
1096
|
+
background: var(--tplane-chat-surface-alt);
|
|
1097
|
+
border: 1px solid var(--tplane-chat-separator);
|
|
1098
|
+
color: var(--tplane-chat-text-muted);
|
|
1099
|
+
font-size: 11px; font-weight: 600;
|
|
1100
|
+
display: flex; align-items: center; justify-content: center;
|
|
1101
|
+
margin-top: 1px;
|
|
1102
|
+
}
|
|
1103
|
+
.chat-citations-card__body { min-width: 0; flex: 1; }
|
|
1104
|
+
.chat-citations-card__top {
|
|
1105
|
+
display: flex; align-items: center; gap: 6px; margin-bottom: 3px;
|
|
1106
|
+
}
|
|
1107
|
+
.chat-citations-card__fav {
|
|
1108
|
+
width: 14px; height: 14px; border-radius: 3px; flex: 0 0 auto; object-fit: cover;
|
|
1109
|
+
}
|
|
1110
|
+
.chat-citations-card__fav--mono {
|
|
1111
|
+
display: flex; align-items: center; justify-content: center;
|
|
1112
|
+
color: #fff; font-size: 8px; font-weight: 700;
|
|
1113
|
+
}
|
|
1114
|
+
.chat-citations-card__domain { font-size: 11.5px; color: var(--tplane-chat-text-muted); }
|
|
1115
|
+
.chat-citations-card__type { margin-left: auto; font-size: 10.5px; color: var(--tplane-chat-text-muted); }
|
|
1116
|
+
.chat-citations-card__title {
|
|
1117
|
+
font-size: 13.5px; font-weight: 600; line-height: 1.35;
|
|
1118
|
+
margin: 0 0 2px;
|
|
1119
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1120
|
+
}
|
|
1121
|
+
.chat-citations-card:hover .chat-citations-card__title { color: var(--tplane-chat-citation-accent); }
|
|
1122
|
+
.chat-citations-card__snippet {
|
|
1123
|
+
font-size: 12px; color: var(--tplane-chat-text-muted); line-height: 1.45;
|
|
1124
|
+
margin: 0;
|
|
1125
|
+
display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
|
|
1126
|
+
}
|
|
1127
|
+
`;
|
|
1128
|
+
|
|
840
1129
|
// libs/chat/src/lib/primitives/chat-citations/chat-citations-card.component.ts
|
|
841
1130
|
// SPDX-License-Identifier: MIT
|
|
1131
|
+
/**
|
|
1132
|
+
* Sources-panel detail card: index badge, favicon/monogram + domain + type,
|
|
1133
|
+
* title, one-line snippet. Renders as an <a> (opens the source) when a url is
|
|
1134
|
+
* present, otherwise a non-interactive <div>. Shares the panel style module.
|
|
1135
|
+
*/
|
|
842
1136
|
class ChatCitationsCardComponent {
|
|
843
1137
|
citation = input.required(...(ngDevMode ? [{ debugName: "citation" }] : []));
|
|
1138
|
+
domain = computed(() => deriveDomain(this.citation().url), ...(ngDevMode ? [{ debugName: "domain" }] : []));
|
|
1139
|
+
title = computed(() => this.citation().title ?? this.citation().url ?? null, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1140
|
+
monogram = computed(() => deriveMonogram(this.citation()), ...(ngDevMode ? [{ debugName: "monogram" }] : []));
|
|
1141
|
+
monoColor = computed(() => monogramColor(this.citation()), ...(ngDevMode ? [{ debugName: "monoColor" }] : []));
|
|
1142
|
+
typeLabel = computed(() => citationTypeLabel(this.citation()), ...(ngDevMode ? [{ debugName: "typeLabel" }] : []));
|
|
844
1143
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
845
1144
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatCitationsCardComponent, isStandalone: true, selector: "chat-citations-card", inputs: { citation: { classPropertyName: "citation", publicName: "citation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
846
|
-
|
|
847
|
-
<
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1145
|
+
@if (citation().url; as url) {
|
|
1146
|
+
<a class="chat-citations-card" [href]="url" target="_blank" rel="noopener noreferrer"
|
|
1147
|
+
[attr.aria-label]="'Source ' + citation().index + ': ' + (citation().title ?? url)">
|
|
1148
|
+
<ng-container [ngTemplateOutlet]="inner" />
|
|
1149
|
+
</a>
|
|
1150
|
+
} @else {
|
|
1151
|
+
<div class="chat-citations-card">
|
|
1152
|
+
<ng-container [ngTemplateOutlet]="inner" />
|
|
1153
|
+
</div>
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
<ng-template #inner>
|
|
1157
|
+
<span class="chat-citations-card__index">{{ citation().index }}</span>
|
|
1158
|
+
<span class="chat-citations-card__body">
|
|
1159
|
+
<span class="chat-citations-card__top">
|
|
1160
|
+
@if (citation().iconUrl; as icon) {
|
|
1161
|
+
<img class="chat-citations-card__fav" [src]="icon" alt="" width="14" height="14" />
|
|
1162
|
+
} @else {
|
|
1163
|
+
<span class="chat-citations-card__fav chat-citations-card__fav--mono"
|
|
1164
|
+
[style.background]="monoColor()">{{ monogram() }}</span>
|
|
1165
|
+
}
|
|
1166
|
+
@if (domain(); as d) { <span class="chat-citations-card__domain">{{ d }}</span> }
|
|
1167
|
+
@if (typeLabel(); as t) { <span class="chat-citations-card__type">{{ t }}</span> }
|
|
1168
|
+
</span>
|
|
1169
|
+
@if (title(); as t) {
|
|
1170
|
+
<span class="chat-citations-card__title">{{ t }}</span>
|
|
855
1171
|
}
|
|
856
1172
|
@if (citation().snippet; as s) {
|
|
857
|
-
<
|
|
1173
|
+
<span class="chat-citations-card__snippet">{{ s }}</span>
|
|
858
1174
|
}
|
|
859
|
-
</
|
|
860
|
-
</
|
|
861
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1175
|
+
</span>
|
|
1176
|
+
</ng-template>
|
|
1177
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
862
1178
|
}
|
|
863
1179
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsCardComponent, decorators: [{
|
|
864
1180
|
type: Component,
|
|
865
|
-
args: [{
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
<div class="chat-citations-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1181
|
+
args: [{ selector: 'chat-citations-card', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: `
|
|
1182
|
+
@if (citation().url; as url) {
|
|
1183
|
+
<a class="chat-citations-card" [href]="url" target="_blank" rel="noopener noreferrer"
|
|
1184
|
+
[attr.aria-label]="'Source ' + citation().index + ': ' + (citation().title ?? url)">
|
|
1185
|
+
<ng-container [ngTemplateOutlet]="inner" />
|
|
1186
|
+
</a>
|
|
1187
|
+
} @else {
|
|
1188
|
+
<div class="chat-citations-card">
|
|
1189
|
+
<ng-container [ngTemplateOutlet]="inner" />
|
|
1190
|
+
</div>
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
<ng-template #inner>
|
|
1194
|
+
<span class="chat-citations-card__index">{{ citation().index }}</span>
|
|
1195
|
+
<span class="chat-citations-card__body">
|
|
1196
|
+
<span class="chat-citations-card__top">
|
|
1197
|
+
@if (citation().iconUrl; as icon) {
|
|
1198
|
+
<img class="chat-citations-card__fav" [src]="icon" alt="" width="14" height="14" />
|
|
1199
|
+
} @else {
|
|
1200
|
+
<span class="chat-citations-card__fav chat-citations-card__fav--mono"
|
|
1201
|
+
[style.background]="monoColor()">{{ monogram() }}</span>
|
|
1202
|
+
}
|
|
1203
|
+
@if (domain(); as d) { <span class="chat-citations-card__domain">{{ d }}</span> }
|
|
1204
|
+
@if (typeLabel(); as t) { <span class="chat-citations-card__type">{{ t }}</span> }
|
|
1205
|
+
</span>
|
|
1206
|
+
@if (title(); as t) {
|
|
1207
|
+
<span class="chat-citations-card__title">{{ t }}</span>
|
|
879
1208
|
}
|
|
880
1209
|
@if (citation().snippet; as s) {
|
|
881
|
-
<
|
|
1210
|
+
<span class="chat-citations-card__snippet">{{ s }}</span>
|
|
882
1211
|
}
|
|
883
|
-
</
|
|
884
|
-
</
|
|
885
|
-
`,
|
|
886
|
-
}]
|
|
1212
|
+
</span>
|
|
1213
|
+
</ng-template>
|
|
1214
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
|
|
887
1215
|
}], propDecorators: { citation: [{ type: i0.Input, args: [{ isSignal: true, alias: "citation", required: true }] }] } });
|
|
888
1216
|
|
|
889
1217
|
// libs/chat/src/lib/markdown/citations-resolver.service.ts
|
|
@@ -952,29 +1280,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
952
1280
|
type: Directive,
|
|
953
1281
|
args: [{ selector: 'ng-template[chatCitationCard]', standalone: true }]
|
|
954
1282
|
}] });
|
|
1283
|
+
let nextCitationsId = 0;
|
|
955
1284
|
class ChatCitationsComponent {
|
|
956
1285
|
message = input.required(...(ngDevMode ? [{ debugName: "message" }] : []));
|
|
957
1286
|
heading = input('Sources', ...(ngDevMode ? [{ debugName: "heading" }] : []));
|
|
1287
|
+
expanded = signal(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
1288
|
+
listId = `chat-citations-list-${nextCitationsId++}`;
|
|
958
1289
|
cardTpl = null;
|
|
959
|
-
/**
|
|
960
|
-
* Optional resolver — present when chat-citations is rendered inside a
|
|
961
|
-
* chat-message that provides CitationsResolverService (the standard
|
|
962
|
-
* placement). When absent, the panel reads only Message.citations.
|
|
963
|
-
*/
|
|
964
1290
|
resolver = inject(CitationsResolverService, { optional: true });
|
|
965
1291
|
/**
|
|
966
1292
|
* Combined citation list:
|
|
967
1293
|
* 1. Message.citations (provider-populated, takes precedence by id)
|
|
968
|
-
* 2. Markdown sidecar defs (Pandoc
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
* Sorted by index ascending. This guarantees the sources panel surfaces
|
|
972
|
-
* citations whether they come from message metadata, content syntax, or
|
|
973
|
-
* both — matching the same precedence as inline-marker resolution.
|
|
1294
|
+
* 2. Markdown sidecar defs (Pandoc [^id]: lines), merged for unseen ids.
|
|
1295
|
+
* Sorted by index ascending.
|
|
974
1296
|
*/
|
|
975
1297
|
citations = computed(() => {
|
|
976
1298
|
const fromMessage = this.message().citations ?? [];
|
|
977
|
-
const seenIds = new Set(fromMessage.map(c => c.id));
|
|
1299
|
+
const seenIds = new Set(fromMessage.map((c) => c.id));
|
|
978
1300
|
const fromMarkdown = [];
|
|
979
1301
|
const mdDefs = this.resolver?.markdownDefs();
|
|
980
1302
|
if (mdDefs) {
|
|
@@ -985,52 +1307,100 @@ class ChatCitationsComponent {
|
|
|
985
1307
|
}
|
|
986
1308
|
return [...fromMessage, ...fromMarkdown].sort((a, b) => a.index - b.index);
|
|
987
1309
|
}, ...(ngDevMode ? [{ debugName: "citations" }] : []));
|
|
1310
|
+
/** First 3 sources, mapped to favicon/monogram chips for the header preview. */
|
|
1311
|
+
favstack = computed(() => this.citations().slice(0, 3).map((c) => ({
|
|
1312
|
+
id: c.id, iconUrl: c.iconUrl, mono: deriveMonogram(c), color: monogramColor(c),
|
|
1313
|
+
})), ...(ngDevMode ? [{ debugName: "favstack" }] : []));
|
|
988
1314
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
989
1315
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatCitationsComponent, isStandalone: true, selector: "chat-citations", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "cardTpl", first: true, predicate: ChatCitationCardTemplateDirective, descendants: true }], ngImport: i0, template: `
|
|
990
1316
|
@if (citations().length > 0) {
|
|
991
1317
|
<section class="chat-citations">
|
|
992
|
-
<
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1318
|
+
<button
|
|
1319
|
+
type="button"
|
|
1320
|
+
class="chat-citations__header"
|
|
1321
|
+
[attr.aria-expanded]="expanded()"
|
|
1322
|
+
[attr.aria-controls]="listId"
|
|
1323
|
+
(click)="expanded.set(!expanded())"
|
|
1324
|
+
>
|
|
1325
|
+
<span class="chat-citations__heading">{{ heading() }}</span>
|
|
1326
|
+
<span class="chat-citations__count">{{ citations().length }}</span>
|
|
1327
|
+
<span class="chat-citations__favstack" aria-hidden="true">
|
|
1328
|
+
@for (f of favstack(); track f.id) {
|
|
1329
|
+
@if (f.iconUrl) {
|
|
1330
|
+
<img class="chat-citations__fav" [src]="f.iconUrl" alt="" width="16" height="16" />
|
|
998
1331
|
} @else {
|
|
999
|
-
<chat-
|
|
1332
|
+
<span class="chat-citations__fav chat-citations__fav--mono"
|
|
1333
|
+
[style.background]="f.color">{{ f.mono }}</span>
|
|
1000
1334
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1335
|
+
}
|
|
1336
|
+
</span>
|
|
1337
|
+
<svg class="chat-citations__chevron" [class.is-open]="expanded()"
|
|
1338
|
+
viewBox="0 0 24 24" aria-hidden="true" width="15" height="15">
|
|
1339
|
+
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" />
|
|
1340
|
+
</svg>
|
|
1341
|
+
</button>
|
|
1342
|
+
@if (expanded()) {
|
|
1343
|
+
<ul class="chat-citations__list" [id]="listId">
|
|
1344
|
+
@for (c of citations(); track c.id) {
|
|
1345
|
+
<li class="chat-citations__item">
|
|
1346
|
+
@if (cardTpl) {
|
|
1347
|
+
<ng-container *ngTemplateOutlet="cardTpl.tpl; context: { $implicit: c }" />
|
|
1348
|
+
} @else {
|
|
1349
|
+
<chat-citations-card [citation]="c" />
|
|
1350
|
+
}
|
|
1351
|
+
</li>
|
|
1352
|
+
}
|
|
1353
|
+
</ul>
|
|
1354
|
+
}
|
|
1004
1355
|
</section>
|
|
1005
1356
|
}
|
|
1006
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatCitationsCardComponent, selector: "chat-citations-card", inputs: ["citation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1357
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatCitationsCardComponent, selector: "chat-citations-card", inputs: ["citation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1007
1358
|
}
|
|
1008
1359
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsComponent, decorators: [{
|
|
1009
1360
|
type: Component,
|
|
1010
|
-
args: [{
|
|
1011
|
-
selector: 'chat-citations',
|
|
1012
|
-
standalone: true,
|
|
1013
|
-
imports: [NgTemplateOutlet, ChatCitationsCardComponent],
|
|
1014
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1015
|
-
template: `
|
|
1361
|
+
args: [{ selector: 'chat-citations', standalone: true, imports: [NgTemplateOutlet, ChatCitationsCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1016
1362
|
@if (citations().length > 0) {
|
|
1017
1363
|
<section class="chat-citations">
|
|
1018
|
-
<
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1364
|
+
<button
|
|
1365
|
+
type="button"
|
|
1366
|
+
class="chat-citations__header"
|
|
1367
|
+
[attr.aria-expanded]="expanded()"
|
|
1368
|
+
[attr.aria-controls]="listId"
|
|
1369
|
+
(click)="expanded.set(!expanded())"
|
|
1370
|
+
>
|
|
1371
|
+
<span class="chat-citations__heading">{{ heading() }}</span>
|
|
1372
|
+
<span class="chat-citations__count">{{ citations().length }}</span>
|
|
1373
|
+
<span class="chat-citations__favstack" aria-hidden="true">
|
|
1374
|
+
@for (f of favstack(); track f.id) {
|
|
1375
|
+
@if (f.iconUrl) {
|
|
1376
|
+
<img class="chat-citations__fav" [src]="f.iconUrl" alt="" width="16" height="16" />
|
|
1024
1377
|
} @else {
|
|
1025
|
-
<chat-
|
|
1378
|
+
<span class="chat-citations__fav chat-citations__fav--mono"
|
|
1379
|
+
[style.background]="f.color">{{ f.mono }}</span>
|
|
1026
1380
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1381
|
+
}
|
|
1382
|
+
</span>
|
|
1383
|
+
<svg class="chat-citations__chevron" [class.is-open]="expanded()"
|
|
1384
|
+
viewBox="0 0 24 24" aria-hidden="true" width="15" height="15">
|
|
1385
|
+
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" />
|
|
1386
|
+
</svg>
|
|
1387
|
+
</button>
|
|
1388
|
+
@if (expanded()) {
|
|
1389
|
+
<ul class="chat-citations__list" [id]="listId">
|
|
1390
|
+
@for (c of citations(); track c.id) {
|
|
1391
|
+
<li class="chat-citations__item">
|
|
1392
|
+
@if (cardTpl) {
|
|
1393
|
+
<ng-container *ngTemplateOutlet="cardTpl.tpl; context: { $implicit: c }" />
|
|
1394
|
+
} @else {
|
|
1395
|
+
<chat-citations-card [citation]="c" />
|
|
1396
|
+
}
|
|
1397
|
+
</li>
|
|
1398
|
+
}
|
|
1399
|
+
</ul>
|
|
1400
|
+
}
|
|
1030
1401
|
</section>
|
|
1031
1402
|
}
|
|
1032
|
-
`,
|
|
1033
|
-
}]
|
|
1403
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
|
|
1034
1404
|
}], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], cardTpl: [{
|
|
1035
1405
|
type: ContentChild,
|
|
1036
1406
|
args: [ChatCitationCardTemplateDirective]
|
|
@@ -2397,1593 +2767,2127 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
2397
2767
|
}]
|
|
2398
2768
|
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2399
2769
|
|
|
2400
|
-
// libs/chat/src/lib/
|
|
2770
|
+
// libs/chat/src/lib/primitives/overlay/overlay-container.ts
|
|
2401
2771
|
// SPDX-License-Identifier: MIT
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
@if (r.citation.url; as href) {
|
|
2413
|
-
<a class="chat-citation-marker"
|
|
2414
|
-
[attr.href]="href"
|
|
2415
|
-
[attr.title]="r.citation.snippet ?? href"
|
|
2416
|
-
target="_blank" rel="noopener noreferrer">
|
|
2417
|
-
<sup>[{{ node().index }}]</sup>
|
|
2418
|
-
</a>
|
|
2419
|
-
} @else {
|
|
2420
|
-
<span class="chat-citation-marker chat-citation-marker--no-url"
|
|
2421
|
-
[attr.title]="r.citation.snippet ?? r.citation.title ?? null">
|
|
2422
|
-
<sup>[{{ node().index }}]</sup>
|
|
2423
|
-
</span>
|
|
2424
|
-
}
|
|
2425
|
-
} @else {
|
|
2426
|
-
<span class="chat-citation-marker chat-citation-marker--unresolved"
|
|
2427
|
-
[attr.title]="'No source available'">
|
|
2428
|
-
<sup>[{{ node().index }}]</sup>
|
|
2429
|
-
</span>
|
|
2430
|
-
}
|
|
2431
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2772
|
+
const CONTAINER_CLASS = 'chat-overlay-container';
|
|
2773
|
+
const STYLE_ID = 'chat-overlay-structure';
|
|
2774
|
+
// Structural CSS, injected once into <head> (same pattern as ROOT_TOKEN_STYLES
|
|
2775
|
+
// in chat-tokens.ts) so consumers need not import any stylesheet.
|
|
2776
|
+
const STRUCTURE_CSS = `
|
|
2777
|
+
.${CONTAINER_CLASS} {
|
|
2778
|
+
position: fixed;
|
|
2779
|
+
inset: 0;
|
|
2780
|
+
z-index: 1000;
|
|
2781
|
+
pointer-events: none;
|
|
2432
2782
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
<span class="chat-citation-marker chat-citation-marker--no-url"
|
|
2450
|
-
[attr.title]="r.citation.snippet ?? r.citation.title ?? null">
|
|
2451
|
-
<sup>[{{ node().index }}]</sup>
|
|
2452
|
-
</span>
|
|
2453
|
-
}
|
|
2454
|
-
} @else {
|
|
2455
|
-
<span class="chat-citation-marker chat-citation-marker--unresolved"
|
|
2456
|
-
[attr.title]="'No source available'">
|
|
2457
|
-
<sup>[{{ node().index }}]</sup>
|
|
2458
|
-
</span>
|
|
2783
|
+
.chat-overlay-pane {
|
|
2784
|
+
position: absolute;
|
|
2785
|
+
pointer-events: auto;
|
|
2786
|
+
}
|
|
2787
|
+
`;
|
|
2788
|
+
/** Returns the single shared overlay container appended to <body>, creating it
|
|
2789
|
+
* (and injecting structural CSS) on first call. */
|
|
2790
|
+
function getOverlayContainer(doc) {
|
|
2791
|
+
const existing = doc.querySelector('.' + CONTAINER_CLASS);
|
|
2792
|
+
if (existing)
|
|
2793
|
+
return existing;
|
|
2794
|
+
if (!doc.getElementById(STYLE_ID)) {
|
|
2795
|
+
const style = doc.createElement('style');
|
|
2796
|
+
style.id = STYLE_ID;
|
|
2797
|
+
style.textContent = STRUCTURE_CSS;
|
|
2798
|
+
doc.head.appendChild(style);
|
|
2459
2799
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
// SPDX-License-Identifier: MIT
|
|
2466
|
-
/**
|
|
2467
|
-
* Provided by MarkdownTableRowComponent for header rows so that
|
|
2468
|
-
* MarkdownTableCellComponent can render <th> instead of <td>.
|
|
2469
|
-
* The value is a Signal<boolean> so that it tracks the row's isHeader reactively.
|
|
2470
|
-
*/
|
|
2471
|
-
const IS_HEADER_ROW = new InjectionToken('IS_HEADER_ROW', {
|
|
2472
|
-
providedIn: null,
|
|
2473
|
-
factory: () => signal(false),
|
|
2474
|
-
});
|
|
2800
|
+
const container = doc.createElement('div');
|
|
2801
|
+
container.className = CONTAINER_CLASS;
|
|
2802
|
+
doc.body.appendChild(container);
|
|
2803
|
+
return container;
|
|
2804
|
+
}
|
|
2475
2805
|
|
|
2476
|
-
// libs/chat/src/lib/
|
|
2806
|
+
// libs/chat/src/lib/primitives/overlay/connected-position.ts
|
|
2477
2807
|
// SPDX-License-Identifier: MIT
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
}
|
|
2487
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2488
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownTableRowComponent, isStandalone: true, selector: "chat-md-table-row", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
2489
|
-
{
|
|
2490
|
-
provide: IS_HEADER_ROW,
|
|
2491
|
-
useFactory: () => {
|
|
2492
|
-
const comp = inject(MarkdownTableRowComponent);
|
|
2493
|
-
return computed(() => comp.node().isHeader);
|
|
2494
|
-
},
|
|
2495
|
-
},
|
|
2496
|
-
], ngImport: i0, template: `
|
|
2497
|
-
<tr class="chat-md-table-row" [class.chat-md-table-row--header]="node().isHeader">
|
|
2498
|
-
@for (child of node().children; track $index) {
|
|
2499
|
-
@let comp = resolve(child);
|
|
2500
|
-
@if (comp) {
|
|
2501
|
-
<ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
</tr>
|
|
2505
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2808
|
+
//
|
|
2809
|
+
// Minimal port of CDK's FlexibleConnectedPositionStrategy fit logic
|
|
2810
|
+
// (~/repos/components/src/cdk/overlay/position/flexible-connected-position-strategy.ts):
|
|
2811
|
+
// _getOriginPoint + _getOverlayPoint + _getOverlayFit + _pushOverlayOnScreen.
|
|
2812
|
+
// Omits flexible-dimensions, grow-after-open, RTL, and virtual-keyboard handling.
|
|
2813
|
+
function originPoint(origin, pos) {
|
|
2814
|
+
const x = pos.originX === 'center' ? origin.left + origin.width / 2 : pos.originX === 'start' ? origin.left : origin.right;
|
|
2815
|
+
const y = pos.originY === 'center' ? origin.top + origin.height / 2 : pos.originY === 'top' ? origin.top : origin.bottom;
|
|
2816
|
+
return { x, y };
|
|
2506
2817
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
<ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
</tr>
|
|
2523
|
-
`,
|
|
2524
|
-
providers: [
|
|
2525
|
-
{
|
|
2526
|
-
provide: IS_HEADER_ROW,
|
|
2527
|
-
useFactory: () => {
|
|
2528
|
-
const comp = inject(MarkdownTableRowComponent);
|
|
2529
|
-
return computed(() => comp.node().isHeader);
|
|
2530
|
-
},
|
|
2531
|
-
},
|
|
2532
|
-
],
|
|
2533
|
-
}]
|
|
2534
|
-
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2535
|
-
|
|
2536
|
-
// libs/chat/src/lib/markdown/views/markdown-table.component.ts
|
|
2537
|
-
// SPDX-License-Identifier: MIT
|
|
2538
|
-
class MarkdownTableComponent {
|
|
2539
|
-
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
2540
|
-
headerRow = computed(() => {
|
|
2541
|
-
const rows = this.node().children;
|
|
2542
|
-
return rows.length > 0 && rows[0].isHeader ? rows[0] : null;
|
|
2543
|
-
}, ...(ngDevMode ? [{ debugName: "headerRow" }] : []));
|
|
2544
|
-
bodyRows = computed(() => {
|
|
2545
|
-
const rows = this.node().children;
|
|
2546
|
-
return rows[0]?.isHeader ? rows.slice(1) : rows;
|
|
2547
|
-
}, ...(ngDevMode ? [{ debugName: "bodyRows" }] : []));
|
|
2548
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2549
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownTableComponent, isStandalone: true, selector: "chat-md-table", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
2550
|
-
<table class="chat-md-table">
|
|
2551
|
-
<thead>
|
|
2552
|
-
@if (headerRow(); as row) {
|
|
2553
|
-
<chat-md-table-row [node]="row" />
|
|
2554
|
-
}
|
|
2555
|
-
</thead>
|
|
2556
|
-
<tbody>
|
|
2557
|
-
@for (row of bodyRows(); track $index) {
|
|
2558
|
-
<chat-md-table-row [node]="row" />
|
|
2559
|
-
}
|
|
2560
|
-
</tbody>
|
|
2561
|
-
</table>
|
|
2562
|
-
`, isInline: true, dependencies: [{ kind: "component", type: MarkdownTableRowComponent, selector: "chat-md-table-row", inputs: ["node"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2818
|
+
function overlayPoint(origin, size, pos) {
|
|
2819
|
+
let x = origin.x;
|
|
2820
|
+
if (pos.overlayX === 'center')
|
|
2821
|
+
x -= size.width / 2;
|
|
2822
|
+
else if (pos.overlayX === 'end')
|
|
2823
|
+
x -= size.width;
|
|
2824
|
+
let y = origin.y;
|
|
2825
|
+
if (pos.overlayY === 'center')
|
|
2826
|
+
y -= size.height / 2;
|
|
2827
|
+
else if (pos.overlayY === 'bottom')
|
|
2828
|
+
y -= size.height;
|
|
2829
|
+
return { x: x + (pos.offsetX ?? 0), y: y + (pos.offsetY ?? 0) };
|
|
2563
2830
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2831
|
+
function fitArea(point, size, viewport) {
|
|
2832
|
+
const left = point.x;
|
|
2833
|
+
const right = point.x + size.width;
|
|
2834
|
+
const top = point.y;
|
|
2835
|
+
const bottom = point.y + size.height;
|
|
2836
|
+
const visibleW = Math.max(0, Math.min(right, viewport.right) - Math.max(left, viewport.left));
|
|
2837
|
+
const visibleH = Math.max(0, Math.min(bottom, viewport.bottom) - Math.max(top, viewport.top));
|
|
2838
|
+
const fits = left >= viewport.left && right <= viewport.right && top >= viewport.top && bottom <= viewport.bottom;
|
|
2839
|
+
return { area: visibleW * visibleH, fits };
|
|
2840
|
+
}
|
|
2841
|
+
function pushOnScreen(point, size, viewport) {
|
|
2842
|
+
const maxLeft = viewport.right - size.width;
|
|
2843
|
+
const maxTop = viewport.bottom - size.height;
|
|
2844
|
+
return {
|
|
2845
|
+
x: Math.max(viewport.left, Math.min(point.x, maxLeft)),
|
|
2846
|
+
y: Math.max(viewport.top, Math.min(point.y, maxTop)),
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
/** Sensible fallback when no positions are supplied: below the origin, start-aligned. */
|
|
2850
|
+
const DEFAULT_POSITION = { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' };
|
|
2851
|
+
function computeConnectedPosition(args) {
|
|
2852
|
+
const { originRect, overlaySize, viewport, positions } = args;
|
|
2853
|
+
// An empty positions array (the directive's input default) must still anchor to
|
|
2854
|
+
// the trigger rather than crash on best! / render at 0,0.
|
|
2855
|
+
const list = positions.length ? positions : [DEFAULT_POSITION];
|
|
2856
|
+
let best = null;
|
|
2857
|
+
for (const pos of list) {
|
|
2858
|
+
const point = overlayPoint(originPoint(originRect, pos), overlaySize, pos);
|
|
2859
|
+
const { area, fits } = fitArea(point, overlaySize, viewport);
|
|
2860
|
+
if (fits) {
|
|
2861
|
+
return { top: point.y, left: point.x, position: pos };
|
|
2581
2862
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
`,
|
|
2585
|
-
}]
|
|
2586
|
-
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2587
|
-
|
|
2588
|
-
// libs/chat/src/lib/markdown/views/markdown-table-cell.component.ts
|
|
2589
|
-
// SPDX-License-Identifier: MIT
|
|
2590
|
-
class MarkdownTableCellComponent {
|
|
2591
|
-
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
2592
|
-
isHeaderRowToken = inject(IS_HEADER_ROW, { optional: true });
|
|
2593
|
-
isHeader = computed(() => this.isHeaderRowToken ? this.isHeaderRowToken() : false, ...(ngDevMode ? [{ debugName: "isHeader" }] : []));
|
|
2594
|
-
alignment = computed(() => this.node().alignment, ...(ngDevMode ? [{ debugName: "alignment" }] : []));
|
|
2595
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2596
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownTableCellComponent, isStandalone: true, selector: "chat-md-table-cell", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
2597
|
-
@if (isHeader()) {
|
|
2598
|
-
<th class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
2599
|
-
<chat-md-children [parent]="node()" />
|
|
2600
|
-
</th>
|
|
2601
|
-
} @else {
|
|
2602
|
-
<td class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
2603
|
-
<chat-md-children [parent]="node()" />
|
|
2604
|
-
</td>
|
|
2863
|
+
if (!best || area > best.area)
|
|
2864
|
+
best = { point, pos, area };
|
|
2605
2865
|
}
|
|
2606
|
-
|
|
2866
|
+
const pushed = pushOnScreen(best.point, overlaySize, viewport);
|
|
2867
|
+
return { top: pushed.y, left: pushed.x, position: best.pos };
|
|
2868
|
+
}
|
|
2869
|
+
function narrowViewport(win, margin) {
|
|
2870
|
+
return {
|
|
2871
|
+
left: margin,
|
|
2872
|
+
top: margin,
|
|
2873
|
+
right: win.innerWidth - margin,
|
|
2874
|
+
bottom: win.innerHeight - margin,
|
|
2875
|
+
width: win.innerWidth - 2 * margin,
|
|
2876
|
+
height: win.innerHeight - 2 * margin,
|
|
2877
|
+
};
|
|
2607
2878
|
}
|
|
2608
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableCellComponent, decorators: [{
|
|
2609
|
-
type: Component,
|
|
2610
|
-
args: [{
|
|
2611
|
-
selector: 'chat-md-table-cell',
|
|
2612
|
-
standalone: true,
|
|
2613
|
-
imports: [MarkdownChildrenComponent],
|
|
2614
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2615
|
-
template: `
|
|
2616
|
-
@if (isHeader()) {
|
|
2617
|
-
<th class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
2618
|
-
<chat-md-children [parent]="node()" />
|
|
2619
|
-
</th>
|
|
2620
|
-
} @else {
|
|
2621
|
-
<td class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
2622
|
-
<chat-md-children [parent]="node()" />
|
|
2623
|
-
</td>
|
|
2624
|
-
}
|
|
2625
|
-
`,
|
|
2626
|
-
}]
|
|
2627
|
-
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2628
2879
|
|
|
2629
|
-
// libs/chat/src/lib/
|
|
2880
|
+
// libs/chat/src/lib/primitives/overlay/connected-overlay.directive.ts
|
|
2630
2881
|
// SPDX-License-Identifier: MIT
|
|
2631
|
-
|
|
2632
|
-
*
|
|
2633
|
-
* the
|
|
2634
|
-
*
|
|
2635
|
-
*
|
|
2636
|
-
*
|
|
2637
|
-
*/
|
|
2638
|
-
class
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
static
|
|
2642
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: MarkdownHtmlComponent, isStandalone: true, selector: "chat-md-html", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `{{ raw() }}`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2882
|
+
/* eslint-disable @angular-eslint/no-input-rename, @angular-eslint/no-output-rename --
|
|
2883
|
+
* The `chatOverlay*` binding aliases ARE the intended public API: they namespace
|
|
2884
|
+
* the directive's inputs/outputs under the `chatOverlay` prefix (mirroring Angular
|
|
2885
|
+
* CDK's `cdkConnectedOverlay*` convention) rather than exposing bare names like
|
|
2886
|
+
* `open`/`positions` on an `<ng-template>`. The internal property names stay
|
|
2887
|
+
* concise, so aliasing here is deliberate, not a rename to avoid. */
|
|
2888
|
+
/** Marks the anchor element a connected overlay positions against. */
|
|
2889
|
+
class ChatOverlayOriginDirective {
|
|
2890
|
+
elementRef = inject(ElementRef);
|
|
2891
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatOverlayOriginDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
2892
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.6", type: ChatOverlayOriginDirective, isStandalone: true, selector: "[chatOverlayOrigin]", exportAs: ["chatOverlayOrigin"], ngImport: i0 });
|
|
2643
2893
|
}
|
|
2644
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
2645
|
-
type:
|
|
2894
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatOverlayOriginDirective, decorators: [{
|
|
2895
|
+
type: Directive,
|
|
2646
2896
|
args: [{
|
|
2647
|
-
selector: '
|
|
2897
|
+
selector: '[chatOverlayOrigin]',
|
|
2648
2898
|
standalone: true,
|
|
2649
|
-
|
|
2650
|
-
template: `{{ raw() }}`,
|
|
2899
|
+
exportAs: 'chatOverlayOrigin',
|
|
2651
2900
|
}]
|
|
2652
|
-
}]
|
|
2653
|
-
|
|
2654
|
-
// libs/chat/src/lib/markdown/cacheplane-markdown-views.ts
|
|
2655
|
-
// SPDX-License-Identifier: MIT
|
|
2901
|
+
}] });
|
|
2902
|
+
const VIEWPORT_MARGIN = 8;
|
|
2656
2903
|
/**
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2904
|
+
* Applied to an `<ng-template>`. When `chatOverlayOpen` is true, the template
|
|
2905
|
+
* content is portaled into the shared body-level overlay container and
|
|
2906
|
+
* positioned connected to `chatOverlayOrigin`, repositioning live on
|
|
2907
|
+
* scroll/resize. Closes on outside mousedown (via the `chatOverlayOutsideClick`
|
|
2908
|
+
* output) and Tab; returns focus to the origin when focus was inside the pane.
|
|
2660
2909
|
*
|
|
2661
|
-
*
|
|
2910
|
+
* NOTE: the directive does not own the open state — it only emits. To fully
|
|
2911
|
+
* close the overlay, consumers MUST handle BOTH `(chatOverlayOutsideClick)` and
|
|
2912
|
+
* `(chatOverlayDetach)`. Tab-close (and Escape, when the consumer routes it)
|
|
2913
|
+
* surface through `chatOverlayDetach`, so wiring only `chatOverlayOutsideClick`
|
|
2914
|
+
* leaves the overlay stuck open on Tab.
|
|
2662
2915
|
*/
|
|
2663
|
-
|
|
2664
|
-
'
|
|
2665
|
-
'
|
|
2666
|
-
'
|
|
2667
|
-
'
|
|
2668
|
-
|
|
2669
|
-
'
|
|
2670
|
-
'
|
|
2671
|
-
'
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
// parser. finish() is only needed to mark final node status (not used visually)
|
|
2698
|
-
// and to revert a genuinely-truncated trailing construct to CommonMark — the
|
|
2699
|
-
// live `parser.root` projection already renders everything during streaming, so
|
|
2700
|
-
// this delay has NO visual cost. It must comfortably exceed real inter-chunk
|
|
2701
|
-
// gaps (e.g. the pause between a table's header row and its delimiter row) and
|
|
2702
|
-
// any `streaming` flag flap, so finalize never fires mid-stream and reverts an
|
|
2703
|
-
// in-progress table to raw "| a | b |" text.
|
|
2704
|
-
const FINALIZE_DEBOUNCE_MS = 600;
|
|
2705
|
-
/**
|
|
2706
|
-
* Renders streaming markdown by walking a @cacheplane/partial-markdown AST
|
|
2707
|
-
* through @threadplane/render's view registry.
|
|
2708
|
-
*
|
|
2709
|
-
* Reactivity model: the live `parser.root` keeps a stable JS reference
|
|
2710
|
-
* across pushes (partial-markdown's identity guarantee). To make Angular
|
|
2711
|
-
* signals propagate downstream when the underlying tree changes, we surface
|
|
2712
|
-
* a materialized snapshot via `materialize()`. The snapshot shares
|
|
2713
|
-
* structurally — unchanged subtrees keep the SAME reference, and any
|
|
2714
|
-
* descendant change yields a NEW root reference. This lets Angular's
|
|
2715
|
-
* `Object.is` equality check both detect changes (root reference differs)
|
|
2716
|
-
* and short-circuit unchanged subtrees (per-node references stable).
|
|
2717
|
-
*
|
|
2718
|
-
* Override per-node-type renderers via the `[viewRegistry]` input or by
|
|
2719
|
-
* supplying a different `MARKDOWN_VIEW_REGISTRY` provider in the injector
|
|
2720
|
-
* tree.
|
|
2721
|
-
*/
|
|
2722
|
-
class ChatStreamingMdComponent {
|
|
2723
|
-
content = input('', ...(ngDevMode ? [{ debugName: "content" }] : []));
|
|
2724
|
-
streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
|
|
2725
|
-
viewRegistry = input(undefined, ...(ngDevMode ? [{ debugName: "viewRegistry" }] : []));
|
|
2726
|
-
resolvedRegistry = computed(() => this.viewRegistry() ?? cacheplaneMarkdownViews, ...(ngDevMode ? [{ debugName: "resolvedRegistry" }] : []));
|
|
2727
|
-
resolver = inject(CitationsResolverService, { optional: true });
|
|
2916
|
+
class ChatConnectedOverlayDirective {
|
|
2917
|
+
origin = input.required({ ...(ngDevMode ? { debugName: "origin" } : {}), alias: 'chatOverlayOrigin' });
|
|
2918
|
+
open = input(false, { ...(ngDevMode ? { debugName: "open" } : {}), alias: 'chatOverlayOpen' });
|
|
2919
|
+
positions = input([], { ...(ngDevMode ? { debugName: "positions" } : {}), alias: 'chatOverlayPositions' });
|
|
2920
|
+
panelClass = input('', { ...(ngDevMode ? { debugName: "panelClass" } : {}), alias: 'chatOverlayPanelClass' });
|
|
2921
|
+
/** Emits the pane element once attached (consumers focus content from it). */
|
|
2922
|
+
attached = output({ alias: 'chatOverlayAttached' });
|
|
2923
|
+
outsideClick = output({ alias: 'chatOverlayOutsideClick' });
|
|
2924
|
+
detached = output({ alias: 'chatOverlayDetach' });
|
|
2925
|
+
templateRef = inject((TemplateRef));
|
|
2926
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
2927
|
+
document = inject(DOCUMENT);
|
|
2928
|
+
pane = null;
|
|
2929
|
+
viewRef = null;
|
|
2930
|
+
resizeObs = null;
|
|
2931
|
+
rafId = 0;
|
|
2932
|
+
previouslyFocused = null;
|
|
2933
|
+
onScrollOrResize = () => this.scheduleReposition();
|
|
2934
|
+
onDocMouseDown = (e) => {
|
|
2935
|
+
if (!this.pane)
|
|
2936
|
+
return;
|
|
2937
|
+
const path = e.composedPath();
|
|
2938
|
+
if (path.includes(this.pane) || path.includes(this.origin().elementRef.nativeElement))
|
|
2939
|
+
return;
|
|
2940
|
+
this.outsideClick.emit(e);
|
|
2941
|
+
};
|
|
2942
|
+
onKeydown = (e) => {
|
|
2943
|
+
if (e.key !== 'Tab' || !this.pane)
|
|
2944
|
+
return;
|
|
2945
|
+
const active = this.document.activeElement;
|
|
2946
|
+
if (this.pane.contains(active) || active === this.origin().elementRef.nativeElement) {
|
|
2947
|
+
this.detached.emit();
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2728
2950
|
constructor() {
|
|
2729
2951
|
effect(() => {
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
});
|
|
2735
|
-
// Debounced finalization. `finish()` is terminal and DESTRUCTIVE: it reverts
|
|
2736
|
-
// an incomplete trailing construct to its CommonMark fallback — e.g. a table
|
|
2737
|
-
// header before its delimiter row becomes raw "| a | b |" paragraph text. We
|
|
2738
|
-
// must therefore never finalize while tokens are still arriving. The
|
|
2739
|
-
// `streaming` input is not a reliable "still arriving" signal — it can flap
|
|
2740
|
-
// false mid-stream, and at cold start it can read false for an entire live
|
|
2741
|
-
// stream — so we finalize only once streaming is false AND no new content
|
|
2742
|
-
// has arrived for a short, imperceptible window. Any new content or a
|
|
2743
|
-
// streaming=true flap re-arms the timer, so finalize fires exactly once,
|
|
2744
|
-
// after the stream truly stops. Until then the live `parser.root` projection
|
|
2745
|
-
// (0.5.x) renders the in-progress content, including streaming tables.
|
|
2746
|
-
let timer = null;
|
|
2747
|
-
effect((onCleanup) => {
|
|
2748
|
-
const isStreaming = this.streaming();
|
|
2749
|
-
this.content(); // re-arm whenever new content arrives
|
|
2750
|
-
if (timer) {
|
|
2751
|
-
clearTimeout(timer);
|
|
2752
|
-
timer = null;
|
|
2753
|
-
}
|
|
2754
|
-
if (isStreaming || this.finished)
|
|
2755
|
-
return;
|
|
2756
|
-
timer = setTimeout(() => {
|
|
2757
|
-
timer = null;
|
|
2758
|
-
if (this.streaming() || this.finished)
|
|
2759
|
-
return;
|
|
2760
|
-
if (!this.prior.endsWith('\n'))
|
|
2761
|
-
this.parser.push('\n');
|
|
2762
|
-
this.parser.finish();
|
|
2763
|
-
this.finished = true;
|
|
2764
|
-
this.finalizeTick.update((v) => v + 1);
|
|
2765
|
-
}, FINALIZE_DEBOUNCE_MS);
|
|
2766
|
-
onCleanup(() => {
|
|
2767
|
-
if (timer) {
|
|
2768
|
-
clearTimeout(timer);
|
|
2769
|
-
timer = null;
|
|
2770
|
-
}
|
|
2771
|
-
});
|
|
2952
|
+
if (this.open())
|
|
2953
|
+
this.attach();
|
|
2954
|
+
else
|
|
2955
|
+
this.dispose();
|
|
2772
2956
|
});
|
|
2957
|
+
inject(DestroyRef).onDestroy(() => this.dispose());
|
|
2773
2958
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
this.
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
}
|
|
2801
|
-
this.prior = c;
|
|
2959
|
+
attach() {
|
|
2960
|
+
if (this.pane)
|
|
2961
|
+
return;
|
|
2962
|
+
const win = this.document.defaultView;
|
|
2963
|
+
if (!win)
|
|
2964
|
+
return; // SSR / detached document
|
|
2965
|
+
this.previouslyFocused = this.document.activeElement;
|
|
2966
|
+
const pane = this.document.createElement('div');
|
|
2967
|
+
pane.className = 'chat-overlay-pane';
|
|
2968
|
+
for (const c of this.normalizePanelClass())
|
|
2969
|
+
pane.classList.add(c);
|
|
2970
|
+
getOverlayContainer(this.document).appendChild(pane);
|
|
2971
|
+
this.viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef);
|
|
2972
|
+
this.viewRef.detectChanges();
|
|
2973
|
+
for (const node of this.viewRef.rootNodes)
|
|
2974
|
+
pane.appendChild(node);
|
|
2975
|
+
this.pane = pane;
|
|
2976
|
+
this.reposition();
|
|
2977
|
+
win.addEventListener('scroll', this.onScrollOrResize, { capture: true, passive: true });
|
|
2978
|
+
win.addEventListener('resize', this.onScrollOrResize, { passive: true });
|
|
2979
|
+
this.document.addEventListener('mousedown', this.onDocMouseDown, true);
|
|
2980
|
+
this.document.addEventListener('keydown', this.onKeydown, true);
|
|
2981
|
+
if (typeof win.ResizeObserver === 'function') {
|
|
2982
|
+
this.resizeObs = new win.ResizeObserver(() => this.scheduleReposition());
|
|
2983
|
+
this.resizeObs.observe(this.origin().elementRef.nativeElement);
|
|
2984
|
+
this.resizeObs.observe(pane);
|
|
2802
2985
|
}
|
|
2803
|
-
|
|
2804
|
-
// any descendant subtree changed; same reference when nothing changed
|
|
2805
|
-
// (structural sharing). This is what makes signal-based CD propagate
|
|
2806
|
-
// downstream changes despite the parser preserving identity.
|
|
2807
|
-
return materialize(this.parser.root);
|
|
2808
|
-
}, ...(ngDevMode ? [{ debugName: "root" }] : []));
|
|
2809
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatStreamingMdComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2810
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatStreamingMdComponent, isStandalone: true, selector: "chat-streaming-md", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, viewRegistry: { classPropertyName: "viewRegistry", publicName: "viewRegistry", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
2811
|
-
{
|
|
2812
|
-
provide: MARKDOWN_VIEW_REGISTRY,
|
|
2813
|
-
useFactory: (host) => host.resolvedRegistry(),
|
|
2814
|
-
deps: [ChatStreamingMdComponent],
|
|
2815
|
-
},
|
|
2816
|
-
], ngImport: i0, template: `
|
|
2817
|
-
@if (root(); as r) {
|
|
2818
|
-
<chat-md-children [parent]="r" />
|
|
2986
|
+
this.attached.emit(pane);
|
|
2819
2987
|
}
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2988
|
+
scheduleReposition() {
|
|
2989
|
+
const win = this.document.defaultView;
|
|
2990
|
+
if (!win || !this.pane)
|
|
2991
|
+
return;
|
|
2992
|
+
if (this.rafId)
|
|
2993
|
+
win.cancelAnimationFrame(this.rafId);
|
|
2994
|
+
this.rafId = win.requestAnimationFrame(() => this.reposition());
|
|
2827
2995
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2996
|
+
reposition() {
|
|
2997
|
+
const win = this.document.defaultView;
|
|
2998
|
+
if (!win || !this.pane)
|
|
2999
|
+
return;
|
|
3000
|
+
const r = this.pane.getBoundingClientRect();
|
|
3001
|
+
const result = computeConnectedPosition({
|
|
3002
|
+
originRect: this.origin().elementRef.nativeElement.getBoundingClientRect(),
|
|
3003
|
+
overlaySize: { width: r.width, height: r.height },
|
|
3004
|
+
viewport: narrowViewport(win, VIEWPORT_MARGIN),
|
|
3005
|
+
positions: this.positions(),
|
|
3006
|
+
});
|
|
3007
|
+
this.pane.style.top = `${Math.round(result.top)}px`;
|
|
3008
|
+
this.pane.style.left = `${Math.round(result.left)}px`;
|
|
3009
|
+
}
|
|
3010
|
+
dispose() {
|
|
3011
|
+
const win = this.document.defaultView;
|
|
3012
|
+
if (this.rafId && win)
|
|
3013
|
+
win.cancelAnimationFrame(this.rafId);
|
|
3014
|
+
this.rafId = 0;
|
|
3015
|
+
if (win) {
|
|
3016
|
+
win.removeEventListener('scroll', this.onScrollOrResize, { capture: true });
|
|
3017
|
+
win.removeEventListener('resize', this.onScrollOrResize);
|
|
3018
|
+
}
|
|
3019
|
+
this.document.removeEventListener('mousedown', this.onDocMouseDown, true);
|
|
3020
|
+
this.document.removeEventListener('keydown', this.onKeydown, true);
|
|
3021
|
+
this.resizeObs?.disconnect();
|
|
3022
|
+
this.resizeObs = null;
|
|
3023
|
+
const focusWasInPane = !!this.pane && this.pane.contains(this.document.activeElement);
|
|
3024
|
+
this.viewRef?.destroy();
|
|
3025
|
+
this.viewRef = null;
|
|
3026
|
+
this.pane?.remove();
|
|
3027
|
+
this.pane = null;
|
|
3028
|
+
if (focusWasInPane && this.previouslyFocused)
|
|
3029
|
+
this.previouslyFocused.focus();
|
|
3030
|
+
this.previouslyFocused = null;
|
|
3031
|
+
}
|
|
3032
|
+
normalizePanelClass() {
|
|
3033
|
+
const pc = this.panelClass();
|
|
3034
|
+
return Array.isArray(pc) ? pc : pc ? [pc] : [];
|
|
3035
|
+
}
|
|
3036
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatConnectedOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3037
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: ChatConnectedOverlayDirective, isStandalone: true, selector: "[chatConnectedOverlay]", inputs: { origin: { classPropertyName: "origin", publicName: "chatOverlayOrigin", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "chatOverlayOpen", isSignal: true, isRequired: false, transformFunction: null }, positions: { classPropertyName: "positions", publicName: "chatOverlayPositions", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "chatOverlayPanelClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { attached: "chatOverlayAttached", outsideClick: "chatOverlayOutsideClick", detached: "chatOverlayDetach" }, ngImport: i0 });
|
|
3038
|
+
}
|
|
3039
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatConnectedOverlayDirective, decorators: [{
|
|
3040
|
+
type: Directive,
|
|
3041
|
+
args: [{
|
|
3042
|
+
selector: '[chatConnectedOverlay]',
|
|
3043
|
+
standalone: true,
|
|
3044
|
+
}]
|
|
3045
|
+
}], ctorParameters: () => [], propDecorators: { origin: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayOrigin", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayOpen", required: false }] }], positions: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayPositions", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayPanelClass", required: false }] }], attached: [{ type: i0.Output, args: ["chatOverlayAttached"] }], outsideClick: [{ type: i0.Output, args: ["chatOverlayOutsideClick"] }], detached: [{ type: i0.Output, args: ["chatOverlayDetach"] }] } });
|
|
2859
3046
|
|
|
2860
|
-
// libs/chat/src/lib/primitives/chat-
|
|
3047
|
+
// libs/chat/src/lib/primitives/chat-citations/chat-citation-preview.component.ts
|
|
2861
3048
|
// SPDX-License-Identifier: MIT
|
|
2862
3049
|
/**
|
|
2863
|
-
*
|
|
2864
|
-
*
|
|
2865
|
-
*
|
|
2866
|
-
* - Streaming: pill shows "Thinking…" with a pulsing dot; auto-expanded
|
|
2867
|
-
* so the user sees reasoning stream in real time.
|
|
2868
|
-
* - Idle, with durationMs known: pill shows "Thought for {duration}";
|
|
2869
|
-
* collapsed by default, expand on click.
|
|
2870
|
-
* - Idle, no duration: pill shows "Show reasoning"; collapsed by default.
|
|
2871
|
-
*
|
|
2872
|
-
* The body re-uses chat-streaming-md so reasoning content gets the same
|
|
2873
|
-
* markdown rendering pipeline as the visible response (lists, code,
|
|
2874
|
-
* step labels often appear in reasoning output).
|
|
2875
|
-
*
|
|
2876
|
-
* Internal state: a tristate "expanded" — null means follow auto state-
|
|
2877
|
-
* driven logic (force-expand on isStreaming, otherwise honor
|
|
2878
|
-
* defaultExpanded), boolean is a manual user choice that wins for the
|
|
2879
|
-
* lifetime of the instance.
|
|
3050
|
+
* Presentational provenance card for a single Citation. Rendered inside the
|
|
3051
|
+
* inline marker's connected-overlay pane (hover/tap preview) — self-contained
|
|
3052
|
+
* so its encapsulated styles apply even when portaled to the body-level pane.
|
|
2880
3053
|
*/
|
|
2881
|
-
class
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
if (
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
3054
|
+
class ChatCitationPreviewComponent {
|
|
3055
|
+
citation = input.required(...(ngDevMode ? [{ debugName: "citation" }] : []));
|
|
3056
|
+
domain = computed(() => deriveDomain(this.citation().url), ...(ngDevMode ? [{ debugName: "domain" }] : []));
|
|
3057
|
+
monogram = computed(() => deriveMonogram(this.citation()), ...(ngDevMode ? [{ debugName: "monogram" }] : []));
|
|
3058
|
+
monoColor = computed(() => monogramColor(this.citation()), ...(ngDevMode ? [{ debugName: "monoColor" }] : []));
|
|
3059
|
+
typeLabel = computed(() => citationTypeLabel(this.citation()), ...(ngDevMode ? [{ debugName: "typeLabel" }] : []));
|
|
3060
|
+
published = computed(() => formatPublished(this.citation().publishedAt), ...(ngDevMode ? [{ debugName: "published" }] : []));
|
|
3061
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3062
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatCitationPreviewComponent, isStandalone: true, selector: "chat-citation-preview", inputs: { citation: { classPropertyName: "citation", publicName: "citation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
3063
|
+
<div class="chat-citation-preview" role="group" [attr.aria-label]="'Source ' + citation().index">
|
|
3064
|
+
<div class="chat-citation-preview__head">
|
|
3065
|
+
@if (citation().iconUrl; as icon) {
|
|
3066
|
+
<img class="chat-citation-preview__fav" [src]="icon" alt="" width="16" height="16" />
|
|
3067
|
+
} @else {
|
|
3068
|
+
<span class="chat-citation-preview__fav chat-citation-preview__fav--mono"
|
|
3069
|
+
[style.background]="monoColor()">{{ monogram() }}</span>
|
|
3070
|
+
}
|
|
3071
|
+
@if (domain(); as d) { <span class="chat-citation-preview__domain">{{ d }}</span> }
|
|
3072
|
+
@if (typeLabel(); as t) { <span class="chat-citation-preview__type">{{ t }}</span> }
|
|
3073
|
+
</div>
|
|
3074
|
+
@if (citation().title; as title) {
|
|
3075
|
+
<p class="chat-citation-preview__title">{{ title }}</p>
|
|
3076
|
+
}
|
|
3077
|
+
@if (citation().snippet; as s) {
|
|
3078
|
+
<p class="chat-citation-preview__snippet">{{ s }}</p>
|
|
3079
|
+
}
|
|
3080
|
+
@if (citation().url; as url) {
|
|
3081
|
+
<div class="chat-citation-preview__foot">
|
|
3082
|
+
<a class="chat-citation-preview__open" [href]="url" target="_blank" rel="noopener noreferrer">
|
|
3083
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" width="12" height="12">
|
|
3084
|
+
<path d="M7 17L17 7M17 7H8M17 7v9" fill="none" stroke="currentColor" stroke-width="2" />
|
|
3085
|
+
</svg>
|
|
3086
|
+
Open source
|
|
3087
|
+
</a>
|
|
3088
|
+
@if (published(); as p) { <span class="chat-citation-preview__meta">{{ p }}</span> }
|
|
3089
|
+
</div>
|
|
3090
|
+
}
|
|
3091
|
+
</div>
|
|
3092
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citation-preview{width:320px;max-width:calc(100vw - 24px);box-sizing:border-box;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);box-shadow:var(--tplane-chat-shadow-md);padding:12px 13px;text-align:left;color:var(--tplane-chat-text)}.chat-citation-preview__head{display:flex;align-items:center;gap:7px;margin-bottom:7px}.chat-citation-preview__fav{width:16px;height:16px;border-radius:4px;flex:0 0 auto;object-fit:cover}.chat-citation-preview__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:700}.chat-citation-preview__domain{font-size:12px;color:var(--tplane-chat-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citation-preview__type{margin-left:auto;font-size:11px;color:var(--tplane-chat-text-muted);flex:0 0 auto}.chat-citation-preview__title{font-size:14px;font-weight:600;line-height:1.35;margin:0 0 5px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__snippet{font-size:12.5px;color:var(--tplane-chat-text-muted);line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:9px;border-top:1px solid var(--tplane-chat-separator)}.chat-citation-preview__open{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--tplane-chat-citation-accent);text-decoration:none}.chat-citation-preview__open:hover{text-decoration:underline}.chat-citation-preview__meta{font-size:11px;color:var(--tplane-chat-text-muted)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3093
|
+
}
|
|
3094
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationPreviewComponent, decorators: [{
|
|
3095
|
+
type: Component,
|
|
3096
|
+
args: [{ selector: 'chat-citation-preview', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3097
|
+
<div class="chat-citation-preview" role="group" [attr.aria-label]="'Source ' + citation().index">
|
|
3098
|
+
<div class="chat-citation-preview__head">
|
|
3099
|
+
@if (citation().iconUrl; as icon) {
|
|
3100
|
+
<img class="chat-citation-preview__fav" [src]="icon" alt="" width="16" height="16" />
|
|
3101
|
+
} @else {
|
|
3102
|
+
<span class="chat-citation-preview__fav chat-citation-preview__fav--mono"
|
|
3103
|
+
[style.background]="monoColor()">{{ monogram() }}</span>
|
|
3104
|
+
}
|
|
3105
|
+
@if (domain(); as d) { <span class="chat-citation-preview__domain">{{ d }}</span> }
|
|
3106
|
+
@if (typeLabel(); as t) { <span class="chat-citation-preview__type">{{ t }}</span> }
|
|
3107
|
+
</div>
|
|
3108
|
+
@if (citation().title; as title) {
|
|
3109
|
+
<p class="chat-citation-preview__title">{{ title }}</p>
|
|
3110
|
+
}
|
|
3111
|
+
@if (citation().snippet; as s) {
|
|
3112
|
+
<p class="chat-citation-preview__snippet">{{ s }}</p>
|
|
3113
|
+
}
|
|
3114
|
+
@if (citation().url; as url) {
|
|
3115
|
+
<div class="chat-citation-preview__foot">
|
|
3116
|
+
<a class="chat-citation-preview__open" [href]="url" target="_blank" rel="noopener noreferrer">
|
|
3117
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" width="12" height="12">
|
|
3118
|
+
<path d="M7 17L17 7M17 7H8M17 7v9" fill="none" stroke="currentColor" stroke-width="2" />
|
|
3119
|
+
</svg>
|
|
3120
|
+
Open source
|
|
3121
|
+
</a>
|
|
3122
|
+
@if (published(); as p) { <span class="chat-citation-preview__meta">{{ p }}</span> }
|
|
3123
|
+
</div>
|
|
3124
|
+
}
|
|
3125
|
+
</div>
|
|
3126
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citation-preview{width:320px;max-width:calc(100vw - 24px);box-sizing:border-box;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);box-shadow:var(--tplane-chat-shadow-md);padding:12px 13px;text-align:left;color:var(--tplane-chat-text)}.chat-citation-preview__head{display:flex;align-items:center;gap:7px;margin-bottom:7px}.chat-citation-preview__fav{width:16px;height:16px;border-radius:4px;flex:0 0 auto;object-fit:cover}.chat-citation-preview__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:700}.chat-citation-preview__domain{font-size:12px;color:var(--tplane-chat-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citation-preview__type{margin-left:auto;font-size:11px;color:var(--tplane-chat-text-muted);flex:0 0 auto}.chat-citation-preview__title{font-size:14px;font-weight:600;line-height:1.35;margin:0 0 5px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__snippet{font-size:12.5px;color:var(--tplane-chat-text-muted);line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:9px;border-top:1px solid var(--tplane-chat-separator)}.chat-citation-preview__open{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--tplane-chat-citation-accent);text-decoration:none}.chat-citation-preview__open:hover{text-decoration:underline}.chat-citation-preview__meta{font-size:11px;color:var(--tplane-chat-text-muted)}\n"] }]
|
|
3127
|
+
}], propDecorators: { citation: [{ type: i0.Input, args: [{ isSignal: true, alias: "citation", required: true }] }] } });
|
|
3128
|
+
|
|
3129
|
+
// libs/chat/src/lib/markdown/views/markdown-citation-reference.component.ts
|
|
3130
|
+
// SPDX-License-Identifier: MIT
|
|
3131
|
+
const OPEN_DELAY_MS = 120;
|
|
3132
|
+
const CLOSE_DELAY_MS = 200;
|
|
3133
|
+
/**
|
|
3134
|
+
* Inline citation marker. Renders a numbered pill and reveals a provenance
|
|
3135
|
+
* preview card (portaled via the connected-overlay primitive) on hover/focus
|
|
3136
|
+
* (desktop) or tap (touch). Click navigates on desktop when a url exists;
|
|
3137
|
+
* on touch it previews instead of navigating.
|
|
3138
|
+
*/
|
|
3139
|
+
class MarkdownCitationReferenceComponent {
|
|
3140
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
3141
|
+
resolver = inject(CitationsResolverService);
|
|
3142
|
+
document = inject(DOCUMENT);
|
|
3143
|
+
resolved = computed(() => this.resolver.lookup(this.node().refId)(), ...(ngDevMode ? [{ debugName: "resolved" }] : []));
|
|
3144
|
+
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
3145
|
+
// Prefer below-start, flip above when it won't fit. The positioner clamps to view.
|
|
3146
|
+
positions = [
|
|
3147
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6 },
|
|
3148
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -6 },
|
|
3149
|
+
];
|
|
3150
|
+
/** Read fresh each time: SSR renders with no window, and hybrid devices change. */
|
|
3151
|
+
get hoverCapable() {
|
|
3152
|
+
return this.document.defaultView?.matchMedia?.('(hover: hover) and (pointer: fine)').matches ?? false;
|
|
3153
|
+
}
|
|
3154
|
+
openTimer = 0;
|
|
3155
|
+
closeTimer = 0;
|
|
3156
|
+
pane = null;
|
|
3157
|
+
justOpenedByFocus = false;
|
|
2910
3158
|
constructor() {
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
}
|
|
3159
|
+
inject(DestroyRef).onDestroy(() => this.clearTimers());
|
|
3160
|
+
}
|
|
3161
|
+
ariaLabel(c) {
|
|
3162
|
+
const domain = deriveDomain(c.url);
|
|
3163
|
+
const parts = [`Source ${c.index}`];
|
|
3164
|
+
if (c.title)
|
|
3165
|
+
parts.push(c.title);
|
|
3166
|
+
if (domain)
|
|
3167
|
+
parts.push(domain);
|
|
3168
|
+
const base = parts.join(', ');
|
|
3169
|
+
return c.url ? `${base}, opens in new tab` : base;
|
|
3170
|
+
}
|
|
3171
|
+
onEnter() {
|
|
3172
|
+
if (!this.hoverCapable)
|
|
3173
|
+
return;
|
|
3174
|
+
this.cancelClose();
|
|
3175
|
+
const win = this.document.defaultView;
|
|
3176
|
+
if (win)
|
|
3177
|
+
this.openTimer = win.setTimeout(() => this.open.set(true), OPEN_DELAY_MS);
|
|
2922
3178
|
}
|
|
2923
|
-
|
|
2924
|
-
|
|
3179
|
+
onLeave() {
|
|
3180
|
+
if (!this.hoverCapable)
|
|
3181
|
+
return;
|
|
3182
|
+
this.cancelOpen();
|
|
3183
|
+
this.scheduleClose();
|
|
2925
3184
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
type="button"
|
|
2930
|
-
class="chat-reasoning__header"
|
|
2931
|
-
[attr.aria-expanded]="expanded()"
|
|
2932
|
-
(click)="toggle()"
|
|
2933
|
-
>
|
|
2934
|
-
<svg class="chat-reasoning__chevron" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
2935
|
-
<path d="M4 2l4 4-4 4"/>
|
|
2936
|
-
</svg>
|
|
2937
|
-
@if (isStreaming()) {
|
|
2938
|
-
<span class="chat-reasoning__pulse" aria-hidden="true"></span>
|
|
2939
|
-
}
|
|
2940
|
-
<span class="chat-reasoning__label">{{ resolvedLabel() }}</span>
|
|
2941
|
-
</button>
|
|
2942
|
-
@if (expanded()) {
|
|
2943
|
-
<div class="chat-reasoning__body">
|
|
2944
|
-
<chat-streaming-md [content]="content()" [streaming]="isStreaming()" />
|
|
2945
|
-
</div>
|
|
3185
|
+
onFocus() {
|
|
3186
|
+
this.open.set(true);
|
|
3187
|
+
this.justOpenedByFocus = true;
|
|
2946
3188
|
}
|
|
2947
|
-
|
|
3189
|
+
onBlur() {
|
|
3190
|
+
this.justOpenedByFocus = false;
|
|
3191
|
+
const active = this.document.activeElement;
|
|
3192
|
+
if (this.pane && active && this.pane.contains(active))
|
|
3193
|
+
return; // focus moved into card
|
|
3194
|
+
this.close();
|
|
3195
|
+
}
|
|
3196
|
+
onClick(e, c) {
|
|
3197
|
+
// Desktop + real url: let the native link navigate.
|
|
3198
|
+
if (this.hoverCapable && c.url)
|
|
3199
|
+
return;
|
|
3200
|
+
// No url, or touch device: preview instead of navigating.
|
|
3201
|
+
e.preventDefault();
|
|
3202
|
+
if (this.justOpenedByFocus) {
|
|
3203
|
+
// focus (fired first in this tap/gesture) already opened it — don't toggle closed
|
|
3204
|
+
this.justOpenedByFocus = false;
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
this.open.update((v) => !v);
|
|
3208
|
+
}
|
|
3209
|
+
onKeydown(e, c) {
|
|
3210
|
+
if (e.key === 'Escape') {
|
|
3211
|
+
this.close();
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
// A no-url marker is a button: Enter/Space reveals the preview (Escape/blur close it).
|
|
3215
|
+
if (!c.url && (e.key === 'Enter' || e.key === ' ')) {
|
|
3216
|
+
e.preventDefault();
|
|
3217
|
+
this.open.set(true);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
onAttached(pane) {
|
|
3221
|
+
this.pane = pane;
|
|
3222
|
+
pane.addEventListener('mouseenter', this.onPaneEnter);
|
|
3223
|
+
pane.addEventListener('mouseleave', this.onPaneLeave);
|
|
3224
|
+
}
|
|
3225
|
+
close() {
|
|
3226
|
+
this.clearTimers();
|
|
3227
|
+
this.justOpenedByFocus = false;
|
|
3228
|
+
this.open.set(false);
|
|
3229
|
+
this.pane = null; // directive removes the pane element on detach
|
|
3230
|
+
}
|
|
3231
|
+
onPaneEnter = () => this.cancelClose();
|
|
3232
|
+
onPaneLeave = () => this.scheduleClose();
|
|
3233
|
+
scheduleClose() {
|
|
3234
|
+
const win = this.document.defaultView;
|
|
3235
|
+
if (win)
|
|
3236
|
+
this.closeTimer = win.setTimeout(() => this.open.set(false), CLOSE_DELAY_MS);
|
|
3237
|
+
}
|
|
3238
|
+
cancelOpen() {
|
|
3239
|
+
const win = this.document.defaultView;
|
|
3240
|
+
if (this.openTimer && win)
|
|
3241
|
+
win.clearTimeout(this.openTimer);
|
|
3242
|
+
this.openTimer = 0;
|
|
3243
|
+
}
|
|
3244
|
+
cancelClose() {
|
|
3245
|
+
const win = this.document.defaultView;
|
|
3246
|
+
if (this.closeTimer && win)
|
|
3247
|
+
win.clearTimeout(this.closeTimer);
|
|
3248
|
+
this.closeTimer = 0;
|
|
3249
|
+
}
|
|
3250
|
+
clearTimers() {
|
|
3251
|
+
this.cancelOpen();
|
|
3252
|
+
this.cancelClose();
|
|
3253
|
+
}
|
|
3254
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownCitationReferenceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3255
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownCitationReferenceComponent, isStandalone: true, selector: "chat-md-citation-reference", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
3256
|
+
@if (resolved(); as r) {
|
|
3257
|
+
<a
|
|
3258
|
+
class="chat-citation-marker"
|
|
3259
|
+
[class.chat-citation-marker--no-url]="!r.citation.url"
|
|
3260
|
+
chatOverlayOrigin
|
|
3261
|
+
#origin="chatOverlayOrigin"
|
|
3262
|
+
[attr.href]="r.citation.url ?? null"
|
|
3263
|
+
[attr.target]="r.citation.url ? '_blank' : null"
|
|
3264
|
+
[attr.rel]="r.citation.url ? 'noopener noreferrer' : null"
|
|
3265
|
+
[attr.role]="r.citation.url ? null : 'button'"
|
|
3266
|
+
[attr.tabindex]="r.citation.url ? null : '0'"
|
|
3267
|
+
aria-haspopup="dialog"
|
|
3268
|
+
[attr.aria-expanded]="open()"
|
|
3269
|
+
[attr.aria-label]="ariaLabel(r.citation)"
|
|
3270
|
+
(mouseenter)="onEnter()"
|
|
3271
|
+
(mouseleave)="onLeave()"
|
|
3272
|
+
(focus)="onFocus()"
|
|
3273
|
+
(blur)="onBlur()"
|
|
3274
|
+
(click)="onClick($event, r.citation)"
|
|
3275
|
+
(keydown)="onKeydown($event, r.citation)"
|
|
3276
|
+
>{{ node().index }}</a>
|
|
3277
|
+
<ng-template
|
|
3278
|
+
chatConnectedOverlay
|
|
3279
|
+
[chatOverlayOrigin]="origin"
|
|
3280
|
+
[chatOverlayOpen]="open()"
|
|
3281
|
+
[chatOverlayPositions]="positions"
|
|
3282
|
+
[chatOverlayPanelClass]="'chat-citation-preview-pane'"
|
|
3283
|
+
(chatOverlayAttached)="onAttached($event)"
|
|
3284
|
+
(chatOverlayOutsideClick)="close()"
|
|
3285
|
+
(chatOverlayDetach)="close()"
|
|
3286
|
+
>
|
|
3287
|
+
<chat-citation-preview [citation]="r.citation" />
|
|
3288
|
+
</ng-template>
|
|
3289
|
+
} @else {
|
|
3290
|
+
<span
|
|
3291
|
+
class="chat-citation-marker chat-citation-marker--unresolved"
|
|
3292
|
+
[attr.title]="'No source available'"
|
|
3293
|
+
[attr.aria-label]="'Citation ' + node().index + ': source unavailable'"
|
|
3294
|
+
>{{ node().index }}</span>
|
|
3295
|
+
}
|
|
3296
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:inline}.chat-citation-marker{display:inline-flex;align-items:center;justify-content:center;min-width:17px;height:17px;padding:0 5px;margin:0 1px;font-size:11px;font-weight:600;line-height:1;color:var(--tplane-chat-citation-marker-fg);background:var(--tplane-chat-citation-marker-bg);border:1px solid var(--tplane-chat-citation-marker-border);border-radius:var(--tplane-chat-citation-radius);translate:0 -1px;text-decoration:none;cursor:pointer;white-space:nowrap;transition:background .12s ease,border-color .12s ease,color .12s ease}.chat-citation-marker:hover,.chat-citation-marker:focus-visible{background:var(--tplane-chat-citation-accent-soft);border-color:var(--tplane-chat-citation-accent-border);color:var(--tplane-chat-citation-accent);outline:none}.chat-citation-marker:focus-visible{box-shadow:0 0 0 2px var(--tplane-chat-citation-accent-border)}.chat-citation-marker--unresolved{color:var(--tplane-chat-text-muted);background:transparent;border-style:dashed;cursor:default}.chat-citation-marker--unresolved:hover{background:transparent;border-color:var(--tplane-chat-citation-marker-border);color:var(--tplane-chat-text-muted)}.chat-citation-marker--no-url{cursor:help}\n"], dependencies: [{ kind: "directive", type: ChatConnectedOverlayDirective, selector: "[chatConnectedOverlay]", inputs: ["chatOverlayOrigin", "chatOverlayOpen", "chatOverlayPositions", "chatOverlayPanelClass"], outputs: ["chatOverlayAttached", "chatOverlayOutsideClick", "chatOverlayDetach"] }, { kind: "directive", type: ChatOverlayOriginDirective, selector: "[chatOverlayOrigin]", exportAs: ["chatOverlayOrigin"] }, { kind: "component", type: ChatCitationPreviewComponent, selector: "chat-citation-preview", inputs: ["citation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2948
3297
|
}
|
|
2949
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
3298
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownCitationReferenceComponent, decorators: [{
|
|
2950
3299
|
type: Component,
|
|
2951
|
-
args: [{ selector: 'chat-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3300
|
+
args: [{ selector: 'chat-md-citation-reference', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChatConnectedOverlayDirective, ChatOverlayOriginDirective, ChatCitationPreviewComponent], template: `
|
|
3301
|
+
@if (resolved(); as r) {
|
|
3302
|
+
<a
|
|
3303
|
+
class="chat-citation-marker"
|
|
3304
|
+
[class.chat-citation-marker--no-url]="!r.citation.url"
|
|
3305
|
+
chatOverlayOrigin
|
|
3306
|
+
#origin="chatOverlayOrigin"
|
|
3307
|
+
[attr.href]="r.citation.url ?? null"
|
|
3308
|
+
[attr.target]="r.citation.url ? '_blank' : null"
|
|
3309
|
+
[attr.rel]="r.citation.url ? 'noopener noreferrer' : null"
|
|
3310
|
+
[attr.role]="r.citation.url ? null : 'button'"
|
|
3311
|
+
[attr.tabindex]="r.citation.url ? null : '0'"
|
|
3312
|
+
aria-haspopup="dialog"
|
|
3313
|
+
[attr.aria-expanded]="open()"
|
|
3314
|
+
[attr.aria-label]="ariaLabel(r.citation)"
|
|
3315
|
+
(mouseenter)="onEnter()"
|
|
3316
|
+
(mouseleave)="onLeave()"
|
|
3317
|
+
(focus)="onFocus()"
|
|
3318
|
+
(blur)="onBlur()"
|
|
3319
|
+
(click)="onClick($event, r.citation)"
|
|
3320
|
+
(keydown)="onKeydown($event, r.citation)"
|
|
3321
|
+
>{{ node().index }}</a>
|
|
3322
|
+
<ng-template
|
|
3323
|
+
chatConnectedOverlay
|
|
3324
|
+
[chatOverlayOrigin]="origin"
|
|
3325
|
+
[chatOverlayOpen]="open()"
|
|
3326
|
+
[chatOverlayPositions]="positions"
|
|
3327
|
+
[chatOverlayPanelClass]="'chat-citation-preview-pane'"
|
|
3328
|
+
(chatOverlayAttached)="onAttached($event)"
|
|
3329
|
+
(chatOverlayOutsideClick)="close()"
|
|
3330
|
+
(chatOverlayDetach)="close()"
|
|
3331
|
+
>
|
|
3332
|
+
<chat-citation-preview [citation]="r.citation" />
|
|
3333
|
+
</ng-template>
|
|
3334
|
+
} @else {
|
|
3335
|
+
<span
|
|
3336
|
+
class="chat-citation-marker chat-citation-marker--unresolved"
|
|
3337
|
+
[attr.title]="'No source available'"
|
|
3338
|
+
[attr.aria-label]="'Citation ' + node().index + ': source unavailable'"
|
|
3339
|
+
>{{ node().index }}</span>
|
|
2974
3340
|
}
|
|
2975
|
-
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:
|
|
2976
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
3341
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:inline}.chat-citation-marker{display:inline-flex;align-items:center;justify-content:center;min-width:17px;height:17px;padding:0 5px;margin:0 1px;font-size:11px;font-weight:600;line-height:1;color:var(--tplane-chat-citation-marker-fg);background:var(--tplane-chat-citation-marker-bg);border:1px solid var(--tplane-chat-citation-marker-border);border-radius:var(--tplane-chat-citation-radius);translate:0 -1px;text-decoration:none;cursor:pointer;white-space:nowrap;transition:background .12s ease,border-color .12s ease,color .12s ease}.chat-citation-marker:hover,.chat-citation-marker:focus-visible{background:var(--tplane-chat-citation-accent-soft);border-color:var(--tplane-chat-citation-accent-border);color:var(--tplane-chat-citation-accent);outline:none}.chat-citation-marker:focus-visible{box-shadow:0 0 0 2px var(--tplane-chat-citation-accent-border)}.chat-citation-marker--unresolved{color:var(--tplane-chat-text-muted);background:transparent;border-style:dashed;cursor:default}.chat-citation-marker--unresolved:hover{background:transparent;border-color:var(--tplane-chat-citation-marker-border);color:var(--tplane-chat-text-muted)}.chat-citation-marker--no-url{cursor:help}\n"] }]
|
|
3342
|
+
}], ctorParameters: () => [], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2977
3343
|
|
|
2978
|
-
// libs/chat/src/lib/
|
|
3344
|
+
// libs/chat/src/lib/markdown/views/markdown-table.component.ts
|
|
2979
3345
|
// SPDX-License-Identifier: MIT
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
</
|
|
3015
|
-
</
|
|
3016
|
-
`, isInline: true,
|
|
3346
|
+
class MarkdownTableComponent {
|
|
3347
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
3348
|
+
headerRow = computed(() => {
|
|
3349
|
+
const rows = this.node().children;
|
|
3350
|
+
return rows.length > 0 && rows[0].isHeader ? rows[0] : null;
|
|
3351
|
+
}, ...(ngDevMode ? [{ debugName: "headerRow" }] : []));
|
|
3352
|
+
bodyRows = computed(() => {
|
|
3353
|
+
const rows = this.node().children;
|
|
3354
|
+
return rows[0]?.isHeader ? rows.slice(1) : rows;
|
|
3355
|
+
}, ...(ngDevMode ? [{ debugName: "bodyRows" }] : []));
|
|
3356
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3357
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownTableComponent, isStandalone: true, selector: "chat-md-table", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
3358
|
+
<table class="chat-md-table">
|
|
3359
|
+
<thead>
|
|
3360
|
+
@if (headerRow(); as row) {
|
|
3361
|
+
<tr class="chat-md-table-row chat-md-table-row--header">
|
|
3362
|
+
@for (cell of row.children; track $index) {
|
|
3363
|
+
<th class="chat-md-table-cell" [style.text-align]="cell.alignment ?? null">
|
|
3364
|
+
<chat-md-children [parent]="cell" />
|
|
3365
|
+
</th>
|
|
3366
|
+
}
|
|
3367
|
+
</tr>
|
|
3368
|
+
}
|
|
3369
|
+
</thead>
|
|
3370
|
+
<tbody>
|
|
3371
|
+
@for (row of bodyRows(); track $index) {
|
|
3372
|
+
<tr class="chat-md-table-row">
|
|
3373
|
+
@for (cell of row.children; track $index) {
|
|
3374
|
+
<td class="chat-md-table-cell" [style.text-align]="cell.alignment ?? null">
|
|
3375
|
+
<chat-md-children [parent]="cell" />
|
|
3376
|
+
</td>
|
|
3377
|
+
}
|
|
3378
|
+
</tr>
|
|
3379
|
+
}
|
|
3380
|
+
</tbody>
|
|
3381
|
+
</table>
|
|
3382
|
+
`, isInline: true, dependencies: [{ kind: "component", type: MarkdownChildrenComponent, selector: "chat-md-children", inputs: ["parent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3017
3383
|
}
|
|
3018
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
3384
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableComponent, decorators: [{
|
|
3019
3385
|
type: Component,
|
|
3020
|
-
args: [{
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3386
|
+
args: [{
|
|
3387
|
+
selector: 'chat-md-table',
|
|
3388
|
+
standalone: true,
|
|
3389
|
+
imports: [MarkdownChildrenComponent],
|
|
3390
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3391
|
+
template: `
|
|
3392
|
+
<table class="chat-md-table">
|
|
3393
|
+
<thead>
|
|
3394
|
+
@if (headerRow(); as row) {
|
|
3395
|
+
<tr class="chat-md-table-row chat-md-table-row--header">
|
|
3396
|
+
@for (cell of row.children; track $index) {
|
|
3397
|
+
<th class="chat-md-table-cell" [style.text-align]="cell.alignment ?? null">
|
|
3398
|
+
<chat-md-children [parent]="cell" />
|
|
3399
|
+
</th>
|
|
3400
|
+
}
|
|
3401
|
+
</tr>
|
|
3402
|
+
}
|
|
3403
|
+
</thead>
|
|
3404
|
+
<tbody>
|
|
3405
|
+
@for (row of bodyRows(); track $index) {
|
|
3406
|
+
<tr class="chat-md-table-row">
|
|
3407
|
+
@for (cell of row.children; track $index) {
|
|
3408
|
+
<td class="chat-md-table-cell" [style.text-align]="cell.alignment ?? null">
|
|
3409
|
+
<chat-md-children [parent]="cell" />
|
|
3410
|
+
</td>
|
|
3411
|
+
}
|
|
3412
|
+
</tr>
|
|
3413
|
+
}
|
|
3414
|
+
</tbody>
|
|
3415
|
+
</table>
|
|
3416
|
+
`,
|
|
3417
|
+
}]
|
|
3418
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
3028
3419
|
|
|
3420
|
+
// libs/chat/src/lib/markdown/markdown-table-row.token.ts
|
|
3029
3421
|
// SPDX-License-Identifier: MIT
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
color: var(--tplane-chat-text);
|
|
3040
|
-
cursor: pointer;
|
|
3041
|
-
transition: transform 200ms ease;
|
|
3042
|
-
}
|
|
3043
|
-
.chat-suggestion:hover { transform: scale(1.03); }
|
|
3044
|
-
.chat-suggestion:disabled { cursor: wait; opacity: 0.6; }
|
|
3045
|
-
`;
|
|
3422
|
+
/**
|
|
3423
|
+
* Provided by MarkdownTableRowComponent for header rows so that
|
|
3424
|
+
* MarkdownTableCellComponent can render <th> instead of <td>.
|
|
3425
|
+
* The value is a Signal<boolean> so that it tracks the row's isHeader reactively.
|
|
3426
|
+
*/
|
|
3427
|
+
const IS_HEADER_ROW = new InjectionToken('IS_HEADER_ROW', {
|
|
3428
|
+
providedIn: null,
|
|
3429
|
+
factory: () => signal(false),
|
|
3430
|
+
});
|
|
3046
3431
|
|
|
3432
|
+
// libs/chat/src/lib/markdown/views/markdown-table-row.component.ts
|
|
3047
3433
|
// SPDX-License-Identifier: MIT
|
|
3048
|
-
class
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3434
|
+
class MarkdownTableRowComponent {
|
|
3435
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
3436
|
+
registry = inject(MARKDOWN_VIEW_REGISTRY);
|
|
3437
|
+
resolve(child) {
|
|
3438
|
+
const entry = this.registry[child.type];
|
|
3439
|
+
if (!entry)
|
|
3440
|
+
return null;
|
|
3441
|
+
return typeof entry === 'function' ? entry : entry.component;
|
|
3442
|
+
}
|
|
3443
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3444
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownTableRowComponent, isStandalone: true, selector: "chat-md-table-row", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
3445
|
+
{
|
|
3446
|
+
provide: IS_HEADER_ROW,
|
|
3447
|
+
useFactory: () => {
|
|
3448
|
+
const comp = inject(MarkdownTableRowComponent);
|
|
3449
|
+
return computed(() => comp.node().isHeader);
|
|
3450
|
+
},
|
|
3451
|
+
},
|
|
3452
|
+
], ngImport: i0, template: `
|
|
3453
|
+
<tr class="chat-md-table-row" [class.chat-md-table-row--header]="node().isHeader">
|
|
3454
|
+
@for (child of node().children; track $index) {
|
|
3455
|
+
@let comp = resolve(child);
|
|
3456
|
+
@if (comp) {
|
|
3457
|
+
<ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
|
|
3458
|
+
}
|
|
3056
3459
|
}
|
|
3057
|
-
</
|
|
3058
|
-
`, isInline: true,
|
|
3460
|
+
</tr>
|
|
3461
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3059
3462
|
}
|
|
3060
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
3463
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableRowComponent, decorators: [{
|
|
3061
3464
|
type: Component,
|
|
3062
|
-
args: [{
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3465
|
+
args: [{
|
|
3466
|
+
selector: 'chat-md-table-row',
|
|
3467
|
+
standalone: true,
|
|
3468
|
+
imports: [NgComponentOutlet],
|
|
3469
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3470
|
+
template: `
|
|
3471
|
+
<tr class="chat-md-table-row" [class.chat-md-table-row--header]="node().isHeader">
|
|
3472
|
+
@for (child of node().children; track $index) {
|
|
3473
|
+
@let comp = resolve(child);
|
|
3474
|
+
@if (comp) {
|
|
3475
|
+
<ng-container *ngComponentOutlet="comp; inputs: { node: child }" />
|
|
3476
|
+
}
|
|
3066
3477
|
}
|
|
3067
|
-
</
|
|
3068
|
-
`,
|
|
3069
|
-
|
|
3478
|
+
</tr>
|
|
3479
|
+
`,
|
|
3480
|
+
providers: [
|
|
3481
|
+
{
|
|
3482
|
+
provide: IS_HEADER_ROW,
|
|
3483
|
+
useFactory: () => {
|
|
3484
|
+
const comp = inject(MarkdownTableRowComponent);
|
|
3485
|
+
return computed(() => comp.node().isHeader);
|
|
3486
|
+
},
|
|
3487
|
+
},
|
|
3488
|
+
],
|
|
3489
|
+
}]
|
|
3490
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
3070
3491
|
|
|
3071
|
-
// libs/chat/src/lib/
|
|
3492
|
+
// libs/chat/src/lib/markdown/views/markdown-table-cell.component.ts
|
|
3072
3493
|
// SPDX-License-Identifier: MIT
|
|
3073
|
-
|
|
3074
|
-
:
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3494
|
+
class MarkdownTableCellComponent {
|
|
3495
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
3496
|
+
isHeaderRowToken = inject(IS_HEADER_ROW, { optional: true });
|
|
3497
|
+
isHeader = computed(() => this.isHeaderRowToken ? this.isHeaderRowToken() : false, ...(ngDevMode ? [{ debugName: "isHeader" }] : []));
|
|
3498
|
+
alignment = computed(() => this.node().alignment, ...(ngDevMode ? [{ debugName: "alignment" }] : []));
|
|
3499
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3500
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownTableCellComponent, isStandalone: true, selector: "chat-md-table-cell", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
3501
|
+
@if (isHeader()) {
|
|
3502
|
+
<th class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
3503
|
+
<chat-md-children [parent]="node()" />
|
|
3504
|
+
</th>
|
|
3505
|
+
} @else {
|
|
3506
|
+
<td class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
3507
|
+
<chat-md-children [parent]="node()" />
|
|
3508
|
+
</td>
|
|
3509
|
+
}
|
|
3510
|
+
`, isInline: true, dependencies: [{ kind: "component", type: MarkdownChildrenComponent, selector: "chat-md-children", inputs: ["parent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3079
3511
|
}
|
|
3512
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownTableCellComponent, decorators: [{
|
|
3513
|
+
type: Component,
|
|
3514
|
+
args: [{
|
|
3515
|
+
selector: 'chat-md-table-cell',
|
|
3516
|
+
standalone: true,
|
|
3517
|
+
imports: [MarkdownChildrenComponent],
|
|
3518
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3519
|
+
template: `
|
|
3520
|
+
@if (isHeader()) {
|
|
3521
|
+
<th class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
3522
|
+
<chat-md-children [parent]="node()" />
|
|
3523
|
+
</th>
|
|
3524
|
+
} @else {
|
|
3525
|
+
<td class="chat-md-table-cell" [style.text-align]="alignment() ?? null">
|
|
3526
|
+
<chat-md-children [parent]="node()" />
|
|
3527
|
+
</td>
|
|
3528
|
+
}
|
|
3529
|
+
`,
|
|
3530
|
+
}]
|
|
3531
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
3080
3532
|
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3533
|
+
// libs/chat/src/lib/markdown/views/markdown-html.component.ts
|
|
3534
|
+
// SPDX-License-Identifier: MIT
|
|
3535
|
+
/**
|
|
3536
|
+
* Renders a `html-block` / `html-inline` markdown node as **escaped text** —
|
|
3537
|
+
* the raw HTML is shown literally (Angular interpolation auto-escapes it),
|
|
3538
|
+
* never injected as live markup. This preserves the pre-0.4 behavior where
|
|
3539
|
+
* raw HTML was plain text, and keeps the chat XSS-safe: model-emitted
|
|
3540
|
+
* `<script>`, `<iframe>`, etc. are displayed as text and never executed.
|
|
3541
|
+
*/
|
|
3542
|
+
class MarkdownHtmlComponent {
|
|
3543
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
3544
|
+
raw = computed(() => this.node().raw, ...(ngDevMode ? [{ debugName: "raw" }] : []));
|
|
3545
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownHtmlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3546
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: MarkdownHtmlComponent, isStandalone: true, selector: "chat-md-html", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `{{ raw() }}`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3085
3547
|
}
|
|
3548
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownHtmlComponent, decorators: [{
|
|
3549
|
+
type: Component,
|
|
3550
|
+
args: [{
|
|
3551
|
+
selector: 'chat-md-html',
|
|
3552
|
+
standalone: true,
|
|
3553
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3554
|
+
template: `{{ raw() }}`,
|
|
3555
|
+
}]
|
|
3556
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
3086
3557
|
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
width: 36px;
|
|
3127
|
-
height: 36px;
|
|
3128
|
-
border-radius: 50%;
|
|
3129
|
-
border: 0;
|
|
3130
|
-
display: flex;
|
|
3131
|
-
align-items: center;
|
|
3132
|
-
justify-content: center;
|
|
3133
|
-
cursor: pointer;
|
|
3134
|
-
transition: opacity 150ms ease, transform 150ms ease, background 150ms ease;
|
|
3135
|
-
padding: 0;
|
|
3136
|
-
}
|
|
3137
|
-
.chat-input__send {
|
|
3138
|
-
background: var(--tplane-chat-text);
|
|
3139
|
-
color: var(--tplane-chat-bg);
|
|
3140
|
-
}
|
|
3141
|
-
.chat-input__send:disabled {
|
|
3142
|
-
opacity: 0.35;
|
|
3143
|
-
cursor: not-allowed;
|
|
3144
|
-
background: var(--tplane-chat-text-muted);
|
|
3145
|
-
}
|
|
3146
|
-
.chat-input__send:not(:disabled):hover { transform: scale(1.05); }
|
|
3147
|
-
.chat-input__send svg { width: 16px; height: 16px; }
|
|
3148
|
-
|
|
3149
|
-
.chat-input__send--stop {
|
|
3150
|
-
background: var(--tplane-chat-text-muted);
|
|
3151
|
-
color: var(--tplane-chat-bg);
|
|
3152
|
-
}
|
|
3153
|
-
.chat-input__send--stop:hover { transform: scale(1.05); }
|
|
3154
|
-
.chat-input__send--stop svg { width: 14px; height: 14px; }
|
|
3155
|
-
`;
|
|
3558
|
+
// libs/chat/src/lib/markdown/cacheplane-markdown-views.ts
|
|
3559
|
+
// SPDX-License-Identifier: MIT
|
|
3560
|
+
/**
|
|
3561
|
+
* Default view registry consumed by <chat-streaming-md>. Maps every
|
|
3562
|
+
* MarkdownNode.type emitted by @cacheplane/partial-markdown@0.2 to its
|
|
3563
|
+
* corresponding Angular component.
|
|
3564
|
+
*
|
|
3565
|
+
* Override per-node-type via `withViews(cacheplaneMarkdownViews, { … })`.
|
|
3566
|
+
*/
|
|
3567
|
+
const cacheplaneMarkdownViews = views({
|
|
3568
|
+
'document': MarkdownDocumentComponent,
|
|
3569
|
+
'paragraph': MarkdownParagraphComponent,
|
|
3570
|
+
'heading': MarkdownHeadingComponent,
|
|
3571
|
+
'blockquote': MarkdownBlockquoteComponent,
|
|
3572
|
+
'list': MarkdownListComponent,
|
|
3573
|
+
'list-item': MarkdownListItemComponent,
|
|
3574
|
+
'code-block': MarkdownCodeBlockComponent,
|
|
3575
|
+
'thematic-break': MarkdownThematicBreakComponent,
|
|
3576
|
+
'text': MarkdownTextComponent,
|
|
3577
|
+
'emphasis': MarkdownEmphasisComponent,
|
|
3578
|
+
'strong': MarkdownStrongComponent,
|
|
3579
|
+
'strikethrough': MarkdownStrikethroughComponent,
|
|
3580
|
+
'inline-code': MarkdownInlineCodeComponent,
|
|
3581
|
+
'math-inline': MarkdownMathComponent,
|
|
3582
|
+
'math-display': MarkdownMathComponent,
|
|
3583
|
+
'link': MarkdownLinkComponent,
|
|
3584
|
+
'autolink': MarkdownAutolinkComponent,
|
|
3585
|
+
'image': MarkdownImageComponent,
|
|
3586
|
+
'soft-break': MarkdownSoftBreakComponent,
|
|
3587
|
+
'hard-break': MarkdownHardBreakComponent,
|
|
3588
|
+
'citation-reference': MarkdownCitationReferenceComponent,
|
|
3589
|
+
'table': MarkdownTableComponent,
|
|
3590
|
+
'table-row': MarkdownTableRowComponent,
|
|
3591
|
+
'table-cell': MarkdownTableCellComponent,
|
|
3592
|
+
// Raw HTML (added by partial-markdown 0.4.x) renders as escaped literal text
|
|
3593
|
+
// — XSS-safe, and matches the pre-0.4 behavior where HTML was plain text.
|
|
3594
|
+
'html-block': MarkdownHtmlComponent,
|
|
3595
|
+
'html-inline': MarkdownHtmlComponent,
|
|
3596
|
+
});
|
|
3156
3597
|
|
|
3157
|
-
// libs/chat/src/lib/
|
|
3598
|
+
// libs/chat/src/lib/streaming/streaming-markdown.component.ts
|
|
3158
3599
|
// SPDX-License-Identifier: MIT
|
|
3600
|
+
// How long streaming must be false AND content stable before we finalize the
|
|
3601
|
+
// parser. finish() is only needed to mark final node status (not used visually)
|
|
3602
|
+
// and to revert a genuinely-truncated trailing construct to CommonMark — the
|
|
3603
|
+
// live `parser.root` projection already renders everything during streaming, so
|
|
3604
|
+
// this delay has NO visual cost. It must comfortably exceed real inter-chunk
|
|
3605
|
+
// gaps (e.g. the pause between a table's header row and its delimiter row) and
|
|
3606
|
+
// any `streaming` flag flap, so finalize never fires mid-stream and reverts an
|
|
3607
|
+
// in-progress table to raw "| a | b |" text.
|
|
3608
|
+
const FINALIZE_DEBOUNCE_MS = 600;
|
|
3159
3609
|
/**
|
|
3160
|
-
*
|
|
3161
|
-
*
|
|
3610
|
+
* Renders streaming markdown by walking a @cacheplane/partial-markdown AST
|
|
3611
|
+
* through @threadplane/render's view registry.
|
|
3612
|
+
*
|
|
3613
|
+
* Reactivity model: the live `parser.root` keeps a stable JS reference
|
|
3614
|
+
* across pushes (partial-markdown's identity guarantee). To make Angular
|
|
3615
|
+
* signals propagate downstream when the underlying tree changes, we surface
|
|
3616
|
+
* a materialized snapshot via `materialize()`. The snapshot shares
|
|
3617
|
+
* structurally — unchanged subtrees keep the SAME reference, and any
|
|
3618
|
+
* descendant change yields a NEW root reference. This lets Angular's
|
|
3619
|
+
* `Object.is` equality check both detect changes (root reference differs)
|
|
3620
|
+
* and short-circuit unchanged subtrees (per-node references stable).
|
|
3621
|
+
*
|
|
3622
|
+
* Override per-node-type renderers via the `[viewRegistry]` input or by
|
|
3623
|
+
* supplying a different `MARKDOWN_VIEW_REGISTRY` provider in the injector
|
|
3624
|
+
* tree.
|
|
3162
3625
|
*/
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
}
|
|
3170
|
-
class ChatInputComponent {
|
|
3171
|
-
agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
3172
|
-
submitOnEnter = input(true, ...(ngDevMode ? [{ debugName: "submitOnEnter" }] : []));
|
|
3173
|
-
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
3174
|
-
/** When true (default), shows a stop button while the agent is streaming. */
|
|
3175
|
-
showStopButton = input(true, ...(ngDevMode ? [{ debugName: "showStopButton" }] : []));
|
|
3176
|
-
submitted = output();
|
|
3177
|
-
stopped = output();
|
|
3178
|
-
messageText = signal('', ...(ngDevMode ? [{ debugName: "messageText" }] : []));
|
|
3179
|
-
isLoading = computed(() => this.agent().isLoading(), ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
3180
|
-
/** True while an IME composition (CJK input, accent, autocorrect) is active. */
|
|
3181
|
-
composing = signal(false, ...(ngDevMode ? [{ debugName: "composing" }] : []));
|
|
3182
|
-
focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
|
|
3183
|
-
/** Submit is allowed only when not loading and there's non-whitespace text. */
|
|
3184
|
-
canSubmit = computed(() => {
|
|
3185
|
-
if (this.isLoading())
|
|
3186
|
-
return false;
|
|
3187
|
-
return this.messageText().trim().length > 0;
|
|
3188
|
-
}, ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
|
|
3189
|
-
/** The stop button only appears when the consumer opted in AND we're loading. */
|
|
3190
|
-
canStop = computed(() => this.showStopButton(), ...(ngDevMode ? [{ debugName: "canStop" }] : []));
|
|
3191
|
-
textareaEl = viewChild('textareaEl', ...(ngDevMode ? [{ debugName: "textareaEl" }] : []));
|
|
3192
|
-
/**
|
|
3193
|
-
* Auto-resize the textarea to fit its content as the user types or pastes
|
|
3194
|
-
* multi-line text. Caps at min(40vh, 320px); beyond that the textarea
|
|
3195
|
-
* scrolls. Without this, multi-line input is hidden behind the rows="1"
|
|
3196
|
-
* fixed height (caught by live browser smoke).
|
|
3197
|
-
*/
|
|
3626
|
+
class ChatStreamingMdComponent {
|
|
3627
|
+
content = input('', ...(ngDevMode ? [{ debugName: "content" }] : []));
|
|
3628
|
+
streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
|
|
3629
|
+
viewRegistry = input(undefined, ...(ngDevMode ? [{ debugName: "viewRegistry" }] : []));
|
|
3630
|
+
resolvedRegistry = computed(() => this.viewRegistry() ?? cacheplaneMarkdownViews, ...(ngDevMode ? [{ debugName: "resolvedRegistry" }] : []));
|
|
3631
|
+
resolver = inject(CitationsResolverService, { optional: true });
|
|
3198
3632
|
constructor() {
|
|
3199
3633
|
effect(() => {
|
|
3200
|
-
const
|
|
3201
|
-
|
|
3202
|
-
|
|
3634
|
+
const r = this.root();
|
|
3635
|
+
if (this.resolver && r) {
|
|
3636
|
+
this.resolver.markdownDefs.set(r.citations ?? new Map());
|
|
3637
|
+
}
|
|
3638
|
+
});
|
|
3639
|
+
// Debounced finalization. `finish()` is terminal and DESTRUCTIVE: it reverts
|
|
3640
|
+
// an incomplete trailing construct to its CommonMark fallback — e.g. a table
|
|
3641
|
+
// header before its delimiter row becomes raw "| a | b |" paragraph text. We
|
|
3642
|
+
// must therefore never finalize while tokens are still arriving. The
|
|
3643
|
+
// `streaming` input is not a reliable "still arriving" signal — it can flap
|
|
3644
|
+
// false mid-stream, and at cold start it can read false for an entire live
|
|
3645
|
+
// stream — so we finalize only once streaming is false AND no new content
|
|
3646
|
+
// has arrived for a short, imperceptible window. Any new content or a
|
|
3647
|
+
// streaming=true flap re-arms the timer, so finalize fires exactly once,
|
|
3648
|
+
// after the stream truly stops. Until then the live `parser.root` projection
|
|
3649
|
+
// (0.5.x) renders the in-progress content, including streaming tables.
|
|
3650
|
+
let timer = null;
|
|
3651
|
+
effect((onCleanup) => {
|
|
3652
|
+
const isStreaming = this.streaming();
|
|
3653
|
+
this.content(); // re-arm whenever new content arrives
|
|
3654
|
+
if (timer) {
|
|
3655
|
+
clearTimeout(timer);
|
|
3656
|
+
timer = null;
|
|
3657
|
+
}
|
|
3658
|
+
if (isStreaming || this.finished)
|
|
3203
3659
|
return;
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3660
|
+
timer = setTimeout(() => {
|
|
3661
|
+
timer = null;
|
|
3662
|
+
if (this.streaming() || this.finished)
|
|
3663
|
+
return;
|
|
3664
|
+
if (!this.prior.endsWith('\n'))
|
|
3665
|
+
this.parser.push('\n');
|
|
3666
|
+
this.parser.finish();
|
|
3667
|
+
this.finished = true;
|
|
3668
|
+
this.finalizeTick.update((v) => v + 1);
|
|
3669
|
+
}, FINALIZE_DEBOUNCE_MS);
|
|
3670
|
+
onCleanup(() => {
|
|
3671
|
+
if (timer) {
|
|
3672
|
+
clearTimeout(timer);
|
|
3673
|
+
timer = null;
|
|
3674
|
+
}
|
|
3675
|
+
});
|
|
3213
3676
|
});
|
|
3214
3677
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
//
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3678
|
+
// Parser instance is rebuilt only when content diverges from the prior
|
|
3679
|
+
// prefix (rare). For the common streaming case where content extends the
|
|
3680
|
+
// prior content, we push the delta and reuse the existing parser tree.
|
|
3681
|
+
parser = createPartialMarkdownParser();
|
|
3682
|
+
prior = '';
|
|
3683
|
+
finished = false;
|
|
3684
|
+
// Bumped by the debounced finalizer so the `root` computed re-materializes
|
|
3685
|
+
// the now-finished parser tree.
|
|
3686
|
+
finalizeTick = signal(0, ...(ngDevMode ? [{ debugName: "finalizeTick" }] : []));
|
|
3687
|
+
root = computed(() => {
|
|
3688
|
+
const c = this.content();
|
|
3689
|
+
this.finalizeTick(); // re-materialize after a debounced finalize
|
|
3690
|
+
if (c !== this.prior) {
|
|
3691
|
+
// Re-parse from scratch when the content diverged from the prior prefix,
|
|
3692
|
+
// OR when the parser was already finalized — finish() is terminal, so
|
|
3693
|
+
// pushing further deltas into a finished parser corrupts its state. A
|
|
3694
|
+
// transient `streaming=false` mid-stream that finalized early thus
|
|
3695
|
+
// recovers here: new content rebuilds an open, projecting parser.
|
|
3696
|
+
if (c.startsWith(this.prior) && !this.finished) {
|
|
3697
|
+
this.parser.push(c.slice(this.prior.length));
|
|
3698
|
+
}
|
|
3699
|
+
else {
|
|
3700
|
+
this.parser = createPartialMarkdownParser();
|
|
3701
|
+
this.finished = false;
|
|
3702
|
+
if (c.length > 0)
|
|
3703
|
+
this.parser.push(c);
|
|
3704
|
+
}
|
|
3705
|
+
this.prior = c;
|
|
3706
|
+
}
|
|
3707
|
+
// Materialize for Angular reactivity: produces a NEW root reference when
|
|
3708
|
+
// any descendant subtree changed; same reference when nothing changed
|
|
3709
|
+
// (structural sharing). This is what makes signal-based CD propagate
|
|
3710
|
+
// downstream changes despite the parser preserving identity.
|
|
3711
|
+
return materialize(this.parser.root);
|
|
3712
|
+
}, ...(ngDevMode ? [{ debugName: "root" }] : []));
|
|
3713
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatStreamingMdComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3714
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatStreamingMdComponent, isStandalone: true, selector: "chat-streaming-md", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, viewRegistry: { classPropertyName: "viewRegistry", publicName: "viewRegistry", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
3715
|
+
{
|
|
3716
|
+
provide: MARKDOWN_VIEW_REGISTRY,
|
|
3717
|
+
useFactory: (host) => host.resolvedRegistry(),
|
|
3718
|
+
deps: [ChatStreamingMdComponent],
|
|
3719
|
+
},
|
|
3720
|
+
], ngImport: i0, template: `
|
|
3721
|
+
@if (root(); as r) {
|
|
3722
|
+
<chat-md-children [parent]="r" />
|
|
3255
3723
|
}
|
|
3256
|
-
|
|
3257
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatInputComponent, isStandalone: true, selector: "chat-input", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, submitOnEnter: { classPropertyName: "submitOnEnter", publicName: "submitOnEnter", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showStopButton: { classPropertyName: "showStopButton", publicName: "showStopButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { submitted: "submitted", stopped: "stopped" }, viewQueries: [{ propertyName: "textareaEl", first: true, predicate: ["textareaEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
3258
|
-
<div class="chat-input__container">
|
|
3259
|
-
<ng-content select="[chatInputBanner]" />
|
|
3260
|
-
<ng-content select="[chatInputAttachments]" />
|
|
3261
|
-
<div class="chat-input__pill">
|
|
3262
|
-
<ng-content select="[chatInputLeading]" />
|
|
3263
|
-
<textarea
|
|
3264
|
-
#textareaEl
|
|
3265
|
-
class="chat-input__textarea"
|
|
3266
|
-
[value]="messageText()"
|
|
3267
|
-
(input)="onInput($event)"
|
|
3268
|
-
[placeholder]="placeholder()"
|
|
3269
|
-
(keydown.enter)="onKeydown($any($event))"
|
|
3270
|
-
(compositionstart)="composing.set(true)"
|
|
3271
|
-
(compositionend)="composing.set(false)"
|
|
3272
|
-
(focus)="focused.set(true)"
|
|
3273
|
-
(blur)="focused.set(false)"
|
|
3274
|
-
name="messageText"
|
|
3275
|
-
rows="1"
|
|
3276
|
-
aria-label="Type a message"
|
|
3277
|
-
></textarea>
|
|
3278
|
-
<div class="chat-input__controls">
|
|
3279
|
-
<ng-content select="[chatInputModelSelect]" />
|
|
3280
|
-
<ng-content select="[chatInputTrailing]" />
|
|
3281
|
-
@if (isLoading() && canStop()) {
|
|
3282
|
-
<button
|
|
3283
|
-
type="button"
|
|
3284
|
-
class="chat-input__send chat-input__send--stop"
|
|
3285
|
-
(click)="onStop()"
|
|
3286
|
-
aria-label="Stop generating"
|
|
3287
|
-
title="Stop generating"
|
|
3288
|
-
>
|
|
3289
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
3290
|
-
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
3291
|
-
</svg>
|
|
3292
|
-
</button>
|
|
3293
|
-
} @else {
|
|
3294
|
-
<button
|
|
3295
|
-
type="button"
|
|
3296
|
-
class="chat-input__send"
|
|
3297
|
-
[disabled]="!canSubmit()"
|
|
3298
|
-
(click)="onSubmit()"
|
|
3299
|
-
aria-label="Send message"
|
|
3300
|
-
>
|
|
3301
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3302
|
-
<line x1="12" y1="19" x2="12" y2="5"/>
|
|
3303
|
-
<polyline points="5 12 12 5 19 12"/>
|
|
3304
|
-
</svg>
|
|
3305
|
-
</button>
|
|
3306
|
-
}
|
|
3307
|
-
</div>
|
|
3308
|
-
</div>
|
|
3309
|
-
<ng-content select="[chatInputFooter]" />
|
|
3310
|
-
</div>
|
|
3311
|
-
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;width:100%;padding:0 var(--tplane-chat-edge-pad);box-sizing:border-box}.chat-input__container{width:100%;max-width:var(--tplane-chat-max-width);margin:0 auto}.chat-input__pill{display:flex;align-items:center;gap:8px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:9999px;padding:8px 8px 8px 16px;min-height:56px;box-sizing:border-box}.chat-input__textarea{flex:1 1 auto;border:0;outline:none;resize:none;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:1rem;line-height:1.5;padding:0;field-sizing:content;overflow-y:auto}.chat-input__textarea::placeholder{color:var(--tplane-chat-text-muted)}.chat-input__textarea::-webkit-scrollbar{width:4px}.chat-input__textarea::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:4px}.chat-input__controls{display:flex;align-items:center;gap:4px;flex:none}.chat-input__send,.chat-input__send--stop{width:36px;height:36px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:opacity .15s ease,transform .15s ease,background .15s ease;padding:0}.chat-input__send{background:var(--tplane-chat-text);color:var(--tplane-chat-bg)}.chat-input__send:disabled{opacity:.35;cursor:not-allowed;background:var(--tplane-chat-text-muted)}.chat-input__send:not(:disabled):hover{transform:scale(1.05)}.chat-input__send svg{width:16px;height:16px}.chat-input__send--stop{background:var(--tplane-chat-text-muted);color:var(--tplane-chat-bg)}.chat-input__send--stop:hover{transform:scale(1.05)}.chat-input__send--stop svg{width:14px;height:14px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3724
|
+
`, isInline: true, styles: ["chat-streaming-md{display:block;color:var(--tplane-chat-text);line-height:var(--tplane-chat-line-height)}chat-streaming-md h1,chat-streaming-md h2,chat-streaming-md h3,chat-streaming-md h4,chat-streaming-md h5,chat-streaming-md h6{font-weight:600;line-height:1.25;margin:1.25rem 0 .75rem}chat-streaming-md h1:first-child,chat-streaming-md h2:first-child,chat-streaming-md h3:first-child,chat-streaming-md h4:first-child,chat-streaming-md h5:first-child,chat-streaming-md h6:first-child{margin-top:0}chat-streaming-md h1{font-size:1.5em;font-weight:700}chat-streaming-md h2{font-size:1.25em}chat-streaming-md h3{font-size:1.1em}chat-streaming-md h4{font-size:1em}chat-streaming-md h5,chat-streaming-md h6{font-size:.95em;color:var(--tplane-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--tplane-chat-font-size)}chat-streaming-md p:last-child{margin-bottom:0}chat-streaming-md strong,chat-streaming-md b{font-weight:700}chat-streaming-md em,chat-streaming-md i{font-style:italic}chat-streaming-md del,chat-streaming-md s{text-decoration:line-through;color:var(--tplane-chat-text-muted)}chat-streaming-md mark{background:var(--tplane-chat-surface-alt);padding:0 2px;border-radius:2px}chat-streaming-md sub{font-size:.75em;vertical-align:sub}chat-streaming-md sup{font-size:.75em;vertical-align:super}chat-streaming-md a{color:var(--tplane-chat-primary);text-decoration:underline;text-underline-offset:2px}chat-streaming-md a:hover{text-decoration-thickness:2px}chat-streaming-md ul,chat-streaming-md ol{margin:0 0 .75rem;padding-left:1.5rem}chat-streaming-md ul{list-style:disc outside}chat-streaming-md ol{list-style:decimal outside}chat-streaming-md ul ul{list-style:circle outside}chat-streaming-md ul ul ul{list-style:square outside}chat-streaming-md li{margin:.2rem 0}chat-streaming-md li::marker{color:var(--tplane-chat-text-muted)}chat-streaming-md li>p{margin:0 0 .25rem}chat-streaming-md li>ul,chat-streaming-md li>ol{margin:.25rem 0 0}chat-streaming-md li:has(>input[type=checkbox]){list-style:none;margin-left:-1.25rem}chat-streaming-md li>input[type=checkbox]{margin-right:.5rem;vertical-align:middle}chat-streaming-md code{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--tplane-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text);padding:12px 14px;border-radius:var(--tplane-chat-radius-card);overflow-x:auto;font-family:var(--tplane-chat-font-mono);font-size:var(--tplane-chat-font-size-sm);line-height:1.5;margin:0 0 .75rem}chat-streaming-md pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}chat-streaming-md blockquote{border-left:3px solid var(--tplane-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--tplane-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--tplane-chat-separator);margin:1rem 0}chat-streaming-md table{border-collapse:collapse;margin:0 0 .75rem;width:100%;font-size:.95em}chat-streaming-md thead{background:var(--tplane-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--tplane-chat-separator);padding:6px 10px;text-align:left;vertical-align:top}chat-streaming-md th{font-weight:600}chat-streaming-md chat-md-table{display:block;overflow-x:auto;max-width:100%;margin:0 0 .75rem}chat-streaming-md chat-md-table-row{display:contents}chat-streaming-md chat-md-table-cell{display:contents}chat-streaming-md chat-md-table>table{margin:0}chat-streaming-md li.chat-md-list-item--task{list-style:none;margin-left:-1.25rem;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}chat-streaming-md li.chat-md-list-item--task>input[type=checkbox]{margin:0;flex:0 0 auto;transform:translateY(2px)}chat-streaming-md li.chat-md-list-item--task>chat-md-children{flex:1 1 auto;min-width:0}chat-streaming-md li.chat-md-list-item--task>chat-md-children>chat-md-paragraph:first-child>p{margin:0}chat-streaming-md img{max-width:100%;height:auto;border-radius:6px}chat-streaming-md .chat-md-image--broken{display:inline-flex;align-items:center;gap:.4rem;padding:.25rem .5rem;background:var(--tplane-chat-surface-alt);border:1px dashed var(--tplane-chat-separator);border-radius:6px;font-size:.9em;color:var(--tplane-chat-text-muted, currentColor);opacity:.85}chat-streaming-md .chat-md-image__icon{font-size:1em;line-height:1}chat-streaming-md .chat-md-image__alt{font-style:italic}chat-streaming-md .chat-md-math--display{display:block;margin:.5em 0;overflow-x:auto}chat-streaming-md .chat-md-math--raw{font-family:var(--tplane-chat-font-mono, ui-monospace, monospace)}\n"], dependencies: [{ kind: "component", type: MarkdownChildrenComponent, selector: "chat-md-children", inputs: ["parent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
3312
3725
|
}
|
|
3313
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
3726
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatStreamingMdComponent, decorators: [{
|
|
3314
3727
|
type: Component,
|
|
3315
|
-
args: [{ selector: 'chat-
|
|
3316
|
-
|
|
3317
|
-
<
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
(keydown.enter)="onKeydown($any($event))"
|
|
3328
|
-
(compositionstart)="composing.set(true)"
|
|
3329
|
-
(compositionend)="composing.set(false)"
|
|
3330
|
-
(focus)="focused.set(true)"
|
|
3331
|
-
(blur)="focused.set(false)"
|
|
3332
|
-
name="messageText"
|
|
3333
|
-
rows="1"
|
|
3334
|
-
aria-label="Type a message"
|
|
3335
|
-
></textarea>
|
|
3336
|
-
<div class="chat-input__controls">
|
|
3337
|
-
<ng-content select="[chatInputModelSelect]" />
|
|
3338
|
-
<ng-content select="[chatInputTrailing]" />
|
|
3339
|
-
@if (isLoading() && canStop()) {
|
|
3340
|
-
<button
|
|
3341
|
-
type="button"
|
|
3342
|
-
class="chat-input__send chat-input__send--stop"
|
|
3343
|
-
(click)="onStop()"
|
|
3344
|
-
aria-label="Stop generating"
|
|
3345
|
-
title="Stop generating"
|
|
3346
|
-
>
|
|
3347
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
3348
|
-
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
3349
|
-
</svg>
|
|
3350
|
-
</button>
|
|
3351
|
-
} @else {
|
|
3352
|
-
<button
|
|
3353
|
-
type="button"
|
|
3354
|
-
class="chat-input__send"
|
|
3355
|
-
[disabled]="!canSubmit()"
|
|
3356
|
-
(click)="onSubmit()"
|
|
3357
|
-
aria-label="Send message"
|
|
3358
|
-
>
|
|
3359
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3360
|
-
<line x1="12" y1="19" x2="12" y2="5"/>
|
|
3361
|
-
<polyline points="5 12 12 5 19 12"/>
|
|
3362
|
-
</svg>
|
|
3363
|
-
</button>
|
|
3364
|
-
}
|
|
3365
|
-
</div>
|
|
3366
|
-
</div>
|
|
3367
|
-
<ng-content select="[chatInputFooter]" />
|
|
3368
|
-
</div>
|
|
3369
|
-
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;width:100%;padding:0 var(--tplane-chat-edge-pad);box-sizing:border-box}.chat-input__container{width:100%;max-width:var(--tplane-chat-max-width);margin:0 auto}.chat-input__pill{display:flex;align-items:center;gap:8px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:9999px;padding:8px 8px 8px 16px;min-height:56px;box-sizing:border-box}.chat-input__textarea{flex:1 1 auto;border:0;outline:none;resize:none;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:1rem;line-height:1.5;padding:0;field-sizing:content;overflow-y:auto}.chat-input__textarea::placeholder{color:var(--tplane-chat-text-muted)}.chat-input__textarea::-webkit-scrollbar{width:4px}.chat-input__textarea::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:4px}.chat-input__controls{display:flex;align-items:center;gap:4px;flex:none}.chat-input__send,.chat-input__send--stop{width:36px;height:36px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:opacity .15s ease,transform .15s ease,background .15s ease;padding:0}.chat-input__send{background:var(--tplane-chat-text);color:var(--tplane-chat-bg)}.chat-input__send:disabled{opacity:.35;cursor:not-allowed;background:var(--tplane-chat-text-muted)}.chat-input__send:not(:disabled):hover{transform:scale(1.05)}.chat-input__send svg{width:16px;height:16px}.chat-input__send--stop{background:var(--tplane-chat-text-muted);color:var(--tplane-chat-bg)}.chat-input__send--stop:hover{transform:scale(1.05)}.chat-input__send--stop svg{width:14px;height:14px}\n"] }]
|
|
3370
|
-
}], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], submitOnEnter: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitOnEnter", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showStopButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showStopButton", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }], stopped: [{ type: i0.Output, args: ["stopped"] }], textareaEl: [{ type: i0.ViewChild, args: ['textareaEl', { isSignal: true }] }] } });
|
|
3728
|
+
args: [{ selector: 'chat-streaming-md', standalone: true, imports: [MarkdownChildrenComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
3729
|
+
@if (root(); as r) {
|
|
3730
|
+
<chat-md-children [parent]="r" />
|
|
3731
|
+
}
|
|
3732
|
+
`, providers: [
|
|
3733
|
+
{
|
|
3734
|
+
provide: MARKDOWN_VIEW_REGISTRY,
|
|
3735
|
+
useFactory: (host) => host.resolvedRegistry(),
|
|
3736
|
+
deps: [ChatStreamingMdComponent],
|
|
3737
|
+
},
|
|
3738
|
+
], styles: ["chat-streaming-md{display:block;color:var(--tplane-chat-text);line-height:var(--tplane-chat-line-height)}chat-streaming-md h1,chat-streaming-md h2,chat-streaming-md h3,chat-streaming-md h4,chat-streaming-md h5,chat-streaming-md h6{font-weight:600;line-height:1.25;margin:1.25rem 0 .75rem}chat-streaming-md h1:first-child,chat-streaming-md h2:first-child,chat-streaming-md h3:first-child,chat-streaming-md h4:first-child,chat-streaming-md h5:first-child,chat-streaming-md h6:first-child{margin-top:0}chat-streaming-md h1{font-size:1.5em;font-weight:700}chat-streaming-md h2{font-size:1.25em}chat-streaming-md h3{font-size:1.1em}chat-streaming-md h4{font-size:1em}chat-streaming-md h5,chat-streaming-md h6{font-size:.95em;color:var(--tplane-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--tplane-chat-font-size)}chat-streaming-md p:last-child{margin-bottom:0}chat-streaming-md strong,chat-streaming-md b{font-weight:700}chat-streaming-md em,chat-streaming-md i{font-style:italic}chat-streaming-md del,chat-streaming-md s{text-decoration:line-through;color:var(--tplane-chat-text-muted)}chat-streaming-md mark{background:var(--tplane-chat-surface-alt);padding:0 2px;border-radius:2px}chat-streaming-md sub{font-size:.75em;vertical-align:sub}chat-streaming-md sup{font-size:.75em;vertical-align:super}chat-streaming-md a{color:var(--tplane-chat-primary);text-decoration:underline;text-underline-offset:2px}chat-streaming-md a:hover{text-decoration-thickness:2px}chat-streaming-md ul,chat-streaming-md ol{margin:0 0 .75rem;padding-left:1.5rem}chat-streaming-md ul{list-style:disc outside}chat-streaming-md ol{list-style:decimal outside}chat-streaming-md ul ul{list-style:circle outside}chat-streaming-md ul ul ul{list-style:square outside}chat-streaming-md li{margin:.2rem 0}chat-streaming-md li::marker{color:var(--tplane-chat-text-muted)}chat-streaming-md li>p{margin:0 0 .25rem}chat-streaming-md li>ul,chat-streaming-md li>ol{margin:.25rem 0 0}chat-streaming-md li:has(>input[type=checkbox]){list-style:none;margin-left:-1.25rem}chat-streaming-md li>input[type=checkbox]{margin-right:.5rem;vertical-align:middle}chat-streaming-md code{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--tplane-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text);padding:12px 14px;border-radius:var(--tplane-chat-radius-card);overflow-x:auto;font-family:var(--tplane-chat-font-mono);font-size:var(--tplane-chat-font-size-sm);line-height:1.5;margin:0 0 .75rem}chat-streaming-md pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}chat-streaming-md blockquote{border-left:3px solid var(--tplane-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--tplane-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--tplane-chat-separator);margin:1rem 0}chat-streaming-md table{border-collapse:collapse;margin:0 0 .75rem;width:100%;font-size:.95em}chat-streaming-md thead{background:var(--tplane-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--tplane-chat-separator);padding:6px 10px;text-align:left;vertical-align:top}chat-streaming-md th{font-weight:600}chat-streaming-md chat-md-table{display:block;overflow-x:auto;max-width:100%;margin:0 0 .75rem}chat-streaming-md chat-md-table-row{display:contents}chat-streaming-md chat-md-table-cell{display:contents}chat-streaming-md chat-md-table>table{margin:0}chat-streaming-md li.chat-md-list-item--task{list-style:none;margin-left:-1.25rem;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}chat-streaming-md li.chat-md-list-item--task>input[type=checkbox]{margin:0;flex:0 0 auto;transform:translateY(2px)}chat-streaming-md li.chat-md-list-item--task>chat-md-children{flex:1 1 auto;min-width:0}chat-streaming-md li.chat-md-list-item--task>chat-md-children>chat-md-paragraph:first-child>p{margin:0}chat-streaming-md img{max-width:100%;height:auto;border-radius:6px}chat-streaming-md .chat-md-image--broken{display:inline-flex;align-items:center;gap:.4rem;padding:.25rem .5rem;background:var(--tplane-chat-surface-alt);border:1px dashed var(--tplane-chat-separator);border-radius:6px;font-size:.9em;color:var(--tplane-chat-text-muted, currentColor);opacity:.85}chat-streaming-md .chat-md-image__icon{font-size:1em;line-height:1}chat-streaming-md .chat-md-image__alt{font-style:italic}chat-streaming-md .chat-md-math--display{display:block;margin:.5em 0;overflow-x:auto}chat-streaming-md .chat-md-math--raw{font-family:var(--tplane-chat-font-mono, ui-monospace, monospace)}\n"] }]
|
|
3739
|
+
}], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], viewRegistry: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewRegistry", required: false }] }] } });
|
|
3371
3740
|
|
|
3372
3741
|
// SPDX-License-Identifier: MIT
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
.chat-typing__dot:nth-child(3) { animation-delay: 0.4s; }
|
|
3395
|
-
`;
|
|
3742
|
+
/**
|
|
3743
|
+
* Render a millisecond duration as a human-readable label suitable for
|
|
3744
|
+
* the chat-reasoning "Thought for Ns" pill.
|
|
3745
|
+
*
|
|
3746
|
+
* - <1 s → "<1s"
|
|
3747
|
+
* - 1–59 s → "Ns" (e.g. "4s")
|
|
3748
|
+
* - ≥60 s → "Nm Ms" (e.g. "1m 12s", "60m 0s")
|
|
3749
|
+
*
|
|
3750
|
+
* Negative or non-finite inputs collapse to "<1s" so a corrupted timing
|
|
3751
|
+
* map never produces noisy output.
|
|
3752
|
+
*/
|
|
3753
|
+
function formatDuration(ms) {
|
|
3754
|
+
if (!Number.isFinite(ms) || ms < 1000)
|
|
3755
|
+
return '<1s';
|
|
3756
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
3757
|
+
if (totalSeconds < 60)
|
|
3758
|
+
return `${totalSeconds}s`;
|
|
3759
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3760
|
+
const seconds = totalSeconds - minutes * 60;
|
|
3761
|
+
return `${minutes}m ${seconds}s`;
|
|
3762
|
+
}
|
|
3396
3763
|
|
|
3397
|
-
// libs/chat/src/lib/primitives/chat-
|
|
3764
|
+
// libs/chat/src/lib/primitives/chat-reasoning/chat-reasoning.component.ts
|
|
3398
3765
|
// SPDX-License-Identifier: MIT
|
|
3399
3766
|
/**
|
|
3400
|
-
*
|
|
3401
|
-
*
|
|
3767
|
+
* Renders an assistant's reasoning content as a compact pill that
|
|
3768
|
+
* expands to reveal the underlying text. Three visual states:
|
|
3402
3769
|
*
|
|
3403
|
-
*
|
|
3404
|
-
*
|
|
3405
|
-
*
|
|
3406
|
-
*
|
|
3407
|
-
*
|
|
3408
|
-
*
|
|
3409
|
-
*
|
|
3770
|
+
* - Streaming: pill shows "Thinking…" with a pulsing dot; auto-expanded
|
|
3771
|
+
* so the user sees reasoning stream in real time.
|
|
3772
|
+
* - Idle, with durationMs known: pill shows "Thought for {duration}";
|
|
3773
|
+
* collapsed by default, expand on click.
|
|
3774
|
+
* - Idle, no duration: pill shows "Show reasoning"; collapsed by default.
|
|
3775
|
+
*
|
|
3776
|
+
* The body re-uses chat-streaming-md so reasoning content gets the same
|
|
3777
|
+
* markdown rendering pipeline as the visible response (lists, code,
|
|
3778
|
+
* step labels often appear in reasoning output).
|
|
3779
|
+
*
|
|
3780
|
+
* Internal state: a tristate "expanded" — null means follow auto state-
|
|
3781
|
+
* driven logic (force-expand on isStreaming, otherwise honor
|
|
3782
|
+
* defaultExpanded), boolean is a manual user choice that wins for the
|
|
3783
|
+
* lifetime of the instance.
|
|
3410
3784
|
*/
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3785
|
+
class ChatReasoningComponent {
|
|
3786
|
+
content = input('', ...(ngDevMode ? [{ debugName: "content" }] : []));
|
|
3787
|
+
isStreaming = input(false, ...(ngDevMode ? [{ debugName: "isStreaming" }] : []));
|
|
3788
|
+
durationMs = input(undefined, ...(ngDevMode ? [{ debugName: "durationMs" }] : []));
|
|
3789
|
+
label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
3790
|
+
defaultExpanded = input(false, ...(ngDevMode ? [{ debugName: "defaultExpanded" }] : []));
|
|
3791
|
+
hasContent = computed(() => (this.content() ?? '').length > 0, ...(ngDevMode ? [{ debugName: "hasContent" }] : []));
|
|
3792
|
+
/** null = follow auto logic (streaming → expanded, else defaultExpanded). */
|
|
3793
|
+
_expandedOverride = signal(null, ...(ngDevMode ? [{ debugName: "_expandedOverride" }] : []));
|
|
3794
|
+
expanded = computed(() => {
|
|
3795
|
+
const override = this._expandedOverride();
|
|
3796
|
+
if (override !== null)
|
|
3797
|
+
return override;
|
|
3798
|
+
if (this.isStreaming())
|
|
3799
|
+
return true;
|
|
3800
|
+
return this.defaultExpanded();
|
|
3801
|
+
}, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
3802
|
+
expandedStr = computed(() => String(this.expanded()), ...(ngDevMode ? [{ debugName: "expandedStr" }] : []));
|
|
3803
|
+
resolvedLabel = computed(() => {
|
|
3804
|
+
const explicit = this.label();
|
|
3805
|
+
if (explicit)
|
|
3806
|
+
return explicit;
|
|
3807
|
+
if (this.isStreaming())
|
|
3808
|
+
return 'Thinking…';
|
|
3809
|
+
const ms = this.durationMs();
|
|
3810
|
+
if (typeof ms === 'number')
|
|
3811
|
+
return `Thought for ${formatDuration(ms)}`;
|
|
3812
|
+
return 'Show reasoning';
|
|
3813
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : []));
|
|
3814
|
+
constructor() {
|
|
3815
|
+
// Reset the manual override when streaming re-engages from idle (e.g.
|
|
3816
|
+
// follow-up turn that re-uses this instance) so the auto force-expand
|
|
3817
|
+
// logic takes over again. Spec §3.3 bullet 3.
|
|
3818
|
+
let prevStreaming = false;
|
|
3819
|
+
effect(() => {
|
|
3820
|
+
const streaming = this.isStreaming();
|
|
3821
|
+
if (!prevStreaming && streaming) {
|
|
3822
|
+
this._expandedOverride.set(null);
|
|
3823
|
+
}
|
|
3824
|
+
prevStreaming = streaming;
|
|
3825
|
+
});
|
|
3424
3826
|
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3827
|
+
toggle() {
|
|
3828
|
+
this._expandedOverride.set(!this.expanded());
|
|
3829
|
+
}
|
|
3830
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatReasoningComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3831
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatReasoningComponent, isStandalone: true, selector: "chat-reasoning", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, isStreaming: { classPropertyName: "isStreaming", publicName: "isStreaming", isSignal: true, isRequired: false, transformFunction: null }, durationMs: { classPropertyName: "durationMs", publicName: "durationMs", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, defaultExpanded: { classPropertyName: "defaultExpanded", publicName: "defaultExpanded", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-has-content": "hasContent()", "attr.data-expanded": "expandedStr()", "attr.data-streaming": "isStreaming()" } }, ngImport: i0, template: `
|
|
3832
|
+
<button
|
|
3833
|
+
type="button"
|
|
3834
|
+
class="chat-reasoning__header"
|
|
3835
|
+
[attr.aria-expanded]="expanded()"
|
|
3836
|
+
(click)="toggle()"
|
|
3837
|
+
>
|
|
3838
|
+
<svg class="chat-reasoning__chevron" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3839
|
+
<path d="M4 2l4 4-4 4"/>
|
|
3840
|
+
</svg>
|
|
3841
|
+
@if (isStreaming()) {
|
|
3842
|
+
<span class="chat-reasoning__pulse" aria-hidden="true"></span>
|
|
3843
|
+
}
|
|
3844
|
+
<span class="chat-reasoning__label">{{ resolvedLabel() }}</span>
|
|
3845
|
+
</button>
|
|
3846
|
+
@if (expanded()) {
|
|
3847
|
+
<div class="chat-reasoning__body">
|
|
3848
|
+
<chat-streaming-md [content]="content()" [streaming]="isStreaming()" />
|
|
3437
3849
|
</div>
|
|
3438
3850
|
}
|
|
3439
|
-
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;
|
|
3851
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;margin:0 0 .5rem}:host([data-has-content=\"false\"]){display:none}.chat-reasoning__header{display:inline-flex;align-items:center;gap:.5rem;padding:4px 10px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:9999px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-xs);font-family:inherit;cursor:pointer;line-height:1.2}.chat-reasoning__header:hover{color:var(--tplane-chat-text)}.chat-reasoning__chevron{width:10px;height:10px;transition:transform .12s ease}:host([data-expanded=\"true\"]) .chat-reasoning__chevron{transform:rotate(90deg)}.chat-reasoning__pulse{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:chat-reasoning-pulse 1.2s ease-in-out infinite}@keyframes chat-reasoning-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-reasoning__body{margin-top:.5rem;padding-left:12px;border-left:2px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted)}.chat-reasoning__body chat-streaming-md{font-size:.95em}\n"], dependencies: [{ kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3440
3852
|
}
|
|
3441
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
3853
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatReasoningComponent, decorators: [{
|
|
3442
3854
|
type: Component,
|
|
3443
|
-
args: [{ selector: 'chat-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3855
|
+
args: [{ selector: 'chat-reasoning', standalone: true, imports: [ChatStreamingMdComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
3856
|
+
'[attr.data-has-content]': 'hasContent()',
|
|
3857
|
+
'[attr.data-expanded]': 'expandedStr()',
|
|
3858
|
+
'[attr.data-streaming]': 'isStreaming()',
|
|
3859
|
+
}, template: `
|
|
3860
|
+
<button
|
|
3861
|
+
type="button"
|
|
3862
|
+
class="chat-reasoning__header"
|
|
3863
|
+
[attr.aria-expanded]="expanded()"
|
|
3864
|
+
(click)="toggle()"
|
|
3865
|
+
>
|
|
3866
|
+
<svg class="chat-reasoning__chevron" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3867
|
+
<path d="M4 2l4 4-4 4"/>
|
|
3868
|
+
</svg>
|
|
3869
|
+
@if (isStreaming()) {
|
|
3870
|
+
<span class="chat-reasoning__pulse" aria-hidden="true"></span>
|
|
3871
|
+
}
|
|
3872
|
+
<span class="chat-reasoning__label">{{ resolvedLabel() }}</span>
|
|
3873
|
+
</button>
|
|
3874
|
+
@if (expanded()) {
|
|
3875
|
+
<div class="chat-reasoning__body">
|
|
3876
|
+
<chat-streaming-md [content]="content()" [streaming]="isStreaming()" />
|
|
3449
3877
|
</div>
|
|
3450
3878
|
}
|
|
3451
|
-
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;
|
|
3452
|
-
}], propDecorators: {
|
|
3879
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;margin:0 0 .5rem}:host([data-has-content=\"false\"]){display:none}.chat-reasoning__header{display:inline-flex;align-items:center;gap:.5rem;padding:4px 10px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:9999px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-xs);font-family:inherit;cursor:pointer;line-height:1.2}.chat-reasoning__header:hover{color:var(--tplane-chat-text)}.chat-reasoning__chevron{width:10px;height:10px;transition:transform .12s ease}:host([data-expanded=\"true\"]) .chat-reasoning__chevron{transform:rotate(90deg)}.chat-reasoning__pulse{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:chat-reasoning-pulse 1.2s ease-in-out infinite}@keyframes chat-reasoning-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-reasoning__body{margin-top:.5rem;padding-left:12px;border-left:2px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted)}.chat-reasoning__body chat-streaming-md{font-size:.95em}\n"] }]
|
|
3880
|
+
}], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], isStreaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "isStreaming", required: false }] }], durationMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "durationMs", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], defaultExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultExpanded", required: false }] }] } });
|
|
3453
3881
|
|
|
3454
|
-
// libs/chat/src/lib/styles/chat-
|
|
3882
|
+
// libs/chat/src/lib/styles/chat-launcher-button.styles.ts
|
|
3455
3883
|
// SPDX-License-Identifier: MIT
|
|
3456
|
-
const
|
|
3457
|
-
:host { display:
|
|
3458
|
-
.chat-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3884
|
+
const CHAT_LAUNCHER_BUTTON_STYLES = `
|
|
3885
|
+
:host { display: inline-block; }
|
|
3886
|
+
.chat-launcher-button {
|
|
3887
|
+
width: 56px;
|
|
3888
|
+
height: 56px;
|
|
3889
|
+
border-radius: var(--tplane-chat-radius-launcher);
|
|
3890
|
+
background: var(--tplane-chat-primary);
|
|
3891
|
+
color: var(--tplane-chat-on-primary);
|
|
3463
3892
|
border: 0;
|
|
3464
|
-
padding: 0;
|
|
3465
3893
|
cursor: pointer;
|
|
3466
|
-
}
|
|
3467
|
-
.chat-history-search-palette {
|
|
3468
|
-
position: fixed;
|
|
3469
|
-
top: 15vh;
|
|
3470
|
-
left: 50%;
|
|
3471
|
-
transform: translateX(-50%);
|
|
3472
|
-
width: min(560px, 90vw);
|
|
3473
|
-
max-height: 70vh;
|
|
3474
|
-
background: var(--tplane-chat-bg);
|
|
3475
|
-
border: 1px solid var(--tplane-chat-separator);
|
|
3476
|
-
border-radius: 12px;
|
|
3477
|
-
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25);
|
|
3478
|
-
z-index: var(--tplane-chat-z-modal, 1101);
|
|
3479
|
-
display: flex;
|
|
3480
|
-
flex-direction: column;
|
|
3481
|
-
overflow: hidden;
|
|
3482
|
-
}
|
|
3483
|
-
.chat-history-search-palette__input-row {
|
|
3484
3894
|
display: flex;
|
|
3485
3895
|
align-items: center;
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
}
|
|
3490
|
-
.chat-history-search-palette__icon {
|
|
3491
|
-
width: 18px;
|
|
3492
|
-
height: 18px;
|
|
3493
|
-
color: var(--tplane-chat-text-muted);
|
|
3494
|
-
flex-shrink: 0;
|
|
3896
|
+
justify-content: center;
|
|
3897
|
+
box-shadow: var(--tplane-chat-shadow-md);
|
|
3898
|
+
transition: transform 200ms ease;
|
|
3495
3899
|
}
|
|
3496
|
-
.chat-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3900
|
+
.chat-launcher-button:hover { transform: scale(1.05); }
|
|
3901
|
+
.chat-launcher-button svg { width: 24px; height: 24px; }
|
|
3902
|
+
`;
|
|
3903
|
+
|
|
3904
|
+
// SPDX-License-Identifier: MIT
|
|
3905
|
+
class ChatLauncherButtonComponent {
|
|
3906
|
+
/** Fires when the inner <button> receives a click. Prefer this over
|
|
3907
|
+
* binding `(click)` on the host element — explicit output gives
|
|
3908
|
+
* consumers (and Playwright) an unambiguous click target that won't
|
|
3909
|
+
* be intercepted by sibling overlays in higher stacking contexts.
|
|
3910
|
+
* Native `(click)` on the host still works for back-compat: the
|
|
3911
|
+
* click event bubbles through unchanged. */
|
|
3912
|
+
clicked = output();
|
|
3913
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatLauncherButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3914
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.6", type: ChatLauncherButtonComponent, isStandalone: true, selector: "chat-launcher-button", outputs: { clicked: "clicked" }, ngImport: i0, template: `
|
|
3915
|
+
<button type="button" class="chat-launcher-button" aria-label="Open chat" (click)="clicked.emit()">
|
|
3916
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3917
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
3918
|
+
</svg>
|
|
3919
|
+
</button>
|
|
3920
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:inline-block}.chat-launcher-button{width:56px;height:56px;border-radius:var(--tplane-chat-radius-launcher);background:var(--tplane-chat-primary);color:var(--tplane-chat-on-primary);border:0;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--tplane-chat-shadow-md);transition:transform .2s ease}.chat-launcher-button:hover{transform:scale(1.05)}.chat-launcher-button svg{width:24px;height:24px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3921
|
+
}
|
|
3922
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatLauncherButtonComponent, decorators: [{
|
|
3923
|
+
type: Component,
|
|
3924
|
+
args: [{ selector: 'chat-launcher-button', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3925
|
+
<button type="button" class="chat-launcher-button" aria-label="Open chat" (click)="clicked.emit()">
|
|
3926
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3927
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
3928
|
+
</svg>
|
|
3929
|
+
</button>
|
|
3930
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:inline-block}.chat-launcher-button{width:56px;height:56px;border-radius:var(--tplane-chat-radius-launcher);background:var(--tplane-chat-primary);color:var(--tplane-chat-on-primary);border:0;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--tplane-chat-shadow-md);transition:transform .2s ease}.chat-launcher-button:hover{transform:scale(1.05)}.chat-launcher-button svg{width:24px;height:24px}\n"] }]
|
|
3931
|
+
}], propDecorators: { clicked: [{ type: i0.Output, args: ["clicked"] }] } });
|
|
3932
|
+
|
|
3933
|
+
// SPDX-License-Identifier: MIT
|
|
3934
|
+
const CHAT_SUGGESTIONS_STYLES = `
|
|
3935
|
+
:host { display: block; }
|
|
3936
|
+
.chat-suggestions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
|
|
3937
|
+
.chat-suggestion {
|
|
3938
|
+
padding: 6px 10px;
|
|
3939
|
+
font-size: var(--tplane-chat-font-size-xs);
|
|
3940
|
+
border-radius: var(--tplane-chat-radius-bubble);
|
|
3941
|
+
border: 1px solid var(--tplane-chat-muted);
|
|
3500
3942
|
background: transparent;
|
|
3501
3943
|
color: var(--tplane-chat-text);
|
|
3502
|
-
font: inherit;
|
|
3503
|
-
font-size: 1rem;
|
|
3504
|
-
}
|
|
3505
|
-
.chat-history-search-palette__input::placeholder {
|
|
3506
|
-
color: var(--tplane-chat-text-muted);
|
|
3507
|
-
}
|
|
3508
|
-
.chat-history-search-palette__close {
|
|
3509
|
-
background: transparent;
|
|
3510
|
-
border: 0;
|
|
3511
|
-
padding: 4px;
|
|
3512
|
-
color: var(--tplane-chat-text-muted);
|
|
3513
|
-
cursor: pointer;
|
|
3514
|
-
border-radius: 4px;
|
|
3515
|
-
}
|
|
3516
|
-
.chat-history-search-palette__close:hover { color: var(--tplane-chat-text); }
|
|
3517
|
-
.chat-history-search-palette__list {
|
|
3518
|
-
flex: 1 1 auto;
|
|
3519
|
-
overflow-y: auto;
|
|
3520
|
-
padding: 4px;
|
|
3521
|
-
margin: 0;
|
|
3522
|
-
list-style: none;
|
|
3523
|
-
}
|
|
3524
|
-
.chat-history-search-palette__row {
|
|
3525
|
-
display: flex;
|
|
3526
|
-
flex-direction: column;
|
|
3527
|
-
padding: 10px 12px;
|
|
3528
|
-
border-radius: 8px;
|
|
3529
3944
|
cursor: pointer;
|
|
3945
|
+
transition: transform 200ms ease;
|
|
3530
3946
|
}
|
|
3531
|
-
.chat-
|
|
3532
|
-
|
|
3533
|
-
}
|
|
3534
|
-
.chat-history-search-palette__row-title {
|
|
3535
|
-
color: var(--tplane-chat-text);
|
|
3536
|
-
font-size: var(--tplane-chat-font-size);
|
|
3537
|
-
}
|
|
3538
|
-
.chat-history-search-palette__row-subtitle {
|
|
3539
|
-
color: var(--tplane-chat-text-muted);
|
|
3540
|
-
font-size: var(--tplane-chat-font-size-sm);
|
|
3541
|
-
margin-top: 2px;
|
|
3542
|
-
}
|
|
3543
|
-
.chat-history-search-palette__empty,
|
|
3544
|
-
.chat-history-search-palette__hint {
|
|
3545
|
-
padding: 24px 16px;
|
|
3546
|
-
color: var(--tplane-chat-text-muted);
|
|
3547
|
-
text-align: center;
|
|
3548
|
-
font-size: var(--tplane-chat-font-size-sm);
|
|
3549
|
-
}
|
|
3550
|
-
.chat-history-search-palette__skeleton {
|
|
3551
|
-
padding: 8px 4px;
|
|
3552
|
-
}
|
|
3553
|
-
.chat-history-search-palette__skeleton-row {
|
|
3554
|
-
height: 36px;
|
|
3555
|
-
margin: 4px 0;
|
|
3556
|
-
background: var(--tplane-chat-surface-alt);
|
|
3557
|
-
border-radius: 8px;
|
|
3558
|
-
animation: tplane-chat-pulse 1.4s ease-in-out infinite;
|
|
3559
|
-
}
|
|
3947
|
+
.chat-suggestion:hover { transform: scale(1.03); }
|
|
3948
|
+
.chat-suggestion:disabled { cursor: wait; opacity: 0.6; }
|
|
3560
3949
|
`;
|
|
3561
3950
|
|
|
3562
|
-
// libs/chat/src/lib/primitives/chat-history-search-palette/chat-history-search-palette.component.ts
|
|
3563
3951
|
// SPDX-License-Identifier: MIT
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3952
|
+
class ChatSuggestionsComponent {
|
|
3953
|
+
suggestions = input([], ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
|
|
3954
|
+
selected = output();
|
|
3955
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSuggestionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3956
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatSuggestionsComponent, isStandalone: true, selector: "chat-suggestions", inputs: { suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: `
|
|
3957
|
+
<div class="chat-suggestions">
|
|
3958
|
+
@for (s of suggestions(); track s) {
|
|
3959
|
+
<button type="button" class="chat-suggestion" (click)="selected.emit(s)">{{ s }}</button>
|
|
3960
|
+
}
|
|
3961
|
+
</div>
|
|
3962
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-suggestions{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}.chat-suggestion{padding:6px 10px;font-size:var(--tplane-chat-font-size-xs);border-radius:var(--tplane-chat-radius-bubble);border:1px solid var(--tplane-chat-muted);background:transparent;color:var(--tplane-chat-text);cursor:pointer;transition:transform .2s ease}.chat-suggestion:hover{transform:scale(1.03)}.chat-suggestion:disabled{cursor:wait;opacity:.6}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3963
|
+
}
|
|
3964
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSuggestionsComponent, decorators: [{
|
|
3965
|
+
type: Component,
|
|
3966
|
+
args: [{ selector: 'chat-suggestions', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3967
|
+
<div class="chat-suggestions">
|
|
3968
|
+
@for (s of suggestions(); track s) {
|
|
3969
|
+
<button type="button" class="chat-suggestion" (click)="selected.emit(s)">{{ s }}</button>
|
|
3970
|
+
}
|
|
3971
|
+
</div>
|
|
3972
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-suggestions{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}.chat-suggestion{padding:6px 10px;font-size:var(--tplane-chat-font-size-xs);border-radius:var(--tplane-chat-radius-bubble);border:1px solid var(--tplane-chat-muted);background:transparent;color:var(--tplane-chat-text);cursor:pointer;transition:transform .2s ease}.chat-suggestion:hover{transform:scale(1.03)}.chat-suggestion:disabled{cursor:wait;opacity:.6}\n"] }]
|
|
3973
|
+
}], propDecorators: { suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
3974
|
+
|
|
3975
|
+
// libs/chat/src/lib/styles/chat-input.styles.ts
|
|
3976
|
+
// SPDX-License-Identifier: MIT
|
|
3977
|
+
const CHAT_INPUT_STYLES = `
|
|
3978
|
+
:host {
|
|
3979
|
+
display: block;
|
|
3980
|
+
width: 100%;
|
|
3981
|
+
padding: 0 var(--tplane-chat-edge-pad);
|
|
3982
|
+
box-sizing: border-box;
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
.chat-input__container {
|
|
3986
|
+
width: 100%;
|
|
3987
|
+
max-width: var(--tplane-chat-max-width);
|
|
3988
|
+
margin: 0 auto;
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
.chat-input__pill {
|
|
3992
|
+
display: flex;
|
|
3993
|
+
align-items: center;
|
|
3994
|
+
gap: 8px;
|
|
3995
|
+
background: var(--tplane-chat-surface);
|
|
3996
|
+
border: 1px solid var(--tplane-chat-separator);
|
|
3997
|
+
border-radius: 9999px;
|
|
3998
|
+
padding: 8px 8px 8px 16px;
|
|
3999
|
+
min-height: 56px;
|
|
4000
|
+
box-sizing: border-box;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
.chat-input__textarea {
|
|
4004
|
+
flex: 1 1 auto;
|
|
4005
|
+
border: 0;
|
|
4006
|
+
outline: none;
|
|
4007
|
+
resize: none;
|
|
4008
|
+
background: transparent;
|
|
4009
|
+
color: var(--tplane-chat-text);
|
|
4010
|
+
font: inherit;
|
|
4011
|
+
font-size: 1rem;
|
|
4012
|
+
line-height: 1.5;
|
|
4013
|
+
padding: 0;
|
|
4014
|
+
field-sizing: content;
|
|
4015
|
+
overflow-y: auto;
|
|
4016
|
+
}
|
|
4017
|
+
.chat-input__textarea::placeholder { color: var(--tplane-chat-text-muted); }
|
|
4018
|
+
.chat-input__textarea::-webkit-scrollbar { width: 4px; }
|
|
4019
|
+
.chat-input__textarea::-webkit-scrollbar-thumb { background: var(--tplane-chat-separator); border-radius: 4px; }
|
|
4020
|
+
|
|
4021
|
+
.chat-input__controls {
|
|
4022
|
+
display: flex;
|
|
4023
|
+
align-items: center;
|
|
4024
|
+
gap: 4px;
|
|
4025
|
+
flex: none;
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
.chat-input__send,
|
|
4029
|
+
.chat-input__send--stop {
|
|
4030
|
+
width: 36px;
|
|
4031
|
+
height: 36px;
|
|
4032
|
+
border-radius: 50%;
|
|
4033
|
+
border: 0;
|
|
4034
|
+
display: flex;
|
|
4035
|
+
align-items: center;
|
|
4036
|
+
justify-content: center;
|
|
4037
|
+
cursor: pointer;
|
|
4038
|
+
transition: opacity 150ms ease, transform 150ms ease, background 150ms ease;
|
|
4039
|
+
padding: 0;
|
|
4040
|
+
}
|
|
4041
|
+
.chat-input__send {
|
|
4042
|
+
background: var(--tplane-chat-text);
|
|
4043
|
+
color: var(--tplane-chat-bg);
|
|
4044
|
+
}
|
|
4045
|
+
.chat-input__send:disabled {
|
|
4046
|
+
opacity: 0.35;
|
|
4047
|
+
cursor: not-allowed;
|
|
4048
|
+
background: var(--tplane-chat-text-muted);
|
|
4049
|
+
}
|
|
4050
|
+
.chat-input__send:not(:disabled):hover { transform: scale(1.05); }
|
|
4051
|
+
.chat-input__send svg { width: 16px; height: 16px; }
|
|
4052
|
+
|
|
4053
|
+
.chat-input__send--stop {
|
|
4054
|
+
background: var(--tplane-chat-text-muted);
|
|
4055
|
+
color: var(--tplane-chat-bg);
|
|
4056
|
+
}
|
|
4057
|
+
.chat-input__send--stop:hover { transform: scale(1.05); }
|
|
4058
|
+
.chat-input__send--stop svg { width: 14px; height: 14px; }
|
|
4059
|
+
`;
|
|
4060
|
+
|
|
4061
|
+
// libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
|
|
4062
|
+
// SPDX-License-Identifier: MIT
|
|
4063
|
+
/**
|
|
4064
|
+
* Submits a trimmed message to the agent.
|
|
4065
|
+
* Returns the trimmed string on success, or `null` if the input was empty.
|
|
4066
|
+
*/
|
|
4067
|
+
function submitMessage(agent, text) {
|
|
4068
|
+
const trimmed = text.trim();
|
|
4069
|
+
if (!trimmed)
|
|
4070
|
+
return null;
|
|
4071
|
+
void agent.submit({ message: trimmed });
|
|
4072
|
+
return trimmed;
|
|
4073
|
+
}
|
|
4074
|
+
class ChatInputComponent {
|
|
4075
|
+
agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
4076
|
+
submitOnEnter = input(true, ...(ngDevMode ? [{ debugName: "submitOnEnter" }] : []));
|
|
4077
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
4078
|
+
/** When true (default), shows a stop button while the agent is streaming. */
|
|
4079
|
+
showStopButton = input(true, ...(ngDevMode ? [{ debugName: "showStopButton" }] : []));
|
|
4080
|
+
submitted = output();
|
|
4081
|
+
stopped = output();
|
|
4082
|
+
messageText = signal('', ...(ngDevMode ? [{ debugName: "messageText" }] : []));
|
|
4083
|
+
isLoading = computed(() => this.agent().isLoading(), ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
4084
|
+
/** True while an IME composition (CJK input, accent, autocorrect) is active. */
|
|
4085
|
+
composing = signal(false, ...(ngDevMode ? [{ debugName: "composing" }] : []));
|
|
4086
|
+
focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
|
|
4087
|
+
/** Submit is allowed only when not loading and there's non-whitespace text. */
|
|
4088
|
+
canSubmit = computed(() => {
|
|
4089
|
+
if (this.isLoading())
|
|
4090
|
+
return false;
|
|
4091
|
+
return this.messageText().trim().length > 0;
|
|
4092
|
+
}, ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
|
|
4093
|
+
/** The stop button only appears when the consumer opted in AND we're loading. */
|
|
4094
|
+
canStop = computed(() => this.showStopButton(), ...(ngDevMode ? [{ debugName: "canStop" }] : []));
|
|
4095
|
+
textareaEl = viewChild('textareaEl', ...(ngDevMode ? [{ debugName: "textareaEl" }] : []));
|
|
4096
|
+
/**
|
|
4097
|
+
* Auto-resize the textarea to fit its content as the user types or pastes
|
|
4098
|
+
* multi-line text. Caps at min(40vh, 320px); beyond that the textarea
|
|
4099
|
+
* scrolls. Without this, multi-line input is hidden behind the rows="1"
|
|
4100
|
+
* fixed height (caught by live browser smoke).
|
|
4101
|
+
*/
|
|
3576
4102
|
constructor() {
|
|
3577
4103
|
effect(() => {
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
const
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
4104
|
+
const text = this.messageText();
|
|
4105
|
+
const el = this.textareaEl()?.nativeElement;
|
|
4106
|
+
if (!el)
|
|
4107
|
+
return;
|
|
4108
|
+
// Cap: min(40vh, 320px). Recomputed on each input so viewport resizes
|
|
4109
|
+
// between keystrokes are picked up without a dedicated resize listener.
|
|
4110
|
+
const viewportH = typeof window === 'undefined' ? 600 : window.innerHeight;
|
|
4111
|
+
const cap = Math.min(viewportH * 0.4, 320);
|
|
4112
|
+
el.style.height = 'auto';
|
|
4113
|
+
const next = Math.min(el.scrollHeight, cap);
|
|
4114
|
+
el.style.height = `${next}px`;
|
|
4115
|
+
el.style.overflowY = el.scrollHeight > cap ? 'auto' : 'hidden';
|
|
4116
|
+
void text;
|
|
3588
4117
|
});
|
|
3589
4118
|
}
|
|
3590
|
-
|
|
3591
|
-
|
|
4119
|
+
focusTextarea() {
|
|
4120
|
+
this.textareaEl()?.nativeElement.focus();
|
|
3592
4121
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
4122
|
+
onSubmit() {
|
|
4123
|
+
const submitted = submitMessage(this.agent(), this.messageText());
|
|
4124
|
+
if (submitted !== null) {
|
|
4125
|
+
this.submitted.emit(submitted);
|
|
4126
|
+
this.messageText.set('');
|
|
4127
|
+
const el = this.textareaEl()?.nativeElement;
|
|
4128
|
+
if (el)
|
|
4129
|
+
el.value = '';
|
|
4130
|
+
requestAnimationFrame(() => this.textareaEl()?.nativeElement.focus());
|
|
4131
|
+
}
|
|
3595
4132
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
4133
|
+
/** Sync the textarea's value into the signal on user input. A direct
|
|
4134
|
+
* [value]/(input) pair is used instead of ngModel: NgModel does not
|
|
4135
|
+
* reliably write a programmatic clear back to the view under zoneless
|
|
4136
|
+
* + OnPush, leaving sent text visible in the composer (audit F1). */
|
|
4137
|
+
onInput(event) {
|
|
4138
|
+
this.messageText.set(event.target.value);
|
|
3599
4139
|
}
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
4140
|
+
/** Abort the current streaming response (if the adapter supports it). */
|
|
4141
|
+
onStop() {
|
|
4142
|
+
const a = this.agent();
|
|
4143
|
+
if (typeof a.stop === 'function') {
|
|
4144
|
+
void a.stop();
|
|
3605
4145
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
return;
|
|
3611
|
-
this.activeIndex.set(Math.min(this.activeIndex() + 1, max));
|
|
4146
|
+
this.stopped.emit();
|
|
4147
|
+
}
|
|
4148
|
+
onKeydown(event) {
|
|
4149
|
+
if (!this.submitOnEnter() || event.shiftKey)
|
|
3612
4150
|
return;
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
4151
|
+
// Don't submit while an IME composition is in progress (CJK input,
|
|
4152
|
+
// dead-key accents, autocorrect popups). The composition's terminating
|
|
4153
|
+
// Enter must reach the textarea so the candidate is committed; submitting
|
|
4154
|
+
// here would discard the user's in-progress character.
|
|
4155
|
+
if (this.composing() || event.isComposing || event.keyCode === 229)
|
|
3617
4156
|
return;
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
e.preventDefault();
|
|
3621
|
-
const rows = this.results();
|
|
3622
|
-
if (rows.length === 0)
|
|
3623
|
-
return;
|
|
3624
|
-
const row = rows[this.activeIndex()];
|
|
3625
|
-
this.threadSelected.emit(row.id);
|
|
3626
|
-
return;
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
onRowClick(id) {
|
|
3630
|
-
this.threadSelected.emit(id);
|
|
4157
|
+
event.preventDefault();
|
|
4158
|
+
this.onSubmit();
|
|
3631
4159
|
}
|
|
3632
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
3633
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type:
|
|
3634
|
-
|
|
3635
|
-
<
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
4160
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4161
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatInputComponent, isStandalone: true, selector: "chat-input", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, submitOnEnter: { classPropertyName: "submitOnEnter", publicName: "submitOnEnter", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showStopButton: { classPropertyName: "showStopButton", publicName: "showStopButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { submitted: "submitted", stopped: "stopped" }, viewQueries: [{ propertyName: "textareaEl", first: true, predicate: ["textareaEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4162
|
+
<div class="chat-input__container">
|
|
4163
|
+
<ng-content select="[chatInputBanner]" />
|
|
4164
|
+
<ng-content select="[chatInputAttachments]" />
|
|
4165
|
+
<div class="chat-input__pill">
|
|
4166
|
+
<ng-content select="[chatInputLeading]" />
|
|
4167
|
+
<textarea
|
|
4168
|
+
#textareaEl
|
|
4169
|
+
class="chat-input__textarea"
|
|
4170
|
+
[value]="messageText()"
|
|
4171
|
+
(input)="onInput($event)"
|
|
4172
|
+
[placeholder]="placeholder()"
|
|
4173
|
+
(keydown.enter)="onKeydown($any($event))"
|
|
4174
|
+
(compositionstart)="composing.set(true)"
|
|
4175
|
+
(compositionend)="composing.set(false)"
|
|
4176
|
+
(focus)="focused.set(true)"
|
|
4177
|
+
(blur)="focused.set(false)"
|
|
4178
|
+
name="messageText"
|
|
4179
|
+
rows="1"
|
|
4180
|
+
aria-label="Type a message"
|
|
4181
|
+
></textarea>
|
|
4182
|
+
<div class="chat-input__controls">
|
|
4183
|
+
<ng-content select="[chatInputModelSelect]" />
|
|
4184
|
+
<ng-content select="[chatInputTrailing]" />
|
|
4185
|
+
@if (isLoading() && canStop()) {
|
|
4186
|
+
<button
|
|
4187
|
+
type="button"
|
|
4188
|
+
class="chat-input__send chat-input__send--stop"
|
|
4189
|
+
(click)="onStop()"
|
|
4190
|
+
aria-label="Stop generating"
|
|
4191
|
+
title="Stop generating"
|
|
4192
|
+
>
|
|
4193
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
4194
|
+
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
4195
|
+
</svg>
|
|
4196
|
+
</button>
|
|
4197
|
+
} @else {
|
|
4198
|
+
<button
|
|
4199
|
+
type="button"
|
|
4200
|
+
class="chat-input__send"
|
|
4201
|
+
[disabled]="!canSubmit()"
|
|
4202
|
+
(click)="onSubmit()"
|
|
4203
|
+
aria-label="Send message"
|
|
4204
|
+
>
|
|
4205
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4206
|
+
<line x1="12" y1="19" x2="12" y2="5"/>
|
|
4207
|
+
<polyline points="5 12 12 5 19 12"/>
|
|
4208
|
+
</svg>
|
|
4209
|
+
</button>
|
|
4210
|
+
}
|
|
3676
4211
|
</div>
|
|
3677
|
-
|
|
3678
|
-
@if (loading() && results().length === 0) {
|
|
3679
|
-
<div class="chat-history-search-palette__skeleton" aria-hidden="true">
|
|
3680
|
-
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
3681
|
-
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
3682
|
-
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
3683
|
-
</div>
|
|
3684
|
-
} @else if (results().length === 0 && query().length === 0) {
|
|
3685
|
-
<div class="chat-history-search-palette__hint">Type to search your conversations.</div>
|
|
3686
|
-
} @else if (results().length === 0) {
|
|
3687
|
-
<div class="chat-history-search-palette__empty">No conversations match.</div>
|
|
3688
|
-
} @else {
|
|
3689
|
-
<ul class="chat-history-search-palette__list" role="listbox" [id]="listId">
|
|
3690
|
-
@for (row of results(); let i = $index; track row.id) {
|
|
3691
|
-
<li
|
|
3692
|
-
class="chat-history-search-palette__row"
|
|
3693
|
-
role="option"
|
|
3694
|
-
tabindex="-1"
|
|
3695
|
-
[id]="rowId(i)"
|
|
3696
|
-
[attr.aria-selected]="i === activeIndex() ? 'true' : 'false'"
|
|
3697
|
-
(click)="onRowClick(row.id)"
|
|
3698
|
-
(keydown.enter)="onRowClick(row.id)"
|
|
3699
|
-
(keydown.space)="onRowClick(row.id)"
|
|
3700
|
-
>
|
|
3701
|
-
<span class="chat-history-search-palette__row-title">{{ row.title }}</span>
|
|
3702
|
-
@if (row.subtitle) {
|
|
3703
|
-
<span class="chat-history-search-palette__row-subtitle">{{ row.subtitle }}</span>
|
|
3704
|
-
}
|
|
3705
|
-
</li>
|
|
3706
|
-
}
|
|
3707
|
-
</ul>
|
|
3708
|
-
}
|
|
3709
4212
|
</div>
|
|
3710
|
-
|
|
3711
|
-
|
|
4213
|
+
<ng-content select="[chatInputFooter]" />
|
|
4214
|
+
</div>
|
|
4215
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;width:100%;padding:0 var(--tplane-chat-edge-pad);box-sizing:border-box}.chat-input__container{width:100%;max-width:var(--tplane-chat-max-width);margin:0 auto}.chat-input__pill{display:flex;align-items:center;gap:8px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:9999px;padding:8px 8px 8px 16px;min-height:56px;box-sizing:border-box}.chat-input__textarea{flex:1 1 auto;border:0;outline:none;resize:none;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:1rem;line-height:1.5;padding:0;field-sizing:content;overflow-y:auto}.chat-input__textarea::placeholder{color:var(--tplane-chat-text-muted)}.chat-input__textarea::-webkit-scrollbar{width:4px}.chat-input__textarea::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:4px}.chat-input__controls{display:flex;align-items:center;gap:4px;flex:none}.chat-input__send,.chat-input__send--stop{width:36px;height:36px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:opacity .15s ease,transform .15s ease,background .15s ease;padding:0}.chat-input__send{background:var(--tplane-chat-text);color:var(--tplane-chat-bg)}.chat-input__send:disabled{opacity:.35;cursor:not-allowed;background:var(--tplane-chat-text-muted)}.chat-input__send:not(:disabled):hover{transform:scale(1.05)}.chat-input__send svg{width:16px;height:16px}.chat-input__send--stop{background:var(--tplane-chat-text-muted);color:var(--tplane-chat-bg)}.chat-input__send--stop:hover{transform:scale(1.05)}.chat-input__send--stop svg{width:14px;height:14px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3712
4216
|
}
|
|
3713
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
4217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatInputComponent, decorators: [{
|
|
3714
4218
|
type: Component,
|
|
3715
|
-
args: [{ selector: 'chat-
|
|
3716
|
-
|
|
3717
|
-
<
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
4219
|
+
args: [{ selector: 'chat-input', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4220
|
+
<div class="chat-input__container">
|
|
4221
|
+
<ng-content select="[chatInputBanner]" />
|
|
4222
|
+
<ng-content select="[chatInputAttachments]" />
|
|
4223
|
+
<div class="chat-input__pill">
|
|
4224
|
+
<ng-content select="[chatInputLeading]" />
|
|
4225
|
+
<textarea
|
|
4226
|
+
#textareaEl
|
|
4227
|
+
class="chat-input__textarea"
|
|
4228
|
+
[value]="messageText()"
|
|
4229
|
+
(input)="onInput($event)"
|
|
4230
|
+
[placeholder]="placeholder()"
|
|
4231
|
+
(keydown.enter)="onKeydown($any($event))"
|
|
4232
|
+
(compositionstart)="composing.set(true)"
|
|
4233
|
+
(compositionend)="composing.set(false)"
|
|
4234
|
+
(focus)="focused.set(true)"
|
|
4235
|
+
(blur)="focused.set(false)"
|
|
4236
|
+
name="messageText"
|
|
4237
|
+
rows="1"
|
|
4238
|
+
aria-label="Type a message"
|
|
4239
|
+
></textarea>
|
|
4240
|
+
<div class="chat-input__controls">
|
|
4241
|
+
<ng-content select="[chatInputModelSelect]" />
|
|
4242
|
+
<ng-content select="[chatInputTrailing]" />
|
|
4243
|
+
@if (isLoading() && canStop()) {
|
|
4244
|
+
<button
|
|
4245
|
+
type="button"
|
|
4246
|
+
class="chat-input__send chat-input__send--stop"
|
|
4247
|
+
(click)="onStop()"
|
|
4248
|
+
aria-label="Stop generating"
|
|
4249
|
+
title="Stop generating"
|
|
4250
|
+
>
|
|
4251
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
4252
|
+
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
4253
|
+
</svg>
|
|
4254
|
+
</button>
|
|
4255
|
+
} @else {
|
|
4256
|
+
<button
|
|
4257
|
+
type="button"
|
|
4258
|
+
class="chat-input__send"
|
|
4259
|
+
[disabled]="!canSubmit()"
|
|
4260
|
+
(click)="onSubmit()"
|
|
4261
|
+
aria-label="Send message"
|
|
4262
|
+
>
|
|
4263
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4264
|
+
<line x1="12" y1="19" x2="12" y2="5"/>
|
|
4265
|
+
<polyline points="5 12 12 5 19 12"/>
|
|
4266
|
+
</svg>
|
|
4267
|
+
</button>
|
|
4268
|
+
}
|
|
3758
4269
|
</div>
|
|
4270
|
+
</div>
|
|
4271
|
+
<ng-content select="[chatInputFooter]" />
|
|
4272
|
+
</div>
|
|
4273
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;width:100%;padding:0 var(--tplane-chat-edge-pad);box-sizing:border-box}.chat-input__container{width:100%;max-width:var(--tplane-chat-max-width);margin:0 auto}.chat-input__pill{display:flex;align-items:center;gap:8px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:9999px;padding:8px 8px 8px 16px;min-height:56px;box-sizing:border-box}.chat-input__textarea{flex:1 1 auto;border:0;outline:none;resize:none;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:1rem;line-height:1.5;padding:0;field-sizing:content;overflow-y:auto}.chat-input__textarea::placeholder{color:var(--tplane-chat-text-muted)}.chat-input__textarea::-webkit-scrollbar{width:4px}.chat-input__textarea::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:4px}.chat-input__controls{display:flex;align-items:center;gap:4px;flex:none}.chat-input__send,.chat-input__send--stop{width:36px;height:36px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:opacity .15s ease,transform .15s ease,background .15s ease;padding:0}.chat-input__send{background:var(--tplane-chat-text);color:var(--tplane-chat-bg)}.chat-input__send:disabled{opacity:.35;cursor:not-allowed;background:var(--tplane-chat-text-muted)}.chat-input__send:not(:disabled):hover{transform:scale(1.05)}.chat-input__send svg{width:16px;height:16px}.chat-input__send--stop{background:var(--tplane-chat-text-muted);color:var(--tplane-chat-bg)}.chat-input__send--stop:hover{transform:scale(1.05)}.chat-input__send--stop svg{width:14px;height:14px}\n"] }]
|
|
4274
|
+
}], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], submitOnEnter: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitOnEnter", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showStopButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showStopButton", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }], stopped: [{ type: i0.Output, args: ["stopped"] }], textareaEl: [{ type: i0.ViewChild, args: ['textareaEl', { isSignal: true }] }] } });
|
|
3759
4275
|
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
4276
|
+
// SPDX-License-Identifier: MIT
|
|
4277
|
+
const CHAT_TYPING_INDICATOR_STYLES = `
|
|
4278
|
+
/* Sit in the same centered column as chat-message-list so the dots
|
|
4279
|
+
don't flash at the scroll container's left edge before the assistant
|
|
4280
|
+
message renders. */
|
|
4281
|
+
:host {
|
|
4282
|
+
display: block;
|
|
4283
|
+
padding: 0 var(--tplane-chat-space-6) var(--tplane-chat-space-3);
|
|
4284
|
+
max-width: var(--tplane-chat-max-width);
|
|
4285
|
+
margin: 0 auto;
|
|
4286
|
+
width: 100%;
|
|
4287
|
+
box-sizing: border-box;
|
|
4288
|
+
}
|
|
4289
|
+
.chat-typing__dots { display: inline-flex; gap: 4px; align-items: center; }
|
|
4290
|
+
.chat-typing__dot {
|
|
4291
|
+
width: 6px;
|
|
4292
|
+
height: 6px;
|
|
4293
|
+
border-radius: 50%;
|
|
4294
|
+
background: var(--tplane-chat-text-muted);
|
|
4295
|
+
animation: tplane-chat-typing-dot 1.4s ease-in-out infinite both;
|
|
4296
|
+
}
|
|
4297
|
+
.chat-typing__dot:nth-child(2) { animation-delay: 0.2s; }
|
|
4298
|
+
.chat-typing__dot:nth-child(3) { animation-delay: 0.4s; }
|
|
4299
|
+
`;
|
|
4300
|
+
|
|
4301
|
+
// libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
|
|
4302
|
+
// SPDX-License-Identifier: MIT
|
|
4303
|
+
/**
|
|
4304
|
+
* Whether the agent should show a "typing" indicator — it is loading and has
|
|
4305
|
+
* not yet started streaming the assistant's reply.
|
|
4306
|
+
*
|
|
4307
|
+
* @param agent The agent to inspect.
|
|
4308
|
+
* @returns `true` while the agent is awaiting a response but no assistant text
|
|
4309
|
+
* has streamed yet; `false` once tokens arrive or the agent is idle.
|
|
4310
|
+
* @example
|
|
4311
|
+
* ```ts
|
|
4312
|
+
* \@if (isTyping(agent)) { <chat-typing-indicator [agent]="agent" /> }
|
|
4313
|
+
* ```
|
|
4314
|
+
*/
|
|
4315
|
+
function isTyping(agent) {
|
|
4316
|
+
if (!agent.isLoading())
|
|
4317
|
+
return false;
|
|
4318
|
+
const msgs = agent.messages();
|
|
4319
|
+
if (msgs.length === 0)
|
|
4320
|
+
return true;
|
|
4321
|
+
const last = msgs[msgs.length - 1];
|
|
4322
|
+
if (last.role === 'user')
|
|
4323
|
+
return true;
|
|
4324
|
+
if (last.role === 'assistant') {
|
|
4325
|
+
return typeof last.content === 'string'
|
|
4326
|
+
? !last.content
|
|
4327
|
+
: last.content.length === 0;
|
|
4328
|
+
}
|
|
4329
|
+
return false;
|
|
4330
|
+
}
|
|
4331
|
+
class ChatTypingIndicatorComponent {
|
|
4332
|
+
agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
4333
|
+
visible = computed(() => isTyping(this.agent()), ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
4334
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatTypingIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4335
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatTypingIndicatorComponent, isStandalone: true, selector: "chat-typing-indicator", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
4336
|
+
@if (visible()) {
|
|
4337
|
+
<div class="chat-typing__dots" role="status" aria-label="Assistant is typing">
|
|
4338
|
+
<span class="chat-typing__dot"></span>
|
|
4339
|
+
<span class="chat-typing__dot"></span>
|
|
4340
|
+
<span class="chat-typing__dot"></span>
|
|
3791
4341
|
</div>
|
|
3792
4342
|
}
|
|
3793
|
-
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:
|
|
3794
|
-
|
|
4343
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;padding:0 var(--tplane-chat-space-6) var(--tplane-chat-space-3);max-width:var(--tplane-chat-max-width);margin:0 auto;width:100%;box-sizing:border-box}.chat-typing__dots{display:inline-flex;gap:4px;align-items:center}.chat-typing__dot{width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:tplane-chat-typing-dot 1.4s ease-in-out infinite both}.chat-typing__dot:nth-child(2){animation-delay:.2s}.chat-typing__dot:nth-child(3){animation-delay:.4s}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4344
|
+
}
|
|
4345
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatTypingIndicatorComponent, decorators: [{
|
|
4346
|
+
type: Component,
|
|
4347
|
+
args: [{ selector: 'chat-typing-indicator', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4348
|
+
@if (visible()) {
|
|
4349
|
+
<div class="chat-typing__dots" role="status" aria-label="Assistant is typing">
|
|
4350
|
+
<span class="chat-typing__dot"></span>
|
|
4351
|
+
<span class="chat-typing__dot"></span>
|
|
4352
|
+
<span class="chat-typing__dot"></span>
|
|
4353
|
+
</div>
|
|
4354
|
+
}
|
|
4355
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;padding:0 var(--tplane-chat-space-6) var(--tplane-chat-space-3);max-width:var(--tplane-chat-max-width);margin:0 auto;width:100%;box-sizing:border-box}.chat-typing__dots{display:inline-flex;gap:4px;align-items:center}.chat-typing__dot{width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:tplane-chat-typing-dot 1.4s ease-in-out infinite both}.chat-typing__dot:nth-child(2){animation-delay:.2s}.chat-typing__dot:nth-child(3){animation-delay:.4s}\n"] }]
|
|
4356
|
+
}], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }] } });
|
|
3795
4357
|
|
|
3796
|
-
// libs/chat/src/lib/styles/chat-
|
|
4358
|
+
// libs/chat/src/lib/styles/chat-history-search-palette.styles.ts
|
|
3797
4359
|
// SPDX-License-Identifier: MIT
|
|
3798
|
-
const
|
|
4360
|
+
const CHAT_HISTORY_SEARCH_PALETTE_STYLES = `
|
|
3799
4361
|
:host { display: contents; }
|
|
3800
|
-
.chat-
|
|
4362
|
+
.chat-history-search-palette__scrim {
|
|
3801
4363
|
position: fixed;
|
|
3802
4364
|
inset: 0;
|
|
3803
|
-
background:
|
|
3804
|
-
z-index:
|
|
4365
|
+
background: rgba(0, 0, 0, 0.4);
|
|
4366
|
+
z-index: var(--tplane-chat-z-modal-scrim, 1100);
|
|
3805
4367
|
border: 0;
|
|
3806
4368
|
padding: 0;
|
|
3807
|
-
cursor:
|
|
4369
|
+
cursor: pointer;
|
|
3808
4370
|
}
|
|
3809
|
-
.chat-
|
|
4371
|
+
.chat-history-search-palette {
|
|
3810
4372
|
position: fixed;
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
4373
|
+
top: 15vh;
|
|
4374
|
+
left: 50%;
|
|
4375
|
+
transform: translateX(-50%);
|
|
4376
|
+
width: min(560px, 90vw);
|
|
4377
|
+
max-height: 70vh;
|
|
3816
4378
|
background: var(--tplane-chat-bg);
|
|
3817
4379
|
border: 1px solid var(--tplane-chat-separator);
|
|
3818
|
-
border-radius:
|
|
3819
|
-
box-shadow: 0
|
|
4380
|
+
border-radius: 12px;
|
|
4381
|
+
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25);
|
|
4382
|
+
z-index: var(--tplane-chat-z-modal, 1101);
|
|
4383
|
+
display: flex;
|
|
4384
|
+
flex-direction: column;
|
|
4385
|
+
overflow: hidden;
|
|
3820
4386
|
}
|
|
3821
|
-
.chat-
|
|
3822
|
-
display:
|
|
3823
|
-
|
|
3824
|
-
|
|
4387
|
+
.chat-history-search-palette__input-row {
|
|
4388
|
+
display: flex;
|
|
4389
|
+
align-items: center;
|
|
4390
|
+
gap: 8px;
|
|
4391
|
+
padding: 12px 16px;
|
|
4392
|
+
border-bottom: 1px solid var(--tplane-chat-separator);
|
|
4393
|
+
}
|
|
4394
|
+
.chat-history-search-palette__icon {
|
|
4395
|
+
width: 18px;
|
|
4396
|
+
height: 18px;
|
|
4397
|
+
color: var(--tplane-chat-text-muted);
|
|
4398
|
+
flex-shrink: 0;
|
|
4399
|
+
}
|
|
4400
|
+
.chat-history-search-palette__input {
|
|
4401
|
+
flex: 1 1 auto;
|
|
4402
|
+
border: 0;
|
|
4403
|
+
outline: none;
|
|
4404
|
+
background: transparent;
|
|
3825
4405
|
color: var(--tplane-chat-text);
|
|
3826
|
-
font
|
|
4406
|
+
font: inherit;
|
|
4407
|
+
font-size: 1rem;
|
|
4408
|
+
}
|
|
4409
|
+
.chat-history-search-palette__input::placeholder {
|
|
4410
|
+
color: var(--tplane-chat-text-muted);
|
|
4411
|
+
}
|
|
4412
|
+
.chat-history-search-palette__close {
|
|
4413
|
+
background: transparent;
|
|
4414
|
+
border: 0;
|
|
4415
|
+
padding: 4px;
|
|
4416
|
+
color: var(--tplane-chat-text-muted);
|
|
3827
4417
|
cursor: pointer;
|
|
3828
|
-
|
|
4418
|
+
border-radius: 4px;
|
|
3829
4419
|
}
|
|
3830
|
-
.chat-
|
|
4420
|
+
.chat-history-search-palette__close:hover { color: var(--tplane-chat-text); }
|
|
4421
|
+
.chat-history-search-palette__list {
|
|
4422
|
+
flex: 1 1 auto;
|
|
4423
|
+
overflow-y: auto;
|
|
4424
|
+
padding: 4px;
|
|
4425
|
+
margin: 0;
|
|
4426
|
+
list-style: none;
|
|
4427
|
+
}
|
|
4428
|
+
.chat-history-search-palette__row {
|
|
4429
|
+
display: flex;
|
|
4430
|
+
flex-direction: column;
|
|
4431
|
+
padding: 10px 12px;
|
|
4432
|
+
border-radius: 8px;
|
|
4433
|
+
cursor: pointer;
|
|
4434
|
+
}
|
|
4435
|
+
.chat-history-search-palette__row[aria-selected="true"] {
|
|
3831
4436
|
background: var(--tplane-chat-surface-alt);
|
|
3832
4437
|
}
|
|
3833
|
-
.chat-
|
|
3834
|
-
|
|
3835
|
-
|
|
4438
|
+
.chat-history-search-palette__row-title {
|
|
4439
|
+
color: var(--tplane-chat-text);
|
|
4440
|
+
font-size: var(--tplane-chat-font-size);
|
|
3836
4441
|
}
|
|
3837
|
-
.chat-
|
|
3838
|
-
color: var(--tplane-chat-
|
|
4442
|
+
.chat-history-search-palette__row-subtitle {
|
|
4443
|
+
color: var(--tplane-chat-text-muted);
|
|
4444
|
+
font-size: var(--tplane-chat-font-size-sm);
|
|
4445
|
+
margin-top: 2px;
|
|
3839
4446
|
}
|
|
3840
|
-
.chat-
|
|
4447
|
+
.chat-history-search-palette__empty,
|
|
4448
|
+
.chat-history-search-palette__hint {
|
|
4449
|
+
padding: 24px 16px;
|
|
3841
4450
|
color: var(--tplane-chat-text-muted);
|
|
3842
|
-
|
|
3843
|
-
|
|
4451
|
+
text-align: center;
|
|
4452
|
+
font-size: var(--tplane-chat-font-size-sm);
|
|
4453
|
+
}
|
|
4454
|
+
.chat-history-search-palette__skeleton {
|
|
4455
|
+
padding: 8px 4px;
|
|
4456
|
+
}
|
|
4457
|
+
.chat-history-search-palette__skeleton-row {
|
|
4458
|
+
height: 36px;
|
|
4459
|
+
margin: 4px 0;
|
|
4460
|
+
background: var(--tplane-chat-surface-alt);
|
|
4461
|
+
border-radius: 8px;
|
|
4462
|
+
animation: tplane-chat-pulse 1.4s ease-in-out infinite;
|
|
3844
4463
|
}
|
|
3845
4464
|
`;
|
|
3846
4465
|
|
|
3847
|
-
// libs/chat/src/lib/primitives/chat-
|
|
4466
|
+
// libs/chat/src/lib/primitives/chat-history-search-palette/chat-history-search-palette.component.ts
|
|
3848
4467
|
// SPDX-License-Identifier: MIT
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
itemSelected = output();
|
|
4468
|
+
let paletteInstanceCounter = 0;
|
|
4469
|
+
class ChatHistorySearchPaletteComponent {
|
|
4470
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
4471
|
+
query = model('', ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
4472
|
+
results = input([], ...(ngDevMode ? [{ debugName: "results" }] : []));
|
|
4473
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
4474
|
+
placeholder = input('Search conversations', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
4475
|
+
threadSelected = output();
|
|
3858
4476
|
closed = output();
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
const pos = this.anchorPos();
|
|
3863
|
-
if (pos) {
|
|
3864
|
-
return { top: pos.y + 4, left: Math.max(pos.x, 8) };
|
|
3865
|
-
}
|
|
3866
|
-
const el = this.anchor();
|
|
3867
|
-
if (!el) {
|
|
3868
|
-
const vw = typeof window === 'undefined' ? 0 : window.innerWidth;
|
|
3869
|
-
const vh = typeof window === 'undefined' ? 0 : window.innerHeight;
|
|
3870
|
-
return { top: Math.max(vh / 3, 0), left: Math.max(vw / 2 - 80, 0) };
|
|
3871
|
-
}
|
|
3872
|
-
const rect = el.getBoundingClientRect();
|
|
3873
|
-
return { top: rect.bottom + 4, left: Math.max(rect.right - 160, 8) };
|
|
3874
|
-
}, ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
4477
|
+
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
|
|
4478
|
+
listId = `chat-history-search-palette__results-${++paletteInstanceCounter}`;
|
|
4479
|
+
inputEl = viewChild('inputEl', ...(ngDevMode ? [{ debugName: "inputEl" }] : []));
|
|
3875
4480
|
constructor() {
|
|
3876
4481
|
effect(() => {
|
|
3877
|
-
if (
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4482
|
+
if (this.open()) {
|
|
4483
|
+
this.activeIndex.set(0);
|
|
4484
|
+
queueMicrotask(() => this.inputEl()?.nativeElement.focus());
|
|
4485
|
+
}
|
|
4486
|
+
});
|
|
4487
|
+
effect(() => {
|
|
4488
|
+
const max = this.results().length - 1;
|
|
4489
|
+
if (max >= 0 && this.activeIndex() > max) {
|
|
4490
|
+
this.activeIndex.set(max);
|
|
4491
|
+
}
|
|
3884
4492
|
});
|
|
3885
4493
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
return;
|
|
3889
|
-
this.itemSelected.emit(item.id);
|
|
3890
|
-
this.closed.emit();
|
|
4494
|
+
rowId(index) {
|
|
4495
|
+
return `${this.listId}__row-${index}`;
|
|
3891
4496
|
}
|
|
3892
|
-
|
|
4497
|
+
activeRowId() {
|
|
4498
|
+
return this.results().length > 0 ? this.rowId(this.activeIndex()) : null;
|
|
4499
|
+
}
|
|
4500
|
+
onInput(e) {
|
|
4501
|
+
const value = e.target.value;
|
|
4502
|
+
this.query.set(value);
|
|
4503
|
+
}
|
|
4504
|
+
onInputKeydown(e) {
|
|
3893
4505
|
if (e.key === 'Escape') {
|
|
3894
4506
|
e.preventDefault();
|
|
3895
4507
|
this.closed.emit();
|
|
3896
4508
|
return;
|
|
3897
4509
|
}
|
|
3898
|
-
if (e.key === 'ArrowDown'
|
|
4510
|
+
if (e.key === 'ArrowDown') {
|
|
3899
4511
|
e.preventDefault();
|
|
3900
|
-
const
|
|
3901
|
-
|
|
3902
|
-
if (items.length === 0)
|
|
4512
|
+
const max = this.results().length - 1;
|
|
4513
|
+
if (max < 0)
|
|
3903
4514
|
return;
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
4515
|
+
this.activeIndex.set(Math.min(this.activeIndex() + 1, max));
|
|
4516
|
+
return;
|
|
4517
|
+
}
|
|
4518
|
+
if (e.key === 'ArrowUp') {
|
|
4519
|
+
e.preventDefault();
|
|
4520
|
+
this.activeIndex.set(Math.max(this.activeIndex() - 1, 0));
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
if (e.key === 'Enter') {
|
|
4524
|
+
e.preventDefault();
|
|
4525
|
+
const rows = this.results();
|
|
4526
|
+
if (rows.length === 0)
|
|
4527
|
+
return;
|
|
4528
|
+
const row = rows[this.activeIndex()];
|
|
4529
|
+
this.threadSelected.emit(row.id);
|
|
4530
|
+
return;
|
|
3910
4531
|
}
|
|
3911
4532
|
}
|
|
3912
|
-
|
|
3913
|
-
|
|
4533
|
+
onRowClick(id) {
|
|
4534
|
+
this.threadSelected.emit(id);
|
|
4535
|
+
}
|
|
4536
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatHistorySearchPaletteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4537
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatHistorySearchPaletteComponent, isStandalone: true, selector: "chat-history-search-palette", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, results: { classPropertyName: "results", publicName: "results", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", query: "queryChange", threadSelected: "threadSelected", closed: "closed" }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
3914
4538
|
@if (open()) {
|
|
3915
4539
|
<button
|
|
3916
4540
|
type="button"
|
|
3917
|
-
class="chat-
|
|
3918
|
-
aria-label="Close
|
|
4541
|
+
class="chat-history-search-palette__scrim"
|
|
4542
|
+
aria-label="Close search"
|
|
3919
4543
|
(click)="closed.emit()"
|
|
3920
4544
|
></button>
|
|
3921
|
-
<
|
|
3922
|
-
class="chat-
|
|
3923
|
-
role="
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
[style.left.px]="position().left"
|
|
3927
|
-
(keydown)="onMenuKeydown($event)"
|
|
4545
|
+
<div
|
|
4546
|
+
class="chat-history-search-palette"
|
|
4547
|
+
role="dialog"
|
|
4548
|
+
aria-modal="true"
|
|
4549
|
+
aria-label="Search conversations"
|
|
3928
4550
|
>
|
|
3929
|
-
|
|
3930
|
-
<
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
4551
|
+
<div class="chat-history-search-palette__input-row">
|
|
4552
|
+
<svg class="chat-history-search-palette__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4553
|
+
<circle cx="11" cy="11" r="7"/>
|
|
4554
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
4555
|
+
</svg>
|
|
4556
|
+
<input
|
|
4557
|
+
#inputEl
|
|
4558
|
+
class="chat-history-search-palette__input"
|
|
4559
|
+
type="text"
|
|
4560
|
+
role="combobox"
|
|
4561
|
+
aria-expanded="true"
|
|
4562
|
+
[attr.aria-controls]="listId"
|
|
4563
|
+
[attr.aria-activedescendant]="activeRowId()"
|
|
4564
|
+
[placeholder]="placeholder()"
|
|
4565
|
+
[value]="query()"
|
|
4566
|
+
(input)="onInput($event)"
|
|
4567
|
+
(keydown)="onInputKeydown($event)"
|
|
4568
|
+
/>
|
|
4569
|
+
<button
|
|
4570
|
+
type="button"
|
|
4571
|
+
class="chat-history-search-palette__close"
|
|
4572
|
+
aria-label="Close"
|
|
4573
|
+
(click)="closed.emit()"
|
|
3941
4574
|
>
|
|
3942
|
-
|
|
3943
|
-
|
|
4575
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4576
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
4577
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
4578
|
+
</svg>
|
|
4579
|
+
</button>
|
|
4580
|
+
</div>
|
|
4581
|
+
|
|
4582
|
+
@if (loading() && results().length === 0) {
|
|
4583
|
+
<div class="chat-history-search-palette__skeleton" aria-hidden="true">
|
|
4584
|
+
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
4585
|
+
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
4586
|
+
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
4587
|
+
</div>
|
|
4588
|
+
} @else if (results().length === 0 && query().length === 0) {
|
|
4589
|
+
<div class="chat-history-search-palette__hint">Type to search your conversations.</div>
|
|
4590
|
+
} @else if (results().length === 0) {
|
|
4591
|
+
<div class="chat-history-search-palette__empty">No conversations match.</div>
|
|
4592
|
+
} @else {
|
|
4593
|
+
<ul class="chat-history-search-palette__list" role="listbox" [id]="listId">
|
|
4594
|
+
@for (row of results(); let i = $index; track row.id) {
|
|
4595
|
+
<li
|
|
4596
|
+
class="chat-history-search-palette__row"
|
|
4597
|
+
role="option"
|
|
4598
|
+
tabindex="-1"
|
|
4599
|
+
[id]="rowId(i)"
|
|
4600
|
+
[attr.aria-selected]="i === activeIndex() ? 'true' : 'false'"
|
|
4601
|
+
(click)="onRowClick(row.id)"
|
|
4602
|
+
(keydown.enter)="onRowClick(row.id)"
|
|
4603
|
+
(keydown.space)="onRowClick(row.id)"
|
|
4604
|
+
>
|
|
4605
|
+
<span class="chat-history-search-palette__row-title">{{ row.title }}</span>
|
|
4606
|
+
@if (row.subtitle) {
|
|
4607
|
+
<span class="chat-history-search-palette__row-subtitle">{{ row.subtitle }}</span>
|
|
4608
|
+
}
|
|
4609
|
+
</li>
|
|
4610
|
+
}
|
|
4611
|
+
</ul>
|
|
3944
4612
|
}
|
|
3945
|
-
</
|
|
4613
|
+
</div>
|
|
3946
4614
|
}
|
|
3947
|
-
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:contents}.chat-
|
|
4615
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:contents}.chat-history-search-palette__scrim{position:fixed;inset:0;background:#0006;z-index:var(--tplane-chat-z-modal-scrim, 1100);border:0;padding:0;cursor:pointer}.chat-history-search-palette{position:fixed;top:15vh;left:50%;transform:translate(-50%);width:min(560px,90vw);max-height:70vh;background:var(--tplane-chat-bg);border:1px solid var(--tplane-chat-separator);border-radius:12px;box-shadow:0 16px 48px #00000040;z-index:var(--tplane-chat-z-modal, 1101);display:flex;flex-direction:column;overflow:hidden}.chat-history-search-palette__input-row{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--tplane-chat-separator)}.chat-history-search-palette__icon{width:18px;height:18px;color:var(--tplane-chat-text-muted);flex-shrink:0}.chat-history-search-palette__input{flex:1 1 auto;border:0;outline:none;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:1rem}.chat-history-search-palette__input::placeholder{color:var(--tplane-chat-text-muted)}.chat-history-search-palette__close{background:transparent;border:0;padding:4px;color:var(--tplane-chat-text-muted);cursor:pointer;border-radius:4px}.chat-history-search-palette__close:hover{color:var(--tplane-chat-text)}.chat-history-search-palette__list{flex:1 1 auto;overflow-y:auto;padding:4px;margin:0;list-style:none}.chat-history-search-palette__row{display:flex;flex-direction:column;padding:10px 12px;border-radius:8px;cursor:pointer}.chat-history-search-palette__row[aria-selected=true]{background:var(--tplane-chat-surface-alt)}.chat-history-search-palette__row-title{color:var(--tplane-chat-text);font-size:var(--tplane-chat-font-size)}.chat-history-search-palette__row-subtitle{color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-sm);margin-top:2px}.chat-history-search-palette__empty,.chat-history-search-palette__hint{padding:24px 16px;color:var(--tplane-chat-text-muted);text-align:center;font-size:var(--tplane-chat-font-size-sm)}.chat-history-search-palette__skeleton{padding:8px 4px}.chat-history-search-palette__skeleton-row{height:36px;margin:4px 0;background:var(--tplane-chat-surface-alt);border-radius:8px;animation:tplane-chat-pulse 1.4s ease-in-out infinite}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3948
4616
|
}
|
|
3949
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type:
|
|
4617
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatHistorySearchPaletteComponent, decorators: [{
|
|
3950
4618
|
type: Component,
|
|
3951
|
-
args: [{ selector: 'chat-
|
|
4619
|
+
args: [{ selector: 'chat-history-search-palette', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3952
4620
|
@if (open()) {
|
|
3953
4621
|
<button
|
|
3954
4622
|
type="button"
|
|
3955
|
-
class="chat-
|
|
3956
|
-
aria-label="Close
|
|
4623
|
+
class="chat-history-search-palette__scrim"
|
|
4624
|
+
aria-label="Close search"
|
|
3957
4625
|
(click)="closed.emit()"
|
|
3958
4626
|
></button>
|
|
3959
|
-
<
|
|
3960
|
-
class="chat-
|
|
3961
|
-
role="
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
[style.left.px]="position().left"
|
|
3965
|
-
(keydown)="onMenuKeydown($event)"
|
|
4627
|
+
<div
|
|
4628
|
+
class="chat-history-search-palette"
|
|
4629
|
+
role="dialog"
|
|
4630
|
+
aria-modal="true"
|
|
4631
|
+
aria-label="Search conversations"
|
|
3966
4632
|
>
|
|
3967
|
-
|
|
3968
|
-
<
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
4633
|
+
<div class="chat-history-search-palette__input-row">
|
|
4634
|
+
<svg class="chat-history-search-palette__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4635
|
+
<circle cx="11" cy="11" r="7"/>
|
|
4636
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
4637
|
+
</svg>
|
|
4638
|
+
<input
|
|
4639
|
+
#inputEl
|
|
4640
|
+
class="chat-history-search-palette__input"
|
|
4641
|
+
type="text"
|
|
4642
|
+
role="combobox"
|
|
4643
|
+
aria-expanded="true"
|
|
4644
|
+
[attr.aria-controls]="listId"
|
|
4645
|
+
[attr.aria-activedescendant]="activeRowId()"
|
|
4646
|
+
[placeholder]="placeholder()"
|
|
4647
|
+
[value]="query()"
|
|
4648
|
+
(input)="onInput($event)"
|
|
4649
|
+
(keydown)="onInputKeydown($event)"
|
|
4650
|
+
/>
|
|
4651
|
+
<button
|
|
4652
|
+
type="button"
|
|
4653
|
+
class="chat-history-search-palette__close"
|
|
4654
|
+
aria-label="Close"
|
|
4655
|
+
(click)="closed.emit()"
|
|
3979
4656
|
>
|
|
3980
|
-
|
|
3981
|
-
|
|
4657
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4658
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
4659
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
4660
|
+
</svg>
|
|
4661
|
+
</button>
|
|
4662
|
+
</div>
|
|
4663
|
+
|
|
4664
|
+
@if (loading() && results().length === 0) {
|
|
4665
|
+
<div class="chat-history-search-palette__skeleton" aria-hidden="true">
|
|
4666
|
+
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
4667
|
+
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
4668
|
+
<div class="chat-history-search-palette__skeleton-row"></div>
|
|
4669
|
+
</div>
|
|
4670
|
+
} @else if (results().length === 0 && query().length === 0) {
|
|
4671
|
+
<div class="chat-history-search-palette__hint">Type to search your conversations.</div>
|
|
4672
|
+
} @else if (results().length === 0) {
|
|
4673
|
+
<div class="chat-history-search-palette__empty">No conversations match.</div>
|
|
4674
|
+
} @else {
|
|
4675
|
+
<ul class="chat-history-search-palette__list" role="listbox" [id]="listId">
|
|
4676
|
+
@for (row of results(); let i = $index; track row.id) {
|
|
4677
|
+
<li
|
|
4678
|
+
class="chat-history-search-palette__row"
|
|
4679
|
+
role="option"
|
|
4680
|
+
tabindex="-1"
|
|
4681
|
+
[id]="rowId(i)"
|
|
4682
|
+
[attr.aria-selected]="i === activeIndex() ? 'true' : 'false'"
|
|
4683
|
+
(click)="onRowClick(row.id)"
|
|
4684
|
+
(keydown.enter)="onRowClick(row.id)"
|
|
4685
|
+
(keydown.space)="onRowClick(row.id)"
|
|
4686
|
+
>
|
|
4687
|
+
<span class="chat-history-search-palette__row-title">{{ row.title }}</span>
|
|
4688
|
+
@if (row.subtitle) {
|
|
4689
|
+
<span class="chat-history-search-palette__row-subtitle">{{ row.subtitle }}</span>
|
|
4690
|
+
}
|
|
4691
|
+
</li>
|
|
4692
|
+
}
|
|
4693
|
+
</ul>
|
|
3982
4694
|
}
|
|
3983
|
-
</
|
|
4695
|
+
</div>
|
|
3984
4696
|
}
|
|
3985
|
-
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:contents}.chat-
|
|
3986
|
-
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }],
|
|
4697
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:contents}.chat-history-search-palette__scrim{position:fixed;inset:0;background:#0006;z-index:var(--tplane-chat-z-modal-scrim, 1100);border:0;padding:0;cursor:pointer}.chat-history-search-palette{position:fixed;top:15vh;left:50%;transform:translate(-50%);width:min(560px,90vw);max-height:70vh;background:var(--tplane-chat-bg);border:1px solid var(--tplane-chat-separator);border-radius:12px;box-shadow:0 16px 48px #00000040;z-index:var(--tplane-chat-z-modal, 1101);display:flex;flex-direction:column;overflow:hidden}.chat-history-search-palette__input-row{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--tplane-chat-separator)}.chat-history-search-palette__icon{width:18px;height:18px;color:var(--tplane-chat-text-muted);flex-shrink:0}.chat-history-search-palette__input{flex:1 1 auto;border:0;outline:none;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:1rem}.chat-history-search-palette__input::placeholder{color:var(--tplane-chat-text-muted)}.chat-history-search-palette__close{background:transparent;border:0;padding:4px;color:var(--tplane-chat-text-muted);cursor:pointer;border-radius:4px}.chat-history-search-palette__close:hover{color:var(--tplane-chat-text)}.chat-history-search-palette__list{flex:1 1 auto;overflow-y:auto;padding:4px;margin:0;list-style:none}.chat-history-search-palette__row{display:flex;flex-direction:column;padding:10px 12px;border-radius:8px;cursor:pointer}.chat-history-search-palette__row[aria-selected=true]{background:var(--tplane-chat-surface-alt)}.chat-history-search-palette__row-title{color:var(--tplane-chat-text);font-size:var(--tplane-chat-font-size)}.chat-history-search-palette__row-subtitle{color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-sm);margin-top:2px}.chat-history-search-palette__empty,.chat-history-search-palette__hint{padding:24px 16px;color:var(--tplane-chat-text-muted);text-align:center;font-size:var(--tplane-chat-font-size-sm)}.chat-history-search-palette__skeleton{padding:8px 4px}.chat-history-search-palette__skeleton-row{height:36px;margin:4px 0;background:var(--tplane-chat-surface-alt);border-radius:8px;animation:tplane-chat-pulse 1.4s ease-in-out infinite}\n"] }]
|
|
4698
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], results: [{ type: i0.Input, args: [{ isSignal: true, alias: "results", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }], inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }] } });
|
|
4699
|
+
|
|
4700
|
+
// libs/chat/src/lib/styles/chat-overflow-menu.styles.ts
|
|
4701
|
+
// SPDX-License-Identifier: MIT
|
|
4702
|
+
const CHAT_OVERFLOW_MENU_STYLES = `
|
|
4703
|
+
:host { display: contents; }
|
|
4704
|
+
.chat-overflow-menu__scrim {
|
|
4705
|
+
position: fixed;
|
|
4706
|
+
inset: 0;
|
|
4707
|
+
background: transparent;
|
|
4708
|
+
z-index: 59;
|
|
4709
|
+
border: 0;
|
|
4710
|
+
padding: 0;
|
|
4711
|
+
cursor: default;
|
|
4712
|
+
}
|
|
4713
|
+
.chat-overflow-menu {
|
|
4714
|
+
position: fixed;
|
|
4715
|
+
z-index: 60;
|
|
4716
|
+
min-width: 160px;
|
|
4717
|
+
padding: 4px;
|
|
4718
|
+
margin: 0;
|
|
4719
|
+
list-style: none;
|
|
4720
|
+
background: var(--tplane-chat-bg);
|
|
4721
|
+
border: 1px solid var(--tplane-chat-separator);
|
|
4722
|
+
border-radius: 8px;
|
|
4723
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
4724
|
+
}
|
|
4725
|
+
.chat-overflow-menu__item {
|
|
4726
|
+
display: block;
|
|
4727
|
+
padding: 8px 12px;
|
|
4728
|
+
border-radius: 4px;
|
|
4729
|
+
color: var(--tplane-chat-text);
|
|
4730
|
+
font-size: var(--tplane-chat-font-size-sm);
|
|
4731
|
+
cursor: pointer;
|
|
4732
|
+
user-select: none;
|
|
4733
|
+
}
|
|
4734
|
+
.chat-overflow-menu__item:hover {
|
|
4735
|
+
background: var(--tplane-chat-surface-alt);
|
|
4736
|
+
}
|
|
4737
|
+
.chat-overflow-menu__item:focus-visible {
|
|
4738
|
+
outline: 2px solid var(--tplane-chat-primary);
|
|
4739
|
+
outline-offset: -2px;
|
|
4740
|
+
}
|
|
4741
|
+
.chat-overflow-menu__item--destructive {
|
|
4742
|
+
color: var(--tplane-chat-error-text);
|
|
4743
|
+
}
|
|
4744
|
+
.chat-overflow-menu__item--disabled {
|
|
4745
|
+
color: var(--tplane-chat-text-muted);
|
|
4746
|
+
cursor: not-allowed;
|
|
4747
|
+
pointer-events: none;
|
|
4748
|
+
}
|
|
4749
|
+
`;
|
|
4750
|
+
|
|
4751
|
+
// libs/chat/src/lib/primitives/chat-overflow-menu/chat-overflow-menu.component.ts
|
|
4752
|
+
// SPDX-License-Identifier: MIT
|
|
4753
|
+
class ChatOverflowMenuComponent {
|
|
4754
|
+
open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
4755
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
4756
|
+
/** Element the menu anchors against (positions just below its bottom-right corner). */
|
|
4757
|
+
anchor = input(null, ...(ngDevMode ? [{ debugName: "anchor" }] : []));
|
|
4758
|
+
/** Alternative anchor: explicit viewport coordinates (e.g. cursor position
|
|
4759
|
+
* from a right-click). Takes precedence over `anchor` when set. */
|
|
4760
|
+
anchorPos = input(null, ...(ngDevMode ? [{ debugName: "anchorPos" }] : []));
|
|
4761
|
+
itemSelected = output();
|
|
4762
|
+
closed = output();
|
|
4763
|
+
position = computed(() => {
|
|
4764
|
+
if (!this.open())
|
|
4765
|
+
return { top: 0, left: 0 };
|
|
4766
|
+
const pos = this.anchorPos();
|
|
4767
|
+
if (pos) {
|
|
4768
|
+
return { top: pos.y + 4, left: Math.max(pos.x, 8) };
|
|
4769
|
+
}
|
|
4770
|
+
const el = this.anchor();
|
|
4771
|
+
if (!el) {
|
|
4772
|
+
const vw = typeof window === 'undefined' ? 0 : window.innerWidth;
|
|
4773
|
+
const vh = typeof window === 'undefined' ? 0 : window.innerHeight;
|
|
4774
|
+
return { top: Math.max(vh / 3, 0), left: Math.max(vw / 2 - 80, 0) };
|
|
4775
|
+
}
|
|
4776
|
+
const rect = el.getBoundingClientRect();
|
|
4777
|
+
return { top: rect.bottom + 4, left: Math.max(rect.right - 160, 8) };
|
|
4778
|
+
}, ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
4779
|
+
constructor() {
|
|
4780
|
+
effect(() => {
|
|
4781
|
+
if (!this.open())
|
|
4782
|
+
return;
|
|
4783
|
+
queueMicrotask(() => {
|
|
4784
|
+
const root = document.querySelector('.chat-overflow-menu');
|
|
4785
|
+
const first = root?.querySelector('.chat-overflow-menu__item:not(.chat-overflow-menu__item--disabled)');
|
|
4786
|
+
first?.focus();
|
|
4787
|
+
});
|
|
4788
|
+
});
|
|
4789
|
+
}
|
|
4790
|
+
onItemClick(item) {
|
|
4791
|
+
if (item.disabled)
|
|
4792
|
+
return;
|
|
4793
|
+
this.itemSelected.emit(item.id);
|
|
4794
|
+
this.closed.emit();
|
|
4795
|
+
}
|
|
4796
|
+
onMenuKeydown(e) {
|
|
4797
|
+
if (e.key === 'Escape') {
|
|
4798
|
+
e.preventDefault();
|
|
4799
|
+
this.closed.emit();
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4802
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
4803
|
+
e.preventDefault();
|
|
4804
|
+
const root = e.currentTarget;
|
|
4805
|
+
const items = Array.from(root.querySelectorAll('.chat-overflow-menu__item:not(.chat-overflow-menu__item--disabled)'));
|
|
4806
|
+
if (items.length === 0)
|
|
4807
|
+
return;
|
|
4808
|
+
const current = document.activeElement;
|
|
4809
|
+
const idx = current ? items.indexOf(current) : -1;
|
|
4810
|
+
const next = e.key === 'ArrowDown'
|
|
4811
|
+
? Math.min((idx < 0 ? 0 : idx + 1), items.length - 1)
|
|
4812
|
+
: Math.max(idx - 1, 0);
|
|
4813
|
+
items[next]?.focus();
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatOverflowMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4817
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatOverflowMenuComponent, isStandalone: true, selector: "chat-overflow-menu", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, anchorPos: { classPropertyName: "anchorPos", publicName: "anchorPos", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected", closed: "closed" }, ngImport: i0, template: `
|
|
4818
|
+
@if (open()) {
|
|
4819
|
+
<button
|
|
4820
|
+
type="button"
|
|
4821
|
+
class="chat-overflow-menu__scrim"
|
|
4822
|
+
aria-label="Close menu"
|
|
4823
|
+
(click)="closed.emit()"
|
|
4824
|
+
></button>
|
|
4825
|
+
<ul
|
|
4826
|
+
class="chat-overflow-menu"
|
|
4827
|
+
role="menu"
|
|
4828
|
+
tabindex="-1"
|
|
4829
|
+
[style.top.px]="position().top"
|
|
4830
|
+
[style.left.px]="position().left"
|
|
4831
|
+
(keydown)="onMenuKeydown($event)"
|
|
4832
|
+
>
|
|
4833
|
+
@for (item of items(); track item.id) {
|
|
4834
|
+
<li
|
|
4835
|
+
role="menuitem"
|
|
4836
|
+
tabindex="0"
|
|
4837
|
+
class="chat-overflow-menu__item"
|
|
4838
|
+
[class.chat-overflow-menu__item--destructive]="item.tone === 'destructive'"
|
|
4839
|
+
[class.chat-overflow-menu__item--disabled]="item.disabled"
|
|
4840
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
4841
|
+
[attr.tabindex]="item.disabled ? -1 : 0"
|
|
4842
|
+
(click)="onItemClick(item)"
|
|
4843
|
+
(keydown.enter)="onItemClick(item)"
|
|
4844
|
+
(keydown.space)="onItemClick(item)"
|
|
4845
|
+
>
|
|
4846
|
+
{{ item.label }}
|
|
4847
|
+
</li>
|
|
4848
|
+
}
|
|
4849
|
+
</ul>
|
|
4850
|
+
}
|
|
4851
|
+
`, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:contents}.chat-overflow-menu__scrim{position:fixed;inset:0;background:transparent;z-index:59;border:0;padding:0;cursor:default}.chat-overflow-menu{position:fixed;z-index:60;min-width:160px;padding:4px;margin:0;list-style:none;background:var(--tplane-chat-bg);border:1px solid var(--tplane-chat-separator);border-radius:8px;box-shadow:0 8px 24px #00000026}.chat-overflow-menu__item{display:block;padding:8px 12px;border-radius:4px;color:var(--tplane-chat-text);font-size:var(--tplane-chat-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none}.chat-overflow-menu__item:hover{background:var(--tplane-chat-surface-alt)}.chat-overflow-menu__item:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:-2px}.chat-overflow-menu__item--destructive{color:var(--tplane-chat-error-text)}.chat-overflow-menu__item--disabled{color:var(--tplane-chat-text-muted);cursor:not-allowed;pointer-events:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4852
|
+
}
|
|
4853
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatOverflowMenuComponent, decorators: [{
|
|
4854
|
+
type: Component,
|
|
4855
|
+
args: [{ selector: 'chat-overflow-menu', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4856
|
+
@if (open()) {
|
|
4857
|
+
<button
|
|
4858
|
+
type="button"
|
|
4859
|
+
class="chat-overflow-menu__scrim"
|
|
4860
|
+
aria-label="Close menu"
|
|
4861
|
+
(click)="closed.emit()"
|
|
4862
|
+
></button>
|
|
4863
|
+
<ul
|
|
4864
|
+
class="chat-overflow-menu"
|
|
4865
|
+
role="menu"
|
|
4866
|
+
tabindex="-1"
|
|
4867
|
+
[style.top.px]="position().top"
|
|
4868
|
+
[style.left.px]="position().left"
|
|
4869
|
+
(keydown)="onMenuKeydown($event)"
|
|
4870
|
+
>
|
|
4871
|
+
@for (item of items(); track item.id) {
|
|
4872
|
+
<li
|
|
4873
|
+
role="menuitem"
|
|
4874
|
+
tabindex="0"
|
|
4875
|
+
class="chat-overflow-menu__item"
|
|
4876
|
+
[class.chat-overflow-menu__item--destructive]="item.tone === 'destructive'"
|
|
4877
|
+
[class.chat-overflow-menu__item--disabled]="item.disabled"
|
|
4878
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
4879
|
+
[attr.tabindex]="item.disabled ? -1 : 0"
|
|
4880
|
+
(click)="onItemClick(item)"
|
|
4881
|
+
(keydown.enter)="onItemClick(item)"
|
|
4882
|
+
(keydown.space)="onItemClick(item)"
|
|
4883
|
+
>
|
|
4884
|
+
{{ item.label }}
|
|
4885
|
+
</li>
|
|
4886
|
+
}
|
|
4887
|
+
</ul>
|
|
4888
|
+
}
|
|
4889
|
+
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:contents}.chat-overflow-menu__scrim{position:fixed;inset:0;background:transparent;z-index:59;border:0;padding:0;cursor:default}.chat-overflow-menu{position:fixed;z-index:60;min-width:160px;padding:4px;margin:0;list-style:none;background:var(--tplane-chat-bg);border:1px solid var(--tplane-chat-separator);border-radius:8px;box-shadow:0 8px 24px #00000026}.chat-overflow-menu__item{display:block;padding:8px 12px;border-radius:4px;color:var(--tplane-chat-text);font-size:var(--tplane-chat-font-size-sm);cursor:pointer;-webkit-user-select:none;user-select:none}.chat-overflow-menu__item:hover{background:var(--tplane-chat-surface-alt)}.chat-overflow-menu__item:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:-2px}.chat-overflow-menu__item--destructive{color:var(--tplane-chat-error-text)}.chat-overflow-menu__item--disabled{color:var(--tplane-chat-text-muted);cursor:not-allowed;pointer-events:none}\n"] }]
|
|
4890
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], anchorPos: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorPos", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
3987
4891
|
|
|
3988
4892
|
// libs/chat/src/lib/styles/chat-confirm-dialog.styles.ts
|
|
3989
4893
|
// SPDX-License-Identifier: MIT
|
|
@@ -6755,283 +7659,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
6755
7659
|
`, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:inline-block}.chat-welcome-suggestion{display:inline-flex;align-items:center;gap:.5rem;padding:10px 16px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:9999px;color:var(--tplane-chat-text);font-family:inherit;font-size:var(--tplane-chat-font-size-sm);text-align:center;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .12s ease}.chat-welcome-suggestion:hover{background:var(--tplane-chat-surface-alt);border-color:var(--tplane-chat-text-muted)}.chat-welcome-suggestion:active{transform:scale(.98)}.chat-welcome-suggestion:focus-visible{outline:2px solid var(--tplane-chat-text-muted);outline-offset:2px}.chat-welcome-suggestion__label{white-space:nowrap}.chat-welcome-suggestion__chevron{display:none}\n"] }]
|
|
6756
7660
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
6757
7661
|
|
|
6758
|
-
// libs/chat/src/lib/primitives/overlay/overlay-container.ts
|
|
6759
|
-
// SPDX-License-Identifier: MIT
|
|
6760
|
-
const CONTAINER_CLASS = 'chat-overlay-container';
|
|
6761
|
-
const STYLE_ID = 'chat-overlay-structure';
|
|
6762
|
-
// Structural CSS, injected once into <head> (same pattern as ROOT_TOKEN_STYLES
|
|
6763
|
-
// in chat-tokens.ts) so consumers need not import any stylesheet.
|
|
6764
|
-
const STRUCTURE_CSS = `
|
|
6765
|
-
.${CONTAINER_CLASS} {
|
|
6766
|
-
position: fixed;
|
|
6767
|
-
inset: 0;
|
|
6768
|
-
z-index: 1000;
|
|
6769
|
-
pointer-events: none;
|
|
6770
|
-
}
|
|
6771
|
-
.chat-overlay-pane {
|
|
6772
|
-
position: absolute;
|
|
6773
|
-
pointer-events: auto;
|
|
6774
|
-
}
|
|
6775
|
-
`;
|
|
6776
|
-
/** Returns the single shared overlay container appended to <body>, creating it
|
|
6777
|
-
* (and injecting structural CSS) on first call. */
|
|
6778
|
-
function getOverlayContainer(doc) {
|
|
6779
|
-
const existing = doc.querySelector('.' + CONTAINER_CLASS);
|
|
6780
|
-
if (existing)
|
|
6781
|
-
return existing;
|
|
6782
|
-
if (!doc.getElementById(STYLE_ID)) {
|
|
6783
|
-
const style = doc.createElement('style');
|
|
6784
|
-
style.id = STYLE_ID;
|
|
6785
|
-
style.textContent = STRUCTURE_CSS;
|
|
6786
|
-
doc.head.appendChild(style);
|
|
6787
|
-
}
|
|
6788
|
-
const container = doc.createElement('div');
|
|
6789
|
-
container.className = CONTAINER_CLASS;
|
|
6790
|
-
doc.body.appendChild(container);
|
|
6791
|
-
return container;
|
|
6792
|
-
}
|
|
6793
|
-
|
|
6794
|
-
// libs/chat/src/lib/primitives/overlay/connected-position.ts
|
|
6795
|
-
// SPDX-License-Identifier: MIT
|
|
6796
|
-
//
|
|
6797
|
-
// Minimal port of CDK's FlexibleConnectedPositionStrategy fit logic
|
|
6798
|
-
// (~/repos/components/src/cdk/overlay/position/flexible-connected-position-strategy.ts):
|
|
6799
|
-
// _getOriginPoint + _getOverlayPoint + _getOverlayFit + _pushOverlayOnScreen.
|
|
6800
|
-
// Omits flexible-dimensions, grow-after-open, RTL, and virtual-keyboard handling.
|
|
6801
|
-
function originPoint(origin, pos) {
|
|
6802
|
-
const x = pos.originX === 'center' ? origin.left + origin.width / 2 : pos.originX === 'start' ? origin.left : origin.right;
|
|
6803
|
-
const y = pos.originY === 'center' ? origin.top + origin.height / 2 : pos.originY === 'top' ? origin.top : origin.bottom;
|
|
6804
|
-
return { x, y };
|
|
6805
|
-
}
|
|
6806
|
-
function overlayPoint(origin, size, pos) {
|
|
6807
|
-
let x = origin.x;
|
|
6808
|
-
if (pos.overlayX === 'center')
|
|
6809
|
-
x -= size.width / 2;
|
|
6810
|
-
else if (pos.overlayX === 'end')
|
|
6811
|
-
x -= size.width;
|
|
6812
|
-
let y = origin.y;
|
|
6813
|
-
if (pos.overlayY === 'center')
|
|
6814
|
-
y -= size.height / 2;
|
|
6815
|
-
else if (pos.overlayY === 'bottom')
|
|
6816
|
-
y -= size.height;
|
|
6817
|
-
return { x: x + (pos.offsetX ?? 0), y: y + (pos.offsetY ?? 0) };
|
|
6818
|
-
}
|
|
6819
|
-
function fitArea(point, size, viewport) {
|
|
6820
|
-
const left = point.x;
|
|
6821
|
-
const right = point.x + size.width;
|
|
6822
|
-
const top = point.y;
|
|
6823
|
-
const bottom = point.y + size.height;
|
|
6824
|
-
const visibleW = Math.max(0, Math.min(right, viewport.right) - Math.max(left, viewport.left));
|
|
6825
|
-
const visibleH = Math.max(0, Math.min(bottom, viewport.bottom) - Math.max(top, viewport.top));
|
|
6826
|
-
const fits = left >= viewport.left && right <= viewport.right && top >= viewport.top && bottom <= viewport.bottom;
|
|
6827
|
-
return { area: visibleW * visibleH, fits };
|
|
6828
|
-
}
|
|
6829
|
-
function pushOnScreen(point, size, viewport) {
|
|
6830
|
-
const maxLeft = viewport.right - size.width;
|
|
6831
|
-
const maxTop = viewport.bottom - size.height;
|
|
6832
|
-
return {
|
|
6833
|
-
x: Math.max(viewport.left, Math.min(point.x, maxLeft)),
|
|
6834
|
-
y: Math.max(viewport.top, Math.min(point.y, maxTop)),
|
|
6835
|
-
};
|
|
6836
|
-
}
|
|
6837
|
-
/** Sensible fallback when no positions are supplied: below the origin, start-aligned. */
|
|
6838
|
-
const DEFAULT_POSITION = { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' };
|
|
6839
|
-
function computeConnectedPosition(args) {
|
|
6840
|
-
const { originRect, overlaySize, viewport, positions } = args;
|
|
6841
|
-
// An empty positions array (the directive's input default) must still anchor to
|
|
6842
|
-
// the trigger rather than crash on best! / render at 0,0.
|
|
6843
|
-
const list = positions.length ? positions : [DEFAULT_POSITION];
|
|
6844
|
-
let best = null;
|
|
6845
|
-
for (const pos of list) {
|
|
6846
|
-
const point = overlayPoint(originPoint(originRect, pos), overlaySize, pos);
|
|
6847
|
-
const { area, fits } = fitArea(point, overlaySize, viewport);
|
|
6848
|
-
if (fits) {
|
|
6849
|
-
return { top: point.y, left: point.x, position: pos };
|
|
6850
|
-
}
|
|
6851
|
-
if (!best || area > best.area)
|
|
6852
|
-
best = { point, pos, area };
|
|
6853
|
-
}
|
|
6854
|
-
const pushed = pushOnScreen(best.point, overlaySize, viewport);
|
|
6855
|
-
return { top: pushed.y, left: pushed.x, position: best.pos };
|
|
6856
|
-
}
|
|
6857
|
-
function narrowViewport(win, margin) {
|
|
6858
|
-
return {
|
|
6859
|
-
left: margin,
|
|
6860
|
-
top: margin,
|
|
6861
|
-
right: win.innerWidth - margin,
|
|
6862
|
-
bottom: win.innerHeight - margin,
|
|
6863
|
-
width: win.innerWidth - 2 * margin,
|
|
6864
|
-
height: win.innerHeight - 2 * margin,
|
|
6865
|
-
};
|
|
6866
|
-
}
|
|
6867
|
-
|
|
6868
|
-
// libs/chat/src/lib/primitives/overlay/connected-overlay.directive.ts
|
|
6869
|
-
// SPDX-License-Identifier: MIT
|
|
6870
|
-
/* eslint-disable @angular-eslint/no-input-rename, @angular-eslint/no-output-rename --
|
|
6871
|
-
* The `chatOverlay*` binding aliases ARE the intended public API: they namespace
|
|
6872
|
-
* the directive's inputs/outputs under the `chatOverlay` prefix (mirroring Angular
|
|
6873
|
-
* CDK's `cdkConnectedOverlay*` convention) rather than exposing bare names like
|
|
6874
|
-
* `open`/`positions` on an `<ng-template>`. The internal property names stay
|
|
6875
|
-
* concise, so aliasing here is deliberate, not a rename to avoid. */
|
|
6876
|
-
/** Marks the anchor element a connected overlay positions against. */
|
|
6877
|
-
class ChatOverlayOriginDirective {
|
|
6878
|
-
elementRef = inject(ElementRef);
|
|
6879
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatOverlayOriginDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6880
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.6", type: ChatOverlayOriginDirective, isStandalone: true, selector: "[chatOverlayOrigin]", exportAs: ["chatOverlayOrigin"], ngImport: i0 });
|
|
6881
|
-
}
|
|
6882
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatOverlayOriginDirective, decorators: [{
|
|
6883
|
-
type: Directive,
|
|
6884
|
-
args: [{
|
|
6885
|
-
selector: '[chatOverlayOrigin]',
|
|
6886
|
-
standalone: true,
|
|
6887
|
-
exportAs: 'chatOverlayOrigin',
|
|
6888
|
-
}]
|
|
6889
|
-
}] });
|
|
6890
|
-
const VIEWPORT_MARGIN = 8;
|
|
6891
|
-
/**
|
|
6892
|
-
* Applied to an `<ng-template>`. When `chatOverlayOpen` is true, the template
|
|
6893
|
-
* content is portaled into the shared body-level overlay container and
|
|
6894
|
-
* positioned connected to `chatOverlayOrigin`, repositioning live on
|
|
6895
|
-
* scroll/resize. Closes on outside mousedown (via the `chatOverlayOutsideClick`
|
|
6896
|
-
* output) and Tab; returns focus to the origin when focus was inside the pane.
|
|
6897
|
-
*
|
|
6898
|
-
* NOTE: the directive does not own the open state — it only emits. To fully
|
|
6899
|
-
* close the overlay, consumers MUST handle BOTH `(chatOverlayOutsideClick)` and
|
|
6900
|
-
* `(chatOverlayDetach)`. Tab-close (and Escape, when the consumer routes it)
|
|
6901
|
-
* surface through `chatOverlayDetach`, so wiring only `chatOverlayOutsideClick`
|
|
6902
|
-
* leaves the overlay stuck open on Tab.
|
|
6903
|
-
*/
|
|
6904
|
-
class ChatConnectedOverlayDirective {
|
|
6905
|
-
origin = input.required({ ...(ngDevMode ? { debugName: "origin" } : {}), alias: 'chatOverlayOrigin' });
|
|
6906
|
-
open = input(false, { ...(ngDevMode ? { debugName: "open" } : {}), alias: 'chatOverlayOpen' });
|
|
6907
|
-
positions = input([], { ...(ngDevMode ? { debugName: "positions" } : {}), alias: 'chatOverlayPositions' });
|
|
6908
|
-
panelClass = input('', { ...(ngDevMode ? { debugName: "panelClass" } : {}), alias: 'chatOverlayPanelClass' });
|
|
6909
|
-
/** Emits the pane element once attached (consumers focus content from it). */
|
|
6910
|
-
attached = output({ alias: 'chatOverlayAttached' });
|
|
6911
|
-
outsideClick = output({ alias: 'chatOverlayOutsideClick' });
|
|
6912
|
-
detached = output({ alias: 'chatOverlayDetach' });
|
|
6913
|
-
templateRef = inject((TemplateRef));
|
|
6914
|
-
viewContainerRef = inject(ViewContainerRef);
|
|
6915
|
-
document = inject(DOCUMENT);
|
|
6916
|
-
pane = null;
|
|
6917
|
-
viewRef = null;
|
|
6918
|
-
resizeObs = null;
|
|
6919
|
-
rafId = 0;
|
|
6920
|
-
previouslyFocused = null;
|
|
6921
|
-
onScrollOrResize = () => this.scheduleReposition();
|
|
6922
|
-
onDocMouseDown = (e) => {
|
|
6923
|
-
if (!this.pane)
|
|
6924
|
-
return;
|
|
6925
|
-
const path = e.composedPath();
|
|
6926
|
-
if (path.includes(this.pane) || path.includes(this.origin().elementRef.nativeElement))
|
|
6927
|
-
return;
|
|
6928
|
-
this.outsideClick.emit(e);
|
|
6929
|
-
};
|
|
6930
|
-
onKeydown = (e) => {
|
|
6931
|
-
if (e.key !== 'Tab' || !this.pane)
|
|
6932
|
-
return;
|
|
6933
|
-
const active = this.document.activeElement;
|
|
6934
|
-
if (this.pane.contains(active) || active === this.origin().elementRef.nativeElement) {
|
|
6935
|
-
this.detached.emit();
|
|
6936
|
-
}
|
|
6937
|
-
};
|
|
6938
|
-
constructor() {
|
|
6939
|
-
effect(() => {
|
|
6940
|
-
if (this.open())
|
|
6941
|
-
this.attach();
|
|
6942
|
-
else
|
|
6943
|
-
this.dispose();
|
|
6944
|
-
});
|
|
6945
|
-
inject(DestroyRef).onDestroy(() => this.dispose());
|
|
6946
|
-
}
|
|
6947
|
-
attach() {
|
|
6948
|
-
if (this.pane)
|
|
6949
|
-
return;
|
|
6950
|
-
const win = this.document.defaultView;
|
|
6951
|
-
if (!win)
|
|
6952
|
-
return; // SSR / detached document
|
|
6953
|
-
this.previouslyFocused = this.document.activeElement;
|
|
6954
|
-
const pane = this.document.createElement('div');
|
|
6955
|
-
pane.className = 'chat-overlay-pane';
|
|
6956
|
-
for (const c of this.normalizePanelClass())
|
|
6957
|
-
pane.classList.add(c);
|
|
6958
|
-
getOverlayContainer(this.document).appendChild(pane);
|
|
6959
|
-
this.viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef);
|
|
6960
|
-
this.viewRef.detectChanges();
|
|
6961
|
-
for (const node of this.viewRef.rootNodes)
|
|
6962
|
-
pane.appendChild(node);
|
|
6963
|
-
this.pane = pane;
|
|
6964
|
-
this.reposition();
|
|
6965
|
-
win.addEventListener('scroll', this.onScrollOrResize, { capture: true, passive: true });
|
|
6966
|
-
win.addEventListener('resize', this.onScrollOrResize, { passive: true });
|
|
6967
|
-
this.document.addEventListener('mousedown', this.onDocMouseDown, true);
|
|
6968
|
-
this.document.addEventListener('keydown', this.onKeydown, true);
|
|
6969
|
-
if (typeof win.ResizeObserver === 'function') {
|
|
6970
|
-
this.resizeObs = new win.ResizeObserver(() => this.scheduleReposition());
|
|
6971
|
-
this.resizeObs.observe(this.origin().elementRef.nativeElement);
|
|
6972
|
-
this.resizeObs.observe(pane);
|
|
6973
|
-
}
|
|
6974
|
-
this.attached.emit(pane);
|
|
6975
|
-
}
|
|
6976
|
-
scheduleReposition() {
|
|
6977
|
-
const win = this.document.defaultView;
|
|
6978
|
-
if (!win || !this.pane)
|
|
6979
|
-
return;
|
|
6980
|
-
if (this.rafId)
|
|
6981
|
-
win.cancelAnimationFrame(this.rafId);
|
|
6982
|
-
this.rafId = win.requestAnimationFrame(() => this.reposition());
|
|
6983
|
-
}
|
|
6984
|
-
reposition() {
|
|
6985
|
-
const win = this.document.defaultView;
|
|
6986
|
-
if (!win || !this.pane)
|
|
6987
|
-
return;
|
|
6988
|
-
const r = this.pane.getBoundingClientRect();
|
|
6989
|
-
const result = computeConnectedPosition({
|
|
6990
|
-
originRect: this.origin().elementRef.nativeElement.getBoundingClientRect(),
|
|
6991
|
-
overlaySize: { width: r.width, height: r.height },
|
|
6992
|
-
viewport: narrowViewport(win, VIEWPORT_MARGIN),
|
|
6993
|
-
positions: this.positions(),
|
|
6994
|
-
});
|
|
6995
|
-
this.pane.style.top = `${Math.round(result.top)}px`;
|
|
6996
|
-
this.pane.style.left = `${Math.round(result.left)}px`;
|
|
6997
|
-
}
|
|
6998
|
-
dispose() {
|
|
6999
|
-
const win = this.document.defaultView;
|
|
7000
|
-
if (this.rafId && win)
|
|
7001
|
-
win.cancelAnimationFrame(this.rafId);
|
|
7002
|
-
this.rafId = 0;
|
|
7003
|
-
if (win) {
|
|
7004
|
-
win.removeEventListener('scroll', this.onScrollOrResize, { capture: true });
|
|
7005
|
-
win.removeEventListener('resize', this.onScrollOrResize);
|
|
7006
|
-
}
|
|
7007
|
-
this.document.removeEventListener('mousedown', this.onDocMouseDown, true);
|
|
7008
|
-
this.document.removeEventListener('keydown', this.onKeydown, true);
|
|
7009
|
-
this.resizeObs?.disconnect();
|
|
7010
|
-
this.resizeObs = null;
|
|
7011
|
-
const focusWasInPane = !!this.pane && this.pane.contains(this.document.activeElement);
|
|
7012
|
-
this.viewRef?.destroy();
|
|
7013
|
-
this.viewRef = null;
|
|
7014
|
-
this.pane?.remove();
|
|
7015
|
-
this.pane = null;
|
|
7016
|
-
if (focusWasInPane && this.previouslyFocused)
|
|
7017
|
-
this.previouslyFocused.focus();
|
|
7018
|
-
this.previouslyFocused = null;
|
|
7019
|
-
}
|
|
7020
|
-
normalizePanelClass() {
|
|
7021
|
-
const pc = this.panelClass();
|
|
7022
|
-
return Array.isArray(pc) ? pc : pc ? [pc] : [];
|
|
7023
|
-
}
|
|
7024
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatConnectedOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
7025
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: ChatConnectedOverlayDirective, isStandalone: true, selector: "[chatConnectedOverlay]", inputs: { origin: { classPropertyName: "origin", publicName: "chatOverlayOrigin", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "chatOverlayOpen", isSignal: true, isRequired: false, transformFunction: null }, positions: { classPropertyName: "positions", publicName: "chatOverlayPositions", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "chatOverlayPanelClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { attached: "chatOverlayAttached", outsideClick: "chatOverlayOutsideClick", detached: "chatOverlayDetach" }, ngImport: i0 });
|
|
7026
|
-
}
|
|
7027
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatConnectedOverlayDirective, decorators: [{
|
|
7028
|
-
type: Directive,
|
|
7029
|
-
args: [{
|
|
7030
|
-
selector: '[chatConnectedOverlay]',
|
|
7031
|
-
standalone: true,
|
|
7032
|
-
}]
|
|
7033
|
-
}], ctorParameters: () => [], propDecorators: { origin: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayOrigin", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayOpen", required: false }] }], positions: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayPositions", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "chatOverlayPanelClass", required: false }] }], attached: [{ type: i0.Output, args: ["chatOverlayAttached"] }], outsideClick: [{ type: i0.Output, args: ["chatOverlayOutsideClick"] }], detached: [{ type: i0.Output, args: ["chatOverlayDetach"] }] } });
|
|
7034
|
-
|
|
7035
7662
|
// libs/chat/src/lib/styles/chat-select.styles.ts
|
|
7036
7663
|
// SPDX-License-Identifier: MIT
|
|
7037
7664
|
const CHAT_SELECT_STYLES = `
|
|
@@ -9387,8 +10014,8 @@ class ChatComponent {
|
|
|
9387
10014
|
*
|
|
9388
10015
|
* Matches the same `streaming + current` condition the bubble uses
|
|
9389
10016
|
* to enable `.chat-message__caret`:
|
|
9390
|
-
* `agent().isLoading() && i === agent().messages().length - 1`
|
|
9391
|
-
* `i === agent().messages().length - 1`
|
|
10017
|
+
* `this.agent().isLoading() && i === this.agent().messages().length - 1`
|
|
10018
|
+
* `i === this.agent().messages().length - 1`
|
|
9392
10019
|
*
|
|
9393
10020
|
* Restricted to assistant role because the caret only renders on
|
|
9394
10021
|
* assistant bubbles (`:host([data-role="assistant"][data-current=...
|
|
@@ -13158,5 +13785,5 @@ function mockAgent(opts = {}) {
|
|
|
13158
13785
|
* Generated bundle index. Do not edit.
|
|
13159
13786
|
*/
|
|
13160
13787
|
|
|
13161
|
-
export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, AGENT_ERROR_MESSAGES, AgentError, CHAT_CONFIG, CHAT_LIFECYCLE, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatConnectedOverlayDirective, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatOverlayOriginDirective, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownHtmlComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownMathComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, createA2uiSurfaceStore, createAgentRef, createContentClassifier, createParseTreeStore, createPartialArgsBridge, deriveJsonSchema, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, getInterrupt, getMessageType, injectThreadRouting, isAbortError, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, startClientToolExecutor, statusColor, submitMessage, toAgentError, toClientToolSpecs, tools, validateArgs, view };
|
|
13788
|
+
export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, AGENT_ERROR_MESSAGES, AgentError, CHAT_CONFIG, CHAT_LIFECYCLE, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationPreviewComponent, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatConnectedOverlayDirective, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatOverlayOriginDirective, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownHtmlComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownMathComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, citationTypeLabel, createA2uiSurfaceStore, createAgentRef, createContentClassifier, createParseTreeStore, createPartialArgsBridge, deriveDomain, deriveJsonSchema, deriveMonogram, deriveSourceType, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, formatPublished, getInterrupt, getMessageType, injectThreadRouting, isAbortError, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, monogramColor, monogramHue, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, startClientToolExecutor, statusColor, submitMessage, toAgentError, toClientToolSpecs, tools, validateArgs, view };
|
|
13162
13789
|
//# sourceMappingURL=threadplane-chat.mjs.map
|