diff --git a/packages/ui/@/components/ai-elements/message.tsx b/packages/ui/@/components/ai-elements/message.tsx index fa560526a8..48398ef962 100644 --- a/packages/ui/@/components/ai-elements/message.tsx +++ b/packages/ui/@/components/ai-elements/message.tsx @@ -1,7 +1,12 @@ "use client"; import type { UIMessage } from "ai"; -import type { ComponentProps, HTMLAttributes, ReactElement } from "react"; +import type { + ComponentProps, + HTMLAttributes, + ReactElement, + ReactNode, +} from "react"; import { Button } from "@/components/ui/button"; import { @@ -25,6 +30,7 @@ import { useMemo, useState, } from "react"; +import { Streamdown } from "streamdown"; export type MessageProps = HTMLAttributes & { from: UIMessage["role"]; @@ -33,7 +39,7 @@ export type MessageProps = HTMLAttributes & { export const Message = ({ className, from, ...props }: MessageProps) => (
; +function extractText(children: ReactNode): string | null { + if (children == null || typeof children === "boolean") { + return ""; + } + + if ( + typeof children === "string" || + typeof children === "number" || + typeof children === "bigint" + ) { + return String(children); + } + + if (Array.isArray(children)) { + let result = ""; + for (const child of children) { + const part = extractText(child); + if (part === null) { + return null; + } + result += part; + } + return result; + } + + return null; +} + export const MessageResponse = memo( - ({ className, ...props }: MessageResponseProps) => ( -
*:first-child]:mt-0 [&>*:last-child]:mb-0", - className - )} - {...props} - /> - ), + ({ className, children, ...props }: MessageResponseProps) => { + const text = extractText(children); + + return ( +
*:first-child]:mt-0 [&>*:last-child]:mb-0", + className + )} + {...props} + > + {text !== null ? ( + + {text} + + ) : ( + children + )} +
+ ); + }, (prevProps, nextProps) => prevProps.children === nextProps.children ); diff --git a/packages/ui/@/components/ai-elements/prompt-input.tsx b/packages/ui/@/components/ai-elements/prompt-input.tsx index c63f9f4240..6485fdae48 100644 --- a/packages/ui/@/components/ai-elements/prompt-input.tsx +++ b/packages/ui/@/components/ai-elements/prompt-input.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { ArrowUpIcon } from "lucide-react"; -import { forwardRef } from "react"; +import { forwardRef, useCallback, useLayoutEffect, useRef } from "react"; export type PromptInputProps = ComponentProps<"form">; @@ -27,35 +27,76 @@ PromptInput.displayName = "PromptInput"; export type PromptInputTextareaProps = ComponentProps; +const PROMPT_INPUT_TEXTAREA_MAX_HEIGHT = 220; + +function autoResizeTextarea( + textarea: HTMLTextAreaElement, + maxHeight = PROMPT_INPUT_TEXTAREA_MAX_HEIGHT +): void { + textarea.style.height = "auto"; + const nextHeight = Math.min(textarea.scrollHeight, maxHeight); + textarea.style.height = `${nextHeight}px`; + textarea.style.overflowY = + textarea.scrollHeight > maxHeight ? "auto" : "hidden"; +} + export const PromptInputTextarea = forwardRef< HTMLTextAreaElement, PromptInputTextareaProps ->(({ className, onKeyDown, ...props }, ref) => ( -