fix dialog animations, mobile input ux, and tool descriptions (#1362)

This commit is contained in:
josh 2025-12-20 00:23:15 +00:00 committed by GitHub
parent 4d3ba8d9fe
commit 22de923298
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 101 additions and 74 deletions

View file

@ -1,11 +1,9 @@
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { Suspense } from "react"; import { Suspense } from "react";
import { Chat } from "@/components/chat"; import { Chat } from "@/components/chat";
import { DataStreamHandler } from "@/components/data-stream-handler"; import { DataStreamHandler } from "@/components/data-stream-handler";
import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models"; import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models";
import { generateUUID } from "@/lib/utils"; import { generateUUID } from "@/lib/utils";
import { auth } from "../(auth)/auth";
export default function Page() { export default function Page() {
return ( return (
@ -16,16 +14,9 @@ export default function Page() {
} }
async function NewChatPage() { async function NewChatPage() {
const session = await auth();
if (!session) {
redirect("/api/auth/guest");
}
const id = generateUUID();
const cookieStore = await cookies(); const cookieStore = await cookies();
const modelIdFromCookie = cookieStore.get("chat-model"); const modelIdFromCookie = cookieStore.get("chat-model");
const id = generateUUID();
if (!modelIdFromCookie) { if (!modelIdFromCookie) {
return ( return (

View file

@ -11,7 +11,6 @@ import {
type SetStateAction, type SetStateAction,
useCallback, useCallback,
useEffect, useEffect,
useMemo,
useRef, useRef,
useState, useState,
} from "react"; } from "react";
@ -100,6 +99,17 @@ function PureMultimodalInput({
} }
}, [adjustHeight]); }, [adjustHeight]);
const hasAutoFocused = useRef(false);
useEffect(() => {
if (!hasAutoFocused.current && width) {
const timer = setTimeout(() => {
textareaRef.current?.focus();
hasAutoFocused.current = true;
}, 100);
return () => clearTimeout(timer);
}
}, [width]);
const resetHeight = useCallback(() => { const resetHeight = useCallback(() => {
if (textareaRef.current) { if (textareaRef.current) {
textareaRef.current.style.height = "44px"; textareaRef.current.style.height = "44px";
@ -352,8 +362,7 @@ function PureMultimodalInput({
)} )}
<div className="flex flex-row items-start gap-1 sm:gap-2"> <div className="flex flex-row items-start gap-1 sm:gap-2">
<PromptInputTextarea <PromptInputTextarea
autoFocus className="grow resize-none border-0! border-none! bg-transparent p-2 text-base outline-none ring-0 [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 [&::-webkit-scrollbar]:hidden"
className="grow resize-none border-0! border-none! bg-transparent p-2 text-sm outline-none ring-0 [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 [&::-webkit-scrollbar]:hidden"
data-testid="multimodal-input" data-testid="multimodal-input"
disableAutoResize={true} disableAutoResize={true}
maxHeight={200} maxHeight={200}

View file

@ -1,16 +1,15 @@
"use client" "use client";
import * as React from "react" import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
import { AlertDialog as AlertDialogPrimitive } from "radix-ui" import * as React from "react";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { cn } from "@/lib/utils" const AlertDialog = AlertDialogPrimitive.Root;
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef< const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>, React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
@ -18,14 +17,14 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
className className
)} )}
{...props} {...props}
ref={ref} ref={ref}
/> />
)) ));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef< const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>, React.ElementRef<typeof AlertDialogPrimitive.Content>,
@ -34,16 +33,16 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:rounded-lg",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
</AlertDialogPortal> </AlertDialogPortal>
)) ));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ const AlertDialogHeader = ({
className, className,
@ -56,8 +55,8 @@ const AlertDialogHeader = ({
)} )}
{...props} {...props}
/> />
) );
AlertDialogHeader.displayName = "AlertDialogHeader" AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ const AlertDialogFooter = ({
className, className,
@ -70,61 +69,61 @@ const AlertDialogFooter = ({
)} )}
{...props} {...props}
/> />
) );
AlertDialogFooter.displayName = "AlertDialogFooter" AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef< const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>, React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
className={cn("font-semibold text-lg", className)}
ref={ref} ref={ref}
className={cn("text-lg font-semibold", className)}
{...props} {...props}
/> />
)) ));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef< const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>, React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
className={cn("text-muted-foreground text-sm", className)}
ref={ref} ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
AlertDialogDescription.displayName = AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef< const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>, React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action <AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)} className={cn(buttonVariants(), className)}
ref={ref}
{...props} {...props}
/> />
)) ));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef< const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>, React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel <AlertDialogPrimitive.Cancel
ref={ref}
className={cn( className={cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0", "mt-2 sm:mt-0",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
)) ));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export { export {
AlertDialog, AlertDialog,
@ -138,4 +137,4 @@ export {
AlertDialogDescription, AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
} };

View file

@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg",
className className
)} )}
{...props} {...props}

View file

@ -30,10 +30,16 @@ This is a guide for using artifacts tools: \`createDocument\` and \`updateDocume
- Immediately after creating a document - Immediately after creating a document
Do not update document right after creating it. Wait for user feedback or request to update it. Do not update document right after creating it. Wait for user feedback or request to update it.
**Using \`requestSuggestions\`:**
- ONLY use when the user explicitly asks for suggestions on an existing document
- Requires a valid document ID from a previously created document
- Never use for general questions or information requests
`; `;
export const regularPrompt = export const regularPrompt = `You are a friendly assistant! Keep your responses concise and helpful.
"You are a friendly assistant! Keep your responses concise and helpful.";
When asked to write, create, or help with something, just do it directly. Don't ask clarifying questions unless absolutely necessary - make reasonable assumptions and proceed with the task.`;
export type RequestHints = { export type RequestHints = {
latitude: Geo["latitude"]; latitude: Geo["latitude"];

View file

@ -1,4 +1,4 @@
import { streamObject, tool, type UIMessageStreamWriter } from "ai"; import { Output, streamText, tool, type UIMessageStreamWriter } from "ai";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import { z } from "zod"; import { z } from "zod";
import { getDocumentById, saveSuggestions } from "@/lib/db/queries"; import { getDocumentById, saveSuggestions } from "@/lib/db/queries";
@ -17,11 +17,14 @@ export const requestSuggestions = ({
dataStream, dataStream,
}: RequestSuggestionsProps) => }: RequestSuggestionsProps) =>
tool({ tool({
description: "Request suggestions for a document", description:
"Request writing suggestions for an existing document artifact. Only use this when the user explicitly asks to improve or get suggestions for a document they have already created. Never use for general questions.",
inputSchema: z.object({ inputSchema: z.object({
documentId: z documentId: z
.string() .string()
.describe("The ID of the document to request edits"), .describe(
"The UUID of an existing document artifact that was previously created with createDocument"
),
}), }),
execute: async ({ documentId }) => { execute: async ({ documentId }) => {
const document = await getDocumentById({ id: documentId }); const document = await getDocumentById({ id: documentId });
@ -37,22 +40,39 @@ export const requestSuggestions = ({
"userId" | "createdAt" | "documentCreatedAt" "userId" | "createdAt" | "documentCreatedAt"
>[] = []; >[] = [];
const { elementStream } = streamObject({ const { partialOutputStream } = streamText({
model: getArtifactModel(), model: getArtifactModel(),
system: system:
"You are a help writing assistant. Given a piece of writing, please offer suggestions to improve the piece of writing and describe the change. It is very important for the edits to contain full sentences instead of just words. Max 5 suggestions.", "You are a help writing assistant. Given a piece of writing, please offer suggestions to improve the piece of writing and describe the change. It is very important for the edits to contain full sentences instead of just words. Max 5 suggestions.",
prompt: document.content, prompt: document.content,
output: "array", output: Output.array({
schema: z.object({ element: z.object({
originalSentence: z.string().describe("The original sentence"), originalSentence: z.string().describe("The original sentence"),
suggestedSentence: z.string().describe("The suggested sentence"), suggestedSentence: z.string().describe("The suggested sentence"),
description: z.string().describe("The description of the suggestion"), description: z
.string()
.describe("The description of the suggestion"),
}),
}), }),
}); });
for await (const element of elementStream) { let processedCount = 0;
// @ts-expect-error todo: fix type for await (const partialOutput of partialOutputStream) {
const suggestion: Suggestion = { if (!partialOutput) {
continue;
}
for (let i = processedCount; i < partialOutput.length; i++) {
const element = partialOutput[i];
if (
!element?.originalSentence ||
!element?.suggestedSentence ||
!element?.description
) {
continue;
}
const suggestion = {
originalText: element.originalSentence, originalText: element.originalSentence,
suggestedText: element.suggestedSentence, suggestedText: element.suggestedSentence,
description: element.description, description: element.description,
@ -63,11 +83,13 @@ export const requestSuggestions = ({
dataStream.write({ dataStream.write({
type: "data-suggestion", type: "data-suggestion",
data: suggestion, data: suggestion as Suggestion,
transient: true, transient: true,
}); });
suggestions.push(suggestion); suggestions.push(suggestion);
processedCount++;
}
} }
if (session.user?.id) { if (session.user?.id) {