@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
|
@@ -47,6 +47,8 @@ function unstable_useLiveCompletionAdapter(options) {
|
|
|
47
47
|
const pendingQueryRef = useRef(null);
|
|
48
48
|
const retryableQueryRef = useRef(null);
|
|
49
49
|
const pendingRetryQueryRef = useRef(null);
|
|
50
|
+
const inactiveRef = useRef(true);
|
|
51
|
+
const deferredQueryRef = useRef(null);
|
|
50
52
|
const cancelTimer = useCallback(() => {
|
|
51
53
|
if (timerRef.current !== null) {
|
|
52
54
|
clearTimeout(timerRef.current);
|
|
@@ -61,6 +63,10 @@ function unstable_useLiveCompletionAdapter(options) {
|
|
|
61
63
|
}, []);
|
|
62
64
|
const scheduleFetch = useCallback((query) => {
|
|
63
65
|
if (!enabled) return;
|
|
66
|
+
if (inactiveRef.current) {
|
|
67
|
+
deferredQueryRef.current = query;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
64
70
|
if (pendingQueryRef.current === query) return;
|
|
65
71
|
rearmPendingRetry();
|
|
66
72
|
if (retryableQueryRef.current === query) {
|
|
@@ -100,6 +106,10 @@ function unstable_useLiveCompletionAdapter(options) {
|
|
|
100
106
|
cancelTimer,
|
|
101
107
|
rearmPendingRetry
|
|
102
108
|
]);
|
|
109
|
+
const scheduleFetchRef = useRef(scheduleFetch);
|
|
110
|
+
useLayoutEffect(() => {
|
|
111
|
+
scheduleFetchRef.current = scheduleFetch;
|
|
112
|
+
}, [scheduleFetch]);
|
|
103
113
|
const invalidatePending = useCallback(() => {
|
|
104
114
|
rearmPendingRetry();
|
|
105
115
|
cancelTimer();
|
|
@@ -129,7 +139,16 @@ function unstable_useLiveCompletionAdapter(options) {
|
|
|
129
139
|
failed: false
|
|
130
140
|
});
|
|
131
141
|
}, [enabled, invalidatePending]);
|
|
132
|
-
|
|
142
|
+
useLayoutEffect(() => {
|
|
143
|
+
inactiveRef.current = false;
|
|
144
|
+
const deferredQuery = deferredQueryRef.current;
|
|
145
|
+
deferredQueryRef.current = null;
|
|
146
|
+
if (deferredQuery !== null) scheduleFetchRef.current(deferredQuery);
|
|
147
|
+
return () => {
|
|
148
|
+
inactiveRef.current = true;
|
|
149
|
+
invalidatePending();
|
|
150
|
+
};
|
|
151
|
+
}, [invalidatePending]);
|
|
133
152
|
useEffect(() => {
|
|
134
153
|
retryableQueryRef.current = state.failed ? state.query : null;
|
|
135
154
|
}, [state]);
|
|
@@ -139,7 +158,10 @@ function unstable_useLiveCompletionAdapter(options) {
|
|
|
139
158
|
categoryItems: () => [],
|
|
140
159
|
search: (query) => {
|
|
141
160
|
if (query !== state.query || retryableQueryRef.current === query) queueMicrotask(() => scheduleFetch(query));
|
|
142
|
-
else
|
|
161
|
+
else queueMicrotask(() => {
|
|
162
|
+
if (deferredQueryRef.current !== null && deferredQueryRef.current !== query) deferredQueryRef.current = null;
|
|
163
|
+
if (pendingQueryRef.current !== null && pendingQueryRef.current !== query) invalidatePending();
|
|
164
|
+
});
|
|
143
165
|
return state.items;
|
|
144
166
|
}
|
|
145
167
|
}), [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useLayoutEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","cacheKey","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","failed","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","retryableQueryRef","pendingRetryQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","invalidatePending","cacheKeyRef","s","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /**\n * Identifies the fetcher's data source. Change this when switching accounts,\n * workspaces, or another boundary that should invalidate cached results.\n */\n readonly cacheKey?: string | number | undefined;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n failed: boolean;\n }>({ query: NO_QUERY, items: [], failed: false });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n // The debounce timer must only observe fetchers from committed renders.\n useLayoutEffect(() => {\n fetcherRef.current = fetcher;\n }, [fetcher]);\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n const retryableQueryRef = useRef<string | null>(null);\n const pendingRetryQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const rearmPendingRetry = useCallback(() => {\n const query = pendingRetryQueryRef.current;\n if (query === null) return;\n retryableQueryRef.current = query;\n pendingRetryQueryRef.current = null;\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (pendingQueryRef.current === query) return;\n rearmPendingRetry();\n if (retryableQueryRef.current === query) {\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = query;\n }\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n Promise.resolve()\n .then(() => fetcherRef.current(query))\n .then(\n (items) => {\n if (token !== tokenRef.current) return;\n pendingRetryQueryRef.current = null;\n setState({ query, items, failed: false });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n pendingQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query, items: [], failed: true });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer, rearmPendingRetry],\n );\n\n const invalidatePending = useCallback(() => {\n rearmPendingRetry();\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer, rearmPendingRetry]);\n\n const cacheKeyRef = useRef(cacheKey);\n useLayoutEffect(() => {\n if (cacheKeyRef.current === cacheKey) return;\n cacheKeyRef.current = cacheKey;\n invalidatePending();\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query: NO_QUERY, items: [], failed: false });\n }, [cacheKey, invalidatePending]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [], failed: false },\n );\n }, [enabled, invalidatePending]);\n\n useEffect(() => cancelTimer, [cancelTimer]);\n\n // Arm retries only after the failed state commits. Arming during rejection\n // would let the failure render immediately schedule another request.\n useEffect(() => {\n retryableQueryRef.current = state.failed ? state.query : null;\n }, [state]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query || retryableQueryRef.current === query) {\n queueMicrotask(() => scheduleFetch(query));\n } else if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n // the query returned to a cached value while a fetch for a different\n // query is in flight; drop it so its result cannot overwrite the cache\n queueMicrotask(invalidatePending);\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useLayoutEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","cacheKey","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","failed","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","retryableQueryRef","pendingRetryQueryRef","inactiveRef","deferredQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","scheduleFetchRef","invalidatePending","cacheKeyRef","s","deferredQuery","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /**\n * Identifies the fetcher's data source. Change this when switching accounts,\n * workspaces, or another boundary that should invalidate cached results.\n */\n readonly cacheKey?: string | number | undefined;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n failed: boolean;\n }>({ query: NO_QUERY, items: [], failed: false });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n // The debounce timer must only observe fetchers from committed renders.\n useLayoutEffect(() => {\n fetcherRef.current = fetcher;\n }, [fetcher]);\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n const retryableQueryRef = useRef<string | null>(null);\n const pendingRetryQueryRef = useRef<string | null>(null);\n const inactiveRef = useRef(true);\n const deferredQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const rearmPendingRetry = useCallback(() => {\n const query = pendingRetryQueryRef.current;\n if (query === null) return;\n retryableQueryRef.current = query;\n pendingRetryQueryRef.current = null;\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (inactiveRef.current) {\n deferredQueryRef.current = query;\n return;\n }\n if (pendingQueryRef.current === query) return;\n rearmPendingRetry();\n if (retryableQueryRef.current === query) {\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = query;\n }\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n Promise.resolve()\n .then(() => fetcherRef.current(query))\n .then(\n (items) => {\n if (token !== tokenRef.current) return;\n pendingRetryQueryRef.current = null;\n setState({ query, items, failed: false });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n pendingQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query, items: [], failed: true });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer, rearmPendingRetry],\n );\n\n const scheduleFetchRef = useRef(scheduleFetch);\n useLayoutEffect(() => {\n scheduleFetchRef.current = scheduleFetch;\n }, [scheduleFetch]);\n\n const invalidatePending = useCallback(() => {\n rearmPendingRetry();\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer, rearmPendingRetry]);\n\n const cacheKeyRef = useRef(cacheKey);\n useLayoutEffect(() => {\n if (cacheKeyRef.current === cacheKey) return;\n cacheKeyRef.current = cacheKey;\n invalidatePending();\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query: NO_QUERY, items: [], failed: false });\n }, [cacheKey, invalidatePending]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [], failed: false },\n );\n }, [enabled, invalidatePending]);\n\n // Render-time searches can outlive an abandoned render, so they only arm\n // request work after this hook commits.\n useLayoutEffect(() => {\n inactiveRef.current = false;\n const deferredQuery = deferredQueryRef.current;\n deferredQueryRef.current = null;\n if (deferredQuery !== null) scheduleFetchRef.current(deferredQuery);\n return () => {\n inactiveRef.current = true;\n invalidatePending();\n };\n }, [invalidatePending]);\n\n // Arm retries only after the failed state commits. Arming during rejection\n // would let the failure render immediately schedule another request.\n useEffect(() => {\n retryableQueryRef.current = state.failed ? state.query : null;\n }, [state]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query || retryableQueryRef.current === query) {\n queueMicrotask(() => scheduleFetch(query));\n } else {\n queueMicrotask(() => {\n if (\n deferredQueryRef.current !== null &&\n deferredQueryRef.current !== query\n ) {\n deferredQueryRef.current = null;\n }\n if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n invalidatePending();\n }\n });\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
|
|
@@ -21,7 +21,10 @@ type Unstable_MentionCategory = {
|
|
|
21
21
|
readonly items: readonly Unstable_Mention[];
|
|
22
22
|
};
|
|
23
23
|
type Unstable_ModelContextToolsOptions = {
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Wrap tools in a dedicated category. Selects drill-down mode on its own
|
|
26
|
+
* when `categories` is unset and `items` is omitted.
|
|
27
|
+
*/
|
|
25
28
|
readonly category?: {
|
|
26
29
|
readonly id: string;
|
|
27
30
|
readonly label: string;
|
|
@@ -32,7 +35,10 @@ type Unstable_ModelContextToolsOptions = {
|
|
|
32
35
|
readonly icon?: string;
|
|
33
36
|
};
|
|
34
37
|
type Unstable_UseMentionAdapterOptions = {
|
|
35
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Flat mention list. Ignored when `categories` is set, and keeps the
|
|
40
|
+
* adapter flat when a tool `category` is configured.
|
|
41
|
+
*/
|
|
36
42
|
readonly items?: readonly Unstable_Mention[];
|
|
37
43
|
/** Categorized mentions for drill-down navigation. */
|
|
38
44
|
readonly categories?: readonly Unstable_MentionCategory[];
|
|
@@ -41,7 +47,7 @@ type Unstable_UseMentionAdapterOptions = {
|
|
|
41
47
|
* - `false`: exclude.
|
|
42
48
|
* - `true`: include (default when no `items`/`categories`; as a category
|
|
43
49
|
* if `categories` is set, flat otherwise).
|
|
44
|
-
* - object: explicit config.
|
|
50
|
+
* - object: explicit config; `category` also selects drill-down mode.
|
|
45
51
|
*
|
|
46
52
|
* Omitted → defaults to `true` iff neither `items` nor `categories`.
|
|
47
53
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentionAdapter.d.ts","names":[],"sources":["../../src/unstable/useMentionAdapter.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"useMentionAdapter.d.ts","names":[],"sources":["../../src/unstable/useMentionAdapter.ts"],"mappings":";;;;;KAoBY,yBAAyB;EAAK;;KAE9B;WACD;WACA;WACA;WACA;;WAEA;WACA,WAAW;;KAGV;WACD;WACA;WACA,gBAAgB;;KAGf;;;;;WAKD;aAAsB;aAAqB;;;WAE3C,eAAe;;WAEf;;KAGC;;;;;WAKD,iBAAiB;;WAEjB,sBAAsB;;;;;;;;;;WAUtB,qCAEL;;WAEK,YAAY;;WAEZ,cAAc,MAAM;;WAEpB,UAAU,eAAe;;WAEzB,eAAe;;KAGd;WACD,WAAW;WACX,eAAe,MAAM;;;;;;;;;;;;;;;iBAoDhB,2BACd,UAAU;EAEV,SAAS;EACT,WAAW;EACX,UAAU,eAAe;EACzB,eAAe"}
|
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery.js";
|
|
3
|
+
import { shallowEqualRecords, useModelContextSnapshot } from "./useModelContextSnapshot.js";
|
|
2
4
|
import { useAui } from "@assistant-ui/store";
|
|
3
5
|
import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
4
6
|
import { useMemo } from "@assistant-ui/tap/react-shim";
|
|
5
7
|
//#region src/unstable/useMentionAdapter.ts
|
|
8
|
+
const EMPTY_TOOL_MENTIONS = Object.freeze({});
|
|
9
|
+
const toolMentionSource = {
|
|
10
|
+
empty: EMPTY_TOOL_MENTIONS,
|
|
11
|
+
read: (aui) => {
|
|
12
|
+
const tools = aui.thread.getModelContext().tools;
|
|
13
|
+
if (!tools) return EMPTY_TOOL_MENTIONS;
|
|
14
|
+
const mentions = Object.create(null);
|
|
15
|
+
for (const [name, tool] of Object.entries(tools)) mentions[name] = tool.description;
|
|
16
|
+
return mentions;
|
|
17
|
+
},
|
|
18
|
+
subscribe: (aui, onChange) => {
|
|
19
|
+
const unsubscribeContext = aui.on("thread.modelContextUpdate", onChange);
|
|
20
|
+
const unsubscribeSelection = aui.on({
|
|
21
|
+
scope: "*",
|
|
22
|
+
event: "threads.selectionChanged"
|
|
23
|
+
}, onChange);
|
|
24
|
+
return () => {
|
|
25
|
+
unsubscribeContext();
|
|
26
|
+
unsubscribeSelection();
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
isEqual: shallowEqualRecords
|
|
30
|
+
};
|
|
6
31
|
/**
|
|
7
32
|
* @deprecated Under active development and might change without notice.
|
|
8
33
|
*
|
|
@@ -25,38 +50,30 @@ function unstable_useMentionAdapter(options) {
|
|
|
25
50
|
const wantsTools = includeTools !== false;
|
|
26
51
|
const formatter = options?.formatter;
|
|
27
52
|
const onInserted = options?.onInserted;
|
|
53
|
+
const isCategorized = categories !== void 0 || toolsConfig?.category !== void 0 && items === void 0;
|
|
54
|
+
const toolMentions = useModelContextSnapshot(aui, wantsTools, toolMentionSource);
|
|
28
55
|
return {
|
|
29
56
|
adapter: useMemo(() => {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
icon: defaultIcon
|
|
42
|
-
}));
|
|
43
|
-
};
|
|
44
|
-
if (categories && categories.length > 0) {
|
|
45
|
-
const groups = categories.map((cat) => ({
|
|
57
|
+
const formatLabel = toolsConfig?.formatLabel;
|
|
58
|
+
const defaultIcon = toolsConfig?.icon;
|
|
59
|
+
const toolItems = wantsTools ? Object.entries(toolMentions).map(([name, description]) => toTriggerItem({
|
|
60
|
+
id: name,
|
|
61
|
+
type: "tool",
|
|
62
|
+
label: formatLabel ? formatLabel(name) : name,
|
|
63
|
+
description,
|
|
64
|
+
icon: defaultIcon
|
|
65
|
+
})) : [];
|
|
66
|
+
if (isCategorized) {
|
|
67
|
+
const groups = (categories ?? []).map((cat) => ({
|
|
46
68
|
id: cat.id,
|
|
47
69
|
label: cat.label,
|
|
48
70
|
items: cat.items.map(toTriggerItem)
|
|
49
71
|
}));
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
label: toolsConfig?.category?.label ?? "Tools",
|
|
56
|
-
items: toolItems
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const allGroups = toolCategory ? [...groups, toolCategory] : groups;
|
|
72
|
+
const allGroups = toolItems.length > 0 ? [...groups, {
|
|
73
|
+
id: toolsConfig?.category?.id ?? "tools",
|
|
74
|
+
label: toolsConfig?.category?.label ?? "Tools",
|
|
75
|
+
items: toolItems
|
|
76
|
+
}] : groups;
|
|
60
77
|
return {
|
|
61
78
|
categories: () => allGroups.map(({ id, label }) => ({
|
|
62
79
|
id,
|
|
@@ -65,31 +82,28 @@ function unstable_useMentionAdapter(options) {
|
|
|
65
82
|
categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],
|
|
66
83
|
search: (query) => {
|
|
67
84
|
const lower = query.toLowerCase();
|
|
68
|
-
return allGroups.flatMap((g) => g.items).filter((item) =>
|
|
85
|
+
return allGroups.flatMap((g) => g.items).filter((item) => matchesTriggerItemQuery(item, lower));
|
|
69
86
|
}
|
|
70
87
|
};
|
|
71
88
|
}
|
|
72
89
|
const flatItems = (items ?? []).map(toTriggerItem);
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const toolItems = getModelContextTools();
|
|
76
|
-
const seen = new Set(flatItems.map((i) => i.id));
|
|
77
|
-
return [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];
|
|
78
|
-
};
|
|
90
|
+
const seen = new Set(flatItems.map((i) => i.id));
|
|
91
|
+
const flatPool = [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];
|
|
79
92
|
return {
|
|
80
93
|
categories: () => [],
|
|
81
94
|
categoryItems: () => [],
|
|
82
95
|
search: (query) => {
|
|
83
96
|
const lower = query.toLowerCase();
|
|
84
|
-
return
|
|
97
|
+
return flatPool.filter((item) => matchesTriggerItemQuery(item, lower));
|
|
85
98
|
}
|
|
86
99
|
};
|
|
87
100
|
}, [
|
|
88
|
-
aui,
|
|
89
101
|
items,
|
|
90
102
|
categories,
|
|
91
103
|
wantsTools,
|
|
92
|
-
toolsConfig
|
|
104
|
+
toolsConfig,
|
|
105
|
+
isCategorized,
|
|
106
|
+
toolMentions
|
|
93
107
|
]),
|
|
94
108
|
directive: useMemo(() => ({
|
|
95
109
|
formatter: formatter ?? unstable_defaultDirectiveFormatter,
|
|
@@ -112,13 +126,6 @@ function toTriggerItem(m) {
|
|
|
112
126
|
...metadata !== void 0 ? { metadata } : {}
|
|
113
127
|
};
|
|
114
128
|
}
|
|
115
|
-
function matchesQuery(item, lower) {
|
|
116
|
-
if (!lower) return true;
|
|
117
|
-
if (item.id.toLowerCase().includes(lower)) return true;
|
|
118
|
-
if (item.label.toLowerCase().includes(lower)) return true;
|
|
119
|
-
if (item.description?.toLowerCase().includes(lower)) return true;
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
129
|
//#endregion
|
|
123
130
|
export { unstable_useMentionAdapter };
|
|
124
131
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","unstable_useMentionAdapter","options","adapter","directive","aui","includeTools","toolsConfig","undefined","wantsTools","getModelContextTools","ctx","thread","getModelContext","tools","defaultIcon","Object","entries","map","name","tool","toTriggerItem","length","groups","cat","toolCategory","toolItems","allGroups","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","matchesQuery","flatItems","getFlatPool","seen","Set","i","t","has","m","includes"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /** Wrap tools in a dedicated category (drill-down mode). */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /** Flat mention list. Ignored when `categories` is set. */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const getModelContextTools = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return [];\n const ctx = aui.thread.getModelContext();\n const tools = ctx.tools;\n if (!tools) return [];\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n return Object.entries(tools).map(([name, tool]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description: tool.description ?? undefined,\n icon: defaultIcon,\n }),\n );\n };\n\n // Categorized: drill-down mode\n if (categories && categories.length > 0) {\n const groups = categories.map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n\n let toolCategory: {\n id: string;\n label: string;\n items: Unstable_TriggerItem[];\n } | null = null;\n if (wantsTools) {\n const toolItems = getModelContextTools();\n if (toolItems.length > 0) {\n toolCategory = {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n };\n }\n }\n const allGroups = toolCategory ? [...groups, toolCategory] : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n const getFlatPool = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return flatItems;\n const toolItems = getModelContextTools();\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n return [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];\n };\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return getFlatPool().filter((item) => matchesQuery(item, lower));\n },\n };\n }, [aui, items, categories, wantsTools, toolsConfig]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n\nfunction matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {\n if (!lower) return true;\n if (item.id.toLowerCase().includes(lower)) return true;\n if (item.label.toLowerCase().includes(lower)) return true;\n if (item.description?.toLowerCase().includes(lower)) return true;\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","matchesTriggerItemQuery","shallowEqualRecords","useModelContextSnapshot","ModelContextSnapshotSource","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","EMPTY_TOOL_MENTIONS","Readonly","Object","freeze","toolMentionSource","empty","read","aui","tools","thread","getModelContext","mentions","create","name","tool","entries","subscribe","onChange","unsubscribeContext","on","unsubscribeSelection","scope","event","isEqual","unstable_useMentionAdapter","options","adapter","directive","includeTools","toolsConfig","undefined","wantsTools","isCategorized","toolMentions","defaultIcon","toolItems","map","toTriggerItem","groups","cat","allGroups","length","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","flatItems","seen","Set","i","flatPool","t","has","m"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { matchesTriggerItemQuery } from \"../primitives/composer/trigger/matchesTriggerItemQuery\";\nimport {\n shallowEqualRecords,\n useModelContextSnapshot,\n type ModelContextSnapshotSource,\n} from \"./useModelContextSnapshot\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /**\n * Wrap tools in a dedicated category. Selects drill-down mode on its own\n * when `categories` is unset and `items` is omitted.\n */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /**\n * Flat mention list. Ignored when `categories` is set, and keeps the\n * adapter flat when a tool `category` is configured.\n */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config; `category` also selects drill-down mode.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\nconst EMPTY_TOOL_MENTIONS: Readonly<Record<string, string | undefined>> =\n Object.freeze({});\n\n// The description is copied out rather than reached through the tool, so a\n// provider that returns a stable tool object and edits it in place is observed.\nconst toolMentionSource: ModelContextSnapshotSource<\n Readonly<Record<string, string | undefined>>\n> = {\n empty: EMPTY_TOOL_MENTIONS,\n read: (aui) => {\n const tools = aui.thread.getModelContext().tools;\n if (!tools) return EMPTY_TOOL_MENTIONS;\n const mentions = Object.create(null) as Record<string, string | undefined>;\n for (const [name, tool] of Object.entries(tools)) {\n mentions[name] = tool.description;\n }\n return mentions;\n },\n subscribe: (aui, onChange) => {\n const unsubscribeContext = aui.on(\"thread.modelContextUpdate\", onChange);\n // Rebinding the thread event subject to a new thread does not replay it,\n // so a switch between threads carrying different providers is its own\n // refresh trigger. Subscribed globally because a composer can render\n // without a thread list scope.\n const unsubscribeSelection = aui.on(\n { scope: \"*\", event: \"threads.selectionChanged\" },\n onChange,\n );\n return () => {\n unsubscribeContext();\n unsubscribeSelection();\n };\n },\n isEqual: shallowEqualRecords,\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n const isCategorized =\n categories !== undefined ||\n (toolsConfig?.category !== undefined && items === undefined);\n const toolMentions = useModelContextSnapshot(\n aui,\n wantsTools,\n toolMentionSource,\n );\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n const toolItems = wantsTools\n ? Object.entries(toolMentions).map(([name, description]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description,\n icon: defaultIcon,\n }),\n )\n : [];\n\n // Categorized: drill-down mode\n if (isCategorized) {\n const groups = (categories ?? []).map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n const allGroups =\n toolItems.length > 0\n ? [\n ...groups,\n {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n },\n ]\n : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n const flatPool = [\n ...flatItems,\n ...toolItems.filter((t) => !seen.has(t.id)),\n ];\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return flatPool.filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }, [items, categories, wantsTools, toolsConfig, isCategorized, toolMentions]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -17,11 +17,10 @@ type Unstable_MessageStallDetection = {
|
|
|
17
17
|
* @deprecated Under active development and might change without notice.
|
|
18
18
|
*
|
|
19
19
|
* Detects mid-run output stalls on the current message: while the message is
|
|
20
|
-
* running, watches
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* tokens have already streamed.
|
|
20
|
+
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
21
|
+
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
22
|
+
* Useful for re-surfacing a "still working" indicator during tool think-time or
|
|
23
|
+
* provider stalls, after the first tokens have already streamed.
|
|
25
24
|
*
|
|
26
25
|
* Must be used inside a message scope.
|
|
27
26
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageStallDetection.d.ts","names":[],"sources":["../../src/unstable/useMessageStallDetection.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"useMessageStallDetection.d.ts","names":[],"sources":["../../src/unstable/useMessageStallDetection.ts"],"mappings":";KAMY;;;;;;EAMV;;KAGU;;EAEV;;EAEA;;;;;;;;;;;;;iBAcc,kCACd,UAAU,wCACT"}
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useAuiState } from "@assistant-ui/store";
|
|
3
3
|
import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
4
|
+
import { useShallowSelector } from "@assistant-ui/store/internal";
|
|
4
5
|
//#region src/unstable/useMessageStallDetection.ts
|
|
5
6
|
/**
|
|
6
7
|
* @deprecated Under active development and might change without notice.
|
|
7
8
|
*
|
|
8
9
|
* Detects mid-run output stalls on the current message: while the message is
|
|
9
|
-
* running, watches
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* tokens have already streamed.
|
|
10
|
+
* running, watches its text, reasoning, and tool-argument values plus tool-result
|
|
11
|
+
* availability and reports a stall once they stop changing for `thresholdMs`.
|
|
12
|
+
* Useful for re-surfacing a "still working" indicator during tool think-time or
|
|
13
|
+
* provider stalls, after the first tokens have already streamed.
|
|
14
14
|
*
|
|
15
15
|
* Must be used inside a message scope.
|
|
16
16
|
*/
|
|
17
17
|
function unstable_useMessageStallDetection(options) {
|
|
18
18
|
const thresholdMs = options?.thresholdMs ?? 2e3;
|
|
19
|
-
const
|
|
20
|
-
if (s.message.status?.type
|
|
21
|
-
|
|
22
|
-
for (const part of s.message.content) if (part.type === "text" || part.type === "reasoning")
|
|
23
|
-
else if (part.type === "tool-call")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const activity = useAuiState(useShallowSelector((s) => {
|
|
20
|
+
if (!(s.message.status?.type === "running")) return [false];
|
|
21
|
+
const values = [true, s.message.content.length];
|
|
22
|
+
for (const part of s.message.content) if (part.type === "text" || part.type === "reasoning") values.push(part.type, part.text);
|
|
23
|
+
else if (part.type === "tool-call") values.push(part.type, part.argsText, part.result !== void 0);
|
|
24
|
+
else values.push(part.type);
|
|
25
|
+
return values;
|
|
26
|
+
}));
|
|
27
|
+
const running = activity[0] === true;
|
|
27
28
|
const lastActivityRef = useRef(Date.now());
|
|
28
29
|
const [stalled, setStalled] = useState(false);
|
|
29
30
|
const [, setTick] = useState(0);
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (!running) return void 0;
|
|
32
33
|
lastActivityRef.current = Date.now();
|
|
33
|
-
}, [running,
|
|
34
|
+
}, [running, activity]);
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
if (!running) {
|
|
36
37
|
setStalled(false);
|
|
@@ -46,7 +47,7 @@ function unstable_useMessageStallDetection(options) {
|
|
|
46
47
|
return () => clearTimeout(id);
|
|
47
48
|
}, [
|
|
48
49
|
running,
|
|
49
|
-
|
|
50
|
+
activity,
|
|
50
51
|
thresholdMs
|
|
51
52
|
]);
|
|
52
53
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageStallDetection.js","names":["useEffect","useRef","useState","useAuiState","Unstable_MessageStallDetectionOptions","thresholdMs","Unstable_MessageStallDetection","stalled","stalledForMs","unstable_useMessageStallDetection","options","
|
|
1
|
+
{"version":3,"file":"useMessageStallDetection.js","names":["useEffect","useRef","useState","useAuiState","useShallowSelector","Unstable_MessageStallDetectionOptions","thresholdMs","Unstable_MessageStallDetection","stalled","stalledForMs","unstable_useMessageStallDetection","options","activity","s","running","message","status","type","values","content","length","part","push","text","argsText","result","undefined","lastActivityRef","Date","now","setStalled","setTick","current","sinceActivity","id","setTimeout","clearTimeout","setInterval","t","clearInterval","Math","max"],"sources":["../../src/unstable/useMessageStallDetection.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useAuiState } from \"@assistant-ui/store\";\nimport { useShallowSelector } from \"@assistant-ui/store/internal\";\n\nexport type Unstable_MessageStallDetectionOptions = {\n /**\n * Milliseconds of unchanged message content before the message counts as\n * stalled.\n * @default 2000\n */\n thresholdMs?: number | undefined;\n};\n\nexport type Unstable_MessageStallDetection = {\n /** True while the message is running and its content has not changed for at least `thresholdMs`. */\n stalled: boolean;\n /** Milliseconds since the last observed content change. `0` while not stalled. */\n stalledForMs: number;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Detects mid-run output stalls on the current message: while the message is\n * running, watches its text, reasoning, and tool-argument values plus tool-result\n * availability and reports a stall once they stop changing for `thresholdMs`.\n * Useful for re-surfacing a \"still working\" indicator during tool think-time or\n * provider stalls, after the first tokens have already streamed.\n *\n * Must be used inside a message scope.\n */\nexport function unstable_useMessageStallDetection(\n options?: Unstable_MessageStallDetectionOptions,\n): Unstable_MessageStallDetection {\n const thresholdMs = options?.thresholdMs ?? 2000;\n\n const activity = useAuiState(\n useShallowSelector((s) => {\n const running = s.message.status?.type === \"running\";\n if (!running) return [false];\n\n const values: unknown[] = [true, s.message.content.length];\n\n for (const part of s.message.content) {\n if (part.type === \"text\" || part.type === \"reasoning\") {\n values.push(part.type, part.text);\n } else if (part.type === \"tool-call\") {\n values.push(part.type, part.argsText, part.result !== undefined);\n } else {\n values.push(part.type);\n }\n }\n return values;\n }),\n );\n\n const running = activity[0] === true;\n const lastActivityRef = useRef(Date.now());\n const [stalled, setStalled] = useState(false);\n const [, setTick] = useState(0);\n\n useEffect(() => {\n if (!running) return undefined;\n lastActivityRef.current = Date.now();\n return undefined;\n }, [running, activity]);\n\n useEffect(() => {\n if (!running) {\n setStalled(false);\n return undefined;\n }\n\n const sinceActivity = Date.now() - lastActivityRef.current;\n if (sinceActivity >= thresholdMs) {\n setStalled(true);\n return undefined;\n }\n\n setStalled(false);\n const id = setTimeout(() => setStalled(true), thresholdMs - sinceActivity);\n return () => clearTimeout(id);\n }, [running, activity, thresholdMs]);\n\n useEffect(() => {\n if (!stalled) return undefined;\n const id = setInterval(() => setTick((t) => t + 1), 1000);\n return () => clearInterval(id);\n }, [stalled]);\n\n if (!stalled) return { stalled: false, stalledForMs: 0 };\n return {\n stalled: true,\n stalledForMs: Math.max(0, Date.now() - lastActivityRef.current),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,SAAgBU,kCACdC,SACgC;CAChC,MAAML,cAAcK,SAASL,eAAe;CAE5C,MAAMM,WAAWT,YACfC,oBAAoBS,MAAM;EAExB,IAAI,EADYA,EAAEE,QAAQC,QAAQC,SAAS,YAC7B,OAAO,CAAC,KAAK;EAE3B,MAAMC,SAAoB,CAAC,MAAML,EAAEE,QAAQI,QAAQC,MAAM;EAEzD,KAAK,MAAMC,QAAQR,EAAEE,QAAQI,SAC3B,IAAIE,KAAKJ,SAAS,UAAUI,KAAKJ,SAAS,aACxCC,OAAOI,KAAKD,KAAKJ,MAAMI,KAAKE,IAAI;OAC3B,IAAIF,KAAKJ,SAAS,aACvBC,OAAOI,KAAKD,KAAKJ,MAAMI,KAAKG,UAAUH,KAAKI,WAAWC,KAAAA,CAAS;OAE/DR,OAAOI,KAAKD,KAAKJ,IAAI;EAGzB,OAAOC;CACT,CAAC,CACH;CAEA,MAAMJ,UAAUF,SAAS,OAAO;CAChC,MAAMe,kBAAkB1B,OAAO2B,KAAKC,IAAI,CAAC;CACzC,MAAM,CAACrB,SAASsB,cAAc5B,SAAS,KAAK;CAC5C,MAAM,GAAG6B,WAAW7B,SAAS,CAAC;CAE9BF,gBAAgB;EACd,IAAI,CAACc,SAAS,OAAOY,KAAAA;EACrBC,gBAAgBK,UAAUJ,KAAKC,IAAI;CAErC,GAAG,CAACf,SAASF,QAAQ,CAAC;CAEtBZ,gBAAgB;EACd,IAAI,CAACc,SAAS;GACZgB,WAAW,KAAK;GAChB;EACF;EAEA,MAAMG,gBAAgBL,KAAKC,IAAI,IAAIF,gBAAgBK;EACnD,IAAIC,iBAAiB3B,aAAa;GAChCwB,WAAW,IAAI;GACf;EACF;EAEAA,WAAW,KAAK;EAChB,MAAMI,KAAKC,iBAAiBL,WAAW,IAAI,GAAGxB,cAAc2B,aAAa;EACzE,aAAaG,aAAaF,EAAE;CAC9B,GAAG;EAACpB;EAASF;EAAUN;CAAW,CAAC;CAEnCN,gBAAgB;EACd,IAAI,CAACQ,SAAS,OAAOkB,KAAAA;EACrB,MAAMQ,KAAKG,kBAAkBN,SAASO,MAAMA,IAAI,CAAC,GAAG,GAAI;EACxD,aAAaC,cAAcL,EAAE;CAC/B,GAAG,CAAC1B,OAAO,CAAC;CAEZ,IAAI,CAACA,SAAS,OAAO;EAAEA,SAAS;EAAOC,cAAc;CAAE;CACvD,OAAO;EACLD,SAAS;EACTC,cAAc+B,KAAKC,IAAI,GAAGb,KAAKC,IAAI,IAAIF,gBAAgBK,OAAO;CAChE;AACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AssistantClient } from "@assistant-ui/store";
|
|
2
|
+
import { Unsubscribe } from "@assistant-ui/core";
|
|
3
|
+
//#region src/unstable/useModelContextSnapshot.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Where a snapshot comes from and what counts as a change to it. Declared once
|
|
6
|
+
* per consumer at module scope: the subscription effect keys on this object, so
|
|
7
|
+
* an inline source without an `isEqual` re-reads itself into a render loop.
|
|
8
|
+
*/
|
|
9
|
+
type ModelContextSnapshotSource<T> = {
|
|
10
|
+
/** Seeds the snapshot when it starts disabled. */
|
|
11
|
+
readonly empty: T;
|
|
12
|
+
/** Projects the fields the consumer observes out of the live model context. */
|
|
13
|
+
read(aui: AssistantClient): T;
|
|
14
|
+
subscribe(aui: AssistantClient, onChange: () => void): Unsubscribe;
|
|
15
|
+
/**
|
|
16
|
+
* An equal pair keeps the previous reference. Omitted by a consumer that
|
|
17
|
+
* re-derives on every notification, where a caller republishing an unchanged
|
|
18
|
+
* tool set is how it asks to be re-run.
|
|
19
|
+
*/
|
|
20
|
+
isEqual?(previous: T, next: T): boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* `getModelContext()` rebuilds its result on every call, so a projection of the
|
|
24
|
+
* fields a consumer observes is held in state and refreshed on notification
|
|
25
|
+
* rather than read during render. Keeping the previous reference for an equal
|
|
26
|
+
* projection is what stops a notification carrying no observable change from
|
|
27
|
+
* invalidating everything derived from it.
|
|
28
|
+
*/
|
|
29
|
+
declare const useModelContextSnapshot: <T>(aui: AssistantClient, enabled: boolean, source: ModelContextSnapshotSource<T>) => T;
|
|
30
|
+
/** Shallow own-key comparison, for a projection whose values are records. */
|
|
31
|
+
declare const shallowEqualRecords: (previous: Readonly<Record<string, unknown>>, next: Readonly<Record<string, unknown>>) => boolean;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { ModelContextSnapshotSource, shallowEqualRecords, useModelContextSnapshot };
|
|
34
|
+
//# sourceMappingURL=useModelContextSnapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useModelContextSnapshot.d.ts","names":[],"sources":["../../src/unstable/useModelContextSnapshot.ts"],"mappings":";;;;;;;;KAWY,2BAA2B;;WAE5B,OAAO;;EAEhB,KAAK,KAAK,kBAAkB;EAC5B,UAAU,KAAK,iBAAiB,uBAAuB;;;;;;EAMvD,SAAS,UAAU,GAAG,MAAM;;;;;;;;;cAUjB,0BAA2B,GACtC,KAAK,iBACL,kBACA,QAAQ,2BAA2B,OAClC;;cAqBU,sBACX,UAAU,SAAS,0BACnB,MAAM,SAAS"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
3
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
4
|
+
//#region src/unstable/useModelContextSnapshot.ts
|
|
5
|
+
/**
|
|
6
|
+
* `getModelContext()` rebuilds its result on every call, so a projection of the
|
|
7
|
+
* fields a consumer observes is held in state and refreshed on notification
|
|
8
|
+
* rather than read during render. Keeping the previous reference for an equal
|
|
9
|
+
* projection is what stops a notification carrying no observable change from
|
|
10
|
+
* invalidating everything derived from it.
|
|
11
|
+
*/
|
|
12
|
+
const useModelContextSnapshot = (aui, enabled, source) => {
|
|
13
|
+
const $ = c(9);
|
|
14
|
+
let t0;
|
|
15
|
+
if ($[0] !== aui || $[1] !== enabled || $[2] !== source) {
|
|
16
|
+
t0 = () => enabled ? source.read(aui) : source.empty;
|
|
17
|
+
$[0] = aui;
|
|
18
|
+
$[1] = enabled;
|
|
19
|
+
$[2] = source;
|
|
20
|
+
$[3] = t0;
|
|
21
|
+
} else t0 = $[3];
|
|
22
|
+
const [snapshot, setSnapshot] = useState(t0);
|
|
23
|
+
let t1;
|
|
24
|
+
let t2;
|
|
25
|
+
if ($[4] !== aui || $[5] !== enabled || $[6] !== source) {
|
|
26
|
+
t1 = () => {
|
|
27
|
+
if (!enabled) return;
|
|
28
|
+
const read = () => {
|
|
29
|
+
const next = source.read(aui);
|
|
30
|
+
setSnapshot((previous) => source.isEqual?.(previous, next) ? previous : next);
|
|
31
|
+
};
|
|
32
|
+
read();
|
|
33
|
+
return source.subscribe(aui, read);
|
|
34
|
+
};
|
|
35
|
+
t2 = [
|
|
36
|
+
aui,
|
|
37
|
+
enabled,
|
|
38
|
+
source
|
|
39
|
+
];
|
|
40
|
+
$[4] = aui;
|
|
41
|
+
$[5] = enabled;
|
|
42
|
+
$[6] = source;
|
|
43
|
+
$[7] = t1;
|
|
44
|
+
$[8] = t2;
|
|
45
|
+
} else {
|
|
46
|
+
t1 = $[7];
|
|
47
|
+
t2 = $[8];
|
|
48
|
+
}
|
|
49
|
+
useEffect(t1, t2);
|
|
50
|
+
return snapshot;
|
|
51
|
+
};
|
|
52
|
+
/** Shallow own-key comparison, for a projection whose values are records. */
|
|
53
|
+
const shallowEqualRecords = (previous, next) => {
|
|
54
|
+
const keys = Object.keys(previous);
|
|
55
|
+
if (keys.length !== Object.keys(next).length) return false;
|
|
56
|
+
return keys.every((key) => Object.hasOwn(next, key) && previous[key] === next[key]);
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
export { shallowEqualRecords, useModelContextSnapshot };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=useModelContextSnapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useModelContextSnapshot.js","names":["c","_c","useEffect","useState","AssistantClient","Unsubscribe","ModelContextSnapshotSource","empty","T","read","aui","subscribe","onChange","isEqual","previous","next","useModelContextSnapshot","enabled","source","$","t0","snapshot","setSnapshot","t1","t2","shallowEqualRecords","Readonly","Record","keys","Object","length","every","key","hasOwn"],"sources":["../../src/unstable/useModelContextSnapshot.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { AssistantClient } from \"@assistant-ui/store\";\nimport type { Unsubscribe } from \"@assistant-ui/core\";\n\n/**\n * Where a snapshot comes from and what counts as a change to it. Declared once\n * per consumer at module scope: the subscription effect keys on this object, so\n * an inline source without an `isEqual` re-reads itself into a render loop.\n */\nexport type ModelContextSnapshotSource<T> = {\n /** Seeds the snapshot when it starts disabled. */\n readonly empty: T;\n /** Projects the fields the consumer observes out of the live model context. */\n read(aui: AssistantClient): T;\n subscribe(aui: AssistantClient, onChange: () => void): Unsubscribe;\n /**\n * An equal pair keeps the previous reference. Omitted by a consumer that\n * re-derives on every notification, where a caller republishing an unchanged\n * tool set is how it asks to be re-run.\n */\n isEqual?(previous: T, next: T): boolean;\n};\n\n/**\n * `getModelContext()` rebuilds its result on every call, so a projection of the\n * fields a consumer observes is held in state and refreshed on notification\n * rather than read during render. Keeping the previous reference for an equal\n * projection is what stops a notification carrying no observable change from\n * invalidating everything derived from it.\n */\nexport const useModelContextSnapshot = <T>(\n aui: AssistantClient,\n enabled: boolean,\n source: ModelContextSnapshotSource<T>,\n): T => {\n const [snapshot, setSnapshot] = useState(() =>\n enabled ? source.read(aui) : source.empty,\n );\n\n useEffect(() => {\n if (!enabled) return undefined;\n const read = () => {\n const next = source.read(aui);\n setSnapshot((previous) =>\n source.isEqual?.(previous, next) ? previous : next,\n );\n };\n read();\n return source.subscribe(aui, read);\n }, [aui, enabled, source]);\n\n return snapshot;\n};\n\n/** Shallow own-key comparison, for a projection whose values are records. */\nexport const shallowEqualRecords = (\n previous: Readonly<Record<string, unknown>>,\n next: Readonly<Record<string, unknown>>,\n): boolean => {\n const keys = Object.keys(previous);\n if (keys.length !== Object.keys(next).length) return false;\n return keys.every(\n (key) => Object.hasOwn(next, key) && previous[key] === next[key],\n );\n};\n"],"mappings":";;;;;;;;;;;AAgCA,MAAagB,2BAA0BN,KAAAO,SAAAC,WAAA;CAAA,MAAAC,IAAAlB,EAAA,CAAA;CAAA,IAAAmB;CAAA,IAAAD,EAAA,OAAAT,OAAAS,EAAA,OAAAF,WAAAE,EAAA,OAAAD,QAAA;EAKIE,WACvCH,UAAUC,OAAMT,KAAMC,GAAkB,IAAXQ,OAAMX;EAAMY,EAAA,KAAAT;EAAAS,EAAA,KAAAF;EAAAE,EAAA,KAAAD;EAAAC,EAAA,KAAAC;CAAA,OAAAA,KAAAD,EAAA;CAD3C,MAAA,CAAAE,UAAAC,eAAgCnB,SAASiB,EAEzC;CAAE,IAAAG;CAAA,IAAAC;CAAA,IAAAL,EAAA,OAAAT,OAAAS,EAAA,OAAAF,WAAAE,EAAA,OAAAD,QAAA;EAEQK,WAAA;GACR,IAAI,CAACN,SAAO;GACZ,MAAAR,aAAa;IACX,MAAAM,OAAaG,OAAMT,KAAMC,GAAG;IAC5BY,aAAYR,aACVI,OAAML,UAAWC,UAAUC,IAAsB,IAAjDD,WAAAC,IACF;GAAC;GAEHN,KAAK;GAAC,OACCS,OAAMP,UAAWD,KAAKD,IAAI;EAAC;EACjCe,KAAA;GAACd;GAAKO;GAASC;EAAM;EAACC,EAAA,KAAAT;EAAAS,EAAA,KAAAF;EAAAE,EAAA,KAAAD;EAAAC,EAAA,KAAAI;EAAAJ,EAAA,KAAAK;CAAA,OAAA;EAAAD,KAAAJ,EAAA;EAAAK,KAAAL,EAAA;CAAA;CAVzBjB,UAAUqB,IAUPC,EAAsB;CAAC,OAEnBH;AAAQ;;AAIjB,MAAaI,uBACXX,UACAC,SACY;CACZ,MAAMa,OAAOC,OAAOD,KAAKd,QAAQ;CACjC,IAAIc,KAAKE,WAAWD,OAAOD,KAAKb,IAAI,CAAC,CAACe,QAAQ,OAAO;CACrD,OAAOF,KAAKG,OACTC,QAAQH,OAAOI,OAAOlB,MAAMiB,GAAG,KAAKlB,SAASkB,SAASjB,KAAKiB,IAC9D;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSlashCommandAdapter.d.ts","names":[],"sources":["../../src/unstable/useSlashCommandAdapter.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"useSlashCommandAdapter.d.ts","names":[],"sources":["../../src/unstable/useSlashCommandAdapter.ts"],"mappings":";;;KAUY;WACD;WACA;WACA;WACA;WACA;;KAGC;WACD,mBAAmB;;WAEnB;;WAEA,UAAU,eAAe;;WAEzB,eAAe;;KAGd;WACD,YAAY,MAAM;WAClB;;;;;;;;;;;;;;;;;;;;;;iBAuBK,gCACd,SAAS;EAET,SAAS;EACT,QAAQ;EACR,UAAU,eAAe;EACzB,eAAe"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery.js";
|
|
2
3
|
import { useLayoutEffect, useMemo, useRef } from "@assistant-ui/tap/react-shim";
|
|
3
4
|
//#region src/unstable/useSlashCommandAdapter.ts
|
|
4
5
|
/**
|
|
@@ -36,7 +37,7 @@ function unstable_useSlashCommandAdapter(options) {
|
|
|
36
37
|
categoryItems: () => [],
|
|
37
38
|
search: (query) => {
|
|
38
39
|
const lower = query.toLowerCase();
|
|
39
|
-
return items.filter((item) =>
|
|
40
|
+
return items.filter((item) => matchesTriggerItemQuery(item, lower));
|
|
40
41
|
}
|
|
41
42
|
}), [items]);
|
|
42
43
|
const action = useMemo(() => ({
|
|
@@ -66,13 +67,6 @@ function toItem(cmd) {
|
|
|
66
67
|
...cmd.icon !== void 0 ? { metadata: { icon: cmd.icon } } : {}
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
|
-
function matchesQuery(item, lower) {
|
|
70
|
-
if (!lower) return true;
|
|
71
|
-
if (item.id.toLowerCase().includes(lower)) return true;
|
|
72
|
-
if (item.label.toLowerCase().includes(lower)) return true;
|
|
73
|
-
if (item.description?.toLowerCase().includes(lower)) return true;
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
70
|
function areTriggerItemsEqual(previous, next) {
|
|
77
71
|
if (!previous || previous.length !== next.length) return false;
|
|
78
72
|
return previous.every((item, index) => {
|