Arun Dass

AI Prompt Input Box

Component 2

About

AI Prompt Input Box

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds AI Prompt Input Box to your project and fits it to your theme.

Code

How AI Prompt Input Box is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import React, { useState, useRef, useEffect } from 'react';
import {
  Paperclip,
  Mic,
  ArrowUp,
  Globe,
  BrainCircuit,
  ChevronDown,
  Square,
  Check,
  Folder
} from 'lucide-react';

const MODELS = [
  { id: 'opus', name: 'Opus 4.8', icon: 'bg-purple-400', shadow: 'shadow-[0_0_8px_rgba(192,132,252,0.5)]' },
  { id: 'gpt', name: 'GPT 5.5', icon: 'bg-emerald-400', shadow: 'shadow-[0_0_8px_rgba(52,211,153,0.5)]' },
  { id: 'gemini', name: 'Gemini 3.1 Pro', icon: 'bg-blue-400', shadow: 'shadow-[0_0_8px_rgba(96,165,250,0.5)]' }
];

export const AiChatInput = () => {
  const [value, setValue] = useState('');
  const [isFocused, setIsFocused] = useState(false);
  const [activeModel, setActiveModel] = useState(MODELS[0]);
  const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false);
  const [isWebSearchActive, setIsWebSearchActive] = useState(false);
  const [isReasoningActive, setIsReasoningActive] = useState(true);
  const [isRecording, setIsRecording] = useState(false);
  const [recordingTime, setRecordingTime] = useState(0);

  const textareaRef = useRef<HTMLTextAreaElement>(null);

  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    if (isRecording) {
      interval = setInterval(() => {
        setRecordingTime((prev) => prev + 1);
      }, 1000);
    } else {
      setRecordingTime(0);
    }
    return () => clearInterval(interval);
  }, [isRecording]);

  const formatTime = (seconds: number) => {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  };

 
  useEffect(() => {
    if (textareaRef.current) {
      textareaRef.current.style.height = 'inherit';
      const scrollHeight = textareaRef.current.scrollHeight;
      textareaRef.current.style.height = `${Math.min(scrollHeight, 200)}px`;
    }
  }, [value, isRecording]);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      if (value.trim()) {
        setValue('');
      }
    }
  };

  const toggleRecord = () => {
    setIsRecording(!isRecording);
    if (!isRecording) {
      setValue(''); 
    }
  };

  return (
    <div className="relative w-full h-full min-h-screen flex items-center justify-center p-4 sm:p-8 font-sans overflow-hidden bg-black/95">

      
      <div className="absolute inset-0 overflow-hidden pointer-events-none">
        <div className="absolute top-[20%] left-[30%] w-[500px] h-[500px] bg-indigo-500/10 rounded-full blur-[120px] mix-blend-screen" />
        <div className="absolute bottom-[20%] right-[20%] w-[600px] h-[600px] bg-purple-500/10 rounded-full blur-[130px] mix-blend-screen" />
        <div className="absolute top-[50%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[800px] h-[300px] bg-blue-500/10 rounded-full blur-[150px] mix-blend-screen" />

       
        <div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-[0.03] mix-blend-overlay" />
        <div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_20%,transparent_100%)]" />
      </div>

      
      <style>{`
        @keyframes audio-wave {
          0%, 100% { height: 6px; }
          50% { height: 24px; }
        }
      `}</style>

      
      <div className="w-full max-w-2xl relative z-10 flex flex-col gap-4">

        
        <div
          className={`relative flex flex-col w-full rounded-[24px] transition-all duration-300 ease-out
            ${isFocused || isRecording || isModelSelectorOpen
              ? 'bg-zinc-900/80 backdrop-blur-2xl border border-indigo-500/40 shadow-[0_0_30px_rgba(99,102,241,0.15)] ring-4 ring-indigo-500/10'
              : 'bg-zinc-900/60 backdrop-blur-xl border border-white/10 shadow-2xl hover:bg-zinc-900/70 hover:border-white/15'
            }
          `}
        >
          

        
          <div className="pl-5 pr-3 pt-4 pb-0 relative">
            {isRecording ? (
              <div className="flex items-center gap-3 h-[44px] px-2 animate-in fade-in duration-300">
                <span className="w-2 h-2 rounded-full bg-rose-500 animate-pulse shadow-[0_0_10px_rgba(244,63,94,0.5)] mt-[2px]" />
                <span className="text-sm font-medium text-rose-400/90 mt-[1px]">Listening...</span>
                <span className="text-sm font-medium text-rose-400/60 mt-[1px] ml-1">{formatTime(recordingTime)}</span>

              
                <div className="flex items-center gap-[3px] ml-2 h-[24px]">
                  <div className="w-1 bg-rose-400/60 rounded-full" style={{ animation: 'audio-wave 1.2s ease-in-out infinite 0.0s' }} />
                  <div className="w-1 bg-rose-400/80 rounded-full" style={{ animation: 'audio-wave 1.2s ease-in-out infinite 0.2s' }} />
                  <div className="w-1 bg-rose-400/60 rounded-full" style={{ animation: 'audio-wave 1.2s ease-in-out infinite 0.4s' }} />
                  <div className="w-1 bg-rose-400/80 rounded-full" style={{ animation: 'audio-wave 1.2s ease-in-out infinite 0.1s' }} />
                  <div className="w-1 bg-rose-400/60 rounded-full" style={{ animation: 'audio-wave 1.2s ease-in-out infinite 0.3s' }} />
                </div>
              </div>
            ) : (
              <textarea
                ref={textareaRef}
                value={value}
                onChange={(e) => setValue(e.target.value)}
                onFocus={() => {
                  setIsFocused(true);
                  setIsModelSelectorOpen(false);
                }}
                onBlur={() => setIsFocused(false)}
                onKeyDown={handleKeyDown}
                placeholder="Start typing or record to ask for anything"
                className="w-full resize-none bg-transparent outline-none text-white/90 text-[14px] leading-[20px] placeholder:text-white/30 custom-scrollbar"
                style={{
                  minHeight: '24px',
                  maxHeight: '200px'
                }}
              />
            )}
          </div>

         
          <div className="flex items-center justify-between px-2 pb-2 pt-0">

          
            <div className="flex items-center gap-0.5">
              <button
                className="p-1.5 rounded-lg text-white/40 hover:text-white/90 hover:bg-white/10 transition-all duration-200 hover:scale-105 active:scale-95"
                title="Attach files"
              >
                <Paperclip className="w-4 h-4" />
              </button>

              <button
                className="p-1.5 rounded-lg text-white/40 hover:text-white/90 hover:bg-white/10 transition-all duration-200 hover:scale-105 active:scale-95"
                title="Browse folders & media"
              >
                <Folder className="w-4 h-4" />
              </button>

              <button
                className={`flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 hover:scale-105 active:scale-95 ml-1
                  ${isWebSearchActive
                    ? 'bg-blue-500/15 text-blue-400 hover:bg-blue-500/25 border border-blue-500/20'
                    : 'text-white/40 hover:text-white/90 hover:bg-white/10 border border-transparent'
                  }
                `}
                onClick={() => setIsWebSearchActive(!isWebSearchActive)}
              >
                <Globe className="w-3.5 h-3.5" />
                <span className="hidden sm:inline">Search</span>
              </button>

              <button
                className={`flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 hover:scale-105 active:scale-95
                  ${isReasoningActive
                    ? 'bg-purple-500/15 text-purple-400 hover:bg-purple-500/25 border border-purple-500/20'
                    : 'text-white/40 hover:text-white/90 hover:bg-white/10 border border-transparent'
                  }
                `}
                onClick={() => setIsReasoningActive(!isReasoningActive)}
              >
                <BrainCircuit className="w-3.5 h-3.5" />
                <span className="hidden sm:inline">Reason</span>
              </button>
            </div>

            
            <div className="flex items-center gap-1.5">

              
              <div className="relative">
                <button
                  onClick={() => setIsModelSelectorOpen(!isModelSelectorOpen)}
                  className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-white/40 hover:text-white/90 hover:bg-white/10 transition-all duration-200 hover:scale-105 active:scale-95 text-[11px] font-medium"
                >
                  <span className={`w-1.5 h-1.5 rounded-full ${activeModel.icon} ${activeModel.shadow}`} />
                  <span className="hidden sm:inline">{activeModel.name}</span>
                  <ChevronDown className="w-3 h-3 ml-0.5 opacity-50" />
                </button>

                
                {isModelSelectorOpen && (
                  <div className="absolute bottom-full right-0 mb-2 w-48 bg-zinc-900/95 backdrop-blur-xl border border-white/10 rounded-xl shadow-2xl overflow-hidden z-50 animate-in fade-in slide-in-from-bottom-2 duration-200">
                    <div className="p-1.5 flex flex-col gap-0.5">
                      {MODELS.map((m) => (
                        <button
                          key={m.id}
                          onClick={() => {
                            setActiveModel(m);
                            setIsModelSelectorOpen(false);
                          }}
                          className="flex items-center justify-between w-full px-2.5 py-2 rounded-lg text-left text-xs font-medium hover:bg-white/10 transition-colors group"
                        >
                          <div className="flex items-center gap-2">
                            <span className={`w-1.5 h-1.5 rounded-full ${m.icon} shadow-[0_0_8px_rgba(255,255,255,0.2)]`} />
                            <span className={activeModel.id === m.id ? 'text-white' : 'text-white/70 group-hover:text-white/90'}>
                              {m.name}
                            </span>
                          </div>
                          {activeModel.id === m.id && <Check className="w-3.5 h-3.5 text-white/90" />}
                        </button>
                      ))}
                    </div>
                  </div>
                )}
              </div>

              {value.length === 0 && (
                <button
                  onClick={toggleRecord}
                  className={`flex items-center justify-center w-9 h-9 rounded-xl transition-all duration-300 hover:scale-105 active:scale-95
                    ${isRecording
                      ? 'text-rose-400 bg-rose-500/15 hover:bg-rose-500/25 shadow-[0_0_15px_rgba(244,63,94,0.15)] ring-1 ring-rose-500/30'
                      : 'text-white/40 hover:text-white/90 hover:bg-white/10'
                    }
                  `}
                  title={isRecording ? "Stop recording" : "Voice input"}
                >
                  {isRecording ? <Square className="w-5 h-5 fill-rose-400/20" /> : <Mic className="w-5 h-5" />}
                </button>
              )}

              {!isRecording && (
                <button
                  disabled={value.trim().length === 0}
                  className={`flex items-center justify-center w-9 h-9 rounded-xl transition-all duration-300 active:scale-95
                    ${value.trim().length > 0
                      ? 'bg-white text-black hover:bg-white/90 hover:scale-105 hover:shadow-[0_0_20px_rgba(255,255,255,0.3)] shadow-md cursor-pointer'
                      : 'bg-white/5 text-white/20 cursor-not-allowed'
                    }
                  `}
                >
                  <ArrowUp className="w-5 h-5" strokeWidth={2.5} />
                </button>
              )}
            </div>

          </div>
        </div>

      </div>
    </div>
  );
};

export default AiChatInput;

More by Arun Dass

View all
  • Dashboard Sidebar

    Dashboard Sidebar

    1.7K
  • AI Planning

    AI Planning

    453
  • AI Agent Processing States

    AI Agent Processing States

    183
  • Kanban Board

    Kanban Board

    160
  • FileUpload

    FileUpload

    155
  • Neomorphism Metric Card

    Neomorphism Metric Card

    88
  • Focus Hero

    Focus Hero

    38
  • Fuji

    Fuji

    1