@threadplane/chat 0.0.51 → 0.0.53
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 +22 -0
- package/fesm2022/threadplane-chat.mjs +561 -158
- package/fesm2022/threadplane-chat.mjs.map +1 -1
- package/package.json +9 -3
- package/types/threadplane-chat.d.ts +208 -23
|
@@ -4,6 +4,7 @@ import { NgTemplateOutlet, NgComponentOutlet, KeyValuePipe } from '@angular/comm
|
|
|
4
4
|
import { createPartialMarkdownParser, materialize } from '@cacheplane/partial-markdown';
|
|
5
5
|
import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
|
|
6
6
|
export { toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
|
|
7
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
7
8
|
import { runLicenseCheck, inferNoncommercial, LICENSE_PUBLIC_KEY } from '@threadplane/licensing';
|
|
8
9
|
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
9
10
|
import { Router, NavigationEnd } from '@angular/router';
|
|
@@ -174,15 +175,55 @@ function toAgentError(raw) {
|
|
|
174
175
|
return make('server', true, raw, undefined, msg);
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Type guard narrowing a {@link Message} to `role: 'user'`.
|
|
180
|
+
*
|
|
181
|
+
* @param m The message to test.
|
|
182
|
+
* @returns `true` (and narrows `m`) when the message was sent by the user.
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const userTurns = agent.messages().filter(isUserMessage);
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
177
188
|
function isUserMessage(m) {
|
|
178
189
|
return m.role === 'user';
|
|
179
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Type guard narrowing a {@link Message} to `role: 'assistant'`.
|
|
193
|
+
*
|
|
194
|
+
* @param m The message to test.
|
|
195
|
+
* @returns `true` (and narrows `m`) when the message came from the assistant.
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const reply = agent.messages().findLast(isAssistantMessage);
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
180
201
|
function isAssistantMessage(m) {
|
|
181
202
|
return m.role === 'assistant';
|
|
182
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Type guard narrowing a {@link Message} to `role: 'tool'` (a tool result turn).
|
|
206
|
+
*
|
|
207
|
+
* @param m The message to test.
|
|
208
|
+
* @returns `true` (and narrows `m`) when the message is a tool result.
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* if (isToolMessage(m)) console.log(m.toolCallId);
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
183
214
|
function isToolMessage(m) {
|
|
184
215
|
return m.role === 'tool';
|
|
185
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Type guard narrowing a {@link Message} to `role: 'system'`.
|
|
219
|
+
*
|
|
220
|
+
* @param m The message to test.
|
|
221
|
+
* @returns `true` (and narrows `m`) when the message is a system message.
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const visible = agent.messages().filter((m) => !isSystemMessage(m));
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
186
227
|
function isSystemMessage(m) {
|
|
187
228
|
return m.role === 'system';
|
|
188
229
|
}
|
|
@@ -1754,6 +1795,11 @@ const CHAT_MARKDOWN_STYLES = `
|
|
|
1754
1795
|
}
|
|
1755
1796
|
chat-streaming-md .chat-md-image__icon { font-size: 1em; line-height: 1; }
|
|
1756
1797
|
chat-streaming-md .chat-md-image__alt { font-style: italic; }
|
|
1798
|
+
/* Math (KaTeX). Display math is a centered block that scrolls horizontally
|
|
1799
|
+
on overflow; the raw fallback (KaTeX missing or invalid LaTeX) reads as
|
|
1800
|
+
monospace so the source delimiters look intentional. */
|
|
1801
|
+
chat-streaming-md .chat-md-math--display { display: block; margin: 0.5em 0; overflow-x: auto; }
|
|
1802
|
+
chat-streaming-md .chat-md-math--raw { font-family: var(--ngaf-chat-font-mono, ui-monospace, monospace); }
|
|
1757
1803
|
`;
|
|
1758
1804
|
|
|
1759
1805
|
// libs/chat/src/lib/markdown/markdown-view-registry.ts
|
|
@@ -2103,6 +2149,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
2103
2149
|
}]
|
|
2104
2150
|
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2105
2151
|
|
|
2152
|
+
// libs/chat/src/lib/markdown/katex-loader.ts
|
|
2153
|
+
// SPDX-License-Identifier: MIT
|
|
2154
|
+
/**
|
|
2155
|
+
* Lazy KaTeX integration for the markdown math view. Mirrors the lazy `marked`
|
|
2156
|
+
* loader in ../streaming/markdown-render.ts: `katex` is an optional peer
|
|
2157
|
+
* dependency, dynamically imported on module load, so chats that never contain
|
|
2158
|
+
* math pay zero base-bundle cost. `renderMath` returns the KaTeX HTML string,
|
|
2159
|
+
* or null when KaTeX is unavailable or the LaTeX is invalid — the view then
|
|
2160
|
+
* renders the raw `$…$` source instead.
|
|
2161
|
+
*
|
|
2162
|
+
* CSS: the KaTeX stylesheet (and its woff2 fonts) is the CONSUMER's
|
|
2163
|
+
* responsibility — this lib injects nothing at runtime (no third-party CDN
|
|
2164
|
+
* fetch). Apps import `katex/dist/katex.min.css` themselves; math still renders
|
|
2165
|
+
* without it, just unstyled.
|
|
2166
|
+
*/
|
|
2167
|
+
let katexRender = null;
|
|
2168
|
+
/**
|
|
2169
|
+
* Flips to true once KaTeX has loaded. The math view reads it as a signal
|
|
2170
|
+
* dependency so any math that fell back to raw source while KaTeX was still
|
|
2171
|
+
* loading re-renders once it becomes available.
|
|
2172
|
+
*/
|
|
2173
|
+
const katexReady = signal(false, ...(ngDevMode ? [{ debugName: "katexReady" }] : []));
|
|
2174
|
+
/** Resolves once the KaTeX import has settled (success or failure). Tests await this. */
|
|
2175
|
+
const katexLoaded = import('katex')
|
|
2176
|
+
.then((m) => {
|
|
2177
|
+
const katex = (m.default ?? m);
|
|
2178
|
+
katexRender = (latex, displayMode) =>
|
|
2179
|
+
// throwOnError:true so invalid LaTeX throws → we catch → raw-source
|
|
2180
|
+
// fallback, instead of KaTeX emitting its own red error markup.
|
|
2181
|
+
katex.renderToString(latex, { displayMode, throwOnError: true });
|
|
2182
|
+
katexReady.set(true);
|
|
2183
|
+
})
|
|
2184
|
+
.catch(() => {
|
|
2185
|
+
katexRender = null;
|
|
2186
|
+
});
|
|
2187
|
+
/**
|
|
2188
|
+
* Render LaTeX to a KaTeX HTML string, or null if KaTeX is unavailable or the
|
|
2189
|
+
* input throws (invalid LaTeX) — the caller then renders the raw `$…$` source.
|
|
2190
|
+
*/
|
|
2191
|
+
function renderMath(latex, displayMode) {
|
|
2192
|
+
if (!katexRender)
|
|
2193
|
+
return null;
|
|
2194
|
+
try {
|
|
2195
|
+
return katexRender(latex, displayMode);
|
|
2196
|
+
}
|
|
2197
|
+
catch {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
// libs/chat/src/lib/markdown/views/markdown-math.component.ts
|
|
2203
|
+
// SPDX-License-Identifier: MIT
|
|
2204
|
+
/** Opener/closer source text per delimiter, used for the raw fallback. */
|
|
2205
|
+
const DELIMITERS = {
|
|
2206
|
+
$: ['$', '$'],
|
|
2207
|
+
'$$': ['$$', '$$'],
|
|
2208
|
+
'\\(\\)': ['\\(', '\\)'],
|
|
2209
|
+
'\\[\\]': ['\\[', '\\]'],
|
|
2210
|
+
};
|
|
2211
|
+
/**
|
|
2212
|
+
* Renders a `math-inline` / `math-display` markdown node as KaTeX. KaTeX is
|
|
2213
|
+
* lazy-loaded (see katex-loader); until it resolves, or if the LaTeX is
|
|
2214
|
+
* invalid, the raw `$…$` source is shown — never blank, never a crash.
|
|
2215
|
+
*/
|
|
2216
|
+
class MarkdownMathComponent {
|
|
2217
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
2218
|
+
sanitizer = inject(DomSanitizer);
|
|
2219
|
+
display = computed(() => this.node().type === 'math-display', ...(ngDevMode ? [{ debugName: "display" }] : []));
|
|
2220
|
+
raw = computed(() => {
|
|
2221
|
+
const n = this.node();
|
|
2222
|
+
const [open, close] = DELIMITERS[n.delimiter];
|
|
2223
|
+
return `${open}${n.text}${close}`;
|
|
2224
|
+
}, ...(ngDevMode ? [{ debugName: "raw" }] : []));
|
|
2225
|
+
html = computed(() => {
|
|
2226
|
+
katexReady(); // re-render once KaTeX finishes loading
|
|
2227
|
+
const n = this.node();
|
|
2228
|
+
const out = renderMath(n.text, n.type === 'math-display');
|
|
2229
|
+
if (out == null)
|
|
2230
|
+
return null;
|
|
2231
|
+
// Trust KaTeX output directly: with KaTeX's default `trust:false` it emits
|
|
2232
|
+
// only safe presentational markup (no scripts/event handlers/links), and
|
|
2233
|
+
// Angular's HTML sanitizer would strip the inline styles KaTeX layout
|
|
2234
|
+
// depends on.
|
|
2235
|
+
return this.sanitizer.bypassSecurityTrustHtml(out);
|
|
2236
|
+
}, ...(ngDevMode ? [{ debugName: "html" }] : []));
|
|
2237
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownMathComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2238
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MarkdownMathComponent, isStandalone: true, selector: "chat-md-math", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
2239
|
+
@if (html(); as h) {
|
|
2240
|
+
<span
|
|
2241
|
+
class="chat-md-math"
|
|
2242
|
+
[class.chat-md-math--display]="display()"
|
|
2243
|
+
[innerHTML]="h"
|
|
2244
|
+
></span>
|
|
2245
|
+
} @else {
|
|
2246
|
+
<span class="chat-md-math chat-md-math--raw">{{ raw() }}</span>
|
|
2247
|
+
}
|
|
2248
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2249
|
+
}
|
|
2250
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownMathComponent, decorators: [{
|
|
2251
|
+
type: Component,
|
|
2252
|
+
args: [{
|
|
2253
|
+
selector: 'chat-md-math',
|
|
2254
|
+
standalone: true,
|
|
2255
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2256
|
+
encapsulation: ViewEncapsulation.None,
|
|
2257
|
+
template: `
|
|
2258
|
+
@if (html(); as h) {
|
|
2259
|
+
<span
|
|
2260
|
+
class="chat-md-math"
|
|
2261
|
+
[class.chat-md-math--display]="display()"
|
|
2262
|
+
[innerHTML]="h"
|
|
2263
|
+
></span>
|
|
2264
|
+
} @else {
|
|
2265
|
+
<span class="chat-md-math chat-md-math--raw">{{ raw() }}</span>
|
|
2266
|
+
}
|
|
2267
|
+
`,
|
|
2268
|
+
}]
|
|
2269
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2270
|
+
|
|
2106
2271
|
// libs/chat/src/lib/markdown/views/markdown-link.component.ts
|
|
2107
2272
|
// SPDX-License-Identifier: MIT
|
|
2108
2273
|
class MarkdownLinkComponent {
|
|
@@ -2444,6 +2609,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
2444
2609
|
}]
|
|
2445
2610
|
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2446
2611
|
|
|
2612
|
+
// libs/chat/src/lib/markdown/views/markdown-html.component.ts
|
|
2613
|
+
// SPDX-License-Identifier: MIT
|
|
2614
|
+
/**
|
|
2615
|
+
* Renders a `html-block` / `html-inline` markdown node as **escaped text** —
|
|
2616
|
+
* the raw HTML is shown literally (Angular interpolation auto-escapes it),
|
|
2617
|
+
* never injected as live markup. This preserves the pre-0.4 behavior where
|
|
2618
|
+
* raw HTML was plain text, and keeps the chat XSS-safe: model-emitted
|
|
2619
|
+
* `<script>`, `<iframe>`, etc. are displayed as text and never executed.
|
|
2620
|
+
*/
|
|
2621
|
+
class MarkdownHtmlComponent {
|
|
2622
|
+
node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
|
|
2623
|
+
raw = computed(() => this.node().raw, ...(ngDevMode ? [{ debugName: "raw" }] : []));
|
|
2624
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownHtmlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2625
|
+
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 });
|
|
2626
|
+
}
|
|
2627
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MarkdownHtmlComponent, decorators: [{
|
|
2628
|
+
type: Component,
|
|
2629
|
+
args: [{
|
|
2630
|
+
selector: 'chat-md-html',
|
|
2631
|
+
standalone: true,
|
|
2632
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2633
|
+
template: `{{ raw() }}`,
|
|
2634
|
+
}]
|
|
2635
|
+
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
2636
|
+
|
|
2447
2637
|
// libs/chat/src/lib/markdown/cacheplane-markdown-views.ts
|
|
2448
2638
|
// SPDX-License-Identifier: MIT
|
|
2449
2639
|
/**
|
|
@@ -2467,6 +2657,8 @@ const cacheplaneMarkdownViews = views({
|
|
|
2467
2657
|
'strong': MarkdownStrongComponent,
|
|
2468
2658
|
'strikethrough': MarkdownStrikethroughComponent,
|
|
2469
2659
|
'inline-code': MarkdownInlineCodeComponent,
|
|
2660
|
+
'math-inline': MarkdownMathComponent,
|
|
2661
|
+
'math-display': MarkdownMathComponent,
|
|
2470
2662
|
'link': MarkdownLinkComponent,
|
|
2471
2663
|
'autolink': MarkdownAutolinkComponent,
|
|
2472
2664
|
'image': MarkdownImageComponent,
|
|
@@ -2476,6 +2668,10 @@ const cacheplaneMarkdownViews = views({
|
|
|
2476
2668
|
'table': MarkdownTableComponent,
|
|
2477
2669
|
'table-row': MarkdownTableRowComponent,
|
|
2478
2670
|
'table-cell': MarkdownTableCellComponent,
|
|
2671
|
+
// Raw HTML (added by partial-markdown 0.4.x) renders as escaped literal text
|
|
2672
|
+
// — XSS-safe, and matches the pre-0.4 behavior where HTML was plain text.
|
|
2673
|
+
'html-block': MarkdownHtmlComponent,
|
|
2674
|
+
'html-inline': MarkdownHtmlComponent,
|
|
2479
2675
|
});
|
|
2480
2676
|
|
|
2481
2677
|
// libs/chat/src/lib/streaming/streaming-markdown.component.ts
|
|
@@ -2569,7 +2765,7 @@ class ChatStreamingMdComponent {
|
|
|
2569
2765
|
@if (root(); as r) {
|
|
2570
2766
|
<chat-md-children [parent]="r" />
|
|
2571
2767
|
}
|
|
2572
|
-
`, isInline: true, styles: ["chat-streaming-md{display:block;color:var(--ngaf-chat-text);line-height:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md mark{background:var(--ngaf-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(--ngaf-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(--ngaf-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(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--ngaf-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:12px 14px;border-radius:var(--ngaf-chat-radius-card);overflow-x:auto;font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-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(--ngaf-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--ngaf-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--ngaf-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(--ngaf-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--ngaf-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(--ngaf-chat-surface-alt);border:1px dashed var(--ngaf-chat-separator);border-radius:6px;font-size:.9em;color:var(--ngaf-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}\n"], dependencies: [{ kind: "component", type: MarkdownChildrenComponent, selector: "chat-md-children", inputs: ["parent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2768
|
+
`, isInline: true, styles: ["chat-streaming-md{display:block;color:var(--ngaf-chat-text);line-height:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md mark{background:var(--ngaf-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(--ngaf-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(--ngaf-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(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--ngaf-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:12px 14px;border-radius:var(--ngaf-chat-radius-card);overflow-x:auto;font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-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(--ngaf-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--ngaf-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--ngaf-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(--ngaf-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--ngaf-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(--ngaf-chat-surface-alt);border:1px dashed var(--ngaf-chat-separator);border-radius:6px;font-size:.9em;color:var(--ngaf-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(--ngaf-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 });
|
|
2573
2769
|
}
|
|
2574
2770
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatStreamingMdComponent, decorators: [{
|
|
2575
2771
|
type: Component,
|
|
@@ -2583,7 +2779,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
2583
2779
|
useFactory: (host) => host.resolvedRegistry(),
|
|
2584
2780
|
deps: [ChatStreamingMdComponent],
|
|
2585
2781
|
},
|
|
2586
|
-
], styles: ["chat-streaming-md{display:block;color:var(--ngaf-chat-text);line-height:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md mark{background:var(--ngaf-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(--ngaf-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(--ngaf-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(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--ngaf-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:12px 14px;border-radius:var(--ngaf-chat-radius-card);overflow-x:auto;font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-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(--ngaf-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--ngaf-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--ngaf-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(--ngaf-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--ngaf-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(--ngaf-chat-surface-alt);border:1px dashed var(--ngaf-chat-separator);border-radius:6px;font-size:.9em;color:var(--ngaf-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}\n"] }]
|
|
2782
|
+
], styles: ["chat-streaming-md{display:block;color:var(--ngaf-chat-text);line-height:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--ngaf-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(--ngaf-chat-text-muted)}chat-streaming-md mark{background:var(--ngaf-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(--ngaf-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(--ngaf-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(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--ngaf-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text);padding:12px 14px;border-radius:var(--ngaf-chat-radius-card);overflow-x:auto;font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-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(--ngaf-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--ngaf-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--ngaf-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(--ngaf-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--ngaf-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(--ngaf-chat-surface-alt);border:1px dashed var(--ngaf-chat-separator);border-radius:6px;font-size:.9em;color:var(--ngaf-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(--ngaf-chat-font-mono, ui-monospace, monospace)}\n"] }]
|
|
2587
2783
|
}], 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 }] }] } });
|
|
2588
2784
|
|
|
2589
2785
|
// SPDX-License-Identifier: MIT
|
|
@@ -3148,6 +3344,18 @@ const CHAT_TYPING_INDICATOR_STYLES = `
|
|
|
3148
3344
|
|
|
3149
3345
|
// libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
|
|
3150
3346
|
// SPDX-License-Identifier: MIT
|
|
3347
|
+
/**
|
|
3348
|
+
* Whether the agent should show a "typing" indicator — it is loading and has
|
|
3349
|
+
* not yet started streaming the assistant's reply.
|
|
3350
|
+
*
|
|
3351
|
+
* @param agent The agent to inspect.
|
|
3352
|
+
* @returns `true` while the agent is awaiting a response but no assistant text
|
|
3353
|
+
* has streamed yet; `false` once tokens arrive or the agent is idle.
|
|
3354
|
+
* @example
|
|
3355
|
+
* ```ts
|
|
3356
|
+
* \@if (isTyping(agent)) { <chat-typing-indicator [agent]="agent" /> }
|
|
3357
|
+
* ```
|
|
3358
|
+
*/
|
|
3151
3359
|
function isTyping(agent) {
|
|
3152
3360
|
if (!agent.isLoading())
|
|
3153
3361
|
return false;
|
|
@@ -4056,6 +4264,19 @@ const CHAT_ERROR_STYLES = `
|
|
|
4056
4264
|
|
|
4057
4265
|
// libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
|
|
4058
4266
|
// SPDX-License-Identifier: MIT
|
|
4267
|
+
/**
|
|
4268
|
+
* Coerce an unknown error value into a human-readable message string — reads
|
|
4269
|
+
* `.message` from `Error`s, returns strings as-is, and `String()`-casts the
|
|
4270
|
+
* rest. Useful when rendering an agent's `error` outside the built-in
|
|
4271
|
+
* `chat-error` component.
|
|
4272
|
+
*
|
|
4273
|
+
* @param error Any caught/agent error value.
|
|
4274
|
+
* @returns The message text, or `null` when `error` is nullish.
|
|
4275
|
+
* @example
|
|
4276
|
+
* ```ts
|
|
4277
|
+
* const msg = extractErrorMessage(agent.error());
|
|
4278
|
+
* ```
|
|
4279
|
+
*/
|
|
4059
4280
|
function extractErrorMessage(error) {
|
|
4060
4281
|
if (!error)
|
|
4061
4282
|
return null;
|
|
@@ -4118,6 +4339,18 @@ const CHAT_INTERRUPT_STYLES = `
|
|
|
4118
4339
|
|
|
4119
4340
|
// libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
|
|
4120
4341
|
// SPDX-License-Identifier: MIT
|
|
4342
|
+
/**
|
|
4343
|
+
* Read the agent's current human-in-the-loop interrupt, if any.
|
|
4344
|
+
*
|
|
4345
|
+
* @param agent The agent to inspect.
|
|
4346
|
+
* @returns The pending {@link AgentInterrupt}, or `undefined` when the agent is
|
|
4347
|
+
* not currently waiting on an interrupt.
|
|
4348
|
+
* @example
|
|
4349
|
+
* ```ts
|
|
4350
|
+
* const interrupt = getInterrupt(agent);
|
|
4351
|
+
* if (interrupt) agent.resume('approved');
|
|
4352
|
+
* ```
|
|
4353
|
+
*/
|
|
4121
4354
|
function getInterrupt(agent) {
|
|
4122
4355
|
return agent.interrupt?.();
|
|
4123
4356
|
}
|
|
@@ -4298,6 +4531,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
4298
4531
|
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.tcc__name{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-sm, 13px);color:var(--ngaf-chat-text-muted);font-weight:400;padding-left:2px}.tcc__pill{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:9999px;background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted);font-size:10px;font-weight:500;margin-left:6px;line-height:1.4}.tcc__pill svg{width:10px;height:10px}.tcc__pill[data-status=running] svg{animation:tcc-spin .8s linear infinite}@keyframes tcc-spin{to{transform:rotate(360deg)}}.tcc__section{padding:8px 0}.tcc__section+.tcc__section{border-top:1px solid var(--ngaf-chat-separator)}.tcc__section-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ngaf-chat-text-muted);margin:0 0 4px}.tcc__section-body{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text);white-space:pre-wrap;overflow-x:auto;margin:0}\n"] }]
|
|
4299
4532
|
}], propDecorators: { toolCall: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolCall", required: true }] }], defaultCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCollapsed", required: false }] }] } });
|
|
4300
4533
|
|
|
4534
|
+
// libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
|
|
4535
|
+
// SPDX-License-Identifier: MIT
|
|
4536
|
+
/**
|
|
4537
|
+
* Returns a CSS style string for a subagent's status badge.
|
|
4538
|
+
* Kept exported for backward compatibility with existing consumers; the
|
|
4539
|
+
* preferred way to style status visually is via the `data-status` attribute
|
|
4540
|
+
* + CSS selectors (see component styles below).
|
|
4541
|
+
*/
|
|
4542
|
+
function statusColor(status) {
|
|
4543
|
+
switch (status) {
|
|
4544
|
+
case 'pending': return 'background: var(--ngaf-chat-surface-alt); color: var(--ngaf-chat-text-muted);';
|
|
4545
|
+
case 'running': return 'background: var(--ngaf-chat-warning-bg); color: var(--ngaf-chat-warning-text);';
|
|
4546
|
+
case 'complete': return 'color: var(--ngaf-chat-success);';
|
|
4547
|
+
case 'error': return 'background: var(--ngaf-chat-error-bg); color: var(--ngaf-chat-error-text);';
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
function statusToTraceState(s) {
|
|
4551
|
+
switch (s) {
|
|
4552
|
+
case 'pending': return 'pending';
|
|
4553
|
+
case 'running': return 'running';
|
|
4554
|
+
case 'complete': return 'done';
|
|
4555
|
+
case 'error': return 'error';
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
class ChatSubagentCardComponent {
|
|
4559
|
+
subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
|
|
4560
|
+
state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
4561
|
+
textOf(m) {
|
|
4562
|
+
const c = m.content;
|
|
4563
|
+
return typeof c === 'string' ? c : '';
|
|
4564
|
+
}
|
|
4565
|
+
toolCallsFor(m) {
|
|
4566
|
+
const ids = m.toolCallIds ?? [];
|
|
4567
|
+
if (ids.length === 0)
|
|
4568
|
+
return [];
|
|
4569
|
+
const all = this.subagent().toolCalls?.() ?? [];
|
|
4570
|
+
return ids.map((id) => all.find((tc) => tc.id === id)).filter((tc) => !!tc);
|
|
4571
|
+
}
|
|
4572
|
+
toToolCallInfo(tc) {
|
|
4573
|
+
return { id: tc.id, name: tc.name, args: tc.args, result: tc.result, status: tc.status };
|
|
4574
|
+
}
|
|
4575
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4576
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatSubagentCardComponent, isStandalone: true, selector: "chat-subagent-card", inputs: { subagent: { classPropertyName: "subagent", publicName: "subagent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
4577
|
+
<chat-trace [state]="state()">
|
|
4578
|
+
<span traceLabel>
|
|
4579
|
+
<span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
|
|
4580
|
+
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4581
|
+
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4582
|
+
</span>
|
|
4583
|
+
<div class="sac__count" traceMeta>{{ subagent().messages().length }} message(s)</div>
|
|
4584
|
+
@for (m of subagent().messages(); track m.id) {
|
|
4585
|
+
<div class="sac__msg" [attr.data-role]="m.role">
|
|
4586
|
+
@if (m.reasoning) {
|
|
4587
|
+
<div class="sac__reasoning">{{ m.reasoning }}</div>
|
|
4588
|
+
}
|
|
4589
|
+
@if (textOf(m); as t) {
|
|
4590
|
+
<chat-streaming-md [content]="t" />
|
|
4591
|
+
}
|
|
4592
|
+
@for (tc of toolCallsFor(m); track tc.id) {
|
|
4593
|
+
<chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
|
|
4594
|
+
}
|
|
4595
|
+
</div>
|
|
4596
|
+
}
|
|
4597
|
+
</chat-trace>
|
|
4598
|
+
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--ngaf-chat-separator)}.sac__reasoning{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);font-style:italic;margin-bottom:4px}\n"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4599
|
+
}
|
|
4600
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
|
|
4601
|
+
type: Component,
|
|
4602
|
+
args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent, ChatToolCallCardComponent, ChatStreamingMdComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4603
|
+
<chat-trace [state]="state()">
|
|
4604
|
+
<span traceLabel>
|
|
4605
|
+
<span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
|
|
4606
|
+
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4607
|
+
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4608
|
+
</span>
|
|
4609
|
+
<div class="sac__count" traceMeta>{{ subagent().messages().length }} message(s)</div>
|
|
4610
|
+
@for (m of subagent().messages(); track m.id) {
|
|
4611
|
+
<div class="sac__msg" [attr.data-role]="m.role">
|
|
4612
|
+
@if (m.reasoning) {
|
|
4613
|
+
<div class="sac__reasoning">{{ m.reasoning }}</div>
|
|
4614
|
+
}
|
|
4615
|
+
@if (textOf(m); as t) {
|
|
4616
|
+
<chat-streaming-md [content]="t" />
|
|
4617
|
+
}
|
|
4618
|
+
@for (tc of toolCallsFor(m); track tc.id) {
|
|
4619
|
+
<chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
|
|
4620
|
+
}
|
|
4621
|
+
</div>
|
|
4622
|
+
}
|
|
4623
|
+
</chat-trace>
|
|
4624
|
+
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--ngaf-chat-separator)}.sac__reasoning{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);font-style:italic;margin-bottom:4px}\n"] }]
|
|
4625
|
+
}], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
|
|
4626
|
+
|
|
4301
4627
|
// libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-call-template.directive.ts
|
|
4302
4628
|
// SPDX-License-Identifier: MIT
|
|
4303
4629
|
/**
|
|
@@ -4426,14 +4752,23 @@ class ChatToolCallsComponent {
|
|
|
4426
4752
|
groups = computed(() => {
|
|
4427
4753
|
const excludeSet = new Set(this.excludeToolNames());
|
|
4428
4754
|
const calls = this.toolCalls().filter(tc => !excludeSet.has(tc.name));
|
|
4755
|
+
const subs = this.agent().subagents?.() ?? new Map();
|
|
4429
4756
|
const groupingMode = this.grouping();
|
|
4430
4757
|
const registry = this.templateRegistry();
|
|
4431
4758
|
const wildcard = registry.get('*');
|
|
4432
4759
|
const out = [];
|
|
4433
4760
|
for (const tc of calls) {
|
|
4761
|
+
// A tool call that spawned a subagent renders as a standalone subagent
|
|
4762
|
+
// card anchored to that call. It never groups with adjacent calls, on
|
|
4763
|
+
// either side: it is its own group and carries a `subagent`, so the next
|
|
4764
|
+
// call can't append to it (a subagent group is never a group target).
|
|
4765
|
+
if (subs.has(tc.id)) {
|
|
4766
|
+
out.push({ name: tc.name, calls: [tc], subagent: subs.get(tc.id) });
|
|
4767
|
+
continue;
|
|
4768
|
+
}
|
|
4434
4769
|
const tpl = registry.get(tc.name) ?? wildcard;
|
|
4435
4770
|
const last = out[out.length - 1];
|
|
4436
|
-
const sameName = last && last.name === tc.name;
|
|
4771
|
+
const sameName = last && !last.subagent && last.name === tc.name;
|
|
4437
4772
|
const canGroup = groupingMode === 'auto' && sameName;
|
|
4438
4773
|
if (canGroup) {
|
|
4439
4774
|
last.calls.push(tc);
|
|
@@ -4467,7 +4802,9 @@ class ChatToolCallsComponent {
|
|
|
4467
4802
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolCallsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4468
4803
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatToolCallsComponent, isStandalone: true, selector: "chat-tool-calls", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, grouping: { classPropertyName: "grouping", publicName: "grouping", isSignal: true, isRequired: false, transformFunction: null }, groupSummary: { classPropertyName: "groupSummary", publicName: "groupSummary", isSignal: true, isRequired: false, transformFunction: null }, excludeToolNames: { classPropertyName: "excludeToolNames", publicName: "excludeToolNames", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "templates", predicate: ChatToolCallTemplateDirective, isSignal: true }], ngImport: i0, template: `
|
|
4469
4804
|
@for (group of groups(); track $index) {
|
|
4470
|
-
@if (group.
|
|
4805
|
+
@if (group.subagent) {
|
|
4806
|
+
<chat-subagent-card [subagent]="group.subagent" />
|
|
4807
|
+
} @else if (group.calls.length > 1 && !group.templateRef) {
|
|
4471
4808
|
<!-- Default grouped strip -->
|
|
4472
4809
|
@let expanded = expandedGroups().has($index);
|
|
4473
4810
|
<div class="ctc__group" [attr.data-group]="true" [attr.data-expanded]="expanded">
|
|
@@ -4498,13 +4835,15 @@ class ChatToolCallsComponent {
|
|
|
4498
4835
|
}
|
|
4499
4836
|
}
|
|
4500
4837
|
}
|
|
4501
|
-
`, isInline: true, styles: [":host{display:block;margin-bottom:20px}.ctc__group{border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);margin:0 0 4px}.ctc__group-header{display:flex;align-items:center;gap:.5rem;width:100%;padding:8px 12px;background:transparent;border:0;font:inherit;color:var(--ngaf-chat-text);cursor:pointer;text-align:left}.ctc__group-chevron{width:10px;height:10px;transition:transform .12s ease}.ctc__group[data-expanded=true] .ctc__group-chevron{transform:rotate(90deg)}.ctc__group-body{padding:0 12px 8px;border-top:1px solid var(--ngaf-chat-separator)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4838
|
+
`, isInline: true, styles: [":host{display:block;margin-bottom:20px}.ctc__group{border:1px solid var(--ngaf-chat-separator);border-radius:var(--ngaf-chat-radius-card);margin:0 0 4px}.ctc__group-header{display:flex;align-items:center;gap:.5rem;width:100%;padding:8px 12px;background:transparent;border:0;font:inherit;color:var(--ngaf-chat-text);cursor:pointer;text-align:left}.ctc__group-chevron{width:10px;height:10px;transition:transform .12s ease}.ctc__group[data-expanded=true] .ctc__group-chevron{transform:rotate(90deg)}.ctc__group-body{padding:0 12px 8px;border-top:1px solid var(--ngaf-chat-separator)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatSubagentCardComponent, selector: "chat-subagent-card", inputs: ["subagent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4502
4839
|
}
|
|
4503
4840
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolCallsComponent, decorators: [{
|
|
4504
4841
|
type: Component,
|
|
4505
|
-
args: [{ selector: 'chat-tool-calls', standalone: true, imports: [NgTemplateOutlet, ChatToolCallCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4842
|
+
args: [{ selector: 'chat-tool-calls', standalone: true, imports: [NgTemplateOutlet, ChatToolCallCardComponent, ChatSubagentCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4506
4843
|
@for (group of groups(); track $index) {
|
|
4507
|
-
@if (group.
|
|
4844
|
+
@if (group.subagent) {
|
|
4845
|
+
<chat-subagent-card [subagent]="group.subagent" />
|
|
4846
|
+
} @else if (group.calls.length > 1 && !group.templateRef) {
|
|
4508
4847
|
<!-- Default grouped strip -->
|
|
4509
4848
|
@let expanded = expandedGroups().has($index);
|
|
4510
4849
|
<div class="ctc__group" [attr.data-group]="true" [attr.data-expanded]="expanded">
|
|
@@ -4778,99 +5117,6 @@ function isRecord$2(v) {
|
|
|
4778
5117
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
4779
5118
|
}
|
|
4780
5119
|
|
|
4781
|
-
// libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
|
|
4782
|
-
// SPDX-License-Identifier: MIT
|
|
4783
|
-
/**
|
|
4784
|
-
* Returns a CSS style string for a subagent's status badge.
|
|
4785
|
-
* Kept exported for backward compatibility with existing consumers; the
|
|
4786
|
-
* preferred way to style status visually is via the `data-status` attribute
|
|
4787
|
-
* + CSS selectors (see component styles below).
|
|
4788
|
-
*/
|
|
4789
|
-
function statusColor(status) {
|
|
4790
|
-
switch (status) {
|
|
4791
|
-
case 'pending': return 'background: var(--ngaf-chat-surface-alt); color: var(--ngaf-chat-text-muted);';
|
|
4792
|
-
case 'running': return 'background: var(--ngaf-chat-warning-bg); color: var(--ngaf-chat-warning-text);';
|
|
4793
|
-
case 'complete': return 'color: var(--ngaf-chat-success);';
|
|
4794
|
-
case 'error': return 'background: var(--ngaf-chat-error-bg); color: var(--ngaf-chat-error-text);';
|
|
4795
|
-
}
|
|
4796
|
-
}
|
|
4797
|
-
function statusToTraceState(s) {
|
|
4798
|
-
switch (s) {
|
|
4799
|
-
case 'pending': return 'pending';
|
|
4800
|
-
case 'running': return 'running';
|
|
4801
|
-
case 'complete': return 'done';
|
|
4802
|
-
case 'error': return 'error';
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
class ChatSubagentCardComponent {
|
|
4806
|
-
subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
|
|
4807
|
-
state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
4808
|
-
textOf(m) {
|
|
4809
|
-
const c = m.content;
|
|
4810
|
-
return typeof c === 'string' ? c : '';
|
|
4811
|
-
}
|
|
4812
|
-
toolCallsFor(m) {
|
|
4813
|
-
const ids = m.toolCallIds ?? [];
|
|
4814
|
-
if (ids.length === 0)
|
|
4815
|
-
return [];
|
|
4816
|
-
const all = this.subagent().toolCalls?.() ?? [];
|
|
4817
|
-
return ids.map((id) => all.find((tc) => tc.id === id)).filter((tc) => !!tc);
|
|
4818
|
-
}
|
|
4819
|
-
toToolCallInfo(tc) {
|
|
4820
|
-
return { id: tc.id, name: tc.name, args: tc.args, result: tc.result, status: tc.status };
|
|
4821
|
-
}
|
|
4822
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4823
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatSubagentCardComponent, isStandalone: true, selector: "chat-subagent-card", inputs: { subagent: { classPropertyName: "subagent", publicName: "subagent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
4824
|
-
<chat-trace [state]="state()">
|
|
4825
|
-
<span traceLabel>
|
|
4826
|
-
<span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
|
|
4827
|
-
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4828
|
-
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4829
|
-
</span>
|
|
4830
|
-
<div class="sac__count">{{ subagent().messages().length }} message(s)</div>
|
|
4831
|
-
@for (m of subagent().messages(); track m.id) {
|
|
4832
|
-
<div class="sac__msg" [attr.data-role]="m.role">
|
|
4833
|
-
@if (m.reasoning) {
|
|
4834
|
-
<div class="sac__reasoning">{{ m.reasoning }}</div>
|
|
4835
|
-
}
|
|
4836
|
-
@if (textOf(m); as t) {
|
|
4837
|
-
<chat-streaming-md [content]="t" />
|
|
4838
|
-
}
|
|
4839
|
-
@for (tc of toolCallsFor(m); track tc.id) {
|
|
4840
|
-
<chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
|
|
4841
|
-
}
|
|
4842
|
-
</div>
|
|
4843
|
-
}
|
|
4844
|
-
</chat-trace>
|
|
4845
|
-
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--ngaf-chat-separator)}.sac__reasoning{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);font-style:italic;margin-bottom:4px}\n"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4846
|
-
}
|
|
4847
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
|
|
4848
|
-
type: Component,
|
|
4849
|
-
args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent, ChatToolCallCardComponent, ChatStreamingMdComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4850
|
-
<chat-trace [state]="state()">
|
|
4851
|
-
<span traceLabel>
|
|
4852
|
-
<span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
|
|
4853
|
-
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4854
|
-
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4855
|
-
</span>
|
|
4856
|
-
<div class="sac__count">{{ subagent().messages().length }} message(s)</div>
|
|
4857
|
-
@for (m of subagent().messages(); track m.id) {
|
|
4858
|
-
<div class="sac__msg" [attr.data-role]="m.role">
|
|
4859
|
-
@if (m.reasoning) {
|
|
4860
|
-
<div class="sac__reasoning">{{ m.reasoning }}</div>
|
|
4861
|
-
}
|
|
4862
|
-
@if (textOf(m); as t) {
|
|
4863
|
-
<chat-streaming-md [content]="t" />
|
|
4864
|
-
}
|
|
4865
|
-
@for (tc of toolCallsFor(m); track tc.id) {
|
|
4866
|
-
<chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
|
|
4867
|
-
}
|
|
4868
|
-
</div>
|
|
4869
|
-
}
|
|
4870
|
-
</chat-trace>
|
|
4871
|
-
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.sac__name{color:var(--ngaf-chat-text);font-weight:500;font-size:var(--ngaf-chat-font-size-sm)}.sac__id{font-family:var(--ngaf-chat-font-mono);font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text-muted)}.sac__pill[data-status=running]{background:var(--ngaf-chat-warning-bg);color:var(--ngaf-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--ngaf-chat-success)}.sac__pill[data-status=error]{background:var(--ngaf-chat-error-bg);color:var(--ngaf-chat-error-text)}.sac__count{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--ngaf-chat-separator)}.sac__reasoning{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);font-style:italic;margin-bottom:4px}\n"] }]
|
|
4872
|
-
}], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
|
|
4873
|
-
|
|
4874
5120
|
// libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
|
|
4875
5121
|
// SPDX-License-Identifier: MIT
|
|
4876
5122
|
function activeSubagentsFromAgent(agent) {
|
|
@@ -7441,6 +7687,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
7441
7687
|
}], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: true }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], surfaceFallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceFallback", required: false }] }], events: [{ type: i0.Output, args: ["events"] }], action: [{ type: i0.Output, args: ["action"] }] } });
|
|
7442
7688
|
|
|
7443
7689
|
// SPDX-License-Identifier: MIT
|
|
7690
|
+
/**
|
|
7691
|
+
* Create a {@link ParseTreeStore} — feeds streamed JSON chunks through a
|
|
7692
|
+
* partial-JSON parser and exposes the progressively-materialized spec and
|
|
7693
|
+
* per-element accumulation state as signals, so a generative-UI surface can
|
|
7694
|
+
* render while the spec is still arriving.
|
|
7695
|
+
*
|
|
7696
|
+
* @param parser The partial-JSON parser used to incrementally materialize chunks.
|
|
7697
|
+
* @returns A {@link ParseTreeStore}; call `push(chunk)` as bytes stream in.
|
|
7698
|
+
* @example
|
|
7699
|
+
* ```ts
|
|
7700
|
+
* const store = createParseTreeStore(parser);
|
|
7701
|
+
* store.push('{"type":"Car');
|
|
7702
|
+
* store.spec(); // best-effort Spec | null
|
|
7703
|
+
* ```
|
|
7704
|
+
*/
|
|
7444
7705
|
function createParseTreeStore(parser) {
|
|
7445
7706
|
const specSignal = signal(null, ...(ngDevMode ? [{ debugName: "specSignal" }] : []));
|
|
7446
7707
|
const elementStatesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "elementStatesSignal" }] : []));
|
|
@@ -7581,6 +7842,19 @@ function resolveProps(value, dataModel) {
|
|
|
7581
7842
|
}
|
|
7582
7843
|
return value;
|
|
7583
7844
|
}
|
|
7845
|
+
/**
|
|
7846
|
+
* Create an {@link A2uiSurfaceStore} — the per-conversation store that buffers
|
|
7847
|
+
* streamed A2UI surface updates, tracks each surface's data model + lifecycle
|
|
7848
|
+
* state, and exposes them as signals for rendering. One store backs a chat
|
|
7849
|
+
* thread's A2UI surfaces.
|
|
7850
|
+
*
|
|
7851
|
+
* @returns A fresh, empty {@link A2uiSurfaceStore}.
|
|
7852
|
+
* @example
|
|
7853
|
+
* ```ts
|
|
7854
|
+
* const store = createA2uiSurfaceStore();
|
|
7855
|
+
* const surfaces = store.surfaces; // Signal<Map<string, A2uiSurface>>
|
|
7856
|
+
* ```
|
|
7857
|
+
*/
|
|
7584
7858
|
function createA2uiSurfaceStore() {
|
|
7585
7859
|
const surfacesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "surfacesSignal" }] : []));
|
|
7586
7860
|
const surfaceStatesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "surfaceStatesSignal" }] : []));
|
|
@@ -7802,6 +8076,19 @@ function trace(...args) {
|
|
|
7802
8076
|
|
|
7803
8077
|
// SPDX-License-Identifier: MIT
|
|
7804
8078
|
const A2UI_PREFIX = '---a2ui_JSON---';
|
|
8079
|
+
/**
|
|
8080
|
+
* Create a {@link ContentClassifier} — the streaming accumulator that inspects
|
|
8081
|
+
* an assistant message's content as it arrives and classifies it (markdown vs a
|
|
8082
|
+
* generative-UI/A2UI spec), exposing the parsed result and per-element state as
|
|
8083
|
+
* signals so the renderer can switch modes mid-stream.
|
|
8084
|
+
*
|
|
8085
|
+
* @returns A fresh {@link ContentClassifier}; call `dispose()` when done.
|
|
8086
|
+
* @example
|
|
8087
|
+
* ```ts
|
|
8088
|
+
* const cc = createContentClassifier();
|
|
8089
|
+
* effect(() => console.log(cc.type())); // 'pending' | 'markdown' | 'spec'
|
|
8090
|
+
* ```
|
|
8091
|
+
*/
|
|
7805
8092
|
function createContentClassifier() {
|
|
7806
8093
|
const typeSignal = signal('pending', ...(ngDevMode ? [{ debugName: "typeSignal" }] : []));
|
|
7807
8094
|
const markdownSignal = signal('', ...(ngDevMode ? [{ debugName: "markdownSignal" }] : []));
|
|
@@ -8673,6 +8960,62 @@ class ChatComponent {
|
|
|
8673
8960
|
const text = typeof message.content === 'string' ? message.content : '';
|
|
8674
8961
|
return text.length === 0;
|
|
8675
8962
|
}
|
|
8963
|
+
/** The nearest preceding assistant message (skipping hidden tool messages), or undefined. */
|
|
8964
|
+
prevAssistant(msgs, index) {
|
|
8965
|
+
for (let j = index - 1; j >= 0; j--) {
|
|
8966
|
+
if (msgs[j].role === 'tool')
|
|
8967
|
+
continue;
|
|
8968
|
+
return msgs[j].role === 'assistant' ? msgs[j] : undefined;
|
|
8969
|
+
}
|
|
8970
|
+
return undefined;
|
|
8971
|
+
}
|
|
8972
|
+
/**
|
|
8973
|
+
* True when message[index] starts a reasoning RUN — a maximal sequence of
|
|
8974
|
+
* consecutive assistant reasoning steps separated only by (hidden) tool
|
|
8975
|
+
* messages. The merged reasoning pill renders once, here.
|
|
8976
|
+
*/
|
|
8977
|
+
reasoningRunStart(index) {
|
|
8978
|
+
const msgs = this.agent().messages();
|
|
8979
|
+
if (!msgs[index]?.reasoning)
|
|
8980
|
+
return false;
|
|
8981
|
+
return !this.prevAssistant(msgs, index)?.reasoning;
|
|
8982
|
+
}
|
|
8983
|
+
/**
|
|
8984
|
+
* Aggregate the reasoning RUN starting at `index`: joins each step's
|
|
8985
|
+
* reasoning, sums durations, counts steps, and computes the streaming flag
|
|
8986
|
+
* and the merged label when N > 1 ("Thought for {total} · {N} steps", or
|
|
8987
|
+
* just "{N} steps" when no step reported timing).
|
|
8988
|
+
*/
|
|
8989
|
+
reasoningRun(index) {
|
|
8990
|
+
const msgs = this.agent().messages();
|
|
8991
|
+
const steps = [];
|
|
8992
|
+
for (let j = index; j < msgs.length; j++) {
|
|
8993
|
+
const m = msgs[j];
|
|
8994
|
+
if (m.role === 'tool')
|
|
8995
|
+
continue; // skip hidden tool messages
|
|
8996
|
+
if (m.role === 'assistant' && m.reasoning) {
|
|
8997
|
+
steps.push({ msg: m, idx: j });
|
|
8998
|
+
continue;
|
|
8999
|
+
}
|
|
9000
|
+
break; // any other message ends the run
|
|
9001
|
+
}
|
|
9002
|
+
const content = steps.map((s) => s.msg.reasoning ?? '').filter(Boolean).join('\n\n');
|
|
9003
|
+
const durations = steps
|
|
9004
|
+
.map((s) => s.msg.reasoningDurationMs)
|
|
9005
|
+
.filter((d) => typeof d === 'number');
|
|
9006
|
+
const durationMs = durations.length ? durations.reduce((a, b) => a + b, 0) : undefined;
|
|
9007
|
+
const last = steps[steps.length - 1];
|
|
9008
|
+
const streaming = last ? this.isReasoningStreaming(last.msg, last.idx) : false;
|
|
9009
|
+
// Only claim a duration when at least one step reported timing. Otherwise
|
|
9010
|
+
// "Thought for <1s" would read as "fast" when it really means "unknown", so
|
|
9011
|
+
// drop the duration phrase and label by step count alone.
|
|
9012
|
+
const label = steps.length > 1
|
|
9013
|
+
? durationMs !== undefined
|
|
9014
|
+
? `Thought for ${formatDuration(durationMs)} · ${steps.length} steps`
|
|
9015
|
+
: `${steps.length} steps`
|
|
9016
|
+
: undefined;
|
|
9017
|
+
return { content, durationMs, streaming, label };
|
|
9018
|
+
}
|
|
8676
9019
|
classifiers = new Map();
|
|
8677
9020
|
destroyRef = inject(DestroyRef);
|
|
8678
9021
|
injector = inject(Injector);
|
|
@@ -9232,11 +9575,19 @@ class ChatComponent {
|
|
|
9232
9575
|
[streaming]="agent().isLoading() && i === agent().messages().length - 1"
|
|
9233
9576
|
[current]="i === agent().messages().length - 1"
|
|
9234
9577
|
>
|
|
9235
|
-
|
|
9578
|
+
<!-- Reasoning is merged across a run of consecutive (tool-
|
|
9579
|
+
separated) reasoning steps and rendered ONCE at the run's
|
|
9580
|
+
first step as "Thought for {total} · {N} steps", so a
|
|
9581
|
+
multi-step agent shows one compact pill instead of a
|
|
9582
|
+
stack of "Thought for 1s" chips. Single-step turns render
|
|
9583
|
+
a normal "Thought for {duration}" pill. -->
|
|
9584
|
+
@if (message.reasoning && reasoningRunStart(i)) {
|
|
9585
|
+
@let run = reasoningRun(i);
|
|
9236
9586
|
<chat-reasoning
|
|
9237
|
-
[content]="
|
|
9238
|
-
[isStreaming]="
|
|
9239
|
-
[durationMs]="
|
|
9587
|
+
[content]="run.content"
|
|
9588
|
+
[isStreaming]="run.streaming"
|
|
9589
|
+
[durationMs]="run.durationMs"
|
|
9590
|
+
[label]="run.label"
|
|
9240
9591
|
/>
|
|
9241
9592
|
}
|
|
9242
9593
|
<chat-tool-calls [agent]="agent()" [message]="message" [excludeToolNames]="excludedToolNames()">
|
|
@@ -9252,7 +9603,6 @@ class ChatComponent {
|
|
|
9252
9603
|
[handlers]="handlers()"
|
|
9253
9604
|
(events)="onClientToolEvent($event)"
|
|
9254
9605
|
/>
|
|
9255
|
-
<chat-subagents [agent]="agent()" />
|
|
9256
9606
|
@if (classified.markdown(); as md) {
|
|
9257
9607
|
<chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
|
|
9258
9608
|
}
|
|
@@ -9284,14 +9634,21 @@ class ChatComponent {
|
|
|
9284
9634
|
/>
|
|
9285
9635
|
}
|
|
9286
9636
|
}
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9637
|
+
<!-- Only show message actions when there is copyable assistant
|
|
9638
|
+
text. Content-less messages (a bare tool call or a subagent
|
|
9639
|
+
delegation card) have nothing to copy/regenerate/rate, so the
|
|
9640
|
+
actions panel is pure whitespace there — suppress it to keep
|
|
9641
|
+
the stream compact. -->
|
|
9642
|
+
@if (content.trim()) {
|
|
9643
|
+
<chat-message-actions
|
|
9644
|
+
chatMessageControls
|
|
9645
|
+
[content]="content"
|
|
9646
|
+
[disabled]="agent().isLoading()"
|
|
9647
|
+
(regenerate)="onRegenerate(i)"
|
|
9648
|
+
(rate)="onRate(message, $event)"
|
|
9649
|
+
(contentCopied)="onCopy(message, $event)"
|
|
9650
|
+
/>
|
|
9651
|
+
}
|
|
9295
9652
|
</chat-message>
|
|
9296
9653
|
</ng-template>
|
|
9297
9654
|
|
|
@@ -9340,7 +9697,7 @@ class ChatComponent {
|
|
|
9340
9697
|
</div>
|
|
9341
9698
|
</div>
|
|
9342
9699
|
}
|
|
9343
|
-
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--ngaf-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--ngaf-chat-separator);background:var(--ngaf-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--ngaf-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--ngaf-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--ngaf-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--ngaf-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--ngaf-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type:
|
|
9700
|
+
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--ngaf-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--ngaf-chat-separator);background:var(--ngaf-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--ngaf-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--ngaf-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--ngaf-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--ngaf-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--ngaf-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--ngaf-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "isStreaming", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9344
9701
|
}
|
|
9345
9702
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
|
|
9346
9703
|
type: Component,
|
|
@@ -9349,7 +9706,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
9349
9706
|
ChatWindowComponent, ChatMessageListComponent, MessageTemplateDirective, ChatMessageComponent,
|
|
9350
9707
|
ChatInputComponent, ChatTypingIndicatorComponent, ChatErrorComponent,
|
|
9351
9708
|
ChatThreadListComponent, ChatGenerativeUiComponent,
|
|
9352
|
-
ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent,
|
|
9709
|
+
ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent, A2uiSurfaceComponent,
|
|
9353
9710
|
ChatMessageActionsComponent, ChatWelcomeComponent, ChatSelectComponent, ChatReasoningComponent,
|
|
9354
9711
|
ChatScrollBubbleComponent,
|
|
9355
9712
|
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
@@ -9402,11 +9759,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
9402
9759
|
[streaming]="agent().isLoading() && i === agent().messages().length - 1"
|
|
9403
9760
|
[current]="i === agent().messages().length - 1"
|
|
9404
9761
|
>
|
|
9405
|
-
|
|
9762
|
+
<!-- Reasoning is merged across a run of consecutive (tool-
|
|
9763
|
+
separated) reasoning steps and rendered ONCE at the run's
|
|
9764
|
+
first step as "Thought for {total} · {N} steps", so a
|
|
9765
|
+
multi-step agent shows one compact pill instead of a
|
|
9766
|
+
stack of "Thought for 1s" chips. Single-step turns render
|
|
9767
|
+
a normal "Thought for {duration}" pill. -->
|
|
9768
|
+
@if (message.reasoning && reasoningRunStart(i)) {
|
|
9769
|
+
@let run = reasoningRun(i);
|
|
9406
9770
|
<chat-reasoning
|
|
9407
|
-
[content]="
|
|
9408
|
-
[isStreaming]="
|
|
9409
|
-
[durationMs]="
|
|
9771
|
+
[content]="run.content"
|
|
9772
|
+
[isStreaming]="run.streaming"
|
|
9773
|
+
[durationMs]="run.durationMs"
|
|
9774
|
+
[label]="run.label"
|
|
9410
9775
|
/>
|
|
9411
9776
|
}
|
|
9412
9777
|
<chat-tool-calls [agent]="agent()" [message]="message" [excludeToolNames]="excludedToolNames()">
|
|
@@ -9422,7 +9787,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
9422
9787
|
[handlers]="handlers()"
|
|
9423
9788
|
(events)="onClientToolEvent($event)"
|
|
9424
9789
|
/>
|
|
9425
|
-
<chat-subagents [agent]="agent()" />
|
|
9426
9790
|
@if (classified.markdown(); as md) {
|
|
9427
9791
|
<chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
|
|
9428
9792
|
}
|
|
@@ -9454,14 +9818,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
9454
9818
|
/>
|
|
9455
9819
|
}
|
|
9456
9820
|
}
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9821
|
+
<!-- Only show message actions when there is copyable assistant
|
|
9822
|
+
text. Content-less messages (a bare tool call or a subagent
|
|
9823
|
+
delegation card) have nothing to copy/regenerate/rate, so the
|
|
9824
|
+
actions panel is pure whitespace there — suppress it to keep
|
|
9825
|
+
the stream compact. -->
|
|
9826
|
+
@if (content.trim()) {
|
|
9827
|
+
<chat-message-actions
|
|
9828
|
+
chatMessageControls
|
|
9829
|
+
[content]="content"
|
|
9830
|
+
[disabled]="agent().isLoading()"
|
|
9831
|
+
(regenerate)="onRegenerate(i)"
|
|
9832
|
+
(rate)="onRate(message, $event)"
|
|
9833
|
+
(contentCopied)="onCopy(message, $event)"
|
|
9834
|
+
/>
|
|
9835
|
+
}
|
|
9465
9836
|
</chat-message>
|
|
9466
9837
|
</ng-template>
|
|
9467
9838
|
|
|
@@ -11179,22 +11550,6 @@ function renderMarkdownToString(content, sanitizer) {
|
|
|
11179
11550
|
return plainTextToHtml(content);
|
|
11180
11551
|
}
|
|
11181
11552
|
|
|
11182
|
-
// SPDX-License-Identifier: MIT
|
|
11183
|
-
/** Chevron down (▼ replacement). 12x12, stroke-based. */
|
|
11184
|
-
const ICON_CHEVRON_DOWN = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4.5L6 7.5L9 4.5"/></svg>`;
|
|
11185
|
-
/** Chevron up (▲ replacement). 12x12, stroke-based. */
|
|
11186
|
-
const ICON_CHEVRON_UP = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5L6 4.5L9 7.5"/></svg>`;
|
|
11187
|
-
/** Gear icon (⚙ replacement). 14x14. */
|
|
11188
|
-
const ICON_TOOL = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>`;
|
|
11189
|
-
/** Warning triangle (⚠ replacement). 18x18. */
|
|
11190
|
-
const ICON_WARNING = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`;
|
|
11191
|
-
/** Robot/agent icon (replacement). 14x14. */
|
|
11192
|
-
const ICON_AGENT = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/></svg>`;
|
|
11193
|
-
/** Check mark replacement. 12x12. */
|
|
11194
|
-
const ICON_CHECK = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 6L5 8.5L9.5 3.5"/></svg>`;
|
|
11195
|
-
/** Send arrow (for chat input). 16x16. */
|
|
11196
|
-
const ICON_SEND = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 12V4M8 4L4 8M8 4L12 8"/></svg>`;
|
|
11197
|
-
|
|
11198
11553
|
/** Normalize a catalog entry to the `A2uiViewEntry` shape. Bare
|
|
11199
11554
|
* `Type<unknown>` entries are wrapped as `{ component }`; entries
|
|
11200
11555
|
* already in the discriminated shape are returned unchanged. */
|
|
@@ -11512,6 +11867,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
11512
11867
|
}], propDecorators: { axis: [{ type: i0.Input, args: [{ isSignal: true, alias: "axis", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
|
|
11513
11868
|
|
|
11514
11869
|
// SPDX-License-Identifier: MIT
|
|
11870
|
+
/**
|
|
11871
|
+
* Convert an icon identifier to its Material Symbols ligature form.
|
|
11872
|
+
*
|
|
11873
|
+
* Material Symbols ligatures are snake_case (`account_circle`, `trending_up`),
|
|
11874
|
+
* but A2UI catalogs commonly emit camelCase identifiers (`accountCircle`,
|
|
11875
|
+
* `shoppingCart`). Splitting on lower→upper boundaries and lowercasing maps
|
|
11876
|
+
* camelCase → the matching ligature. Already-snake_case names, single words,
|
|
11877
|
+
* and non-identifier glyphs (emoji) have no boundaries to split and pass
|
|
11878
|
+
* through unchanged. Unknown names still fall back to the browser default.
|
|
11879
|
+
*/
|
|
11880
|
+
function toMaterialSymbolName(name) {
|
|
11881
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
11882
|
+
}
|
|
11515
11883
|
class A2uiIconComponent {
|
|
11516
11884
|
/** v1 canonical prop. */
|
|
11517
11885
|
name = input(undefined, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
@@ -11525,24 +11893,32 @@ class A2uiIconComponent {
|
|
|
11525
11893
|
childKeys = input([], ...(ngDevMode ? [{ debugName: "childKeys" }] : []));
|
|
11526
11894
|
spec = input(undefined, ...(ngDevMode ? [{ debugName: "spec" }] : []));
|
|
11527
11895
|
effectiveName = computed(() => this.name() ?? this.icon(), ...(ngDevMode ? [{ debugName: "effectiveName" }] : []));
|
|
11896
|
+
/** The effective name as a Material Symbols ligature (camelCase → snake_case). */
|
|
11897
|
+
glyphName = computed(() => toMaterialSymbolName(this.effectiveName()), ...(ngDevMode ? [{ debugName: "glyphName" }] : []));
|
|
11528
11898
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11529
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11899
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: A2uiIconComponent, isStandalone: true, selector: "a2ui-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, bindings: { classPropertyName: "bindings", publicName: "bindings", isSignal: true, isRequired: false, transformFunction: null }, emit: { classPropertyName: "emit", publicName: "emit", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, childKeys: { classPropertyName: "childKeys", publicName: "childKeys", isSignal: true, isRequired: false, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
11900
|
+
@if (effectiveName(); as name) {
|
|
11901
|
+
<span
|
|
11902
|
+
class="a2ui-icon material-symbols-outlined"
|
|
11903
|
+
[style.font-size]="size() ? size() + 'px' : '1.125rem'"
|
|
11904
|
+
[attr.aria-label]="name"
|
|
11905
|
+
role="img"
|
|
11906
|
+
>{{ glyphName() }}</span>
|
|
11907
|
+
}
|
|
11908
|
+
`, isInline: true, styles: [".a2ui-icon{font-family:Material Symbols Outlined;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased;font-variation-settings:\"FILL\" 0,\"wght\" 400,\"GRAD\" 0,\"opsz\" 24;color:currentColor;display:inline-flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none}\n"] });
|
|
11536
11909
|
}
|
|
11537
11910
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: A2uiIconComponent, decorators: [{
|
|
11538
11911
|
type: Component,
|
|
11539
11912
|
args: [{ selector: 'a2ui-icon', standalone: true, template: `
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11913
|
+
@if (effectiveName(); as name) {
|
|
11914
|
+
<span
|
|
11915
|
+
class="a2ui-icon material-symbols-outlined"
|
|
11916
|
+
[style.font-size]="size() ? size() + 'px' : '1.125rem'"
|
|
11917
|
+
[attr.aria-label]="name"
|
|
11918
|
+
role="img"
|
|
11919
|
+
>{{ glyphName() }}</span>
|
|
11920
|
+
}
|
|
11921
|
+
`, styles: [".a2ui-icon{font-family:Material Symbols Outlined;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased;font-variation-settings:\"FILL\" 0,\"wght\" 400,\"GRAD\" 0,\"opsz\" 24;color:currentColor;display:inline-flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none}\n"] }]
|
|
11546
11922
|
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
|
|
11547
11923
|
|
|
11548
11924
|
// SPDX-License-Identifier: MIT
|
|
@@ -12222,6 +12598,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
12222
12598
|
}], propDecorators: { url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], autoPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlay", required: false }] }], controls: [{ type: i0.Input, args: [{ isSignal: true, alias: "controls", required: false }] }], bindings: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindings", required: false }] }], emit: [{ type: i0.Input, args: [{ isSignal: true, alias: "emit", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], childKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "childKeys", required: false }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }] } });
|
|
12223
12599
|
|
|
12224
12600
|
// SPDX-License-Identifier: MIT
|
|
12601
|
+
/**
|
|
12602
|
+
* Build the built-in A2UI component catalog — a {@link ViewRegistry} mapping
|
|
12603
|
+
* the standard A2UI element types (Card, Button, TextField, Image, AudioPlayer,
|
|
12604
|
+
* Video, …) to their Angular renderers. Spread it into `provideViews` (with any
|
|
12605
|
+
* of your own views) so an agent's A2UI surface specs render.
|
|
12606
|
+
*
|
|
12607
|
+
* @returns A {@link ViewRegistry} of the standard A2UI components.
|
|
12608
|
+
* @example
|
|
12609
|
+
* ```ts
|
|
12610
|
+
* providers: [provideViews({ ...a2uiBasicCatalog(), MyWidget: MyWidgetComponent })]
|
|
12611
|
+
* ```
|
|
12612
|
+
*/
|
|
12225
12613
|
function a2uiBasicCatalog() {
|
|
12226
12614
|
return views({
|
|
12227
12615
|
AudioPlayer: A2uiAudioPlayerComponent,
|
|
@@ -12359,6 +12747,21 @@ function tools(map) {
|
|
|
12359
12747
|
}
|
|
12360
12748
|
|
|
12361
12749
|
// SPDX-License-Identifier: MIT
|
|
12750
|
+
/**
|
|
12751
|
+
* Build an in-memory {@link Agent} for tests and stories — no transport, no
|
|
12752
|
+
* network. Every field is a writable signal so a test can drive UI states
|
|
12753
|
+
* (loading, error, interrupts, tool calls, subagents) deterministically.
|
|
12754
|
+
*
|
|
12755
|
+
* @param opts Initial values for the mock's signals; all optional.
|
|
12756
|
+
* @returns A {@link MockAgent} satisfying the full `Agent` contract.
|
|
12757
|
+
* @example
|
|
12758
|
+
* ```ts
|
|
12759
|
+
* const agent = mockAgent({
|
|
12760
|
+
* messages: [{ id: '1', role: 'assistant', content: 'Hi' }],
|
|
12761
|
+
* isLoading: true,
|
|
12762
|
+
* });
|
|
12763
|
+
* ```
|
|
12764
|
+
*/
|
|
12362
12765
|
function mockAgent(opts = {}) {
|
|
12363
12766
|
const messages = signal(opts.messages ?? [], ...(ngDevMode ? [{ debugName: "messages" }] : []));
|
|
12364
12767
|
const status = signal(opts.status ?? 'idle', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
@@ -12410,5 +12813,5 @@ function mockAgent(opts = {}) {
|
|
|
12410
12813
|
* Generated bundle index. Do not edit.
|
|
12411
12814
|
*/
|
|
12412
12815
|
|
|
12413
|
-
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,
|
|
12816
|
+
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, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, 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 };
|
|
12414
12817
|
//# sourceMappingURL=threadplane-chat.mjs.map
|