Loading...
Loading...
Builds terminal chat UIs with @assistant-ui/react-ink and ANSI markdown with @assistant-ui/react-ink-markdown. Use when scaffolding `create --ink`, mounting `AssistantRuntimeProvider` around `useChatRuntime`, composing `ThreadPrimitive`, `ComposerPrimitive`, `MessagePrimitive`, `LoadingPrimitive`, `TextInput`, thread history, attachments, notifications, and terminal keyboard navigation, or when a chat needs grapheme safe editing, multiline display columns, an absolute backend URL, local file storage, or a custom `RemoteThreadListAdapter`. Route terminal markdown, tool output, message rendering, and Ink focus problems here. For browser application setup, web elements, and the standard web runtime, use [setup](../setup/SKILL.md).
npx skill4agent add assistant-ui/skills ink@assistant-ui/react-inkBoxText@assistant-ui/react-ink-markdownTextInputnpx assistant-ui@latest create --ink my-app
cd my-app--inkwith-react-inkwith-react-ink-web--inknpm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink react @assistant-ui/ai-sdk/api/chatAssistantChatTransportimport { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/ai-sdk";
import { Box } from "ink";
import { TerminalThread } from "./components/terminal-thread.js";
const CHAT_API_URL = "http://localhost:3000/api/chat";
export function App() {
const runtime = useChatRuntime({
transport: new AssistantChatTransport({ api: CHAT_API_URL }),
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Box flexDirection="column">
<TerminalThread />
</Box>
</AssistantRuntimeProvider>
);
}useChatRuntimeAssistantChatTransport@assistant-ui/ai-sdkThreadPrimitive.Messagess.messageAuiIfLoadingPrimitiveimport { Box, Text } from "ink";
import {
AuiIf,
ComposerPrimitive,
LoadingPrimitive,
ThreadPrimitive,
useAuiState,
} from "@assistant-ui/react-ink";
import { MarkdownText } from "@assistant-ui/react-ink-markdown";
function Message() {
const message = useAuiState((s) => s.message);
const text = message.content
.filter((part) => part.type === "text")
.map((part) => ("text" in part ? part.text : ""))
.join("");
if (message.role === "user") {
return <Text color="green">You: {text}</Text>;
}
return (
<Box flexDirection="column" marginBottom={1}>
<Text color="blue">Assistant:</Text>
<MarkdownText text={text} />
</Box>
);
}
export function TerminalThread() {
return (
<ThreadPrimitive.Root flexDirection="column">
<AuiIf condition={(s) => s.thread.isEmpty}>
<Text dimColor>Send a message to begin.</Text>
</AuiIf>
<ThreadPrimitive.Messages>{() => <Message />}</ThreadPrimitive.Messages>
<LoadingPrimitive.Root gap={1}>
<LoadingPrimitive.Spinner variant="bar" />
<LoadingPrimitive.Text />
<LoadingPrimitive.ElapsedTime />
</LoadingPrimitive.Root>
<Box borderStyle="round" borderColor="gray" paddingX={1}>
<Text color="gray">{"> "}</Text>
<ComposerPrimitive.Input submitOnEnter placeholder="Message..." autoFocus />
</Box>
</ThreadPrimitive.Root>
);
}MessagePrimitive.PartsComposerPrimitive.InputTextInputsubmitOnEntermultiLinesubmitOnEnterCtrl JCtrl DCtrl ACtrl ECtrl WAlt BAlt FAlt DIntl.SegmenterCtrl UCtrl KCtrl KsubmitOnEnterTextInputMarkdownText@assistant-ui/react-ink-markdownMarkdownTextPrimitiveuseShikiHighlighterMarkdownTextimport { MarkdownText } from "@assistant-ui/react-ink-markdown";
export function AssistantReply({ text }: { text: string }) {
return <MarkdownText text={text} />;
}AssistantChatTransportapihttp://localhost:3000/api/chatBoxText@assistant-ui/react-inkAssistantRuntimeProvider runtime={runtime}ThreadPrimitive.MessagesMessagePrimitive.PartssubmitOnEntermultiLineComposerPrimitive.InputTextInputuseLocalRuntimecreateFileStorageAdapterRemoteThreadListAdapterMarkdownText@assistant-ui/react-ink-markdown