feat: add tests (#843)
This commit is contained in:
parent
95a2af2535
commit
9dd9a9898c
35 changed files with 1063 additions and 191 deletions
|
|
@ -44,6 +44,7 @@ function PureArtifactMessages({
|
|||
key={message.id}
|
||||
message={message}
|
||||
isLoading={isLoading && index === messages.length - 1}
|
||||
index={index}
|
||||
vote={
|
||||
votes
|
||||
? votes.find((vote) => vote.messageId === message.id)
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
|
|||
<img
|
||||
src={content.value}
|
||||
alt="output"
|
||||
className="rounded-md max-w-[600px] w-full"
|
||||
className="rounded-md max-w-screen-toast-mobile w-full"
|
||||
/>
|
||||
</picture>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1153,3 +1153,22 @@ export const LineChartIcon = ({ size = 16 }: { size?: number }) => (
|
|||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const WarningIcon = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg
|
||||
height={size}
|
||||
strokeLinejoin="round"
|
||||
viewBox="0 0 16 16"
|
||||
width={size}
|
||||
style={{ color: 'currentcolor' }}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.55846 0.5C9.13413 0.5 9.65902 0.829456 9.90929 1.34788L15.8073 13.5653C16.1279 14.2293 15.6441 15 14.9068 15H1.09316C0.355835 15 -0.127943 14.2293 0.192608 13.5653L6.09065 1.34787C6.34092 0.829454 6.86581 0.5 7.44148 0.5H8.55846ZM8.74997 4.75V5.5V8V8.75H7.24997V8V5.5V4.75H8.74997ZM7.99997 12C8.55226 12 8.99997 11.5523 8.99997 11C8.99997 10.4477 8.55226 10 7.99997 10C7.44769 10 6.99997 10.4477 6.99997 11C6.99997 11.5523 7.44769 12 7.99997 12Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export function MessageEditor({
|
|||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<Textarea
|
||||
data-testid="message-editor"
|
||||
ref={textareaRef}
|
||||
className="bg-transparent outline-none overflow-hidden resize-none !text-base rounded-xl w-full"
|
||||
value={draftContent}
|
||||
|
|
@ -67,6 +68,7 @@ export function MessageEditor({
|
|||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="message-editor-send-button"
|
||||
variant="default"
|
||||
className="h-fit py-2 px-3"
|
||||
disabled={isSubmitting}
|
||||
|
|
|
|||
|
|
@ -43,14 +43,15 @@ export function MessageReasoning({
|
|||
) : (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div className="font-medium">Reasoned for a few seconds</div>
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsExpanded(!isExpanded);
|
||||
}}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,10 @@
|
|||
import type { ChatRequestOptions, Message } from 'ai';
|
||||
import cx from 'classnames';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
|
||||
import { memo, useState } from 'react';
|
||||
import type { Vote } from '@/lib/db/schema';
|
||||
|
||||
import { DocumentToolCall, DocumentToolResult } from './document';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
LoaderIcon,
|
||||
PencilEditIcon,
|
||||
SparklesIcon,
|
||||
} from './icons';
|
||||
import { PencilEditIcon, SparklesIcon } from './icons';
|
||||
import { Markdown } from './markdown';
|
||||
import { MessageActions } from './message-actions';
|
||||
import { PreviewAttachment } from './preview-attachment';
|
||||
|
|
@ -34,6 +27,7 @@ const PurePreviewMessage = ({
|
|||
setMessages,
|
||||
reload,
|
||||
isReadonly,
|
||||
index,
|
||||
}: {
|
||||
chatId: string;
|
||||
message: Message;
|
||||
|
|
@ -46,12 +40,14 @@ const PurePreviewMessage = ({
|
|||
chatRequestOptions?: ChatRequestOptions,
|
||||
) => Promise<string | null | undefined>;
|
||||
isReadonly: boolean;
|
||||
index: number;
|
||||
}) => {
|
||||
const [mode, setMode] = useState<'view' | 'edit'>('view');
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
data-testid={`message-${message.role}-${index}`}
|
||||
className="w-full mx-auto max-w-3xl px-4 group/message"
|
||||
initial={{ y: 5, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
|
|
@ -76,7 +72,10 @@ const PurePreviewMessage = ({
|
|||
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{message.experimental_attachments && (
|
||||
<div className="flex flex-row justify-end gap-2">
|
||||
<div
|
||||
data-testid={`message-attachments-${index}`}
|
||||
className="flex flex-row justify-end gap-2"
|
||||
>
|
||||
{message.experimental_attachments.map((attachment) => (
|
||||
<PreviewAttachment
|
||||
key={attachment.url}
|
||||
|
|
@ -99,6 +98,7 @@ const PurePreviewMessage = ({
|
|||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
data-testid={`edit-${message.role}-${index}`}
|
||||
variant="ghost"
|
||||
className="px-2 h-fit rounded-full text-muted-foreground opacity-0 group-hover/message:opacity-100"
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ function PureMessages({
|
|||
{messages.map((message, index) => (
|
||||
<PreviewMessage
|
||||
key={message.id}
|
||||
index={index}
|
||||
chatId={chatId}
|
||||
message={message}
|
||||
isLoading={isLoading && messages.length - 1 === index}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,10 @@ function PureMultimodalInput({
|
|||
/>
|
||||
|
||||
{(attachments.length > 0 || uploadQueue.length > 0) && (
|
||||
<div className="flex flex-row gap-2 overflow-x-scroll items-end">
|
||||
<div
|
||||
data-testid="attachments-preview"
|
||||
className="flex flex-row gap-2 overflow-x-scroll items-end"
|
||||
>
|
||||
{attachments.map((attachment) => (
|
||||
<PreviewAttachment key={attachment.url} attachment={attachment} />
|
||||
))}
|
||||
|
|
@ -231,6 +234,7 @@ function PureMultimodalInput({
|
|||
)}
|
||||
|
||||
<Textarea
|
||||
data-testid="multimodal-input"
|
||||
ref={textareaRef}
|
||||
placeholder="Send a message..."
|
||||
value={input}
|
||||
|
|
@ -293,6 +297,7 @@ function PureAttachmentsButton({
|
|||
}) {
|
||||
return (
|
||||
<Button
|
||||
data-testid="attachments-button"
|
||||
className="rounded-md rounded-bl-lg p-[7px] h-fit dark:border-zinc-700 hover:dark:bg-zinc-900 hover:bg-zinc-200"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -317,6 +322,7 @@ function PureStopButton({
|
|||
}) {
|
||||
return (
|
||||
<Button
|
||||
data-testid="stop-button"
|
||||
className="rounded-full p-1.5 h-fit border dark:border-zinc-600"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -342,6 +348,7 @@ function PureSendButton({
|
|||
}) {
|
||||
return (
|
||||
<Button
|
||||
data-testid="send-button"
|
||||
className="rounded-full p-1.5 h-fit border dark:border-zinc-600"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const PreviewAttachment = ({
|
|||
const { name, url, contentType } = attachment;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div data-testid="input-attachment-preview" className="flex flex-col gap-2">
|
||||
<div className="w-20 h-16 aspect-video bg-muted rounded-md relative flex flex-col items-center justify-center">
|
||||
{contentType ? (
|
||||
contentType.startsWith('image') ? (
|
||||
|
|
@ -32,7 +32,10 @@ export const PreviewAttachment = ({
|
|||
)}
|
||||
|
||||
{isUploading && (
|
||||
<div className="animate-spin absolute text-zinc-500">
|
||||
<div
|
||||
data-testid="input-attachment-loader"
|
||||
className="animate-spin absolute text-zinc-500"
|
||||
>
|
||||
<LoaderIcon />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ function PureSuggestedActions({ chatId, append }: SuggestedActionsProps) {
|
|||
];
|
||||
|
||||
return (
|
||||
<div className="grid sm:grid-cols-2 gap-2 w-full">
|
||||
<div
|
||||
data-testid="suggested-actions"
|
||||
className="grid sm:grid-cols-2 gap-2 w-full"
|
||||
>
|
||||
{suggestedActions.map((suggestedAction, index) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
|
|
|||
44
components/toast.tsx
Normal file
44
components/toast.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
'use client';
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { toast as sonnerToast } from 'sonner';
|
||||
import { CheckCircleFillIcon, WarningIcon } from './icons';
|
||||
|
||||
const iconsByType: Record<'success' | 'error', ReactNode> = {
|
||||
success: <CheckCircleFillIcon />,
|
||||
error: <WarningIcon />,
|
||||
};
|
||||
|
||||
export function toast(props: Omit<ToastProps, 'id'>) {
|
||||
return sonnerToast.custom((id) => (
|
||||
<Toast id={id} type={props.type} description={props.description} />
|
||||
));
|
||||
}
|
||||
|
||||
function Toast(props: ToastProps) {
|
||||
const { id, type, description } = props;
|
||||
|
||||
return (
|
||||
<div className="flex w-full toast-mobile:w-[356px] justify-center">
|
||||
<div
|
||||
data-testid="toast"
|
||||
key={id}
|
||||
className="bg-zinc-100 p-3 rounded-lg w-full toast-mobile:w-fit flex flex-row gap-2 items-center"
|
||||
>
|
||||
<div
|
||||
data-type={type}
|
||||
className="data-[type=error]:text-red-600 data-[type=success]:text-green-600"
|
||||
>
|
||||
{iconsByType[type]}
|
||||
</div>
|
||||
<div className="text-zinc-950 text-sm">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ToastProps {
|
||||
id: string | number;
|
||||
type: 'success' | 'error';
|
||||
description: string;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue