diff --git a/app/search/page.tsx b/app/search/page.tsx index 060dadf..a508a09 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -16,9 +16,9 @@ import ReactMarkdown from 'react-markdown'; import { useTheme } from 'next-themes'; import Marked, { ReactRenderer } from 'marked-react'; import { track } from '@vercel/analytics'; -import { useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useChat } from 'ai/react'; -import { ToolInvocation } from 'ai'; +import { Message, ToolInvocation } from 'ai'; import { toast } from 'sonner'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; @@ -118,13 +118,20 @@ interface Attachment { } const HomeContent = () => { + const router = useRouter(); const searchParams = useSearchParams(); const initialQuery = searchParams.get('query') || ''; const initialModel = searchParams.get('model') || 'azure:gpt4o-mini'; - const lastSubmittedQueryRef = useRef(initialQuery); - const [hasSubmitted, setHasSubmitted] = useState(!!initialQuery); - const [selectedModel, setSelectedModel] = useState(initialModel); + // Memoize initial values to prevent re-calculation + const initialState = useMemo(() => ({ + 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(null); const [suggestedQuestions, setSuggestedQuestions] = useState([]); const [isEditingMessage, setIsEditingMessage] = useState(false); @@ -132,6 +139,7 @@ const HomeContent = () => { const [attachments, setAttachments] = useState([]); const fileInputRef = useRef(null); const inputRef = useRef(null); + const initializedRef = useRef(false); 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 { theme, setTheme } = useTheme(); @@ -219,10 +240,9 @@ const HomeContent = () => { id: "1", title: "New Updates!", images: [ - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-maps-beta.png", - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-run.png", - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-results.png", - "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-new-claude.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-nearby-search-maps-demo.png", + "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-multi-search-demo.png" ], content: `## **Nearby Map Search Beta** @@ -283,8 +303,12 @@ The new Anthropic models: Claude 3.5 Sonnet and 3.5 Haiku models are now availab

{changelog.title}

, - p: ({ node, className, ...props }) =>

, + h2: ({ node, className, ...props }) => ( +

+ ), + p: ({ node, className, ...props }) => ( +

+ ), }} className="text-sm" > diff --git a/components/ui/form-component.tsx b/components/ui/form-component.tsx index 6c4f757..d94fa9b 100644 --- a/components/ui/form-component.tsx +++ b/components/ui/form-component.tsx @@ -24,10 +24,10 @@ interface ModelSwitcherProps { } const models = [ - { value: "azure:gpt4o-mini", label: "GPT-4o Mini", icon: Zap, description: "God speed, good quality", color: "emerald" }, - { 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-sonnet-latest", label: "Claude 3.5 Sonnet (New)", icon: Sparkles, description: "High quality, good speed", color: "indigo" }, - { value: "azure:gpt-4o", label: "GPT-4o", icon: Cpu, description: "Higher quality, normal speed", color: "blue" }, + { 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", vision: false }, + { 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", vision: true }, ]; @@ -71,13 +71,13 @@ const ModelSwitcher: React.FC = ({ selectedModel, setSelecte "flex items-center justify-center w-8 h-8 rounded-full transition-all duration-300", getColorClasses(selectedModelData.color, true), "focus:outline-none focus:ring-2 focus:ring-opacity-50", - `focus:ring-${selectedModelData.color}-500`, + `!focus:ring-${selectedModelData.color}-500`, className )} > - + {models.map((model) => ( { const MAX_IMAGES = 3; +const hasVisionSupport = (modelValue: string): boolean => { + const selectedModel = models.find(model => model.value === modelValue); + return selectedModel?.vision === true +}; + interface FormComponentProps { input: string; setInput: (input: string) => void; @@ -312,23 +317,18 @@ const FormComponent: React.FC = ({ const [uploadQueue, setUploadQueue] = useState>([]); const { width } = useWindowSize(); const postSubmitFileInputRef = useRef(null); - - 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 [isFocused, setIsFocused] = useState(false); const handleInput = (event: React.ChangeEvent) => { setInput(event.target.value); - adjustHeight(); + }; + + const handleFocus = () => { + setIsFocused(true); + }; + + const handleBlur = () => { + setIsFocused(false); }; const uploadFile = async (file: File): Promise => { @@ -392,7 +392,7 @@ const FormComponent: React.FC = ({ if (input.trim() || attachments.length > 0) { setHasSubmitted(true); lastSubmittedQueryRef.current = input.trim(); - track("search input", {query: input.trim()}) + track("search input", { query: input.trim() }) handleSubmit(event, { experimental_attachments: attachments, @@ -432,7 +432,8 @@ const FormComponent: React.FC = ({ return (

0 || uploadQueue.length > 0 ? "bg-gray-100/70 dark:bg-neutral-800 p-1" : "bg-transparent" @@ -473,14 +474,16 @@ const FormComponent: React.FC = ({ value={input} onChange={handleInput} disabled={isLoading} + onFocus={handleFocus} + onBlur={handleBlur} 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", "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", - "pr-20 py-2" + "px-4 pt-3 pb-10" // Increased bottom padding to prevent overlap )} rows={3} onKeyDown={(event) => { @@ -495,49 +498,53 @@ const FormComponent: React.FC = ({ }} /> -
+
-
-
- +
+ {hasVisionSupport(selectedModel) && ( + + )} - {isLoading ? ( - - ) : ( - - )} + {isLoading ? ( + + ) : ( + + )} +