@assistant-ui/react-ink 0.0.13 → 0.0.16
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 +45 -52
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives/internal/MemoMessage.d.ts +14 -0
- package/dist/primitives/internal/MemoMessage.d.ts.map +1 -0
- package/dist/primitives/internal/MemoMessage.js +14 -0
- package/dist/primitives/internal/MemoMessage.js.map +1 -0
- package/dist/primitives/thread/ThreadMessages.d.ts +18 -3
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +58 -16
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/package.json +12 -11
- package/src/index.ts +7 -0
- package/src/primitives/internal/MemoMessage.tsx +35 -0
- package/src/primitives/thread/ThreadMessages.test.tsx +220 -0
- package/src/primitives/thread/ThreadMessages.tsx +120 -29
package/README.md
CHANGED
|
@@ -1,74 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@assistant-ui/react-ink`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@assistant-ui/react-ink)
|
|
4
|
+
[](https://www.npmjs.com/package/@assistant-ui/react-ink)
|
|
5
|
+
[](https://github.com/assistant-ui/assistant-ui)
|
|
6
|
+

|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
[Ink](https://github.com/vadimdemedes/ink) bindings for assistant-ui. Composable, unstyled terminal primitives that share the same runtime, adapters, and tools as `@assistant-ui/react`, so you can ship a CLI chat UI without rewriting your backend code.
|
|
6
9
|
|
|
7
10
|
## Installation
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
Requires React 19 and ink 6 or newer.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
10
15
|
npm install @assistant-ui/react-ink ink react
|
|
11
16
|
```
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
For markdown rendering with syntax highlighting, also install [`@assistant-ui/react-ink-markdown`](https://www.npmjs.com/package/@assistant-ui/react-ink-markdown).
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
14
21
|
|
|
15
22
|
```tsx
|
|
16
|
-
import { render } from "ink";
|
|
17
|
-
import { Box, Text } from "ink";
|
|
23
|
+
import { render, Box, Text } from "ink";
|
|
18
24
|
import {
|
|
19
|
-
|
|
25
|
+
AssistantRuntimeProvider,
|
|
20
26
|
useLocalRuntime,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
ThreadPrimitive,
|
|
28
|
+
ComposerPrimitive,
|
|
29
|
+
useAuiState,
|
|
24
30
|
type ChatModelAdapter,
|
|
25
31
|
} from "@assistant-ui/react-ink";
|
|
26
32
|
|
|
27
|
-
const
|
|
33
|
+
const adapter: ChatModelAdapter = {
|
|
28
34
|
async *run({ messages }) {
|
|
29
|
-
// your AI backend here
|
|
30
35
|
yield { content: [{ type: "text", text: "Hello from AI!" }] };
|
|
31
36
|
},
|
|
32
37
|
};
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
const
|
|
39
|
+
const Message = () => {
|
|
40
|
+
const message = useAuiState((s) => s.message);
|
|
41
|
+
const text = message.content.find((p) => p.type === "text")?.text ?? "";
|
|
42
|
+
return (
|
|
43
|
+
<Box marginBottom={1}>
|
|
44
|
+
<Text color={message.role === "user" ? "green" : "blue"}>
|
|
45
|
+
{message.role === "user" ? "You: " : "AI: "}
|
|
46
|
+
</Text>
|
|
47
|
+
<Text>{text}</Text>
|
|
48
|
+
</Box>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
36
51
|
|
|
52
|
+
const App = () => {
|
|
53
|
+
const runtime = useLocalRuntime(adapter);
|
|
37
54
|
return (
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.join("")}
|
|
48
|
-
</Text>
|
|
49
|
-
</Box>
|
|
50
|
-
)}
|
|
51
|
-
/>
|
|
52
|
-
<ComposerInput submitOnEnter placeholder="Message..." autoFocus />
|
|
53
|
-
</ThreadRoot>
|
|
54
|
-
</AssistantProvider>
|
|
55
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
56
|
+
<ThreadPrimitive.Root>
|
|
57
|
+
<ThreadPrimitive.Messages>{() => <Message />}</ThreadPrimitive.Messages>
|
|
58
|
+
<Box borderStyle="round" paddingX={1}>
|
|
59
|
+
<Text>{"> "}</Text>
|
|
60
|
+
<ComposerPrimitive.Input submitOnEnter placeholder="Message..." autoFocus />
|
|
61
|
+
</Box>
|
|
62
|
+
</ThreadPrimitive.Root>
|
|
63
|
+
</AssistantRuntimeProvider>
|
|
55
64
|
);
|
|
56
|
-
}
|
|
65
|
+
};
|
|
57
66
|
|
|
58
67
|
render(<App />);
|
|
59
68
|
```
|
|
60
69
|
|
|
61
|
-
## Features
|
|
62
|
-
|
|
63
|
-
- Composable, unstyled primitives (Thread, Composer, Message, ActionBar, etc.)
|
|
64
|
-
- Streaming responses with real-time updates
|
|
65
|
-
- Tool call support with built-in ToolFallback component
|
|
66
|
-
- Diff rendering with DiffPrimitive components and DiffView for unified diffs and file comparisons in the terminal
|
|
67
|
-
- Message branching and editing
|
|
68
|
-
- Multi-thread support with thread list management
|
|
69
|
-
- Markdown rendering via `@assistant-ui/react-ink-markdown`
|
|
70
|
-
- Same runtime as `@assistant-ui/react` — share adapters, tools, and backend code
|
|
71
|
-
|
|
72
70
|
## Documentation
|
|
73
71
|
|
|
74
72
|
- [Getting Started](https://www.assistant-ui.com/docs/ink)
|
|
@@ -76,12 +74,7 @@ render(<App />);
|
|
|
76
74
|
- [Primitives](https://www.assistant-ui.com/docs/ink/primitives)
|
|
77
75
|
- [Hooks](https://www.assistant-ui.com/docs/ink/hooks)
|
|
78
76
|
|
|
79
|
-
##
|
|
80
|
-
|
|
81
|
-
- [`@assistant-ui/react-ink-markdown`](https://www.npmjs.com/package/@assistant-ui/react-ink-markdown) — Terminal markdown rendering with syntax highlighting
|
|
82
|
-
- [`@assistant-ui/react`](https://www.npmjs.com/package/@assistant-ui/react) — Web (React) bindings
|
|
83
|
-
- [`@assistant-ui/react-native`](https://www.npmjs.com/package/@assistant-ui/react-native) — React Native bindings
|
|
84
|
-
|
|
85
|
-
## License
|
|
77
|
+
## For other platforms
|
|
86
78
|
|
|
87
|
-
|
|
79
|
+
- Web: [`@assistant-ui/react`](https://www.npmjs.com/package/@assistant-ui/react)
|
|
80
|
+
- React Native: [`@assistant-ui/react-native`](https://www.npmjs.com/package/@assistant-ui/react-native)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/react" />
|
|
2
|
-
export type { ThreadMessage, ThreadUserMessage, ThreadAssistantMessage, ThreadSystemMessage, MessageStatus, MessageRole, ThreadMessageLike, AppendMessage, RunConfig, TextMessagePart, ReasoningMessagePart, SourceMessagePart, ToolCallMessagePart, ImageMessagePart, FileMessagePart, DataMessagePart, Unstable_AudioMessagePart, ThreadUserMessagePart, ThreadAssistantMessagePart, AssistantRuntime, ThreadRuntime, MessageRuntime, ThreadComposerRuntime, EditComposerRuntime, ComposerRuntime, ThreadListRuntime, ThreadListItemRuntime, ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult, RuntimeCapabilities, Attachment, PendingAttachment, CreateAttachment, AttachmentRuntime, AttachmentAdapter, ThreadHistoryAdapter, FeedbackAdapter, RealtimeVoiceAdapter, VoiceSessionControls, VoiceSessionHelpers, SuggestionAdapter, Unsubscribe, } from "@assistant-ui/core";
|
|
2
|
+
export type { ThreadMessage, ThreadUserMessage, ThreadAssistantMessage, ThreadSystemMessage, MessageStatus, MessageRole, ThreadMessageLike, AppendMessage, RunConfig, TextMessagePart, ReasoningMessagePart, SourceMessagePart, ToolCallMessagePart, ToolModelContentPart, ImageMessagePart, FileMessagePart, DataMessagePart, Unstable_AudioMessagePart, ThreadUserMessagePart, ThreadAssistantMessagePart, AssistantRuntime, ThreadRuntime, MessageRuntime, ThreadComposerRuntime, EditComposerRuntime, ComposerRuntime, ThreadListRuntime, ThreadListItemRuntime, ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult, RuntimeCapabilities, Attachment, PendingAttachment, CompleteAttachment, CreateAttachment, AttachmentRuntime, AttachmentAdapter, ThreadHistoryAdapter, FeedbackAdapter, RealtimeVoiceAdapter, VoiceSessionControls, VoiceSessionHelpers, SuggestionAdapter, Unsubscribe, } from "@assistant-ui/core";
|
|
3
3
|
export type { RemoteThreadListAdapter, RemoteThreadListOptions, } from "@assistant-ui/core";
|
|
4
4
|
export { InMemoryThreadListAdapter } from "@assistant-ui/core";
|
|
5
5
|
export { createVoiceSession } from "@assistant-ui/core";
|
|
@@ -7,6 +7,7 @@ export { SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, CompositeAtt
|
|
|
7
7
|
export type { ThreadState, ThreadsState, MessageState, ComposerState, AttachmentState, ThreadListItemState, } from "@assistant-ui/core/store";
|
|
8
8
|
export { useAui, useAuiState, useAuiEvent, AuiProvider, AuiIf, type AssistantClient, type AssistantState, type AssistantEventScope, type AssistantEventSelector, type AssistantEventName, type AssistantEventPayload, type AssistantEventCallback, } from "@assistant-ui/store";
|
|
9
9
|
export { AssistantRuntimeProvider } from "./context/AssistantContext.js";
|
|
10
|
+
export { RuntimeAdapterProvider, useRuntimeAdapters, type RuntimeAdapters, } from "@assistant-ui/core/react";
|
|
10
11
|
export { useLocalRuntime, type LocalRuntimeOptions, } from "./runtimes/useLocalRuntime.js";
|
|
11
12
|
export { useRemoteThreadListRuntime } from "./runtimes/useRemoteThreadListRuntime.js";
|
|
12
13
|
export * as ThreadPrimitive from "./primitives/thread.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAEV,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,SAAS,EAET,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,yBAAyB,EACzB,qBAAqB,EACrB,0BAA0B,EAE1B,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EAErB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EAEnB,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EAEjB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EAEjB,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EACf,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,wBAAwB,EAAE,sCAAmC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, CompositeAtt
|
|
|
7
7
|
export { useAui, useAuiState, useAuiEvent, AuiProvider, AuiIf, } from "@assistant-ui/store";
|
|
8
8
|
// Context providers
|
|
9
9
|
export { AssistantRuntimeProvider } from "./context/AssistantContext.js";
|
|
10
|
+
export { RuntimeAdapterProvider, useRuntimeAdapters, } from "@assistant-ui/core/react";
|
|
10
11
|
// Runtime
|
|
11
12
|
export { useLocalRuntime, } from "./runtimes/useLocalRuntime.js";
|
|
12
13
|
export { useRemoteThreadListRuntime } from "./runtimes/useRemoteThreadListRuntime.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AA+DlD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,qCAAqC;AACrC,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAY5B,6BAA6B;AAC7B,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,GAQN,MAAM,qBAAqB,CAAC;AAE7B,oBAAoB;AACpB,OAAO,EAAE,wBAAwB,EAAE,sCAAmC;AACtE,OAAO,EACL,sBAAsB,EACtB,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAElC,UAAU;AACV,OAAO,EACL,eAAe,GAEhB,sCAAmC;AACpC,OAAO,EAAE,0BAA0B,EAAE,iDAA8C;AAEnF,aAAa;AACb,OAAO,KAAK,eAAe,+BAA4B;AACvD,OAAO,KAAK,iBAAiB,iCAA8B;AAC3D,OAAO,KAAK,gBAAgB,gCAA6B;AACzD,OAAO,KAAK,mBAAmB,mCAAgC;AAC/D,OAAO,KAAK,kBAAkB,kCAA+B;AAC7D,OAAO,KAAK,qBAAqB,qCAAkC;AACnE,OAAO,KAAK,mBAAmB,mCAAgC;AAC/D,OAAO,KAAK,uBAAuB,uCAAoC;AACvE,OAAO,KAAK,uBAAuB,uCAAoC;AACvE,OAAO,KAAK,mBAAmB,mCAAgC;AAC/D,OAAO,KAAK,iBAAiB,iCAA8B;AAC3D,OAAO,KAAK,cAAc,oCAA2B;AACrD,OAAO,KAAK,aAAa,6BAA0B;AACnD,OAAO,EAAE,QAAQ,EAAsB,sCAAmC;AAE1E,6CAA6C;AAC7C,OAAO,EACL,6BAA6B,EAC7B,+BAA+B,EAC/B,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,iCAAiC,EACjC,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAElC,iCAAiC;AACjC,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EAEnB,mBAAmB,EAEnB,gBAAgB,EAEhB,kBAAkB,EAElB,kBAAkB,EAElB,wBAAwB,EACxB,mBAAmB,EAEnB,eAAe,EAGf,KAAK,EACL,aAAa,EACb,aAAa,EACb,wBAAwB,EAExB,oBAAoB,EACpB,iBAAiB,GAElB,MAAM,0BAA0B,CAAC;AAOlC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAyB,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAO1D,iBAAiB;AACjB,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AA0BhE,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ThreadMessage } from "@assistant-ui/core";
|
|
3
|
+
type MemoMessageProps = {
|
|
4
|
+
index: number;
|
|
5
|
+
render: (value: {
|
|
6
|
+
message: ThreadMessage;
|
|
7
|
+
}) => ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare const MemoMessage: import("react").MemoExoticComponent<{
|
|
10
|
+
({ index, render }: MemoMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
displayName: string;
|
|
12
|
+
}>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=MemoMessage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemoMessage.d.ts","sourceRoot":"","sources":["../../../src/primitives/internal/MemoMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAQ,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,aAAa,CAAA;KAAE,KAAK,SAAS,CAAC;CAC1D,CAAC;AAsBF,eAAO,MAAM,WAAW;wBApBoB,gBAAgB;;EAwB3D,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { RenderChildrenWithAccessor } from "@assistant-ui/store";
|
|
4
|
+
import { MessageByIndexProvider } from "@assistant-ui/core/react";
|
|
5
|
+
const MemoMessageImpl = ({ index, render }) => {
|
|
6
|
+
return (_jsx(MessageByIndexProvider, { index: index, children: _jsx(RenderChildrenWithAccessor, { getItemState: (aui) => aui.thread().message({ index }).getState(), children: (getItem) => render({
|
|
7
|
+
get message() {
|
|
8
|
+
return getItem();
|
|
9
|
+
},
|
|
10
|
+
}) }) }));
|
|
11
|
+
};
|
|
12
|
+
MemoMessageImpl.displayName = "ThreadPrimitive.Messages.MemoItem";
|
|
13
|
+
export const MemoMessage = memo(MemoMessageImpl, (prev, next) => prev.index === next.index && Object.is(prev.render, next.render));
|
|
14
|
+
//# sourceMappingURL=MemoMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemoMessage.js","sourceRoot":"","sources":["../../../src/primitives/internal/MemoMessage.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAkB,IAAI,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAOlE,MAAM,eAAe,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAoB,EAAE,EAAE;IAC9D,OAAO,CACL,KAAC,sBAAsB,IAAC,KAAK,EAAE,KAAK,YAClC,KAAC,0BAA0B,IACzB,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,YAEhE,CAAC,OAAO,EAAE,EAAE,CACX,MAAM,CAAC;gBACL,IAAI,OAAO;oBACT,OAAO,OAAO,EAAE,CAAC;gBACnB,CAAC;aACF,CAAC,GAEuB,GACN,CAC1B,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,CAAC,WAAW,GAAG,mCAAmC,CAAC;AAElE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAC7B,eAAe,EACf,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CACb,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CACnE,CAAC"}
|
|
@@ -19,15 +19,30 @@ type MessageComponents = {
|
|
|
19
19
|
AssistantMessage: ComponentType;
|
|
20
20
|
SystemMessage?: ComponentType | undefined;
|
|
21
21
|
};
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Live render region keeps the last `windowSize + windowOverscan` messages;
|
|
24
|
+
* older messages graduate through Ink's `<Static>` into terminal scrollback
|
|
25
|
+
* and stop repainting. Defaults to no windowing.
|
|
26
|
+
*
|
|
27
|
+
* Per-message memoization only engages when the render callback is
|
|
28
|
+
* referentially stable. The `components` API handles stability internally;
|
|
29
|
+
* with the children render-fn API, hoist or memoize the function.
|
|
30
|
+
*/
|
|
31
|
+
type WindowingProps = {
|
|
32
|
+
/** Recent messages kept live. Unset renders all dynamically. Negative clamped to 0. */
|
|
33
|
+
windowSize?: number | undefined;
|
|
34
|
+
/** Extra live messages above the window to absorb boundary churn. Defaults to 4. Negative clamped to 0. */
|
|
35
|
+
windowOverscan?: number | undefined;
|
|
36
|
+
};
|
|
37
|
+
export type ThreadMessagesProps = ({
|
|
23
38
|
components: MessageComponents;
|
|
24
39
|
children?: never;
|
|
25
|
-
} | {
|
|
40
|
+
} & WindowingProps) | ({
|
|
26
41
|
children: (value: {
|
|
27
42
|
message: ThreadMessage;
|
|
28
43
|
}) => ReactNode;
|
|
29
44
|
components?: never;
|
|
30
|
-
};
|
|
45
|
+
} & WindowingProps);
|
|
31
46
|
export declare const ThreadMessages: FC<ThreadMessagesProps>;
|
|
32
47
|
export {};
|
|
33
48
|
//# sourceMappingURL=ThreadMessages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadMessages.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadMessages.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ThreadMessages.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadMessages.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,EAAE,EACP,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,KAAK,iBAAiB,GAClB;IACE,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACzC,gBAAgB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC7C,qBAAqB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAClD,kBAAkB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC/C,WAAW,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC,gBAAgB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC7C,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC3C,GACD;IACE,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACzC,gBAAgB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC7C,qBAAqB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAClD,kBAAkB,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC/C,WAAW,EAAE,aAAa,CAAC;IAC3B,gBAAgB,EAAE,aAAa,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEN;;;;;;;;GAQG;AACH,KAAK,cAAc,GAAG;IACpB,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,2GAA2G;IAC3G,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,CAAC;IACC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,GAAG,cAAc,CAAC,GACnB,CAAC;IACC,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,aAAa,CAAA;KAAE,KAAK,SAAS,CAAC;IAC3D,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB,GAAG,cAAc,CAAC,CAAC;AA8GxB,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAqElD,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { Box } from "ink";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, } from "react";
|
|
3
|
+
import { Box, Static } from "ink";
|
|
4
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
5
|
+
import { MemoMessage } from "../internal/MemoMessage.js";
|
|
6
6
|
const DEFAULT_SYSTEM_MESSAGE = () => null;
|
|
7
7
|
const getComponent = (components, role, isEditing) => {
|
|
8
8
|
switch (role) {
|
|
@@ -51,22 +51,64 @@ const ThreadMessageComponent = ({ components, }) => {
|
|
|
51
51
|
const Component = getComponent(components, role, isEditing);
|
|
52
52
|
return _jsx(Component, {});
|
|
53
53
|
};
|
|
54
|
-
const ThreadMessagesInner = ({ children }) => {
|
|
54
|
+
const ThreadMessagesInner = ({ children, windowSize, windowOverscan = 4 }) => {
|
|
55
55
|
const messagesLength = useAuiState((s) => s.thread.messages.length);
|
|
56
|
-
|
|
56
|
+
const tailStart = windowSize !== undefined
|
|
57
|
+
? Math.max(0, messagesLength -
|
|
58
|
+
Math.max(0, windowSize) -
|
|
59
|
+
Math.max(0, windowOverscan))
|
|
60
|
+
: 0;
|
|
61
|
+
const prefixIndices = useMemo(() => Array.from({ length: tailStart }, (_, i) => i), [tailStart]);
|
|
62
|
+
const tail = useMemo(() => {
|
|
57
63
|
if (messagesLength === 0)
|
|
58
64
|
return null;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}, [messagesLength, children]);
|
|
65
|
+
const items = [];
|
|
66
|
+
for (let index = tailStart; index < messagesLength; index++) {
|
|
67
|
+
items.push(_jsx(MemoMessage, { index: index, render: children }, index));
|
|
68
|
+
}
|
|
69
|
+
return items;
|
|
70
|
+
}, [messagesLength, tailStart, children]);
|
|
71
|
+
if (tailStart === 0)
|
|
72
|
+
return tail;
|
|
73
|
+
return (_jsxs(_Fragment, { children: [_jsx(Static, { items: prefixIndices, children: (index) => _jsx(MemoMessage, { index: index, render: children }, index) }), tail] }));
|
|
65
74
|
};
|
|
66
|
-
export const ThreadMessages = ({ components, children, }) => {
|
|
75
|
+
export const ThreadMessages = ({ components, children, windowSize, windowOverscan, }) => {
|
|
76
|
+
const Message = components?.Message;
|
|
77
|
+
const EditComposer = components?.EditComposer;
|
|
78
|
+
const UserEditComposer = components?.UserEditComposer;
|
|
79
|
+
const AssistantEditComposer = components?.AssistantEditComposer;
|
|
80
|
+
const SystemEditComposer = components?.SystemEditComposer;
|
|
81
|
+
const UserMessage = components?.UserMessage;
|
|
82
|
+
const AssistantMessage = components?.AssistantMessage;
|
|
83
|
+
const SystemMessage = components?.SystemMessage;
|
|
84
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `components` excluded so inline literals do not bust the memo; per-field deps cover real changes.
|
|
85
|
+
const stableComponents = useMemo(() => {
|
|
86
|
+
if (!components)
|
|
87
|
+
return undefined;
|
|
88
|
+
return {
|
|
89
|
+
Message,
|
|
90
|
+
EditComposer,
|
|
91
|
+
UserEditComposer,
|
|
92
|
+
AssistantEditComposer,
|
|
93
|
+
SystemEditComposer,
|
|
94
|
+
UserMessage,
|
|
95
|
+
AssistantMessage,
|
|
96
|
+
SystemMessage,
|
|
97
|
+
};
|
|
98
|
+
}, [
|
|
99
|
+
Message,
|
|
100
|
+
EditComposer,
|
|
101
|
+
UserEditComposer,
|
|
102
|
+
AssistantEditComposer,
|
|
103
|
+
SystemEditComposer,
|
|
104
|
+
UserMessage,
|
|
105
|
+
AssistantMessage,
|
|
106
|
+
SystemMessage,
|
|
107
|
+
]);
|
|
108
|
+
const renderFromComponents = useCallback(() => stableComponents ? (_jsx(ThreadMessageComponent, { components: stableComponents })) : null, [stableComponents]);
|
|
67
109
|
if (components) {
|
|
68
|
-
return (_jsx(Box, { flexDirection: "column", children: _jsx(ThreadMessagesInner, {
|
|
110
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(ThreadMessagesInner, { windowSize: windowSize, windowOverscan: windowOverscan, children: renderFromComponents }) }));
|
|
69
111
|
}
|
|
70
|
-
return (_jsx(Box, { flexDirection: "column", children: _jsx(ThreadMessagesInner, { children: children }) }));
|
|
112
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(ThreadMessagesInner, { windowSize: windowSize, windowOverscan: windowOverscan, children: children }) }));
|
|
71
113
|
};
|
|
72
114
|
//# sourceMappingURL=ThreadMessages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadMessages.js","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadMessages.tsx"],"names":[],"mappings":";AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ThreadMessages.js","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadMessages.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-ink",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "React Ink (terminal UI) bindings for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
],
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@assistant-ui/core": "^0.1
|
|
36
|
-
"@assistant-ui/store": "^0.2.
|
|
37
|
-
"@assistant-ui/tap": "^0.5.
|
|
38
|
-
"assistant-stream": "^0.3.
|
|
35
|
+
"@assistant-ui/core": "^0.2.1",
|
|
36
|
+
"@assistant-ui/store": "^0.2.10",
|
|
37
|
+
"@assistant-ui/tap": "^0.5.11",
|
|
38
|
+
"assistant-stream": "^0.3.14",
|
|
39
39
|
"diff": "^9.0.0",
|
|
40
40
|
"ink-spinner": "^5.0.0",
|
|
41
41
|
"parse-diff": "^0.12.0",
|
|
42
|
-
"zustand": "^5.0.
|
|
42
|
+
"zustand": "^5.0.13"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@types/react": "*",
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/react": "^19.2.14",
|
|
56
|
-
"ink": "^7.0.
|
|
56
|
+
"ink": "^7.0.2",
|
|
57
57
|
"ink-testing-library": "^4.0.0",
|
|
58
58
|
"react": "^19.2.5",
|
|
59
|
+
"tsx": "^4.21.0",
|
|
59
60
|
"vitest": "^4.1.5",
|
|
60
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
61
|
+
"@assistant-ui/x-buildutils": "0.0.7"
|
|
61
62
|
},
|
|
62
63
|
"publishConfig": {
|
|
63
|
-
"access": "public"
|
|
64
|
-
"provenance": true
|
|
64
|
+
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"homepage": "https://www.assistant-ui.com/",
|
|
67
67
|
"repository": {
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "aui-build",
|
|
77
77
|
"test": "vitest run",
|
|
78
|
-
"test:watch": "vitest"
|
|
78
|
+
"test:watch": "vitest",
|
|
79
|
+
"benchmark": "tsx benchmarks/run.ts"
|
|
79
80
|
}
|
|
80
81
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type {
|
|
|
17
17
|
ReasoningMessagePart,
|
|
18
18
|
SourceMessagePart,
|
|
19
19
|
ToolCallMessagePart,
|
|
20
|
+
ToolModelContentPart,
|
|
20
21
|
ImageMessagePart,
|
|
21
22
|
FileMessagePart,
|
|
22
23
|
DataMessagePart,
|
|
@@ -40,6 +41,7 @@ export type {
|
|
|
40
41
|
// Attachment types
|
|
41
42
|
Attachment,
|
|
42
43
|
PendingAttachment,
|
|
44
|
+
CompleteAttachment,
|
|
43
45
|
CreateAttachment,
|
|
44
46
|
AttachmentRuntime,
|
|
45
47
|
// Adapter types
|
|
@@ -97,6 +99,11 @@ export {
|
|
|
97
99
|
|
|
98
100
|
// Context providers
|
|
99
101
|
export { AssistantRuntimeProvider } from "./context/AssistantContext";
|
|
102
|
+
export {
|
|
103
|
+
RuntimeAdapterProvider,
|
|
104
|
+
useRuntimeAdapters,
|
|
105
|
+
type RuntimeAdapters,
|
|
106
|
+
} from "@assistant-ui/core/react";
|
|
100
107
|
|
|
101
108
|
// Runtime
|
|
102
109
|
export {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ReactNode, memo } from "react";
|
|
2
|
+
import type { ThreadMessage } from "@assistant-ui/core";
|
|
3
|
+
import { RenderChildrenWithAccessor } from "@assistant-ui/store";
|
|
4
|
+
import { MessageByIndexProvider } from "@assistant-ui/core/react";
|
|
5
|
+
|
|
6
|
+
type MemoMessageProps = {
|
|
7
|
+
index: number;
|
|
8
|
+
render: (value: { message: ThreadMessage }) => ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const MemoMessageImpl = ({ index, render }: MemoMessageProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<MessageByIndexProvider index={index}>
|
|
14
|
+
<RenderChildrenWithAccessor
|
|
15
|
+
getItemState={(aui) => aui.thread().message({ index }).getState()}
|
|
16
|
+
>
|
|
17
|
+
{(getItem) =>
|
|
18
|
+
render({
|
|
19
|
+
get message() {
|
|
20
|
+
return getItem();
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
</RenderChildrenWithAccessor>
|
|
25
|
+
</MessageByIndexProvider>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
MemoMessageImpl.displayName = "ThreadPrimitive.Messages.MemoItem";
|
|
30
|
+
|
|
31
|
+
export const MemoMessage = memo(
|
|
32
|
+
MemoMessageImpl,
|
|
33
|
+
(prev, next) =>
|
|
34
|
+
prev.index === next.index && Object.is(prev.render, next.render),
|
|
35
|
+
);
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { cleanup, render } from "ink-testing-library";
|
|
4
|
+
import { Text } from "ink";
|
|
5
|
+
|
|
6
|
+
const hoisted = vi.hoisted(() => ({
|
|
7
|
+
state: { messagesLength: 0 },
|
|
8
|
+
capturedIndices: [] as number[],
|
|
9
|
+
staticItemCounts: [] as number[],
|
|
10
|
+
tailItemCounts: [] as number[],
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("ink", async (importOriginal) => {
|
|
14
|
+
const actual = await importOriginal<typeof import("ink")>();
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
Static: <T,>({
|
|
18
|
+
items,
|
|
19
|
+
children,
|
|
20
|
+
}: {
|
|
21
|
+
items: readonly T[];
|
|
22
|
+
children: (item: T, index: number) => ReactNode;
|
|
23
|
+
}) => {
|
|
24
|
+
hoisted.staticItemCounts.push(items.length);
|
|
25
|
+
return <>{items.map((item, i) => children(item, i))}</>;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
31
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
32
|
+
return {
|
|
33
|
+
...actual,
|
|
34
|
+
useAuiState: (selector: (s: unknown) => unknown) => {
|
|
35
|
+
const state = {
|
|
36
|
+
thread: { messages: { length: hoisted.state.messagesLength } },
|
|
37
|
+
message: {
|
|
38
|
+
role: "user" as const,
|
|
39
|
+
composer: { isEditing: false },
|
|
40
|
+
parts: [],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
return selector(state);
|
|
44
|
+
},
|
|
45
|
+
RenderChildrenWithAccessor: ({
|
|
46
|
+
children,
|
|
47
|
+
}: {
|
|
48
|
+
children: (getItem: () => unknown) => ReactNode;
|
|
49
|
+
}) => <>{children(() => ({}))}</>,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => {
|
|
54
|
+
const actual =
|
|
55
|
+
await importOriginal<typeof import("@assistant-ui/core/react")>();
|
|
56
|
+
return {
|
|
57
|
+
...actual,
|
|
58
|
+
MessageByIndexProvider: ({
|
|
59
|
+
index,
|
|
60
|
+
children,
|
|
61
|
+
}: {
|
|
62
|
+
index: number;
|
|
63
|
+
children: ReactNode;
|
|
64
|
+
}) => {
|
|
65
|
+
hoisted.capturedIndices.push(index);
|
|
66
|
+
return <>{children}</>;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
import { ThreadPrimitive } from "../../index";
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
hoisted.state.messagesLength = 0;
|
|
75
|
+
hoisted.capturedIndices = [];
|
|
76
|
+
hoisted.staticItemCounts = [];
|
|
77
|
+
hoisted.tailItemCounts = [];
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
cleanup();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("ThreadPrimitive.Messages", () => {
|
|
85
|
+
it("renders every message when windowSize is undefined", () => {
|
|
86
|
+
hoisted.state.messagesLength = 5;
|
|
87
|
+
|
|
88
|
+
render(
|
|
89
|
+
<ThreadPrimitive.Messages>
|
|
90
|
+
{() => <Text>m</Text>}
|
|
91
|
+
</ThreadPrimitive.Messages>,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2, 3, 4]);
|
|
95
|
+
expect(hoisted.staticItemCounts).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("renders nothing when there are no messages", () => {
|
|
99
|
+
hoisted.state.messagesLength = 0;
|
|
100
|
+
|
|
101
|
+
render(
|
|
102
|
+
<ThreadPrimitive.Messages>
|
|
103
|
+
{() => <Text>m</Text>}
|
|
104
|
+
</ThreadPrimitive.Messages>,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(hoisted.capturedIndices).toEqual([]);
|
|
108
|
+
expect(hoisted.staticItemCounts).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("keeps every message live when total fits within windowSize + windowOverscan", () => {
|
|
112
|
+
hoisted.state.messagesLength = 5;
|
|
113
|
+
|
|
114
|
+
render(
|
|
115
|
+
<ThreadPrimitive.Messages windowSize={3} windowOverscan={4}>
|
|
116
|
+
{() => <Text>m</Text>}
|
|
117
|
+
</ThreadPrimitive.Messages>,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2, 3, 4]);
|
|
121
|
+
expect(hoisted.staticItemCounts).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("splits long threads into a Static prefix plus live tail covering windowSize + windowOverscan", () => {
|
|
125
|
+
hoisted.state.messagesLength = 20;
|
|
126
|
+
|
|
127
|
+
render(
|
|
128
|
+
<ThreadPrimitive.Messages windowSize={3} windowOverscan={4}>
|
|
129
|
+
{() => <Text>m</Text>}
|
|
130
|
+
</ThreadPrimitive.Messages>,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(hoisted.staticItemCounts.at(-1)).toBe(13);
|
|
134
|
+
expect(hoisted.capturedIndices).toHaveLength(20);
|
|
135
|
+
expect(hoisted.capturedIndices.slice(0, 13)).toEqual(
|
|
136
|
+
Array.from({ length: 13 }, (_, i) => i),
|
|
137
|
+
);
|
|
138
|
+
expect(hoisted.capturedIndices.slice(13)).toEqual([
|
|
139
|
+
13, 14, 15, 16, 17, 18, 19,
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("defaults windowOverscan to 4", () => {
|
|
144
|
+
hoisted.state.messagesLength = 7;
|
|
145
|
+
|
|
146
|
+
render(
|
|
147
|
+
<ThreadPrimitive.Messages windowSize={2}>
|
|
148
|
+
{() => <Text>m</Text>}
|
|
149
|
+
</ThreadPrimitive.Messages>,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(hoisted.staticItemCounts.at(-1)).toBe(1);
|
|
153
|
+
expect(hoisted.capturedIndices).toHaveLength(7);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("clamps negative windowSize and windowOverscan to 0", () => {
|
|
157
|
+
hoisted.state.messagesLength = 6;
|
|
158
|
+
|
|
159
|
+
render(
|
|
160
|
+
<ThreadPrimitive.Messages windowSize={-5} windowOverscan={-2}>
|
|
161
|
+
{() => <Text>m</Text>}
|
|
162
|
+
</ThreadPrimitive.Messages>,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(hoisted.staticItemCounts.at(-1)).toBe(6);
|
|
166
|
+
expect(hoisted.capturedIndices).toHaveLength(6);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("with windowSize=0 keeps only windowOverscan messages live", () => {
|
|
170
|
+
hoisted.state.messagesLength = 10;
|
|
171
|
+
|
|
172
|
+
render(
|
|
173
|
+
<ThreadPrimitive.Messages windowSize={0} windowOverscan={4}>
|
|
174
|
+
{() => <Text>m</Text>}
|
|
175
|
+
</ThreadPrimitive.Messages>,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(hoisted.staticItemCounts.at(-1)).toBe(6);
|
|
179
|
+
expect(hoisted.capturedIndices).toHaveLength(10);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("does not re-render messages on parent rerender with a stable render fn", () => {
|
|
183
|
+
hoisted.state.messagesLength = 3;
|
|
184
|
+
const stableRender = () => <Text>m</Text>;
|
|
185
|
+
|
|
186
|
+
const instance = render(
|
|
187
|
+
<ThreadPrimitive.Messages>{stableRender}</ThreadPrimitive.Messages>,
|
|
188
|
+
);
|
|
189
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2]);
|
|
190
|
+
|
|
191
|
+
instance.rerender(
|
|
192
|
+
<ThreadPrimitive.Messages>{stableRender}</ThreadPrimitive.Messages>,
|
|
193
|
+
);
|
|
194
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("supports the components API alongside windowing", () => {
|
|
198
|
+
hoisted.state.messagesLength = 4;
|
|
199
|
+
const Message = () => <Text>m</Text>;
|
|
200
|
+
|
|
201
|
+
render(
|
|
202
|
+
<ThreadPrimitive.Messages windowSize={2} components={{ Message }} />,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2, 3]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("does not re-render messages when an inline components literal is passed", () => {
|
|
209
|
+
hoisted.state.messagesLength = 3;
|
|
210
|
+
const Message = () => <Text>m</Text>;
|
|
211
|
+
|
|
212
|
+
const App = () => <ThreadPrimitive.Messages components={{ Message }} />;
|
|
213
|
+
|
|
214
|
+
const instance = render(<App />);
|
|
215
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2]);
|
|
216
|
+
|
|
217
|
+
instance.rerender(<App />);
|
|
218
|
+
expect(hoisted.capturedIndices).toEqual([0, 1, 2]);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type ComponentType,
|
|
3
|
+
type FC,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { Box, Static } from "ink";
|
|
3
9
|
import type { ThreadMessage } from "@assistant-ui/core";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
10
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
11
|
+
import { MemoMessage } from "../internal/MemoMessage";
|
|
6
12
|
|
|
7
13
|
type MessageComponents =
|
|
8
14
|
| {
|
|
@@ -26,15 +32,31 @@ type MessageComponents =
|
|
|
26
32
|
SystemMessage?: ComponentType | undefined;
|
|
27
33
|
};
|
|
28
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Live render region keeps the last `windowSize + windowOverscan` messages;
|
|
37
|
+
* older messages graduate through Ink's `<Static>` into terminal scrollback
|
|
38
|
+
* and stop repainting. Defaults to no windowing.
|
|
39
|
+
*
|
|
40
|
+
* Per-message memoization only engages when the render callback is
|
|
41
|
+
* referentially stable. The `components` API handles stability internally;
|
|
42
|
+
* with the children render-fn API, hoist or memoize the function.
|
|
43
|
+
*/
|
|
44
|
+
type WindowingProps = {
|
|
45
|
+
/** Recent messages kept live. Unset renders all dynamically. Negative clamped to 0. */
|
|
46
|
+
windowSize?: number | undefined;
|
|
47
|
+
/** Extra live messages above the window to absorb boundary churn. Defaults to 4. Negative clamped to 0. */
|
|
48
|
+
windowOverscan?: number | undefined;
|
|
49
|
+
};
|
|
50
|
+
|
|
29
51
|
export type ThreadMessagesProps =
|
|
30
|
-
| {
|
|
52
|
+
| ({
|
|
31
53
|
components: MessageComponents;
|
|
32
54
|
children?: never;
|
|
33
|
-
}
|
|
34
|
-
| {
|
|
55
|
+
} & WindowingProps)
|
|
56
|
+
| ({
|
|
35
57
|
children: (value: { message: ThreadMessage }) => ReactNode;
|
|
36
58
|
components?: never;
|
|
37
|
-
};
|
|
59
|
+
} & WindowingProps);
|
|
38
60
|
|
|
39
61
|
const DEFAULT_SYSTEM_MESSAGE = () => null;
|
|
40
62
|
|
|
@@ -103,45 +125,114 @@ const ThreadMessageComponent: FC<{ components: MessageComponents }> = ({
|
|
|
103
125
|
|
|
104
126
|
const ThreadMessagesInner: FC<{
|
|
105
127
|
children: (value: { message: ThreadMessage }) => ReactNode;
|
|
106
|
-
|
|
128
|
+
windowSize?: number | undefined;
|
|
129
|
+
windowOverscan?: number | undefined;
|
|
130
|
+
}> = ({ children, windowSize, windowOverscan = 4 }) => {
|
|
107
131
|
const messagesLength = useAuiState((s) => s.thread.messages.length);
|
|
108
132
|
|
|
109
|
-
|
|
133
|
+
const tailStart =
|
|
134
|
+
windowSize !== undefined
|
|
135
|
+
? Math.max(
|
|
136
|
+
0,
|
|
137
|
+
messagesLength -
|
|
138
|
+
Math.max(0, windowSize) -
|
|
139
|
+
Math.max(0, windowOverscan),
|
|
140
|
+
)
|
|
141
|
+
: 0;
|
|
142
|
+
|
|
143
|
+
const prefixIndices = useMemo(
|
|
144
|
+
() => Array.from({ length: tailStart }, (_, i) => i),
|
|
145
|
+
[tailStart],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const tail = useMemo(() => {
|
|
110
149
|
if (messagesLength === 0) return null;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
</
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
const items: ReactNode[] = [];
|
|
151
|
+
for (let index = tailStart; index < messagesLength; index++) {
|
|
152
|
+
items.push(<MemoMessage key={index} index={index} render={children} />);
|
|
153
|
+
}
|
|
154
|
+
return items;
|
|
155
|
+
}, [messagesLength, tailStart, children]);
|
|
156
|
+
|
|
157
|
+
if (tailStart === 0) return tail;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
<Static items={prefixIndices}>
|
|
162
|
+
{(index) => <MemoMessage key={index} index={index} render={children} />}
|
|
163
|
+
</Static>
|
|
164
|
+
{tail}
|
|
165
|
+
</>
|
|
166
|
+
);
|
|
127
167
|
};
|
|
128
168
|
|
|
129
169
|
export const ThreadMessages: FC<ThreadMessagesProps> = ({
|
|
130
170
|
components,
|
|
131
171
|
children,
|
|
172
|
+
windowSize,
|
|
173
|
+
windowOverscan,
|
|
132
174
|
}) => {
|
|
175
|
+
const Message = components?.Message;
|
|
176
|
+
const EditComposer = components?.EditComposer;
|
|
177
|
+
const UserEditComposer = components?.UserEditComposer;
|
|
178
|
+
const AssistantEditComposer = components?.AssistantEditComposer;
|
|
179
|
+
const SystemEditComposer = components?.SystemEditComposer;
|
|
180
|
+
const UserMessage = components?.UserMessage;
|
|
181
|
+
const AssistantMessage = components?.AssistantMessage;
|
|
182
|
+
const SystemMessage = components?.SystemMessage;
|
|
183
|
+
|
|
184
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `components` excluded so inline literals do not bust the memo; per-field deps cover real changes.
|
|
185
|
+
const stableComponents = useMemo<MessageComponents | undefined>(() => {
|
|
186
|
+
if (!components) return undefined;
|
|
187
|
+
return {
|
|
188
|
+
Message,
|
|
189
|
+
EditComposer,
|
|
190
|
+
UserEditComposer,
|
|
191
|
+
AssistantEditComposer,
|
|
192
|
+
SystemEditComposer,
|
|
193
|
+
UserMessage,
|
|
194
|
+
AssistantMessage,
|
|
195
|
+
SystemMessage,
|
|
196
|
+
} as MessageComponents;
|
|
197
|
+
}, [
|
|
198
|
+
Message,
|
|
199
|
+
EditComposer,
|
|
200
|
+
UserEditComposer,
|
|
201
|
+
AssistantEditComposer,
|
|
202
|
+
SystemEditComposer,
|
|
203
|
+
UserMessage,
|
|
204
|
+
AssistantMessage,
|
|
205
|
+
SystemMessage,
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
const renderFromComponents = useCallback(
|
|
209
|
+
() =>
|
|
210
|
+
stableComponents ? (
|
|
211
|
+
<ThreadMessageComponent components={stableComponents} />
|
|
212
|
+
) : null,
|
|
213
|
+
[stableComponents],
|
|
214
|
+
);
|
|
215
|
+
|
|
133
216
|
if (components) {
|
|
134
217
|
return (
|
|
135
218
|
<Box flexDirection="column">
|
|
136
|
-
<ThreadMessagesInner
|
|
137
|
-
|
|
219
|
+
<ThreadMessagesInner
|
|
220
|
+
windowSize={windowSize}
|
|
221
|
+
windowOverscan={windowOverscan}
|
|
222
|
+
>
|
|
223
|
+
{renderFromComponents}
|
|
138
224
|
</ThreadMessagesInner>
|
|
139
225
|
</Box>
|
|
140
226
|
);
|
|
141
227
|
}
|
|
142
228
|
return (
|
|
143
229
|
<Box flexDirection="column">
|
|
144
|
-
<ThreadMessagesInner
|
|
230
|
+
<ThreadMessagesInner
|
|
231
|
+
windowSize={windowSize}
|
|
232
|
+
windowOverscan={windowOverscan}
|
|
233
|
+
>
|
|
234
|
+
{children}
|
|
235
|
+
</ThreadMessagesInner>
|
|
145
236
|
</Box>
|
|
146
237
|
);
|
|
147
238
|
};
|