feat: fixed search parameter functionality and form component

This commit is contained in:
zaidmukaddam 2024-11-26 22:05:14 +05:30
parent 489e6b556b
commit d7434ce63d
2 changed files with 105 additions and 74 deletions

View File

@ -16,9 +16,9 @@ import ReactMarkdown from 'react-markdown';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
import Marked, { ReactRenderer } from 'marked-react'; import Marked, { ReactRenderer } from 'marked-react';
import { track } from '@vercel/analytics'; import { track } from '@vercel/analytics';
import { useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { useChat } from 'ai/react'; import { useChat } from 'ai/react';
import { ToolInvocation } from 'ai'; import { Message, ToolInvocation } from 'ai';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
@ -118,13 +118,20 @@ interface Attachment {
} }
const HomeContent = () => { const HomeContent = () => {
const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const initialQuery = searchParams.get('query') || ''; const initialQuery = searchParams.get('query') || '';
const initialModel = searchParams.get('model') || 'azure:gpt4o-mini'; const initialModel = searchParams.get('model') || 'azure:gpt4o-mini';
const lastSubmittedQueryRef = useRef(initialQuery); // Memoize initial values to prevent re-calculation
const [hasSubmitted, setHasSubmitted] = useState(!!initialQuery); const initialState = useMemo(() => ({
const [selectedModel, setSelectedModel] = useState(initialModel); query: searchParams.get('query') || '',
model: searchParams.get('model') || 'azure:gpt4o-mini'
}), []); // Empty dependency array as we only want this on mount
const lastSubmittedQueryRef = useRef(initialState.query);
const [hasSubmitted, setHasSubmitted] = useState(() => !!initialState.query);
const [selectedModel, setSelectedModel] = useState(initialState.model);
const bottomRef = useRef<HTMLDivElement>(null); const bottomRef = useRef<HTMLDivElement>(null);
const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([]); const [suggestedQuestions, setSuggestedQuestions] = useState<string[]>([]);
const [isEditingMessage, setIsEditingMessage] = useState(false); const [isEditingMessage, setIsEditingMessage] = useState(false);
@ -132,6 +139,7 @@ const HomeContent = () => {
const [attachments, setAttachments] = useState<Attachment[]>([]); const [attachments, setAttachments] = useState<Attachment[]>([]);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const initializedRef = useRef(false);
const { theme } = useTheme(); const { theme } = useTheme();
@ -162,6 +170,19 @@ const HomeContent = () => {
}, },
}); });
useEffect(() => {
if (!initializedRef.current && initialState.query && !messages.length) {
initializedRef.current = true;
setHasSubmitted(true);
console.log("[initial query]:", initialState.query);
append({
content: initialState.query,
role: 'user'
});
}
}, [initialState.query, append, setInput, messages.length]);
const ThemeToggle: React.FC = () => { const ThemeToggle: React.FC = () => {
const { theme, setTheme } = useTheme(); const { theme, setTheme } = useTheme();
@ -219,10 +240,9 @@ const HomeContent = () => {
id: "1", id: "1",
title: "New Updates!", title: "New Updates!",
images: [ images: [
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-maps-beta.png", "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-new-claude-models.png",
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-run.png", "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-nearby-search-maps-demo.png",
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-results.png", "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-search-demo.png"
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-new-claude.png"
], ],
content: content:
`## **Nearby Map Search Beta** `## **Nearby Map Search Beta**
@ -283,8 +303,12 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab
<h3 className="text-2xl font-medium font-serif text-neutral-800 dark:text-neutral-100">{changelog.title}</h3> <h3 className="text-2xl font-medium font-serif text-neutral-800 dark:text-neutral-100">{changelog.title}</h3>
<ReactMarkdown <ReactMarkdown
components={{ components={{
h2: ({ node, className, ...props }) => <h2 {...props} className={cn(className, "my-1 text-neutral-800 dark:text-neutral-100")} />, h2: ({ node, className, ...props }) => (
p: ({ node, className, ...props }) => <p {...props} className={cn(className, "mb-2 text-neutral-700 dark:text-neutral-300")} />, <h2 {...props} className={cn("my-2 text-lg font-medium text-neutral-800 dark:text-neutral-100", className)} />
),
p: ({ node, className, ...props }) => (
<p {...props} className={cn("mb-3 text-neutral-700 dark:text-neutral-300 leading-relaxed", className)} />
),
}} }}
className="text-sm" className="text-sm"
> >

View File

@ -24,10 +24,10 @@ interface ModelSwitcherProps {
} }
const models = [ const models = [
{ value: "azure:gpt4o-mini", label: "GPT-4o Mini", icon: Zap, description: "God speed, good quality", color: "emerald" }, { value: "azure:gpt4o-mini", label: "GPT-4o Mini", icon: Zap, description: "God speed, good quality", color: "emerald", vision: true },
{ value: "anthropic:claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku", icon: Sparkles, description: "Good quality, high speed", color: "orange" }, { value: "anthropic:claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku", icon: Sparkles, description: "Good quality, high speed", color: "orange", vision: false },
{ value: "anthropic:claude-3-5-sonnet-latest", label: "Claude 3.5 Sonnet (New)", icon: Sparkles, description: "High quality, good speed", color: "indigo" }, { value: "anthropic:claude-3-5-sonnet-latest", label: "Claude 3.5 Sonnet (New)", icon: Sparkles, description: "High quality, good speed", color: "indigo", vision: true },
{ value: "azure:gpt-4o", label: "GPT-4o", icon: Cpu, description: "Higher quality, normal speed", color: "blue" }, { value: "azure:gpt-4o", label: "GPT-4o", icon: Cpu, description: "Higher quality, normal speed", color: "blue", vision: true },
]; ];
@ -71,13 +71,13 @@ const ModelSwitcher: React.FC<ModelSwitcherProps> = ({ selectedModel, setSelecte
"flex items-center justify-center w-8 h-8 rounded-full transition-all duration-300", "flex items-center justify-center w-8 h-8 rounded-full transition-all duration-300",
getColorClasses(selectedModelData.color, true), getColorClasses(selectedModelData.color, true),
"focus:outline-none focus:ring-2 focus:ring-opacity-50", "focus:outline-none focus:ring-2 focus:ring-opacity-50",
`focus:ring-${selectedModelData.color}-500`, `!focus:ring-${selectedModelData.color}-500`,
className className
)} )}
> >
<selectedModelData.icon className="w-4 h-4" /> <selectedModelData.icon className="w-4 h-4" />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-[220px] p-1 !font-sans rounded-md shadow-md bg-white dark:bg-neutral-800 ml-4 sm:m-auto"> <DropdownMenuContent className="w-[220px] p-1 !font-sans rounded-md shadow-md bg-white dark:bg-neutral-800 ml-4 !mt-0 sm:m-auto !z-[52]">
{models.map((model) => ( {models.map((model) => (
<DropdownMenuItem <DropdownMenuItem
key={model.value} key={model.value}
@ -170,6 +170,11 @@ const PaperclipIcon = ({ size = 16 }: { size?: number }) => {
const MAX_IMAGES = 3; const MAX_IMAGES = 3;
const hasVisionSupport = (modelValue: string): boolean => {
const selectedModel = models.find(model => model.value === modelValue);
return selectedModel?.vision === true
};
interface FormComponentProps { interface FormComponentProps {
input: string; input: string;
setInput: (input: string) => void; setInput: (input: string) => void;
@ -312,23 +317,18 @@ const FormComponent: React.FC<FormComponentProps> = ({
const [uploadQueue, setUploadQueue] = useState<Array<string>>([]); const [uploadQueue, setUploadQueue] = useState<Array<string>>([]);
const { width } = useWindowSize(); const { width } = useWindowSize();
const postSubmitFileInputRef = useRef<HTMLInputElement>(null); const postSubmitFileInputRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
const adjustHeight = useCallback(() => {
if (inputRef.current) {
inputRef.current.style.height = "auto";
inputRef.current.style.height = `${inputRef.current.scrollHeight + 2}px`;
}
}, [inputRef]);
useEffect(() => {
if (inputRef.current) {
adjustHeight();
}
}, [adjustHeight, input, inputRef]);
const handleInput = (event: React.ChangeEvent<HTMLTextAreaElement>) => { const handleInput = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setInput(event.target.value); setInput(event.target.value);
adjustHeight(); };
const handleFocus = () => {
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false);
}; };
const uploadFile = async (file: File): Promise<Attachment> => { const uploadFile = async (file: File): Promise<Attachment> => {
@ -392,7 +392,7 @@ const FormComponent: React.FC<FormComponentProps> = ({
if (input.trim() || attachments.length > 0) { if (input.trim() || attachments.length > 0) {
setHasSubmitted(true); setHasSubmitted(true);
lastSubmittedQueryRef.current = input.trim(); lastSubmittedQueryRef.current = input.trim();
track("search input", {query: input.trim()}) track("search input", { query: input.trim() })
handleSubmit(event, { handleSubmit(event, {
experimental_attachments: attachments, experimental_attachments: attachments,
@ -432,7 +432,8 @@ const FormComponent: React.FC<FormComponentProps> = ({
return ( return (
<div className={cn( <div className={cn(
"relative w-full flex flex-col gap-2 rounded-lg transition-all duration-300 z-[99]", "relative w-full flex flex-col gap-2 rounded-lg transition-all duration-300 z-[51]",
attachments.length > 0 || uploadQueue.length > 0 attachments.length > 0 || uploadQueue.length > 0
? "bg-gray-100/70 dark:bg-neutral-800 p-1" ? "bg-gray-100/70 dark:bg-neutral-800 p-1"
: "bg-transparent" : "bg-transparent"
@ -473,14 +474,16 @@ const FormComponent: React.FC<FormComponentProps> = ({
value={input} value={input}
onChange={handleInput} onChange={handleInput}
disabled={isLoading} disabled={isLoading}
onFocus={handleFocus}
onBlur={handleBlur}
className={cn( className={cn(
"min-h-[48px] overflow-hidden resize-none rounded-lg text-base", "min-h-[40px] max-h-[200px] w-full resize-none rounded-lg",
"overflow-y-auto overflow-x-hidden",
"text-base leading-relaxed",
"bg-neutral-100 dark:bg-neutral-900", "bg-neutral-100 dark:bg-neutral-900",
"text-neutral-900 dark:text-neutral-100", "text-neutral-900 dark:text-neutral-100",
"border border-neutral-200 dark:border-neutral-700",
"focus:border-neutral-300 dark:focus:border-neutral-600",
"focus:ring-2 focus:ring-neutral-300 dark:focus:ring-neutral-600", "focus:ring-2 focus:ring-neutral-300 dark:focus:ring-neutral-600",
"pr-20 py-2" "px-4 pt-3 pb-10" // Increased bottom padding to prevent overlap
)} )}
rows={3} rows={3}
onKeyDown={(event) => { onKeyDown={(event) => {
@ -495,49 +498,53 @@ const FormComponent: React.FC<FormComponentProps> = ({
}} }}
/> />
<div className="absolute left-2 bottom-2 mt-4"> <div className={cn("absolute bottom-0 inset-x-0 flex justify-between items-center rounded-b-lg p-2 bg-neutral-100 dark:bg-neutral-900",
"border border-t-0",
)}>
<ModelSwitcher <ModelSwitcher
selectedModel={selectedModel} selectedModel={selectedModel}
setSelectedModel={setSelectedModel} setSelectedModel={setSelectedModel}
/> />
</div>
<div className="absolute right-2 bottom-2 flex items-center gap-2 mt-4"> <div className="flex items-center gap-2">
<Button {hasVisionSupport(selectedModel) && (
className="rounded-full p-1.5 h-8 w-8 bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-300 dark:hover:bg-neutral-600" <Button
onClick={(event) => { className="rounded-full p-1.5 h-8 w-8 bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-300 dark:hover:bg-neutral-600"
event.preventDefault(); onClick={(event) => {
triggerFileInput(); event.preventDefault();
}} triggerFileInput();
variant="outline" }}
disabled={isLoading} variant="outline"
> disabled={isLoading}
<PaperclipIcon size={14} /> >
</Button> <PaperclipIcon size={14} />
</Button>
)}
{isLoading ? ( {isLoading ? (
<Button <Button
className="rounded-full p-1.5 h-8 w-8" className="rounded-full p-1.5 h-8 w-8"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
stop(); stop();
}} }}
variant="destructive" variant="destructive"
> >
<StopIcon size={14} /> <StopIcon size={14} />
</Button> </Button>
) : ( ) : (
<Button <Button
className="rounded-full p-1.5 h-8 w-8 " className="rounded-full p-1.5 h-8 w-8"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
submitForm(); submitForm();
}} }}
disabled={input.length === 0 && attachments.length === 0 || uploadQueue.length > 0} disabled={input.length === 0 && attachments.length === 0 || uploadQueue.length > 0}
> >
<ArrowUpIcon size={14} /> <ArrowUpIcon size={14} />
</Button> </Button>
)} )}
</div>
</div> </div>
</div> </div>
</div> </div>