@runtypelabs/persona 3.11.0 → 3.12.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/dist/index.cjs +43 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.global.js +61 -61
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +43 -43
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/theme-editor.cjs +133 -14
- package/dist/theme-editor.d.cts +58 -0
- package/dist/theme-editor.d.ts +58 -0
- package/dist/theme-editor.js +133 -14
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.js +1 -1
- package/package.json +1 -1
- package/src/components/reasoning-bubble.ts +139 -5
- package/src/defaults.ts +1 -0
- package/src/install.ts +4 -1
- package/src/theme-reference.ts +6 -3
- package/src/tool-call-display-defaults.test.ts +1 -0
- package/src/types.ts +58 -0
- package/src/utils/formatting.test.ts +25 -1
- package/src/utils/formatting.ts +15 -0
- package/src/utils/message-fingerprint.test.ts +12 -0
- package/src/utils/message-fingerprint.ts +1 -0
- package/src/utils/morph.test.ts +86 -0
- package/src/utils/morph.ts +8 -0
package/src/theme-reference.ts
CHANGED
|
@@ -218,9 +218,12 @@ export const THEME_TOKEN_DOCS = {
|
|
|
218
218
|
'shadow, backgroundColor, borderColor, borderWidth, borderRadius, headerBackgroundColor, headerTextColor, headerPaddingX, headerPaddingY, contentBackgroundColor, contentTextColor, contentPaddingX, contentPaddingY, codeBlockBackgroundColor, codeBlockBorderColor, codeBlockTextColor, toggleTextColor, labelTextColor, activeTextTemplate, completeTextTemplate, loadingAnimationColor, loadingAnimationSecondaryColor, loadingAnimationDuration, renderCollapsedSummary, renderCollapsedPreview, renderGroupedSummary.',
|
|
219
219
|
},
|
|
220
220
|
reasoning: {
|
|
221
|
-
description:
|
|
221
|
+
description:
|
|
222
|
+
'Reasoning/thinking row rendering hooks, text templates, and loading animations. ' +
|
|
223
|
+
'Text templates support {duration} placeholder and inline formatting (~dim~, *italic*, **bold**). ' +
|
|
224
|
+
'renderCollapsedSummary receives elapsed (static string) and createElapsedElement() (live-updating span) in its context.',
|
|
222
225
|
properties:
|
|
223
|
-
'renderCollapsedSummary, renderCollapsedPreview.',
|
|
226
|
+
'renderCollapsedSummary, renderCollapsedPreview, activeTextTemplate, completeTextTemplate, loadingAnimationColor, loadingAnimationSecondaryColor, loadingAnimationDuration.',
|
|
224
227
|
},
|
|
225
228
|
approval: {
|
|
226
229
|
description:
|
|
@@ -285,7 +288,7 @@ export const THEME_TOKEN_DOCS = {
|
|
|
285
288
|
features: {
|
|
286
289
|
description: 'Feature flags.',
|
|
287
290
|
properties:
|
|
288
|
-
'showReasoning (AI thinking steps), showToolCalls (tool invocations), toolCallDisplay (collapsedMode, activePreview, activeMinHeight, previewMaxLines, grouped, expandable, loadingAnimation), reasoningDisplay (activePreview, activeMinHeight, previewMaxLines, expandable), artifacts (sidebar config).',
|
|
291
|
+
'showReasoning (AI thinking steps), showToolCalls (tool invocations), toolCallDisplay (collapsedMode, activePreview, activeMinHeight, previewMaxLines, grouped, expandable, loadingAnimation), reasoningDisplay (activePreview, activeMinHeight, previewMaxLines, expandable, loadingAnimation), artifacts (sidebar config).',
|
|
289
292
|
},
|
|
290
293
|
},
|
|
291
294
|
}
|
package/src/types.ts
CHANGED
|
@@ -656,6 +656,17 @@ export type AgentWidgetReasoningDisplayFeature = {
|
|
|
656
656
|
* @default true
|
|
657
657
|
*/
|
|
658
658
|
expandable?: boolean;
|
|
659
|
+
/**
|
|
660
|
+
* Animation mode applied to the reasoning header text while reasoning is active.
|
|
661
|
+
* Reuses the same modes as tool call animations.
|
|
662
|
+
* - "none" — static text, no animation
|
|
663
|
+
* - "pulse" — opacity pulse on the entire header text
|
|
664
|
+
* - "shimmer" — monochrome opacity sweep per character
|
|
665
|
+
* - "shimmer-color" — color gradient sweep per character
|
|
666
|
+
* - "rainbow" — rainbow color cycle per character
|
|
667
|
+
* @default "none"
|
|
668
|
+
*/
|
|
669
|
+
loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
|
|
659
670
|
};
|
|
660
671
|
|
|
661
672
|
export type AgentWidgetFeatureFlags = {
|
|
@@ -1390,6 +1401,14 @@ export type AgentWidgetReasoningConfig = {
|
|
|
1390
1401
|
previewText: string;
|
|
1391
1402
|
isActive: boolean;
|
|
1392
1403
|
config: AgentWidgetConfig;
|
|
1404
|
+
/** Static elapsed time snapshot, e.g. "2.6s". */
|
|
1405
|
+
elapsed: string;
|
|
1406
|
+
/**
|
|
1407
|
+
* Returns a `<span>` whose text content is automatically updated every
|
|
1408
|
+
* 100ms by the widget's global timer. Place it anywhere in your returned
|
|
1409
|
+
* HTMLElement to get a live-ticking duration display.
|
|
1410
|
+
*/
|
|
1411
|
+
createElapsedElement: () => HTMLElement;
|
|
1393
1412
|
}) => HTMLElement | string | null;
|
|
1394
1413
|
/**
|
|
1395
1414
|
* Override the lightweight collapsed preview content shown for active reasoning rows.
|
|
@@ -1402,6 +1421,45 @@ export type AgentWidgetReasoningConfig = {
|
|
|
1402
1421
|
isActive: boolean;
|
|
1403
1422
|
config: AgentWidgetConfig;
|
|
1404
1423
|
}) => HTMLElement | string | null;
|
|
1424
|
+
/**
|
|
1425
|
+
* Template string for the header text while reasoning is active (streaming).
|
|
1426
|
+
*
|
|
1427
|
+
* **Placeholders:** `{duration}` (live-updating elapsed time).
|
|
1428
|
+
*
|
|
1429
|
+
* **Inline formatting:** `~dim~`, `*italic*`, `**bold**` — parsed at render time.
|
|
1430
|
+
*
|
|
1431
|
+
* When not set, falls back to the default "Thinking..." text.
|
|
1432
|
+
* @example "Thinking... ~{duration}~"
|
|
1433
|
+
*/
|
|
1434
|
+
activeTextTemplate?: string;
|
|
1435
|
+
/**
|
|
1436
|
+
* Template string for the header text when reasoning is complete.
|
|
1437
|
+
*
|
|
1438
|
+
* **Placeholders:** `{duration}` (final elapsed time).
|
|
1439
|
+
*
|
|
1440
|
+
* **Inline formatting:** `~dim~`, `*italic*`, `**bold**` — same syntax as `activeTextTemplate`.
|
|
1441
|
+
*
|
|
1442
|
+
* When not set, falls back to the default "Thought for X seconds" text.
|
|
1443
|
+
* @example "Thought for ~{duration}~"
|
|
1444
|
+
*/
|
|
1445
|
+
completeTextTemplate?: string;
|
|
1446
|
+
/**
|
|
1447
|
+
* Primary color for shimmer-color animation mode.
|
|
1448
|
+
* Defaults to the current text color.
|
|
1449
|
+
*/
|
|
1450
|
+
loadingAnimationColor?: string;
|
|
1451
|
+
/**
|
|
1452
|
+
* Secondary/end color for shimmer-color animation mode.
|
|
1453
|
+
* Creates a gradient sweep between `loadingAnimationColor` and this color.
|
|
1454
|
+
* @default "#3b82f6"
|
|
1455
|
+
*/
|
|
1456
|
+
loadingAnimationSecondaryColor?: string;
|
|
1457
|
+
/**
|
|
1458
|
+
* Duration of one full animation cycle in milliseconds.
|
|
1459
|
+
* Applies to pulse, shimmer, shimmer-color, and rainbow modes.
|
|
1460
|
+
* @default 2000
|
|
1461
|
+
*/
|
|
1462
|
+
loadingAnimationDuration?: number;
|
|
1405
1463
|
};
|
|
1406
1464
|
|
|
1407
1465
|
export type AgentWidgetSuggestionChipsConfig = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { createJsonStreamParser, parseFormattedTemplate } from "./formatting";
|
|
2
|
+
import { createJsonStreamParser, parseFormattedTemplate, computeReasoningElapsed } from "./formatting";
|
|
3
3
|
|
|
4
4
|
describe("JSON Stream Parser", () => {
|
|
5
5
|
it("should extract text field incrementally as JSON streams in", () => {
|
|
@@ -244,3 +244,27 @@ describe("parseFormattedTemplate", () => {
|
|
|
244
244
|
]);
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
describe("computeReasoningElapsed", () => {
|
|
249
|
+
it("uses durationMs when provided", () => {
|
|
250
|
+
const result = computeReasoningElapsed({
|
|
251
|
+
id: "r1", status: "complete", chunks: [], durationMs: 2600,
|
|
252
|
+
});
|
|
253
|
+
expect(result).toBe("2.6s");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("computes from startedAt/completedAt when durationMs is undefined", () => {
|
|
257
|
+
const result = computeReasoningElapsed({
|
|
258
|
+
id: "r2", status: "complete", chunks: [],
|
|
259
|
+
startedAt: 1000, completedAt: 16000,
|
|
260
|
+
});
|
|
261
|
+
expect(result).toBe("15s");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("returns <0.1s for very short durations", () => {
|
|
265
|
+
const result = computeReasoningElapsed({
|
|
266
|
+
id: "r3", status: "complete", chunks: [], durationMs: 50,
|
|
267
|
+
});
|
|
268
|
+
expect(result).toBe("<0.1s");
|
|
269
|
+
});
|
|
270
|
+
});
|
package/src/utils/formatting.ts
CHANGED
|
@@ -115,6 +115,21 @@ export const computeToolElapsed = (tool: AgentWidgetToolCall): string => {
|
|
|
115
115
|
return formatElapsedMs(durationMs);
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Computes the current elapsed time string for a reasoning block.
|
|
120
|
+
*/
|
|
121
|
+
export const computeReasoningElapsed = (reasoning: AgentWidgetReasoning): string => {
|
|
122
|
+
const durationMs =
|
|
123
|
+
reasoning.durationMs !== undefined
|
|
124
|
+
? reasoning.durationMs
|
|
125
|
+
: Math.max(
|
|
126
|
+
0,
|
|
127
|
+
(reasoning.completedAt ?? Date.now()) -
|
|
128
|
+
(reasoning.startedAt ?? reasoning.completedAt ?? Date.now())
|
|
129
|
+
);
|
|
130
|
+
return formatElapsedMs(durationMs);
|
|
131
|
+
};
|
|
132
|
+
|
|
118
133
|
/**
|
|
119
134
|
* Resolves a text template with tool call placeholders.
|
|
120
135
|
* Supported placeholders: {toolName}, {duration}
|
|
@@ -90,6 +90,18 @@ describe("computeMessageFingerprint", () => {
|
|
|
90
90
|
expect(fp1).not.toBe(fp2);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
it("changes when toolCall name changes", () => {
|
|
94
|
+
const fp1 = computeMessageFingerprint(
|
|
95
|
+
makeMessage({ toolCall: { status: "running" } }),
|
|
96
|
+
0
|
|
97
|
+
);
|
|
98
|
+
const fp2 = computeMessageFingerprint(
|
|
99
|
+
makeMessage({ toolCall: { status: "running", name: "UCP Search Catalog" } }),
|
|
100
|
+
0
|
|
101
|
+
);
|
|
102
|
+
expect(fp1).not.toBe(fp2);
|
|
103
|
+
});
|
|
104
|
+
|
|
93
105
|
it("changes when toolCall chunks change", () => {
|
|
94
106
|
const fp1 = computeMessageFingerprint(
|
|
95
107
|
makeMessage({ toolCall: { status: "running", chunks: ["Loaded tools"] } }),
|
|
@@ -53,6 +53,7 @@ export function computeMessageFingerprint(
|
|
|
53
53
|
message.llmContent?.length ?? 0,
|
|
54
54
|
message.approval?.status ?? "",
|
|
55
55
|
message.toolCall?.status ?? "",
|
|
56
|
+
message.toolCall?.name ?? "",
|
|
56
57
|
message.toolCall?.chunks?.length ?? 0,
|
|
57
58
|
message.toolCall?.chunks?.[message.toolCall.chunks.length - 1]?.slice(-32) ?? "",
|
|
58
59
|
typeof message.toolCall?.args === "string"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import { morphMessages } from "./morph";
|
|
4
|
+
|
|
5
|
+
function makeContainer(html: string): HTMLElement {
|
|
6
|
+
const div = document.createElement("div");
|
|
7
|
+
div.innerHTML = html;
|
|
8
|
+
return div;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function makeNewContent(html: string): HTMLElement {
|
|
12
|
+
const div = document.createElement("div");
|
|
13
|
+
div.innerHTML = html;
|
|
14
|
+
return div;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("morphMessages", () => {
|
|
18
|
+
describe("data-preserve-animation", () => {
|
|
19
|
+
it("preserves animated element when old and new both have data-preserve-animation with same text", () => {
|
|
20
|
+
const container = makeContainer(
|
|
21
|
+
'<span data-preserve-animation="true">Calling tool... 0.1s</span>'
|
|
22
|
+
);
|
|
23
|
+
const oldSpan = container.querySelector("span")!;
|
|
24
|
+
|
|
25
|
+
morphMessages(
|
|
26
|
+
container,
|
|
27
|
+
makeNewContent(
|
|
28
|
+
'<span data-preserve-animation="true">Calling tool... 0.1s</span>'
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(container.querySelector("span")).toBe(oldSpan);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("allows morph when new node drops data-preserve-animation (tool completed)", () => {
|
|
36
|
+
const container = makeContainer(
|
|
37
|
+
'<span data-preserve-animation="true">Calling tool... 0.5s</span>'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
morphMessages(
|
|
41
|
+
container,
|
|
42
|
+
makeNewContent("<span>Finished tool 0.5s</span>")
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(container.querySelector("span")!.textContent).toBe(
|
|
46
|
+
"Finished tool 0.5s"
|
|
47
|
+
);
|
|
48
|
+
expect(
|
|
49
|
+
container.querySelector("span")!.hasAttribute("data-preserve-animation")
|
|
50
|
+
).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("allows morph when text content changes despite both having data-preserve-animation", () => {
|
|
54
|
+
const container = makeContainer(
|
|
55
|
+
'<span data-preserve-animation="true">Calling tool... 0.1s</span>'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
morphMessages(
|
|
59
|
+
container,
|
|
60
|
+
makeNewContent(
|
|
61
|
+
'<span data-preserve-animation="true">Calling UCP Search Catalog... 0.2s</span>'
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(container.querySelector("span")!.textContent).toBe(
|
|
66
|
+
"Calling UCP Search Catalog... 0.2s"
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does not preserve when preserveTypingAnimation is false", () => {
|
|
71
|
+
const container = makeContainer(
|
|
72
|
+
'<span data-preserve-animation="true">Old text</span>'
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
morphMessages(
|
|
76
|
+
container,
|
|
77
|
+
makeNewContent(
|
|
78
|
+
'<span data-preserve-animation="true">New text</span>'
|
|
79
|
+
),
|
|
80
|
+
{ preserveTypingAnimation: false }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(container.querySelector("span")!.textContent).toBe("New text");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
package/src/utils/morph.ts
CHANGED
|
@@ -35,6 +35,14 @@ export const morphMessages = (
|
|
|
35
35
|
if (newNode instanceof HTMLElement && !newNode.hasAttribute("data-preserve-animation")) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
// Allow morph when content has meaningfully changed (e.g. tool name arrived)
|
|
39
|
+
if (newNode instanceof HTMLElement && newNode.hasAttribute("data-preserve-animation")) {
|
|
40
|
+
const oldText = oldNode.textContent ?? "";
|
|
41
|
+
const newText = newNode.textContent ?? "";
|
|
42
|
+
if (oldText !== newText) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
38
46
|
return false;
|
|
39
47
|
}
|
|
40
48
|
}
|