@m6d/cortex-angular 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/fesm2022/m6d-cortex-angular.mjs +4754 -0
- package/package.json +40 -0
- package/styles.css +69 -0
- package/types/m6d-cortex-angular.d.ts +294 -0
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@m6d/cortex-angular",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable AI agent chat UI library for Angular",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
"./styles.css": "./styles.css",
|
|
9
|
+
"./package.json": {
|
|
10
|
+
"default": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./types/m6d-cortex-angular.d.ts",
|
|
14
|
+
"default": "./fesm2022/m6d-cortex-angular.mjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"tslib": "^2.8.1"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@angular/common": "^21.2.0",
|
|
22
|
+
"@angular/core": "^21.2.0",
|
|
23
|
+
"@angular/forms": "^21.2.0",
|
|
24
|
+
"@angular/platform-browser": "^21.2.0",
|
|
25
|
+
"@ngx-translate/core": "^17.0.0",
|
|
26
|
+
"@tanstack/ai": ">=0.43.0 <1.0.0",
|
|
27
|
+
"@tanstack/ai-angular": ">=0.4.0 <1.0.0",
|
|
28
|
+
"@tanstack/ai-client": ">=0.23.0 <1.0.0",
|
|
29
|
+
"dompurify": "^3.0.0",
|
|
30
|
+
"highlight.js": "^11.0.0",
|
|
31
|
+
"marked": "^17.0.0",
|
|
32
|
+
"rxjs": "^7.8.0"
|
|
33
|
+
},
|
|
34
|
+
"packageManager": "bun@1.3.3",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"module": "fesm2022/m6d-cortex-angular.mjs",
|
|
39
|
+
"typings": "types/m6d-cortex-angular.d.ts"
|
|
40
|
+
}
|
package/styles.css
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* fallow-ignore-file code-duplication -- deliberately restates styles/_theme.scss: the whole point of this file is the full token set in one place */
|
|
2
|
+
/*
|
|
3
|
+
* Theming surface.
|
|
4
|
+
*
|
|
5
|
+
* Every value below is the widget's own default, restated here so the full set is
|
|
6
|
+
* discoverable in one place — the same declarations already ship inside cortex.css,
|
|
7
|
+
* so importing this file changes nothing until you edit it. Override any token on
|
|
8
|
+
* :root, or on any ancestor of the widget, to retheme it.
|
|
9
|
+
*
|
|
10
|
+
* Each color is a light-dark() pair. Which side applies follows the widget root's
|
|
11
|
+
* color-scheme, set by the `theme` config option ('light' | 'dark' | 'system').
|
|
12
|
+
* The radius and font tokens below the colors are plain values — neither depends
|
|
13
|
+
* on the scheme.
|
|
14
|
+
*/
|
|
15
|
+
:root {
|
|
16
|
+
--cortex-color-surface: light-dark(#ffffff, #0a0a0a);
|
|
17
|
+
--cortex-color-surface-subtle: light-dark(#f8fafc, #141414);
|
|
18
|
+
--cortex-color-surface-muted: light-dark(#f1f5f9, #1f1f1f);
|
|
19
|
+
|
|
20
|
+
--cortex-color-border: light-dark(#e2e8f0, #2e2e2e);
|
|
21
|
+
--cortex-color-border-strong: light-dark(#cbd5e1, #444444);
|
|
22
|
+
|
|
23
|
+
--cortex-color-text-heading: light-dark(#1e293b, #fafafa);
|
|
24
|
+
--cortex-color-text-strong: light-dark(#334155, #e5e5e5);
|
|
25
|
+
--cortex-color-text: light-dark(#475569, #d4d4d4);
|
|
26
|
+
--cortex-color-text-secondary: light-dark(#64748b, #a3a3a3);
|
|
27
|
+
--cortex-color-text-muted: light-dark(#94a3b8, #737373);
|
|
28
|
+
|
|
29
|
+
--cortex-color-primary: light-dark(#1e293b, #fafafa);
|
|
30
|
+
--cortex-color-primary-foreground: light-dark(#ffffff, #0a0a0a);
|
|
31
|
+
--cortex-color-accent: light-dark(#4f46e5, #818cf8);
|
|
32
|
+
--cortex-color-accent-surface: light-dark(#e0e7ff, #312e81);
|
|
33
|
+
--cortex-color-link: light-dark(#2563eb, #60a5fa);
|
|
34
|
+
--cortex-color-hover: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.06));
|
|
35
|
+
|
|
36
|
+
/* User messages default to the same subtle surface every other quiet panel
|
|
37
|
+
* uses, split out so a consumer can give their own side of the chat a color
|
|
38
|
+
* without touching anything else. */
|
|
39
|
+
--cortex-color-user-surface: var(--cortex-color-surface-subtle);
|
|
40
|
+
--cortex-color-user-border: var(--cortex-color-border);
|
|
41
|
+
|
|
42
|
+
--cortex-color-success: light-dark(#059669, #34d399);
|
|
43
|
+
--cortex-color-success-surface: light-dark(#ecfdf5, #064e3b);
|
|
44
|
+
--cortex-color-success-border: light-dark(#d1fae5, #065f46);
|
|
45
|
+
--cortex-color-danger: light-dark(#dc2626, #f87171);
|
|
46
|
+
--cortex-color-danger-surface: light-dark(#fef2f2, #450a0a);
|
|
47
|
+
--cortex-color-danger-border: light-dark(#fecaca, #7f1d1d);
|
|
48
|
+
|
|
49
|
+
/* The corner scale every rounded surface derives from: the widget multiplies
|
|
50
|
+
* this by 0.5/0.75/1.5/2, so overriding it rescales all of them together. */
|
|
51
|
+
--cortex-radius: 0.5rem;
|
|
52
|
+
|
|
53
|
+
--cortex-font-mono: var(
|
|
54
|
+
--default-mono-font-family,
|
|
55
|
+
ui-monospace,
|
|
56
|
+
SFMono-Regular,
|
|
57
|
+
Menlo,
|
|
58
|
+
Monaco,
|
|
59
|
+
Consolas,
|
|
60
|
+
monospace
|
|
61
|
+
);
|
|
62
|
+
--cortex-font-size: 0.8125rem;
|
|
63
|
+
|
|
64
|
+
/* The widget's headings read --cortex-font-display and fall back to `inherit`,
|
|
65
|
+
* so it ships unset on purpose — declaring it here is how you opt in:
|
|
66
|
+
*
|
|
67
|
+
* --cortex-font-display: 'Your Display Face', sans-serif;
|
|
68
|
+
*/
|
|
69
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
+
|
|
3
|
+
import * as _angular_core from '@angular/core';
|
|
4
|
+
import { Injector, OnInit, Type } from '@angular/core';
|
|
5
|
+
import * as _tanstack_ai from '@tanstack/ai';
|
|
6
|
+
import * as _tanstack_ai_angular from '@tanstack/ai-angular';
|
|
7
|
+
import { Observable } from 'rxjs';
|
|
8
|
+
|
|
9
|
+
declare const ATTACHMENT_STATUSES: readonly [
|
|
10
|
+
"pending",
|
|
11
|
+
"described",
|
|
12
|
+
"failed",
|
|
13
|
+
"skipped"
|
|
14
|
+
];
|
|
15
|
+
export type AttachmentStatus = (typeof ATTACHMENT_STATUSES)[number];
|
|
16
|
+
export type AttachmentSummary = {
|
|
17
|
+
id: string;
|
|
18
|
+
filename: string;
|
|
19
|
+
contentType: string;
|
|
20
|
+
sizeBytes: number;
|
|
21
|
+
status: AttachmentStatus;
|
|
22
|
+
description?: string;
|
|
23
|
+
};
|
|
24
|
+
export type ThreadSummary = {
|
|
25
|
+
id: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
isRunning: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type TokenUsage = {
|
|
32
|
+
input: {
|
|
33
|
+
noCache: number;
|
|
34
|
+
cacheRead: number;
|
|
35
|
+
cacheWrite: number;
|
|
36
|
+
total: number;
|
|
37
|
+
};
|
|
38
|
+
output: {
|
|
39
|
+
reasoning: number;
|
|
40
|
+
text: number;
|
|
41
|
+
total: number;
|
|
42
|
+
};
|
|
43
|
+
total: number;
|
|
44
|
+
};
|
|
45
|
+
export type MessageMetadata = {
|
|
46
|
+
modelId?: string;
|
|
47
|
+
providerMetadata?: unknown;
|
|
48
|
+
isAborted?: boolean;
|
|
49
|
+
tokenUsage?: TokenUsage;
|
|
50
|
+
attachments?: AttachmentSummary[];
|
|
51
|
+
};
|
|
52
|
+
export type CortexHeaders = Record<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* How the widget reaches its cortex server. `baseUrl` may be a thunk so a host
|
|
55
|
+
* with a reactive origin (an Angular Signal is itself a `() => string`) is read
|
|
56
|
+
* at the moment of each request rather than captured once.
|
|
57
|
+
*/
|
|
58
|
+
export type CortexTransport = {
|
|
59
|
+
baseUrl: string | (() => string);
|
|
60
|
+
getHeaders: () => CortexHeaders | Promise<CortexHeaders>;
|
|
61
|
+
};
|
|
62
|
+
export type LlmRequest = {
|
|
63
|
+
id: string;
|
|
64
|
+
prompt: string;
|
|
65
|
+
output: string | null;
|
|
66
|
+
tokenUsage: TokenUsage | null;
|
|
67
|
+
};
|
|
68
|
+
export type CortexViewMode = "helper" | "full";
|
|
69
|
+
export type CortexTheme = "light" | "dark" | "system";
|
|
70
|
+
export type CortexToolCallContext = {
|
|
71
|
+
threadId: string;
|
|
72
|
+
};
|
|
73
|
+
/** A tool the server left for the client to answer. */
|
|
74
|
+
export type CortexToolCall = {
|
|
75
|
+
toolCallId: string;
|
|
76
|
+
toolName: string;
|
|
77
|
+
input: unknown;
|
|
78
|
+
};
|
|
79
|
+
export type CortexHooks = {
|
|
80
|
+
onToolCall?: (toolCall: CortexToolCall, context: CortexToolCallContext) => unknown;
|
|
81
|
+
onThreadSelected?: (thread: ThreadSummary) => Promise<void> | void;
|
|
82
|
+
onThreadDeselected?: (thread: ThreadSummary) => Promise<void> | void;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The framework-neutral part of the widget's configuration. Each SDK extends it
|
|
86
|
+
* with its own `toolComponents` slot, which is the one field whose type is
|
|
87
|
+
* inherently framework-bound.
|
|
88
|
+
*/
|
|
89
|
+
export type CortexConfig = {
|
|
90
|
+
/** `'en' | 'ar'`, defaults to `'en'`. */
|
|
91
|
+
locale?: string;
|
|
92
|
+
transport: CortexTransport;
|
|
93
|
+
wsUrl?: string;
|
|
94
|
+
viewMode?: CortexViewMode;
|
|
95
|
+
/** Color scheme; `'system'` follows the OS preference. Defaults to `'light'`. */
|
|
96
|
+
theme?: CortexTheme;
|
|
97
|
+
showDebugButton?: boolean;
|
|
98
|
+
hooks?: CortexHooks;
|
|
99
|
+
};
|
|
100
|
+
export type QueuedAttachment = {
|
|
101
|
+
localId: string;
|
|
102
|
+
filename: string;
|
|
103
|
+
status: "uploading" | "ready" | "deleting" | "error";
|
|
104
|
+
threadId?: string;
|
|
105
|
+
attachmentId?: string;
|
|
106
|
+
summary?: AttachmentSummary;
|
|
107
|
+
};
|
|
108
|
+
export type CortexClientConfig = CortexConfig & {
|
|
109
|
+
toolComponents?: Record<string, Type<unknown>>;
|
|
110
|
+
injector?: Injector;
|
|
111
|
+
};
|
|
112
|
+
declare const ATTACHMENT_STATUSES$1: readonly [
|
|
113
|
+
"pending",
|
|
114
|
+
"described",
|
|
115
|
+
"failed",
|
|
116
|
+
"skipped"
|
|
117
|
+
];
|
|
118
|
+
type AttachmentStatus$1 = (typeof ATTACHMENT_STATUSES$1)[number];
|
|
119
|
+
type AttachmentSummary$1 = {
|
|
120
|
+
id: string;
|
|
121
|
+
filename: string;
|
|
122
|
+
contentType: string;
|
|
123
|
+
sizeBytes: number;
|
|
124
|
+
status: AttachmentStatus$1;
|
|
125
|
+
description?: string;
|
|
126
|
+
};
|
|
127
|
+
declare class AttachmentQueueService {
|
|
128
|
+
readonly items: _angular_core.WritableSignal<QueuedAttachment[]>;
|
|
129
|
+
private readonly flags;
|
|
130
|
+
readonly uploading: _angular_core.Signal<boolean>;
|
|
131
|
+
readonly busy: _angular_core.Signal<boolean>;
|
|
132
|
+
readonly hasReady: _angular_core.Signal<boolean>;
|
|
133
|
+
private readonly chatService;
|
|
134
|
+
private readonly queue;
|
|
135
|
+
constructor();
|
|
136
|
+
accept(files: File[]): void;
|
|
137
|
+
remove(localId: string): void;
|
|
138
|
+
clear(threadId: string | undefined): void;
|
|
139
|
+
consumeReady(): {
|
|
140
|
+
status: "ready";
|
|
141
|
+
summary: AttachmentSummary$1;
|
|
142
|
+
localId: string;
|
|
143
|
+
filename: string;
|
|
144
|
+
threadId?: string;
|
|
145
|
+
attachmentId?: string;
|
|
146
|
+
}[];
|
|
147
|
+
restoreConsumed(threadId: string | undefined): void;
|
|
148
|
+
discardConsumed(): void;
|
|
149
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AttachmentQueueService, never>;
|
|
150
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AttachmentQueueService>;
|
|
151
|
+
}
|
|
152
|
+
declare class CortexChatService {
|
|
153
|
+
/**
|
|
154
|
+
* Selection is the Thread handed to `selectThread`, resolved against the live
|
|
155
|
+
* list: the row there receives the title and isRunning updates the server
|
|
156
|
+
* pushes, so the selection follows them without hand-syncing. The snapshot
|
|
157
|
+
* only answers for a thread the list has not caught up with yet.
|
|
158
|
+
*/
|
|
159
|
+
readonly selectedThread: _angular_core.Signal<ThreadSummary | undefined>;
|
|
160
|
+
readonly chat: _angular_core.WritableSignal<_tanstack_ai_angular.InjectChatResult<readonly _tanstack_ai.AnyClientTool[], _tanstack_ai.SchemaInput | undefined> | undefined>;
|
|
161
|
+
readonly isLoadingMessages: _angular_core.WritableSignal<boolean>;
|
|
162
|
+
readonly hasPendingToolCalls: _angular_core.WritableSignal<boolean>;
|
|
163
|
+
readonly isAgentWorking: _angular_core.Signal<boolean>;
|
|
164
|
+
/**
|
|
165
|
+
* The per-message facts the SDK's `UIMessage` has no room for — usage, model
|
|
166
|
+
* id, the aborted flag, attachments. They are cortex's own envelope around the
|
|
167
|
+
* stored parts, so they arrive with the persisted transcript rather than over
|
|
168
|
+
* the stream, and are merged back in by message id at render time.
|
|
169
|
+
*/
|
|
170
|
+
readonly messageMetadata: _angular_core.WritableSignal<ReadonlyMap<string, MessageMetadata>>;
|
|
171
|
+
private readonly events;
|
|
172
|
+
private readonly selectedThreadSnapshot;
|
|
173
|
+
private readonly config;
|
|
174
|
+
private readonly api;
|
|
175
|
+
private readonly threadService;
|
|
176
|
+
private readonly wsService;
|
|
177
|
+
private readonly destroyRef;
|
|
178
|
+
private readonly environmentInjector;
|
|
179
|
+
/** Owns the current thread's chat client; destroying it stops and disposes it. */
|
|
180
|
+
private chatInjector;
|
|
181
|
+
private readonly dispatchedToolCalls;
|
|
182
|
+
/**
|
|
183
|
+
* Whether this widget is driving the run it is showing or watching one another
|
|
184
|
+
* tab started. A watcher owes nothing: the tool calls it sees belong to the tab
|
|
185
|
+
* that sent the message, and how the turn ended is the server's to tell it.
|
|
186
|
+
*/
|
|
187
|
+
private ownsRun;
|
|
188
|
+
/**
|
|
189
|
+
* Whether the server has answered the turn in flight. Until it does, a failure
|
|
190
|
+
* means the message never got out and the composer takes back the files it was
|
|
191
|
+
* carrying; after it, those files are on the message the server stored.
|
|
192
|
+
*/
|
|
193
|
+
private serverAnswered;
|
|
194
|
+
private wasAborted;
|
|
195
|
+
private pendingThreadCreation;
|
|
196
|
+
private refreshing;
|
|
197
|
+
constructor();
|
|
198
|
+
/**
|
|
199
|
+
* Kicked off by the widget once its `config` input is bound. This cannot happen in
|
|
200
|
+
* the constructor: the widget builds its children during its creation pass, which
|
|
201
|
+
* Angular runs before it binds inputs, so the transport is not knowable yet.
|
|
202
|
+
*/
|
|
203
|
+
start(): void;
|
|
204
|
+
get events$(): Observable<"onFinish" | "onError" | "onThreadSelected" | "onSend">;
|
|
205
|
+
selectThread(thread: ThreadSummary, options?: {
|
|
206
|
+
skipLoadingMessages?: boolean;
|
|
207
|
+
}): Observable<ThreadSummary>;
|
|
208
|
+
deselectThread(): void;
|
|
209
|
+
/**
|
|
210
|
+
* The id of the thread the next message belongs to, creating it when the widget is
|
|
211
|
+
* still on a blank chat. `prompt` only seeds the generated title.
|
|
212
|
+
*/
|
|
213
|
+
ensureThread(prompt?: string): Observable<string>;
|
|
214
|
+
send(prompt: string, attachments?: AttachmentSummary[]): Observable<void>;
|
|
215
|
+
/** Answers a tool call on behalf of a consumer-supplied tool component. */
|
|
216
|
+
addToolResult(toolCallId: string, toolName: string, output: unknown): void;
|
|
217
|
+
fetchLlmRequests(messageId: string): Promise<LlmRequest[]>;
|
|
218
|
+
abort(): Promise<void>;
|
|
219
|
+
deleteThread(threadId: string): Observable<void>;
|
|
220
|
+
/**
|
|
221
|
+
* Points the widget at a thread with a chat client of its own. The client is
|
|
222
|
+
* server-authoritative: on mount it asks cortex for the transcript and for
|
|
223
|
+
* whatever run is still generating, so selecting a thread, reloading the page
|
|
224
|
+
* and a second tab all re-attach through one path.
|
|
225
|
+
*/
|
|
226
|
+
private mountChat;
|
|
227
|
+
/**
|
|
228
|
+
* Re-mounts the current thread's chat so its hydration re-reads the transcript
|
|
229
|
+
* and re-attaches to whatever run is live now. This is how a run another tab
|
|
230
|
+
* started, and a websocket that dropped and came back, land in this widget.
|
|
231
|
+
* A caller passes the thread when it holds fresher state than the list.
|
|
232
|
+
*/
|
|
233
|
+
private reattach;
|
|
234
|
+
private createConnection;
|
|
235
|
+
/**
|
|
236
|
+
* A finished stream leaves the SDK holding only what came over the wire, while
|
|
237
|
+
* usage, model id and attachments live in cortex's envelope — so a settled turn
|
|
238
|
+
* is read back from the database. The one turn that is not read back is one
|
|
239
|
+
* this widget is still in the middle of: a tool call it owes an answer to.
|
|
240
|
+
*/
|
|
241
|
+
private settleTurn;
|
|
242
|
+
private refreshMessages;
|
|
243
|
+
private readBackMessages;
|
|
244
|
+
private setMetadata;
|
|
245
|
+
/**
|
|
246
|
+
* Hands the calls the run left open to the consumer's hook — cortex's tools all
|
|
247
|
+
* run on the server, so anything still unanswered when the stream ends is one
|
|
248
|
+
* the client was asked to resolve. Reports whether any are still waiting.
|
|
249
|
+
*/
|
|
250
|
+
private dispatchPendingToolCalls;
|
|
251
|
+
private resolveToolCall;
|
|
252
|
+
private syncPendingToolCalls;
|
|
253
|
+
private subscribeToWsEvents;
|
|
254
|
+
/**
|
|
255
|
+
* Only what these events mean for the open conversation — their list-shaped
|
|
256
|
+
* side is CortexThreadService's, which absorbs them on its own subscription.
|
|
257
|
+
*/
|
|
258
|
+
private onWsEvent;
|
|
259
|
+
private subscribeToWsConnections;
|
|
260
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CortexChatService, never>;
|
|
261
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<CortexChatService>;
|
|
262
|
+
}
|
|
263
|
+
export declare class CortexChatWidgetComponent implements OnInit {
|
|
264
|
+
readonly config: _angular_core.InputSignal<CortexClientConfig>;
|
|
265
|
+
protected readonly debugMode: _angular_core.WritableSignal<boolean>;
|
|
266
|
+
protected readonly screen: _angular_core.WritableSignal<"threads" | "chat">;
|
|
267
|
+
protected readonly sidebarOpen: _angular_core.WritableSignal<boolean>;
|
|
268
|
+
protected readonly viewMode: _angular_core.Signal<CortexViewMode>;
|
|
269
|
+
private readonly composer;
|
|
270
|
+
protected chatService: CortexChatService;
|
|
271
|
+
protected queue: AttachmentQueueService;
|
|
272
|
+
private readonly injector;
|
|
273
|
+
private readonly translateService;
|
|
274
|
+
private readonly destroyRef;
|
|
275
|
+
ngOnInit(): void;
|
|
276
|
+
protected toggleSidebar(): void;
|
|
277
|
+
protected closeSidebar(): void;
|
|
278
|
+
protected toggleDebugMode(): void;
|
|
279
|
+
protected selectThread(thread: ThreadSummary): void;
|
|
280
|
+
protected goBack(): void;
|
|
281
|
+
protected onWidgetDragOver(event: DragEvent): void;
|
|
282
|
+
protected onWidgetDrop(event: DragEvent): void;
|
|
283
|
+
protected newChat(): void;
|
|
284
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CortexChatWidgetComponent, never>;
|
|
285
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CortexChatWidgetComponent, "cortex-chat-widget", never, {
|
|
286
|
+
"config": {
|
|
287
|
+
"alias": "config";
|
|
288
|
+
"required": true;
|
|
289
|
+
"isSignal": true;
|
|
290
|
+
};
|
|
291
|
+
}, {}, never, never, true, never>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export {};
|