@assistant-ui/react 0.15.31 → 0.15.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.js +1 -2
- package/dist/adapters/GeminiLiveVoiceAdapter.d.ts +26 -0
- package/dist/adapters/GeminiLiveVoiceAdapter.d.ts.map +1 -0
- package/dist/adapters/GeminiLiveVoiceAdapter.js +346 -0
- package/dist/adapters/GeminiLiveVoiceAdapter.js.map +1 -0
- package/dist/adapters/OpenAIRealtimeVoiceAdapter.js +1 -2
- package/dist/adapters/gemini-live/audio.d.ts +27 -0
- package/dist/adapters/gemini-live/audio.d.ts.map +1 -0
- package/dist/adapters/gemini-live/audio.js +291 -0
- package/dist/adapters/gemini-live/audio.js.map +1 -0
- package/dist/assistant-transport.js +1 -2
- package/dist/cloud-renderer/CloudRendererHost.js +1 -2
- package/dist/context/ReadonlyStore.js +1 -2
- package/dist/context/providers/MessageProvider.js +1 -2
- package/dist/context/providers/ThreadViewportProvider.js +1 -2
- package/dist/context/react/ThreadViewportContext.js +1 -2
- package/dist/context/react/utils/createContextHook.js +1 -2
- package/dist/context/react/utils/createContextStoreHook.js +1 -2
- package/dist/context/stores/ThreadViewport.js +2 -3
- package/dist/devtools/DevToolsHooks.js +1 -2
- package/dist/hooks/useMessageQuote.js +1 -2
- package/dist/hooks/useMessageTiming.js +1 -2
- package/dist/hooks/useToolCallElapsed.js +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/internal.js +2 -3
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -2
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +4 -4
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +1 -2
- package/dist/mcp-apps/app-frame.js +2 -3
- package/dist/mcp-apps/bridge.js +1 -2
- package/dist/mcp-apps/csp.js +1 -2
- package/dist/mcp-apps/types.js +1 -2
- package/dist/mcp-apps/utils.js +1 -2
- package/dist/model-context/frame/useAssistantFrameHost.js +1 -2
- package/dist/model-context/makeAssistantVisible.js +1 -2
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -2
- package/dist/primitives/actionBar/ActionBarEdit.js +1 -2
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -2
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -2
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -2
- package/dist/primitives/actionBar/ActionBarInteractionContext.js +1 -2
- package/dist/primitives/actionBar/ActionBarReload.js +1 -2
- package/dist/primitives/actionBar/ActionBarRoot.js +1 -2
- package/dist/primitives/actionBar/ActionBarSpeak.js +1 -2
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +1 -2
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -2
- package/dist/primitives/actionBar.js +1 -2
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js +1 -2
- package/dist/primitives/actionBarMore/ActionBarMoreItem.js +1 -2
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +1 -2
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +1 -2
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +1 -2
- package/dist/primitives/actionBarMore/scope.js +1 -2
- package/dist/primitives/actionBarMore.js +1 -2
- package/dist/primitives/assistantModal/AssistantModalAnchor.js +1 -2
- package/dist/primitives/assistantModal/AssistantModalContent.js +1 -2
- package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -2
- package/dist/primitives/assistantModal/AssistantModalTrigger.js +1 -2
- package/dist/primitives/assistantModal/popoverRenderPrimitives.js +1 -2
- package/dist/primitives/assistantModal/scope.js +1 -2
- package/dist/primitives/assistantModal.js +1 -2
- package/dist/primitives/attachment/AttachmentName.js +1 -2
- package/dist/primitives/attachment/AttachmentRemove.js +1 -2
- package/dist/primitives/attachment/AttachmentRoot.js +1 -2
- package/dist/primitives/attachment/AttachmentThumb.js +1 -2
- package/dist/primitives/attachment.js +1 -2
- package/dist/primitives/branchPicker/BranchPickerCount.js +1 -2
- package/dist/primitives/branchPicker/BranchPickerNext.js +1 -2
- package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -2
- package/dist/primitives/branchPicker/BranchPickerPrevious.js +1 -2
- package/dist/primitives/branchPicker/BranchPickerRoot.js +1 -2
- package/dist/primitives/branchPicker.js +1 -2
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +5 -4
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +57 -20
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +1 -2
- package/dist/primitives/chainOfThought.js +1 -2
- package/dist/primitives/composer/ComposerAddAttachment.js +1 -2
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -2
- package/dist/primitives/composer/ComposerCancel.js +1 -2
- package/dist/primitives/composer/ComposerCompactContext.js +1 -2
- package/dist/primitives/composer/ComposerDictate.js +1 -2
- package/dist/primitives/composer/ComposerDictationTranscript.js +1 -2
- package/dist/primitives/composer/ComposerInput.js +1 -2
- package/dist/primitives/composer/ComposerInputPluginContext.js +1 -2
- package/dist/primitives/composer/ComposerQuote.js +1 -2
- package/dist/primitives/composer/ComposerResume.js +1 -2
- package/dist/primitives/composer/ComposerRoot.js +1 -2
- package/dist/primitives/composer/ComposerSend.js +1 -2
- package/dist/primitives/composer/ComposerStopDictation.js +1 -2
- package/dist/primitives/composer/composerInputSelector.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopover.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +1 -2
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +1 -2
- package/dist/primitives/composer/trigger/detectTrigger.js +1 -2
- package/dist/primitives/composer/trigger/index.js +1 -2
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js +1 -2
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +1 -2
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +1 -2
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +1 -2
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +1 -2
- package/dist/primitives/composer/useComposerCancelWithFocus.js +1 -2
- package/dist/primitives/composer/useComposerInputState.js +1 -2
- package/dist/primitives/composer.js +1 -2
- package/dist/primitives/dropdownMenuRenderPrimitives.js +1 -2
- package/dist/primitives/error/ErrorMessage.js +1 -2
- package/dist/primitives/error/ErrorRoot.js +1 -2
- package/dist/primitives/error.js +1 -2
- package/dist/primitives/message/MessageError.js +1 -2
- package/dist/primitives/message/MessageIf.js +1 -2
- package/dist/primitives/message/MessageParts.js +1 -2
- package/dist/primitives/message/MessagePartsGrouped.js +2 -3
- package/dist/primitives/message/MessageRoot.js +1 -2
- package/dist/primitives/message.js +1 -2
- package/dist/primitives/messagePart/MessagePartImage.js +1 -2
- package/dist/primitives/messagePart/MessagePartText.js +1 -2
- package/dist/primitives/messagePart/useMessagePartData.js +1 -2
- package/dist/primitives/messagePart/useMessagePartFile.js +1 -2
- package/dist/primitives/messagePart/useMessagePartImage.js +1 -2
- package/dist/primitives/messagePart/useMessagePartReasoning.js +1 -2
- package/dist/primitives/messagePart/useMessagePartSource.js +1 -2
- package/dist/primitives/messagePart/useMessagePartText.js +1 -2
- package/dist/primitives/messagePart.js +1 -2
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -2
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -2
- package/dist/primitives/queueItem/QueueItemText.js +1 -2
- package/dist/primitives/queueItem.js +1 -2
- package/dist/primitives/reasoning/useScrollLock.d.ts.map +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +41 -25
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -2
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +1 -2
- package/dist/primitives/selectionToolbar.js +1 -2
- package/dist/primitives/suggestion/SuggestionDescription.js +1 -2
- package/dist/primitives/suggestion/SuggestionTitle.js +1 -2
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -2
- package/dist/primitives/suggestion.js +1 -2
- package/dist/primitives/thread/ThreadEmpty.js +1 -2
- package/dist/primitives/thread/ThreadIf.js +1 -2
- package/dist/primitives/thread/ThreadLoadEarlier.js +1 -2
- package/dist/primitives/thread/ThreadRoot.js +1 -2
- package/dist/primitives/thread/ThreadRootElementContext.js +1 -2
- package/dist/primitives/thread/ThreadScrollToBottom.js +1 -2
- package/dist/primitives/thread/ThreadSuggestion.js +1 -2
- package/dist/primitives/thread/ThreadViewport.js +1 -2
- package/dist/primitives/thread/ThreadViewportFooter.js +1 -2
- package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.js +1 -2
- package/dist/primitives/thread/topAnchor/createReserveObservers.js +1 -2
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js +1 -2
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js +1 -2
- package/dist/primitives/thread/topAnchor/topAnchorUtils.js +1 -2
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +1 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -2
- package/dist/primitives/thread.js +1 -2
- package/dist/primitives/threadList/ThreadListLoadMore.js +1 -2
- package/dist/primitives/threadList/ThreadListNew.js +1 -2
- package/dist/primitives/threadList/ThreadListRoot.js +1 -2
- package/dist/primitives/threadList.js +1 -2
- package/dist/primitives/threadListFocusGroup.js +1 -2
- package/dist/primitives/threadListItem/ThreadListItemArchive.js +1 -2
- package/dist/primitives/threadListItem/ThreadListItemDelete.js +1 -2
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js +2 -3
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -2
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.js +1 -2
- package/dist/primitives/threadListItem.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
- package/dist/primitives/threadListItemMore/scope.js +1 -2
- package/dist/primitives/threadListItemMore.js +1 -2
- package/dist/sandbox-host/SandboxHost.js +2 -3
- package/dist/unstable/useComposerInput.js +1 -2
- package/dist/unstable/useComposerInputHistory.js +2 -3
- package/dist/unstable/useLiveCompletionAdapter.js +1 -2
- package/dist/unstable/useMentionAdapter.js +1 -2
- package/dist/unstable/useMessageStallDetection.js +1 -2
- package/dist/unstable/useModelContextSnapshot.js +1 -2
- package/dist/unstable/useSlashCommandAdapter.js +1 -2
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js +1 -2
- package/dist/unstable/webmcp/convertTools.js +1 -2
- package/dist/unstable/webmcp/useWebMcpProvider.js +1 -2
- package/dist/unstable/webmcp/webmcp-host.js +1 -2
- package/dist/utils/Primitive.js +1 -2
- package/dist/utils/createActionButton.js +1 -2
- package/dist/utils/getSelectionMessageId.js +1 -2
- package/dist/utils/hooks/useManagedRef.js +1 -2
- package/dist/utils/hooks/useMediaQuery.js +1 -2
- package/dist/utils/hooks/useOnScrollToBottom.js +1 -2
- package/dist/utils/hooks/useSizeHandle.js +1 -2
- package/dist/utils/isCompositionKey.js +1 -2
- package/dist/utils/smooth/SmoothContext.js +1 -2
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +11 -4
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/dist/utils/useIsomorphicLayoutEffect.js +0 -1
- package/package.json +8 -8
- package/src/adapters/GeminiLiveVoiceAdapter.test.ts +992 -0
- package/src/adapters/GeminiLiveVoiceAdapter.ts +454 -0
- package/src/adapters/gemini-live/audio.test.ts +880 -0
- package/src/adapters/gemini-live/audio.ts +333 -0
- package/src/index.test.ts +34 -0
- package/src/index.ts +4 -0
- package/src/mcp-apps/McpAppRenderer.composer.test.tsx +111 -0
- package/src/mcp-apps/McpAppRenderer.test.tsx +24 -23
- package/src/mcp-apps/McpAppRenderer.tsx +9 -1
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.test.tsx +91 -0
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.tsx +54 -0
- package/src/primitives/reasoning/useScrollLock.test.tsx +71 -2
- package/src/primitives/reasoning/useScrollLock.ts +78 -51
- package/src/primitives/threadListItem/ThreadListItemRoot.tsx +4 -1
- package/src/primitives/threadListItem/ThreadListKeyboardNav.test.tsx +25 -0
- package/src/utils/smooth/useSmooth.test.tsx +113 -4
- package/src/utils/smooth/useSmooth.ts +16 -2
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +0 -49
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { createRef, type MouseEvent, type Ref } from "react";
|
|
3
|
+
import { cleanup, fireEvent, render, waitFor } from "@testing-library/react";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { AuiProvider, useAui, useAuiState } from "@assistant-ui/store";
|
|
6
|
+
import { ChainOfThoughtClient } from "@assistant-ui/core/store";
|
|
7
|
+
import { ChainOfThoughtPrimitiveAccordionTrigger } from "./ChainOfThoughtAccordionTrigger";
|
|
8
|
+
|
|
9
|
+
afterEach(cleanup);
|
|
10
|
+
|
|
11
|
+
const Content = () => {
|
|
12
|
+
const collapsed = useAuiState((s) => s.chainOfThought.collapsed);
|
|
13
|
+
return (
|
|
14
|
+
<div data-testid="reasoning" hidden={collapsed}>
|
|
15
|
+
Reasoning details
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const App = ({
|
|
21
|
+
triggerRef,
|
|
22
|
+
...props
|
|
23
|
+
}: ChainOfThoughtPrimitiveAccordionTrigger.Props & {
|
|
24
|
+
triggerRef?: Ref<HTMLButtonElement>;
|
|
25
|
+
}) => {
|
|
26
|
+
const aui = useAui({
|
|
27
|
+
chainOfThought: ChainOfThoughtClient({
|
|
28
|
+
parts: [
|
|
29
|
+
{
|
|
30
|
+
type: "reasoning",
|
|
31
|
+
text: "Reasoning details",
|
|
32
|
+
status: { type: "complete" },
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
getMessagePart: () => {
|
|
36
|
+
throw new Error("Part lookup is not used by the toggle");
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
return (
|
|
41
|
+
<AuiProvider value={aui}>
|
|
42
|
+
<ChainOfThoughtPrimitiveAccordionTrigger {...props} ref={triggerRef} />
|
|
43
|
+
<Content />
|
|
44
|
+
</AuiProvider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("ChainOfThoughtPrimitiveAccordionTrigger", () => {
|
|
49
|
+
it.each(["button", "render", "asChild"] as const)(
|
|
50
|
+
"exposes expansion and forwards the ref with %s",
|
|
51
|
+
async (mode) => {
|
|
52
|
+
const ref = createRef<HTMLButtonElement>();
|
|
53
|
+
const { getByRole, getByTestId } = render(
|
|
54
|
+
<App
|
|
55
|
+
triggerRef={ref}
|
|
56
|
+
{...(mode === "render" ? { render: <button /> } : {})}
|
|
57
|
+
{...(mode === "asChild" ? { asChild: true } : {})}
|
|
58
|
+
>
|
|
59
|
+
{mode === "asChild" ? <button>Thinking</button> : "Thinking"}
|
|
60
|
+
</App>,
|
|
61
|
+
);
|
|
62
|
+
const button = getByRole("button", { name: "Thinking" });
|
|
63
|
+
const content = getByTestId("reasoning");
|
|
64
|
+
expect(ref.current).toBe(button);
|
|
65
|
+
expect(content.hidden).toBe(true);
|
|
66
|
+
expect(button.getAttribute("aria-expanded")).toBe("false");
|
|
67
|
+
fireEvent.click(button);
|
|
68
|
+
await waitFor(() => expect(content.hidden).toBe(false));
|
|
69
|
+
expect(button.getAttribute("aria-expanded")).toBe("true");
|
|
70
|
+
fireEvent.click(button);
|
|
71
|
+
await waitFor(() => expect(content.hidden).toBe(true));
|
|
72
|
+
expect(button.getAttribute("aria-expanded")).toBe("false");
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
it("lets the caller veto expansion", () => {
|
|
77
|
+
const onClick = vi.fn((event: MouseEvent) => event.preventDefault());
|
|
78
|
+
const { getByRole, getByTestId } = render(
|
|
79
|
+
<App onClick={onClick}>Thinking</App>,
|
|
80
|
+
);
|
|
81
|
+
fireEvent.click(getByRole("button"));
|
|
82
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
83
|
+
expect(getByTestId("reasoning").hidden).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("does not expand a disabled trigger", () => {
|
|
87
|
+
const { getByRole, getByTestId } = render(<App disabled>Thinking</App>);
|
|
88
|
+
fireEvent.click(getByRole("button"));
|
|
89
|
+
expect(getByTestId("reasoning").hidden).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ActionButtonElement } from "../../utils/createActionButton";
|
|
4
|
+
import { type ComponentPropsWithoutRef, forwardRef, useCallback } from "react";
|
|
5
|
+
import { useAuiState, useAui } from "@assistant-ui/store";
|
|
6
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
7
|
+
import { Primitive } from "../../utils/Primitive";
|
|
8
|
+
|
|
9
|
+
export namespace ChainOfThoughtPrimitiveAccordionTrigger {
|
|
10
|
+
export type Element = ActionButtonElement;
|
|
11
|
+
/**
|
|
12
|
+
* Props for the ChainOfThoughtPrimitive.AccordionTrigger component.
|
|
13
|
+
* Inherits all button element props and action button functionality.
|
|
14
|
+
*/
|
|
15
|
+
export type Props = ComponentPropsWithoutRef<typeof Primitive.button>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A button component that toggles the collapsed state of the chain of thought accordion.
|
|
20
|
+
*
|
|
21
|
+
* This component automatically handles the toggle functionality, expanding or collapsing
|
|
22
|
+
* the chain of thought parts when clicked.
|
|
23
|
+
* Exposes the current disclosure state through `aria-expanded`.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <ChainOfThoughtPrimitive.AccordionTrigger>
|
|
28
|
+
* Toggle Reasoning
|
|
29
|
+
* </ChainOfThoughtPrimitive.AccordionTrigger>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const ChainOfThoughtPrimitiveAccordionTrigger = forwardRef<
|
|
33
|
+
ChainOfThoughtPrimitiveAccordionTrigger.Element,
|
|
34
|
+
ChainOfThoughtPrimitiveAccordionTrigger.Props
|
|
35
|
+
>(({ onClick, ...props }, ref) => {
|
|
36
|
+
const aui = useAui();
|
|
37
|
+
const collapsed = useAuiState((s) => s.chainOfThought.collapsed);
|
|
38
|
+
const toggle = useCallback(() => {
|
|
39
|
+
aui.chainOfThought.setCollapsed(!collapsed);
|
|
40
|
+
}, [aui, collapsed]);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Primitive.button
|
|
44
|
+
type="button"
|
|
45
|
+
{...props}
|
|
46
|
+
ref={ref}
|
|
47
|
+
aria-expanded={!collapsed}
|
|
48
|
+
onClick={composeEventHandlers(onClick, toggle)}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
ChainOfThoughtPrimitiveAccordionTrigger.displayName =
|
|
54
|
+
"ChainOfThoughtPrimitive.AccordionTrigger";
|
|
@@ -31,9 +31,29 @@ function stubWidths(
|
|
|
31
31
|
function lockWithin(container: HTMLElement) {
|
|
32
32
|
const animated = document.createElement("div");
|
|
33
33
|
container.appendChild(animated);
|
|
34
|
+
return createLock(animated).lock;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createLock(animated: HTMLElement) {
|
|
34
38
|
const ref = { current: animated };
|
|
35
|
-
const { result } = renderHook(() => useScrollLock(ref, 200));
|
|
36
|
-
return result.current;
|
|
39
|
+
const { result, unmount } = renderHook(() => useScrollLock(ref, 200));
|
|
40
|
+
return { lock: result.current, dispose: unmount };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createSharedScroller() {
|
|
44
|
+
const scroller = document.createElement("div");
|
|
45
|
+
scroller.style.overflowY = "auto";
|
|
46
|
+
scroller.style.borderWidth = "0px";
|
|
47
|
+
scroller.style.paddingRight = "4px";
|
|
48
|
+
document.body.appendChild(scroller);
|
|
49
|
+
Object.defineProperty(scroller, "offsetWidth", { value: 306 });
|
|
50
|
+
Object.defineProperty(scroller, "clientWidth", {
|
|
51
|
+
get: () => (scroller.style.scrollbarWidth === "none" ? 306 : 300),
|
|
52
|
+
});
|
|
53
|
+
const first = document.createElement("div");
|
|
54
|
+
const second = document.createElement("div");
|
|
55
|
+
scroller.append(first, second);
|
|
56
|
+
return { scroller, first: createLock(first), second: createLock(second) };
|
|
37
57
|
}
|
|
38
58
|
|
|
39
59
|
describe("useScrollLock", () => {
|
|
@@ -115,6 +135,55 @@ describe("useScrollLock", () => {
|
|
|
115
135
|
expect(scroller.style.scrollbarWidth).toBe("");
|
|
116
136
|
});
|
|
117
137
|
|
|
138
|
+
it("keeps overlapping animations locked until the last one finishes", () => {
|
|
139
|
+
vi.useFakeTimers();
|
|
140
|
+
const { scroller, first, second } = createSharedScroller();
|
|
141
|
+
scroller.scrollTop = 120;
|
|
142
|
+
|
|
143
|
+
first.lock();
|
|
144
|
+
vi.advanceTimersByTime(100);
|
|
145
|
+
second.lock();
|
|
146
|
+
expect(scroller.style.paddingRight).toBe("10px");
|
|
147
|
+
|
|
148
|
+
vi.advanceTimersByTime(100);
|
|
149
|
+
expect(scroller.style.scrollbarWidth).toBe("none");
|
|
150
|
+
expect(scroller.style.paddingRight).toBe("10px");
|
|
151
|
+
scroller.scrollTop = 90;
|
|
152
|
+
scroller.dispatchEvent(new Event("scroll"));
|
|
153
|
+
expect(scroller.scrollTop).toBe(120);
|
|
154
|
+
|
|
155
|
+
vi.advanceTimersByTime(100);
|
|
156
|
+
expect(scroller.style.scrollbarWidth).toBe("");
|
|
157
|
+
expect(scroller.style.paddingRight).toBe("4px");
|
|
158
|
+
scroller.scrollTop = 90;
|
|
159
|
+
scroller.dispatchEvent(new Event("scroll"));
|
|
160
|
+
expect(scroller.scrollTop).toBe(90);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it.each(["first", "second"] as const)(
|
|
164
|
+
"preserves a restarted shared lock when the %s owner is disposed first",
|
|
165
|
+
(disposedFirst) => {
|
|
166
|
+
vi.useFakeTimers();
|
|
167
|
+
const shared = createSharedScroller();
|
|
168
|
+
const { scroller, first, second } = shared;
|
|
169
|
+
first.lock();
|
|
170
|
+
second.lock();
|
|
171
|
+
vi.advanceTimersByTime(100);
|
|
172
|
+
first.lock();
|
|
173
|
+
|
|
174
|
+
shared[disposedFirst].dispose();
|
|
175
|
+
expect(scroller.style.scrollbarWidth).toBe("none");
|
|
176
|
+
expect(scroller.style.paddingRight).toBe("10px");
|
|
177
|
+
|
|
178
|
+
shared[disposedFirst === "first" ? "second" : "first"].dispose();
|
|
179
|
+
expect(scroller.style.scrollbarWidth).toBe("");
|
|
180
|
+
expect(scroller.style.paddingRight).toBe("4px");
|
|
181
|
+
scroller.style.scrollbarWidth = "thin";
|
|
182
|
+
vi.advanceTimersByTime(200);
|
|
183
|
+
expect(scroller.style.scrollbarWidth).toBe("thin");
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
|
|
118
187
|
it("leaves the padding alone when no scrollbar takes space", () => {
|
|
119
188
|
const scroller = document.createElement("div");
|
|
120
189
|
scroller.style.overflowY = "auto";
|
|
@@ -12,6 +12,74 @@ const findScrollableAncestor = (element: HTMLElement | null) => {
|
|
|
12
12
|
return null;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const scrollLocks = new WeakMap<
|
|
16
|
+
HTMLElement,
|
|
17
|
+
{ count: number; restore: () => void }
|
|
18
|
+
>();
|
|
19
|
+
|
|
20
|
+
const acquireScrollLock = (scrollContainer: HTMLElement) => {
|
|
21
|
+
const existing = scrollLocks.get(scrollContainer);
|
|
22
|
+
if (existing) {
|
|
23
|
+
existing.count++;
|
|
24
|
+
return existing;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const scrollPosition = scrollContainer.scrollTop;
|
|
28
|
+
const scrollbarWidth = scrollContainer.style.scrollbarWidth;
|
|
29
|
+
|
|
30
|
+
// Hiding the scrollbar collapses its gutter on classic scrollbars, which
|
|
31
|
+
// shifts centered content horizontally; compensate with padding on the
|
|
32
|
+
// side the scrollbar occupies (the left side in RTL).
|
|
33
|
+
const computed = getComputedStyle(scrollContainer);
|
|
34
|
+
const paddingSide =
|
|
35
|
+
computed.direction === "rtl" ? "paddingLeft" : "paddingRight";
|
|
36
|
+
const previousPadding = scrollContainer.style[paddingSide];
|
|
37
|
+
const elementScrollbarSize =
|
|
38
|
+
scrollContainer.offsetWidth -
|
|
39
|
+
scrollContainer.clientWidth -
|
|
40
|
+
parseFloat(computed.borderLeftWidth) -
|
|
41
|
+
parseFloat(computed.borderRightWidth);
|
|
42
|
+
// A root element's offsetWidth already excludes the viewport scrollbar, so
|
|
43
|
+
// the element formula reports zero once the scroll propagates to the
|
|
44
|
+
// viewport, and only then does the viewport measure apply. A body that
|
|
45
|
+
// scrolls in its own right is measured by the element formula like any
|
|
46
|
+
// other scroller, whether or not the viewport is scrolling too.
|
|
47
|
+
const ownerDocument = scrollContainer.ownerDocument;
|
|
48
|
+
const isRootScroller =
|
|
49
|
+
scrollContainer === ownerDocument.documentElement ||
|
|
50
|
+
scrollContainer === ownerDocument.body;
|
|
51
|
+
const scrollbarSize =
|
|
52
|
+
isRootScroller && elementScrollbarSize <= 0
|
|
53
|
+
? (ownerDocument.defaultView?.innerWidth ?? 0) -
|
|
54
|
+
ownerDocument.documentElement.clientWidth
|
|
55
|
+
: elementScrollbarSize;
|
|
56
|
+
|
|
57
|
+
scrollContainer.style.scrollbarWidth = "none";
|
|
58
|
+
if (scrollbarSize > 0) {
|
|
59
|
+
scrollContainer.style[paddingSide] = `${
|
|
60
|
+
parseFloat(computed[paddingSide]) + scrollbarSize
|
|
61
|
+
}px`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const restoreStyles = () => {
|
|
65
|
+
scrollContainer.style.scrollbarWidth = scrollbarWidth;
|
|
66
|
+
scrollContainer.style[paddingSide] = previousPadding;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const resetPosition = () => (scrollContainer.scrollTop = scrollPosition);
|
|
70
|
+
scrollContainer.addEventListener("scroll", resetPosition);
|
|
71
|
+
|
|
72
|
+
const lock = {
|
|
73
|
+
count: 1,
|
|
74
|
+
restore: () => {
|
|
75
|
+
scrollContainer.removeEventListener("scroll", resetPosition);
|
|
76
|
+
restoreStyles();
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
scrollLocks.set(scrollContainer, lock);
|
|
80
|
+
return lock;
|
|
81
|
+
};
|
|
82
|
+
|
|
15
83
|
/**
|
|
16
84
|
* Locks scroll position during collapsible/height animations and hides scrollbar.
|
|
17
85
|
*
|
|
@@ -59,61 +127,20 @@ export const useScrollLock = <T extends HTMLElement = HTMLElement>(
|
|
|
59
127
|
return;
|
|
60
128
|
}
|
|
61
129
|
|
|
62
|
-
const
|
|
63
|
-
const scrollbarWidth = scrollContainer.style.scrollbarWidth;
|
|
64
|
-
|
|
65
|
-
// Hiding the scrollbar collapses its gutter on classic scrollbars, which
|
|
66
|
-
// shifts centered content horizontally; compensate with padding on the
|
|
67
|
-
// side the scrollbar occupies (the left side in RTL).
|
|
68
|
-
const computed = getComputedStyle(scrollContainer);
|
|
69
|
-
const paddingSide =
|
|
70
|
-
computed.direction === "rtl" ? "paddingLeft" : "paddingRight";
|
|
71
|
-
const previousPadding = scrollContainer.style[paddingSide];
|
|
72
|
-
const elementScrollbarSize =
|
|
73
|
-
scrollContainer.offsetWidth -
|
|
74
|
-
scrollContainer.clientWidth -
|
|
75
|
-
parseFloat(computed.borderLeftWidth) -
|
|
76
|
-
parseFloat(computed.borderRightWidth);
|
|
77
|
-
// A root element's offsetWidth already excludes the viewport scrollbar, so
|
|
78
|
-
// the element formula reports zero once the scroll propagates to the
|
|
79
|
-
// viewport, and only then does the viewport measure apply. A body that
|
|
80
|
-
// scrolls in its own right is measured by the element formula like any
|
|
81
|
-
// other scroller, whether or not the viewport is scrolling too.
|
|
82
|
-
const ownerDocument = scrollContainer.ownerDocument;
|
|
83
|
-
const isRootScroller =
|
|
84
|
-
scrollContainer === ownerDocument.documentElement ||
|
|
85
|
-
scrollContainer === ownerDocument.body;
|
|
86
|
-
const scrollbarSize =
|
|
87
|
-
isRootScroller && elementScrollbarSize <= 0
|
|
88
|
-
? (ownerDocument.defaultView?.innerWidth ?? 0) -
|
|
89
|
-
ownerDocument.documentElement.clientWidth
|
|
90
|
-
: elementScrollbarSize;
|
|
91
|
-
|
|
92
|
-
scrollContainer.style.scrollbarWidth = "none";
|
|
93
|
-
if (scrollbarSize > 0) {
|
|
94
|
-
scrollContainer.style[paddingSide] = `${
|
|
95
|
-
parseFloat(computed[paddingSide]) + scrollbarSize
|
|
96
|
-
}px`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const restoreStyles = () => {
|
|
100
|
-
scrollContainer.style.scrollbarWidth = scrollbarWidth;
|
|
101
|
-
scrollContainer.style[paddingSide] = previousPadding;
|
|
102
|
-
};
|
|
130
|
+
const lock = acquireScrollLock(scrollContainer);
|
|
103
131
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
scrollContainer.removeEventListener("scroll", resetPosition);
|
|
109
|
-
restoreStyles();
|
|
110
|
-
cleanupRef.current = null;
|
|
111
|
-
}, animationDuration);
|
|
132
|
+
const timeoutId = setTimeout(
|
|
133
|
+
() => cleanupRef.current?.(),
|
|
134
|
+
animationDuration,
|
|
135
|
+
);
|
|
112
136
|
|
|
113
137
|
cleanupRef.current = () => {
|
|
114
138
|
clearTimeout(timeoutId);
|
|
115
|
-
|
|
116
|
-
|
|
139
|
+
if (--lock.count === 0) {
|
|
140
|
+
lock.restore();
|
|
141
|
+
scrollLocks.delete(scrollContainer);
|
|
142
|
+
}
|
|
143
|
+
cleanupRef.current = null;
|
|
117
144
|
};
|
|
118
145
|
}, [animationDuration, animatedElementRef]);
|
|
119
146
|
|
|
@@ -51,7 +51,10 @@ export const ThreadListItemPrimitiveRoot = forwardRef<
|
|
|
51
51
|
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
52
52
|
const triggers = getItems()
|
|
53
53
|
.map((item) => item.ref.current)
|
|
54
|
-
.filter(
|
|
54
|
+
.filter(
|
|
55
|
+
(node): node is HTMLButtonElement =>
|
|
56
|
+
node !== null && !node.disabled,
|
|
57
|
+
);
|
|
55
58
|
const next =
|
|
56
59
|
triggers[
|
|
57
60
|
triggers.indexOf(trigger) + (event.key === "ArrowDown" ? 1 : -1)
|
|
@@ -112,6 +112,31 @@ describe("thread list keyboard navigation", () => {
|
|
|
112
112
|
expect(document.activeElement).toBe(first);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
it("skips disabled triggers in both directions without wrapping", () => {
|
|
116
|
+
const { container } = render(
|
|
117
|
+
<ThreadListPrimitiveRoot>
|
|
118
|
+
{[0, 1, 2].map((i) => (
|
|
119
|
+
<ThreadListItemPrimitiveRoot key={i}>
|
|
120
|
+
<ThreadListItemPrimitiveTrigger disabled={i === 1}>
|
|
121
|
+
item {i}
|
|
122
|
+
</ThreadListItemPrimitiveTrigger>
|
|
123
|
+
</ThreadListItemPrimitiveRoot>
|
|
124
|
+
))}
|
|
125
|
+
</ThreadListPrimitiveRoot>,
|
|
126
|
+
);
|
|
127
|
+
const [first, , third] = triggers(container);
|
|
128
|
+
|
|
129
|
+
first!.focus();
|
|
130
|
+
expect(fireEvent.keyDown(first!, { key: "ArrowDown" })).toBe(false);
|
|
131
|
+
expect(document.activeElement).toBe(third);
|
|
132
|
+
expect(fireEvent.keyDown(third!, { key: "ArrowDown" })).toBe(true);
|
|
133
|
+
expect(document.activeElement).toBe(third);
|
|
134
|
+
expect(fireEvent.keyDown(third!, { key: "ArrowUp" })).toBe(false);
|
|
135
|
+
expect(document.activeElement).toBe(first);
|
|
136
|
+
expect(fireEvent.keyDown(first!, { key: "ArrowUp" })).toBe(true);
|
|
137
|
+
expect(document.activeElement).toBe(first);
|
|
138
|
+
});
|
|
139
|
+
|
|
115
140
|
it("moves between an item and its More button with right/left arrows", () => {
|
|
116
141
|
const { trigger, more } = renderItem();
|
|
117
142
|
|
|
@@ -8,12 +8,14 @@ import type {
|
|
|
8
8
|
TextMessagePart,
|
|
9
9
|
} from "@assistant-ui/core";
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
vi.mock("@assistant-ui/store", () => ({
|
|
13
|
-
|
|
11
|
+
const aui = vi.hoisted(() => ({ part: {} }));
|
|
12
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
13
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
14
|
+
useAui: () => aui,
|
|
14
15
|
useAuiState: (selector: () => unknown) => selector(),
|
|
15
16
|
}));
|
|
16
|
-
vi.mock("./SmoothContext", () => ({
|
|
17
|
+
vi.mock("./SmoothContext", async (importOriginal) => ({
|
|
18
|
+
...(await importOriginal<typeof import("./SmoothContext")>()),
|
|
17
19
|
useSmoothStatusStore: () => null,
|
|
18
20
|
}));
|
|
19
21
|
|
|
@@ -163,6 +165,113 @@ describe("useSmooth", () => {
|
|
|
163
165
|
expect(result.current.status.type).toBe("running");
|
|
164
166
|
});
|
|
165
167
|
|
|
168
|
+
it("keeps completed text visible when smoothing is enabled", () => {
|
|
169
|
+
vi.spyOn(performance, "now").mockReturnValue(1000);
|
|
170
|
+
vi.spyOn(globalThis, "requestAnimationFrame").mockReturnValue(1);
|
|
171
|
+
vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {});
|
|
172
|
+
const { result, rerender } = renderHook(
|
|
173
|
+
({ state, smooth }) => useSmooth(state, smooth),
|
|
174
|
+
{ initialProps: { state: textState("Hello"), smooth: false } },
|
|
175
|
+
);
|
|
176
|
+
rerender({ state: runningState("Hello world"), smooth: false });
|
|
177
|
+
rerender({ state: textState("Hello world"), smooth: false });
|
|
178
|
+
expect(result.current.text).toBe("Hello world");
|
|
179
|
+
|
|
180
|
+
rerender({ state: textState("Hello world"), smooth: true });
|
|
181
|
+
expect(result.current.text).toBe("Hello world");
|
|
182
|
+
expect(result.current.status.type).toBe("complete");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("starts a new part from its own cursor when enabling smoothing in the same update", () => {
|
|
186
|
+
let now = 1000;
|
|
187
|
+
let queued: FrameRequestCallback | undefined;
|
|
188
|
+
vi.spyOn(performance, "now").mockImplementation(() => now);
|
|
189
|
+
vi.spyOn(globalThis, "requestAnimationFrame").mockImplementation((cb) => {
|
|
190
|
+
queued = cb;
|
|
191
|
+
return 1;
|
|
192
|
+
});
|
|
193
|
+
vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {
|
|
194
|
+
queued = undefined;
|
|
195
|
+
});
|
|
196
|
+
const { result, rerender } = renderHook(
|
|
197
|
+
({ state, smooth }) => useSmooth(state, smooth),
|
|
198
|
+
{
|
|
199
|
+
initialProps: {
|
|
200
|
+
state: runningState("Hello"),
|
|
201
|
+
smooth: false as boolean | SmoothOptions,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
expect(result.current.text).toBe("Hello");
|
|
206
|
+
|
|
207
|
+
aui.part = {};
|
|
208
|
+
rerender({
|
|
209
|
+
state: runningState("Hello world"),
|
|
210
|
+
smooth: { maxCharsPerFrame: 1 },
|
|
211
|
+
});
|
|
212
|
+
expect(result.current.text).toBe("");
|
|
213
|
+
act(() => {
|
|
214
|
+
now += 16;
|
|
215
|
+
const callback = queued;
|
|
216
|
+
queued = undefined;
|
|
217
|
+
callback?.(now);
|
|
218
|
+
});
|
|
219
|
+
expect(result.current.text).toBe("H");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it.each([false, true])(
|
|
223
|
+
"animates only new text after reduced motion is disabled, with a part switch: %s",
|
|
224
|
+
(switchPart) => {
|
|
225
|
+
let now = 1000;
|
|
226
|
+
let queued: FrameRequestCallback | undefined;
|
|
227
|
+
vi.spyOn(performance, "now").mockImplementation(() => now);
|
|
228
|
+
vi.spyOn(globalThis, "requestAnimationFrame").mockImplementation((cb) => {
|
|
229
|
+
queued = cb;
|
|
230
|
+
return 1;
|
|
231
|
+
});
|
|
232
|
+
vi.spyOn(globalThis, "cancelAnimationFrame").mockImplementation(() => {
|
|
233
|
+
queued = undefined;
|
|
234
|
+
});
|
|
235
|
+
const listeners = new Set<() => void>();
|
|
236
|
+
const media = {
|
|
237
|
+
matches: true,
|
|
238
|
+
addEventListener: (_event: string, listener: () => void) =>
|
|
239
|
+
listeners.add(listener),
|
|
240
|
+
removeEventListener: (_event: string, listener: () => void) =>
|
|
241
|
+
listeners.delete(listener),
|
|
242
|
+
};
|
|
243
|
+
Object.defineProperty(window, "matchMedia", {
|
|
244
|
+
configurable: true,
|
|
245
|
+
value: () => media,
|
|
246
|
+
});
|
|
247
|
+
const { result, rerender } = renderHook(
|
|
248
|
+
(state) => useSmooth(state, { maxCharsPerFrame: 1 }),
|
|
249
|
+
{ initialProps: runningState("Hello") },
|
|
250
|
+
);
|
|
251
|
+
if (switchPart) aui.part = {};
|
|
252
|
+
rerender(runningState("Hello world"));
|
|
253
|
+
expect(result.current.text).toBe("Hello world");
|
|
254
|
+
|
|
255
|
+
act(() => {
|
|
256
|
+
media.matches = false;
|
|
257
|
+
for (const listener of listeners) listener();
|
|
258
|
+
});
|
|
259
|
+
expect(result.current.text).toBe("Hello world");
|
|
260
|
+
|
|
261
|
+
rerender(runningState("Hello world!"));
|
|
262
|
+
expect(result.current.text).toBe("Hello world");
|
|
263
|
+
act(() => {
|
|
264
|
+
now += 16;
|
|
265
|
+
const callback = queued;
|
|
266
|
+
queued = undefined;
|
|
267
|
+
callback?.(now);
|
|
268
|
+
});
|
|
269
|
+
expect(result.current.text).toBe("Hello world!");
|
|
270
|
+
rerender(textState("Hello world!"));
|
|
271
|
+
expect(result.current.status.type).toBe("complete");
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
166
275
|
it.each([
|
|
167
276
|
{ name: "negative drain time", options: { drainMs: -1 } },
|
|
168
277
|
{ name: "NaN character interval", options: { maxCharIntervalMs: NaN } },
|
|
@@ -217,6 +217,17 @@ export const useSmooth = (
|
|
|
217
217
|
const [animatorRef] = useState(
|
|
218
218
|
() => new TextStreamAnimator(displayedText, setText),
|
|
219
219
|
);
|
|
220
|
+
const [prevEnabled, setPrevEnabled] = useState(enabled);
|
|
221
|
+
if (enabled !== prevEnabled) {
|
|
222
|
+
setPrevEnabled(enabled);
|
|
223
|
+
if (
|
|
224
|
+
enabled &&
|
|
225
|
+
part === prevPart &&
|
|
226
|
+
text.startsWith(animatorRef.currentText)
|
|
227
|
+
) {
|
|
228
|
+
setDisplayedText(animatorRef.currentText);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
220
231
|
|
|
221
232
|
useEffect(() => {
|
|
222
233
|
animatorRef.drainMs = drainMs;
|
|
@@ -227,13 +238,16 @@ export const useSmooth = (
|
|
|
227
238
|
|
|
228
239
|
const animatorPartRef = useRef(part);
|
|
229
240
|
useEffect(() => {
|
|
241
|
+
const partChanged = animatorPartRef.current !== part;
|
|
242
|
+
animatorPartRef.current = part;
|
|
230
243
|
if (!enabled) {
|
|
231
244
|
animatorRef.stop();
|
|
245
|
+
animatorRef.currentText = text;
|
|
246
|
+
animatorRef.targetText = text;
|
|
247
|
+
animatorRef.lastCommitTime = -Infinity;
|
|
232
248
|
return;
|
|
233
249
|
}
|
|
234
250
|
|
|
235
|
-
const partChanged = animatorPartRef.current !== part;
|
|
236
|
-
animatorPartRef.current = part;
|
|
237
251
|
// A new part whose text shares a prefix with the previous target would
|
|
238
252
|
// keep the stale cursor and flicker without this reset.
|
|
239
253
|
if (partChanged || !text.startsWith(animatorRef.targetText)) {
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type ActionButtonElement,
|
|
5
|
-
type ActionButtonProps,
|
|
6
|
-
createActionButton,
|
|
7
|
-
} from "../../utils/createActionButton";
|
|
8
|
-
import { useCallback } from "react";
|
|
9
|
-
import { useAuiState, useAui } from "@assistant-ui/store";
|
|
10
|
-
|
|
11
|
-
const useChainOfThoughtAccordionTrigger = () => {
|
|
12
|
-
const aui = useAui();
|
|
13
|
-
const collapsed = useAuiState((s) => s.chainOfThought.collapsed);
|
|
14
|
-
|
|
15
|
-
const callback = useCallback(() => {
|
|
16
|
-
aui.chainOfThought.setCollapsed(!collapsed);
|
|
17
|
-
}, [aui, collapsed]);
|
|
18
|
-
|
|
19
|
-
return callback;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export namespace ChainOfThoughtPrimitiveAccordionTrigger {
|
|
23
|
-
export type Element = ActionButtonElement;
|
|
24
|
-
/**
|
|
25
|
-
* Props for the ChainOfThoughtPrimitive.AccordionTrigger component.
|
|
26
|
-
* Inherits all button element props and action button functionality.
|
|
27
|
-
*/
|
|
28
|
-
export type Props = ActionButtonProps<
|
|
29
|
-
typeof useChainOfThoughtAccordionTrigger
|
|
30
|
-
>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A button component that toggles the collapsed state of the chain of thought accordion.
|
|
35
|
-
*
|
|
36
|
-
* This component automatically handles the toggle functionality, expanding or collapsing
|
|
37
|
-
* the chain of thought parts when clicked.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```tsx
|
|
41
|
-
* <ChainOfThoughtPrimitive.AccordionTrigger>
|
|
42
|
-
* Toggle Reasoning
|
|
43
|
-
* </ChainOfThoughtPrimitive.AccordionTrigger>
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
export const ChainOfThoughtPrimitiveAccordionTrigger = createActionButton(
|
|
47
|
-
"ChainOfThoughtPrimitive.AccordionTrigger",
|
|
48
|
-
useChainOfThoughtAccordionTrigger,
|
|
49
|
-
);
|