@ulearning/ai-generative-frame 0.0.0-stage → 1.0.0-next.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/LICENSE +21 -0
- package/README.md +233 -2
- package/dist/assistant-ui/entry.d.ts +34 -0
- package/dist/assistant-ui/entry.d.ts.map +1 -0
- package/dist/assistant-ui/entry.js +50 -0
- package/dist/assistant-ui/entry.js.map +1 -0
- package/dist/assistant-ui/extension.d.ts +18 -0
- package/dist/assistant-ui/extension.d.ts.map +1 -0
- package/dist/assistant-ui/extension.js +0 -0
- package/dist/assistant-ui/history.d.ts +15 -0
- package/dist/assistant-ui/history.d.ts.map +1 -0
- package/dist/assistant-ui/history.js +39 -0
- package/dist/assistant-ui/history.js.map +1 -0
- package/dist/assistant-ui/modelContext.d.ts +45 -0
- package/dist/assistant-ui/modelContext.d.ts.map +1 -0
- package/dist/assistant-ui/modelContext.js +88 -0
- package/dist/assistant-ui/modelContext.js.map +1 -0
- package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
- package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
- package/dist/assistant-ui/snapshotSaver.js +83 -0
- package/dist/assistant-ui/snapshotSaver.js.map +1 -0
- package/dist/assistant-ui/tool-calls.d.ts +21 -0
- package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
- package/dist/assistant-ui/tool-calls.js +65 -0
- package/dist/assistant-ui/tool-calls.js.map +1 -0
- package/dist/assistant-ui/toolkit.d.ts +96 -0
- package/dist/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/assistant-ui/toolkit.js +456 -0
- package/dist/assistant-ui/toolkit.js.map +1 -0
- package/dist/assistant-ui/useSharedContext.d.ts +14 -0
- package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
- package/dist/assistant-ui/useSharedContext.js +27 -0
- package/dist/assistant-ui/useSharedContext.js.map +1 -0
- package/dist/assistant-ui.d.ts +3 -0
- package/dist/assistant-ui.d.ts.map +1 -0
- package/dist/assistant-ui.js +1 -0
- package/dist/bootstrap.d.ts +23 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +78 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/csp.d.ts +25 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +78 -0
- package/dist/csp.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/json-schema.d.ts +70 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +163 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/preview.d.ts +30 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +60 -0
- package/dist/preview.js.map +1 -0
- package/dist/prompts/guidance.d.ts +41 -0
- package/dist/prompts/guidance.d.ts.map +1 -0
- package/dist/prompts/guidance.js +268 -0
- package/dist/prompts/guidance.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +1 -0
- package/dist/protocol.d.ts +114 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +49 -0
- package/dist/protocol.js.map +1 -0
- package/dist/repair/repair.d.ts +72 -0
- package/dist/repair/repair.d.ts.map +1 -0
- package/dist/repair/repair.js +103 -0
- package/dist/repair/repair.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +1 -0
- package/dist/rpc.d.ts +60 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +127 -0
- package/dist/rpc.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +17 -0
- package/dist/runtime/diagnostics.d.ts.map +1 -0
- package/dist/runtime/diagnostics.js +128 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/generated.d.ts +2 -0
- package/dist/runtime/generated.d.ts.map +1 -0
- package/dist/runtime/generated.js +5 -0
- package/dist/runtime/generated.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +264 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/morph.d.ts +51 -0
- package/dist/runtime/morph.d.ts.map +1 -0
- package/dist/runtime/morph.js +251 -0
- package/dist/runtime/morph.js.map +1 -0
- package/dist/runtime/screenshot.d.ts +23 -0
- package/dist/runtime/screenshot.d.ts.map +1 -0
- package/dist/runtime/screenshot.js +188 -0
- package/dist/runtime/screenshot.js.map +1 -0
- package/dist/runtime/size.d.ts +10 -0
- package/dist/runtime/size.d.ts.map +1 -0
- package/dist/runtime/size.js +21 -0
- package/dist/runtime/size.js.map +1 -0
- package/dist/runtime/storage.d.ts +4 -0
- package/dist/runtime/storage.d.ts.map +1 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/spec/actions.d.ts +40 -0
- package/dist/spec/actions.d.ts.map +1 -0
- package/dist/spec/actions.js +53 -0
- package/dist/spec/actions.js.map +1 -0
- package/dist/spec/assistant-ui/thread.d.ts +66 -0
- package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
- package/dist/spec/assistant-ui/thread.js +252 -0
- package/dist/spec/assistant-ui/thread.js.map +1 -0
- package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
- package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/spec/assistant-ui/toolkit.js +142 -0
- package/dist/spec/assistant-ui/toolkit.js.map +1 -0
- package/dist/spec/assistant-ui/web.d.ts +6 -0
- package/dist/spec/assistant-ui/web.d.ts.map +1 -0
- package/dist/spec/assistant-ui/web.js +48 -0
- package/dist/spec/assistant-ui/web.js.map +1 -0
- package/dist/spec/assistant-ui.d.ts +3 -0
- package/dist/spec/assistant-ui.d.ts.map +1 -0
- package/dist/spec/assistant-ui.js +1 -0
- package/dist/spec/calc.d.ts +8 -0
- package/dist/spec/calc.d.ts.map +1 -0
- package/dist/spec/calc.js +498 -0
- package/dist/spec/calc.js.map +1 -0
- package/dist/spec/catalog.d.ts +52 -0
- package/dist/spec/catalog.d.ts.map +1 -0
- package/dist/spec/catalog.js +104 -0
- package/dist/spec/catalog.js.map +1 -0
- package/dist/spec/expressions.d.ts +45 -0
- package/dist/spec/expressions.d.ts.map +1 -0
- package/dist/spec/expressions.js +199 -0
- package/dist/spec/expressions.js.map +1 -0
- package/dist/spec/patch.d.ts +20 -0
- package/dist/spec/patch.d.ts.map +1 -0
- package/dist/spec/patch.js +77 -0
- package/dist/spec/patch.js.map +1 -0
- package/dist/spec/pending.d.ts +15 -0
- package/dist/spec/pending.d.ts.map +1 -0
- package/dist/spec/pending.js +33 -0
- package/dist/spec/pending.js.map +1 -0
- package/dist/spec/pointer.d.ts +17 -0
- package/dist/spec/pointer.d.ts.map +1 -0
- package/dist/spec/pointer.js +88 -0
- package/dist/spec/pointer.js.map +1 -0
- package/dist/spec/prompt.d.ts +29 -0
- package/dist/spec/prompt.d.ts.map +1 -0
- package/dist/spec/prompt.js +145 -0
- package/dist/spec/prompt.js.map +1 -0
- package/dist/spec/render-spec.d.ts +50 -0
- package/dist/spec/render-spec.d.ts.map +1 -0
- package/dist/spec/render-spec.js +104 -0
- package/dist/spec/render-spec.js.map +1 -0
- package/dist/spec/resolve.d.ts +39 -0
- package/dist/spec/resolve.d.ts.map +1 -0
- package/dist/spec/resolve.js +51 -0
- package/dist/spec/resolve.js.map +1 -0
- package/dist/spec/resolved.d.ts +3 -0
- package/dist/spec/resolved.d.ts.map +1 -0
- package/dist/spec/resolved.js +36 -0
- package/dist/spec/resolved.js.map +1 -0
- package/dist/spec/state.d.ts +34 -0
- package/dist/spec/state.d.ts.map +1 -0
- package/dist/spec/state.js +111 -0
- package/dist/spec/state.js.map +1 -0
- package/dist/spec/stream.d.ts +50 -0
- package/dist/spec/stream.d.ts.map +1 -0
- package/dist/spec/stream.js +168 -0
- package/dist/spec/stream.js.map +1 -0
- package/dist/spec/tools.d.ts +3 -0
- package/dist/spec/tools.d.ts.map +1 -0
- package/dist/spec/tools.js +3 -0
- package/dist/spec/tree.d.ts +21 -0
- package/dist/spec/tree.d.ts.map +1 -0
- package/dist/spec/tree.js +27 -0
- package/dist/spec/tree.js.map +1 -0
- package/dist/spec/types.d.ts +66 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +9 -0
- package/dist/spec/types.js.map +1 -0
- package/dist/spec/validate.d.ts +32 -0
- package/dist/spec/validate.d.ts.map +1 -0
- package/dist/spec/validate.js +264 -0
- package/dist/spec/validate.js.map +1 -0
- package/dist/spec/vue/SpecRenderer.d.ts +43 -0
- package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
- package/dist/spec/vue/SpecRenderer.js +357 -0
- package/dist/spec/vue/SpecRenderer.js.map +1 -0
- package/dist/spec/vue/useSpecStream.d.ts +19 -0
- package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
- package/dist/spec/vue/useSpecStream.js +115 -0
- package/dist/spec/vue/useSpecStream.js.map +1 -0
- package/dist/spec/vue.d.ts +3 -0
- package/dist/spec/vue.d.ts.map +1 -0
- package/dist/spec/vue.js +3 -0
- package/dist/spec.d.ts +12 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +8 -0
- package/dist/theme.d.ts +38 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +370 -0
- package/dist/theme.js.map +1 -0
- package/dist/tools/define.d.ts +43 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +31 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/edits.d.ts +19 -0
- package/dist/tools/edits.d.ts.map +1 -0
- package/dist/tools/edits.js +55 -0
- package/dist/tools/edits.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +33 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/tools.d.ts +88 -0
- package/dist/tools/tools.d.ts.map +1 -0
- package/dist/tools/tools.js +235 -0
- package/dist/tools/tools.js.map +1 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vue/Widget.d.ts +16 -0
- package/dist/vue/Widget.d.ts.map +1 -0
- package/dist/vue/Widget.js +58 -0
- package/dist/vue/Widget.js.map +1 -0
- package/dist/vue/sync.d.ts +19 -0
- package/dist/vue/sync.d.ts.map +1 -0
- package/dist/vue/sync.js +31 -0
- package/dist/vue/sync.js.map +1 -0
- package/dist/vue/themeStore.d.ts +23 -0
- package/dist/vue/themeStore.d.ts.map +1 -0
- package/dist/vue/themeStore.js +115 -0
- package/dist/vue/themeStore.js.map +1 -0
- package/dist/vue/useThemeTokens.d.ts +6 -0
- package/dist/vue/useThemeTokens.d.ts.map +1 -0
- package/dist/vue/useThemeTokens.js +49 -0
- package/dist/vue/useThemeTokens.js.map +1 -0
- package/dist/vue/useWidget.d.ts +10 -0
- package/dist/vue/useWidget.d.ts.map +1 -0
- package/dist/vue/useWidget.js +81 -0
- package/dist/vue/useWidget.js.map +1 -0
- package/dist/vue.d.ts +5 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +4 -0
- package/dist/widget.d.ts +129 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +655 -0
- package/dist/widget.js.map +1 -0
- package/package.json +125 -3
- package/src/assistant-ui/entry.ts +124 -0
- package/src/assistant-ui/extension.ts +19 -0
- package/src/assistant-ui/history.ts +63 -0
- package/src/assistant-ui/modelContext.ts +162 -0
- package/src/assistant-ui/snapshotSaver.ts +116 -0
- package/src/assistant-ui/tool-calls.ts +115 -0
- package/src/assistant-ui/toolkit.tsx +747 -0
- package/src/assistant-ui/useSharedContext.ts +40 -0
- package/src/assistant-ui.ts +14 -0
- package/src/bootstrap.ts +112 -0
- package/src/csp.ts +96 -0
- package/src/index.ts +44 -0
- package/src/json-schema.ts +353 -0
- package/src/preview.ts +120 -0
- package/src/prompts/guidance.ts +369 -0
- package/src/prompts.ts +8 -0
- package/src/protocol.ts +141 -0
- package/src/repair/repair.ts +202 -0
- package/src/repair.ts +11 -0
- package/src/rpc.ts +228 -0
- package/src/runtime/diagnostics.ts +165 -0
- package/src/runtime/generated.ts +2 -0
- package/src/runtime/index.ts +343 -0
- package/src/runtime/morph.ts +352 -0
- package/src/runtime/screenshot.ts +288 -0
- package/src/runtime/size.ts +17 -0
- package/src/runtime/storage.ts +49 -0
- package/src/spec/actions.ts +111 -0
- package/src/spec/assistant-ui/thread.ts +381 -0
- package/src/spec/assistant-ui/toolkit.tsx +295 -0
- package/src/spec/assistant-ui/web.tsx +63 -0
- package/src/spec/assistant-ui.ts +2 -0
- package/src/spec/calc.ts +598 -0
- package/src/spec/catalog.ts +196 -0
- package/src/spec/expressions.ts +301 -0
- package/src/spec/patch.ts +112 -0
- package/src/spec/pending.ts +36 -0
- package/src/spec/pointer.ts +170 -0
- package/src/spec/prompt.ts +243 -0
- package/src/spec/render-spec.ts +144 -0
- package/src/spec/resolve.ts +123 -0
- package/src/spec/resolved.ts +53 -0
- package/src/spec/state.ts +193 -0
- package/src/spec/stream.ts +230 -0
- package/src/spec/tools.ts +17 -0
- package/src/spec/tree.ts +37 -0
- package/src/spec/types.ts +51 -0
- package/src/spec/validate.ts +429 -0
- package/src/spec/vue/SpecRenderer.tsx +544 -0
- package/src/spec/vue/__tests__/harness.ts +50 -0
- package/src/spec/vue/useSpecStream.ts +162 -0
- package/src/spec/vue.ts +13 -0
- package/src/spec.ts +55 -0
- package/src/theme.ts +423 -0
- package/src/tools/define.ts +65 -0
- package/src/tools/edits.ts +73 -0
- package/src/tools/registry.ts +44 -0
- package/src/tools/tools.ts +361 -0
- package/src/tools.ts +32 -0
- package/src/version.ts +4 -0
- package/src/vue/Widget.tsx +74 -0
- package/src/vue/sync.ts +36 -0
- package/src/vue/themeStore.ts +157 -0
- package/src/vue/useThemeTokens.ts +71 -0
- package/src/vue/useWidget.ts +117 -0
- package/src/vue.ts +8 -0
- package/src/widget.ts +1080 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAssistantInstructions,
|
|
3
|
+
useAui,
|
|
4
|
+
useAuiState,
|
|
5
|
+
useToolArgsStatus,
|
|
6
|
+
type Toolkit,
|
|
7
|
+
type ToolCallMessagePartProps,
|
|
8
|
+
} from "@ulearning/ai-sdk";
|
|
9
|
+
import {
|
|
10
|
+
computed,
|
|
11
|
+
defineComponent,
|
|
12
|
+
h,
|
|
13
|
+
onDeactivated,
|
|
14
|
+
onScopeDispose,
|
|
15
|
+
shallowRef,
|
|
16
|
+
toValue,
|
|
17
|
+
watch,
|
|
18
|
+
watchEffect,
|
|
19
|
+
type Component,
|
|
20
|
+
type MaybeRefOrGetter,
|
|
21
|
+
} from "vue";
|
|
22
|
+
import { useIsomorphicLayoutEffect } from "@ulearning/ai-store/internal";
|
|
23
|
+
import { previewWidget, type PreviewOptions } from "../preview";
|
|
24
|
+
import type { ConsoleEntry, WidgetError } from "../protocol";
|
|
25
|
+
import { buildRepairFeedback } from "../repair/repair";
|
|
26
|
+
import { useThemeTokens } from "../vue/useThemeTokens";
|
|
27
|
+
import { syncWidgetCode } from "../vue/sync";
|
|
28
|
+
import { useWidget, type UseWidgetOptions } from "../vue/useWidget";
|
|
29
|
+
import type { ThemeTokenSources } from "../theme";
|
|
30
|
+
import { createWidgetRegistry, type WidgetRegistry } from "../tools/registry";
|
|
31
|
+
import {
|
|
32
|
+
buildWidgetInstructions,
|
|
33
|
+
createWidgetTools,
|
|
34
|
+
type CreateWidgetToolsOptions,
|
|
35
|
+
type EditWidgetInput,
|
|
36
|
+
type PreviewWidgetInput,
|
|
37
|
+
type ShowWidgetInput,
|
|
38
|
+
type WidgetInstructionsOptions,
|
|
39
|
+
type WidgetTools,
|
|
40
|
+
} from "../tools/tools";
|
|
41
|
+
import type { AnyTool, ToolDefinition } from "../tools/define";
|
|
42
|
+
import {
|
|
43
|
+
hasLaterToolCallInMessage,
|
|
44
|
+
type MessageLike,
|
|
45
|
+
type ToolCall,
|
|
46
|
+
} from "./tool-calls";
|
|
47
|
+
import { toolkitEntry } from "./entry";
|
|
48
|
+
import type { ToolkitDisplay, WidgetToolkitExtension } from "./extension";
|
|
49
|
+
import { resolveWidgetCode, resolveWidgetOrigin } from "./history";
|
|
50
|
+
import {
|
|
51
|
+
frameContext,
|
|
52
|
+
savedContext,
|
|
53
|
+
type ModelContextSource,
|
|
54
|
+
type SharedContext,
|
|
55
|
+
} from "./modelContext";
|
|
56
|
+
import { createSnapshotSaver } from "./snapshotSaver";
|
|
57
|
+
import { useSharedContext } from "./useSharedContext";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Where theme tokens come from in an assistant-ui app: shadcn/ui variables
|
|
61
|
+
* first, because Tailwind v4 aliases `--color-accent` to shadcn's muted
|
|
62
|
+
* `--accent` rather than the primary color.
|
|
63
|
+
*/
|
|
64
|
+
export const ASSISTANT_UI_TOKEN_SOURCES: ThemeTokenSources = {
|
|
65
|
+
"--color-background": ["--background"],
|
|
66
|
+
"--color-surface": ["--card", "--background"],
|
|
67
|
+
"--color-surface-muted": ["--muted", "--secondary"],
|
|
68
|
+
"--color-text": ["--foreground"],
|
|
69
|
+
"--color-text-muted": ["--muted-foreground"],
|
|
70
|
+
"--color-text-subtle": ["--muted-foreground"],
|
|
71
|
+
"--color-border": ["--border"],
|
|
72
|
+
"--color-border-strong": ["--input", "--border"],
|
|
73
|
+
"--color-accent": ["--primary"],
|
|
74
|
+
"--color-accent-text": ["--primary-foreground"],
|
|
75
|
+
"--color-danger": ["--destructive"],
|
|
76
|
+
"--color-success": ["--aui-success", "--color-success"],
|
|
77
|
+
"--color-warning": ["--aui-warning", "--color-warning"],
|
|
78
|
+
"--color-info": ["--color-info"],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** The app's assistant-ui (shadcn/ui) theme as widget tokens, kept in sync. */
|
|
82
|
+
export function useAssistantUiThemeTokens(element?: Element | null) {
|
|
83
|
+
return useThemeTokens(element, ASSISTANT_UI_TOKEN_SOURCES);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type WidgetToolkitOptions = Omit<
|
|
87
|
+
CreateWidgetToolsOptions,
|
|
88
|
+
"registry"
|
|
89
|
+
> & {
|
|
90
|
+
registry?: WidgetRegistry;
|
|
91
|
+
/**
|
|
92
|
+
* `frontend` (default): the tools execute in the browser and the server
|
|
93
|
+
* forwards their schemas (`frontendTools` from `@ulearning/ai-sdk-adapter`).
|
|
94
|
+
* `backend`: the server executes `createWidgetTools()` and the toolkit
|
|
95
|
+
* only renders.
|
|
96
|
+
*/
|
|
97
|
+
execution?: "frontend" | "backend";
|
|
98
|
+
/** Options for every widget frame (csp, product, maxHeight, handlers, …). */
|
|
99
|
+
widget?: Omit<UseWidgetOptions, "tokens" | "initialState">;
|
|
100
|
+
/** Spec mode: `createSpecToolkit(catalog, { components })` from `generative-frame/spec/assistant-ui`. */
|
|
101
|
+
spec?: WidgetToolkitExtension;
|
|
102
|
+
/** More tools for the model, such as data lookups. They execute like the widget tools and render nothing in the thread. */
|
|
103
|
+
extraTools?: Record<string, AnyTool>;
|
|
104
|
+
/** Where the theme is read from. Defaults to the document root. */
|
|
105
|
+
themeElement?: Element | null;
|
|
106
|
+
/** How the widget tools are presented relative to the reasoning trace. Defaults to `standalone`. */
|
|
107
|
+
display?: ToolkitDisplay;
|
|
108
|
+
/**
|
|
109
|
+
* With frontend execution, `show_widget` and `edit_widget` wait for the
|
|
110
|
+
* frame to finish rendering and return its errors as `render`. `false`
|
|
111
|
+
* returns as soon as the code is stored.
|
|
112
|
+
*/
|
|
113
|
+
renderReport?: RenderReportOptions | false;
|
|
114
|
+
/** Keep the PNG in `preview_widget` results. Defaults to false. */
|
|
115
|
+
previewScreenshot?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* The storage id of a widget's frame, from the thread's persisted id and
|
|
118
|
+
* the `toolCallId` of the `show_widget` call that created it, so a widget
|
|
119
|
+
* keeps its localStorage across reloads and edits. Defaults to
|
|
120
|
+
* `<remoteId>:<toolCallId>`, or `aui:<toolCallId>` while the thread has no
|
|
121
|
+
* remote id; `false` gives every frame a fresh origin. Derive it from host
|
|
122
|
+
* data only, never from tool arguments the model wrote.
|
|
123
|
+
*/
|
|
124
|
+
widgetId?:
|
|
125
|
+
| false
|
|
126
|
+
| ((call: { toolCallId: string; threadId: string | undefined }) => string);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type RenderReportOptions = {
|
|
130
|
+
/** How long a tool result waits for the frame. Defaults to 10000 ms. */
|
|
131
|
+
timeoutMs?: number;
|
|
132
|
+
/** Wait after the code ends so late script errors are caught. Defaults to 300 ms. */
|
|
133
|
+
settleMs?: number;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** How a widget rendered in the thread, attached to tool results as `render`. */
|
|
137
|
+
export type WidgetRenderReport =
|
|
138
|
+
| {
|
|
139
|
+
status: "rendered";
|
|
140
|
+
ok: boolean;
|
|
141
|
+
blank: boolean;
|
|
142
|
+
height: number;
|
|
143
|
+
errors: WidgetError[];
|
|
144
|
+
/** Console warnings and errors, most recent last. */
|
|
145
|
+
console: ConsoleEntry[];
|
|
146
|
+
feedback: string;
|
|
147
|
+
}
|
|
148
|
+
| { status: "timeout"; feedback: string };
|
|
149
|
+
|
|
150
|
+
export type WidgetToolkit = {
|
|
151
|
+
/** Pass to `Tools({ toolkit })`, or spread into your own toolkit. */
|
|
152
|
+
toolkit: Toolkit;
|
|
153
|
+
/** The tool definitions behind the toolkit, for instructions or the server. */
|
|
154
|
+
tools: WidgetTools & Record<string, AnyTool>;
|
|
155
|
+
/** Latest code per widget title. */
|
|
156
|
+
registry: WidgetRegistry;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type ShowArgs = Partial<ShowWidgetInput>;
|
|
160
|
+
type EditArgs = Partial<EditWidgetInput>;
|
|
161
|
+
|
|
162
|
+
const REPORT_BACKLOG = 50;
|
|
163
|
+
/** A later show takes over once it has code to show; a later edit only once its arguments are settled and its edits apply, so the widget stays while a show has no code or an edit streams or fails. */
|
|
164
|
+
const takesOverWidget = (messages: readonly MessageLike[], call: ToolCall) =>
|
|
165
|
+
(call.name === "show_widget" &&
|
|
166
|
+
typeof call.args["widget_code"] === "string" &&
|
|
167
|
+
call.args["widget_code"].trim() !== "") ||
|
|
168
|
+
(call.name === "edit_widget" &&
|
|
169
|
+
call.settled &&
|
|
170
|
+
resolveWidgetCode(messages, call.id) !== undefined);
|
|
171
|
+
|
|
172
|
+
/** Hands render reports from the thread's widgets to the tool calls waiting for them. */
|
|
173
|
+
function createRenderReports() {
|
|
174
|
+
const early = new Map<string, WidgetRenderReport>();
|
|
175
|
+
const waiters = new Map<string, (report: WidgetRenderReport) => void>();
|
|
176
|
+
return {
|
|
177
|
+
report(toolCallId: string, report: WidgetRenderReport) {
|
|
178
|
+
const waiter = waiters.get(toolCallId);
|
|
179
|
+
if (waiter) {
|
|
180
|
+
waiters.delete(toolCallId);
|
|
181
|
+
waiter(report);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
early.set(toolCallId, report);
|
|
185
|
+
// Calls rendered from history never execute, so their reports are dropped.
|
|
186
|
+
if (early.size > REPORT_BACKLOG) {
|
|
187
|
+
early.delete(early.keys().next().value as string);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
wait(
|
|
191
|
+
toolCallId: string,
|
|
192
|
+
timeoutMs: number,
|
|
193
|
+
signal?: AbortSignal,
|
|
194
|
+
): Promise<WidgetRenderReport> {
|
|
195
|
+
const ready = early.get(toolCallId);
|
|
196
|
+
if (ready) {
|
|
197
|
+
early.delete(toolCallId);
|
|
198
|
+
return Promise.resolve(ready);
|
|
199
|
+
}
|
|
200
|
+
return new Promise((resolve) => {
|
|
201
|
+
const finish = (report: WidgetRenderReport) => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
signal?.removeEventListener("abort", onAbort);
|
|
204
|
+
waiters.delete(toolCallId);
|
|
205
|
+
resolve(report);
|
|
206
|
+
};
|
|
207
|
+
const timedOut = () =>
|
|
208
|
+
finish({
|
|
209
|
+
status: "timeout",
|
|
210
|
+
feedback: `The widget had not finished rendering after ${timeoutMs} ms, so no render errors are known.`,
|
|
211
|
+
});
|
|
212
|
+
const onAbort = timedOut;
|
|
213
|
+
const timer = setTimeout(timedOut, timeoutMs);
|
|
214
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
215
|
+
waiters.set(toolCallId, finish);
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const visuallyHidden = {
|
|
222
|
+
position: "absolute",
|
|
223
|
+
width: 1,
|
|
224
|
+
height: 1,
|
|
225
|
+
overflow: "hidden",
|
|
226
|
+
clip: "rect(0 0 0 0)",
|
|
227
|
+
whiteSpace: "nowrap",
|
|
228
|
+
} as const;
|
|
229
|
+
|
|
230
|
+
const previewFrameOptions = (
|
|
231
|
+
widget: WidgetToolkitOptions["widget"],
|
|
232
|
+
): PreviewOptions => ({
|
|
233
|
+
...(widget?.product !== undefined ? { product: widget.product } : {}),
|
|
234
|
+
...(widget?.frame !== undefined ? { frame: widget.frame } : {}),
|
|
235
|
+
...(widget?.unsafeShimDomain !== undefined
|
|
236
|
+
? { unsafeShimDomain: widget.unsafeShimDomain }
|
|
237
|
+
: {}),
|
|
238
|
+
...(widget?.opaqueOrigin !== undefined
|
|
239
|
+
? { opaqueOrigin: widget.opaqueOrigin }
|
|
240
|
+
: {}),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
function savedWidgetState(
|
|
244
|
+
log: ToolCallMessagePartProps<ShowArgs>["unstable_interactions"],
|
|
245
|
+
toolCallId: string,
|
|
246
|
+
): unknown {
|
|
247
|
+
const entries = log?.entries ?? [];
|
|
248
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
249
|
+
const { type, payload } = entries[index]!;
|
|
250
|
+
if (
|
|
251
|
+
type === "action" &&
|
|
252
|
+
payload["type"] === "widgetState" &&
|
|
253
|
+
payload["toolCallId"] === toolCallId
|
|
254
|
+
) {
|
|
255
|
+
return payload["state"] ?? null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function useWidgetModelContext(source: ModelContextSource) {
|
|
262
|
+
const aui = useAui();
|
|
263
|
+
useIsomorphicLayoutEffect(
|
|
264
|
+
() =>
|
|
265
|
+
aui.modelContext.register({
|
|
266
|
+
getModelContext: () => {
|
|
267
|
+
const system = source.read();
|
|
268
|
+
return system ? { system } : {};
|
|
269
|
+
},
|
|
270
|
+
subscribe: source.subscribe,
|
|
271
|
+
}),
|
|
272
|
+
[],
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Renders `show_widget`, `edit_widget`, and `render_spec` tool calls in an
|
|
278
|
+
* assistant-ui thread: widget code streams into a frame from the partial
|
|
279
|
+
* tool arguments, `sendPrompt` adds its text to the thread composer, and the
|
|
280
|
+
* frame follows the app's theme. With `execution: "frontend"` the tools run
|
|
281
|
+
* in the browser too, so the server only forwards their schemas.
|
|
282
|
+
*/
|
|
283
|
+
export function createWidgetToolkit(
|
|
284
|
+
options: WidgetToolkitOptions = {},
|
|
285
|
+
): WidgetToolkit {
|
|
286
|
+
const registry = options.registry ?? createWidgetRegistry();
|
|
287
|
+
const tools: WidgetTools & Record<string, AnyTool> = createWidgetTools({
|
|
288
|
+
...options,
|
|
289
|
+
guidance: {
|
|
290
|
+
...options.guidance,
|
|
291
|
+
hostApi: {
|
|
292
|
+
modelContext: true,
|
|
293
|
+
clipboard: options.widget?.onCopyText !== undefined,
|
|
294
|
+
...options.guidance?.hostApi,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
preview:
|
|
298
|
+
options.preview ??
|
|
299
|
+
((code, previewOptions) =>
|
|
300
|
+
previewWidget(code, {
|
|
301
|
+
...previewFrameOptions(options.widget),
|
|
302
|
+
...previewOptions,
|
|
303
|
+
})),
|
|
304
|
+
registry,
|
|
305
|
+
modules: [...(options.modules ?? []), ...(options.spec?.modules ?? [])],
|
|
306
|
+
extraTools: { ...options.extraTools, ...options.spec?.tools },
|
|
307
|
+
});
|
|
308
|
+
const display = options.display ?? "standalone";
|
|
309
|
+
const execution = options.execution ?? "frontend";
|
|
310
|
+
const reports = createRenderReports();
|
|
311
|
+
const reportOptions =
|
|
312
|
+
options.renderReport === false ? undefined : (options.renderReport ?? {});
|
|
313
|
+
const settleMs = reportOptions?.settleMs ?? 300;
|
|
314
|
+
const timeoutMs = reportOptions?.timeoutMs ?? 10_000;
|
|
315
|
+
|
|
316
|
+
function useWidgetProps() {
|
|
317
|
+
const aui = useAui();
|
|
318
|
+
const tokens = useAssistantUiThemeTokens(options.themeElement);
|
|
319
|
+
return computed(() => ({
|
|
320
|
+
...options.widget,
|
|
321
|
+
tokens: tokens.value,
|
|
322
|
+
onPrompt:
|
|
323
|
+
options.widget?.onPrompt ??
|
|
324
|
+
((text: string) => {
|
|
325
|
+
const composer = aui.thread.composer();
|
|
326
|
+
composer.setText(
|
|
327
|
+
[composer.getState().text, text]
|
|
328
|
+
.filter((part) => part.trim())
|
|
329
|
+
.join(" "),
|
|
330
|
+
);
|
|
331
|
+
}),
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function useStorageId(origin: MaybeRefOrGetter<string | undefined>) {
|
|
336
|
+
const threadId = useAuiState((state) => state.threadListItem?.remoteId);
|
|
337
|
+
return computed(() => {
|
|
338
|
+
const toolCallId = toValue(origin);
|
|
339
|
+
if (options.widgetId === false || !toolCallId) return undefined;
|
|
340
|
+
if (options.widgetId)
|
|
341
|
+
return options.widgetId({ toolCallId, threadId: threadId.value });
|
|
342
|
+
return `${threadId.value ?? "aui"}:${toolCallId}`;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
type ToolWidgetProps = {
|
|
347
|
+
code: string;
|
|
348
|
+
streaming: boolean;
|
|
349
|
+
title: string;
|
|
350
|
+
toolCallId: string;
|
|
351
|
+
storageId: string | undefined;
|
|
352
|
+
unstable_interactions: ToolCallMessagePartProps<ShowArgs>["unstable_interactions"];
|
|
353
|
+
unstable_recordInteraction: ToolCallMessagePartProps<ShowArgs>["unstable_recordInteraction"];
|
|
354
|
+
};
|
|
355
|
+
const ToolWidget = defineComponent(
|
|
356
|
+
(props: ToolWidgetProps) => {
|
|
357
|
+
const widgetProps = useWidgetProps();
|
|
358
|
+
const initialState = savedWidgetState(
|
|
359
|
+
props.unstable_interactions,
|
|
360
|
+
props.toolCallId,
|
|
361
|
+
);
|
|
362
|
+
const initialContext = savedContext(
|
|
363
|
+
props.unstable_interactions,
|
|
364
|
+
props.toolCallId,
|
|
365
|
+
"widgetState",
|
|
366
|
+
);
|
|
367
|
+
const latest = { state: initialState, context: initialContext };
|
|
368
|
+
const snapshot = () =>
|
|
369
|
+
latest.context
|
|
370
|
+
? { state: latest.state, context: latest.context }
|
|
371
|
+
: latest.state === null
|
|
372
|
+
? null
|
|
373
|
+
: { state: latest.state };
|
|
374
|
+
let disposed = false;
|
|
375
|
+
const saver = createSnapshotSaver<{
|
|
376
|
+
state: unknown;
|
|
377
|
+
context?: SharedContext;
|
|
378
|
+
} | null>(snapshot(), (saved) =>
|
|
379
|
+
props.unstable_recordInteraction?.({
|
|
380
|
+
type: "action",
|
|
381
|
+
payload: {
|
|
382
|
+
type: "widgetState",
|
|
383
|
+
toolCallId: props.toolCallId,
|
|
384
|
+
state: saved ? saved.state : null,
|
|
385
|
+
...(saved?.context ? { context: saved.context } : {}),
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
onDeactivated(() => saver.flush());
|
|
390
|
+
onScopeDispose(() => {
|
|
391
|
+
disposed = true;
|
|
392
|
+
saver.flush();
|
|
393
|
+
});
|
|
394
|
+
const share = useSharedContext(() => ({
|
|
395
|
+
title: props.title,
|
|
396
|
+
toolCallId: props.toolCallId,
|
|
397
|
+
initial: widgetProps.value.onUpdateModelContext
|
|
398
|
+
? undefined
|
|
399
|
+
: initialContext,
|
|
400
|
+
useModelContext: useWidgetModelContext,
|
|
401
|
+
onShare(context) {
|
|
402
|
+
latest.context = context;
|
|
403
|
+
saver.schedule(snapshot);
|
|
404
|
+
},
|
|
405
|
+
}));
|
|
406
|
+
const { ref, widget } = useWidget(() => {
|
|
407
|
+
const current = widgetProps.value;
|
|
408
|
+
return {
|
|
409
|
+
...current,
|
|
410
|
+
initialState,
|
|
411
|
+
...(props.storageId !== undefined &&
|
|
412
|
+
!current.frame &&
|
|
413
|
+
!current.opaqueOrigin
|
|
414
|
+
? { id: props.storageId }
|
|
415
|
+
: {}),
|
|
416
|
+
onWidgetState(state: unknown) {
|
|
417
|
+
if (disposed) return;
|
|
418
|
+
latest.state = state;
|
|
419
|
+
saver.schedule(snapshot);
|
|
420
|
+
current.onWidgetState?.(state);
|
|
421
|
+
},
|
|
422
|
+
onUpdateModelContext:
|
|
423
|
+
current.onUpdateModelContext ??
|
|
424
|
+
((params: unknown) => {
|
|
425
|
+
share(frameContext(params));
|
|
426
|
+
return {};
|
|
427
|
+
}),
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
watch(
|
|
431
|
+
[widget, () => props.toolCallId],
|
|
432
|
+
([handle, toolCallId], _previous, cleanup) => {
|
|
433
|
+
if (!handle || !reportOptions) return;
|
|
434
|
+
let valid = true;
|
|
435
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
436
|
+
const off = handle.on("end", () => {
|
|
437
|
+
off();
|
|
438
|
+
timer = setTimeout(() => {
|
|
439
|
+
void handle
|
|
440
|
+
.inspect()
|
|
441
|
+
.then((inspection) => {
|
|
442
|
+
if (!valid || disposed || widget.value !== handle) return;
|
|
443
|
+
const feedback = buildRepairFeedback({
|
|
444
|
+
errors: inspection.errors,
|
|
445
|
+
console: inspection.console,
|
|
446
|
+
blank: inspection.blank,
|
|
447
|
+
height: inspection.size.height,
|
|
448
|
+
});
|
|
449
|
+
reports.report(toolCallId, {
|
|
450
|
+
status: "rendered",
|
|
451
|
+
ok: feedback.ok,
|
|
452
|
+
blank: feedback.blank,
|
|
453
|
+
height: inspection.size.height,
|
|
454
|
+
errors: feedback.errors,
|
|
455
|
+
console: feedback.console,
|
|
456
|
+
feedback: feedback.text,
|
|
457
|
+
});
|
|
458
|
+
})
|
|
459
|
+
.catch(() => {});
|
|
460
|
+
}, settleMs);
|
|
461
|
+
});
|
|
462
|
+
cleanup(() => {
|
|
463
|
+
valid = false;
|
|
464
|
+
off();
|
|
465
|
+
clearTimeout(timer);
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
{ immediate: true, flush: "sync" },
|
|
469
|
+
);
|
|
470
|
+
watchEffect(
|
|
471
|
+
() => {
|
|
472
|
+
if (widget.value)
|
|
473
|
+
syncWidgetCode(widget.value, props.code, props.streaming);
|
|
474
|
+
},
|
|
475
|
+
{ flush: "post" },
|
|
476
|
+
);
|
|
477
|
+
return () => h("div", { ref });
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "ToolWidget",
|
|
481
|
+
props: [
|
|
482
|
+
"code",
|
|
483
|
+
"streaming",
|
|
484
|
+
"title",
|
|
485
|
+
"toolCallId",
|
|
486
|
+
"storageId",
|
|
487
|
+
"unstable_interactions",
|
|
488
|
+
"unstable_recordInteraction",
|
|
489
|
+
],
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
function useWidgetCallSuperseded(
|
|
494
|
+
messages: MaybeRefOrGetter<readonly MessageLike[]>,
|
|
495
|
+
toolCallId: () => string,
|
|
496
|
+
title: () => string,
|
|
497
|
+
) {
|
|
498
|
+
const superseded = computed(() => {
|
|
499
|
+
const current = toValue(messages);
|
|
500
|
+
return hasLaterToolCallInMessage(current, toolCallId(), title(), (call) =>
|
|
501
|
+
takesOverWidget(current, call),
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
watch(
|
|
505
|
+
[superseded, toolCallId],
|
|
506
|
+
([replaced, id]) => {
|
|
507
|
+
if (replaced && reportOptions)
|
|
508
|
+
reports.report(id, {
|
|
509
|
+
status: "timeout",
|
|
510
|
+
feedback:
|
|
511
|
+
"This widget was superseded by a later call with the same title in this message and was not rendered.",
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
{ immediate: true, flush: "sync" },
|
|
515
|
+
);
|
|
516
|
+
return superseded;
|
|
517
|
+
}
|
|
518
|
+
const partProps: (
|
|
519
|
+
| "args"
|
|
520
|
+
| "toolCallId"
|
|
521
|
+
| "unstable_interactions"
|
|
522
|
+
| "unstable_recordInteraction"
|
|
523
|
+
)[] = [
|
|
524
|
+
"args",
|
|
525
|
+
"toolCallId",
|
|
526
|
+
"unstable_interactions",
|
|
527
|
+
"unstable_recordInteraction",
|
|
528
|
+
];
|
|
529
|
+
const ShowWidgetUI = defineComponent(
|
|
530
|
+
(props: ToolCallMessagePartProps<ShowArgs>) => {
|
|
531
|
+
const status = useToolArgsStatus<ShowWidgetInput>();
|
|
532
|
+
const messages = useAuiState((state) => state.thread.messages);
|
|
533
|
+
const streaming = computed(
|
|
534
|
+
() => status.value.propStatus.widget_code !== "complete",
|
|
535
|
+
);
|
|
536
|
+
const code = computed(() =>
|
|
537
|
+
typeof props.args.widget_code === "string"
|
|
538
|
+
? props.args.widget_code
|
|
539
|
+
: "",
|
|
540
|
+
);
|
|
541
|
+
const title = computed(() =>
|
|
542
|
+
typeof props.args.title === "string" ? props.args.title : "",
|
|
543
|
+
);
|
|
544
|
+
const storageId = useStorageId(() => props.toolCallId);
|
|
545
|
+
const superseded = useWidgetCallSuperseded(
|
|
546
|
+
messages,
|
|
547
|
+
() => props.toolCallId,
|
|
548
|
+
() => title.value,
|
|
549
|
+
);
|
|
550
|
+
watch(
|
|
551
|
+
[streaming, title, code],
|
|
552
|
+
([running, name, source]) => {
|
|
553
|
+
if (!running && name && source && registry.get(name)?.code !== source)
|
|
554
|
+
registry.set(name, source);
|
|
555
|
+
},
|
|
556
|
+
{ immediate: true, flush: "sync" },
|
|
557
|
+
);
|
|
558
|
+
return () => {
|
|
559
|
+
if (superseded.value) return null;
|
|
560
|
+
const loading = props.args.loading_messages?.[0];
|
|
561
|
+
return h("div", { "data-gf-widget": title.value || undefined }, [
|
|
562
|
+
...(streaming.value && !code.value && loading
|
|
563
|
+
? [
|
|
564
|
+
h(
|
|
565
|
+
"p",
|
|
566
|
+
{ role: "status", style: { margin: "4px 0", opacity: 0.7 } },
|
|
567
|
+
loading,
|
|
568
|
+
),
|
|
569
|
+
]
|
|
570
|
+
: []),
|
|
571
|
+
h(ToolWidget, {
|
|
572
|
+
code: code.value,
|
|
573
|
+
streaming: streaming.value,
|
|
574
|
+
title: title.value,
|
|
575
|
+
toolCallId: props.toolCallId,
|
|
576
|
+
storageId: storageId.value,
|
|
577
|
+
unstable_interactions: props.unstable_interactions,
|
|
578
|
+
unstable_recordInteraction: props.unstable_recordInteraction,
|
|
579
|
+
}),
|
|
580
|
+
]);
|
|
581
|
+
};
|
|
582
|
+
},
|
|
583
|
+
{ name: "ShowWidgetUI", inheritAttrs: false, props: partProps },
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
const EditWidgetUI = defineComponent(
|
|
587
|
+
(props: ToolCallMessagePartProps<EditArgs>) => {
|
|
588
|
+
const status = useToolArgsStatus<EditWidgetInput>();
|
|
589
|
+
const messages = useAuiState((state) => state.thread.messages);
|
|
590
|
+
const title = computed(() =>
|
|
591
|
+
typeof props.args.title === "string" ? props.args.title : "",
|
|
592
|
+
);
|
|
593
|
+
const complete = computed(() => status.value.argsStatus === "complete");
|
|
594
|
+
const superseded = useWidgetCallSuperseded(
|
|
595
|
+
messages,
|
|
596
|
+
() => props.toolCallId,
|
|
597
|
+
() => title.value,
|
|
598
|
+
);
|
|
599
|
+
const code = computed(() =>
|
|
600
|
+
complete.value
|
|
601
|
+
? resolveWidgetCode(messages.value, props.toolCallId)
|
|
602
|
+
: undefined,
|
|
603
|
+
);
|
|
604
|
+
const origin = computed(() =>
|
|
605
|
+
complete.value
|
|
606
|
+
? resolveWidgetOrigin(messages.value, props.toolCallId)
|
|
607
|
+
: undefined,
|
|
608
|
+
);
|
|
609
|
+
const storageId = useStorageId(origin);
|
|
610
|
+
watch(
|
|
611
|
+
[title, code],
|
|
612
|
+
([name, source]) => {
|
|
613
|
+
if (
|
|
614
|
+
name &&
|
|
615
|
+
source !== undefined &&
|
|
616
|
+
registry.get(name)?.code !== source
|
|
617
|
+
)
|
|
618
|
+
registry.set(name, source);
|
|
619
|
+
},
|
|
620
|
+
{ immediate: true, flush: "sync" },
|
|
621
|
+
);
|
|
622
|
+
return () => {
|
|
623
|
+
if (superseded.value) return null;
|
|
624
|
+
if (!complete.value)
|
|
625
|
+
return h(
|
|
626
|
+
"p",
|
|
627
|
+
{ role: "status", style: { margin: "4px 0", opacity: 0.7 } },
|
|
628
|
+
`Updating ${title.value || "widget"}…`,
|
|
629
|
+
);
|
|
630
|
+
if (code.value === undefined)
|
|
631
|
+
return h(
|
|
632
|
+
"p",
|
|
633
|
+
{ role: "note", style: { margin: "4px 0", opacity: 0.7 } },
|
|
634
|
+
`${title.value || "The widget"} could not be updated.`,
|
|
635
|
+
);
|
|
636
|
+
return h("div", { "data-gf-widget": title.value || undefined }, [
|
|
637
|
+
h("span", { style: visuallyHidden }, `Updated ${title.value}`),
|
|
638
|
+
h(ToolWidget, {
|
|
639
|
+
code: code.value,
|
|
640
|
+
streaming: false,
|
|
641
|
+
title: title.value,
|
|
642
|
+
toolCallId: props.toolCallId,
|
|
643
|
+
storageId: storageId.value,
|
|
644
|
+
unstable_interactions: props.unstable_interactions,
|
|
645
|
+
unstable_recordInteraction: props.unstable_recordInteraction,
|
|
646
|
+
}),
|
|
647
|
+
]);
|
|
648
|
+
};
|
|
649
|
+
},
|
|
650
|
+
{ name: "EditWidgetUI", inheritAttrs: false, props: partProps },
|
|
651
|
+
);
|
|
652
|
+
const Silent = () => null;
|
|
653
|
+
|
|
654
|
+
type ExecuteContext = { toolCallId: string; abortSignal?: AbortSignal };
|
|
655
|
+
|
|
656
|
+
const entry = <I, O>(
|
|
657
|
+
tool: ToolDefinition<I, O>,
|
|
658
|
+
render: Component,
|
|
659
|
+
entryDisplay: ToolkitDisplay,
|
|
660
|
+
execute?: (input: I, context: ExecuteContext) => Promise<unknown>,
|
|
661
|
+
) =>
|
|
662
|
+
toolkitEntry(tool, render, { execution, display: entryDisplay }, execute);
|
|
663
|
+
|
|
664
|
+
const withRenderReport =
|
|
665
|
+
<I,>(tool: ToolDefinition<I, { ok: boolean }>) =>
|
|
666
|
+
async (input: I, context: ExecuteContext) => {
|
|
667
|
+
const result = await tool.execute(input);
|
|
668
|
+
if (!result.ok || !reportOptions) return result;
|
|
669
|
+
const render = await reports.wait(
|
|
670
|
+
context.toolCallId,
|
|
671
|
+
timeoutMs,
|
|
672
|
+
context.abortSignal,
|
|
673
|
+
);
|
|
674
|
+
return { ...result, render };
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const preview = async (input: PreviewWidgetInput) => {
|
|
678
|
+
const { screenshot, ...result } = await tools.preview_widget.execute(input);
|
|
679
|
+
const feedback = buildRepairFeedback(result).text;
|
|
680
|
+
return options.previewScreenshot && screenshot !== undefined
|
|
681
|
+
? { ...result, screenshot, feedback }
|
|
682
|
+
: { ...result, feedback };
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
const toolkit: Toolkit = {
|
|
686
|
+
read_me: entry(tools.read_me, Silent, "inline"),
|
|
687
|
+
show_widget: entry(
|
|
688
|
+
tools.show_widget,
|
|
689
|
+
ShowWidgetUI,
|
|
690
|
+
display,
|
|
691
|
+
withRenderReport(tools.show_widget),
|
|
692
|
+
),
|
|
693
|
+
edit_widget: entry(
|
|
694
|
+
tools.edit_widget,
|
|
695
|
+
EditWidgetUI,
|
|
696
|
+
display,
|
|
697
|
+
withRenderReport(tools.edit_widget),
|
|
698
|
+
),
|
|
699
|
+
preview_widget: entry(tools.preview_widget, Silent, "inline", preview),
|
|
700
|
+
...Object.fromEntries(
|
|
701
|
+
Object.entries(options.extraTools ?? {}).map(([name, tool]) => [
|
|
702
|
+
name,
|
|
703
|
+
entry(tool, Silent, "inline"),
|
|
704
|
+
]),
|
|
705
|
+
),
|
|
706
|
+
...options.spec?.entries({ execution, display }),
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
return { toolkit, tools, registry };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** Adds the widget guidance to the model while this Vue scope is active. */
|
|
713
|
+
export function useWidgetInstructions(
|
|
714
|
+
tools: MaybeRefOrGetter<WidgetTools & Record<string, AnyTool>>,
|
|
715
|
+
options: MaybeRefOrGetter<WidgetInstructionsOptions> = {},
|
|
716
|
+
): void {
|
|
717
|
+
const instruction = shallowRef("");
|
|
718
|
+
useIsomorphicLayoutEffect(
|
|
719
|
+
() =>
|
|
720
|
+
watch(
|
|
721
|
+
[
|
|
722
|
+
() => toValue(tools),
|
|
723
|
+
() => JSON.stringify(toValue(options).preload ?? null),
|
|
724
|
+
],
|
|
725
|
+
([currentTools, preloadKey], _previous, cleanup) => {
|
|
726
|
+
let cancelled = false;
|
|
727
|
+
cleanup(() => {
|
|
728
|
+
cancelled = true;
|
|
729
|
+
});
|
|
730
|
+
const preload: WidgetInstructionsOptions["preload"] | null =
|
|
731
|
+
JSON.parse(preloadKey);
|
|
732
|
+
void buildWidgetInstructions(
|
|
733
|
+
currentTools,
|
|
734
|
+
preload ? { preload } : {},
|
|
735
|
+
).then((text) => {
|
|
736
|
+
if (!cancelled) instruction.value = text;
|
|
737
|
+
});
|
|
738
|
+
},
|
|
739
|
+
{ immediate: true },
|
|
740
|
+
),
|
|
741
|
+
[],
|
|
742
|
+
);
|
|
743
|
+
useAssistantInstructions(() => ({
|
|
744
|
+
instruction: instruction.value,
|
|
745
|
+
disabled: !instruction.value,
|
|
746
|
+
}));
|
|
747
|
+
}
|