@jini-ai/chat 0.6.15 → 0.6.16
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 +6 -1
- package/dist/core/antigravity/tool-presentation.d.ts +13 -0
- package/dist/core/antigravity/tool-presentation.js +20 -0
- package/dist/core/assistant-client/ag-ui-transport.js +13 -1
- package/dist/core/events.d.ts +5 -1
- package/dist/core/tool-events.d.ts +18 -8
- package/dist/core/tool-events.js +72 -16
- package/dist/core/tool-presentation.d.ts +14 -0
- package/dist/core/tool-presentation.js +8 -0
- package/dist/core/user-text-redaction.d.ts +2 -1
- package/dist/core/user-text-redaction.js +10 -3
- package/dist/embed-widget/browser/frontend-session-bridge.d.ts +99 -0
- package/dist/embed-widget/browser/frontend-session-bridge.js +384 -0
- package/dist/embed-widget/browser/session-store.d.ts +3 -9
- package/dist/embed-widget/browser/session-store.js +6 -38
- package/dist/embed-widget/react/embed.d.ts +3 -10
- package/dist/embed-widget/react/embed.js +8 -52
- package/dist/embed-widget/vanilla/index.d.ts +0 -5
- package/dist/embed-widget/vanilla/index.js +4 -14
- package/dist/embed-widget/vanilla/state.d.ts +3 -11
- package/dist/embed-widget/vanilla/state.js +6 -37
- package/dist/embed-widget/vanilla/style.css +1 -1
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +2 -56
- package/dist/react/agent-bridge/frontend-session-bridge.js +2 -248
- package/dist/react/components/Icon.d.ts +1 -1
- package/dist/react/components/Icon.js +1 -0
- package/dist/react/components/Markdown.d.ts +1 -0
- package/dist/react/components/Markdown.js +1 -1
- package/dist/react/components/MessageList.d.ts +1 -1
- package/dist/react/components/MessageRow.d.ts +3 -0
- package/dist/react/components/MessageRow.js +10 -8
- package/dist/react/components/ToolCard.d.ts +3 -1
- package/dist/react/components/ToolCard.js +77 -58
- package/dist/react/components/ToolCard.rules.d.ts +20 -0
- package/dist/react/components/ToolCard.rules.js +92 -0
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +18 -6
- package/dist/react/features/chat-pane/components/ChatPane.js +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +9 -3
- package/dist/react/features/chat-pane/styles.d.ts +1 -1
- package/dist/react/features/chat-pane/styles.js +76 -9
- package/dist/react/features/chat-pane/types.d.ts +3 -0
- package/dist/react/hooks/useUserTextGuard.d.ts +1 -0
- package/dist/react/hooks/useUserTextGuard.js +3 -1
- package/dist/react/tool-call-display.d.ts +11 -0
- package/dist/react/tool-call-display.js +59 -2
- package/package.json +91 -86
- package/dist/core/browser/page-actions.d.ts +0 -121
- package/dist/core/browser/page-actions.js +0 -115
- package/dist/embed-widget/browser/page-actions.d.ts +0 -2
- package/dist/embed-widget/browser/page-actions.js +0 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { humanizeToolName } from '../core/index.js';
|
|
1
|
+
import { humanizeToolName, isDelegatedWrapperToolName } from '../core/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Whether a row failed: the tool returned an error, or the run
|
|
4
4
|
* ended without the tool ever answering (`deriveToolStatus` maps both to `'error'`).
|
|
@@ -7,6 +7,62 @@ import { humanizeToolName } from '../core/index.js';
|
|
|
7
7
|
export function isFailedToolRow({ row }) {
|
|
8
8
|
return row.status === 'error';
|
|
9
9
|
}
|
|
10
|
+
/** The tool a row called: a delegated wrapper's `input.toolId`, else its own name (wrappers share one name). */
|
|
11
|
+
function toolIdentity(row) {
|
|
12
|
+
if (!isDelegatedWrapperToolName({ name: row.name }))
|
|
13
|
+
return row.name;
|
|
14
|
+
const input = row.use?.input;
|
|
15
|
+
const toolId = typeof input === 'object' && input !== null ? input.toolId : undefined;
|
|
16
|
+
return typeof toolId === 'string' && toolId ? toolId : row.name;
|
|
17
|
+
}
|
|
18
|
+
/** Prefer a stable id over editable slugs/paths; normalize input aliases within each kind.
|
|
19
|
+
* @complexity O(target bytes), fixed field count; pure, absent coordinates return undefined.
|
|
20
|
+
*/
|
|
21
|
+
function targetCoordinate({ input }, _options = {}) {
|
|
22
|
+
const kinds = [
|
|
23
|
+
['id', ['postId', 'post_id', 'pageId', 'page_id', 'entryId', 'entry_id', 'id']],
|
|
24
|
+
['slug', ['slug']],
|
|
25
|
+
['path', ['path', 'filePath', 'file_path']],
|
|
26
|
+
];
|
|
27
|
+
for (const [kind, fields] of kinds) {
|
|
28
|
+
const field = fields.find(key => typeof input[key] === 'number' || (typeof input[key] === 'string' && input[key] !== ''));
|
|
29
|
+
if (field !== undefined)
|
|
30
|
+
return [kind, input[field]];
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Target coordinates only: corrected content must not prevent a retry from matching.
|
|
35
|
+
* Wrapper inputs belong to the delegated tool, whose identity toolIdentity already resolves.
|
|
36
|
+
* @complexity O(target bytes); the coordinate list is fixed. Pure, with no input mutation.
|
|
37
|
+
*/
|
|
38
|
+
function retryIdentity({ row }, _options = {}) {
|
|
39
|
+
const input = row.use?.input;
|
|
40
|
+
const record = typeof input === 'object' && input !== null ? input : {};
|
|
41
|
+
const target = isDelegatedWrapperToolName({ name: row.name }) ? record.input : record;
|
|
42
|
+
const coordinates = typeof target === 'object' && target !== null ? target : {};
|
|
43
|
+
return JSON.stringify([toolIdentity(row), targetCoordinate({ input: coordinates })]);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Ids of failed rows that a LATER successful call of the same tool and target retried (owner, 2026-10-31: a
|
|
47
|
+
* red "HTTP 400 … page was NOT written" row and "· 2 failed" stayed on screen after the immediate
|
|
48
|
+
* retry wrote the page). Those rows render as a neutral "Retried" and are not counted as failed;
|
|
49
|
+
* a failure nothing later succeeded at stays red. Inputs without target coordinates match by
|
|
50
|
+
* tool alone; a targetless success cannot clear a failure naming a target. Pass one turn's rows.
|
|
51
|
+
* @complexity O(n + target bytes) time and space.
|
|
52
|
+
*/
|
|
53
|
+
export function retriedToolRowIds({ rows }, _options = {}) {
|
|
54
|
+
const succeededLater = new Set();
|
|
55
|
+
const retried = new Set();
|
|
56
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
57
|
+
const row = rows[i];
|
|
58
|
+
const tool = retryIdentity({ row });
|
|
59
|
+
if (row.status === 'complete')
|
|
60
|
+
succeededLater.add(tool);
|
|
61
|
+
else if (isFailedToolRow({ row }) && succeededLater.has(tool))
|
|
62
|
+
retried.add(row.id);
|
|
63
|
+
}
|
|
64
|
+
return retried;
|
|
65
|
+
}
|
|
10
66
|
/** Running = no result yet while the run streams (`'executing'`), or not started (`'inProgress'`). */
|
|
11
67
|
function isRunningToolRow(row) {
|
|
12
68
|
return row.status === 'executing' || row.status === 'inProgress';
|
|
@@ -109,7 +165,8 @@ export function summarizeToolGroup({ rows }) {
|
|
|
109
165
|
if (isRunningToolRow(row))
|
|
110
166
|
return { kind: 'running', name: humanizeToolName({ name: row.name }, { input: row.use?.input }) };
|
|
111
167
|
}
|
|
112
|
-
|
|
168
|
+
const retried = retriedToolRowIds({ rows });
|
|
169
|
+
return { kind: 'done', count: rows.length, failed: rows.filter((row) => isFailedToolRow({ row }) && !retried.has(row.id)).length };
|
|
113
170
|
}
|
|
114
171
|
/**
|
|
115
172
|
* The collapsed line's text. Each phrase is its own translation key (the English string is the key,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/chat",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.16",
|
|
4
4
|
"description": "Jini's chat/artifact frontend, consolidated into one package with two subpaths: framework-free chat vocabulary and pure parsers in `/core`, headless hooks + presentational components + slots in `/react`. Import only the layer you need.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -33,22 +33,39 @@
|
|
|
33
33
|
"./transports/http": "universal",
|
|
34
34
|
"./transports/ag-ui": "universal",
|
|
35
35
|
"./browser/session-store": "browser",
|
|
36
|
-
"./browser/page-actions": "browser",
|
|
37
36
|
"./server/durable-runs": "node",
|
|
38
37
|
"./store/runtime": "node",
|
|
39
38
|
"./vanilla": "browser",
|
|
40
39
|
"./browser/lazy-loader": "browser",
|
|
41
|
-
"./vanilla/style.css": "browser"
|
|
40
|
+
"./vanilla/style.css": "browser",
|
|
41
|
+
"./browser/frontend-session-bridge": "browser"
|
|
42
42
|
},
|
|
43
43
|
"neutralEntries": [
|
|
44
44
|
"src/store/contracts"
|
|
45
45
|
],
|
|
46
46
|
"isolation": {
|
|
47
47
|
"entries": {
|
|
48
|
-
"./browser/page-actions": "core",
|
|
49
48
|
"./transports/http": "core",
|
|
50
|
-
"./transports/ag-ui": "core"
|
|
49
|
+
"./transports/ag-ui": "core",
|
|
50
|
+
"./browser/frontend-session-bridge": "core"
|
|
51
51
|
}
|
|
52
|
+
},
|
|
53
|
+
"optionalPeers": {
|
|
54
|
+
"better-sqlite3": "opt-in: sqlite storage — SQLite storage could not load better-sqlite3. Install better-sqlite3 to enable this feature, or check its installed version and native binaries.",
|
|
55
|
+
"@electric-sql/pglite": "opt-in: embedded postgresql storage — Embedded PostgreSQL storage could not load @electric-sql/pglite. Install @electric-sql/pglite to enable this feature, or check its installed version and native binaries.",
|
|
56
|
+
"pg": "opt-in: postgresql storage — PostgreSQL storage could not load pg. Install pg to enable this feature, or check its installed version and native binaries.",
|
|
57
|
+
"@ag-ui/core": "opt-in: ag ui protocol adapter; import @jini-ai/chat/core/ag-ui — AG UI protocol adapter could not load @ag-ui/core. Install @ag-ui/core to enable this feature, or check its installed version and native binaries.",
|
|
58
|
+
"@mcp-ui/client": "opt-in: mcp ui renderer; import @jini-ai/chat/react — MCP UI renderer could not load @mcp-ui/client. Install @mcp-ui/client to enable this feature, or check its installed version and native binaries.",
|
|
59
|
+
"@radix-ui/react-checkbox": "opt-in: interactive ui controls; import @jini-ai/chat/react — Interactive UI controls could not load @radix-ui/react-checkbox. Install @radix-ui/react-checkbox to enable this feature, or check its installed version and native binaries.",
|
|
60
|
+
"@radix-ui/react-label": "opt-in: interactive ui controls; import @jini-ai/chat/react — Interactive UI controls could not load @radix-ui/react-label. Install @radix-ui/react-label to enable this feature, or check its installed version and native binaries.",
|
|
61
|
+
"@radix-ui/react-radio-group": "opt-in: interactive ui controls; import @jini-ai/chat/react — Interactive UI controls could not load @radix-ui/react-radio-group. Install @radix-ui/react-radio-group to enable this feature, or check its installed version and native binaries.",
|
|
62
|
+
"@radix-ui/react-select": "opt-in: interactive ui controls; import @jini-ai/chat/react — Interactive UI controls could not load @radix-ui/react-select. Install @radix-ui/react-select to enable this feature, or check its installed version and native binaries.",
|
|
63
|
+
"@radix-ui/react-slot": "opt-in: interactive ui controls — Interactive UI controls could not load @radix-ui/react-slot. Install @radix-ui/react-slot to enable this feature, or check its installed version and native binaries.",
|
|
64
|
+
"@jini-ai/agent-runtime": "opt-in: agent execution adapters; import @jini-ai/chat/react — Agent execution adapters could not load @jini-ai/agent-runtime. Install @jini-ai/agent-runtime to enable this feature, or check its installed version and native binaries.",
|
|
65
|
+
"@jini-ai/protocol": "opt-in: agent protocol adapters; import @jini-ai/chat/server/durable-runs — Agent protocol adapters could not load @jini-ai/protocol. Install @jini-ai/protocol to enable this feature, or check its installed version and native binaries.",
|
|
66
|
+
"@jini-ai/ui": "opt-in: ui integrations; import @jini-ai/chat/react — UI integrations could not load @jini-ai/ui. Install @jini-ai/ui to enable this feature, or check its installed version and native binaries.",
|
|
67
|
+
"react": "opt-in: react chat ui; import @jini-ai/chat/react — React chat UI could not load react. Install react to enable this feature, or check its installed version.",
|
|
68
|
+
"react-dom": "opt-in: react chat ui; import @jini-ai/chat/react — React chat UI could not load react-dom. Install react-dom to enable this feature, or check its installed version."
|
|
52
69
|
}
|
|
53
70
|
},
|
|
54
71
|
"type": "module",
|
|
@@ -150,11 +167,6 @@
|
|
|
150
167
|
"import": "./dist/embed-widget/browser/session-store.js",
|
|
151
168
|
"default": "./dist/embed-widget/browser/session-store.js"
|
|
152
169
|
},
|
|
153
|
-
"./browser/page-actions": {
|
|
154
|
-
"types": "./dist/embed-widget/browser/page-actions.d.ts",
|
|
155
|
-
"import": "./dist/embed-widget/browser/page-actions.js",
|
|
156
|
-
"default": "./dist/embed-widget/browser/page-actions.js"
|
|
157
|
-
},
|
|
158
170
|
"./server/durable-runs": {
|
|
159
171
|
"types": "./dist/server/durable-runs/entry.d.ts",
|
|
160
172
|
"import": "./dist/server/durable-runs/entry.js",
|
|
@@ -175,7 +187,12 @@
|
|
|
175
187
|
"import": "./dist/embed-widget/browser/lazy-loader.js",
|
|
176
188
|
"default": "./dist/embed-widget/browser/lazy-loader.js"
|
|
177
189
|
},
|
|
178
|
-
"./vanilla/style.css": "./dist/embed-widget/vanilla/style.css"
|
|
190
|
+
"./vanilla/style.css": "./dist/embed-widget/vanilla/style.css",
|
|
191
|
+
"./browser/frontend-session-bridge": {
|
|
192
|
+
"types": "./dist/embed-widget/browser/frontend-session-bridge.d.ts",
|
|
193
|
+
"import": "./dist/embed-widget/browser/frontend-session-bridge.js",
|
|
194
|
+
"default": "./dist/embed-widget/browser/frontend-session-bridge.js"
|
|
195
|
+
}
|
|
179
196
|
},
|
|
180
197
|
"files": [
|
|
181
198
|
"dist",
|
|
@@ -196,13 +213,15 @@
|
|
|
196
213
|
"registry": "https://registry.npmjs.org"
|
|
197
214
|
},
|
|
198
215
|
"dependencies": {
|
|
199
|
-
"
|
|
216
|
+
"kysely": "^0.29.6",
|
|
217
|
+
"@jini-ai/core": "^0.6.16",
|
|
218
|
+
"@jini-ai/diagnostics": "^0.6.16",
|
|
219
|
+
"@jini-ai/db": "^0.6.16",
|
|
220
|
+
"@jini-ai/agentic": "^0.6.16"
|
|
200
221
|
},
|
|
201
222
|
"peerDependencies": {
|
|
202
223
|
"react": "^18.3.0 || ^19.0.0",
|
|
203
224
|
"react-dom": "^18.3.0 || ^19.0.0",
|
|
204
|
-
"@jini-ai/db": "^0.6.10",
|
|
205
|
-
"kysely": "^0.29.6",
|
|
206
225
|
"better-sqlite3": "^13.0.0",
|
|
207
226
|
"@electric-sql/pglite": "0.5.8",
|
|
208
227
|
"pg": "^8.23.0",
|
|
@@ -214,69 +233,8 @@
|
|
|
214
233
|
"@radix-ui/react-select": "^2.3.7",
|
|
215
234
|
"@radix-ui/react-slot": "^1.3.3",
|
|
216
235
|
"@jini-ai/agent-runtime": "^0.6.10",
|
|
217
|
-
"@jini-ai/agentic": "^0.6.10",
|
|
218
236
|
"@jini-ai/protocol": "^0.6.10",
|
|
219
|
-
"@jini-ai/ui": "^0.6.10"
|
|
220
|
-
"@jini-ai/diagnostics": "^0.6.10"
|
|
221
|
-
},
|
|
222
|
-
"peerDependenciesMeta": {
|
|
223
|
-
"react": {
|
|
224
|
-
"optional": true
|
|
225
|
-
},
|
|
226
|
-
"react-dom": {
|
|
227
|
-
"optional": true
|
|
228
|
-
},
|
|
229
|
-
"@jini-ai/db": {
|
|
230
|
-
"optional": true
|
|
231
|
-
},
|
|
232
|
-
"kysely": {
|
|
233
|
-
"optional": true
|
|
234
|
-
},
|
|
235
|
-
"better-sqlite3": {
|
|
236
|
-
"optional": true
|
|
237
|
-
},
|
|
238
|
-
"@electric-sql/pglite": {
|
|
239
|
-
"optional": true
|
|
240
|
-
},
|
|
241
|
-
"pg": {
|
|
242
|
-
"optional": true
|
|
243
|
-
},
|
|
244
|
-
"@ag-ui/core": {
|
|
245
|
-
"optional": true
|
|
246
|
-
},
|
|
247
|
-
"@mcp-ui/client": {
|
|
248
|
-
"optional": true
|
|
249
|
-
},
|
|
250
|
-
"@radix-ui/react-checkbox": {
|
|
251
|
-
"optional": true
|
|
252
|
-
},
|
|
253
|
-
"@radix-ui/react-label": {
|
|
254
|
-
"optional": true
|
|
255
|
-
},
|
|
256
|
-
"@radix-ui/react-radio-group": {
|
|
257
|
-
"optional": true
|
|
258
|
-
},
|
|
259
|
-
"@radix-ui/react-select": {
|
|
260
|
-
"optional": true
|
|
261
|
-
},
|
|
262
|
-
"@radix-ui/react-slot": {
|
|
263
|
-
"optional": true
|
|
264
|
-
},
|
|
265
|
-
"@jini-ai/agent-runtime": {
|
|
266
|
-
"optional": true
|
|
267
|
-
},
|
|
268
|
-
"@jini-ai/agentic": {
|
|
269
|
-
"optional": true
|
|
270
|
-
},
|
|
271
|
-
"@jini-ai/protocol": {
|
|
272
|
-
"optional": true
|
|
273
|
-
},
|
|
274
|
-
"@jini-ai/ui": {
|
|
275
|
-
"optional": true
|
|
276
|
-
},
|
|
277
|
-
"@jini-ai/diagnostics": {
|
|
278
|
-
"optional": true
|
|
279
|
-
}
|
|
237
|
+
"@jini-ai/ui": "^0.6.10"
|
|
280
238
|
},
|
|
281
239
|
"devDependencies": {
|
|
282
240
|
"@mcp-ui/client": "7.1.1",
|
|
@@ -303,13 +261,13 @@
|
|
|
303
261
|
"pg": "8.23.0",
|
|
304
262
|
"@ag-ui/core": "0.0.58",
|
|
305
263
|
"fast-check": "4.3.0",
|
|
306
|
-
"@jini-ai/
|
|
307
|
-
"@jini-ai/
|
|
308
|
-
"@jini-ai/
|
|
309
|
-
"@jini-ai/
|
|
310
|
-
"@jini-ai/core": "^0.6.
|
|
311
|
-
"@jini-ai/
|
|
312
|
-
"@jini-ai/ui": "^0.6.
|
|
264
|
+
"@jini-ai/agent-runtime": "0.6.16",
|
|
265
|
+
"@jini-ai/db": "0.6.16",
|
|
266
|
+
"@jini-ai/agentic": "^0.6.16",
|
|
267
|
+
"@jini-ai/diagnostics": "^0.6.16",
|
|
268
|
+
"@jini-ai/core": "^0.6.16",
|
|
269
|
+
"@jini-ai/protocol": "^0.6.16",
|
|
270
|
+
"@jini-ai/ui": "^0.6.16"
|
|
313
271
|
},
|
|
314
272
|
"typesVersions": {
|
|
315
273
|
"*": {
|
|
@@ -361,9 +319,6 @@
|
|
|
361
319
|
"browser/session-store": [
|
|
362
320
|
"./dist/embed-widget/browser/session-store.d.ts"
|
|
363
321
|
],
|
|
364
|
-
"browser/page-actions": [
|
|
365
|
-
"./dist/embed-widget/browser/page-actions.d.ts"
|
|
366
|
-
],
|
|
367
322
|
"server/durable-runs": [
|
|
368
323
|
"./dist/server/durable-runs/entry.d.ts"
|
|
369
324
|
],
|
|
@@ -375,9 +330,59 @@
|
|
|
375
330
|
],
|
|
376
331
|
"browser/lazy-loader": [
|
|
377
332
|
"./dist/embed-widget/browser/lazy-loader.d.ts"
|
|
333
|
+
],
|
|
334
|
+
"browser/frontend-session-bridge": [
|
|
335
|
+
"./dist/embed-widget/browser/frontend-session-bridge.d.ts"
|
|
378
336
|
]
|
|
379
337
|
}
|
|
380
338
|
},
|
|
339
|
+
"peerDependenciesMeta": {
|
|
340
|
+
"better-sqlite3": {
|
|
341
|
+
"optional": true
|
|
342
|
+
},
|
|
343
|
+
"@electric-sql/pglite": {
|
|
344
|
+
"optional": true
|
|
345
|
+
},
|
|
346
|
+
"pg": {
|
|
347
|
+
"optional": true
|
|
348
|
+
},
|
|
349
|
+
"@ag-ui/core": {
|
|
350
|
+
"optional": true
|
|
351
|
+
},
|
|
352
|
+
"@mcp-ui/client": {
|
|
353
|
+
"optional": true
|
|
354
|
+
},
|
|
355
|
+
"@radix-ui/react-checkbox": {
|
|
356
|
+
"optional": true
|
|
357
|
+
},
|
|
358
|
+
"@radix-ui/react-label": {
|
|
359
|
+
"optional": true
|
|
360
|
+
},
|
|
361
|
+
"@radix-ui/react-radio-group": {
|
|
362
|
+
"optional": true
|
|
363
|
+
},
|
|
364
|
+
"@radix-ui/react-select": {
|
|
365
|
+
"optional": true
|
|
366
|
+
},
|
|
367
|
+
"@radix-ui/react-slot": {
|
|
368
|
+
"optional": true
|
|
369
|
+
},
|
|
370
|
+
"@jini-ai/agent-runtime": {
|
|
371
|
+
"optional": true
|
|
372
|
+
},
|
|
373
|
+
"@jini-ai/protocol": {
|
|
374
|
+
"optional": true
|
|
375
|
+
},
|
|
376
|
+
"@jini-ai/ui": {
|
|
377
|
+
"optional": true
|
|
378
|
+
},
|
|
379
|
+
"react": {
|
|
380
|
+
"optional": true
|
|
381
|
+
},
|
|
382
|
+
"react-dom": {
|
|
383
|
+
"optional": true
|
|
384
|
+
}
|
|
385
|
+
},
|
|
381
386
|
"scripts": {
|
|
382
387
|
"build": "tsc -p tsconfig.json && mkdir -p dist/react/styles && cp src/react/styles/reference.css dist/react/styles/ && mkdir -p dist/embed-widget/vanilla && node --input-type=module -e \"import {writeFileSync,readFileSync} from 'node:fs'; import {CHAT_BUBBLE_STYLE} from './dist/embed-widget/browser/lazy-loader.js'; writeFileSync('dist/embed-widget/vanilla/style.css',CHAT_BUBBLE_STYLE+'\\n'+readFileSync('src/embed-widget/vanilla/style.css','utf8'));\"",
|
|
383
388
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/** Resolved target supplied by the host; the client never derives a path from a title. */
|
|
2
|
-
export interface ResolvedPageTarget {
|
|
3
|
-
readonly slug: string;
|
|
4
|
-
readonly title: string;
|
|
5
|
-
readonly path: string;
|
|
6
|
-
}
|
|
7
|
-
export type PageAction = {
|
|
8
|
-
readonly type: 'navigate';
|
|
9
|
-
readonly target: ResolvedPageTarget;
|
|
10
|
-
readonly auto: boolean;
|
|
11
|
-
} | {
|
|
12
|
-
readonly type: 'scroll_to' | 'highlight';
|
|
13
|
-
readonly target: ResolvedPageTarget;
|
|
14
|
-
};
|
|
15
|
-
export type NavigateAction = Extract<PageAction, {
|
|
16
|
-
type: 'navigate';
|
|
17
|
-
}>;
|
|
18
|
-
export type NonNavigateAction = Exclude<PageAction, {
|
|
19
|
-
type: 'navigate';
|
|
20
|
-
}>;
|
|
21
|
-
export interface DirectiveCarryingEvent {
|
|
22
|
-
readonly kind: string;
|
|
23
|
-
readonly name?: string;
|
|
24
|
-
readonly data?: unknown;
|
|
25
|
-
}
|
|
26
|
-
export interface SplitPageActions {
|
|
27
|
-
readonly navigate: NavigateAction | null;
|
|
28
|
-
readonly other: NonNavigateAction | null;
|
|
29
|
-
}
|
|
30
|
-
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
31
|
-
export declare function isQueuedPageAction(args: {
|
|
32
|
-
value: unknown;
|
|
33
|
-
}): args is {
|
|
34
|
-
value: PageAction;
|
|
35
|
-
};
|
|
36
|
-
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
37
|
-
export declare function isPageActionDirective(args: {
|
|
38
|
-
value: unknown;
|
|
39
|
-
}): args is {
|
|
40
|
-
value: {
|
|
41
|
-
kind: 'page_action';
|
|
42
|
-
action: PageAction;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
46
|
-
export declare function extractPageActions({ events, directiveEventName }: {
|
|
47
|
-
events: readonly DirectiveCarryingEvent[] | undefined;
|
|
48
|
-
directiveEventName: string;
|
|
49
|
-
}): PageAction[];
|
|
50
|
-
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
51
|
-
export declare function splitPageActions({ actions }: {
|
|
52
|
-
actions: readonly PageAction[];
|
|
53
|
-
}): SplitPageActions;
|
|
54
|
-
export interface TargetSelectionPolicy {
|
|
55
|
-
readonly candidateSelector: string;
|
|
56
|
-
readonly excludedAncestorSelector: string;
|
|
57
|
-
}
|
|
58
|
-
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
59
|
-
export declare function findTargetElement({ title, root, selection }: {
|
|
60
|
-
title: string;
|
|
61
|
-
root: Pick<ParentNode, 'querySelectorAll'>;
|
|
62
|
-
selection: TargetSelectionPolicy;
|
|
63
|
-
}): Element | null;
|
|
64
|
-
export interface PageActionTimerPort {
|
|
65
|
-
schedule(args: {
|
|
66
|
-
callback: () => void;
|
|
67
|
-
delayMs: number;
|
|
68
|
-
}): unknown;
|
|
69
|
-
cancel(args: {
|
|
70
|
-
handle: unknown;
|
|
71
|
-
}): void;
|
|
72
|
-
}
|
|
73
|
-
export interface PageActionPort {
|
|
74
|
-
allows(args: {
|
|
75
|
-
action: PageAction;
|
|
76
|
-
}): boolean;
|
|
77
|
-
navigate(args: {
|
|
78
|
-
action: NavigateAction;
|
|
79
|
-
}): boolean;
|
|
80
|
-
execute(args: {
|
|
81
|
-
action: NonNavigateAction;
|
|
82
|
-
}): boolean;
|
|
83
|
-
clearHighlight(args: Record<string, never>): void;
|
|
84
|
-
}
|
|
85
|
-
export interface BrowserPageActionController extends PageActionPort {
|
|
86
|
-
applyHighlight(args: {
|
|
87
|
-
element: Element;
|
|
88
|
-
}): void;
|
|
89
|
-
scrollToElement(args: {
|
|
90
|
-
element: Element;
|
|
91
|
-
}): void;
|
|
92
|
-
}
|
|
93
|
-
export interface BrowserPageActionsArgs {
|
|
94
|
-
readonly root: Pick<ParentNode, 'querySelectorAll'>;
|
|
95
|
-
readonly selection: TargetSelectionPolicy;
|
|
96
|
-
readonly allowPath: (args: {
|
|
97
|
-
path: string;
|
|
98
|
-
}) => boolean;
|
|
99
|
-
readonly navigate: (args: {
|
|
100
|
-
path: string;
|
|
101
|
-
}) => void;
|
|
102
|
-
readonly reducedMotion: (args: Record<string, never>) => boolean;
|
|
103
|
-
readonly scroll: (args: {
|
|
104
|
-
element: Element;
|
|
105
|
-
options: ScrollIntoViewOptions;
|
|
106
|
-
}) => void;
|
|
107
|
-
readonly timers: PageActionTimerPort;
|
|
108
|
-
readonly highlightClass: string;
|
|
109
|
-
readonly fadeAnimationName: string;
|
|
110
|
-
readonly cleanupFallbackMs: number;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
114
|
-
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
115
|
-
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
116
|
-
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
117
|
-
* never guessed from a title; each controller owns its mutable highlight state.
|
|
118
|
-
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
119
|
-
*/
|
|
120
|
-
export declare function createBrowserPageActions(args: BrowserPageActionsArgs): BrowserPageActionController;
|
|
121
|
-
//# sourceMappingURL=page-actions.d.ts.map
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
function record(value) {
|
|
2
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
-
}
|
|
4
|
-
function target(value) {
|
|
5
|
-
return record(value) && typeof value.slug === 'string' && typeof value.title === 'string' && typeof value.path === 'string';
|
|
6
|
-
}
|
|
7
|
-
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
8
|
-
export function isQueuedPageAction(args) {
|
|
9
|
-
const { value } = args;
|
|
10
|
-
if (!record(value) || !target(value.target))
|
|
11
|
-
return false;
|
|
12
|
-
return value.type === 'navigate' ? typeof value.auto === 'boolean' : value.type === 'scroll_to' || value.type === 'highlight';
|
|
13
|
-
}
|
|
14
|
-
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
15
|
-
export function isPageActionDirective(args) {
|
|
16
|
-
const { value } = args;
|
|
17
|
-
return record(value) && value.kind === 'page_action' && isQueuedPageAction({ value: value.action });
|
|
18
|
-
}
|
|
19
|
-
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
20
|
-
export function extractPageActions({ events, directiveEventName }) {
|
|
21
|
-
const actions = [];
|
|
22
|
-
for (const event of events ?? []) {
|
|
23
|
-
const envelope = { value: event.data };
|
|
24
|
-
if (event.kind === 'ext' && event.name === directiveEventName && isPageActionDirective(envelope)) {
|
|
25
|
-
actions.push(envelope.value.action);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return actions;
|
|
29
|
-
}
|
|
30
|
-
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
31
|
-
export function splitPageActions({ actions }) {
|
|
32
|
-
return {
|
|
33
|
-
navigate: actions.find((a) => a.type === 'navigate') ?? null,
|
|
34
|
-
other: actions.find((a) => a.type !== 'navigate') ?? null,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
38
|
-
export function findTargetElement({ title, root, selection }) {
|
|
39
|
-
const needle = title.trim();
|
|
40
|
-
if (!needle)
|
|
41
|
-
return null;
|
|
42
|
-
const candidates = Array.from(root.querySelectorAll(selection.candidateSelector))
|
|
43
|
-
.filter(el => !el.closest(selection.excludedAncestorSelector));
|
|
44
|
-
// `textContent` is null only for documents and doctypes; every Element the selector yields has a string.
|
|
45
|
-
const text = (el) => el.textContent.trim();
|
|
46
|
-
return candidates.find(el => text(el) === needle)
|
|
47
|
-
?? candidates.find(el => text(el).toLowerCase() === needle.toLowerCase()) ?? null;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
51
|
-
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
52
|
-
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
53
|
-
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
54
|
-
* never guessed from a title; each controller owns its mutable highlight state.
|
|
55
|
-
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
56
|
-
*/
|
|
57
|
-
export function createBrowserPageActions(args) {
|
|
58
|
-
if (!Number.isFinite(args.cleanupFallbackMs) || args.cleanupFallbackMs < 0)
|
|
59
|
-
throw new Error('Invalid highlight deadline');
|
|
60
|
-
let current = null;
|
|
61
|
-
const clearHighlight = (_args) => {
|
|
62
|
-
if (!current)
|
|
63
|
-
return;
|
|
64
|
-
const old = current;
|
|
65
|
-
current = null;
|
|
66
|
-
args.timers.cancel({ handle: old.handle });
|
|
67
|
-
old.element.removeEventListener('animationend', old.onEnd);
|
|
68
|
-
old.element.classList.remove(args.highlightClass);
|
|
69
|
-
};
|
|
70
|
-
function highlight(element) {
|
|
71
|
-
if (current?.element === element)
|
|
72
|
-
return;
|
|
73
|
-
clearHighlight({});
|
|
74
|
-
const onEnd = event => {
|
|
75
|
-
if (event.target === element && event.animationName === args.fadeAnimationName)
|
|
76
|
-
clearHighlight({});
|
|
77
|
-
};
|
|
78
|
-
element.classList.add(args.highlightClass);
|
|
79
|
-
element.addEventListener('animationend', onEnd);
|
|
80
|
-
// Identity check makes even a late callback from a canceled timer harmless.
|
|
81
|
-
const handle = args.timers.schedule({ delayMs: args.cleanupFallbackMs, callback: () => {
|
|
82
|
-
if (current?.element === element && current.onEnd === onEnd)
|
|
83
|
-
clearHighlight({});
|
|
84
|
-
} });
|
|
85
|
-
current = { element, handle, onEnd };
|
|
86
|
-
}
|
|
87
|
-
const scrollToElement = ({ element }) => {
|
|
88
|
-
args.scroll({ element, options: { behavior: args.reducedMotion({}) ? 'auto' : 'smooth', block: 'center' } });
|
|
89
|
-
};
|
|
90
|
-
return {
|
|
91
|
-
applyHighlight({ element }) { highlight(element); },
|
|
92
|
-
scrollToElement,
|
|
93
|
-
allows({ action }) { return args.allowPath({ path: action.target.path }); },
|
|
94
|
-
navigate({ action }) {
|
|
95
|
-
if (!args.allowPath({ path: action.target.path }))
|
|
96
|
-
return false;
|
|
97
|
-
clearHighlight({});
|
|
98
|
-
args.navigate({ path: action.target.path });
|
|
99
|
-
return true;
|
|
100
|
-
},
|
|
101
|
-
execute({ action }) {
|
|
102
|
-
if (!args.allowPath({ path: action.target.path }))
|
|
103
|
-
return false;
|
|
104
|
-
const element = findTargetElement({ title: action.target.title, root: args.root, selection: args.selection });
|
|
105
|
-
if (!element)
|
|
106
|
-
return false;
|
|
107
|
-
scrollToElement({ element });
|
|
108
|
-
if (action.type === 'highlight')
|
|
109
|
-
highlight(element);
|
|
110
|
-
return true;
|
|
111
|
-
},
|
|
112
|
-
clearHighlight,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
//# sourceMappingURL=page-actions.js.map
|