ai-input-react 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useRateLimiter.ts","../src/hooks/useAudioRecorder.ts","../src/hooks/useAiInput.ts","../src/components/AiInput.tsx"],"names":["DEFAULT_OPTIONS","useState","useRef","useCallback","useEffect","error"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useState, useCallback, useRef, useEffect } from 'react'\r\nimport type { UseRateLimiterOptions, UseRateLimiterReturn } from '../types'\r\n\r\nconst DEFAULT_OPTIONS: UseRateLimiterOptions = {\r\n cooldownMs: 1000,\r\n maxRequests: 10,\r\n windowMs: 60000,\r\n}\r\n\r\n/**\r\n * Hook for soft rate limiting at the UI level.\r\n * Provides UX protection by tracking requests and enforcing cooldowns.\r\n * \r\n * Note: This is not a security measure. Actual rate limiting \r\n * should be handled by the AI provider or backend.\r\n * \r\n * @param options - Rate limiting configuration\r\n * @returns Rate limiter state and controls\r\n */\r\nexport function useRateLimiter(\r\n options: Partial<UseRateLimiterOptions> = {}\r\n): UseRateLimiterReturn {\r\n const config = { ...DEFAULT_OPTIONS, ...options }\r\n\r\n // Track request timestamps within the window\r\n const requestTimestamps = useRef<number[]>([])\r\n\r\n // Cooldown state\r\n const [cooldownEnd, setCooldownEnd] = useState<number>(0)\r\n const [cooldownRemaining, setCooldownRemaining] = useState<number>(0)\r\n\r\n // Force re-render for requestsRemaining updates\r\n const [, forceUpdate] = useState({})\r\n\r\n // Cleanup old timestamps and calculate remaining requests\r\n const cleanupAndCount = useCallback(() => {\r\n const now = Date.now()\r\n const windowStart = now - config.windowMs\r\n\r\n // Remove timestamps outside the window\r\n requestTimestamps.current = requestTimestamps.current.filter(\r\n (ts) => ts > windowStart\r\n )\r\n\r\n return config.maxRequests - requestTimestamps.current.length\r\n }, [config.windowMs, config.maxRequests])\r\n\r\n // Update cooldown remaining\r\n useEffect(() => {\r\n if (cooldownEnd <= Date.now()) {\r\n setCooldownRemaining(0)\r\n return\r\n }\r\n\r\n const interval = setInterval(() => {\r\n const remaining = Math.max(0, cooldownEnd - Date.now())\r\n setCooldownRemaining(remaining)\r\n\r\n if (remaining === 0) {\r\n clearInterval(interval)\r\n }\r\n }, 100)\r\n\r\n return () => clearInterval(interval)\r\n }, [cooldownEnd])\r\n\r\n // Check if request is allowed\r\n const canRequest = useCallback(() => {\r\n const now = Date.now()\r\n\r\n // Check cooldown\r\n if (now < cooldownEnd) {\r\n return false\r\n }\r\n\r\n // Check request count\r\n return cleanupAndCount() > 0\r\n }, [cooldownEnd, cleanupAndCount])\r\n\r\n // Record a request\r\n const recordRequest = useCallback(() => {\r\n const now = Date.now()\r\n\r\n // Add timestamp\r\n requestTimestamps.current.push(now)\r\n\r\n // Start cooldown\r\n const newCooldownEnd = now + config.cooldownMs\r\n setCooldownEnd(newCooldownEnd)\r\n setCooldownRemaining(config.cooldownMs)\r\n\r\n // Trigger re-render\r\n forceUpdate({})\r\n }, [config.cooldownMs])\r\n\r\n // Reset rate limiter\r\n const reset = useCallback(() => {\r\n requestTimestamps.current = []\r\n setCooldownEnd(0)\r\n setCooldownRemaining(0)\r\n forceUpdate({})\r\n }, [])\r\n\r\n return {\r\n canRequest: canRequest(),\r\n cooldownRemaining,\r\n requestsRemaining: cleanupAndCount(),\r\n recordRequest,\r\n reset,\r\n }\r\n}\r\n","import { useState, useCallback, useRef, useEffect } from 'react'\r\nimport type { UseAudioRecorderOptions, UseAudioRecorderReturn } from '../types'\r\n\r\nconst DEFAULT_OPTIONS: UseAudioRecorderOptions = {\r\n maxDurationMs: 60000, // 1 minute\r\n mimeTypes: ['audio/webm', 'audio/mp4', 'audio/ogg', 'audio/wav'],\r\n}\r\n\r\n/**\r\n * Get the best supported MIME type for MediaRecorder\r\n */\r\nfunction getSupportedMimeType(preferredTypes: string[]): string | null {\r\n if (typeof MediaRecorder === 'undefined') {\r\n return null\r\n }\r\n\r\n for (const mimeType of preferredTypes) {\r\n if (MediaRecorder.isTypeSupported(mimeType)) {\r\n return mimeType\r\n }\r\n }\r\n\r\n // Fallback to default\r\n return ''\r\n}\r\n\r\n/**\r\n * Hook for audio recording using Web APIs.\r\n * Uses navigator.mediaDevices, MediaRecorder, and Web Audio API for visualization.\r\n * \r\n * @param options - Audio recording configuration\r\n * @returns Audio recorder state and controls\r\n */\r\nexport function useAudioRecorder(\r\n options: Partial<UseAudioRecorderOptions> = {}\r\n): UseAudioRecorderReturn {\r\n const config = { ...DEFAULT_OPTIONS, ...options }\r\n\r\n const [isRecording, setIsRecording] = useState(false)\r\n const [duration, setDuration] = useState(0)\r\n const [audioBlob, setAudioBlob] = useState<Blob | null>(null)\r\n const [error, setError] = useState<Error | null>(null)\r\n const [audioLevels, setAudioLevels] = useState<number[]>([])\r\n\r\n const mediaRecorderRef = useRef<MediaRecorder | null>(null)\r\n const streamRef = useRef<MediaStream | null>(null)\r\n const chunksRef = useRef<Blob[]>([])\r\n const startTimeRef = useRef<number>(0)\r\n const durationIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\r\n const maxDurationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)\r\n\r\n // Web Audio API refs for visualization\r\n const audioContextRef = useRef<AudioContext | null>(null)\r\n const analyserRef = useRef<AnalyserNode | null>(null)\r\n const animationFrameRef = useRef<number | null>(null)\r\n\r\n // Check if audio recording is supported\r\n const isSupported = typeof navigator !== 'undefined'\r\n && 'mediaDevices' in navigator\r\n && 'getUserMedia' in navigator.mediaDevices\r\n && typeof MediaRecorder !== 'undefined'\r\n\r\n // Update audio levels from analyser\r\n const updateAudioLevels = useCallback(() => {\r\n if (!analyserRef.current) return\r\n\r\n const analyser = analyserRef.current\r\n const bufferLength = analyser.frequencyBinCount\r\n const dataArray = new Uint8Array(bufferLength)\r\n analyser.getByteFrequencyData(dataArray)\r\n\r\n // Sample 12 bars from the frequency data\r\n const bars = 12\r\n const step = Math.floor(bufferLength / bars)\r\n const levels: number[] = []\r\n\r\n for (let i = 0; i < bars; i++) {\r\n // Average a range of frequencies for each bar\r\n let sum = 0\r\n for (let j = 0; j < step; j++) {\r\n sum += dataArray[i * step + j]\r\n }\r\n // Normalize to 0-1 range\r\n levels.push((sum / step) / 255)\r\n }\r\n\r\n setAudioLevels(levels)\r\n\r\n // Continue animation loop\r\n animationFrameRef.current = requestAnimationFrame(updateAudioLevels)\r\n }, [])\r\n\r\n // Cleanup function\r\n const cleanup = useCallback(() => {\r\n if (durationIntervalRef.current) {\r\n clearInterval(durationIntervalRef.current)\r\n durationIntervalRef.current = null\r\n }\r\n\r\n if (maxDurationTimeoutRef.current) {\r\n clearTimeout(maxDurationTimeoutRef.current)\r\n maxDurationTimeoutRef.current = null\r\n }\r\n\r\n if (animationFrameRef.current) {\r\n cancelAnimationFrame(animationFrameRef.current)\r\n animationFrameRef.current = null\r\n }\r\n\r\n if (audioContextRef.current) {\r\n audioContextRef.current.close()\r\n audioContextRef.current = null\r\n }\r\n\r\n if (streamRef.current) {\r\n streamRef.current.getTracks().forEach((track) => track.stop())\r\n streamRef.current = null\r\n }\r\n\r\n analyserRef.current = null\r\n mediaRecorderRef.current = null\r\n chunksRef.current = []\r\n setAudioLevels([])\r\n }, [])\r\n\r\n // Stop recording\r\n const stopRecording = useCallback(() => {\r\n if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') {\r\n mediaRecorderRef.current.stop()\r\n }\r\n setIsRecording(false)\r\n }, [])\r\n\r\n // Start recording\r\n const startRecording = useCallback(async () => {\r\n if (!isSupported) {\r\n setError(new Error('Audio recording is not supported in this browser'))\r\n return\r\n }\r\n\r\n // Reset state\r\n setError(null)\r\n setAudioBlob(null)\r\n setDuration(0)\r\n setAudioLevels([])\r\n chunksRef.current = []\r\n\r\n try {\r\n // Get microphone access\r\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\r\n streamRef.current = stream\r\n\r\n // Set up Web Audio API for visualization\r\n const audioContext = new AudioContext()\r\n audioContextRef.current = audioContext\r\n\r\n const source = audioContext.createMediaStreamSource(stream)\r\n const analyser = audioContext.createAnalyser()\r\n analyser.fftSize = 256\r\n analyser.smoothingTimeConstant = 0.8\r\n source.connect(analyser)\r\n analyserRef.current = analyser\r\n\r\n // Get supported MIME type\r\n const mimeType = getSupportedMimeType(config.mimeTypes)\r\n\r\n // Create MediaRecorder\r\n const mediaRecorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined)\r\n mediaRecorderRef.current = mediaRecorder\r\n\r\n // Handle data available\r\n mediaRecorder.ondataavailable = (event) => {\r\n if (event.data.size > 0) {\r\n chunksRef.current.push(event.data)\r\n }\r\n }\r\n\r\n // Handle recording stop\r\n mediaRecorder.onstop = () => {\r\n const blob = new Blob(chunksRef.current, {\r\n type: mimeType || 'audio/webm'\r\n })\r\n setAudioBlob(blob)\r\n\r\n // Call callback if provided\r\n if (config.onRecordingComplete) {\r\n config.onRecordingComplete(blob)\r\n }\r\n\r\n cleanup()\r\n }\r\n\r\n // Handle errors\r\n mediaRecorder.onerror = () => {\r\n setError(new Error('Recording error occurred'))\r\n setIsRecording(false)\r\n cleanup()\r\n }\r\n\r\n // Start recording\r\n mediaRecorder.start(100) // Collect data every 100ms\r\n startTimeRef.current = Date.now()\r\n setIsRecording(true)\r\n\r\n // Start audio level visualization\r\n updateAudioLevels()\r\n\r\n // Update duration every 100ms\r\n durationIntervalRef.current = setInterval(() => {\r\n setDuration(Date.now() - startTimeRef.current)\r\n }, 100)\r\n\r\n // Auto-stop at max duration\r\n maxDurationTimeoutRef.current = setTimeout(() => {\r\n stopRecording()\r\n }, config.maxDurationMs)\r\n\r\n } catch (err) {\r\n const errorMessage = err instanceof Error\r\n ? err.message\r\n : 'Failed to access microphone'\r\n setError(new Error(errorMessage))\r\n cleanup()\r\n }\r\n }, [isSupported, config.mimeTypes, config.maxDurationMs, config.onRecordingComplete, cleanup, stopRecording, updateAudioLevels])\r\n\r\n // Cancel recording (discard audio)\r\n const cancelRecording = useCallback(() => {\r\n cleanup()\r\n setIsRecording(false)\r\n setDuration(0)\r\n setAudioBlob(null)\r\n }, [cleanup])\r\n\r\n // Reset hook state\r\n const reset = useCallback(() => {\r\n cleanup()\r\n setIsRecording(false)\r\n setDuration(0)\r\n setAudioBlob(null)\r\n setError(null)\r\n setAudioLevels([])\r\n }, [cleanup])\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n cleanup()\r\n }\r\n }, [cleanup])\r\n\r\n return {\r\n isRecording,\r\n isSupported,\r\n duration,\r\n audioBlob,\r\n audioLevels,\r\n error,\r\n startRecording,\r\n stopRecording,\r\n cancelRecording,\r\n reset,\r\n }\r\n}\r\n","import { useState, useCallback, useEffect, useRef } from 'react'\r\nimport { useRateLimiter } from './useRateLimiter'\r\nimport { useAudioRecorder } from './useAudioRecorder'\r\nimport type {\r\n UseAiInputOptions,\r\n UseAiInputReturn,\r\n AiInputState,\r\n RateLimitConfig,\r\n AudioConfig,\r\n} from '../types'\r\n\r\nconst DEFAULT_RATE_LIMIT: RateLimitConfig = {\r\n cooldownMs: 1000,\r\n maxRequests: 10,\r\n windowMs: 60000,\r\n}\r\n\r\nconst DEFAULT_AUDIO_CONFIG: AudioConfig = {\r\n maxDurationMs: 60000,\r\n mimeTypes: ['audio/webm', 'audio/mp4', 'audio/ogg', 'audio/wav'],\r\n}\r\n\r\n/**\r\n * Main hook for AI input functionality.\r\n * Combines rate limiting, audio recording, and API communication.\r\n * Unified design - text and audio in single component.\r\n * \r\n * @param options - Configuration options\r\n * @returns Complete state and controls for AI input\r\n */\r\nexport function useAiInput(options: UseAiInputOptions): UseAiInputReturn {\r\n const {\r\n send,\r\n sendAudio,\r\n rateLimit = {},\r\n audioConfig = {},\r\n onSuccess,\r\n onError,\r\n onTranscription,\r\n } = options\r\n\r\n const rateLimitConfig = { ...DEFAULT_RATE_LIMIT, ...rateLimit }\r\n const audioConfigMerged = { ...DEFAULT_AUDIO_CONFIG, ...audioConfig }\r\n\r\n // State\r\n const [state, setState] = useState<AiInputState>('idle')\r\n const [text, setText] = useState('')\r\n const [error, setError] = useState<Error | null>(null)\r\n const [result, setResult] = useState<unknown>(null)\r\n\r\n // Ref to track if we're waiting to submit audio after recording stops\r\n const pendingAudioSubmitRef = useRef(false)\r\n\r\n // Rate limiter\r\n const rateLimiter = useRateLimiter(rateLimitConfig)\r\n\r\n // Audio recorder\r\n const audioRecorder = useAudioRecorder({\r\n ...audioConfigMerged,\r\n })\r\n\r\n // Update state based on rate limiter\r\n useEffect(() => {\r\n if (!rateLimiter.canRequest && state === 'idle') {\r\n setState('rate-limited')\r\n } else if (rateLimiter.canRequest && state === 'rate-limited') {\r\n setState('idle')\r\n }\r\n }, [rateLimiter.canRequest, state])\r\n\r\n // Update state when recording\r\n useEffect(() => {\r\n if (audioRecorder.isRecording && state !== 'recording') {\r\n setState('recording')\r\n }\r\n }, [audioRecorder.isRecording, state])\r\n\r\n // Handle audio recorder errors\r\n useEffect(() => {\r\n if (audioRecorder.error) {\r\n setError(audioRecorder.error)\r\n setState('error')\r\n onError?.(audioRecorder.error)\r\n }\r\n }, [audioRecorder.error, onError])\r\n\r\n // Submit text\r\n const submitText = useCallback(async () => {\r\n if (!text.trim() || !rateLimiter.canRequest) {\r\n return\r\n }\r\n\r\n setState('loading')\r\n setError(null)\r\n rateLimiter.recordRequest()\r\n\r\n try {\r\n const response = await send(text)\r\n setResult(response)\r\n setState('success')\r\n onSuccess?.(response)\r\n // Clear text after successful send\r\n setText('')\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error('Request failed')\r\n setError(error)\r\n setState('error')\r\n onError?.(error)\r\n }\r\n }, [text, rateLimiter, send, onSuccess, onError])\r\n\r\n // Submit audio\r\n const submitAudio = useCallback(async (blob: Blob) => {\r\n if (!rateLimiter.canRequest) {\r\n return\r\n }\r\n\r\n setState('loading')\r\n setError(null)\r\n rateLimiter.recordRequest()\r\n\r\n try {\r\n // Use sendAudio if provided, otherwise use send\r\n const sendFn = sendAudio || send\r\n const response = await sendFn(blob)\r\n setResult(response)\r\n setState('success')\r\n onSuccess?.(response)\r\n\r\n // Handle transcription if callback provided\r\n if (onTranscription && response && typeof response === 'object') {\r\n const res = response as Record<string, unknown>\r\n // Try common transcription response formats\r\n const transcriptionText = res.text || res.transcription || res.transcript\r\n if (typeof transcriptionText === 'string') {\r\n setText(transcriptionText)\r\n onTranscription(transcriptionText)\r\n }\r\n }\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error('Request failed')\r\n setError(error)\r\n setState('error')\r\n onError?.(error)\r\n }\r\n }, [rateLimiter, send, sendAudio, onSuccess, onError, onTranscription])\r\n\r\n // Handle audio blob ready - submit if we were waiting\r\n useEffect(() => {\r\n if (pendingAudioSubmitRef.current && audioRecorder.audioBlob && !audioRecorder.isRecording) {\r\n pendingAudioSubmitRef.current = false\r\n submitAudio(audioRecorder.audioBlob)\r\n }\r\n }, [audioRecorder.audioBlob, audioRecorder.isRecording, submitAudio])\r\n\r\n // Start recording\r\n const startRecording = useCallback(async () => {\r\n if (!rateLimiter.canRequest) {\r\n return\r\n }\r\n await audioRecorder.startRecording()\r\n }, [rateLimiter.canRequest, audioRecorder])\r\n\r\n // Stop recording and submit\r\n const stopRecording = useCallback(() => {\r\n // Mark that we want to submit audio when blob is ready\r\n pendingAudioSubmitRef.current = true\r\n audioRecorder.stopRecording()\r\n }, [audioRecorder])\r\n\r\n // Cancel recording (discard audio)\r\n const cancelRecording = useCallback(() => {\r\n audioRecorder.cancelRecording()\r\n setState('idle')\r\n }, [audioRecorder])\r\n\r\n // Submit based on current state\r\n const submit = useCallback(() => {\r\n if (audioRecorder.isRecording) {\r\n stopRecording()\r\n } else if (text.trim()) {\r\n submitText()\r\n }\r\n }, [audioRecorder.isRecording, text, stopRecording, submitText])\r\n\r\n // Reset all state\r\n const reset = useCallback(() => {\r\n setState('idle')\r\n setText('')\r\n setError(null)\r\n setResult(null)\r\n rateLimiter.reset()\r\n audioRecorder.reset()\r\n }, [rateLimiter, audioRecorder])\r\n\r\n // Can submit check\r\n const canSubmit =\r\n rateLimiter.canRequest &&\r\n state !== 'loading' &&\r\n (audioRecorder.isRecording || text.trim().length > 0)\r\n\r\n return {\r\n // State\r\n state,\r\n error,\r\n result,\r\n\r\n // Text\r\n text,\r\n setText,\r\n submit,\r\n canSubmit,\r\n\r\n // Audio\r\n isRecording: audioRecorder.isRecording,\r\n startRecording,\r\n stopRecording,\r\n cancelRecording,\r\n recordingDuration: audioRecorder.duration,\r\n maxRecordingDuration: audioConfigMerged.maxDurationMs,\r\n audioLevels: audioRecorder.audioLevels,\r\n\r\n // Rate limiting\r\n cooldownRemaining: rateLimiter.cooldownRemaining,\r\n requestsRemaining: rateLimiter.requestsRemaining,\r\n\r\n // Utils\r\n reset,\r\n }\r\n}\r\n","import React from 'react'\r\nimport { useAiInput } from '../hooks/useAiInput'\r\nimport type { AiInputProps, AiInputRenderProps } from '../types'\r\n\r\n/**\r\n * Format milliseconds to MM:SS display\r\n */\r\nfunction formatDuration(ms: number): string {\r\n const seconds = Math.floor(ms / 1000)\r\n const minutes = Math.floor(seconds / 60)\r\n const remainingSeconds = seconds % 60\r\n return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`\r\n}\r\n\r\n/**\r\n * Waveform visualization component - larger for toolbar\r\n */\r\nfunction Waveform({ levels, className = '' }: { levels: number[]; className?: string }) {\r\n // Generate 16 bars if no levels provided\r\n const bars = levels.length > 0 ? levels : Array(16).fill(0.1)\r\n\r\n return (\r\n <div className={`flex items-center justify-center gap-1 h-10 ${className}`}>\r\n {bars.map((level, i) => (\r\n <div\r\n key={i}\r\n className=\"w-1.5 bg-amber-500 rounded-full transition-all duration-75\"\r\n style={{\r\n height: `${Math.max(6, level * 40)}px`,\r\n }}\r\n />\r\n ))}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Microphone icon (Phosphor style)\r\n */\r\nfunction MicIcon({ className = '' }: { className?: string }) {\r\n return (\r\n <svg className={className} viewBox=\"0 0 256 256\" fill=\"currentColor\">\r\n <path d=\"M128,176a48.05,48.05,0,0,0,48-48V64a48,48,0,0,0-96,0v64A48.05,48.05,0,0,0,128,176ZM96,64a32,32,0,0,1,64,0v64a32,32,0,0,1-64,0Zm40,143.6V232a8,8,0,0,1-16,0V207.6A80.11,80.11,0,0,1,48,128a8,8,0,0,1,16,0,64,64,0,0,0,128,0,8,8,0,0,1,16,0A80.11,80.11,0,0,1,136,207.6Z\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Arrow up icon for submit\r\n */\r\nfunction ArrowUpIcon({ className = '' }: { className?: string }) {\r\n return (\r\n <svg className={className} viewBox=\"0 0 256 256\" fill=\"currentColor\">\r\n <path d=\"M205.66,117.66a8,8,0,0,1-11.32,0L136,59.31V216a8,8,0,0,1-16,0V59.31L61.66,117.66a8,8,0,0,1-11.32-11.32l72-72a8,8,0,0,1,11.32,0l72,72A8,8,0,0,1,205.66,117.66Z\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Stop icon\r\n */\r\nfunction StopIcon({ className = '' }: { className?: string }) {\r\n return (\r\n <svg className={className} viewBox=\"0 0 256 256\" fill=\"currentColor\">\r\n <path d=\"M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,160H56V56H200V200Z\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * X icon for cancel\r\n */\r\nfunction XIcon({ className = '' }: { className?: string }) {\r\n return (\r\n <svg className={className} viewBox=\"0 0 256 256\" fill=\"currentColor\">\r\n <path d=\"M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Spinner for loading state\r\n */\r\nfunction Spinner({ className = '' }: { className?: string }) {\r\n return (\r\n <svg className={`animate-spin ${className}`} viewBox=\"0 0 24 24\" fill=\"none\">\r\n <circle\r\n className=\"opacity-25\"\r\n cx=\"12\"\r\n cy=\"12\"\r\n r=\"10\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"4\"\r\n />\r\n <path\r\n className=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Default UI for the unified AiInput component\r\n */\r\nfunction DefaultUI({\r\n text,\r\n setText,\r\n submit,\r\n canSubmit,\r\n state,\r\n error,\r\n isRecording,\r\n startRecording,\r\n stopRecording,\r\n cancelRecording,\r\n recordingDuration,\r\n maxRecordingDuration,\r\n audioLevels,\r\n cooldownRemaining,\r\n placeholder = 'Ask anything...',\r\n disabled = false,\r\n}: AiInputRenderProps & {\r\n placeholder?: string\r\n disabled?: boolean\r\n}) {\r\n const isLoading = state === 'loading'\r\n const isRateLimited = state === 'rate-limited'\r\n const hasError = state === 'error'\r\n\r\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\r\n if (e.key === 'Enter' && !e.shiftKey && canSubmit && !isRecording) {\r\n e.preventDefault()\r\n submit()\r\n }\r\n }\r\n\r\n // Auto-resize textarea\r\n const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\r\n setText(e.target.value)\r\n // Reset height to auto to get the correct scrollHeight\r\n e.target.style.height = 'auto'\r\n // Set height to scrollHeight, with min and max constraints\r\n e.target.style.height = `${Math.min(Math.max(e.target.scrollHeight, 56), 200)}px`\r\n }\r\n\r\n return (\r\n <div className=\"w-full\">\r\n {/* Main container */}\r\n <div\r\n className={`\r\n bg-zinc-900 border border-zinc-800 rounded-xl\r\n focus-within:ring-1 focus-within:ring-amber-500/50 focus-within:border-amber-500/50\r\n transition-all duration-200\r\n ${disabled ? 'opacity-50' : ''}\r\n `}\r\n >\r\n {/* Text input area - always visible for live transcription */}\r\n <textarea\r\n value={text}\r\n onChange={handleInput}\r\n onKeyDown={handleKeyDown}\r\n placeholder={isRecording ? 'Listening...' : placeholder}\r\n disabled={disabled || isLoading || isRateLimited}\r\n rows={1}\r\n className={`\r\n w-full px-4 pt-4 pb-2\r\n bg-transparent text-zinc-100 placeholder:text-zinc-500\r\n focus:outline-none\r\n disabled:cursor-not-allowed\r\n resize-none\r\n min-h-[56px]\r\n `}\r\n style={{ height: '56px' }}\r\n />\r\n\r\n {/* Toolbar */}\r\n <div className=\"flex items-center justify-between px-3 pb-3 pt-1\">\r\n {/* Left side - error/status or waveform during recording */}\r\n <div className=\"flex items-center gap-3\">\r\n {isRecording ? (\r\n <>\r\n {/* Cancel recording */}\r\n <button\r\n onClick={cancelRecording}\r\n disabled={disabled}\r\n className=\"p-2 text-zinc-400 hover:text-zinc-200 transition-colors\"\r\n aria-label=\"Cancel recording\"\r\n >\r\n <XIcon className=\"h-5 w-5\" />\r\n </button>\r\n\r\n {/* Waveform visualization */}\r\n <Waveform levels={audioLevels} />\r\n\r\n {/* Timer - just elapsed time */}\r\n <span className=\"text-sm text-zinc-400 font-mono\">\r\n {formatDuration(recordingDuration)}\r\n </span>\r\n </>\r\n ) : (\r\n <div className=\"text-sm\">\r\n {hasError && error && (\r\n <span className=\"text-red-400\">{error.message}</span>\r\n )}\r\n {isRateLimited && (\r\n <span className=\"text-amber-400\">\r\n Wait {formatDuration(cooldownRemaining)}\r\n </span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right side - action buttons */}\r\n <div className=\"flex items-center gap-2\">\r\n {isRecording ? (\r\n <>\r\n {/* Stop and send */}\r\n <button\r\n onClick={stopRecording}\r\n disabled={disabled}\r\n className={`\r\n p-2.5 rounded-full\r\n bg-red-500 hover:bg-red-600\r\n text-white\r\n transition-colors\r\n disabled:opacity-50 disabled:cursor-not-allowed\r\n `}\r\n aria-label=\"Stop recording\"\r\n >\r\n <StopIcon className=\"h-5 w-5\" />\r\n </button>\r\n </>\r\n ) : (\r\n <>\r\n {/* Mic button */}\r\n <button\r\n onClick={startRecording}\r\n disabled={disabled || isLoading || isRateLimited}\r\n className={`\r\n p-2 text-zinc-400 hover:text-zinc-200\r\n transition-colors\r\n disabled:opacity-50 disabled:cursor-not-allowed\r\n `}\r\n aria-label=\"Start recording\"\r\n >\r\n <MicIcon className=\"h-5 w-5\" />\r\n </button>\r\n\r\n {/* Submit button */}\r\n <button\r\n onClick={submit}\r\n disabled={!canSubmit || disabled}\r\n className={`\r\n p-2.5 rounded-full\r\n transition-colors\r\n disabled:opacity-50 disabled:cursor-not-allowed\r\n ${canSubmit\r\n ? 'bg-amber-500 hover:bg-amber-600 text-zinc-900'\r\n : 'bg-zinc-700 text-zinc-500'\r\n }\r\n `}\r\n aria-label=\"Send message\"\r\n >\r\n {isLoading ? (\r\n <Spinner className=\"h-5 w-5\" />\r\n ) : (\r\n <ArrowUpIcon className=\"h-5 w-5\" />\r\n )}\r\n </button>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * AiInput Component\r\n * \r\n * A React component for text/audio input with AI API integration.\r\n * Unified design with text input and audio recording in a single component.\r\n * \r\n * @example\r\n * // Basic usage\r\n * <AiInput\r\n * send={async (input) => {\r\n * const response = await fetch('/api/chat', {\r\n * method: 'POST',\r\n * body: JSON.stringify({ message: input }),\r\n * })\r\n * return response.json()\r\n * }}\r\n * onSuccess={(result) => console.log(result)}\r\n * />\r\n * \r\n * @example\r\n * // With separate audio handler and transcription\r\n * <AiInput\r\n * send={sendTextFn}\r\n * sendAudio={sendAudioFn}\r\n * onTranscription={(text) => console.log('Transcribed:', text)}\r\n * />\r\n * \r\n * @example\r\n * // Headless mode with custom UI\r\n * <AiInput send={sendFn}>\r\n * {({ text, setText, submit, state, isRecording, audioLevels }) => (\r\n * <div>\r\n * {isRecording ? (\r\n * <MyWaveform levels={audioLevels} />\r\n * ) : (\r\n * <input value={text} onChange={(e) => setText(e.target.value)} />\r\n * )}\r\n * <button onClick={submit}>Send</button>\r\n * </div>\r\n * )}\r\n * </AiInput>\r\n */\r\nexport function AiInput({\r\n send,\r\n sendAudio,\r\n rateLimit,\r\n audioConfig,\r\n onSuccess,\r\n onError,\r\n onTranscription,\r\n children,\r\n placeholder,\r\n className,\r\n disabled = false,\r\n}: AiInputProps) {\r\n const inputState = useAiInput({\r\n send,\r\n sendAudio,\r\n rateLimit,\r\n audioConfig,\r\n onSuccess,\r\n onError,\r\n onTranscription,\r\n })\r\n\r\n // Headless mode - render prop\r\n if (children) {\r\n return <>{children(inputState)}</>\r\n }\r\n\r\n // Default UI\r\n return (\r\n <div className={`w-full ${className || ''}`}>\r\n <DefaultUI\r\n {...inputState}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n />\r\n </div>\r\n )\r\n}\r\n"]}
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-duration:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-400:#f87171;--color-red-500:#ef4444;--color-red-600:#dc2626;--color-amber-400:#fbbf24;--color-amber-500:#f59e0b;--color-amber-600:#d97706;--color-zinc-100:#f4f4f5;--color-zinc-200:#e4e4e7;--color-zinc-400:#a1a1aa;--color-zinc-500:#71717a;--color-zinc-700:#3f3f46;--color-zinc-800:#27272a;--color-zinc-900:#18181b;--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.flex{display:flex}.h-5{height:calc(var(--spacing)*5)}.h-10{height:calc(var(--spacing)*10)}.min-h-\[56px\]{min-height:56px}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-5{width:calc(var(--spacing)*5)}.w-full{width:100%}.animate-spin{animation:var(--animate-spin)}.resize-none{resize:none}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.rounded-full{border-radius:3.40282e38px}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-zinc-800{border-color:var(--color-zinc-800)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-red-500{background-color:var(--color-red-500)}.bg-transparent{background-color:#0000}.bg-zinc-700{background-color:var(--color-zinc-700)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-2{padding:calc(var(--spacing)*2)}.p-2\.5{padding:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-4{padding-top:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-amber-400{color:var(--color-amber-400)}.text-red-400{color:var(--color-red-400)}.text-white{color:var(--color-white)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-900{color:var(--color-zinc-900)}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-200{--tw-duration:.2s;transition-duration:.2s}.placeholder\:text-zinc-500::placeholder{color:var(--color-zinc-500)}.focus-within\:border-amber-500\/50:focus-within{border-color:#f59e0b80}@supports (color:color-mix(in lab, red, red)){.focus-within\:border-amber-500\/50:focus-within{border-color:color-mix(in oklab,var(--color-amber-500)50%,transparent)}}.focus-within\:ring-1:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-amber-500\/50:focus-within{--tw-ring-color:#f59e0b80}@supports (color:color-mix(in lab, red, red)){.focus-within\:ring-amber-500\/50:focus-within{--tw-ring-color:color-mix(in oklab,var(--color-amber-500)50%,transparent)}}@media (hover:hover){.hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:text-zinc-200:hover{color:var(--color-zinc-200)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}}@keyframes wave{0%,to{transform:scaleY(.5)}50%{transform:scaleY(1)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@keyframes spin{to{transform:rotate(360deg)}}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "ai-input-react",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "React component for text/audio input with AI API integration. Framework-agnostic, works with Next.js, Vite, PHP, and any React setup.",
5
+ "keywords": [
6
+ "react",
7
+ "ai",
8
+ "openai",
9
+ "whisper",
10
+ "gpt",
11
+ "audio",
12
+ "speech-to-text",
13
+ "input",
14
+ "component"
15
+ ],
16
+ "author": "",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/Soptik1290/ai-input.git"
21
+ },
22
+ "main": "./dist/index.js",
23
+ "module": "./dist/index.mjs",
24
+ "types": "./dist/index.d.ts",
25
+ "style": "./dist/styles.css",
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./dist/index.d.mts",
30
+ "default": "./dist/index.mjs"
31
+ },
32
+ "require": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ }
36
+ },
37
+ "./styles.css": "./dist/styles.css"
38
+ },
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "sideEffects": [
43
+ "*.css"
44
+ ],
45
+ "scripts": {
46
+ "build:js": "tsup",
47
+ "build:css": "npx @tailwindcss/cli -i ./src/styles.css -o ./dist/styles.css --minify",
48
+ "build": "npm run build:js && npm run build:css",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "prepublishOnly": "npm run build"
52
+ },
53
+ "peerDependencies": {
54
+ "react": "^18.0.0 || ^19.0.0",
55
+ "react-dom": "^18.0.0 || ^19.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "@tailwindcss/cli": "^4.1.0",
59
+ "@types/react": "^19.0.0",
60
+ "@types/react-dom": "^19.0.0",
61
+ "react": "^19.2.0",
62
+ "react-dom": "^19.2.0",
63
+ "tailwindcss": "^4.1.0",
64
+ "tsup": "^8.5.0",
65
+ "typescript": "^5.7.0"
66
+ }
67
+ }