@threadplane/chat 0.0.50 → 0.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/threadplane-chat.mjs +701 -120
- package/fesm2022/threadplane-chat.mjs.map +1 -1
- package/package.json +2 -1
- package/types/threadplane-chat.d.ts +560 -85
|
@@ -1,30 +1,250 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT,
|
|
2
|
+
import { InjectionToken, input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, ViewEncapsulation, viewChild, model, contentChild, untracked, ElementRef, DestroyRef, makeEnvironmentProviders, Injector, runInInjectionContext, ViewContainerRef, SecurityContext } from '@angular/core';
|
|
3
3
|
import { NgTemplateOutlet, NgComponentOutlet, KeyValuePipe } from '@angular/common';
|
|
4
4
|
import { createPartialMarkdownParser, materialize } from '@cacheplane/partial-markdown';
|
|
5
5
|
import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
|
|
6
6
|
export { toRenderRegistry, views, withViews, withoutViews } from '@threadplane/render';
|
|
7
7
|
import { runLicenseCheck, inferNoncommercial, LICENSE_PUBLIC_KEY } from '@threadplane/licensing';
|
|
8
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
|
+
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
9
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
10
|
+
import { filter, map, startWith } from 'rxjs/operators';
|
|
9
11
|
import { toJSONSchema } from 'zod/v4';
|
|
10
12
|
import { resolveDynamic, getByPointer, isPathRef, setByPointer, createA2uiMessageParser } from '@threadplane/a2ui';
|
|
11
13
|
export { isLiteralBoolean, isLiteralNumber, isLiteralString, isPathRef } from '@threadplane/a2ui';
|
|
12
14
|
import { materialize as materialize$1, createPartialJsonParser } from '@cacheplane/partial-json';
|
|
13
15
|
import { fromEvent, EMPTY } from 'rxjs';
|
|
14
16
|
|
|
17
|
+
// SPDX-License-Identifier: MIT
|
|
18
|
+
/**
|
|
19
|
+
* Structured, classified failure surfaced on `Agent.error`. Extends `Error`, so
|
|
20
|
+
* existing `.message` / `instanceof Error` reads keep working — but adds a
|
|
21
|
+
* machine-readable {@link AgentErrorKind}, a `retryable` flag, an optional HTTP
|
|
22
|
+
* `status`, and the original `cause`.
|
|
23
|
+
*
|
|
24
|
+
* You rarely construct one yourself; adapters normalize raw failures via
|
|
25
|
+
* {@link toAgentError}. Read it off the agent to render legible, cause-specific UI:
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const err = agent.error(); // AgentError | undefined
|
|
30
|
+
* if (err) {
|
|
31
|
+
* console.warn(err.message); // legible, per-kind copy
|
|
32
|
+
* if (err.kind === 'auth') showApiKeyHelp();
|
|
33
|
+
* if (err.retryable) showRetryButton(); // → agent.retry()
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
class AgentError extends Error {
|
|
38
|
+
/** The classified failure type. See {@link AgentErrorKind}. */
|
|
39
|
+
kind;
|
|
40
|
+
/** Whether retrying the same request could plausibly succeed:
|
|
41
|
+
* `connection` | `server` (5xx) | `interrupted` → true; `auth` | `aborted` | non-auth `4xx` → false. */
|
|
42
|
+
retryable;
|
|
43
|
+
/** The HTTP status code when the failure came from an HTTP response. */
|
|
44
|
+
status;
|
|
45
|
+
/** The original raw error this was classified from, preserved for debugging/telemetry. */
|
|
46
|
+
cause;
|
|
47
|
+
constructor(init) {
|
|
48
|
+
super(init.message);
|
|
49
|
+
this.name = 'AgentError';
|
|
50
|
+
this.kind = init.kind;
|
|
51
|
+
this.retryable = init.retryable;
|
|
52
|
+
this.status = init.status;
|
|
53
|
+
this.cause = init.cause;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Default, human-facing copy per {@link AgentErrorKind}. Used as the message when
|
|
58
|
+
* a classified error has no better text. Override by mapping `error.kind` to your
|
|
59
|
+
* own strings in a custom error component.
|
|
60
|
+
*/
|
|
61
|
+
const AGENT_ERROR_MESSAGES = {
|
|
62
|
+
connection: "Can't reach the server. Check your connection and try again.",
|
|
63
|
+
auth: 'Authentication failed. Check your API key or credentials.',
|
|
64
|
+
server: 'The server ran into an error. You can try again.',
|
|
65
|
+
interrupted: 'The response was interrupted. Try again.',
|
|
66
|
+
aborted: 'Stopped.',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// SPDX-License-Identifier: MIT
|
|
70
|
+
/**
|
|
71
|
+
* Whether `raw` represents an abort (a `DOMException`/`Error` named `AbortError`,
|
|
72
|
+
* or an abort-ish message). Shared by the runtime adapters and {@link toAgentError}
|
|
73
|
+
* so a user-requested stop settles to idle instead of surfacing as an error.
|
|
74
|
+
*
|
|
75
|
+
* @param raw Any thrown/rejected value.
|
|
76
|
+
* @returns `true` if it looks like an abort.
|
|
77
|
+
*/
|
|
78
|
+
function isAbortError(raw) {
|
|
79
|
+
return raw instanceof Error && (raw.name === 'AbortError' || /\babort/i.test(raw.message));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reads a numeric status from `raw.status` or `raw.cause.status` only.
|
|
83
|
+
* No text parsing — structured fields only.
|
|
84
|
+
*/
|
|
85
|
+
function structuredStatus(raw) {
|
|
86
|
+
const obj = raw;
|
|
87
|
+
const direct = typeof obj?.status === 'number' ? obj.status : undefined;
|
|
88
|
+
const viaCause = typeof obj?.cause?.status === 'number' ? obj.cause.status : undefined;
|
|
89
|
+
if (direct !== undefined)
|
|
90
|
+
return direct;
|
|
91
|
+
if (viaCause !== undefined)
|
|
92
|
+
return viaCause;
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
function isConnectionError(raw) {
|
|
96
|
+
if (!(raw instanceof Error))
|
|
97
|
+
return false;
|
|
98
|
+
return /failed to fetch|networkerror|econnrefused|enotfound|network request failed|load failed/i.test(`${raw.name} ${raw.message}`);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extracts an HTTP status code from a message string, but ONLY when the token
|
|
102
|
+
* is unambiguously HTTP-shaped:
|
|
103
|
+
* - `HTTP/502`, `HTTP 404`, `HTTP404`
|
|
104
|
+
* - `status: 503`, `status=503`, `code: 404`
|
|
105
|
+
*
|
|
106
|
+
* Bare 3-digit numbers (e.g. model version strings like "gpt-500") are NOT matched.
|
|
107
|
+
*/
|
|
108
|
+
function httpStatusFromMessage(raw) {
|
|
109
|
+
const msg = raw instanceof Error ? raw.message : typeof raw === 'string' ? raw : '';
|
|
110
|
+
if (!msg)
|
|
111
|
+
return undefined;
|
|
112
|
+
// "HTTP 500", "HTTP/500", "HTTP500"
|
|
113
|
+
const httpToken = /\bHTTP[ /]?(\d{3})\b/i.exec(msg);
|
|
114
|
+
if (httpToken)
|
|
115
|
+
return Number(httpToken[1]);
|
|
116
|
+
// "status: 503", "status=503", "status 503" (up to 4 non-digit chars between keyword and digits)
|
|
117
|
+
// Also matches "code: 404", "code=404", etc.
|
|
118
|
+
const prefixed = /\b(?:status|code)\b\D{0,4}(\d{3})\b/i.exec(msg);
|
|
119
|
+
if (prefixed)
|
|
120
|
+
return Number(prefixed[1]);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
function make(kind, retryable, raw, status, message) {
|
|
124
|
+
return new AgentError({ kind, retryable, status, cause: raw, message: message ?? AGENT_ERROR_MESSAGES[kind] });
|
|
125
|
+
}
|
|
126
|
+
function classifyByStatus(status, raw) {
|
|
127
|
+
if (status === 401 || status === 403)
|
|
128
|
+
return make('auth', false, raw, status);
|
|
129
|
+
if (status >= 500)
|
|
130
|
+
return make('server', true, raw, status);
|
|
131
|
+
if (status >= 400)
|
|
132
|
+
return make('server', false, raw, status, `The request was rejected (HTTP ${status}).`);
|
|
133
|
+
// Stray 2xx/3xx from a status field — treat as unknown transient failure, no status.
|
|
134
|
+
return make('server', true, raw, undefined, 'Something went wrong. You can try again.');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Classify any raw error into a structured {@link AgentError}.
|
|
138
|
+
*
|
|
139
|
+
* Resolution order (first match wins): an existing `AgentError` is returned
|
|
140
|
+
* unchanged (idempotent) → a user abort → a structured `status`/`cause.status`
|
|
141
|
+
* → network/connection markers → an HTTP-shaped status in the message → a
|
|
142
|
+
* `server` + retryable fallback. The original error is always preserved on
|
|
143
|
+
* `cause`. Runtime adapters call this before setting `Agent.error`; custom
|
|
144
|
+
* backends can call it too (or throw an `AgentError` directly).
|
|
145
|
+
*
|
|
146
|
+
* @param raw Any thrown/rejected value — an `Error`, a `{ status }` object, a string, etc.
|
|
147
|
+
* @returns The classified {@link AgentError} (kind, retryable, status?, cause).
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* const e = toAgentError(new Error('HTTP 500: Internal Server Error'));
|
|
151
|
+
* e.kind; // 'server'
|
|
152
|
+
* e.retryable; // true
|
|
153
|
+
* e.status; // 500
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
function toAgentError(raw) {
|
|
157
|
+
if (raw instanceof AgentError)
|
|
158
|
+
return raw;
|
|
159
|
+
if (isAbortError(raw))
|
|
160
|
+
return make('aborted', false, raw);
|
|
161
|
+
// 1. Structured status (authoritative): raw.status or raw.cause.status.
|
|
162
|
+
const structured = structuredStatus(raw);
|
|
163
|
+
if (structured !== undefined)
|
|
164
|
+
return classifyByStatus(structured, raw);
|
|
165
|
+
// 2. Network/connection markers are definitive — before any loose text parsing.
|
|
166
|
+
if (isConnectionError(raw))
|
|
167
|
+
return make('connection', true, raw);
|
|
168
|
+
// 3. Best-effort: only an HTTP-shaped status token in the message counts.
|
|
169
|
+
const httpStatus = httpStatusFromMessage(raw);
|
|
170
|
+
if (httpStatus !== undefined)
|
|
171
|
+
return classifyByStatus(httpStatus, raw);
|
|
172
|
+
// 4. Fallback: unknown failure, assume transient.
|
|
173
|
+
const msg = raw instanceof Error && raw.message ? raw.message : 'Something went wrong. You can try again.';
|
|
174
|
+
return make('server', true, raw, undefined, msg);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Type guard narrowing a {@link Message} to `role: 'user'`.
|
|
179
|
+
*
|
|
180
|
+
* @param m The message to test.
|
|
181
|
+
* @returns `true` (and narrows `m`) when the message was sent by the user.
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const userTurns = agent.messages().filter(isUserMessage);
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
15
187
|
function isUserMessage(m) {
|
|
16
188
|
return m.role === 'user';
|
|
17
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Type guard narrowing a {@link Message} to `role: 'assistant'`.
|
|
192
|
+
*
|
|
193
|
+
* @param m The message to test.
|
|
194
|
+
* @returns `true` (and narrows `m`) when the message came from the assistant.
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* const reply = agent.messages().findLast(isAssistantMessage);
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
18
200
|
function isAssistantMessage(m) {
|
|
19
201
|
return m.role === 'assistant';
|
|
20
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Type guard narrowing a {@link Message} to `role: 'tool'` (a tool result turn).
|
|
205
|
+
*
|
|
206
|
+
* @param m The message to test.
|
|
207
|
+
* @returns `true` (and narrows `m`) when the message is a tool result.
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* if (isToolMessage(m)) console.log(m.toolCallId);
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
21
213
|
function isToolMessage(m) {
|
|
22
214
|
return m.role === 'tool';
|
|
23
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Type guard narrowing a {@link Message} to `role: 'system'`.
|
|
218
|
+
*
|
|
219
|
+
* @param m The message to test.
|
|
220
|
+
* @returns `true` (and narrows `m`) when the message is a system message.
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* const visible = agent.messages().filter((m) => !isSystemMessage(m));
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
24
226
|
function isSystemMessage(m) {
|
|
25
227
|
return m.role === 'system';
|
|
26
228
|
}
|
|
27
229
|
|
|
230
|
+
// SPDX-License-Identifier: MIT
|
|
231
|
+
/**
|
|
232
|
+
* Create a typed agent handle.
|
|
233
|
+
*
|
|
234
|
+
* @param debugName Optional name shown in Angular DI error messages.
|
|
235
|
+
* @returns An {@link AgentRef} carrying a state-typed `InjectionToken`.
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* interface TripState { day: number; places: string[]; }
|
|
239
|
+
* export const TRIP = createAgentRef<TripState>('trip');
|
|
240
|
+
* // app.config.ts: provideAgent(TRIP, { assistantId: 'trip' })
|
|
241
|
+
* // component: const agent = injectAgent(TRIP); // LangGraphAgent<TripState>
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
function createAgentRef(debugName) {
|
|
245
|
+
return { token: new InjectionToken(debugName ?? 'ThreadplaneAgent') };
|
|
246
|
+
}
|
|
247
|
+
|
|
28
248
|
// SPDX-License-Identifier: MIT
|
|
29
249
|
class MessageTemplateDirective {
|
|
30
250
|
chatMessageTemplate = input.required(...(ngDevMode ? [{ debugName: "chatMessageTemplate" }] : []));
|
|
@@ -2968,6 +3188,18 @@ const CHAT_TYPING_INDICATOR_STYLES = `
|
|
|
2968
3188
|
|
|
2969
3189
|
// libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
|
|
2970
3190
|
// SPDX-License-Identifier: MIT
|
|
3191
|
+
/**
|
|
3192
|
+
* Whether the agent should show a "typing" indicator — it is loading and has
|
|
3193
|
+
* not yet started streaming the assistant's reply.
|
|
3194
|
+
*
|
|
3195
|
+
* @param agent The agent to inspect.
|
|
3196
|
+
* @returns `true` while the agent is awaiting a response but no assistant text
|
|
3197
|
+
* has streamed yet; `false` once tokens arrive or the agent is idle.
|
|
3198
|
+
* @example
|
|
3199
|
+
* ```ts
|
|
3200
|
+
* \@if (isTyping(agent)) { <chat-typing-indicator [agent]="agent" /> }
|
|
3201
|
+
* ```
|
|
3202
|
+
*/
|
|
2971
3203
|
function isTyping(agent) {
|
|
2972
3204
|
if (!agent.isLoading())
|
|
2973
3205
|
return false;
|
|
@@ -3852,6 +4084,26 @@ const CHAT_ERROR_STYLES = `
|
|
|
3852
4084
|
}
|
|
3853
4085
|
.chat-error__icon { flex-shrink: 0; width: 16px; height: 16px; margin-top: 2px; }
|
|
3854
4086
|
.chat-error__msg { flex: 1; min-width: 0; word-break: break-word; }
|
|
4087
|
+
.chat-error__retry {
|
|
4088
|
+
flex-shrink: 0;
|
|
4089
|
+
background: transparent;
|
|
4090
|
+
color: var(--ngaf-chat-error-text);
|
|
4091
|
+
border: 1px solid var(--ngaf-chat-error-border);
|
|
4092
|
+
border-radius: var(--ngaf-chat-radius-card);
|
|
4093
|
+
padding: 2px 10px;
|
|
4094
|
+
font-size: var(--ngaf-chat-font-size-sm);
|
|
4095
|
+
cursor: pointer;
|
|
4096
|
+
transition: background 150ms ease, color 150ms ease;
|
|
4097
|
+
white-space: nowrap;
|
|
4098
|
+
}
|
|
4099
|
+
.chat-error__retry:hover {
|
|
4100
|
+
background: var(--ngaf-chat-error-border);
|
|
4101
|
+
color: var(--ngaf-chat-error-text);
|
|
4102
|
+
}
|
|
4103
|
+
.chat-error__retry:focus-visible {
|
|
4104
|
+
outline: 2px solid var(--ngaf-chat-error-border);
|
|
4105
|
+
outline-offset: 2px;
|
|
4106
|
+
}
|
|
3855
4107
|
`;
|
|
3856
4108
|
|
|
3857
4109
|
// libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
|
|
@@ -3867,31 +4119,36 @@ function extractErrorMessage(error) {
|
|
|
3867
4119
|
}
|
|
3868
4120
|
class ChatErrorComponent {
|
|
3869
4121
|
agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
3870
|
-
errorMessage = computed(() => extractErrorMessage(this.agent().error()), ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
3871
4122
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3872
4123
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatErrorComponent, isStandalone: true, selector: "chat-error", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
3873
|
-
@if (
|
|
4124
|
+
@if (agent().error(); as err) {
|
|
3874
4125
|
<div class="chat-error" role="alert">
|
|
3875
4126
|
<svg class="chat-error__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3876
4127
|
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
3877
4128
|
</svg>
|
|
3878
|
-
<span class="chat-error__msg">{{
|
|
4129
|
+
<span class="chat-error__msg">{{ err.message }}</span>
|
|
4130
|
+
@if (err.retryable) {
|
|
4131
|
+
<button type="button" class="chat-error__retry" (click)="agent().retry()">Retry</button>
|
|
4132
|
+
}
|
|
3879
4133
|
</div>
|
|
3880
4134
|
}
|
|
3881
|
-
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4135
|
+
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}.chat-error__retry{flex-shrink:0;background:transparent;color:var(--ngaf-chat-error-text);border:1px solid var(--ngaf-chat-error-border);border-radius:var(--ngaf-chat-radius-card);padding:2px 10px;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer;transition:background .15s ease,color .15s ease;white-space:nowrap}.chat-error__retry:hover{background:var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text)}.chat-error__retry:focus-visible{outline:2px solid var(--ngaf-chat-error-border);outline-offset:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3882
4136
|
}
|
|
3883
4137
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatErrorComponent, decorators: [{
|
|
3884
4138
|
type: Component,
|
|
3885
4139
|
args: [{ selector: 'chat-error', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3886
|
-
@if (
|
|
4140
|
+
@if (agent().error(); as err) {
|
|
3887
4141
|
<div class="chat-error" role="alert">
|
|
3888
4142
|
<svg class="chat-error__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
3889
4143
|
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
3890
4144
|
</svg>
|
|
3891
|
-
<span class="chat-error__msg">{{
|
|
4145
|
+
<span class="chat-error__msg">{{ err.message }}</span>
|
|
4146
|
+
@if (err.retryable) {
|
|
4147
|
+
<button type="button" class="chat-error__retry" (click)="agent().retry()">Retry</button>
|
|
4148
|
+
}
|
|
3892
4149
|
</div>
|
|
3893
4150
|
}
|
|
3894
|
-
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}\n"] }]
|
|
4151
|
+
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:block}.chat-error{display:flex;align-items:flex-start;gap:.5rem;background:var(--ngaf-chat-error-bg);border:1px solid var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text);border-radius:var(--ngaf-chat-radius-card);padding:8px 12px;font-size:var(--ngaf-chat-font-size-sm);margin:0 var(--ngaf-chat-space-6) var(--ngaf-chat-space-2)}.chat-error__icon{flex-shrink:0;width:16px;height:16px;margin-top:2px}.chat-error__msg{flex:1;min-width:0;word-break:break-word}.chat-error__retry{flex-shrink:0;background:transparent;color:var(--ngaf-chat-error-text);border:1px solid var(--ngaf-chat-error-border);border-radius:var(--ngaf-chat-radius-card);padding:2px 10px;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer;transition:background .15s ease,color .15s ease;white-space:nowrap}.chat-error__retry:hover{background:var(--ngaf-chat-error-border);color:var(--ngaf-chat-error-text)}.chat-error__retry:focus-visible{outline:2px solid var(--ngaf-chat-error-border);outline-offset:2px}\n"] }]
|
|
3895
4152
|
}], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }] } });
|
|
3896
4153
|
|
|
3897
4154
|
// SPDX-License-Identifier: MIT
|
|
@@ -3913,6 +4170,18 @@ const CHAT_INTERRUPT_STYLES = `
|
|
|
3913
4170
|
|
|
3914
4171
|
// libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
|
|
3915
4172
|
// SPDX-License-Identifier: MIT
|
|
4173
|
+
/**
|
|
4174
|
+
* Read the agent's current human-in-the-loop interrupt, if any.
|
|
4175
|
+
*
|
|
4176
|
+
* @param agent The agent to inspect.
|
|
4177
|
+
* @returns The pending {@link AgentInterrupt}, or `undefined` when the agent is
|
|
4178
|
+
* not currently waiting on an interrupt.
|
|
4179
|
+
* @example
|
|
4180
|
+
* ```ts
|
|
4181
|
+
* const interrupt = getInterrupt(agent);
|
|
4182
|
+
* if (interrupt) agent.resume('approved');
|
|
4183
|
+
* ```
|
|
4184
|
+
*/
|
|
3916
4185
|
function getInterrupt(agent) {
|
|
3917
4186
|
return agent.interrupt?.();
|
|
3918
4187
|
}
|
|
@@ -4093,6 +4362,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
4093
4362
|
`, 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"] }]
|
|
4094
4363
|
}], propDecorators: { toolCall: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolCall", required: true }] }], defaultCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultCollapsed", required: false }] }] } });
|
|
4095
4364
|
|
|
4365
|
+
// libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
|
|
4366
|
+
// SPDX-License-Identifier: MIT
|
|
4367
|
+
/**
|
|
4368
|
+
* Returns a CSS style string for a subagent's status badge.
|
|
4369
|
+
* Kept exported for backward compatibility with existing consumers; the
|
|
4370
|
+
* preferred way to style status visually is via the `data-status` attribute
|
|
4371
|
+
* + CSS selectors (see component styles below).
|
|
4372
|
+
*/
|
|
4373
|
+
function statusColor(status) {
|
|
4374
|
+
switch (status) {
|
|
4375
|
+
case 'pending': return 'background: var(--ngaf-chat-surface-alt); color: var(--ngaf-chat-text-muted);';
|
|
4376
|
+
case 'running': return 'background: var(--ngaf-chat-warning-bg); color: var(--ngaf-chat-warning-text);';
|
|
4377
|
+
case 'complete': return 'color: var(--ngaf-chat-success);';
|
|
4378
|
+
case 'error': return 'background: var(--ngaf-chat-error-bg); color: var(--ngaf-chat-error-text);';
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
function statusToTraceState(s) {
|
|
4382
|
+
switch (s) {
|
|
4383
|
+
case 'pending': return 'pending';
|
|
4384
|
+
case 'running': return 'running';
|
|
4385
|
+
case 'complete': return 'done';
|
|
4386
|
+
case 'error': return 'error';
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
class ChatSubagentCardComponent {
|
|
4390
|
+
subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
|
|
4391
|
+
state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
4392
|
+
textOf(m) {
|
|
4393
|
+
const c = m.content;
|
|
4394
|
+
return typeof c === 'string' ? c : '';
|
|
4395
|
+
}
|
|
4396
|
+
toolCallsFor(m) {
|
|
4397
|
+
const ids = m.toolCallIds ?? [];
|
|
4398
|
+
if (ids.length === 0)
|
|
4399
|
+
return [];
|
|
4400
|
+
const all = this.subagent().toolCalls?.() ?? [];
|
|
4401
|
+
return ids.map((id) => all.find((tc) => tc.id === id)).filter((tc) => !!tc);
|
|
4402
|
+
}
|
|
4403
|
+
toToolCallInfo(tc) {
|
|
4404
|
+
return { id: tc.id, name: tc.name, args: tc.args, result: tc.result, status: tc.status };
|
|
4405
|
+
}
|
|
4406
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4407
|
+
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: `
|
|
4408
|
+
<chat-trace [state]="state()">
|
|
4409
|
+
<span traceLabel>
|
|
4410
|
+
<span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
|
|
4411
|
+
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4412
|
+
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4413
|
+
</span>
|
|
4414
|
+
<div class="sac__count" traceMeta>{{ subagent().messages().length }} message(s)</div>
|
|
4415
|
+
@for (m of subagent().messages(); track m.id) {
|
|
4416
|
+
<div class="sac__msg" [attr.data-role]="m.role">
|
|
4417
|
+
@if (m.reasoning) {
|
|
4418
|
+
<div class="sac__reasoning">{{ m.reasoning }}</div>
|
|
4419
|
+
}
|
|
4420
|
+
@if (textOf(m); as t) {
|
|
4421
|
+
<chat-streaming-md [content]="t" />
|
|
4422
|
+
}
|
|
4423
|
+
@for (tc of toolCallsFor(m); track tc.id) {
|
|
4424
|
+
<chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
|
|
4425
|
+
}
|
|
4426
|
+
</div>
|
|
4427
|
+
}
|
|
4428
|
+
</chat-trace>
|
|
4429
|
+
`, 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 });
|
|
4430
|
+
}
|
|
4431
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
|
|
4432
|
+
type: Component,
|
|
4433
|
+
args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent, ChatToolCallCardComponent, ChatStreamingMdComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4434
|
+
<chat-trace [state]="state()">
|
|
4435
|
+
<span traceLabel>
|
|
4436
|
+
<span class="sac__name">{{ subagent().name ?? 'Subagent' }}</span>
|
|
4437
|
+
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4438
|
+
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4439
|
+
</span>
|
|
4440
|
+
<div class="sac__count" traceMeta>{{ subagent().messages().length }} message(s)</div>
|
|
4441
|
+
@for (m of subagent().messages(); track m.id) {
|
|
4442
|
+
<div class="sac__msg" [attr.data-role]="m.role">
|
|
4443
|
+
@if (m.reasoning) {
|
|
4444
|
+
<div class="sac__reasoning">{{ m.reasoning }}</div>
|
|
4445
|
+
}
|
|
4446
|
+
@if (textOf(m); as t) {
|
|
4447
|
+
<chat-streaming-md [content]="t" />
|
|
4448
|
+
}
|
|
4449
|
+
@for (tc of toolCallsFor(m); track tc.id) {
|
|
4450
|
+
<chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
|
|
4451
|
+
}
|
|
4452
|
+
</div>
|
|
4453
|
+
}
|
|
4454
|
+
</chat-trace>
|
|
4455
|
+
`, 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"] }]
|
|
4456
|
+
}], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
|
|
4457
|
+
|
|
4096
4458
|
// libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-call-template.directive.ts
|
|
4097
4459
|
// SPDX-License-Identifier: MIT
|
|
4098
4460
|
/**
|
|
@@ -4221,14 +4583,23 @@ class ChatToolCallsComponent {
|
|
|
4221
4583
|
groups = computed(() => {
|
|
4222
4584
|
const excludeSet = new Set(this.excludeToolNames());
|
|
4223
4585
|
const calls = this.toolCalls().filter(tc => !excludeSet.has(tc.name));
|
|
4586
|
+
const subs = this.agent().subagents?.() ?? new Map();
|
|
4224
4587
|
const groupingMode = this.grouping();
|
|
4225
4588
|
const registry = this.templateRegistry();
|
|
4226
4589
|
const wildcard = registry.get('*');
|
|
4227
4590
|
const out = [];
|
|
4228
4591
|
for (const tc of calls) {
|
|
4592
|
+
// A tool call that spawned a subagent renders as a standalone subagent
|
|
4593
|
+
// card anchored to that call. It never groups with adjacent calls, on
|
|
4594
|
+
// either side: it is its own group and carries a `subagent`, so the next
|
|
4595
|
+
// call can't append to it (a subagent group is never a group target).
|
|
4596
|
+
if (subs.has(tc.id)) {
|
|
4597
|
+
out.push({ name: tc.name, calls: [tc], subagent: subs.get(tc.id) });
|
|
4598
|
+
continue;
|
|
4599
|
+
}
|
|
4229
4600
|
const tpl = registry.get(tc.name) ?? wildcard;
|
|
4230
4601
|
const last = out[out.length - 1];
|
|
4231
|
-
const sameName = last && last.name === tc.name;
|
|
4602
|
+
const sameName = last && !last.subagent && last.name === tc.name;
|
|
4232
4603
|
const canGroup = groupingMode === 'auto' && sameName;
|
|
4233
4604
|
if (canGroup) {
|
|
4234
4605
|
last.calls.push(tc);
|
|
@@ -4262,7 +4633,9 @@ class ChatToolCallsComponent {
|
|
|
4262
4633
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolCallsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4263
4634
|
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: `
|
|
4264
4635
|
@for (group of groups(); track $index) {
|
|
4265
|
-
@if (group.
|
|
4636
|
+
@if (group.subagent) {
|
|
4637
|
+
<chat-subagent-card [subagent]="group.subagent" />
|
|
4638
|
+
} @else if (group.calls.length > 1 && !group.templateRef) {
|
|
4266
4639
|
<!-- Default grouped strip -->
|
|
4267
4640
|
@let expanded = expandedGroups().has($index);
|
|
4268
4641
|
<div class="ctc__group" [attr.data-group]="true" [attr.data-expanded]="expanded">
|
|
@@ -4293,13 +4666,15 @@ class ChatToolCallsComponent {
|
|
|
4293
4666
|
}
|
|
4294
4667
|
}
|
|
4295
4668
|
}
|
|
4296
|
-
`, 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 });
|
|
4669
|
+
`, 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 });
|
|
4297
4670
|
}
|
|
4298
4671
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatToolCallsComponent, decorators: [{
|
|
4299
4672
|
type: Component,
|
|
4300
|
-
args: [{ selector: 'chat-tool-calls', standalone: true, imports: [NgTemplateOutlet, ChatToolCallCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4673
|
+
args: [{ selector: 'chat-tool-calls', standalone: true, imports: [NgTemplateOutlet, ChatToolCallCardComponent, ChatSubagentCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4301
4674
|
@for (group of groups(); track $index) {
|
|
4302
|
-
@if (group.
|
|
4675
|
+
@if (group.subagent) {
|
|
4676
|
+
<chat-subagent-card [subagent]="group.subagent" />
|
|
4677
|
+
} @else if (group.calls.length > 1 && !group.templateRef) {
|
|
4303
4678
|
<!-- Default grouped strip -->
|
|
4304
4679
|
@let expanded = expandedGroups().has($index);
|
|
4305
4680
|
<div class="ctc__group" [attr.data-group]="true" [attr.data-expanded]="expanded">
|
|
@@ -4573,75 +4948,6 @@ function isRecord$2(v) {
|
|
|
4573
4948
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
4574
4949
|
}
|
|
4575
4950
|
|
|
4576
|
-
// libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
|
|
4577
|
-
// SPDX-License-Identifier: MIT
|
|
4578
|
-
/**
|
|
4579
|
-
* Returns a CSS style string for a subagent's status badge.
|
|
4580
|
-
* Kept exported for backward compatibility with existing consumers; the
|
|
4581
|
-
* preferred way to style status visually is via the `data-status` attribute
|
|
4582
|
-
* + CSS selectors (see component styles below).
|
|
4583
|
-
*/
|
|
4584
|
-
function statusColor(status) {
|
|
4585
|
-
switch (status) {
|
|
4586
|
-
case 'pending': return 'background: var(--ngaf-chat-surface-alt); color: var(--ngaf-chat-text-muted);';
|
|
4587
|
-
case 'running': return 'background: var(--ngaf-chat-warning-bg); color: var(--ngaf-chat-warning-text);';
|
|
4588
|
-
case 'complete': return 'color: var(--ngaf-chat-success);';
|
|
4589
|
-
case 'error': return 'background: var(--ngaf-chat-error-bg); color: var(--ngaf-chat-error-text);';
|
|
4590
|
-
}
|
|
4591
|
-
}
|
|
4592
|
-
function statusToTraceState(s) {
|
|
4593
|
-
switch (s) {
|
|
4594
|
-
case 'pending': return 'pending';
|
|
4595
|
-
case 'running': return 'running';
|
|
4596
|
-
case 'complete': return 'done';
|
|
4597
|
-
case 'error': return 'error';
|
|
4598
|
-
}
|
|
4599
|
-
}
|
|
4600
|
-
class ChatSubagentCardComponent {
|
|
4601
|
-
subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
|
|
4602
|
-
state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
4603
|
-
latestMessageContent = computed(() => {
|
|
4604
|
-
const messages = this.subagent().messages();
|
|
4605
|
-
if (messages.length === 0)
|
|
4606
|
-
return '';
|
|
4607
|
-
const last = messages[messages.length - 1];
|
|
4608
|
-
const c = last.content;
|
|
4609
|
-
return typeof c === 'string' ? c : JSON.stringify(c);
|
|
4610
|
-
}, ...(ngDevMode ? [{ debugName: "latestMessageContent" }] : []));
|
|
4611
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4612
|
-
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: `
|
|
4613
|
-
<chat-trace [state]="state()">
|
|
4614
|
-
<span traceLabel>
|
|
4615
|
-
<span class="sac__name">Subagent</span>
|
|
4616
|
-
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4617
|
-
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4618
|
-
</span>
|
|
4619
|
-
<div class="sac__count">{{ subagent().messages().length }} message(s)</div>
|
|
4620
|
-
@if (subagent().messages().length > 0) {
|
|
4621
|
-
<p class="sac__latest-label">Latest message</p>
|
|
4622
|
-
<pre class="sac__latest">{{ latestMessageContent() }}</pre>
|
|
4623
|
-
}
|
|
4624
|
-
</chat-trace>
|
|
4625
|
-
`, 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__latest-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ngaf-chat-text-muted);margin:8px 0 4px}.sac__latest{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"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4626
|
-
}
|
|
4627
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
|
|
4628
|
-
type: Component,
|
|
4629
|
-
args: [{ selector: 'chat-subagent-card', standalone: true, imports: [ChatTraceComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4630
|
-
<chat-trace [state]="state()">
|
|
4631
|
-
<span traceLabel>
|
|
4632
|
-
<span class="sac__name">Subagent</span>
|
|
4633
|
-
<span class="sac__id">{{ subagent().toolCallId }}</span>
|
|
4634
|
-
<span class="sac__pill" [attr.data-status]="subagent().status()">{{ subagent().status() }}</span>
|
|
4635
|
-
</span>
|
|
4636
|
-
<div class="sac__count">{{ subagent().messages().length }} message(s)</div>
|
|
4637
|
-
@if (subagent().messages().length > 0) {
|
|
4638
|
-
<p class="sac__latest-label">Latest message</p>
|
|
4639
|
-
<pre class="sac__latest">{{ latestMessageContent() }}</pre>
|
|
4640
|
-
}
|
|
4641
|
-
</chat-trace>
|
|
4642
|
-
`, 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__latest-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--ngaf-chat-text-muted);margin:8px 0 4px}.sac__latest{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"] }]
|
|
4643
|
-
}], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
|
|
4644
|
-
|
|
4645
4951
|
// libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
|
|
4646
4952
|
// SPDX-License-Identifier: MIT
|
|
4647
4953
|
function activeSubagentsFromAgent(agent) {
|
|
@@ -6193,10 +6499,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
6193
6499
|
class ChatWelcomeSuggestionComponent {
|
|
6194
6500
|
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
6195
6501
|
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
6502
|
+
/** Optional short description, surfaced as a hover/focus tooltip on the chip. */
|
|
6503
|
+
description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
|
|
6196
6504
|
selected = output();
|
|
6197
6505
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatWelcomeSuggestionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6198
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatWelcomeSuggestionComponent, isStandalone: true, selector: "chat-welcome-suggestion", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: `
|
|
6199
|
-
<button
|
|
6506
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.6", type: ChatWelcomeSuggestionComponent, isStandalone: true, selector: "chat-welcome-suggestion", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, ngImport: i0, template: `
|
|
6507
|
+
<button
|
|
6508
|
+
type="button"
|
|
6509
|
+
class="chat-welcome-suggestion"
|
|
6510
|
+
[attr.title]="description() || null"
|
|
6511
|
+
[attr.aria-description]="description() || null"
|
|
6512
|
+
(click)="selected.emit(value())"
|
|
6513
|
+
>
|
|
6200
6514
|
<ng-content select="[chatWelcomeSuggestionIcon]" />
|
|
6201
6515
|
<span class="chat-welcome-suggestion__label">{{ label() }}</span>
|
|
6202
6516
|
<span class="chat-welcome-suggestion__chevron" aria-hidden="true">›</span>
|
|
@@ -6206,13 +6520,19 @@ class ChatWelcomeSuggestionComponent {
|
|
|
6206
6520
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatWelcomeSuggestionComponent, decorators: [{
|
|
6207
6521
|
type: Component,
|
|
6208
6522
|
args: [{ selector: 'chat-welcome-suggestion', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
6209
|
-
<button
|
|
6523
|
+
<button
|
|
6524
|
+
type="button"
|
|
6525
|
+
class="chat-welcome-suggestion"
|
|
6526
|
+
[attr.title]="description() || null"
|
|
6527
|
+
[attr.aria-description]="description() || null"
|
|
6528
|
+
(click)="selected.emit(value())"
|
|
6529
|
+
>
|
|
6210
6530
|
<ng-content select="[chatWelcomeSuggestionIcon]" />
|
|
6211
6531
|
<span class="chat-welcome-suggestion__label">{{ label() }}</span>
|
|
6212
6532
|
<span class="chat-welcome-suggestion__chevron" aria-hidden="true">›</span>
|
|
6213
6533
|
</button>
|
|
6214
6534
|
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block}.chat-welcome-suggestion{display:inline-flex;align-items:center;gap:.5rem;padding:10px 16px;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:9999px;color:var(--ngaf-chat-text);font-family:inherit;font-size:var(--ngaf-chat-font-size-sm);text-align:center;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .12s ease}.chat-welcome-suggestion:hover{background:var(--ngaf-chat-surface-alt);border-color:var(--ngaf-chat-text-muted)}.chat-welcome-suggestion:active{transform:scale(.98)}.chat-welcome-suggestion:focus-visible{outline:2px solid var(--ngaf-chat-text-muted);outline-offset:2px}.chat-welcome-suggestion__label{white-space:nowrap}.chat-welcome-suggestion__chevron{display:none}\n"] }]
|
|
6215
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
6535
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }] } });
|
|
6216
6536
|
|
|
6217
6537
|
// libs/chat/src/lib/styles/chat-select.styles.ts
|
|
6218
6538
|
// SPDX-License-Identifier: MIT
|
|
@@ -6268,7 +6588,10 @@ const CHAT_SELECT_STYLES = `
|
|
|
6268
6588
|
z-index: 10;
|
|
6269
6589
|
}
|
|
6270
6590
|
.chat-select__option {
|
|
6271
|
-
display:
|
|
6591
|
+
display: flex;
|
|
6592
|
+
flex-direction: column;
|
|
6593
|
+
align-items: flex-start;
|
|
6594
|
+
gap: 2px;
|
|
6272
6595
|
width: 100%;
|
|
6273
6596
|
text-align: left;
|
|
6274
6597
|
border: 0;
|
|
@@ -6280,6 +6603,12 @@ const CHAT_SELECT_STYLES = `
|
|
|
6280
6603
|
font-size: var(--ngaf-chat-font-size-sm);
|
|
6281
6604
|
cursor: pointer;
|
|
6282
6605
|
}
|
|
6606
|
+
.chat-select__option-desc {
|
|
6607
|
+
font-size: var(--ngaf-chat-font-size-xs);
|
|
6608
|
+
color: var(--ngaf-chat-text-muted);
|
|
6609
|
+
line-height: 1.3;
|
|
6610
|
+
white-space: normal;
|
|
6611
|
+
}
|
|
6283
6612
|
.chat-select__option:hover:not(:disabled),
|
|
6284
6613
|
.chat-select__option:focus-visible {
|
|
6285
6614
|
background: var(--ngaf-chat-surface-alt);
|
|
@@ -6458,12 +6787,15 @@ class ChatSelectComponent {
|
|
|
6458
6787
|
[attr.aria-selected]="opt.value === value()"
|
|
6459
6788
|
(click)="selectOption(opt)"
|
|
6460
6789
|
>
|
|
6461
|
-
{{ opt.label }}
|
|
6790
|
+
<span class="chat-select__option-label">{{ opt.label }}</span>
|
|
6791
|
+
@if (opt.description) {
|
|
6792
|
+
<span class="chat-select__option-desc">{{ opt.description }}</span>
|
|
6793
|
+
}
|
|
6462
6794
|
</button>
|
|
6463
6795
|
}
|
|
6464
6796
|
</div>
|
|
6465
6797
|
}
|
|
6466
|
-
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:
|
|
6798
|
+
`, isInline: true, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;border:0;background:transparent;padding:8px 10px;border-radius:8px;color:var(--ngaf-chat-text);font:inherit;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer}.chat-select__option-desc{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);line-height:1.3;white-space:normal}.chat-select__option:hover:not(:disabled),.chat-select__option:focus-visible{background:var(--ngaf-chat-surface-alt);outline:none}.chat-select__option.is-active{background:var(--ngaf-chat-surface-alt);font-weight:500}.chat-select__option:disabled{opacity:.4;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6467
6799
|
}
|
|
6468
6800
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSelectComponent, decorators: [{
|
|
6469
6801
|
type: Component,
|
|
@@ -6501,17 +6833,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
6501
6833
|
[attr.aria-selected]="opt.value === value()"
|
|
6502
6834
|
(click)="selectOption(opt)"
|
|
6503
6835
|
>
|
|
6504
|
-
{{ opt.label }}
|
|
6836
|
+
<span class="chat-select__option-label">{{ opt.label }}</span>
|
|
6837
|
+
@if (opt.description) {
|
|
6838
|
+
<span class="chat-select__option-desc">{{ opt.description }}</span>
|
|
6839
|
+
}
|
|
6505
6840
|
</button>
|
|
6506
6841
|
}
|
|
6507
6842
|
</div>
|
|
6508
6843
|
}
|
|
6509
|
-
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:
|
|
6844
|
+
`, styles: [":host{font-family:var(--ngaf-chat-font-family);color:var(--ngaf-chat-text)}\n", ":host{display:inline-block;position:relative}.chat-select__trigger{height:32px;padding:0 10px;border:0;border-radius:9999px;background:transparent;color:var(--ngaf-chat-text-muted);font:inherit;font-size:var(--ngaf-chat-font-size-sm);display:inline-flex;align-items:center;gap:4px;cursor:pointer;transition:background .12s ease,color .12s ease}.chat-select__trigger:hover:not(:disabled){background:var(--ngaf-chat-surface-alt);color:var(--ngaf-chat-text)}.chat-select__trigger:disabled{opacity:.5;cursor:not-allowed}.chat-select__chevron{width:12px;height:12px;transition:transform .12s ease;flex:none}.chat-select__trigger.is-open .chat-select__chevron{transform:rotate(180deg)}.chat-select__menu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:180px;max-height:320px;overflow-y:auto;background:var(--ngaf-chat-surface);border:1px solid var(--ngaf-chat-separator);border-radius:12px;box-shadow:var(--ngaf-chat-shadow-lg);padding:4px;z-index:10}.chat-select__option{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;border:0;background:transparent;padding:8px 10px;border-radius:8px;color:var(--ngaf-chat-text);font:inherit;font-size:var(--ngaf-chat-font-size-sm);cursor:pointer}.chat-select__option-desc{font-size:var(--ngaf-chat-font-size-xs);color:var(--ngaf-chat-text-muted);line-height:1.3;white-space:normal}.chat-select__option:hover:not(:disabled),.chat-select__option:focus-visible{background:var(--ngaf-chat-surface-alt);outline:none}.chat-select__option.is-active{background:var(--ngaf-chat-surface-alt);font-weight:500}.chat-select__option:disabled{opacity:.4;cursor:not-allowed}\n"] }]
|
|
6510
6845
|
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], menuLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuLabel", required: false }] }] } });
|
|
6511
6846
|
|
|
6512
6847
|
// SPDX-License-Identifier: MIT
|
|
6513
6848
|
const PACKAGE_NAME = '@threadplane/chat';
|
|
6514
6849
|
const CHAT_CONFIG = new InjectionToken('CHAT_CONFIG');
|
|
6850
|
+
/**
|
|
6851
|
+
* Bootstrap `@threadplane/chat` in an Angular application or standalone
|
|
6852
|
+
* component tree.
|
|
6853
|
+
*
|
|
6854
|
+
* Call this once inside `bootstrapApplication` (or the `providers` array of a
|
|
6855
|
+
* root `ApplicationConfig`). It registers the shared {@link ChatConfig} token
|
|
6856
|
+
* so every chat component in the tree can read the render registry, avatar
|
|
6857
|
+
* label, and assistant display name without explicit prop threading.
|
|
6858
|
+
*
|
|
6859
|
+
* A license check is fired asynchronously on every call (it never throws; a
|
|
6860
|
+
* watermark is shown in non-commercial builds when no valid token is supplied).
|
|
6861
|
+
*
|
|
6862
|
+
* @param config Options bag that controls the chat feature set:
|
|
6863
|
+
* - `renderRegistry` — shared {@link AngularRegistry} wiring tool-view
|
|
6864
|
+
* components to their names; pass the value returned by
|
|
6865
|
+
* `defineAngularRegistry` from `\@threadplane/render`.
|
|
6866
|
+
* - `avatarLabel` — short label shown in the AI avatar bubble (default `"A"`).
|
|
6867
|
+
* - `assistantName` — display name shown above assistant messages
|
|
6868
|
+
* (default `"Assistant"`).
|
|
6869
|
+
* - `license` — signed token from threadplane.ai; omit in development.
|
|
6870
|
+
* @returns An `EnvironmentProviders` value suitable for the `providers` array
|
|
6871
|
+
* of `bootstrapApplication` or `ApplicationConfig`.
|
|
6872
|
+
* @example
|
|
6873
|
+
* ```ts
|
|
6874
|
+
* // main.ts
|
|
6875
|
+
* import { bootstrapApplication } from '@angular/platform-browser';
|
|
6876
|
+
* import { provideChat } from '@threadplane/chat';
|
|
6877
|
+
* import { defineAngularRegistry, provideRender } from '@threadplane/render';
|
|
6878
|
+
* import { DayCardComponent } from './day-card.component';
|
|
6879
|
+
*
|
|
6880
|
+
* const registry = defineAngularRegistry({ day_card: DayCardComponent });
|
|
6881
|
+
*
|
|
6882
|
+
* bootstrapApplication(AppComponent, {
|
|
6883
|
+
* providers: [
|
|
6884
|
+
* provideChat({ renderRegistry: registry, avatarLabel: 'AI' }),
|
|
6885
|
+
* provideRender({ registry }),
|
|
6886
|
+
* ],
|
|
6887
|
+
* });
|
|
6888
|
+
* ```
|
|
6889
|
+
*/
|
|
6515
6890
|
function provideChat(config) {
|
|
6516
6891
|
void runLicenseCheck({
|
|
6517
6892
|
package: PACKAGE_NAME,
|
|
@@ -6524,6 +6899,65 @@ function provideChat(config) {
|
|
|
6524
6899
|
]);
|
|
6525
6900
|
}
|
|
6526
6901
|
|
|
6902
|
+
// SPDX-License-Identifier: MIT
|
|
6903
|
+
const defaultFromUrl = (url) => {
|
|
6904
|
+
const seg = url.split('?')[0].split('#')[0].split('/').filter(Boolean);
|
|
6905
|
+
return seg.length ? seg[seg.length - 1] : null;
|
|
6906
|
+
};
|
|
6907
|
+
const defaultToCommands = (id) => (id ? ['/', id] : ['/']);
|
|
6908
|
+
/**
|
|
6909
|
+
* Bind an app-owned `activeThreadId` signal to the URL — restore on load, stamp on change,
|
|
6910
|
+
* validate-or-redirect, with a bare URL meaning "no thread" (welcome). URL is the source of
|
|
6911
|
+
* truth; nothing is written to localStorage. Must be called in an injection context.
|
|
6912
|
+
*
|
|
6913
|
+
* @example
|
|
6914
|
+
* ```ts
|
|
6915
|
+
* export const ACTIVE_THREAD = signal<string | null>(null);
|
|
6916
|
+
* // providers: provideAgent({ threadId: ACTIVE_THREAD, onThreadId: id => ACTIVE_THREAD.set(id) })
|
|
6917
|
+
* const threads = inject(LangGraphThreadsAdapter);
|
|
6918
|
+
* injectThreadRouting({ threadId: ACTIVE_THREAD, validate: id => threads.getThread(id).then(Boolean) });
|
|
6919
|
+
* ```
|
|
6920
|
+
*/
|
|
6921
|
+
function injectThreadRouting(config) {
|
|
6922
|
+
const router = inject(Router);
|
|
6923
|
+
const fromUrl = config.threadIdFromUrl ?? defaultFromUrl;
|
|
6924
|
+
const toCommands = config.toCommands ?? defaultToCommands;
|
|
6925
|
+
const extras = config.navigationExtras ?? { queryParamsHandling: 'preserve' };
|
|
6926
|
+
const urlThreadId = toSignal(router.events.pipe(filter((e) => e instanceof NavigationEnd), map((e) => fromUrl(e.urlAfterRedirects)), startWith(fromUrl(router.url))), { initialValue: fromUrl(router.url) });
|
|
6927
|
+
// Seed the signal from the URL once.
|
|
6928
|
+
config.threadId.set(urlThreadId());
|
|
6929
|
+
// URL → signal.
|
|
6930
|
+
effect(() => {
|
|
6931
|
+
const urlId = urlThreadId();
|
|
6932
|
+
if (urlId !== untracked(() => config.threadId()))
|
|
6933
|
+
config.threadId.set(urlId);
|
|
6934
|
+
});
|
|
6935
|
+
// signal → URL.
|
|
6936
|
+
effect(() => {
|
|
6937
|
+
const id = config.threadId();
|
|
6938
|
+
const urlId = untracked(() => urlThreadId());
|
|
6939
|
+
if (id !== urlId)
|
|
6940
|
+
void router.navigate(toCommands(id), extras);
|
|
6941
|
+
});
|
|
6942
|
+
// validate stale ids → redirect to bare. Memoize the last id checked so
|
|
6943
|
+
// re-visiting the same thread (A → B → A) doesn't re-hit the backend.
|
|
6944
|
+
if (config.validate) {
|
|
6945
|
+
const validate = config.validate;
|
|
6946
|
+
let lastValidated = null;
|
|
6947
|
+
effect(() => {
|
|
6948
|
+
const id = urlThreadId();
|
|
6949
|
+
if (!id || id === lastValidated)
|
|
6950
|
+
return;
|
|
6951
|
+
lastValidated = id;
|
|
6952
|
+
void validate(id).then((ok) => {
|
|
6953
|
+
if (!ok && untracked(() => urlThreadId()) === id) {
|
|
6954
|
+
void router.navigate(toCommands(null), { ...extras, replaceUrl: true });
|
|
6955
|
+
}
|
|
6956
|
+
});
|
|
6957
|
+
});
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6960
|
+
|
|
6527
6961
|
// SPDX-License-Identifier: MIT
|
|
6528
6962
|
const CHAT_LIFECYCLE = new InjectionToken('CHAT_LIFECYCLE');
|
|
6529
6963
|
|
|
@@ -7084,6 +7518,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
7084
7518
|
}], 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"] }] } });
|
|
7085
7519
|
|
|
7086
7520
|
// SPDX-License-Identifier: MIT
|
|
7521
|
+
/**
|
|
7522
|
+
* Create a {@link ParseTreeStore} — feeds streamed JSON chunks through a
|
|
7523
|
+
* partial-JSON parser and exposes the progressively-materialized spec and
|
|
7524
|
+
* per-element accumulation state as signals, so a generative-UI surface can
|
|
7525
|
+
* render while the spec is still arriving.
|
|
7526
|
+
*
|
|
7527
|
+
* @param parser The partial-JSON parser used to incrementally materialize chunks.
|
|
7528
|
+
* @returns A {@link ParseTreeStore}; call `push(chunk)` as bytes stream in.
|
|
7529
|
+
* @example
|
|
7530
|
+
* ```ts
|
|
7531
|
+
* const store = createParseTreeStore(parser);
|
|
7532
|
+
* store.push('{"type":"Car');
|
|
7533
|
+
* store.spec(); // best-effort Spec | null
|
|
7534
|
+
* ```
|
|
7535
|
+
*/
|
|
7087
7536
|
function createParseTreeStore(parser) {
|
|
7088
7537
|
const specSignal = signal(null, ...(ngDevMode ? [{ debugName: "specSignal" }] : []));
|
|
7089
7538
|
const elementStatesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "elementStatesSignal" }] : []));
|
|
@@ -7224,6 +7673,19 @@ function resolveProps(value, dataModel) {
|
|
|
7224
7673
|
}
|
|
7225
7674
|
return value;
|
|
7226
7675
|
}
|
|
7676
|
+
/**
|
|
7677
|
+
* Create an {@link A2uiSurfaceStore} — the per-conversation store that buffers
|
|
7678
|
+
* streamed A2UI surface updates, tracks each surface's data model + lifecycle
|
|
7679
|
+
* state, and exposes them as signals for rendering. One store backs a chat
|
|
7680
|
+
* thread's A2UI surfaces.
|
|
7681
|
+
*
|
|
7682
|
+
* @returns A fresh, empty {@link A2uiSurfaceStore}.
|
|
7683
|
+
* @example
|
|
7684
|
+
* ```ts
|
|
7685
|
+
* const store = createA2uiSurfaceStore();
|
|
7686
|
+
* const surfaces = store.surfaces; // Signal<Map<string, A2uiSurface>>
|
|
7687
|
+
* ```
|
|
7688
|
+
*/
|
|
7227
7689
|
function createA2uiSurfaceStore() {
|
|
7228
7690
|
const surfacesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "surfacesSignal" }] : []));
|
|
7229
7691
|
const surfaceStatesSignal = signal(new Map(), ...(ngDevMode ? [{ debugName: "surfaceStatesSignal" }] : []));
|
|
@@ -7445,6 +7907,19 @@ function trace(...args) {
|
|
|
7445
7907
|
|
|
7446
7908
|
// SPDX-License-Identifier: MIT
|
|
7447
7909
|
const A2UI_PREFIX = '---a2ui_JSON---';
|
|
7910
|
+
/**
|
|
7911
|
+
* Create a {@link ContentClassifier} — the streaming accumulator that inspects
|
|
7912
|
+
* an assistant message's content as it arrives and classifies it (markdown vs a
|
|
7913
|
+
* generative-UI/A2UI spec), exposing the parsed result and per-element state as
|
|
7914
|
+
* signals so the renderer can switch modes mid-stream.
|
|
7915
|
+
*
|
|
7916
|
+
* @returns A fresh {@link ContentClassifier}; call `dispose()` when done.
|
|
7917
|
+
* @example
|
|
7918
|
+
* ```ts
|
|
7919
|
+
* const cc = createContentClassifier();
|
|
7920
|
+
* effect(() => console.log(cc.type())); // 'pending' | 'markdown' | 'spec'
|
|
7921
|
+
* ```
|
|
7922
|
+
*/
|
|
7448
7923
|
function createContentClassifier() {
|
|
7449
7924
|
const typeSignal = signal('pending', ...(ngDevMode ? [{ debugName: "typeSignal" }] : []));
|
|
7450
7925
|
const markdownSignal = signal('', ...(ngDevMode ? [{ debugName: "markdownSignal" }] : []));
|
|
@@ -8895,7 +9370,6 @@ class ChatComponent {
|
|
|
8895
9370
|
[handlers]="handlers()"
|
|
8896
9371
|
(events)="onClientToolEvent($event)"
|
|
8897
9372
|
/>
|
|
8898
|
-
<chat-subagents [agent]="agent()" />
|
|
8899
9373
|
@if (classified.markdown(); as md) {
|
|
8900
9374
|
<chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
|
|
8901
9375
|
}
|
|
@@ -8983,7 +9457,7 @@ class ChatComponent {
|
|
|
8983
9457
|
</div>
|
|
8984
9458
|
</div>
|
|
8985
9459
|
}
|
|
8986
|
-
`, 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:
|
|
9460
|
+
`, 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 });
|
|
8987
9461
|
}
|
|
8988
9462
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
|
|
8989
9463
|
type: Component,
|
|
@@ -8992,7 +9466,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
8992
9466
|
ChatWindowComponent, ChatMessageListComponent, MessageTemplateDirective, ChatMessageComponent,
|
|
8993
9467
|
ChatInputComponent, ChatTypingIndicatorComponent, ChatErrorComponent,
|
|
8994
9468
|
ChatThreadListComponent, ChatGenerativeUiComponent,
|
|
8995
|
-
ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent,
|
|
9469
|
+
ChatStreamingMdComponent, ChatToolCallsComponent, ChatToolViewsComponent, A2uiSurfaceComponent,
|
|
8996
9470
|
ChatMessageActionsComponent, ChatWelcomeComponent, ChatSelectComponent, ChatReasoningComponent,
|
|
8997
9471
|
ChatScrollBubbleComponent,
|
|
8998
9472
|
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
@@ -9065,7 +9539,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
9065
9539
|
[handlers]="handlers()"
|
|
9066
9540
|
(events)="onClientToolEvent($event)"
|
|
9067
9541
|
/>
|
|
9068
|
-
<chat-subagents [agent]="agent()" />
|
|
9069
9542
|
@if (classified.markdown(); as md) {
|
|
9070
9543
|
<chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
|
|
9071
9544
|
}
|
|
@@ -10822,22 +11295,6 @@ function renderMarkdownToString(content, sanitizer) {
|
|
|
10822
11295
|
return plainTextToHtml(content);
|
|
10823
11296
|
}
|
|
10824
11297
|
|
|
10825
|
-
// SPDX-License-Identifier: MIT
|
|
10826
|
-
/** Chevron down (▼ replacement). 12x12, stroke-based. */
|
|
10827
|
-
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>`;
|
|
10828
|
-
/** Chevron up (▲ replacement). 12x12, stroke-based. */
|
|
10829
|
-
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>`;
|
|
10830
|
-
/** Gear icon (⚙ replacement). 14x14. */
|
|
10831
|
-
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>`;
|
|
10832
|
-
/** Warning triangle (⚠ replacement). 18x18. */
|
|
10833
|
-
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>`;
|
|
10834
|
-
/** Robot/agent icon (replacement). 14x14. */
|
|
10835
|
-
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>`;
|
|
10836
|
-
/** Check mark replacement. 12x12. */
|
|
10837
|
-
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>`;
|
|
10838
|
-
/** Send arrow (for chat input). 16x16. */
|
|
10839
|
-
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>`;
|
|
10840
|
-
|
|
10841
11298
|
/** Normalize a catalog entry to the `A2uiViewEntry` shape. Bare
|
|
10842
11299
|
* `Type<unknown>` entries are wrapped as `{ component }`; entries
|
|
10843
11300
|
* already in the discriminated shape are returned unchanged. */
|
|
@@ -11865,6 +12322,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
11865
12322
|
}], 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 }] }] } });
|
|
11866
12323
|
|
|
11867
12324
|
// SPDX-License-Identifier: MIT
|
|
12325
|
+
/**
|
|
12326
|
+
* Build the built-in A2UI component catalog — a {@link ViewRegistry} mapping
|
|
12327
|
+
* the standard A2UI element types (Card, Button, TextField, Image, AudioPlayer,
|
|
12328
|
+
* Video, …) to their Angular renderers. Spread it into `provideViews` (with any
|
|
12329
|
+
* of your own views) so an agent's A2UI surface specs render.
|
|
12330
|
+
*
|
|
12331
|
+
* @returns A {@link ViewRegistry} of the standard A2UI components.
|
|
12332
|
+
* @example
|
|
12333
|
+
* ```ts
|
|
12334
|
+
* providers: [provideViews({ ...a2uiBasicCatalog(), MyWidget: MyWidgetComponent })]
|
|
12335
|
+
* ```
|
|
12336
|
+
*/
|
|
11868
12337
|
function a2uiBasicCatalog() {
|
|
11869
12338
|
return views({
|
|
11870
12339
|
AudioPlayer: A2uiAudioPlayerComponent,
|
|
@@ -11888,29 +12357,140 @@ function a2uiBasicCatalog() {
|
|
|
11888
12357
|
});
|
|
11889
12358
|
}
|
|
11890
12359
|
|
|
11891
|
-
/**
|
|
12360
|
+
/**
|
|
12361
|
+
* Declare an async function tool the model can call; its resolved return value
|
|
12362
|
+
* becomes the tool result shipped back to the model.
|
|
12363
|
+
*
|
|
12364
|
+
* @param description Natural-language description the model sees.
|
|
12365
|
+
* @param schema Standard Schema (e.g. a Zod object) for the arguments; the
|
|
12366
|
+
* handler's argument type is inferred from it.
|
|
12367
|
+
* @param handler Runs in the browser when the model calls the tool; its return
|
|
12368
|
+
* type `R` is carried on the resulting {@link FunctionToolDef}.
|
|
12369
|
+
* @returns A {@link FunctionToolDef} for inclusion in {@link tools}.
|
|
12370
|
+
* @example
|
|
12371
|
+
* ```ts
|
|
12372
|
+
* const move = action('Move a stop', z.object({ fromDay: z.number() }), (a) => a.fromDay);
|
|
12373
|
+
* const registry = tools({ move_stop: move });
|
|
12374
|
+
* ```
|
|
12375
|
+
*/
|
|
11892
12376
|
function action(description, schema, handler) {
|
|
11893
12377
|
return { kind: 'function', description, schema, handler };
|
|
11894
12378
|
}
|
|
11895
|
-
/**
|
|
12379
|
+
/**
|
|
12380
|
+
* Render-only component tool — the model fills the component's props from the
|
|
12381
|
+
* schema's output; the tool call is auto-acknowledged once the component mounts.
|
|
12382
|
+
*
|
|
12383
|
+
* The component's signal inputs are checked against the schema output type
|
|
12384
|
+
* (strict-but-flexible: every schema key must be a declared input with an
|
|
12385
|
+
* assignable type; the component may declare extra inputs the schema doesn't fill).
|
|
12386
|
+
* Author the component with `ViewProps<typeof schema>` as the input type set to
|
|
12387
|
+
* guarantee the shapes stay aligned.
|
|
12388
|
+
*
|
|
12389
|
+
* @param description Natural-language description the model sees.
|
|
12390
|
+
* @param schema Standard Schema defining the props the model must supply.
|
|
12391
|
+
* @param component Angular component whose signal inputs must be compatible with
|
|
12392
|
+
* the schema output. A type-level error is reported here when they diverge.
|
|
12393
|
+
* @returns A {@link ViewToolDef} for inclusion in {@link tools}.
|
|
12394
|
+
* @example
|
|
12395
|
+
* ```ts
|
|
12396
|
+
* const schema = z.object({ label: z.string(), day: z.number() });
|
|
12397
|
+
* type Inputs = ViewProps<typeof schema>; // { label: string; day: number }
|
|
12398
|
+
*
|
|
12399
|
+
* \@Component({ ... })
|
|
12400
|
+
* class DayCardComponent {
|
|
12401
|
+
* label = input.required<string>();
|
|
12402
|
+
* day = input.required<number>();
|
|
12403
|
+
* }
|
|
12404
|
+
*
|
|
12405
|
+
* const dayCard = view('Show a day card', schema, DayCardComponent);
|
|
12406
|
+
* const registry = tools({ day_card: dayCard });
|
|
12407
|
+
* ```
|
|
12408
|
+
*/
|
|
11896
12409
|
function view(description, schema, component) {
|
|
11897
|
-
return { kind: 'view', description, schema, component };
|
|
12410
|
+
return { kind: 'view', description, schema, component: component };
|
|
11898
12411
|
}
|
|
11899
|
-
/**
|
|
12412
|
+
/**
|
|
12413
|
+
* Interactive (human-in-the-loop) component tool — the model fills the
|
|
12414
|
+
* component's props from the schema's output; the value the component emits
|
|
12415
|
+
* back to the framework becomes the tool result sent to the model.
|
|
12416
|
+
*
|
|
12417
|
+
* The component's signal inputs are checked against the schema output type
|
|
12418
|
+
* (strict-but-flexible: every schema key must be a declared input with an
|
|
12419
|
+
* assignable type; the component may declare extra inputs the schema doesn't
|
|
12420
|
+
* fill). Author the component with `ViewProps<typeof schema>` to derive input
|
|
12421
|
+
* prop types directly from the schema.
|
|
12422
|
+
*
|
|
12423
|
+
* @param description Natural-language description the model sees.
|
|
12424
|
+
* @param schema Standard Schema defining the props the model must supply.
|
|
12425
|
+
* @param component Angular component whose signal inputs must be compatible with
|
|
12426
|
+
* the schema output. A type-level error is reported here when they diverge.
|
|
12427
|
+
* @returns An {@link AskToolDef} for inclusion in {@link tools}.
|
|
12428
|
+
* @example
|
|
12429
|
+
* ```ts
|
|
12430
|
+
* const schema = z.object({ question: z.string(), options: z.array(z.string()) });
|
|
12431
|
+
* type Inputs = ViewProps<typeof schema>;
|
|
12432
|
+
*
|
|
12433
|
+
* \@Component({ ... })
|
|
12434
|
+
* class ChoiceCardComponent {
|
|
12435
|
+
* question = input.required<string>();
|
|
12436
|
+
* options = input.required<string[]>();
|
|
12437
|
+
* // Emits the chosen option back to the model.
|
|
12438
|
+
* }
|
|
12439
|
+
*
|
|
12440
|
+
* const choice = ask('Ask the user to choose', schema, ChoiceCardComponent);
|
|
12441
|
+
* const registry = tools({ pick_option: choice });
|
|
12442
|
+
* ```
|
|
12443
|
+
*/
|
|
11900
12444
|
function ask(description, schema, component) {
|
|
11901
|
-
return { kind: 'ask', description, schema, component };
|
|
12445
|
+
return { kind: 'ask', description, schema, component: component };
|
|
11902
12446
|
}
|
|
11903
|
-
/**
|
|
12447
|
+
/**
|
|
12448
|
+
* Collect named client tools into a frozen, name-keyed registry.
|
|
12449
|
+
*
|
|
12450
|
+
* The overload is generic over the entire map (`const M`) so that each tool's
|
|
12451
|
+
* precise type ({@link FunctionToolDef}`<S,R>`, {@link ViewToolDef}`<S,C>`, or
|
|
12452
|
+
* {@link AskToolDef}`<S,C>`) and every literal key are preserved in the
|
|
12453
|
+
* {@link ClientToolRegistry} passed to `provideChat`. This lets downstream
|
|
12454
|
+
* consumers look up individual tools without losing generic information.
|
|
12455
|
+
*
|
|
12456
|
+
* @param map An object literal mapping tool names to tool definitions created
|
|
12457
|
+
* by {@link action}, {@link view}, or {@link ask}.
|
|
12458
|
+
* @returns A frozen `Readonly<M>` where `M` is the exact inferred map shape.
|
|
12459
|
+
* @example
|
|
12460
|
+
* ```ts
|
|
12461
|
+
* const move = action('Move a stop', z.object({ fromDay: z.number() }), (a) => a.fromDay);
|
|
12462
|
+
* const dayCard = view('Show a day card', z.object({ label: z.string() }), DayCardComponent);
|
|
12463
|
+
*
|
|
12464
|
+
* const registry = tools({ move_stop: move, day_card: dayCard });
|
|
12465
|
+
* // registry.move_stop is FunctionToolDef<...>
|
|
12466
|
+
* // registry.day_card is ViewToolDef<...>
|
|
12467
|
+
* ```
|
|
12468
|
+
*/
|
|
11904
12469
|
function tools(map) {
|
|
11905
12470
|
return Object.freeze({ ...map });
|
|
11906
12471
|
}
|
|
11907
12472
|
|
|
11908
12473
|
// SPDX-License-Identifier: MIT
|
|
12474
|
+
/**
|
|
12475
|
+
* Build an in-memory {@link Agent} for tests and stories — no transport, no
|
|
12476
|
+
* network. Every field is a writable signal so a test can drive UI states
|
|
12477
|
+
* (loading, error, interrupts, tool calls, subagents) deterministically.
|
|
12478
|
+
*
|
|
12479
|
+
* @param opts Initial values for the mock's signals; all optional.
|
|
12480
|
+
* @returns A {@link MockAgent} satisfying the full `Agent` contract.
|
|
12481
|
+
* @example
|
|
12482
|
+
* ```ts
|
|
12483
|
+
* const agent = mockAgent({
|
|
12484
|
+
* messages: [{ id: '1', role: 'assistant', content: 'Hi' }],
|
|
12485
|
+
* isLoading: true,
|
|
12486
|
+
* });
|
|
12487
|
+
* ```
|
|
12488
|
+
*/
|
|
11909
12489
|
function mockAgent(opts = {}) {
|
|
11910
12490
|
const messages = signal(opts.messages ?? [], ...(ngDevMode ? [{ debugName: "messages" }] : []));
|
|
11911
12491
|
const status = signal(opts.status ?? 'idle', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
11912
12492
|
const isLoading = signal(opts.isLoading ?? false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
11913
|
-
const error = signal(opts.error ??
|
|
12493
|
+
const error = signal(opts.error ?? undefined, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
11914
12494
|
const toolCalls = signal(opts.toolCalls ?? [], ...(ngDevMode ? [{ debugName: "toolCalls" }] : []));
|
|
11915
12495
|
const state = signal(opts.state ?? {}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
11916
12496
|
const interrupt = opts.withInterrupt
|
|
@@ -11935,6 +12515,7 @@ function mockAgent(opts = {}) {
|
|
|
11935
12515
|
events$: opts.events$ ?? EMPTY,
|
|
11936
12516
|
submit: async (input, submitOpts) => { submitCalls.push({ input, opts: submitOpts }); },
|
|
11937
12517
|
stop: async () => { stopCount++; },
|
|
12518
|
+
retry: async () => { return; },
|
|
11938
12519
|
regenerate: async (assistantMessageIndex) => {
|
|
11939
12520
|
// Truncate messages [N..end] and record the call as a synthetic submit so
|
|
11940
12521
|
// tests can assert regenerate behavior via the same submitCalls log.
|
|
@@ -11956,5 +12537,5 @@ function mockAgent(opts = {}) {
|
|
|
11956
12537
|
* Generated bundle index. Do not edit.
|
|
11957
12538
|
*/
|
|
11958
12539
|
|
|
11959
|
-
export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, CHAT_CONFIG, CHAT_LIFECYCLE,
|
|
12540
|
+
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, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, 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 };
|
|
11960
12541
|
//# sourceMappingURL=threadplane-chat.mjs.map
|