@assistant-ui/react 0.15.17 → 0.15.19
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 +8 -2
- package/dist/context/providers/ThreadViewportProvider.js +2 -2
- package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -3
- package/dist/mcp-apps/McpAppRenderer.d.ts +30 -8
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +91 -47
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +57 -33
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/bridge.js +9 -4
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/mcp-apps/index.d.ts +2 -2
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +47 -79
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +61 -138
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +3 -8
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts +1 -2
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.js +1 -6
- package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +46 -15
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +40 -10
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts +6 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js +9 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js.map +1 -0
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +53 -61
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +129 -141
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/message/MessageParts.js +2 -14
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts +3 -3
- package/dist/primitives/message/MessagePartsGrouped.js +2 -2
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +2 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js +13 -36
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +3 -1
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +3 -3
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +36 -9
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +24 -2
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +9 -3
- package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
- package/dist/unstable/useMentionAdapter.js +50 -43
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useMessageStallDetection.d.ts +4 -5
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +16 -15
- package/dist/unstable/useMessageStallDetection.js.map +1 -1
- package/dist/unstable/useModelContextSnapshot.d.ts +34 -0
- package/dist/unstable/useModelContextSnapshot.d.ts.map +1 -0
- package/dist/unstable/useModelContextSnapshot.js +61 -0
- package/dist/unstable/useModelContextSnapshot.js.map +1 -0
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +2 -8
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts +14 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts.map +1 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js +85 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js.map +1 -0
- package/dist/unstable/webmcp/convertTools.d.ts +21 -0
- package/dist/unstable/webmcp/convertTools.d.ts.map +1 -0
- package/dist/unstable/webmcp/convertTools.js +161 -0
- package/dist/unstable/webmcp/convertTools.js.map +1 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts +21 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js +106 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -0
- package/dist/unstable/webmcp/webmcp-host.d.ts +39 -0
- package/dist/unstable/webmcp/webmcp-host.d.ts.map +1 -0
- package/dist/unstable/webmcp/webmcp-host.js +29 -0
- package/dist/unstable/webmcp/webmcp-host.js.map +1 -0
- package/dist/utils/Primitive.d.ts +10 -2
- package/dist/utils/Primitive.d.ts.map +1 -1
- package/dist/utils/Primitive.js +25 -6
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
- package/dist/utils/getSelectionMessageId.js +17 -3
- package/dist/utils/getSelectionMessageId.js.map +1 -1
- package/package.json +13 -13
- package/src/context/providers/ThreadViewportProvider.tsx +2 -2
- package/src/hooks/useToolCallElapsed.ts +3 -0
- package/src/index.ts +14 -1
- package/src/mcp-apps/McpAppRenderer.test.tsx +253 -5
- package/src/mcp-apps/McpAppRenderer.tsx +147 -52
- package/src/mcp-apps/app-frame.test.tsx +437 -1
- package/src/mcp-apps/app-frame.tsx +76 -21
- package/src/mcp-apps/bridge.test.ts +98 -0
- package/src/mcp-apps/bridge.ts +14 -3
- package/src/mcp-apps/index.ts +5 -1
- package/src/primitives/assistantModal/AssistantModalRoot.test.tsx +123 -0
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +11 -31
- package/src/primitives/assistantModal/AssistantModalTrigger.test.tsx +1 -16
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +43 -0
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +2 -10
- package/src/primitives/composer/ComposerInput.test.tsx +22 -0
- package/src/primitives/composer/ComposerInput.tsx +5 -9
- package/src/primitives/composer/ComposerInputPluginContext.tsx +0 -10
- package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +4 -2
- package/src/primitives/composer/trigger/TriggerPopoverBehavior.test.tsx +109 -0
- package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +4 -2
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.test.ts +52 -0
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.ts +13 -0
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +13 -7
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +8 -13
- package/src/primitives/message/MessageParts.tsx +1 -21
- package/src/primitives/message/MessagePartsGrouped.test.tsx +93 -0
- package/src/primitives/message/MessagePartsGrouped.tsx +6 -6
- package/src/primitives/messagePart/MessagePartText.test.tsx +69 -0
- package/src/primitives/messagePart/MessagePartText.tsx +22 -9
- package/src/primitives/reasoning/useScrollLock.test.tsx +129 -0
- package/src/primitives/reasoning/useScrollLock.ts +15 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +38 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +7 -6
- package/src/sandbox-host/SandboxHost.test.tsx +160 -2
- package/src/sandbox-host/SandboxHost.tsx +50 -10
- package/src/tests/MessageParts.rendererOptions.test.tsx +117 -0
- package/src/tests/toolCallTiming.test.tsx +55 -1
- package/src/unstable/useComposerInputHistory.test.tsx +2 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +202 -1
- package/src/unstable/useLiveCompletionAdapter.ts +38 -8
- package/src/unstable/useMentionAdapter.test.tsx +383 -0
- package/src/unstable/useMentionAdapter.ts +94 -57
- package/src/unstable/useMessageStallDetection.ts +26 -19
- package/src/unstable/useModelContextSnapshot.test.tsx +132 -0
- package/src/unstable/useModelContextSnapshot.ts +67 -0
- package/src/unstable/useSlashCommandAdapter.ts +2 -9
- package/src/unstable/webmcp/WebMcpRegistrationResource.ts +92 -0
- package/src/unstable/webmcp/__tests__/webmcp.fake.tsx +168 -0
- package/src/unstable/webmcp/convertTools.test.ts +553 -0
- package/src/unstable/webmcp/convertTools.ts +260 -0
- package/src/unstable/webmcp/useWebMcpProvider.test.tsx +466 -0
- package/src/unstable/webmcp/useWebMcpProvider.ts +148 -0
- package/src/unstable/webmcp/webmcp-host.test.ts +142 -0
- package/src/unstable/webmcp/webmcp-host.ts +83 -0
- package/src/utils/Primitive.test.tsx +46 -1
- package/src/utils/Primitive.tsx +37 -7
- package/src/utils/getSelectionMessageId.test.ts +176 -1
- package/src/utils/getSelectionMessageId.ts +21 -3
- package/dist/utils/invokeCallbackSafely.d.ts +0 -5
- package/dist/utils/invokeCallbackSafely.d.ts.map +0 -1
- package/dist/utils/invokeCallbackSafely.js +0 -15
- package/dist/utils/invokeCallbackSafely.js.map +0 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +0 -19
- package/dist/utils/useToolArgsFieldStatus.d.ts.map +0 -1
- package/dist/utils/useToolArgsFieldStatus.js +0 -23
- package/dist/utils/useToolArgsFieldStatus.js.map +0 -1
- package/src/utils/invokeCallbackSafely.ts +0 -11
- package/src/utils/useToolArgsFieldStatus.ts +0 -18
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
getDefaultWebMcpHost,
|
|
5
|
+
type WebMcpModelContext,
|
|
6
|
+
type WebMcpToolDescriptor,
|
|
7
|
+
} from "./webmcp-host";
|
|
8
|
+
|
|
9
|
+
type Host = { modelContext?: WebMcpModelContext };
|
|
10
|
+
|
|
11
|
+
const descriptor: WebMcpToolDescriptor = {
|
|
12
|
+
name: "get_weather",
|
|
13
|
+
description: "",
|
|
14
|
+
inputSchema: {},
|
|
15
|
+
execute: async () => ({ content: [] }),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const install = (
|
|
19
|
+
context: Partial<WebMcpModelContext>,
|
|
20
|
+
on: "document" | "navigator" = "document",
|
|
21
|
+
) => {
|
|
22
|
+
const host = (on === "document" ? document : navigator) as Host;
|
|
23
|
+
host.modelContext = context as WebMcpModelContext;
|
|
24
|
+
return context;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
delete (document as Host).modelContext;
|
|
29
|
+
delete (navigator as Host).modelContext;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("getDefaultWebMcpHost", () => {
|
|
33
|
+
it("reports unavailable when the page exposes no model context", () => {
|
|
34
|
+
const host = getDefaultWebMcpHost();
|
|
35
|
+
expect(host.available).toBe(false);
|
|
36
|
+
expect(() => host.registerTool(descriptor)()).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("reports unavailable and warns when the platform property has no registerTool", () => {
|
|
40
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
41
|
+
try {
|
|
42
|
+
install({} as Partial<WebMcpModelContext>);
|
|
43
|
+
const host = getDefaultWebMcpHost();
|
|
44
|
+
expect(host.available).toBe(false);
|
|
45
|
+
expect(() => host.registerTool(descriptor)()).not.toThrow();
|
|
46
|
+
expect(warn).toHaveBeenCalledWith(
|
|
47
|
+
expect.stringContaining("no callable registerTool"),
|
|
48
|
+
);
|
|
49
|
+
} finally {
|
|
50
|
+
warn.mockRestore();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("falls back to navigator.modelContext, preferring document when both exist", () => {
|
|
55
|
+
const fromNavigator = vi.fn();
|
|
56
|
+
install({ registerTool: fromNavigator }, "navigator");
|
|
57
|
+
expect(getDefaultWebMcpHost().available).toBe(true);
|
|
58
|
+
getDefaultWebMcpHost().registerTool(descriptor);
|
|
59
|
+
expect(fromNavigator).toHaveBeenCalledOnce();
|
|
60
|
+
|
|
61
|
+
const fromDocument = vi.fn();
|
|
62
|
+
install({ registerTool: fromDocument });
|
|
63
|
+
getDefaultWebMcpHost().registerTool(descriptor);
|
|
64
|
+
expect(fromDocument).toHaveBeenCalledOnce();
|
|
65
|
+
expect(fromNavigator).toHaveBeenCalledOnce();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("registers with an abort signal and aborts it on dispose", () => {
|
|
69
|
+
const registerTool = vi.fn();
|
|
70
|
+
install({ registerTool });
|
|
71
|
+
|
|
72
|
+
const dispose = getDefaultWebMcpHost().registerTool(descriptor);
|
|
73
|
+
const options = registerTool.mock.calls[0]![1];
|
|
74
|
+
expect(registerTool).toHaveBeenCalledWith(descriptor, expect.anything());
|
|
75
|
+
expect(options.signal.aborted).toBe(false);
|
|
76
|
+
|
|
77
|
+
dispose();
|
|
78
|
+
expect(options.signal.aborted).toBe(true);
|
|
79
|
+
expect(() => dispose()).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("reports a rejected registration to the caller", async () => {
|
|
83
|
+
install({
|
|
84
|
+
registerTool: () => Promise.reject(new Error("already registered")),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const onError = vi.fn();
|
|
88
|
+
getDefaultWebMcpHost().registerTool(descriptor, onError);
|
|
89
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledOnce());
|
|
90
|
+
expect(onError.mock.calls[0]![0]).toBeInstanceOf(Error);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("aborts the signal even when the registration was rejected", async () => {
|
|
94
|
+
let options: { signal?: AbortSignal } | undefined;
|
|
95
|
+
install({
|
|
96
|
+
registerTool: (_def, registerOptions) => {
|
|
97
|
+
options = registerOptions;
|
|
98
|
+
return Promise.reject(new Error("already registered"));
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const onError = vi.fn();
|
|
103
|
+
const dispose = getDefaultWebMcpHost().registerTool(descriptor, onError);
|
|
104
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledOnce());
|
|
105
|
+
|
|
106
|
+
dispose();
|
|
107
|
+
expect(options?.signal?.aborted).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("lets a synchronous registration failure propagate", () => {
|
|
111
|
+
install({
|
|
112
|
+
registerTool: () => {
|
|
113
|
+
throw new Error("already registered");
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(() => getDefaultWebMcpHost().registerTool(descriptor)).toThrow(
|
|
118
|
+
"already registered",
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("survives a registration that returns a bare thenable", () => {
|
|
123
|
+
install({
|
|
124
|
+
registerTool: () =>
|
|
125
|
+
({
|
|
126
|
+
then: (resolve: () => void) => resolve(),
|
|
127
|
+
}) as unknown as Promise<void>,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const dispose = getDefaultWebMcpHost().registerTool(descriptor);
|
|
131
|
+
expect(() => dispose()).not.toThrow();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("survives a registration that returns nothing", () => {
|
|
135
|
+
const registerTool = vi.fn();
|
|
136
|
+
install({ registerTool });
|
|
137
|
+
|
|
138
|
+
const dispose = getDefaultWebMcpHost().registerTool(descriptor);
|
|
139
|
+
dispose();
|
|
140
|
+
expect(registerTool.mock.calls[0]![1].signal.aborted).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type WebMcpContent =
|
|
2
|
+
| { type: "text"; text: string }
|
|
3
|
+
| { type: "image"; data: string; mimeType: string };
|
|
4
|
+
|
|
5
|
+
export type WebMcpCallToolResult = {
|
|
6
|
+
content: WebMcpContent[];
|
|
7
|
+
isError?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type WebMcpToolDescriptor = {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
inputSchema: unknown;
|
|
14
|
+
execute: (
|
|
15
|
+
args: unknown,
|
|
16
|
+
context?: { signal?: AbortSignal },
|
|
17
|
+
) => Promise<WebMcpCallToolResult>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type WebMcpModelContext = {
|
|
21
|
+
/**
|
|
22
|
+
* Aborting `options.signal` is what unregisters the tool. The explainer
|
|
23
|
+
* defines no other removal method, and a name-keyed one could not be used
|
|
24
|
+
* safely: a later registration may already hold the name.
|
|
25
|
+
*/
|
|
26
|
+
registerTool(
|
|
27
|
+
tool: WebMcpToolDescriptor,
|
|
28
|
+
options?: { signal?: AbortSignal },
|
|
29
|
+
): Promise<void> | void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type WebMcpHost = {
|
|
33
|
+
available: boolean;
|
|
34
|
+
registerTool(
|
|
35
|
+
def: WebMcpToolDescriptor,
|
|
36
|
+
onError?: (error: unknown) => void,
|
|
37
|
+
): () => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type ModelContextHost = { modelContext?: WebMcpModelContext } | undefined;
|
|
41
|
+
|
|
42
|
+
const isThenable = (value: unknown): value is Promise<unknown> =>
|
|
43
|
+
typeof (value as { then?: unknown } | null | undefined)?.then === "function";
|
|
44
|
+
|
|
45
|
+
// The explainer puts modelContext on document; navigator is tolerated for
|
|
46
|
+
// older drafts and extensions that still inject it there.
|
|
47
|
+
const resolveModelContext = (): WebMcpModelContext | undefined => {
|
|
48
|
+
const context =
|
|
49
|
+
(globalThis.document as ModelContextHost)?.modelContext ??
|
|
50
|
+
(globalThis.navigator as ModelContextHost)?.modelContext;
|
|
51
|
+
if (!context) return undefined;
|
|
52
|
+
if (typeof context.registerTool === "function") return context;
|
|
53
|
+
console.warn(
|
|
54
|
+
"[assistant-ui] Ignoring a modelContext with no callable registerTool; WebMCP reports unsupported.",
|
|
55
|
+
);
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const createHost = (context: WebMcpModelContext): WebMcpHost => ({
|
|
60
|
+
available: true,
|
|
61
|
+
registerTool: (def, onError) => {
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const handle = context.registerTool(def, { signal: controller.signal });
|
|
64
|
+
if (isThenable(handle)) {
|
|
65
|
+
handle.then(
|
|
66
|
+
() => {},
|
|
67
|
+
(error) => onError?.(error),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return () => controller.abort();
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const getDefaultWebMcpHost = (): WebMcpHost => {
|
|
75
|
+
const context = resolveModelContext();
|
|
76
|
+
if (!context) {
|
|
77
|
+
return {
|
|
78
|
+
available: false,
|
|
79
|
+
registerTool: () => () => {},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return createHost(context);
|
|
83
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
-
import { Primitive, withRenderProp } from "./Primitive";
|
|
3
|
+
import { Primitive, renderSlot, withRenderProp } from "./Primitive";
|
|
4
4
|
|
|
5
5
|
const ALL_NODES = [
|
|
6
6
|
"a",
|
|
@@ -103,3 +103,48 @@ describe("Primitive", () => {
|
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
describe("renderSlot", () => {
|
|
108
|
+
it("lets supplied children replace the render element's own", () => {
|
|
109
|
+
expect(
|
|
110
|
+
renderToStaticMarkup(renderSlot(<em>Original</em>, "Override", {})),
|
|
111
|
+
).toBe("<em>Override</em>");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("falls back to the render element's own children", () => {
|
|
115
|
+
expect(
|
|
116
|
+
renderToStaticMarkup(renderSlot(<em>Fallback</em>, undefined, {})),
|
|
117
|
+
).toBe("<em>Fallback</em>");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("treats null children as supplied rather than absent", () => {
|
|
121
|
+
expect(renderToStaticMarkup(renderSlot(<em>Fallback</em>, null, {}))).toBe(
|
|
122
|
+
"<em></em>",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("merges call-site props into the render element", () => {
|
|
127
|
+
const html = renderToStaticMarkup(
|
|
128
|
+
renderSlot(<em className="child" id="kept" />, "text", {
|
|
129
|
+
className: "parent",
|
|
130
|
+
id: "dropped",
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(html).toContain('id="kept"');
|
|
135
|
+
expect(html).toContain("parent");
|
|
136
|
+
expect(html).toContain("child");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("applies the same precedence as withRenderProp", () => {
|
|
140
|
+
expect(
|
|
141
|
+
renderToStaticMarkup(
|
|
142
|
+
renderSlot(<em>Fallback</em>, undefined, { className: "parent" }),
|
|
143
|
+
),
|
|
144
|
+
).toBe(
|
|
145
|
+
renderToStaticMarkup(
|
|
146
|
+
<Primitive.span className="parent" render={<em>Fallback</em>} />,
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
package/src/utils/Primitive.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ComponentProps,
|
|
2
3
|
type ComponentPropsWithoutRef,
|
|
3
4
|
type ComponentRef,
|
|
4
5
|
type ElementType,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
isValidElement,
|
|
13
14
|
} from "react";
|
|
14
15
|
import { Primitive as RadixPrimitive } from "radix-ui/internal";
|
|
16
|
+
import { Slot } from "radix-ui";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Thin wrapper around Radix `Primitive` that adds `render` prop support.
|
|
@@ -64,6 +66,39 @@ type PrimitiveRef<E extends PrimitiveNode> = ComponentRef<
|
|
|
64
66
|
(typeof RadixPrimitive)[E]
|
|
65
67
|
>;
|
|
66
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Composes the children of a `render` element. Outer children win when supplied;
|
|
71
|
+
* the render element's own children are the fallback.
|
|
72
|
+
*/
|
|
73
|
+
function composeRenderElement(
|
|
74
|
+
render: ReactElement,
|
|
75
|
+
children: ReactNode,
|
|
76
|
+
): ReactElement {
|
|
77
|
+
return cloneElement(
|
|
78
|
+
render,
|
|
79
|
+
undefined,
|
|
80
|
+
children !== undefined
|
|
81
|
+
? children
|
|
82
|
+
: (render.props as { children?: ReactNode }).children,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Composes a `render` element at a call site that has already computed its props.
|
|
88
|
+
*
|
|
89
|
+
* `withRenderProp` wraps a component; this covers the case where the props are
|
|
90
|
+
* already in hand and the composition happens inline.
|
|
91
|
+
*/
|
|
92
|
+
function renderSlot(
|
|
93
|
+
render: ReactElement,
|
|
94
|
+
children: ReactNode,
|
|
95
|
+
props: ComponentProps<typeof Slot.Root>,
|
|
96
|
+
): ReactElement {
|
|
97
|
+
return (
|
|
98
|
+
<Slot.Root {...props}>{composeRenderElement(render, children)}</Slot.Root>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
67
102
|
function withRenderProp<T extends ElementType>(Component: T) {
|
|
68
103
|
const Wrapped = forwardRef<ComponentRef<T>, WithRenderPropRuntimeProps<T>>(
|
|
69
104
|
(
|
|
@@ -78,14 +113,9 @@ function withRenderProp<T extends ElementType>(Component: T) {
|
|
|
78
113
|
const Comp = Component as any;
|
|
79
114
|
|
|
80
115
|
if (render && isValidElement(render)) {
|
|
81
|
-
const renderChildren =
|
|
82
|
-
children !== undefined
|
|
83
|
-
? children
|
|
84
|
-
: ((render.props as Record<string, unknown>).children as ReactNode);
|
|
85
|
-
|
|
86
116
|
return (
|
|
87
117
|
<Comp {...(rest as any)} asChild ref={ref}>
|
|
88
|
-
{
|
|
118
|
+
{composeRenderElement(render, children)}
|
|
89
119
|
</Comp>
|
|
90
120
|
);
|
|
91
121
|
}
|
|
@@ -129,5 +159,5 @@ const Primitive = NODES.reduce(
|
|
|
129
159
|
},
|
|
130
160
|
);
|
|
131
161
|
|
|
132
|
-
export { Primitive, withRenderProp };
|
|
162
|
+
export { Primitive, renderSlot, withRenderProp };
|
|
133
163
|
export type { PrimitiveProps, WithRenderPropProps };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @vitest-environment jsdom
|
|
3
3
|
*/
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { afterEach, assert, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { getSelectionMessageId } from "./getSelectionMessageId";
|
|
6
6
|
|
|
7
7
|
const selectText = (start: Text, end = start) => {
|
|
@@ -24,6 +24,7 @@ const textNode = (selector: string) => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
afterEach(() => {
|
|
27
|
+
vi.restoreAllMocks();
|
|
27
28
|
window.getSelection()?.removeAllRanges();
|
|
28
29
|
document.body.replaceChildren();
|
|
29
30
|
});
|
|
@@ -116,6 +117,180 @@ describe("getSelectionMessageId", () => {
|
|
|
116
117
|
).toBeNull();
|
|
117
118
|
});
|
|
118
119
|
|
|
120
|
+
it.each(["", "data-aui-quote-selectable"])(
|
|
121
|
+
"rejects selections spanning an excluded subtree with root %s",
|
|
122
|
+
(marker) => {
|
|
123
|
+
document.body.innerHTML = `
|
|
124
|
+
<div data-message-id="message-1" ${marker}>
|
|
125
|
+
<span id="before">before</span>
|
|
126
|
+
<span data-aui-quote-selectable="false">excluded</span>
|
|
127
|
+
<span id="after">after</span>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
expect(getSelectionMessageId(selectText(textNode("#before")))).toBe(
|
|
132
|
+
"message-1",
|
|
133
|
+
);
|
|
134
|
+
expect(
|
|
135
|
+
getSelectionMessageId(
|
|
136
|
+
selectText(textNode("#before"), textNode("#after")),
|
|
137
|
+
),
|
|
138
|
+
).toBeNull();
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
it("rejects a paragraph selection that spans an inline exclusion", () => {
|
|
143
|
+
document.body.innerHTML = `
|
|
144
|
+
<div data-message-id="message-1">
|
|
145
|
+
<p id="text" data-aui-quote-selectable>before <span data-aui-quote-selectable="false">[1]</span> after</p>
|
|
146
|
+
</div>
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
const paragraph = document.querySelector("#text");
|
|
150
|
+
assert(paragraph);
|
|
151
|
+
const selection = selectText(textNode("#text"));
|
|
152
|
+
selection.getRangeAt(0).selectNodeContents(paragraph);
|
|
153
|
+
|
|
154
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("accepts selections that stop or start at an excluded node boundary", () => {
|
|
158
|
+
document.body.innerHTML = `
|
|
159
|
+
<div data-message-id="message-1" data-aui-quote-selectable>
|
|
160
|
+
<span id="before">before</span><span id="chip" data-aui-quote-selectable="false">[1]</span><span id="after">after</span>
|
|
161
|
+
</div>
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const chip = document.querySelector("#chip");
|
|
165
|
+
assert(chip);
|
|
166
|
+
const before = selectText(textNode("#before"));
|
|
167
|
+
before.getRangeAt(0).setEndBefore(chip);
|
|
168
|
+
expect(getSelectionMessageId(before)).toBe("message-1");
|
|
169
|
+
|
|
170
|
+
const after = selectText(textNode("#after"));
|
|
171
|
+
after.getRangeAt(0).setStartAfter(chip);
|
|
172
|
+
expect(getSelectionMessageId(after)).toBe("message-1");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("preserves a nested quote region inside an excluded subtree", () => {
|
|
176
|
+
document.body.innerHTML = `
|
|
177
|
+
<div data-message-id="message-1">
|
|
178
|
+
<div data-aui-quote-selectable="false">
|
|
179
|
+
<p data-aui-quote-selectable>
|
|
180
|
+
<span id="before">before</span>
|
|
181
|
+
<span data-aui-quote-selectable="false">[1]</span>
|
|
182
|
+
<span id="after">after</span>
|
|
183
|
+
</p>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
expect(getSelectionMessageId(selectText(textNode("#before")))).toBe(
|
|
189
|
+
"message-1",
|
|
190
|
+
);
|
|
191
|
+
expect(
|
|
192
|
+
getSelectionMessageId(
|
|
193
|
+
selectText(textNode("#before"), textNode("#after")),
|
|
194
|
+
),
|
|
195
|
+
).toBeNull();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("checks another range outside the active quote region, including its excluded ancestor", () => {
|
|
199
|
+
document.body.innerHTML = `
|
|
200
|
+
<div data-message-id="message-1">
|
|
201
|
+
<p id="other">before <span id="chip" data-aui-quote-selectable="false">[1]</span> after</p>
|
|
202
|
+
<p id="active" data-aui-quote-selectable>active text</p>
|
|
203
|
+
</div>
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
const other = document.querySelector("#other");
|
|
207
|
+
assert(other);
|
|
208
|
+
const range = document.createRange();
|
|
209
|
+
range.selectNodeContents(other);
|
|
210
|
+
const selection = selectText(textNode("#active"));
|
|
211
|
+
const active = selection.getRangeAt(0);
|
|
212
|
+
vi.spyOn(selection, "rangeCount", "get").mockReturnValue(2);
|
|
213
|
+
vi.spyOn(selection, "getRangeAt").mockImplementation((index) => {
|
|
214
|
+
if (index === 0) return range;
|
|
215
|
+
if (index === 1) return active;
|
|
216
|
+
throw new DOMException("Range index out of bounds", "IndexSizeError");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
220
|
+
|
|
221
|
+
range.selectNodeContents(textNode("#chip"));
|
|
222
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("rejects another range inside the excluded ancestor of the active quote region", () => {
|
|
226
|
+
document.body.innerHTML = `
|
|
227
|
+
<div data-message-id="message-1">
|
|
228
|
+
<div data-aui-quote-selectable="false">
|
|
229
|
+
<span id="sibling">excluded prose</span>
|
|
230
|
+
<p id="active" data-aui-quote-selectable>active text</p>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
const range = document.createRange();
|
|
236
|
+
range.selectNodeContents(textNode("#sibling"));
|
|
237
|
+
const selection = selectText(textNode("#active"));
|
|
238
|
+
const active = selection.getRangeAt(0);
|
|
239
|
+
vi.spyOn(selection, "rangeCount", "get").mockReturnValue(2);
|
|
240
|
+
vi.spyOn(selection, "getRangeAt").mockImplementation((index) => {
|
|
241
|
+
if (index === 0) return range;
|
|
242
|
+
if (index === 1) return active;
|
|
243
|
+
throw new DOMException("Range index out of bounds", "IndexSizeError");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("rejects another range in a different message", () => {
|
|
250
|
+
document.body.innerHTML = `
|
|
251
|
+
<div data-message-id="message-1"><p id="active">active text</p></div>
|
|
252
|
+
<div data-message-id="message-2">
|
|
253
|
+
<p id="other" data-aui-quote-selectable="false">excluded prose</p>
|
|
254
|
+
</div>
|
|
255
|
+
`;
|
|
256
|
+
|
|
257
|
+
const range = document.createRange();
|
|
258
|
+
range.selectNodeContents(textNode("#other"));
|
|
259
|
+
const selection = selectText(textNode("#active"));
|
|
260
|
+
const active = selection.getRangeAt(0);
|
|
261
|
+
vi.spyOn(selection, "rangeCount", "get").mockReturnValue(2);
|
|
262
|
+
vi.spyOn(selection, "getRangeAt").mockImplementation((index) => {
|
|
263
|
+
if (index === 0) return range;
|
|
264
|
+
if (index === 1) return active;
|
|
265
|
+
throw new DOMException("Range index out of bounds", "IndexSizeError");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(getSelectionMessageId(selection)).toBeNull();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("accepts disjoint ranges on either side of an excluded gap", () => {
|
|
272
|
+
document.body.innerHTML = `
|
|
273
|
+
<div data-message-id="message-1" data-aui-quote-selectable>
|
|
274
|
+
<p id="before">before</p>
|
|
275
|
+
<p data-aui-quote-selectable="false">excluded</p>
|
|
276
|
+
<p id="after">after</p>
|
|
277
|
+
</div>
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
const before = document.createRange();
|
|
281
|
+
before.selectNodeContents(textNode("#before"));
|
|
282
|
+
const selection = selectText(textNode("#after"));
|
|
283
|
+
const after = selection.getRangeAt(0);
|
|
284
|
+
vi.spyOn(selection, "rangeCount", "get").mockReturnValue(2);
|
|
285
|
+
vi.spyOn(selection, "getRangeAt").mockImplementation((index) => {
|
|
286
|
+
if (index === 0) return before;
|
|
287
|
+
if (index === 1) return after;
|
|
288
|
+
throw new DOMException("Range index out of bounds", "IndexSizeError");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(getSelectionMessageId(selection)).toBe("message-1");
|
|
292
|
+
});
|
|
293
|
+
|
|
119
294
|
it("rejects selections outside quote-selectable regions when a message opts in", () => {
|
|
120
295
|
document.body.innerHTML = `
|
|
121
296
|
<div data-message-id="message-1">
|
|
@@ -43,6 +43,17 @@ const findQuoteMarker = (
|
|
|
43
43
|
return marker;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
const intersectsExcluded = (scope: Element, selection: Selection): boolean => {
|
|
47
|
+
const ranges = Array.from({ length: selection.rangeCount }, (_, i) =>
|
|
48
|
+
selection.getRangeAt(i),
|
|
49
|
+
);
|
|
50
|
+
for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {
|
|
51
|
+
if (!isExcluded(marker)) continue;
|
|
52
|
+
if (ranges.some((range) => range.intersectsNode(marker))) return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
56
|
+
|
|
46
57
|
export const getSelectionMessageId = (selection: Selection): string | null => {
|
|
47
58
|
const { anchorNode, focusNode } = selection;
|
|
48
59
|
if (!anchorNode || !focusNode) return null;
|
|
@@ -63,9 +74,16 @@ export const getSelectionMessageId = (selection: Selection): string | null => {
|
|
|
63
74
|
if (anchorMarker && isExcluded(anchorMarker)) return null;
|
|
64
75
|
if (focusMarker && isExcluded(focusMarker)) return null;
|
|
65
76
|
|
|
66
|
-
if (
|
|
77
|
+
if (hasQuoteSelectableRegion(anchorMessageElement)) {
|
|
78
|
+
if (!anchorMarker || anchorMarker !== focusMarker) return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const scope = anchorMarker ?? anchorMessageElement;
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
84
|
+
const { commonAncestorContainer } = selection.getRangeAt(i);
|
|
85
|
+
if (!scope.contains(commonAncestorContainer)) return null;
|
|
86
|
+
}
|
|
69
87
|
|
|
70
|
-
return messageId;
|
|
88
|
+
return intersectsExcluded(scope, selection) ? null : messageId;
|
|
71
89
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invokeCallbackSafely.d.ts","names":[],"sources":["../../src/utils/invokeCallbackSafely.ts"],"mappings":";cAAa,uBAAwB,uBAAuB"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//#region src/utils/invokeCallbackSafely.ts
|
|
2
|
-
const invokeCallbackSafely = (invoke, name) => {
|
|
3
|
-
const reportFailure = (error) => {
|
|
4
|
-
console.error(`[assistant-ui] ${name} callback threw an error`, error);
|
|
5
|
-
};
|
|
6
|
-
try {
|
|
7
|
-
Promise.resolve(invoke()).catch(reportFailure);
|
|
8
|
-
} catch (error) {
|
|
9
|
-
reportFailure(error);
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
13
|
-
export { invokeCallbackSafely };
|
|
14
|
-
|
|
15
|
-
//# sourceMappingURL=invokeCallbackSafely.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invokeCallbackSafely.js","names":["invokeCallbackSafely","invoke","name","reportFailure","error","console","Promise","resolve","catch"],"sources":["../../src/utils/invokeCallbackSafely.ts"],"sourcesContent":["export const invokeCallbackSafely = (invoke: () => unknown, name: string) => {\n const reportFailure = (error: unknown) => {\n console.error(`[assistant-ui] ${name} callback threw an error`, error);\n };\n\n try {\n void Promise.resolve(invoke()).catch(reportFailure);\n } catch (error) {\n reportFailure(error);\n }\n};\n"],"mappings":";AAAA,MAAaA,wBAAwBC,QAAuBC,SAAiB;CAC3E,MAAMC,iBAAiBC,UAAmB;EACxCC,QAAQD,MAAM,kBAAkBF,KAAI,2BAA4BE,KAAK;CACvE;CAEA,IAAI;EACF,QAAaG,QAAQN,OAAO,CAAC,CAAC,CAACO,MAAML,aAAa;CACpD,SAASC,OAAO;EACdD,cAAcC,KAAK;CACrB;AACF"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
//#region src/utils/useToolArgsFieldStatus.d.ts
|
|
2
|
-
declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
|
|
3
|
-
type: string;
|
|
4
|
-
} | {
|
|
5
|
-
readonly type: "running";
|
|
6
|
-
} | {
|
|
7
|
-
readonly type: "complete";
|
|
8
|
-
} | {
|
|
9
|
-
readonly type: "incomplete";
|
|
10
|
-
readonly reason: "cancelled" | "length" | "content-filter" | "other" | "error";
|
|
11
|
-
readonly error?: unknown;
|
|
12
|
-
} | {
|
|
13
|
-
readonly type: "incomplete";
|
|
14
|
-
readonly reason: "tool-calls";
|
|
15
|
-
readonly error?: import("assistant-stream/utils").ReadonlyJSONValue;
|
|
16
|
-
};
|
|
17
|
-
//#endregion
|
|
18
|
-
export { useToolArgsFieldStatus };
|
|
19
|
-
//# sourceMappingURL=useToolArgsFieldStatus.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useToolArgsFieldStatus.d.ts","names":[],"sources":["../../src/utils/useToolArgsFieldStatus.ts"],"mappings":";cAKa,yBAA0B"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
2
|
-
import { useAuiState } from "@assistant-ui/store";
|
|
3
|
-
import { getPartialJsonObjectFieldState } from "assistant-stream/utils";
|
|
4
|
-
//#region src/utils/useToolArgsFieldStatus.ts
|
|
5
|
-
const COMPLETE_STATUS = { type: "complete" };
|
|
6
|
-
const useToolArgsFieldStatus = (fieldPath) => {
|
|
7
|
-
const $ = c(2);
|
|
8
|
-
let t0;
|
|
9
|
-
if ($[0] !== fieldPath) {
|
|
10
|
-
t0 = (s) => {
|
|
11
|
-
if (s.part.type !== "tool-call") throw new Error("useToolArgsFieldStatus can only be used inside tool-call message parts");
|
|
12
|
-
if (getPartialJsonObjectFieldState(s.part.args, fieldPath) === "complete" || s.part.status?.type === "requires-action") return COMPLETE_STATUS;
|
|
13
|
-
return s.part.status;
|
|
14
|
-
};
|
|
15
|
-
$[0] = fieldPath;
|
|
16
|
-
$[1] = t0;
|
|
17
|
-
} else t0 = $[1];
|
|
18
|
-
return useAuiState(t0);
|
|
19
|
-
};
|
|
20
|
-
//#endregion
|
|
21
|
-
export { useToolArgsFieldStatus };
|
|
22
|
-
|
|
23
|
-
//# sourceMappingURL=useToolArgsFieldStatus.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useToolArgsFieldStatus.js","names":["getPartialJsonObjectFieldState","useAuiState","COMPLETE_STATUS","type","useToolArgsFieldStatus","fieldPath","$","_c","t0","s","part","Error","state","args","status"],"sources":["../../src/utils/useToolArgsFieldStatus.ts"],"sourcesContent":["import { getPartialJsonObjectFieldState } from \"assistant-stream/utils\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nconst COMPLETE_STATUS = { type: \"complete\" };\n\nexport const useToolArgsFieldStatus = (fieldPath: (string | number)[]) => {\n return useAuiState((s) => {\n if (s.part.type !== \"tool-call\")\n throw new Error(\n \"useToolArgsFieldStatus can only be used inside tool-call message parts\",\n );\n\n const state = getPartialJsonObjectFieldState(s.part.args, fieldPath);\n if (state === \"complete\" || s.part.status?.type === \"requires-action\")\n return COMPLETE_STATUS;\n return s.part.status;\n });\n};\n"],"mappings":";;;;AAGA,MAAME,kBAAkB,EAAEC,MAAM,WAAW;AAE3C,MAAaC,0BAAyBC,cAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAC;CAAA,IAAAF,EAAA,OAAAD,WAAA;EACjBG,MAAAC,MAAA;GACjB,IAAIA,EAACC,KAAKP,SAAU,aAClB,MAAM,IAAIQ,MACR,wEACF;GAGF,IADcX,+BAA+BS,EAACC,KAAKG,MAAOR,SACtDO,MAAU,cAAcH,EAACC,KAAKI,QAAaX,SAAK,mBAAiB,OAC5DD;GAAgB,OAClBO,EAACC,KAAKI;EAAO;EACrBR,EAAA,KAAAD;EAAAC,EAAA,KAAAE;CAAA,OAAAA,KAAAF,EAAA;CAAA,OAVML,YAAYO,EAUlB;AAAC"}
|