Skip to content

Commit 477de25

Browse files
committed
feat: Add smooth streaming utility for message parts and introduce a new shimmer CSS utility.
1 parent dfa20a3 commit 477de25

17 files changed

Lines changed: 470 additions & 39 deletions

File tree

apps/mobile/components/ai-chat/markdown-text.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,32 @@ import {
77
type RenderersMap,
88
MarkdownTextPrimitive,
99
} from "@creatorem/ai-react-native/markdown";
10+
import * as MessagePartPrimitive from "@creatorem/ai-chat/primitives/message-part";
1011
import Clipboard from "@react-native-clipboard/clipboard";
1112
import { Text } from "../ui/text";
1213
import { View } from "react-native";
1314
import { Button } from "../ui/button";
1415
import { Icon } from "../ui/icon";
1516
import { cn } from "~/utils/cn";
1617
import { useCSSVariable } from "uniwind";
18+
// import { ShimmerText } from "../shimmer-text";
1719

1820
const MarkdownTextImpl = () => {
1921
const textColor = useCSSVariable("--color-foreground");
2022
const textSecondaryColor = useCSSVariable("--color-secondary");
2123

2224
return (
23-
<MarkdownTextPrimitive
24-
textColor={textColor}
25-
textSecondaryColor={textSecondaryColor}
26-
className="aui-md"
27-
renderers={defaultComponents as unknown as Partial<RenderersMap>}
28-
/>
25+
<>
26+
<MarkdownTextPrimitive
27+
textColor={textColor}
28+
textSecondaryColor={textSecondaryColor}
29+
className="aui-md"
30+
renderers={defaultComponents as unknown as Partial<RenderersMap>}
31+
/>
32+
{/* <MessagePartPrimitive.BeforeStream>
33+
<ShimmerText text="Typing ..." />
34+
</MessagePartPrimitive.BeforeStream> */}
35+
</>
2936
);
3037
};
3138

apps/nextjs-example/app/test2/markdown-text.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
"use client";
22

3-
import "@creatorem/ai-assistant-react-markdown/styles/dot.css";
3+
import "@creatorem/ai-react/shimmer.css";
44

5-
import remarkGfm from "remark-gfm";
6-
import { type FC, memo, useState } from "react";
7-
import { CheckIcon, CopyIcon } from "lucide-react";
8-
9-
import { TooltipIconButton } from "@/components/ai-chat/tooltip-icon-button";
10-
import { cn } from "@/lib/utils";
5+
import * as MessagePartPrimitive from "@creatorem/ai-chat/primitives/message-part";
116
import {
127
type CodeHeaderProps,
138
MarkdownTextPrimitive,
149
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
1510
useIsMarkdownCodeBlock,
1611
} from "@creatorem/ai-react/markdown";
12+
import { CheckIcon, CopyIcon } from "lucide-react";
13+
import { type FC, memo, useState } from "react";
14+
import remarkGfm from "remark-gfm";
15+
import { TooltipIconButton } from "@/components/ai-chat/tooltip-icon-button";
16+
import { cn } from "@/lib/utils";
1717

1818
const MarkdownTextImpl = () => {
1919
return (
20-
<MarkdownTextPrimitive
21-
remarkPlugins={[remarkGfm]}
22-
className="aui-md"
23-
components={defaultComponents}
24-
/>
20+
<>
21+
<MarkdownTextPrimitive
22+
remarkPlugins={[remarkGfm]}
23+
className="aui-md"
24+
components={defaultComponents}
25+
/>
26+
{/* <MessagePartPrimitive.BeforeStream>
27+
<span className="shimmer text-foreground/60">Typing ...</span>
28+
</MessagePartPrimitive.BeforeStream> */}
29+
</>
2530
);
2631
};
2732

apps/nextjs-example/components/assistant-ui/markdown-text.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
useIsMarkdownCodeBlock,
1010
} from "@creatorem/ai-assistant-react-markdown";
1111
import remarkGfm from "remark-gfm";
12+
import rehypeHighlight from "rehype-highlight";
1213
import { type FC, memo, useState } from "react";
1314
import { CheckIcon, CopyIcon } from "lucide-react";
1415

@@ -19,6 +20,7 @@ const MarkdownTextImpl = () => {
1920
return (
2021
<MarkdownTextPrimitive
2122
remarkPlugins={[remarkGfm]}
23+
rehypePlugins={[rehypeHighlight]}
2224
className="aui-md"
2325
components={defaultComponents}
2426
/>

apps/nextjs-example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"next": "16.1.5",
4040
"react": "19.1.0",
4141
"react-dom": "19.1.0",
42+
"rehype-highlight": "^7.0.2",
4243
"remark-gfm": "^4.0.1",
4344
"tailwind-merge": "^3.4.0",
4445
"tw-animate-css": "^1.4.0",

packages-test-3/ai-chat/src/component-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Event = {
2525
export interface RuntimeComponents {
2626
Box: ComponentType<BaseComponentPropsWithChildren>
2727
Text: ComponentType<BaseComponentPropsWithChildren>
28+
Shimmering: ComponentType<BaseComponentPropsWithChildren>
2829
Form: ComponentType<BaseComponentPropsWithChildren & { onSubmit?: (e: Event) => void; }>
2930
Button: ButtonComponent
3031
ScrollArea: ComponentType<BaseComponentPropsWithChildren>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { MessagePartPrimitiveText as Text } from "./message-part-text";
22
export { MessagePartPrimitiveImage as Image } from "./message-part-image";
3+
// export { MessagePartPrimitiveBeforeStream as BeforeStream, useIsSmoothStreamingMessagePart } from "./message-part-before-stream";
34
export { MessagePartPrimitiveInProgress as InProgress } from "./message-part-in-progress";
45

56
export * from "./use-message-part-text";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use client";
2+
3+
import { FC, PropsWithChildren, useMemo } from "react";
4+
import { useMessagePartText } from "./use-message-part-text";
5+
import { useThread } from "../thread";
6+
7+
export namespace MessagePartPrimitiveBeforeStream {
8+
export type Props = PropsWithChildren;
9+
}
10+
11+
const SMOOTH_START_MIN_CHARS = 100;
12+
13+
export const useIsSmoothStreamingMessagePart = () => {
14+
const messagePartText = useMessagePartText();
15+
const {isRunning} = useThread();
16+
17+
return useMemo(
18+
() =>
19+
isRunning &&
20+
messagePartText.text.length >= SMOOTH_START_MIN_CHARS,
21+
[messagePartText, isRunning],
22+
);
23+
}
24+
25+
export const MessagePartPrimitiveBeforeStream: FC<
26+
MessagePartPrimitiveBeforeStream.Props
27+
> = ({ children }) => {
28+
const {isRunning} = useThread();
29+
const messagePartText = useMessagePartText();
30+
31+
return isRunning && messagePartText.text.length < SMOOTH_START_MIN_CHARS ? children: null;
32+
};
33+

packages-test-3/ai-chat/src/primitives/message-part/message-part-text.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { useMessagePartText } from "./use-message-part-text";
99
import { useRuntime } from "@creatorem/ai-chat/runtime";
1010
import type { RuntimeComponents } from "@creatorem/ai-chat/component-types";
11+
import { useSmoothStream } from "../../utils/smooth-stream";
1112

1213
export namespace MessagePartPrimitiveText {
1314
export type Element = ComponentRef<RuntimeComponents['Text'] >;
@@ -44,8 +45,7 @@ export const MessagePartPrimitiveText = forwardRef<
4445
MessagePartPrimitiveText.Element,
4546
MessagePartPrimitiveText.Props
4647
>(({ smooth = true, ...rest }, forwardedRef) => {
47-
// const { text, status } = useSmooth(useMessagePartText(), smooth);
48-
const { text, status } = useMessagePartText();
48+
const { text, status } = useSmoothStream(useMessagePartText(), smooth);
4949
const { components: { Text } } = useRuntime();
5050

5151
return (

packages-test-3/ai-chat/src/primitives/message/message-parts.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "react";
1010
import { MessagePartPrimitiveText } from "../message-part/message-part-text";
1111
import { MessagePartPrimitiveImage } from "../message-part/message-part-image";
12-
import { MessagePartPrimitiveInProgress } from "../message-part/message-part-in-progress";
12+
// import { MessagePartPrimitiveInProgress } from "../message-part/message-part-before-stream";
1313
import { PartByIndexProvider, usePart } from "../message-part/part-by-index-provider";
1414
import { TextMessagePartProvider } from "./text-message-part-provider";
1515
import { useMessage } from "./message-by-index-provider";
@@ -28,6 +28,7 @@ import type {
2828
} from "../../types/message-part-component-types";
2929
import { MessagePartStatus } from "../../types/assistant-types";
3030
import { useRuntime } from "@creatorem/ai-chat/runtime";
31+
import { MessagePartPrimitiveInProgress } from "../message-part/message-part-in-progress";
3132

3233
type MessagePartRange =
3334
| { type: "single"; index: number }

packages-test-3/ai-chat/src/utils/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export * from './create-context-store-hook';
66
export * from './id-utils';
77
export * from './message-repository';
88
export * from './readonly-store';
9-
export * from './require-at-least-one';
9+
export * from './require-at-least-one';
10+
export * from './smooth-stream'

0 commit comments

Comments
 (0)