@apollo/client-ai-apps 0.3.0 → 0.3.2
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/.github/workflows/compare-build-output.yml +28 -0
- package/config/compare-build-output-to.sh +90 -0
- package/dist/core/ApolloClient.d.ts +14 -0
- package/dist/index.d.ts +17 -11
- package/dist/index.js +96 -44
- package/dist/react/ApolloProvider.d.ts +9 -0
- package/dist/react/context/ToolUseContext.d.ts +15 -0
- package/dist/{hooks → react/hooks}/useOpenAiGlobal.d.ts +1 -1
- package/dist/react/hooks/useOpenExternal.d.ts +3 -0
- package/dist/{hooks → react/hooks}/useRequestDisplayMode.d.ts +1 -1
- package/dist/{hooks → react/hooks}/useToolEffect.d.ts +0 -4
- package/dist/react/hooks/useToolOutput.d.ts +1 -0
- package/dist/react/hooks/useToolResponseMetadata.d.ts +1 -0
- package/dist/react/hooks/useWidgetState.d.ts +4 -0
- package/dist/types/openai.d.ts +1 -2
- package/dist/vite/index.js +5 -0
- package/package.json +5 -1
- package/src/{apollo_client/client.ts → core/ApolloClient.ts} +21 -17
- package/src/{apollo_client/client.test.ts → core/__tests__/ApolloClient.test.ts} +8 -9
- package/src/index.ts +36 -11
- package/src/{apollo_client/provider.tsx → react/ApolloProvider.tsx} +12 -8
- package/src/{apollo_client/provider.test.tsx → react/__tests__/ApolloProvider.test.tsx} +9 -9
- package/src/react/context/ToolUseContext.tsx +30 -0
- package/src/{hooks → react/hooks/__tests__}/useCallTool.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useOpenAiGlobal.test.ts +2 -2
- package/src/react/hooks/__tests__/useOpenExternal.test.tsx +24 -0
- package/src/{hooks → react/hooks/__tests__}/useRequestDisplayMode.test.ts +2 -2
- package/src/{hooks → react/hooks/__tests__}/useSendFollowUpMessage.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useToolEffect.test.tsx +2 -1
- package/src/{hooks → react/hooks/__tests__}/useToolInput.test.ts +1 -1
- package/src/{hooks → react/hooks/__tests__}/useToolName.test.ts +1 -1
- package/src/react/hooks/__tests__/useToolOutput.test.tsx +49 -0
- package/src/react/hooks/__tests__/useToolResponseMetadata.test.tsx +49 -0
- package/src/react/hooks/__tests__/useWidgetState.test.tsx +158 -0
- package/src/{hooks → react/hooks}/useOpenAiGlobal.ts +4 -4
- package/src/react/hooks/useOpenExternal.ts +11 -0
- package/src/{hooks → react/hooks}/useRequestDisplayMode.ts +1 -1
- package/src/{hooks → react/hooks}/useToolEffect.tsx +3 -26
- package/src/{hooks → react/hooks}/useToolName.ts +1 -1
- package/src/react/hooks/useToolOutput.ts +5 -0
- package/src/react/hooks/useToolResponseMetadata.ts +5 -0
- package/src/react/hooks/useWidgetState.ts +48 -0
- package/src/testing/internal/index.ts +2 -0
- package/src/testing/internal/matchers/index.d.ts +9 -0
- package/src/testing/internal/matchers/index.ts +1 -0
- package/src/testing/internal/matchers/toRerender.ts +49 -0
- package/src/testing/internal/openai/dispatchStateChange.ts +9 -0
- package/src/testing/internal/openai/stubOpenAiGlobals.ts +13 -0
- package/src/types/openai.ts +1 -1
- package/src/vite/{absolute_asset_imports_plugin.test.ts → __tests__/absolute_asset_imports_plugin.test.ts} +1 -1
- package/src/vite/{application_manifest_plugin.test.ts → __tests__/application_manifest_plugin.test.ts} +33 -7
- package/src/vite/application_manifest_plugin.ts +6 -0
- package/vitest-setup.ts +1 -0
- package/dist/apollo_client/client.d.ts +0 -13
- package/dist/apollo_client/provider.d.ts +0 -5
- /package/dist/{apollo_client/link → link}/ToolCallLink.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useSendFollowUpMessage.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useToolInput.d.ts +0 -0
- /package/dist/{hooks → react/hooks}/useToolName.d.ts +0 -0
- /package/src/{apollo_client/link → link}/ToolCallLink.ts +0 -0
- /package/src/{hooks → react/hooks}/useCallTool.ts +0 -0
- /package/src/{hooks → react/hooks}/useSendFollowUpMessage.ts +0 -0
- /package/src/{hooks → react/hooks}/useToolInput.ts +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { expect, test, describe, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
import { ApplicationManifest } from "
|
|
2
|
+
import { ApolloClient } from "../ApolloClient";
|
|
3
|
+
import { ApplicationManifest } from "../../types/application-manifest";
|
|
4
4
|
import { parse } from "graphql";
|
|
5
5
|
import { ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
|
|
6
|
-
import {
|
|
7
|
-
import { ToolCallLink } from "./link/ToolCallLink";
|
|
6
|
+
import { ToolCallLink } from "../../link/ToolCallLink";
|
|
8
7
|
|
|
9
8
|
describe("Client Basics", () => {
|
|
10
9
|
test("Should execute tool call when client.query is called", async () => {
|
|
@@ -59,7 +58,7 @@ describe("Client Basics", () => {
|
|
|
59
58
|
resource: "index.html",
|
|
60
59
|
};
|
|
61
60
|
|
|
62
|
-
const client = new
|
|
61
|
+
const client = new ApolloClient({
|
|
63
62
|
cache: new InMemoryCache(),
|
|
64
63
|
manifest: manifest as ApplicationManifest,
|
|
65
64
|
});
|
|
@@ -148,7 +147,7 @@ describe("prefetchData", () => {
|
|
|
148
147
|
resource: "index.html",
|
|
149
148
|
};
|
|
150
149
|
|
|
151
|
-
const client = new
|
|
150
|
+
const client = new ApolloClient({
|
|
152
151
|
cache: new InMemoryCache(),
|
|
153
152
|
manifest: manifest as ApplicationManifest,
|
|
154
153
|
});
|
|
@@ -230,7 +229,7 @@ describe("prefetchData", () => {
|
|
|
230
229
|
resource: "index.html",
|
|
231
230
|
};
|
|
232
231
|
|
|
233
|
-
const client = new
|
|
232
|
+
const client = new ApolloClient({
|
|
234
233
|
cache: new InMemoryCache(),
|
|
235
234
|
manifest: manifest as ApplicationManifest,
|
|
236
235
|
});
|
|
@@ -340,7 +339,7 @@ describe("prefetchData", () => {
|
|
|
340
339
|
resource: "index.html",
|
|
341
340
|
};
|
|
342
341
|
|
|
343
|
-
const client = new
|
|
342
|
+
const client = new ApolloClient({
|
|
344
343
|
cache: new InMemoryCache(),
|
|
345
344
|
manifest: manifest as ApplicationManifest,
|
|
346
345
|
});
|
|
@@ -431,7 +430,7 @@ describe("prefetchData", () => {
|
|
|
431
430
|
resource: "index.html",
|
|
432
431
|
};
|
|
433
432
|
|
|
434
|
-
const client = new
|
|
433
|
+
const client = new ApolloClient({
|
|
435
434
|
cache: new InMemoryCache(),
|
|
436
435
|
manifest: manifest as ApplicationManifest,
|
|
437
436
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
export type {
|
|
2
|
+
API,
|
|
3
|
+
CallTool,
|
|
4
|
+
DeviceType,
|
|
5
|
+
DisplayMode,
|
|
6
|
+
OpenAiGlobals,
|
|
7
|
+
SafeArea,
|
|
8
|
+
SafeAreaInsets,
|
|
9
|
+
Theme,
|
|
10
|
+
UserAgent,
|
|
11
|
+
UnknownObject,
|
|
12
|
+
} from "./types/openai";
|
|
13
|
+
export { SET_GLOBALS_EVENT_TYPE, SetGlobalsEvent } from "./types/openai";
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
ApplicationManifest,
|
|
17
|
+
ManifestOperation,
|
|
18
|
+
ManifestTool,
|
|
19
|
+
ManifestExtraInput,
|
|
20
|
+
ManifestCsp,
|
|
21
|
+
} from "./types/application-manifest";
|
|
22
|
+
|
|
23
|
+
export { ToolUseProvider } from "./react/context/ToolUseContext";
|
|
24
|
+
export { useOpenAiGlobal } from "./react/hooks/useOpenAiGlobal";
|
|
25
|
+
export { useToolName } from "./react/hooks/useToolName";
|
|
26
|
+
export { useToolInput } from "./react/hooks/useToolInput";
|
|
27
|
+
export { useSendFollowUpMessage } from "./react/hooks/useSendFollowUpMessage";
|
|
28
|
+
export { useRequestDisplayMode } from "./react/hooks/useRequestDisplayMode";
|
|
29
|
+
export { useToolEffect } from "./react/hooks/useToolEffect";
|
|
30
|
+
export { useOpenExternal } from "./react/hooks/useOpenExternal";
|
|
31
|
+
export { useToolOutput } from "./react/hooks/useToolOutput";
|
|
32
|
+
export { useToolResponseMetadata } from "./react/hooks/useToolResponseMetadata";
|
|
33
|
+
export { useWidgetState } from "./react/hooks/useWidgetState";
|
|
9
34
|
|
|
10
35
|
export * from "@apollo/client";
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export { ToolCallLink } from "./
|
|
36
|
+
export { ApolloClient } from "./core/ApolloClient";
|
|
37
|
+
export { ApolloProvider } from "./react/ApolloProvider";
|
|
38
|
+
export { ToolCallLink } from "./link/ToolCallLink";
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { ApolloProvider } from "@apollo/client/react";
|
|
3
|
-
import {
|
|
1
|
+
import React, { ReactNode, useEffect, useState } from "react";
|
|
2
|
+
import { ApolloProvider as BaseApolloProvider } from "@apollo/client/react";
|
|
3
|
+
import { ApolloClient } from "../core/ApolloClient";
|
|
4
4
|
import { SET_GLOBALS_EVENT_TYPE } from "../types/openai";
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export declare namespace ApolloProvider {
|
|
7
|
+
export interface Props {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
client: ApolloClient;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ApolloProvider = ({ children, client }: ApolloProvider.Props) => {
|
|
10
14
|
const [hasPreloaded, setHasPreloaded] = useState(false);
|
|
11
15
|
|
|
12
16
|
// This is to prevent against a race condition. We don't know if window.openai will be available when this loads or if it will become available shortly after.
|
|
@@ -33,6 +37,6 @@ export const ExtendedApolloProvider = ({
|
|
|
33
37
|
}, []);
|
|
34
38
|
|
|
35
39
|
return hasPreloaded ?
|
|
36
|
-
<
|
|
40
|
+
<BaseApolloProvider client={client}>{children}</BaseApolloProvider>
|
|
37
41
|
: null;
|
|
38
42
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { ApolloProvider } from "../ApolloProvider";
|
|
3
3
|
import { render } from "@testing-library/react";
|
|
4
|
-
import {
|
|
5
|
-
import { SET_GLOBALS_EVENT_TYPE } from "
|
|
4
|
+
import { ApolloClient } from "../../core/ApolloClient";
|
|
5
|
+
import { SET_GLOBALS_EVENT_TYPE } from "../../types/openai";
|
|
6
6
|
|
|
7
7
|
test("Should call prefetch data when window.open is immediately available", () => {
|
|
8
8
|
vi.stubGlobal("openai", {
|
|
@@ -11,9 +11,9 @@ test("Should call prefetch data when window.open is immediately available", () =
|
|
|
11
11
|
|
|
12
12
|
const client = {
|
|
13
13
|
prefetchData: vi.fn(async () => {}),
|
|
14
|
-
} as unknown as
|
|
14
|
+
} as unknown as ApolloClient;
|
|
15
15
|
|
|
16
|
-
render(<
|
|
16
|
+
render(<ApolloProvider client={client} />);
|
|
17
17
|
|
|
18
18
|
expect(client.prefetchData).toBeCalled();
|
|
19
19
|
});
|
|
@@ -21,9 +21,9 @@ test("Should call prefetch data when window.open is immediately available", () =
|
|
|
21
21
|
test("Should NOT call prefetch data when window.open is not immediately available", () => {
|
|
22
22
|
const client = {
|
|
23
23
|
prefetchData: vi.fn(async () => {}),
|
|
24
|
-
} as unknown as
|
|
24
|
+
} as unknown as ApolloClient;
|
|
25
25
|
|
|
26
|
-
render(<
|
|
26
|
+
render(<ApolloProvider client={client} />);
|
|
27
27
|
|
|
28
28
|
expect(client.prefetchData).not.toBeCalled();
|
|
29
29
|
});
|
|
@@ -31,9 +31,9 @@ test("Should NOT call prefetch data when window.open is not immediately availabl
|
|
|
31
31
|
test("Should call prefetch data when window.open is not immediately available and event is sent", () => {
|
|
32
32
|
const client = {
|
|
33
33
|
prefetchData: vi.fn(async () => {}),
|
|
34
|
-
} as unknown as
|
|
34
|
+
} as unknown as ApolloClient;
|
|
35
35
|
|
|
36
|
-
render(<
|
|
36
|
+
render(<ApolloProvider client={client} />);
|
|
37
37
|
|
|
38
38
|
window.dispatchEvent(new CustomEvent(SET_GLOBALS_EVENT_TYPE));
|
|
39
39
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { createContext, ReactNode, useContext, useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface ToolUseState {
|
|
4
|
+
appName: string;
|
|
5
|
+
hasNavigated: boolean;
|
|
6
|
+
setHasNavigated: (v: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ToolUseContext = createContext<ToolUseState | null>(null);
|
|
10
|
+
|
|
11
|
+
export declare namespace ToolUseProvider {
|
|
12
|
+
export interface Props {
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
appName: string;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ToolUseProvider({ children, appName }: ToolUseProvider.Props) {
|
|
19
|
+
const [hasNavigated, setHasNavigated] = useState(false);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ToolUseContext.Provider value={{ hasNavigated, setHasNavigated, appName }}>
|
|
23
|
+
{children}
|
|
24
|
+
</ToolUseContext.Provider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useToolUseState() {
|
|
29
|
+
return useContext(ToolUseContext);
|
|
30
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest";
|
|
2
|
-
import { useOpenAiGlobal } from "
|
|
2
|
+
import { useOpenAiGlobal } from "../useOpenAiGlobal";
|
|
3
3
|
import { renderHook, act } from "@testing-library/react";
|
|
4
|
-
import { SET_GLOBALS_EVENT_TYPE } from "
|
|
4
|
+
import { SET_GLOBALS_EVENT_TYPE } from "../../../types/openai";
|
|
5
5
|
|
|
6
6
|
test("Should update value when globals are updated and event it triggered", async () => {
|
|
7
7
|
vi.stubGlobal("openai", {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest";
|
|
2
|
+
import { renderHookToSnapshotStream } from "@testing-library/react-render-stream";
|
|
3
|
+
import { useOpenExternal } from "../useOpenExternal";
|
|
4
|
+
import { stubOpenAiGlobals } from "../../../testing/internal";
|
|
5
|
+
|
|
6
|
+
test("calls the global openExternal function", async () => {
|
|
7
|
+
const openExternalMock = vi.fn();
|
|
8
|
+
|
|
9
|
+
stubOpenAiGlobals({ openExternal: openExternalMock });
|
|
10
|
+
|
|
11
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
12
|
+
useOpenExternal()
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const openExternal = await takeSnapshot();
|
|
16
|
+
openExternal({ href: "https://example.com" });
|
|
17
|
+
|
|
18
|
+
expect(openExternalMock).toHaveBeenCalledTimes(1);
|
|
19
|
+
expect(openExternalMock).toHaveBeenCalledWith({
|
|
20
|
+
href: "https://example.com",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await expect(takeSnapshot).not.toRerender();
|
|
24
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest";
|
|
2
|
-
import { useRequestDisplayMode } from "
|
|
3
|
-
import { DisplayMode } from "
|
|
2
|
+
import { useRequestDisplayMode } from "../useRequestDisplayMode";
|
|
3
|
+
import { DisplayMode } from "../../../types/openai";
|
|
4
4
|
|
|
5
5
|
test("Should set display mode when returned function is called", async () => {
|
|
6
6
|
vi.stubGlobal("openai", {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest";
|
|
2
|
-
import { useSendFollowUpMessage } from "
|
|
2
|
+
import { useSendFollowUpMessage } from "../useSendFollowUpMessage";
|
|
3
3
|
|
|
4
4
|
test("Should set display mode when returned function is called", async () => {
|
|
5
5
|
vi.stubGlobal("openai", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest";
|
|
2
|
-
import { useToolEffect
|
|
2
|
+
import { useToolEffect } from "../useToolEffect";
|
|
3
3
|
import { renderHook } from "@testing-library/react";
|
|
4
|
+
import { ToolUseProvider } from "../../context/ToolUseContext";
|
|
4
5
|
|
|
5
6
|
test("Should trigger effect when tool name matches toolResponseMetadata", async () => {
|
|
6
7
|
vi.stubGlobal("openai", {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
dispatchStateChange,
|
|
4
|
+
stubOpenAiGlobals,
|
|
5
|
+
} from "../../../testing/internal";
|
|
6
|
+
import { renderHookToSnapshotStream } from "@testing-library/react-render-stream";
|
|
7
|
+
import { useToolOutput } from "../useToolOutput";
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.unstubAllGlobals();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("returns the tool output set in window", async () => {
|
|
14
|
+
stubOpenAiGlobals({ toolOutput: { test: true } });
|
|
15
|
+
|
|
16
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
17
|
+
useToolOutput()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
await expect(takeSnapshot()).resolves.toEqual({ test: true });
|
|
21
|
+
await expect(takeSnapshot).not.toRerender();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("returns null when not set", async () => {
|
|
25
|
+
stubOpenAiGlobals();
|
|
26
|
+
|
|
27
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
28
|
+
useToolOutput()
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
await expect(takeSnapshot()).resolves.toBeNull();
|
|
32
|
+
await expect(takeSnapshot).not.toRerender();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("reacts to changes in globals", async () => {
|
|
36
|
+
stubOpenAiGlobals({ toolOutput: { initial: true } });
|
|
37
|
+
|
|
38
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
39
|
+
useToolOutput()
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await expect(takeSnapshot()).resolves.toEqual({ initial: true });
|
|
43
|
+
|
|
44
|
+
window.openai.toolOutput = { updated: true };
|
|
45
|
+
dispatchStateChange();
|
|
46
|
+
|
|
47
|
+
await expect(takeSnapshot()).resolves.toEqual({ updated: true });
|
|
48
|
+
await expect(takeSnapshot).not.toRerender();
|
|
49
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
dispatchStateChange,
|
|
4
|
+
stubOpenAiGlobals,
|
|
5
|
+
} from "../../../testing/internal";
|
|
6
|
+
import { renderHookToSnapshotStream } from "@testing-library/react-render-stream";
|
|
7
|
+
import { useToolResponseMetadata } from "../useToolResponseMetadata";
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.unstubAllGlobals();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("returns the tool output set in window", async () => {
|
|
14
|
+
stubOpenAiGlobals({ toolResponseMetadata: { test: true } });
|
|
15
|
+
|
|
16
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
17
|
+
useToolResponseMetadata()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
await expect(takeSnapshot()).resolves.toEqual({ test: true });
|
|
21
|
+
await expect(takeSnapshot).not.toRerender();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("returns null when not set", async () => {
|
|
25
|
+
stubOpenAiGlobals();
|
|
26
|
+
|
|
27
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
28
|
+
useToolResponseMetadata()
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
await expect(takeSnapshot()).resolves.toBeNull();
|
|
32
|
+
await expect(takeSnapshot).not.toRerender();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("reacts to changes in globals", async () => {
|
|
36
|
+
stubOpenAiGlobals({ toolResponseMetadata: { initial: true } });
|
|
37
|
+
|
|
38
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
39
|
+
useToolResponseMetadata()
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await expect(takeSnapshot()).resolves.toEqual({ initial: true });
|
|
43
|
+
|
|
44
|
+
window.openai.toolResponseMetadata = { updated: true };
|
|
45
|
+
dispatchStateChange();
|
|
46
|
+
|
|
47
|
+
await expect(takeSnapshot()).resolves.toEqual({ updated: true });
|
|
48
|
+
await expect(takeSnapshot).not.toRerender();
|
|
49
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
disableActEnvironment,
|
|
4
|
+
renderHookToSnapshotStream,
|
|
5
|
+
} from "@testing-library/react-render-stream";
|
|
6
|
+
import { useWidgetState } from "../useWidgetState";
|
|
7
|
+
import { stubOpenAiGlobals } from "../../../testing/internal";
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.unstubAllGlobals();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("returns state from global", async () => {
|
|
14
|
+
stubOpenAiGlobals({ widgetState: { test: true } });
|
|
15
|
+
|
|
16
|
+
using _disabledAct = disableActEnvironment();
|
|
17
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
18
|
+
useWidgetState()
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const [widgetState] = await takeSnapshot();
|
|
22
|
+
|
|
23
|
+
expect(widgetState).toEqual({ test: true });
|
|
24
|
+
await expect(takeSnapshot).not.toRerender();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("returns null when global does not exist", async () => {
|
|
28
|
+
stubOpenAiGlobals();
|
|
29
|
+
|
|
30
|
+
using _disabledAct = disableActEnvironment();
|
|
31
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
32
|
+
useWidgetState()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const [widgetState] = await takeSnapshot();
|
|
36
|
+
|
|
37
|
+
expect(widgetState).toBeNull();
|
|
38
|
+
await expect(takeSnapshot).not.toRerender();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns provided default state when global does not exist", async () => {
|
|
42
|
+
stubOpenAiGlobals();
|
|
43
|
+
|
|
44
|
+
using _disabledAct = disableActEnvironment();
|
|
45
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
46
|
+
useWidgetState({ defaultValue: true })
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const [widgetState] = await takeSnapshot();
|
|
50
|
+
|
|
51
|
+
expect(widgetState).toEqual({ defaultValue: true });
|
|
52
|
+
await expect(takeSnapshot).not.toRerender();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("returns provided default state returned from init function when global does not exist", async () => {
|
|
56
|
+
stubOpenAiGlobals();
|
|
57
|
+
|
|
58
|
+
using _disabledAct = disableActEnvironment();
|
|
59
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
60
|
+
useWidgetState(() => ({ defaultValueFromFunction: true }))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const [widgetState] = await takeSnapshot();
|
|
64
|
+
|
|
65
|
+
expect(widgetState).toEqual({ defaultValueFromFunction: true });
|
|
66
|
+
await expect(takeSnapshot).not.toRerender();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("prefers global value over default value", async () => {
|
|
70
|
+
stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
|
|
71
|
+
|
|
72
|
+
using _disabledAct = disableActEnvironment();
|
|
73
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
74
|
+
useWidgetState({ defaultValue: true })
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const [widgetState] = await takeSnapshot();
|
|
78
|
+
|
|
79
|
+
expect(widgetState).toEqual({ globalWidgetState: true });
|
|
80
|
+
await expect(takeSnapshot).not.toRerender();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("rerenders with new value after setting new value", async () => {
|
|
84
|
+
stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
|
|
85
|
+
|
|
86
|
+
using _disabledAct = disableActEnvironment();
|
|
87
|
+
const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
|
|
88
|
+
() => useWidgetState()
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
const [widgetState] = await takeSnapshot();
|
|
93
|
+
|
|
94
|
+
expect(widgetState).toEqual({ globalWidgetState: true });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const [, setWidgetState] = getCurrentSnapshot();
|
|
98
|
+
setWidgetState({ rerendered: true });
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
const [widgetState] = await takeSnapshot();
|
|
102
|
+
|
|
103
|
+
expect(widgetState).toEqual({ rerendered: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await expect(takeSnapshot).not.toRerender();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("allows state setter function with previous value", async () => {
|
|
110
|
+
stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
|
|
111
|
+
|
|
112
|
+
using _disabledAct = disableActEnvironment();
|
|
113
|
+
const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
|
|
114
|
+
() => useWidgetState()
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
const [widgetState] = await takeSnapshot();
|
|
119
|
+
|
|
120
|
+
expect(widgetState).toEqual({ globalWidgetState: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const [, setWidgetState] = getCurrentSnapshot();
|
|
124
|
+
setWidgetState((prev) => ({ ...prev, rerendered: true }));
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
const [widgetState] = await takeSnapshot();
|
|
128
|
+
|
|
129
|
+
expect(widgetState).toEqual({ globalWidgetState: true, rerendered: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await expect(takeSnapshot).not.toRerender();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("updates value from window when changed globally", async () => {
|
|
136
|
+
stubOpenAiGlobals({ widgetState: { globalWidgetState: true } });
|
|
137
|
+
|
|
138
|
+
using _disabledAct = disableActEnvironment();
|
|
139
|
+
const { takeSnapshot } = await renderHookToSnapshotStream(() =>
|
|
140
|
+
useWidgetState()
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
{
|
|
144
|
+
const [widgetState] = await takeSnapshot();
|
|
145
|
+
|
|
146
|
+
expect(widgetState).toEqual({ globalWidgetState: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
window.openai.setWidgetState({ fromEvent: true });
|
|
150
|
+
|
|
151
|
+
{
|
|
152
|
+
const [widgetState] = await takeSnapshot();
|
|
153
|
+
|
|
154
|
+
expect(widgetState).toEqual({ fromEvent: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await expect(takeSnapshot).not.toRerender();
|
|
158
|
+
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { useSyncExternalStore } from "react";
|
|
1
|
+
import { useSyncExternalStore, useCallback } from "react";
|
|
2
2
|
import {
|
|
3
3
|
SET_GLOBALS_EVENT_TYPE,
|
|
4
4
|
SetGlobalsEvent,
|
|
5
5
|
OpenAiGlobals,
|
|
6
|
-
} from "
|
|
6
|
+
} from "../../types/openai";
|
|
7
7
|
|
|
8
8
|
export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
|
|
9
9
|
key: K
|
|
10
10
|
): OpenAiGlobals[K] {
|
|
11
11
|
return useSyncExternalStore(
|
|
12
|
-
(onChange) => {
|
|
12
|
+
useCallback((onChange) => {
|
|
13
13
|
const handleSetGlobal = (event: SetGlobalsEvent) => {
|
|
14
14
|
const value = event.detail.globals[key];
|
|
15
15
|
if (value === undefined) {
|
|
@@ -26,7 +26,7 @@ export function useOpenAiGlobal<K extends keyof OpenAiGlobals>(
|
|
|
26
26
|
return () => {
|
|
27
27
|
window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal);
|
|
28
28
|
};
|
|
29
|
-
},
|
|
29
|
+
}, []),
|
|
30
30
|
() => window.openai[key]
|
|
31
31
|
);
|
|
32
32
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { API } from "../../types/openai";
|
|
3
|
+
|
|
4
|
+
type OpenExternalFn = API<any>["openExternal"];
|
|
5
|
+
|
|
6
|
+
export function useOpenExternal() {
|
|
7
|
+
return useCallback<OpenExternalFn>(
|
|
8
|
+
(...args) => window.openai.openExternal(...args),
|
|
9
|
+
[]
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -1,37 +1,14 @@
|
|
|
1
|
-
import React, { useEffect
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import { useToolName } from "./useToolName";
|
|
3
3
|
import { useToolInput } from "./useToolInput";
|
|
4
|
-
|
|
5
|
-
type ToolUseState = {
|
|
6
|
-
appName: string;
|
|
7
|
-
hasNavigated: boolean;
|
|
8
|
-
setHasNavigated: (v: boolean) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const ToolUseContext = React.createContext<ToolUseState | null>(null);
|
|
12
|
-
|
|
13
|
-
export function ToolUseProvider({
|
|
14
|
-
children,
|
|
15
|
-
appName,
|
|
16
|
-
}: {
|
|
17
|
-
children: any;
|
|
18
|
-
appName: string;
|
|
19
|
-
}) {
|
|
20
|
-
const [hasNavigated, setHasNavigated] = useState(false);
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<ToolUseContext.Provider value={{ hasNavigated, setHasNavigated, appName }}>
|
|
24
|
-
{children}
|
|
25
|
-
</ToolUseContext.Provider>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
4
|
+
import { useToolUseState } from "../context/ToolUseContext";
|
|
28
5
|
|
|
29
6
|
export const useToolEffect = (
|
|
30
7
|
toolName: string | string[],
|
|
31
8
|
effect: (toolInput: any) => void,
|
|
32
9
|
deps: React.DependencyList = []
|
|
33
10
|
) => {
|
|
34
|
-
const ctx =
|
|
11
|
+
const ctx = useToolUseState();
|
|
35
12
|
const fullToolName = useToolName();
|
|
36
13
|
const toolInput = useToolInput();
|
|
37
14
|
if (!ctx)
|
|
@@ -3,5 +3,5 @@ import { useOpenAiGlobal } from "./useOpenAiGlobal";
|
|
|
3
3
|
export const useToolName = (): string | undefined => {
|
|
4
4
|
const toolResponseMetadata = useOpenAiGlobal("toolResponseMetadata");
|
|
5
5
|
|
|
6
|
-
return toolResponseMetadata?.toolName;
|
|
6
|
+
return toolResponseMetadata?.toolName as string;
|
|
7
7
|
};
|