MingruiZhang's picture
fix styles
9dbd434
raw
history blame
5.3 kB
'use client';
import * as React from 'react';
import { type UseChatHelpers } from 'ai/react';
import Textarea from 'react-textarea-autosize';
import { Button } from '@/components/ui/Button';
import { MessageBase } from '../../lib/types';
import { useEnterSubmit } from '@/lib/hooks/useEnterSubmit';
import Img from '../ui/Img';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/Tooltip';
import {
IconArrowDown,
IconArrowElbow,
IconImage,
IconRefresh,
IconStop,
} from '@/components/ui/Icons';
import { cn } from '@/lib/utils';
import { generateInputImageMarkdown } from '@/lib/messageUtils';
import { Switch } from '../ui/Switch';
import Chip from '../ui/Chip';
export interface ComposerProps
extends Pick<
UseChatHelpers,
'append' | 'isLoading' | 'reload' | 'stop' | 'input' | 'setInput'
> {
id?: string;
title?: string;
messages: MessageBase[];
mediaUrl?: string;
}
export function Composer({
id,
isLoading,
append,
input,
setInput,
mediaUrl,
// isAtBottom,
}: ComposerProps) {
const { formRef, onKeyDown } = useEnterSubmit();
const inputRef = React.useRef<HTMLTextAreaElement>(null);
React.useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
const mediaName = mediaUrl?.split('/').pop();
return (
<div className="size-full mx-auto max-w-2xl px-6 py-3 space-y-4 bg-zinc-700 rounded-xl relative shadow-lg shadow-zinc-800/40">
{mediaUrl && (
<Tooltip>
<TooltipTrigger>
<Chip>
<div className="flex flex-row items-center">
<IconImage className="size-3 mr-1" />
<p>{mediaName ?? 'media(0)'}</p>
</div>
</Chip>
</TooltipTrigger>
<TooltipContent sideOffset={20}>
<Img
src={mediaUrl}
className="m-1"
quality={100}
alt="zoomed-in-image"
/>
</TooltipContent>
</Tooltip>
)}
<form
onSubmit={async e => {
e.preventDefault();
if (!input?.trim()) {
return;
}
setInput('');
await append({
id,
content:
input +
(mediaUrl ? '\n\n' + generateInputImageMarkdown(mediaUrl) : ''),
role: 'user',
});
}}
ref={formRef}
className="h-full"
>
{/* <div className="border-gray-500 flex overflow-hidden size-full flex flex-row items-center"> */}
<Textarea
ref={inputRef}
tabIndex={0}
onKeyDown={onKeyDown}
rows={1}
value={input}
disabled={isLoading}
onChange={e => setInput(e.target.value)}
placeholder={isLoading ? '🤖 ✨ ...' : 'Message Vision Agent'}
spellCheck={false}
className="w-full grow resize-none bg-transparent focus-within:outline-none text-sm"
/>
{/* Stop / Regenerate Icon */}
{/* <div className="absolute bottom-14 right-4">
{isLoading ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="bg-background"
onClick={() => stop()}
>
<IconStop />
</Button>
</TooltipTrigger>
<TooltipContent>Stop generating</TooltipContent>
</Tooltip>
) : (
messages?.length >= 2 && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="bg-background"
onClick={() => reload()}
>
<IconRefresh />
</Button>
</TooltipTrigger>
<TooltipContent>Regenerate response</TooltipContent>
</Tooltip>
)
)}
</div> */}
{/* </div> */}
{/* Submit Icon */}
<Tooltip>
<TooltipTrigger asChild>
<Button
type="submit"
size="icon"
className="size-6 absolute bottom-3 right-3"
disabled={isLoading || input === ''}
>
<IconArrowElbow className="size-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Send message</TooltipContent>
</Tooltip>
</form>
{/* Scroll to bottom Icon */}
{/* <Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
className={cn(
'absolute top-1 right-3 transition-opacity duration-300 size-6',
isAtBottom ? 'opacity-0' : 'opacity-100',
)}
onClick={() => scrollToBottom()}
>
<IconArrowDown className="size-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Scroll to bottom</TooltipContent>
</Tooltip> */}
</div>
);
}