ai-input-react 1.0.0-beta.3 → 1.0.0-beta.4

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.
package/dist/index.d.mts CHANGED
@@ -180,44 +180,12 @@ type AiInputMode = 'text' | 'audio';
180
180
  /**
181
181
  * AiInput Component
182
182
  *
183
- * A React component for text/audio input with AI API integration.
184
- * Unified design with text input and audio recording in a single component.
183
+ * Text/audio input with automatic light/dark mode detection.
185
184
  *
186
- * @example
187
- * // Basic usage
188
- * <AiInput
189
- * send={async (input) => {
190
- * const response = await fetch('/api/chat', {
191
- * method: 'POST',
192
- * body: JSON.stringify({ message: input }),
193
- * })
194
- * return response.json()
195
- * }}
196
- * onSuccess={(result) => console.log(result)}
197
- * />
198
- *
199
- * @example
200
- * // With separate audio handler and transcription
201
- * <AiInput
202
- * send={sendTextFn}
203
- * sendAudio={sendAudioFn}
204
- * onTranscription={(text) => console.log('Transcribed:', text)}
205
- * />
206
- *
207
- * @example
208
- * // Headless mode with custom UI
209
- * <AiInput send={sendFn}>
210
- * {({ text, setText, submit, state, isRecording, audioLevels }) => (
211
- * <div>
212
- * {isRecording ? (
213
- * <MyWaveform levels={audioLevels} />
214
- * ) : (
215
- * <input value={text} onChange={(e) => setText(e.target.value)} />
216
- * )}
217
- * <button onClick={submit}>Send</button>
218
- * </div>
219
- * )}
220
- * </AiInput>
185
+ * **Theme Detection (in order of priority):**
186
+ * 1. `.dark` class on html/body (Tailwind/Next.js)
187
+ * 2. `data-theme="dark"` attribute
188
+ * 3. `prefers-color-scheme` system preference
221
189
  */
222
190
  declare function AiInput({ send, sendAudio, rateLimit, audioConfig, onSuccess, onError, onTranscription, children, placeholder, className, disabled, }: AiInputProps): react_jsx_runtime.JSX.Element;
223
191
 
package/dist/index.d.ts CHANGED
@@ -180,44 +180,12 @@ type AiInputMode = 'text' | 'audio';
180
180
  /**
181
181
  * AiInput Component
182
182
  *
183
- * A React component for text/audio input with AI API integration.
184
- * Unified design with text input and audio recording in a single component.
183
+ * Text/audio input with automatic light/dark mode detection.
185
184
  *
186
- * @example
187
- * // Basic usage
188
- * <AiInput
189
- * send={async (input) => {
190
- * const response = await fetch('/api/chat', {
191
- * method: 'POST',
192
- * body: JSON.stringify({ message: input }),
193
- * })
194
- * return response.json()
195
- * }}
196
- * onSuccess={(result) => console.log(result)}
197
- * />
198
- *
199
- * @example
200
- * // With separate audio handler and transcription
201
- * <AiInput
202
- * send={sendTextFn}
203
- * sendAudio={sendAudioFn}
204
- * onTranscription={(text) => console.log('Transcribed:', text)}
205
- * />
206
- *
207
- * @example
208
- * // Headless mode with custom UI
209
- * <AiInput send={sendFn}>
210
- * {({ text, setText, submit, state, isRecording, audioLevels }) => (
211
- * <div>
212
- * {isRecording ? (
213
- * <MyWaveform levels={audioLevels} />
214
- * ) : (
215
- * <input value={text} onChange={(e) => setText(e.target.value)} />
216
- * )}
217
- * <button onClick={submit}>Send</button>
218
- * </div>
219
- * )}
220
- * </AiInput>
185
+ * **Theme Detection (in order of priority):**
186
+ * 1. `.dark` class on html/body (Tailwind/Next.js)
187
+ * 2. `data-theme="dark"` attribute
188
+ * 3. `prefers-color-scheme` system preference
221
189
  */
222
190
  declare function AiInput({ send, sendAudio, rateLimit, audioConfig, onSuccess, onError, onTranscription, children, placeholder, className, disabled, }: AiInputProps): react_jsx_runtime.JSX.Element;
223
191
 
package/dist/index.js CHANGED
@@ -444,25 +444,8 @@ function XIcon({ className = "" }) {
444
444
  }
445
445
  function Spinner({ className = "" }) {
446
446
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: `animate-spin ${className}`, viewBox: "0 0 24 24", fill: "none", children: [
447
- /* @__PURE__ */ jsxRuntime.jsx(
448
- "circle",
449
- {
450
- className: "opacity-25",
451
- cx: "12",
452
- cy: "12",
453
- r: "10",
454
- stroke: "currentColor",
455
- strokeWidth: "4"
456
- }
457
- ),
458
- /* @__PURE__ */ jsxRuntime.jsx(
459
- "path",
460
- {
461
- className: "opacity-75",
462
- fill: "currentColor",
463
- 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"
464
- }
465
- )
447
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
448
+ /* @__PURE__ */ jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", 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" })
466
449
  ] });
467
450
  }
468
451
  function DefaultUI({
@@ -496,13 +479,14 @@ function DefaultUI({
496
479
  e.target.style.height = "auto";
497
480
  e.target.style.height = `${Math.min(Math.max(e.target.scrollHeight, 56), 200)}px`;
498
481
  };
499
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(
482
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ai-input w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(
500
483
  "div",
501
484
  {
502
485
  className: `
503
- bg-zinc-900 border rounded-xl
486
+ ai-input-container
487
+ border rounded-xl
504
488
  transition-all duration-300 ease-out
505
- ${isRecording ? "border-red-500/50 shadow-lg shadow-red-500/10" : "border-zinc-800 focus-within:border-amber-500/50 focus-within:shadow-lg focus-within:shadow-amber-500/5"}
489
+ ${isRecording ? "ai-input-recording" : ""}
506
490
  ${disabled ? "opacity-50" : ""}
507
491
  `,
508
492
  children: [
@@ -515,15 +499,7 @@ function DefaultUI({
515
499
  placeholder: isRecording ? "Listening..." : placeholder,
516
500
  disabled: disabled || isLoading || isRateLimited,
517
501
  rows: 1,
518
- className: `
519
- w-full px-4 pt-4 pb-2
520
- bg-transparent text-zinc-100 placeholder:text-zinc-500
521
- focus:outline-none
522
- disabled:cursor-not-allowed
523
- resize-none
524
- min-h-[56px]
525
- transition-colors duration-200
526
- `,
502
+ className: "ai-input-textarea w-full px-4 pt-4 pb-2 bg-transparent focus:outline-none disabled:cursor-not-allowed resize-none min-h-[56px] transition-colors duration-200",
527
503
  style: { height: "56px" }
528
504
  }
529
505
  ),
@@ -534,7 +510,7 @@ function DefaultUI({
534
510
  {
535
511
  onClick: cancelRecording,
536
512
  disabled,
537
- className: "p-2 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 rounded-lg transition-all duration-200 active:scale-95",
513
+ className: "ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 active:scale-95",
538
514
  "aria-label": "Cancel recording",
539
515
  children: /* @__PURE__ */ jsxRuntime.jsx(XIcon, { className: "h-5 w-5" })
540
516
  }
@@ -543,45 +519,30 @@ function DefaultUI({
543
519
  /* @__PURE__ */ jsxRuntime.jsx(RecordingPulse, {}),
544
520
  /* @__PURE__ */ jsxRuntime.jsx(Waveform, { levels: audioLevels })
545
521
  ] }),
546
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-zinc-400 font-mono tabular-nums", children: formatDuration(recordingDuration) })
522
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ai-input-text-muted text-sm font-mono tabular-nums", children: formatDuration(recordingDuration) })
547
523
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm min-h-[28px] flex items-center", children: [
548
- hasError && error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-400 animate-pulse", children: error.message }),
549
- isRateLimited && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-amber-400", children: [
524
+ hasError && error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ai-input-text-error animate-pulse", children: error.message }),
525
+ isRateLimited && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ai-input-text-warning", children: [
550
526
  "Wait ",
551
527
  formatDuration(cooldownRemaining)
552
528
  ] })
553
529
  ] }) }),
554
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: isRecording ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
530
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: isRecording ? /* @__PURE__ */ jsxRuntime.jsx(
555
531
  "button",
556
532
  {
557
533
  onClick: stopRecording,
558
534
  disabled,
559
- className: `
560
- p-2.5 rounded-full
561
- bg-red-500 hover:bg-red-400
562
- text-white
563
- transition-all duration-200
564
- hover:scale-105 active:scale-95
565
- disabled:opacity-50 disabled:cursor-not-allowed
566
- shadow-lg shadow-red-500/25
567
- `,
535
+ className: "p-2.5 rounded-full bg-red-500 hover:bg-red-400 text-white transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-red-500/25",
568
536
  "aria-label": "Stop recording",
569
537
  children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-5 w-5" })
570
538
  }
571
- ) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
539
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
572
540
  /* @__PURE__ */ jsxRuntime.jsx(
573
541
  "button",
574
542
  {
575
543
  onClick: startRecording,
576
544
  disabled: disabled || isLoading || isRateLimited,
577
- className: `
578
- p-2 text-zinc-400
579
- hover:text-amber-400 hover:bg-zinc-800
580
- rounded-lg
581
- transition-all duration-200
582
- hover:scale-105 active:scale-95
583
- disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100
584
- `,
545
+ className: "ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100",
585
546
  "aria-label": "Start recording",
586
547
  children: /* @__PURE__ */ jsxRuntime.jsx(MicIcon, { className: "h-5 w-5" })
587
548
  }
@@ -592,10 +553,9 @@ function DefaultUI({
592
553
  onClick: submit,
593
554
  disabled: !canSubmit || disabled,
594
555
  className: `
595
- p-2.5 rounded-full
596
- transition-all duration-200
556
+ p-2.5 rounded-full transition-all duration-200
597
557
  disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100
598
- ${canSubmit ? "bg-amber-500 hover:bg-amber-400 text-zinc-900 hover:scale-105 active:scale-95 shadow-lg shadow-amber-500/25" : "bg-zinc-800 text-zinc-500"}
558
+ ${canSubmit ? "ai-input-btn-primary hover:scale-105 active:scale-95 shadow-lg" : "ai-input-btn-disabled"}
599
559
  `,
600
560
  "aria-label": "Send message",
601
561
  children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-5 w-5" })
@@ -632,14 +592,7 @@ function AiInput({
632
592
  if (children) {
633
593
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(inputState) });
634
594
  }
635
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full ${className || ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(
636
- DefaultUI,
637
- {
638
- ...inputState,
639
- placeholder,
640
- disabled
641
- }
642
- ) });
595
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full ${className || ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(DefaultUI, { ...inputState, placeholder, disabled }) });
643
596
  }
644
597
 
645
598
  exports.AiInput = AiInput;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useRateLimiter.ts","../src/hooks/useAudioRecorder.ts","../src/hooks/useAiInput.ts","../src/components/AiInput.tsx"],"names":["useRef","useState","useCallback","useEffect","DEFAULT_OPTIONS","error","jsx","jsxs","Fragment"],"mappings":"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","file":"index.js","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 - uses time domain for better voice visualization\r\n const updateAudioLevels = useCallback(() => {\r\n if (!analyserRef.current) return\r\n\r\n const analyser = analyserRef.current\r\n const bufferLength = analyser.fftSize\r\n const dataArray = new Uint8Array(bufferLength)\r\n\r\n // Use time domain data for even distribution across bars\r\n analyser.getByteTimeDomainData(dataArray)\r\n\r\n // Sample 16 bars from the waveform data\r\n const bars = 16\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 // Get amplitude variation from center (128) for each segment\r\n let maxDeviation = 0\r\n for (let j = 0; j < step; j++) {\r\n const value = dataArray[i * step + j]\r\n const deviation = Math.abs(value - 128)\r\n maxDeviation = Math.max(maxDeviation, deviation)\r\n }\r\n // Normalize to 0-1 range (max deviation is 128)\r\n // Apply some amplification for better visibility\r\n levels.push(Math.min(1, (maxDeviation / 128) * 2.5))\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 with smooth animations\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.15)\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-gradient-to-t from-amber-600 to-amber-400 rounded-full transition-all duration-100 ease-out\"\r\n style={{\r\n height: `${Math.max(6, level * 40)}px`,\r\n opacity: 0.6 + level * 0.4,\r\n }}\r\n />\r\n ))}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Recording pulse indicator\r\n */\r\nfunction RecordingPulse() {\r\n return (\r\n <span className=\"relative flex h-3 w-3 mr-2\">\r\n <span className=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75\"></span>\r\n <span className=\"relative inline-flex rounded-full h-3 w-3 bg-red-500\"></span>\r\n </span>\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 (filled square)\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 <rect x=\"64\" y=\"64\" width=\"128\" height=\"128\" rx=\"8\" />\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 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 rounded-xl\r\n transition-all duration-300 ease-out\r\n ${isRecording\r\n ? 'border-red-500/50 shadow-lg shadow-red-500/10'\r\n : 'border-zinc-800 focus-within:border-amber-500/50 focus-within:shadow-lg focus-within:shadow-amber-500/5'\r\n }\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 transition-colors duration-200\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-2\">\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 hover:bg-zinc-800 rounded-lg transition-all duration-200 active:scale-95\"\r\n aria-label=\"Cancel recording\"\r\n >\r\n <XIcon className=\"h-5 w-5\" />\r\n </button>\r\n\r\n {/* Recording indicator + Waveform */}\r\n <div className=\"flex items-center\">\r\n <RecordingPulse />\r\n <Waveform levels={audioLevels} />\r\n </div>\r\n\r\n {/* Timer */}\r\n <span className=\"text-sm text-zinc-400 font-mono tabular-nums\">\r\n {formatDuration(recordingDuration)}\r\n </span>\r\n </>\r\n ) : (\r\n <div className=\"text-sm min-h-[28px] flex items-center\">\r\n {hasError && error && (\r\n <span className=\"text-red-400 animate-pulse\">{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-400\r\n text-white\r\n transition-all duration-200\r\n hover:scale-105 active:scale-95\r\n disabled:opacity-50 disabled:cursor-not-allowed\r\n shadow-lg shadow-red-500/25\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 \r\n hover:text-amber-400 hover:bg-zinc-800\r\n rounded-lg\r\n transition-all duration-200\r\n hover:scale-105 active:scale-95\r\n disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\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-all duration-200\r\n disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\r\n ${canSubmit\r\n ? 'bg-amber-500 hover:bg-amber-400 text-zinc-900 hover:scale-105 active:scale-95 shadow-lg shadow-amber-500/25'\r\n : 'bg-zinc-800 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"]}
1
+ {"version":3,"sources":["../src/hooks/useRateLimiter.ts","../src/hooks/useAudioRecorder.ts","../src/hooks/useAiInput.ts","../src/components/AiInput.tsx"],"names":["useRef","useState","useCallback","useEffect","DEFAULT_OPTIONS","error","jsx","jsxs","Fragment"],"mappings":"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","file":"index.js","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 - uses time domain for better voice visualization\r\n const updateAudioLevels = useCallback(() => {\r\n if (!analyserRef.current) return\r\n\r\n const analyser = analyserRef.current\r\n const bufferLength = analyser.fftSize\r\n const dataArray = new Uint8Array(bufferLength)\r\n\r\n // Use time domain data for even distribution across bars\r\n analyser.getByteTimeDomainData(dataArray)\r\n\r\n // Sample 16 bars from the waveform data\r\n const bars = 16\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 // Get amplitude variation from center (128) for each segment\r\n let maxDeviation = 0\r\n for (let j = 0; j < step; j++) {\r\n const value = dataArray[i * step + j]\r\n const deviation = Math.abs(value - 128)\r\n maxDeviation = Math.max(maxDeviation, deviation)\r\n }\r\n // Normalize to 0-1 range (max deviation is 128)\r\n // Apply some amplification for better visibility\r\n levels.push(Math.min(1, (maxDeviation / 128) * 2.5))\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 with smooth animations\r\n */\r\nfunction Waveform({ levels, className = '' }: { levels: number[]; className?: string }) {\r\n const bars = levels.length > 0 ? levels : Array(16).fill(0.15)\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-gradient-to-t from-amber-600 to-amber-400 rounded-full transition-all duration-100 ease-out\"\r\n style={{\r\n height: `${Math.max(6, level * 40)}px`,\r\n opacity: 0.6 + level * 0.4,\r\n }}\r\n />\r\n ))}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Recording pulse indicator\r\n */\r\nfunction RecordingPulse() {\r\n return (\r\n <span className=\"relative flex h-3 w-3 mr-2\">\r\n <span className=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75\"></span>\r\n <span className=\"relative inline-flex rounded-full h-3 w-3 bg-red-500\"></span>\r\n </span>\r\n )\r\n}\r\n\r\n/**\r\n * Microphone icon\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\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 <rect x=\"64\" y=\"64\" width=\"128\" height=\"128\" rx=\"8\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * X icon\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\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 className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\r\n <path className=\"opacity-75\" fill=\"currentColor\" 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 </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Default UI - uses CSS variables for automatic theme detection\r\n * The CSS variables are defined in styles.css and automatically switch\r\n * based on prefers-color-scheme, .dark class, or data-theme attribute\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 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 const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\r\n setText(e.target.value)\r\n e.target.style.height = 'auto'\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=\"ai-input w-full\">\r\n {/* Main container - uses CSS variables for theming */}\r\n <div\r\n className={`\r\n ai-input-container\r\n border rounded-xl\r\n transition-all duration-300 ease-out\r\n ${isRecording ? 'ai-input-recording' : ''}\r\n ${disabled ? 'opacity-50' : ''}\r\n `}\r\n >\r\n {/* Text input */}\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=\"ai-input-textarea w-full px-4 pt-4 pb-2 bg-transparent focus:outline-none disabled:cursor-not-allowed resize-none min-h-[56px] transition-colors duration-200\"\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 */}\r\n <div className=\"flex items-center gap-2\">\r\n {isRecording ? (\r\n <>\r\n <button\r\n onClick={cancelRecording}\r\n disabled={disabled}\r\n className=\"ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 active:scale-95\"\r\n aria-label=\"Cancel recording\"\r\n >\r\n <XIcon className=\"h-5 w-5\" />\r\n </button>\r\n <div className=\"flex items-center\">\r\n <RecordingPulse />\r\n <Waveform levels={audioLevels} />\r\n </div>\r\n <span className=\"ai-input-text-muted text-sm font-mono tabular-nums\">\r\n {formatDuration(recordingDuration)}\r\n </span>\r\n </>\r\n ) : (\r\n <div className=\"text-sm min-h-[28px] flex items-center\">\r\n {hasError && error && (\r\n <span className=\"ai-input-text-error animate-pulse\">{error.message}</span>\r\n )}\r\n {isRateLimited && (\r\n <span className=\"ai-input-text-warning\">\r\n Wait {formatDuration(cooldownRemaining)}\r\n </span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right side */}\r\n <div className=\"flex items-center gap-2\">\r\n {isRecording ? (\r\n <button\r\n onClick={stopRecording}\r\n disabled={disabled}\r\n className=\"p-2.5 rounded-full bg-red-500 hover:bg-red-400 text-white transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-red-500/25\"\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 <button\r\n onClick={startRecording}\r\n disabled={disabled || isLoading || isRateLimited}\r\n className=\"ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\"\r\n aria-label=\"Start recording\"\r\n >\r\n <MicIcon className=\"h-5 w-5\" />\r\n </button>\r\n <button\r\n onClick={submit}\r\n disabled={!canSubmit || disabled}\r\n className={`\r\n p-2.5 rounded-full transition-all duration-200\r\n disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\r\n ${canSubmit\r\n ? 'ai-input-btn-primary hover:scale-105 active:scale-95 shadow-lg'\r\n : 'ai-input-btn-disabled'\r\n }\r\n `}\r\n aria-label=\"Send message\"\r\n >\r\n {isLoading ? <Spinner className=\"h-5 w-5\" /> : <ArrowUpIcon className=\"h-5 w-5\" />}\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 * Text/audio input with automatic light/dark mode detection.\r\n * \r\n * **Theme Detection (in order of priority):**\r\n * 1. `.dark` class on html/body (Tailwind/Next.js)\r\n * 2. `data-theme=\"dark\"` attribute\r\n * 3. `prefers-color-scheme` system preference\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 if (children) {\r\n return <>{children(inputState)}</>\r\n }\r\n\r\n return (\r\n <div className={`w-full ${className || ''}`}>\r\n <DefaultUI {...inputState} placeholder={placeholder} disabled={disabled} />\r\n </div>\r\n )\r\n}\r\n"]}
package/dist/index.mjs CHANGED
@@ -442,25 +442,8 @@ function XIcon({ className = "" }) {
442
442
  }
443
443
  function Spinner({ className = "" }) {
444
444
  return /* @__PURE__ */ jsxs("svg", { className: `animate-spin ${className}`, viewBox: "0 0 24 24", fill: "none", children: [
445
- /* @__PURE__ */ jsx(
446
- "circle",
447
- {
448
- className: "opacity-25",
449
- cx: "12",
450
- cy: "12",
451
- r: "10",
452
- stroke: "currentColor",
453
- strokeWidth: "4"
454
- }
455
- ),
456
- /* @__PURE__ */ jsx(
457
- "path",
458
- {
459
- className: "opacity-75",
460
- fill: "currentColor",
461
- 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"
462
- }
463
- )
445
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
446
+ /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", 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" })
464
447
  ] });
465
448
  }
466
449
  function DefaultUI({
@@ -494,13 +477,14 @@ function DefaultUI({
494
477
  e.target.style.height = "auto";
495
478
  e.target.style.height = `${Math.min(Math.max(e.target.scrollHeight, 56), 200)}px`;
496
479
  };
497
- return /* @__PURE__ */ jsx("div", { className: "w-full", children: /* @__PURE__ */ jsxs(
480
+ return /* @__PURE__ */ jsx("div", { className: "ai-input w-full", children: /* @__PURE__ */ jsxs(
498
481
  "div",
499
482
  {
500
483
  className: `
501
- bg-zinc-900 border rounded-xl
484
+ ai-input-container
485
+ border rounded-xl
502
486
  transition-all duration-300 ease-out
503
- ${isRecording ? "border-red-500/50 shadow-lg shadow-red-500/10" : "border-zinc-800 focus-within:border-amber-500/50 focus-within:shadow-lg focus-within:shadow-amber-500/5"}
487
+ ${isRecording ? "ai-input-recording" : ""}
504
488
  ${disabled ? "opacity-50" : ""}
505
489
  `,
506
490
  children: [
@@ -513,15 +497,7 @@ function DefaultUI({
513
497
  placeholder: isRecording ? "Listening..." : placeholder,
514
498
  disabled: disabled || isLoading || isRateLimited,
515
499
  rows: 1,
516
- className: `
517
- w-full px-4 pt-4 pb-2
518
- bg-transparent text-zinc-100 placeholder:text-zinc-500
519
- focus:outline-none
520
- disabled:cursor-not-allowed
521
- resize-none
522
- min-h-[56px]
523
- transition-colors duration-200
524
- `,
500
+ className: "ai-input-textarea w-full px-4 pt-4 pb-2 bg-transparent focus:outline-none disabled:cursor-not-allowed resize-none min-h-[56px] transition-colors duration-200",
525
501
  style: { height: "56px" }
526
502
  }
527
503
  ),
@@ -532,7 +508,7 @@ function DefaultUI({
532
508
  {
533
509
  onClick: cancelRecording,
534
510
  disabled,
535
- className: "p-2 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 rounded-lg transition-all duration-200 active:scale-95",
511
+ className: "ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 active:scale-95",
536
512
  "aria-label": "Cancel recording",
537
513
  children: /* @__PURE__ */ jsx(XIcon, { className: "h-5 w-5" })
538
514
  }
@@ -541,45 +517,30 @@ function DefaultUI({
541
517
  /* @__PURE__ */ jsx(RecordingPulse, {}),
542
518
  /* @__PURE__ */ jsx(Waveform, { levels: audioLevels })
543
519
  ] }),
544
- /* @__PURE__ */ jsx("span", { className: "text-sm text-zinc-400 font-mono tabular-nums", children: formatDuration(recordingDuration) })
520
+ /* @__PURE__ */ jsx("span", { className: "ai-input-text-muted text-sm font-mono tabular-nums", children: formatDuration(recordingDuration) })
545
521
  ] }) : /* @__PURE__ */ jsxs("div", { className: "text-sm min-h-[28px] flex items-center", children: [
546
- hasError && error && /* @__PURE__ */ jsx("span", { className: "text-red-400 animate-pulse", children: error.message }),
547
- isRateLimited && /* @__PURE__ */ jsxs("span", { className: "text-amber-400", children: [
522
+ hasError && error && /* @__PURE__ */ jsx("span", { className: "ai-input-text-error animate-pulse", children: error.message }),
523
+ isRateLimited && /* @__PURE__ */ jsxs("span", { className: "ai-input-text-warning", children: [
548
524
  "Wait ",
549
525
  formatDuration(cooldownRemaining)
550
526
  ] })
551
527
  ] }) }),
552
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: isRecording ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
528
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: isRecording ? /* @__PURE__ */ jsx(
553
529
  "button",
554
530
  {
555
531
  onClick: stopRecording,
556
532
  disabled,
557
- className: `
558
- p-2.5 rounded-full
559
- bg-red-500 hover:bg-red-400
560
- text-white
561
- transition-all duration-200
562
- hover:scale-105 active:scale-95
563
- disabled:opacity-50 disabled:cursor-not-allowed
564
- shadow-lg shadow-red-500/25
565
- `,
533
+ className: "p-2.5 rounded-full bg-red-500 hover:bg-red-400 text-white transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-red-500/25",
566
534
  "aria-label": "Stop recording",
567
535
  children: /* @__PURE__ */ jsx(StopIcon, { className: "h-5 w-5" })
568
536
  }
569
- ) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
537
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
570
538
  /* @__PURE__ */ jsx(
571
539
  "button",
572
540
  {
573
541
  onClick: startRecording,
574
542
  disabled: disabled || isLoading || isRateLimited,
575
- className: `
576
- p-2 text-zinc-400
577
- hover:text-amber-400 hover:bg-zinc-800
578
- rounded-lg
579
- transition-all duration-200
580
- hover:scale-105 active:scale-95
581
- disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100
582
- `,
543
+ className: "ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100",
583
544
  "aria-label": "Start recording",
584
545
  children: /* @__PURE__ */ jsx(MicIcon, { className: "h-5 w-5" })
585
546
  }
@@ -590,10 +551,9 @@ function DefaultUI({
590
551
  onClick: submit,
591
552
  disabled: !canSubmit || disabled,
592
553
  className: `
593
- p-2.5 rounded-full
594
- transition-all duration-200
554
+ p-2.5 rounded-full transition-all duration-200
595
555
  disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100
596
- ${canSubmit ? "bg-amber-500 hover:bg-amber-400 text-zinc-900 hover:scale-105 active:scale-95 shadow-lg shadow-amber-500/25" : "bg-zinc-800 text-zinc-500"}
556
+ ${canSubmit ? "ai-input-btn-primary hover:scale-105 active:scale-95 shadow-lg" : "ai-input-btn-disabled"}
597
557
  `,
598
558
  "aria-label": "Send message",
599
559
  children: isLoading ? /* @__PURE__ */ jsx(Spinner, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-5 w-5" })
@@ -630,14 +590,7 @@ function AiInput({
630
590
  if (children) {
631
591
  return /* @__PURE__ */ jsx(Fragment, { children: children(inputState) });
632
592
  }
633
- return /* @__PURE__ */ jsx("div", { className: `w-full ${className || ""}`, children: /* @__PURE__ */ jsx(
634
- DefaultUI,
635
- {
636
- ...inputState,
637
- placeholder,
638
- disabled
639
- }
640
- ) });
593
+ return /* @__PURE__ */ jsx("div", { className: `w-full ${className || ""}`, children: /* @__PURE__ */ jsx(DefaultUI, { ...inputState, placeholder, disabled }) });
641
594
  }
642
595
 
643
596
  export { AiInput, useAiInput, useAudioRecorder, useRateLimiter };
@@ -1 +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 - uses time domain for better voice visualization\r\n const updateAudioLevels = useCallback(() => {\r\n if (!analyserRef.current) return\r\n\r\n const analyser = analyserRef.current\r\n const bufferLength = analyser.fftSize\r\n const dataArray = new Uint8Array(bufferLength)\r\n\r\n // Use time domain data for even distribution across bars\r\n analyser.getByteTimeDomainData(dataArray)\r\n\r\n // Sample 16 bars from the waveform data\r\n const bars = 16\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 // Get amplitude variation from center (128) for each segment\r\n let maxDeviation = 0\r\n for (let j = 0; j < step; j++) {\r\n const value = dataArray[i * step + j]\r\n const deviation = Math.abs(value - 128)\r\n maxDeviation = Math.max(maxDeviation, deviation)\r\n }\r\n // Normalize to 0-1 range (max deviation is 128)\r\n // Apply some amplification for better visibility\r\n levels.push(Math.min(1, (maxDeviation / 128) * 2.5))\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 with smooth animations\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.15)\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-gradient-to-t from-amber-600 to-amber-400 rounded-full transition-all duration-100 ease-out\"\r\n style={{\r\n height: `${Math.max(6, level * 40)}px`,\r\n opacity: 0.6 + level * 0.4,\r\n }}\r\n />\r\n ))}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Recording pulse indicator\r\n */\r\nfunction RecordingPulse() {\r\n return (\r\n <span className=\"relative flex h-3 w-3 mr-2\">\r\n <span className=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75\"></span>\r\n <span className=\"relative inline-flex rounded-full h-3 w-3 bg-red-500\"></span>\r\n </span>\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 (filled square)\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 <rect x=\"64\" y=\"64\" width=\"128\" height=\"128\" rx=\"8\" />\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 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 rounded-xl\r\n transition-all duration-300 ease-out\r\n ${isRecording\r\n ? 'border-red-500/50 shadow-lg shadow-red-500/10'\r\n : 'border-zinc-800 focus-within:border-amber-500/50 focus-within:shadow-lg focus-within:shadow-amber-500/5'\r\n }\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 transition-colors duration-200\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-2\">\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 hover:bg-zinc-800 rounded-lg transition-all duration-200 active:scale-95\"\r\n aria-label=\"Cancel recording\"\r\n >\r\n <XIcon className=\"h-5 w-5\" />\r\n </button>\r\n\r\n {/* Recording indicator + Waveform */}\r\n <div className=\"flex items-center\">\r\n <RecordingPulse />\r\n <Waveform levels={audioLevels} />\r\n </div>\r\n\r\n {/* Timer */}\r\n <span className=\"text-sm text-zinc-400 font-mono tabular-nums\">\r\n {formatDuration(recordingDuration)}\r\n </span>\r\n </>\r\n ) : (\r\n <div className=\"text-sm min-h-[28px] flex items-center\">\r\n {hasError && error && (\r\n <span className=\"text-red-400 animate-pulse\">{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-400\r\n text-white\r\n transition-all duration-200\r\n hover:scale-105 active:scale-95\r\n disabled:opacity-50 disabled:cursor-not-allowed\r\n shadow-lg shadow-red-500/25\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 \r\n hover:text-amber-400 hover:bg-zinc-800\r\n rounded-lg\r\n transition-all duration-200\r\n hover:scale-105 active:scale-95\r\n disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\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-all duration-200\r\n disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\r\n ${canSubmit\r\n ? 'bg-amber-500 hover:bg-amber-400 text-zinc-900 hover:scale-105 active:scale-95 shadow-lg shadow-amber-500/25'\r\n : 'bg-zinc-800 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"]}
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":";;;;AAGA,IAAM,eAAA,GAAyC;AAAA,EAC3C,UAAA,EAAY,GAAA;AAAA,EACZ,WAAA,EAAa,EAAA;AAAA,EACb,QAAA,EAAU;AACd,CAAA;AAYO,SAAS,cAAA,CACZ,OAAA,GAA0C,EAAC,EACvB;AACpB,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,eAAA,EAAiB,GAAG,OAAA,EAAQ;AAGhD,EAAA,MAAM,iBAAA,GAAoB,MAAA,CAAiB,EAAE,CAAA;AAG7C,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAiB,CAAC,CAAA;AACxD,EAAA,MAAM,CAAC,iBAAA,EAAmB,oBAAoB,CAAA,GAAI,SAAiB,CAAC,CAAA;AAGpE,EAAA,MAAM,GAAG,WAAW,CAAA,GAAI,QAAA,CAAS,EAAE,CAAA;AAGnC,EAAA,MAAM,eAAA,GAAkB,YAAY,MAAM;AACtC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,WAAA,GAAc,MAAM,MAAA,CAAO,QAAA;AAGjC,IAAA,iBAAA,CAAkB,OAAA,GAAU,kBAAkB,OAAA,CAAQ,MAAA;AAAA,MAClD,CAAC,OAAO,EAAA,GAAK;AAAA,KACjB;AAEA,IAAA,OAAO,MAAA,CAAO,WAAA,GAAc,iBAAA,CAAkB,OAAA,CAAQ,MAAA;AAAA,EAC1D,GAAG,CAAC,MAAA,CAAO,QAAA,EAAU,MAAA,CAAO,WAAW,CAAC,CAAA;AAGxC,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,IAAI,WAAA,IAAe,IAAA,CAAK,GAAA,EAAI,EAAG;AAC3B,MAAA,oBAAA,CAAqB,CAAC,CAAA;AACtB,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,QAAA,GAAW,YAAY,MAAM;AAC/B,MAAA,MAAM,YAAY,IAAA,CAAK,GAAA,CAAI,GAAG,WAAA,GAAc,IAAA,CAAK,KAAK,CAAA;AACtD,MAAA,oBAAA,CAAqB,SAAS,CAAA;AAE9B,MAAA,IAAI,cAAc,CAAA,EAAG;AACjB,QAAA,aAAA,CAAc,QAAQ,CAAA;AAAA,MAC1B;AAAA,IACJ,GAAG,GAAG,CAAA;AAEN,IAAA,OAAO,MAAM,cAAc,QAAQ,CAAA;AAAA,EACvC,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAGhB,EAAA,MAAM,UAAA,GAAa,YAAY,MAAM;AACjC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAGrB,IAAA,IAAI,MAAM,WAAA,EAAa;AACnB,MAAA,OAAO,KAAA;AAAA,IACX;AAGA,IAAA,OAAO,iBAAgB,GAAI,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,WAAA,EAAa,eAAe,CAAC,CAAA;AAGjC,EAAA,MAAM,aAAA,GAAgB,YAAY,MAAM;AACpC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAGrB,IAAA,iBAAA,CAAkB,OAAA,CAAQ,KAAK,GAAG,CAAA;AAGlC,IAAA,MAAM,cAAA,GAAiB,MAAM,MAAA,CAAO,UAAA;AACpC,IAAA,cAAA,CAAe,cAAc,CAAA;AAC7B,IAAA,oBAAA,CAAqB,OAAO,UAAU,CAAA;AAGtC,IAAA,WAAA,CAAY,EAAE,CAAA;AAAA,EAClB,CAAA,EAAG,CAAC,MAAA,CAAO,UAAU,CAAC,CAAA;AAGtB,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC5B,IAAA,iBAAA,CAAkB,UAAU,EAAC;AAC7B,IAAA,cAAA,CAAe,CAAC,CAAA;AAChB,IAAA,oBAAA,CAAqB,CAAC,CAAA;AACtB,IAAA,WAAA,CAAY,EAAE,CAAA;AAAA,EAClB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACH,YAAY,UAAA,EAAW;AAAA,IACvB,iBAAA;AAAA,IACA,mBAAmB,eAAA,EAAgB;AAAA,IACnC,aAAA;AAAA,IACA;AAAA,GACJ;AACJ;AC3GA,IAAMA,gBAAAA,GAA2C;AAAA,EAC7C,aAAA,EAAe,GAAA;AAAA;AAAA,EACf,SAAA,EAAW,CAAC,YAAA,EAAc,WAAA,EAAa,aAAa,WAAW;AACnE,CAAA;AAKA,SAAS,qBAAqB,cAAA,EAAyC;AACnE,EAAA,IAAI,OAAO,kBAAkB,WAAA,EAAa;AACtC,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,KAAA,MAAW,YAAY,cAAA,EAAgB;AACnC,IAAA,IAAI,aAAA,CAAc,eAAA,CAAgB,QAAQ,CAAA,EAAG;AACzC,MAAA,OAAO,QAAA;AAAA,IACX;AAAA,EACJ;AAGA,EAAA,OAAO,EAAA;AACX;AASO,SAAS,gBAAA,CACZ,OAAA,GAA4C,EAAC,EACvB;AACtB,EAAA,MAAM,MAAA,GAAS,EAAE,GAAGA,gBAAAA,EAAiB,GAAG,OAAA,EAAQ;AAEhD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIC,SAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAS,CAAC,CAAA;AAC1C,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAsB,IAAI,CAAA;AAC5D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,QAAAA,CAAmB,EAAE,CAAA;AAE3D,EAAA,MAAM,gBAAA,GAAmBC,OAA6B,IAAI,CAAA;AAC1D,EAAA,MAAM,SAAA,GAAYA,OAA2B,IAAI,CAAA;AACjD,EAAA,MAAM,SAAA,GAAYA,MAAAA,CAAe,EAAE,CAAA;AACnC,EAAA,MAAM,YAAA,GAAeA,OAAe,CAAC,CAAA;AACrC,EAAA,MAAM,mBAAA,GAAsBA,OAA8C,IAAI,CAAA;AAC9E,EAAA,MAAM,qBAAA,GAAwBA,OAA6C,IAAI,CAAA;AAG/E,EAAA,MAAM,eAAA,GAAkBA,OAA4B,IAAI,CAAA;AACxD,EAAA,MAAM,WAAA,GAAcA,OAA4B,IAAI,CAAA;AACpD,EAAA,MAAM,iBAAA,GAAoBA,OAAsB,IAAI,CAAA;AAGpD,EAAA,MAAM,WAAA,GAAc,OAAO,SAAA,KAAc,WAAA,IAClC,cAAA,IAAkB,aAClB,cAAA,IAAkB,SAAA,CAAU,YAAA,IAC5B,OAAO,aAAA,KAAkB,WAAA;AAGhC,EAAA,MAAM,iBAAA,GAAoBC,YAAY,MAAM;AACxC,IAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AAE1B,IAAA,MAAM,WAAW,WAAA,CAAY,OAAA;AAC7B,IAAA,MAAM,eAAe,QAAA,CAAS,OAAA;AAC9B,IAAA,MAAM,SAAA,GAAY,IAAI,UAAA,CAAW,YAAY,CAAA;AAG7C,IAAA,QAAA,CAAS,sBAAsB,SAAS,CAAA;AAGxC,IAAA,MAAM,IAAA,GAAO,EAAA;AACb,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,YAAA,GAAe,IAAI,CAAA;AAC3C,IAAA,MAAM,SAAmB,EAAC;AAE1B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,EAAM,CAAA,EAAA,EAAK;AAE3B,MAAA,IAAI,YAAA,GAAe,CAAA;AACnB,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,EAAM,CAAA,EAAA,EAAK;AAC3B,QAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,CAAA,GAAI,IAAA,GAAO,CAAC,CAAA;AACpC,QAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,GAAG,CAAA;AACtC,QAAA,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,YAAA,EAAc,SAAS,CAAA;AAAA,MACnD;AAGA,MAAA,MAAA,CAAO,KAAK,IAAA,CAAK,GAAA,CAAI,GAAI,YAAA,GAAe,GAAA,GAAO,GAAG,CAAC,CAAA;AAAA,IACvD;AAEA,IAAA,cAAA,CAAe,MAAM,CAAA;AAGrB,IAAA,iBAAA,CAAkB,OAAA,GAAU,sBAAsB,iBAAiB,CAAA;AAAA,EACvE,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,OAAA,GAAUA,YAAY,MAAM;AAC9B,IAAA,IAAI,oBAAoB,OAAA,EAAS;AAC7B,MAAA,aAAA,CAAc,oBAAoB,OAAO,CAAA;AACzC,MAAA,mBAAA,CAAoB,OAAA,GAAU,IAAA;AAAA,IAClC;AAEA,IAAA,IAAI,sBAAsB,OAAA,EAAS;AAC/B,MAAA,YAAA,CAAa,sBAAsB,OAAO,CAAA;AAC1C,MAAA,qBAAA,CAAsB,OAAA,GAAU,IAAA;AAAA,IACpC;AAEA,IAAA,IAAI,kBAAkB,OAAA,EAAS;AAC3B,MAAA,oBAAA,CAAqB,kBAAkB,OAAO,CAAA;AAC9C,MAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAAA,IAChC;AAEA,IAAA,IAAI,gBAAgB,OAAA,EAAS;AACzB,MAAA,eAAA,CAAgB,QAAQ,KAAA,EAAM;AAC9B,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAAA,IAC9B;AAEA,IAAA,IAAI,UAAU,OAAA,EAAS;AACnB,MAAA,SAAA,CAAU,OAAA,CAAQ,WAAU,CAAE,OAAA,CAAQ,CAAC,KAAA,KAAU,KAAA,CAAM,MAAM,CAAA;AAC7D,MAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AAAA,IACxB;AAEA,IAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,IAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAC3B,IAAA,SAAA,CAAU,UAAU,EAAC;AACrB,IAAA,cAAA,CAAe,EAAE,CAAA;AAAA,EACrB,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,aAAA,GAAgBA,YAAY,MAAM;AACpC,IAAA,IAAI,gBAAA,CAAiB,OAAA,IAAW,gBAAA,CAAiB,OAAA,CAAQ,UAAU,UAAA,EAAY;AAC3E,MAAA,gBAAA,CAAiB,QAAQ,IAAA,EAAK;AAAA,IAClC;AACA,IAAA,cAAA,CAAe,KAAK,CAAA;AAAA,EACxB,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,cAAA,GAAiBA,YAAY,YAAY;AAC3C,IAAA,IAAI,CAAC,WAAA,EAAa;AACd,MAAA,QAAA,CAAS,IAAI,KAAA,CAAM,kDAAkD,CAAC,CAAA;AACtE,MAAA;AAAA,IACJ;AAGA,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,WAAA,CAAY,CAAC,CAAA;AACb,IAAA,cAAA,CAAe,EAAE,CAAA;AACjB,IAAA,SAAA,CAAU,UAAU,EAAC;AAErB,IAAA,IAAI;AAEA,MAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,YAAA,CAAa,aAAa,EAAE,KAAA,EAAO,MAAM,CAAA;AACxE,MAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AAGpB,MAAA,MAAM,YAAA,GAAe,IAAI,YAAA,EAAa;AACtC,MAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAE1B,MAAA,MAAM,MAAA,GAAS,YAAA,CAAa,uBAAA,CAAwB,MAAM,CAAA;AAC1D,MAAA,MAAM,QAAA,GAAW,aAAa,cAAA,EAAe;AAC7C,MAAA,QAAA,CAAS,OAAA,GAAU,GAAA;AACnB,MAAA,QAAA,CAAS,qBAAA,GAAwB,GAAA;AACjC,MAAA,MAAA,CAAO,QAAQ,QAAQ,CAAA;AACvB,MAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAGtB,MAAA,MAAM,QAAA,GAAW,oBAAA,CAAqB,MAAA,CAAO,SAAS,CAAA;AAGtD,MAAA,MAAM,aAAA,GAAgB,IAAI,aAAA,CAAc,MAAA,EAAQ,WAAW,EAAE,QAAA,KAAa,KAAA,CAAS,CAAA;AACnF,MAAA,gBAAA,CAAiB,OAAA,GAAU,aAAA;AAG3B,MAAA,aAAA,CAAc,eAAA,GAAkB,CAAC,KAAA,KAAU;AACvC,QAAA,IAAI,KAAA,CAAM,IAAA,CAAK,IAAA,GAAO,CAAA,EAAG;AACrB,UAAA,SAAA,CAAU,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAAA,QACrC;AAAA,MACJ,CAAA;AAGA,MAAA,aAAA,CAAc,SAAS,MAAM;AACzB,QAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS;AAAA,UACrC,MAAM,QAAA,IAAY;AAAA,SACrB,CAAA;AACD,QAAA,YAAA,CAAa,IAAI,CAAA;AAGjB,QAAA,IAAI,OAAO,mBAAA,EAAqB;AAC5B,UAAA,MAAA,CAAO,oBAAoB,IAAI,CAAA;AAAA,QACnC;AAEA,QAAA,OAAA,EAAQ;AAAA,MACZ,CAAA;AAGA,MAAA,aAAA,CAAc,UAAU,MAAM;AAC1B,QAAA,QAAA,CAAS,IAAI,KAAA,CAAM,0BAA0B,CAAC,CAAA;AAC9C,QAAA,cAAA,CAAe,KAAK,CAAA;AACpB,QAAA,OAAA,EAAQ;AAAA,MACZ,CAAA;AAGA,MAAA,aAAA,CAAc,MAAM,GAAG,CAAA;AACvB,MAAA,YAAA,CAAa,OAAA,GAAU,KAAK,GAAA,EAAI;AAChC,MAAA,cAAA,CAAe,IAAI,CAAA;AAGnB,MAAA,iBAAA,EAAkB;AAGlB,MAAA,mBAAA,CAAoB,OAAA,GAAU,YAAY,MAAM;AAC5C,QAAA,WAAA,CAAY,IAAA,CAAK,GAAA,EAAI,GAAI,YAAA,CAAa,OAAO,CAAA;AAAA,MACjD,GAAG,GAAG,CAAA;AAGN,MAAA,qBAAA,CAAsB,OAAA,GAAU,WAAW,MAAM;AAC7C,QAAA,aAAA,EAAc;AAAA,MAClB,CAAA,EAAG,OAAO,aAAa,CAAA;AAAA,IAE3B,SAAS,GAAA,EAAK;AACV,MAAA,MAAM,YAAA,GAAe,GAAA,YAAe,KAAA,GAC9B,GAAA,CAAI,OAAA,GACJ,6BAAA;AACN,MAAA,QAAA,CAAS,IAAI,KAAA,CAAM,YAAY,CAAC,CAAA;AAChC,MAAA,OAAA,EAAQ;AAAA,IACZ;AAAA,EACJ,CAAA,EAAG,CAAC,WAAA,EAAa,MAAA,CAAO,SAAA,EAAW,MAAA,CAAO,aAAA,EAAe,MAAA,CAAO,mBAAA,EAAqB,OAAA,EAAS,aAAA,EAAe,iBAAiB,CAAC,CAAA;AAG/H,EAAA,MAAM,eAAA,GAAkBA,YAAY,MAAM;AACtC,IAAA,OAAA,EAAQ;AACR,IAAA,cAAA,CAAe,KAAK,CAAA;AACpB,IAAA,WAAA,CAAY,CAAC,CAAA;AACb,IAAA,YAAA,CAAa,IAAI,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAGZ,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC5B,IAAA,OAAA,EAAQ;AACR,IAAA,cAAA,CAAe,KAAK,CAAA;AACpB,IAAA,WAAA,CAAY,CAAC,CAAA;AACb,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,cAAA,CAAe,EAAE,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAGZ,EAAAC,UAAU,MAAM;AACZ,IAAA,OAAO,MAAM;AACT,MAAA,OAAA,EAAQ;AAAA,IACZ,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO;AAAA,IACH,WAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,KAAA;AAAA,IACA,cAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA;AAAA,GACJ;AACJ;;;ACjQA,IAAM,kBAAA,GAAsC;AAAA,EACxC,UAAA,EAAY,GAAA;AAAA,EACZ,WAAA,EAAa,EAAA;AAAA,EACb,QAAA,EAAU;AACd,CAAA;AAEA,IAAM,oBAAA,GAAoC;AAAA,EACtC,aAAA,EAAe,GAAA;AAAA,EACf,SAAA,EAAW,CAAC,YAAA,EAAc,WAAA,EAAa,aAAa,WAAW;AACnE,CAAA;AAUO,SAAS,WAAW,OAAA,EAA8C;AACrE,EAAA,MAAM;AAAA,IACF,IAAA;AAAA,IACA,SAAA;AAAA,IACA,YAAY,EAAC;AAAA,IACb,cAAc,EAAC;AAAA,IACf,SAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACJ,GAAI,OAAA;AAEJ,EAAA,MAAM,eAAA,GAAkB,EAAE,GAAG,kBAAA,EAAoB,GAAG,SAAA,EAAU;AAC9D,EAAA,MAAM,iBAAA,GAAoB,EAAE,GAAG,oBAAA,EAAsB,GAAG,WAAA,EAAY;AAGpE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIH,SAAuB,MAAM,CAAA;AACvD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,SAAS,EAAE,CAAA;AACnC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,SAAkB,IAAI,CAAA;AAGlD,EAAA,MAAM,qBAAA,GAAwBC,OAAO,KAAK,CAAA;AAG1C,EAAA,MAAM,WAAA,GAAc,eAAe,eAAe,CAAA;AAGlD,EAAA,MAAM,gBAAgB,gBAAA,CAAiB;AAAA,IACnC,GAAG;AAAA,GACN,CAAA;AAGD,EAAAE,UAAU,MAAM;AACZ,IAAA,IAAI,CAAC,WAAA,CAAY,UAAA,IAAc,KAAA,KAAU,MAAA,EAAQ;AAC7C,MAAA,QAAA,CAAS,cAAc,CAAA;AAAA,IAC3B,CAAA,MAAA,IAAW,WAAA,CAAY,UAAA,IAAc,KAAA,KAAU,cAAA,EAAgB;AAC3D,MAAA,QAAA,CAAS,MAAM,CAAA;AAAA,IACnB;AAAA,EACJ,CAAA,EAAG,CAAC,WAAA,CAAY,UAAA,EAAY,KAAK,CAAC,CAAA;AAGlC,EAAAA,UAAU,MAAM;AACZ,IAAA,IAAI,aAAA,CAAc,WAAA,IAAe,KAAA,KAAU,WAAA,EAAa;AACpD,MAAA,QAAA,CAAS,WAAW,CAAA;AAAA,IACxB;AAAA,EACJ,CAAA,EAAG,CAAC,aAAA,CAAc,WAAA,EAAa,KAAK,CAAC,CAAA;AAGrC,EAAAA,UAAU,MAAM;AACZ,IAAA,IAAI,cAAc,KAAA,EAAO;AACrB,MAAA,QAAA,CAAS,cAAc,KAAK,CAAA;AAC5B,MAAA,QAAA,CAAS,OAAO,CAAA;AAChB,MAAA,OAAA,GAAU,cAAc,KAAK,CAAA;AAAA,IACjC;AAAA,EACJ,CAAA,EAAG,CAAC,aAAA,CAAc,KAAA,EAAO,OAAO,CAAC,CAAA;AAGjC,EAAA,MAAM,UAAA,GAAaD,YAAY,YAAY;AACvC,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,EAAK,IAAK,CAAC,YAAY,UAAA,EAAY;AACzC,MAAA;AAAA,IACJ;AAEA,IAAA,QAAA,CAAS,SAAS,CAAA;AAClB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,WAAA,CAAY,aAAA,EAAc;AAE1B,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,IAAI,CAAA;AAChC,MAAA,SAAA,CAAU,QAAQ,CAAA;AAClB,MAAA,QAAA,CAAS,SAAS,CAAA;AAClB,MAAA,SAAA,GAAY,QAAQ,CAAA;AAEpB,MAAA,OAAA,CAAQ,EAAE,CAAA;AAAA,IACd,SAAS,GAAA,EAAK;AACV,MAAA,MAAME,SAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,gBAAgB,CAAA;AACrE,MAAA,QAAA,CAASA,MAAK,CAAA;AACd,MAAA,QAAA,CAAS,OAAO,CAAA;AAChB,MAAA,OAAA,GAAUA,MAAK,CAAA;AAAA,IACnB;AAAA,EACJ,GAAG,CAAC,IAAA,EAAM,aAAa,IAAA,EAAM,SAAA,EAAW,OAAO,CAAC,CAAA;AAGhD,EAAA,MAAM,WAAA,GAAcF,WAAAA,CAAY,OAAO,IAAA,KAAe;AAClD,IAAA,IAAI,CAAC,YAAY,UAAA,EAAY;AACzB,MAAA;AAAA,IACJ;AAEA,IAAA,QAAA,CAAS,SAAS,CAAA;AAClB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,WAAA,CAAY,aAAA,EAAc;AAE1B,IAAA,IAAI;AAEA,MAAA,MAAM,SAAS,SAAA,IAAa,IAAA;AAC5B,MAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,IAAI,CAAA;AAClC,MAAA,SAAA,CAAU,QAAQ,CAAA;AAClB,MAAA,QAAA,CAAS,SAAS,CAAA;AAClB,MAAA,SAAA,GAAY,QAAQ,CAAA;AAGpB,MAAA,IAAI,eAAA,IAAmB,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAC7D,QAAA,MAAM,GAAA,GAAM,QAAA;AAEZ,QAAA,MAAM,iBAAA,GAAoB,GAAA,CAAI,IAAA,IAAQ,GAAA,CAAI,iBAAiB,GAAA,CAAI,UAAA;AAC/D,QAAA,IAAI,OAAO,sBAAsB,QAAA,EAAU;AACvC,UAAA,OAAA,CAAQ,iBAAiB,CAAA;AACzB,UAAA,eAAA,CAAgB,iBAAiB,CAAA;AAAA,QACrC;AAAA,MACJ;AAAA,IACJ,SAAS,GAAA,EAAK;AACV,MAAA,MAAME,SAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,gBAAgB,CAAA;AACrE,MAAA,QAAA,CAASA,MAAK,CAAA;AACd,MAAA,QAAA,CAAS,OAAO,CAAA;AAChB,MAAA,OAAA,GAAUA,MAAK,CAAA;AAAA,IACnB;AAAA,EACJ,CAAA,EAAG,CAAC,WAAA,EAAa,IAAA,EAAM,WAAW,SAAA,EAAW,OAAA,EAAS,eAAe,CAAC,CAAA;AAGtE,EAAAD,UAAU,MAAM;AACZ,IAAA,IAAI,sBAAsB,OAAA,IAAW,aAAA,CAAc,SAAA,IAAa,CAAC,cAAc,WAAA,EAAa;AACxF,MAAA,qBAAA,CAAsB,OAAA,GAAU,KAAA;AAChC,MAAA,WAAA,CAAY,cAAc,SAAS,CAAA;AAAA,IACvC;AAAA,EACJ,GAAG,CAAC,aAAA,CAAc,WAAW,aAAA,CAAc,WAAA,EAAa,WAAW,CAAC,CAAA;AAGpE,EAAA,MAAM,cAAA,GAAiBD,YAAY,YAAY;AAC3C,IAAA,IAAI,CAAC,YAAY,UAAA,EAAY;AACzB,MAAA;AAAA,IACJ;AACA,IAAA,MAAM,cAAc,cAAA,EAAe;AAAA,EACvC,CAAA,EAAG,CAAC,WAAA,CAAY,UAAA,EAAY,aAAa,CAAC,CAAA;AAG1C,EAAA,MAAM,aAAA,GAAgBA,YAAY,MAAM;AAEpC,IAAA,qBAAA,CAAsB,OAAA,GAAU,IAAA;AAChC,IAAA,aAAA,CAAc,aAAA,EAAc;AAAA,EAChC,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAGlB,EAAA,MAAM,eAAA,GAAkBA,YAAY,MAAM;AACtC,IAAA,aAAA,CAAc,eAAA,EAAgB;AAC9B,IAAA,QAAA,CAAS,MAAM,CAAA;AAAA,EACnB,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAGlB,EAAA,MAAM,MAAA,GAASA,YAAY,MAAM;AAC7B,IAAA,IAAI,cAAc,WAAA,EAAa;AAC3B,MAAA,aAAA,EAAc;AAAA,IAClB,CAAA,MAAA,IAAW,IAAA,CAAK,IAAA,EAAK,EAAG;AACpB,MAAA,UAAA,EAAW;AAAA,IACf;AAAA,EACJ,GAAG,CAAC,aAAA,CAAc,aAAa,IAAA,EAAM,aAAA,EAAe,UAAU,CAAC,CAAA;AAG/D,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC5B,IAAA,QAAA,CAAS,MAAM,CAAA;AACf,IAAA,OAAA,CAAQ,EAAE,CAAA;AACV,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,SAAA,CAAU,IAAI,CAAA;AACd,IAAA,WAAA,CAAY,KAAA,EAAM;AAClB,IAAA,aAAA,CAAc,KAAA,EAAM;AAAA,EACxB,CAAA,EAAG,CAAC,WAAA,EAAa,aAAa,CAAC,CAAA;AAG/B,EAAA,MAAM,SAAA,GACF,WAAA,CAAY,UAAA,IACZ,KAAA,KAAU,SAAA,KACT,cAAc,WAAA,IAAe,IAAA,CAAK,IAAA,EAAK,CAAE,MAAA,GAAS,CAAA,CAAA;AAEvD,EAAA,OAAO;AAAA;AAAA,IAEH,KAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA;AAAA,IAGA,IAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA;AAAA,IAGA,aAAa,aAAA,CAAc,WAAA;AAAA,IAC3B,cAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA,mBAAmB,aAAA,CAAc,QAAA;AAAA,IACjC,sBAAsB,iBAAA,CAAkB,aAAA;AAAA,IACxC,aAAa,aAAA,CAAc,WAAA;AAAA;AAAA,IAG3B,mBAAmB,WAAA,CAAY,iBAAA;AAAA,IAC/B,mBAAmB,WAAA,CAAY,iBAAA;AAAA;AAAA,IAG/B;AAAA,GACJ;AACJ;AC9NA,SAAS,eAAe,EAAA,EAAoB;AACxC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,EAAA,GAAK,GAAI,CAAA;AACpC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACvC,EAAA,MAAM,mBAAmB,OAAA,GAAU,EAAA;AACnC,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,gBAAA,CAAiB,UAAS,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AACrE;AAKA,SAAS,QAAA,CAAS,EAAE,MAAA,EAAQ,SAAA,GAAY,IAAG,EAA6C;AACpF,EAAA,MAAM,IAAA,GAAO,OAAO,MAAA,GAAS,CAAA,GAAI,SAAS,KAAA,CAAM,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAE7D,EAAA,uBACI,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,CAAA,4CAAA,EAA+C,SAAS,IACnE,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,KAAA,EAAO,CAAA,qBACd,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MAEG,SAAA,EAAU,sGAAA;AAAA,MACV,KAAA,EAAO;AAAA,QACH,QAAQ,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAG,KAAA,GAAQ,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,QAClC,OAAA,EAAS,MAAM,KAAA,GAAQ;AAAA;AAC3B,KAAA;AAAA,IALK;AAAA,GAOZ,CAAA,EACL,CAAA;AAER;AAKA,SAAS,cAAA,GAAiB;AACtB,EAAA,uBACI,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,4BAAA,EACZ,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,oFAAA,EAAqF,CAAA;AAAA,oBACrG,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,sDAAA,EAAuD;AAAA,GAAA,EAC3E,CAAA;AAER;AAKA,SAAS,OAAA,CAAQ,EAAE,SAAA,GAAY,EAAA,EAAG,EAA2B;AACzD,EAAA,uBACI,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,OAAA,EAAQ,aAAA,EAAc,IAAA,EAAK,cAAA,EAClD,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,wQAAA,EAAyQ,CAAA,EACrR,CAAA;AAER;AAKA,SAAS,WAAA,CAAY,EAAE,SAAA,GAAY,EAAA,EAAG,EAA2B;AAC7D,EAAA,uBACI,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,OAAA,EAAQ,aAAA,EAAc,IAAA,EAAK,cAAA,EAClD,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,+JAAA,EAAgK,CAAA,EAC5K,CAAA;AAER;AAKA,SAAS,QAAA,CAAS,EAAE,SAAA,GAAY,EAAA,EAAG,EAA2B;AAC1D,EAAA,2BACK,KAAA,EAAA,EAAI,SAAA,EAAsB,SAAQ,aAAA,EAAc,IAAA,EAAK,gBAClD,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,GAAE,IAAA,EAAK,CAAA,EAAE,MAAK,KAAA,EAAM,KAAA,EAAM,QAAO,KAAA,EAAM,EAAA,EAAG,KAAI,CAAA,EACxD,CAAA;AAER;AAKA,SAAS,KAAA,CAAM,EAAE,SAAA,GAAY,EAAA,EAAG,EAA2B;AACvD,EAAA,uBACI,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,OAAA,EAAQ,aAAA,EAAc,IAAA,EAAK,cAAA,EAClD,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,0LAAA,EAA2L,CAAA,EACvM,CAAA;AAER;AAKA,SAAS,OAAA,CAAQ,EAAE,SAAA,GAAY,EAAA,EAAG,EAA2B;AACzD,EAAA,uBACI,IAAA,CAAC,SAAI,SAAA,EAAW,CAAA,aAAA,EAAgB,SAAS,CAAA,CAAA,EAAI,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAClE,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,QAAA,EAAA,EAAO,SAAA,EAAU,YAAA,EAAa,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,IAAA,EAAK,MAAA,EAAO,cAAA,EAAe,WAAA,EAAY,GAAA,EAAI,CAAA;AAAA,wBAC3F,MAAA,EAAA,EAAK,SAAA,EAAU,cAAa,IAAA,EAAK,cAAA,EAAe,GAAE,iHAAA,EAAkH;AAAA,GAAA,EACzK,CAAA;AAER;AAOA,SAAS,SAAA,CAAU;AAAA,EACf,IAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,WAAA;AAAA,EACA,iBAAA;AAAA,EACA,WAAA,GAAc,iBAAA;AAAA,EACd,QAAA,GAAW;AACf,CAAA,EAGG;AACC,EAAA,MAAM,YAAY,KAAA,KAAU,SAAA;AAC5B,EAAA,MAAM,gBAAgB,KAAA,KAAU,cAAA;AAChC,EAAA,MAAM,WAAW,KAAA,KAAU,OAAA;AAE3B,EAAA,MAAM,aAAA,GAAgB,CAAC,CAAA,KAAgD;AACnE,IAAA,IAAI,CAAA,CAAE,QAAQ,OAAA,IAAW,CAAC,EAAE,QAAA,IAAY,SAAA,IAAa,CAAC,WAAA,EAAa;AAC/D,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAA,EAAO;AAAA,IACX;AAAA,EACJ,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAA8C;AAC/D,IAAA,OAAA,CAAQ,CAAA,CAAE,OAAO,KAAK,CAAA;AACtB,IAAA,CAAA,CAAE,MAAA,CAAO,MAAM,MAAA,GAAS,MAAA;AACxB,IAAA,CAAA,CAAE,MAAA,CAAO,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,KAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,MAAA,CAAO,YAAA,EAAc,EAAE,CAAA,EAAG,GAAG,CAAC,CAAA,EAAA,CAAA;AAAA,EACjF,CAAA;AAEA,EAAA,uBACI,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EAEX,QAAA,kBAAA,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACG,SAAA,EAAW;AAAA;AAAA;AAAA;AAAA,oBAAA,EAIL,WAAA,GAAc,uBAAuB,EAAE;AAAA,oBAAA,EACvC,QAAA,GAAW,eAAe,EAAE;AAAA,gBAAA,CAAA;AAAA,MAIlC,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACG,KAAA,EAAO,IAAA;AAAA,YACP,QAAA,EAAU,WAAA;AAAA,YACV,SAAA,EAAW,aAAA;AAAA,YACX,WAAA,EAAa,cAAc,cAAA,GAAiB,WAAA;AAAA,YAC5C,QAAA,EAAU,YAAY,SAAA,IAAa,aAAA;AAAA,YACnC,IAAA,EAAM,CAAA;AAAA,YACN,SAAA,EAAU,+JAAA;AAAA,YACV,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAA;AAAO;AAAA,SAC5B;AAAA,wBAGA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,kDAAA,EAEX,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACV,QAAA,EAAA,WAAA,mBACG,IAAA,CAAA,QAAA,EAAA,EACI,QAAA,EAAA;AAAA,4BAAA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACG,OAAA,EAAS,eAAA;AAAA,gBACT,QAAA;AAAA,gBACA,SAAA,EAAU,mFAAA;AAAA,gBACV,YAAA,EAAW,kBAAA;AAAA,gBAEX,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,SAAA,EAAU;AAAA;AAAA,aAC/B;AAAA,4BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EACX,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,cAAA,EAAA,EAAe,CAAA;AAAA,8BAChB,GAAA,CAAC,QAAA,EAAA,EAAS,MAAA,EAAQ,WAAA,EAAa;AAAA,aAAA,EACnC,CAAA;AAAA,gCACC,MAAA,EAAA,EAAK,SAAA,EAAU,oDAAA,EACX,QAAA,EAAA,cAAA,CAAe,iBAAiB,CAAA,EACrC;AAAA,WAAA,EACJ,CAAA,mBAEA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wCAAA,EACV,QAAA,EAAA;AAAA,YAAA,QAAA,IAAY,yBACT,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAA,EAAqC,gBAAM,OAAA,EAAQ,CAAA;AAAA,YAEtE,aAAA,oBACG,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAwB,QAAA,EAAA;AAAA,cAAA,OAAA;AAAA,cAC9B,eAAe,iBAAiB;AAAA,aAAA,EAC1C;AAAA,WAAA,EAER,CAAA,EAER,CAAA;AAAA,0BAGA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACV,QAAA,EAAA,WAAA,mBACG,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACG,OAAA,EAAS,aAAA;AAAA,cACT,QAAA;AAAA,cACA,SAAA,EAAU,mMAAA;AAAA,cACV,YAAA,EAAW,gBAAA;AAAA,cAEX,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,SAAA,EAAU,SAAA,EAAU;AAAA;AAAA,8BAGlC,IAAA,CAAA,QAAA,EAAA,EACI,QAAA,EAAA;AAAA,4BAAA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACG,OAAA,EAAS,cAAA;AAAA,gBACT,QAAA,EAAU,YAAY,SAAA,IAAa,aAAA;AAAA,gBACnC,SAAA,EAAU,4KAAA;AAAA,gBACV,YAAA,EAAW,iBAAA;AAAA,gBAEX,QAAA,kBAAA,GAAA,CAAC,OAAA,EAAA,EAAQ,SAAA,EAAU,SAAA,EAAU;AAAA;AAAA,aACjC;AAAA,4BACA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACG,OAAA,EAAS,MAAA;AAAA,gBACT,QAAA,EAAU,CAAC,SAAA,IAAa,QAAA;AAAA,gBACxB,SAAA,EAAW;AAAA;AAAA;AAAA,wCAAA,EAGL,SAAA,GACI,mEACA,uBACN;AAAA,oCAAA,CAAA;AAAA,gBAEJ,YAAA,EAAW,cAAA;AAAA,gBAEV,QAAA,EAAA,SAAA,uBAAa,OAAA,EAAA,EAAQ,SAAA,EAAU,WAAU,CAAA,mBAAK,GAAA,CAAC,WAAA,EAAA,EAAY,SAAA,EAAU,SAAA,EAAU;AAAA;AAAA;AACpF,WAAA,EACJ,CAAA,EAER;AAAA,SAAA,EACJ;AAAA;AAAA;AAAA,GACJ,EACJ,CAAA;AAER;AAYO,SAAS,OAAA,CAAQ;AAAA,EACpB,IAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,eAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA,GAAW;AACf,CAAA,EAAiB;AACb,EAAA,MAAM,aAAa,UAAA,CAAW;AAAA,IAC1B,IAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACH,CAAA;AAED,EAAA,IAAI,QAAA,EAAU;AACV,IAAA,uBAAO,GAAA,CAAA,QAAA,EAAA,EAAG,QAAA,EAAA,QAAA,CAAS,UAAU,CAAA,EAAE,CAAA;AAAA,EACnC;AAEA,EAAA,uBACI,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,CAAA,OAAA,EAAU,SAAA,IAAa,EAAE,CAAA,CAAA,EACrC,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAW,GAAG,UAAA,EAAY,WAAA,EAA0B,UAAoB,CAAA,EAC7E,CAAA;AAER","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 - uses time domain for better voice visualization\r\n const updateAudioLevels = useCallback(() => {\r\n if (!analyserRef.current) return\r\n\r\n const analyser = analyserRef.current\r\n const bufferLength = analyser.fftSize\r\n const dataArray = new Uint8Array(bufferLength)\r\n\r\n // Use time domain data for even distribution across bars\r\n analyser.getByteTimeDomainData(dataArray)\r\n\r\n // Sample 16 bars from the waveform data\r\n const bars = 16\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 // Get amplitude variation from center (128) for each segment\r\n let maxDeviation = 0\r\n for (let j = 0; j < step; j++) {\r\n const value = dataArray[i * step + j]\r\n const deviation = Math.abs(value - 128)\r\n maxDeviation = Math.max(maxDeviation, deviation)\r\n }\r\n // Normalize to 0-1 range (max deviation is 128)\r\n // Apply some amplification for better visibility\r\n levels.push(Math.min(1, (maxDeviation / 128) * 2.5))\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 with smooth animations\r\n */\r\nfunction Waveform({ levels, className = '' }: { levels: number[]; className?: string }) {\r\n const bars = levels.length > 0 ? levels : Array(16).fill(0.15)\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-gradient-to-t from-amber-600 to-amber-400 rounded-full transition-all duration-100 ease-out\"\r\n style={{\r\n height: `${Math.max(6, level * 40)}px`,\r\n opacity: 0.6 + level * 0.4,\r\n }}\r\n />\r\n ))}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Recording pulse indicator\r\n */\r\nfunction RecordingPulse() {\r\n return (\r\n <span className=\"relative flex h-3 w-3 mr-2\">\r\n <span className=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75\"></span>\r\n <span className=\"relative inline-flex rounded-full h-3 w-3 bg-red-500\"></span>\r\n </span>\r\n )\r\n}\r\n\r\n/**\r\n * Microphone icon\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\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 <rect x=\"64\" y=\"64\" width=\"128\" height=\"128\" rx=\"8\" />\r\n </svg>\r\n )\r\n}\r\n\r\n/**\r\n * X icon\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\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 className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\r\n <path className=\"opacity-75\" fill=\"currentColor\" 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 </svg>\r\n )\r\n}\r\n\r\n/**\r\n * Default UI - uses CSS variables for automatic theme detection\r\n * The CSS variables are defined in styles.css and automatically switch\r\n * based on prefers-color-scheme, .dark class, or data-theme attribute\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 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 const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\r\n setText(e.target.value)\r\n e.target.style.height = 'auto'\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=\"ai-input w-full\">\r\n {/* Main container - uses CSS variables for theming */}\r\n <div\r\n className={`\r\n ai-input-container\r\n border rounded-xl\r\n transition-all duration-300 ease-out\r\n ${isRecording ? 'ai-input-recording' : ''}\r\n ${disabled ? 'opacity-50' : ''}\r\n `}\r\n >\r\n {/* Text input */}\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=\"ai-input-textarea w-full px-4 pt-4 pb-2 bg-transparent focus:outline-none disabled:cursor-not-allowed resize-none min-h-[56px] transition-colors duration-200\"\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 */}\r\n <div className=\"flex items-center gap-2\">\r\n {isRecording ? (\r\n <>\r\n <button\r\n onClick={cancelRecording}\r\n disabled={disabled}\r\n className=\"ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 active:scale-95\"\r\n aria-label=\"Cancel recording\"\r\n >\r\n <XIcon className=\"h-5 w-5\" />\r\n </button>\r\n <div className=\"flex items-center\">\r\n <RecordingPulse />\r\n <Waveform levels={audioLevels} />\r\n </div>\r\n <span className=\"ai-input-text-muted text-sm font-mono tabular-nums\">\r\n {formatDuration(recordingDuration)}\r\n </span>\r\n </>\r\n ) : (\r\n <div className=\"text-sm min-h-[28px] flex items-center\">\r\n {hasError && error && (\r\n <span className=\"ai-input-text-error animate-pulse\">{error.message}</span>\r\n )}\r\n {isRateLimited && (\r\n <span className=\"ai-input-text-warning\">\r\n Wait {formatDuration(cooldownRemaining)}\r\n </span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right side */}\r\n <div className=\"flex items-center gap-2\">\r\n {isRecording ? (\r\n <button\r\n onClick={stopRecording}\r\n disabled={disabled}\r\n className=\"p-2.5 rounded-full bg-red-500 hover:bg-red-400 text-white transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-red-500/25\"\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 <button\r\n onClick={startRecording}\r\n disabled={disabled || isLoading || isRateLimited}\r\n className=\"ai-input-btn-secondary p-2 rounded-lg transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\"\r\n aria-label=\"Start recording\"\r\n >\r\n <MicIcon className=\"h-5 w-5\" />\r\n </button>\r\n <button\r\n onClick={submit}\r\n disabled={!canSubmit || disabled}\r\n className={`\r\n p-2.5 rounded-full transition-all duration-200\r\n disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100\r\n ${canSubmit\r\n ? 'ai-input-btn-primary hover:scale-105 active:scale-95 shadow-lg'\r\n : 'ai-input-btn-disabled'\r\n }\r\n `}\r\n aria-label=\"Send message\"\r\n >\r\n {isLoading ? <Spinner className=\"h-5 w-5\" /> : <ArrowUpIcon className=\"h-5 w-5\" />}\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 * Text/audio input with automatic light/dark mode detection.\r\n * \r\n * **Theme Detection (in order of priority):**\r\n * 1. `.dark` class on html/body (Tailwind/Next.js)\r\n * 2. `data-theme=\"dark\"` attribute\r\n * 3. `prefers-color-scheme` system preference\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 if (children) {\r\n return <>{children(inputState)}</>\r\n }\r\n\r\n return (\r\n <div className={`w-full ${className || ''}`}>\r\n <DefaultUI {...inputState} placeholder={placeholder} disabled={disabled} />\r\n </div>\r\n )\r\n}\r\n"]}
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
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-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction: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;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@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-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-800:#27272a;--color-zinc-900:#18181b;--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)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}.absolute{position:absolute}.relative{position:relative}.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}}.mr-2{margin-right:calc(var(--spacing)*2)}.flex{display:flex}.inline-flex{display:inline-flex}.h-3{height:calc(var(--spacing)*3)}.h-5{height:calc(var(--spacing)*5)}.h-10{height:calc(var(--spacing)*10)}.h-full{height:100%}.min-h-\[28px\]{min-height:28px}.min-h-\[56px\]{min-height:56px}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-3{width:calc(var(--spacing)*3)}.w-5{width:calc(var(--spacing)*5)}.w-full{width:100%}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.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)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-red-500\/50{border-color:#ef444480}@supports (color:color-mix(in lab, red, red)){.border-red-500\/50{border-color:color-mix(in oklab,var(--color-red-500)50%,transparent)}}.border-zinc-800{border-color:var(--color-zinc-800)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-transparent{background-color:#0000}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-amber-600{--tw-gradient-from:var(--color-amber-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-amber-400{--tw-gradient-to:var(--color-amber-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.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)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-amber-500\/25{--tw-shadow-color:#f59e0b40}@supports (color:color-mix(in lab, red, red)){.shadow-amber-500\/25{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-amber-500)25%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-red-500\/10{--tw-shadow-color:#ef44441a}@supports (color:color-mix(in lab, red, red)){.shadow-red-500\/10{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-red-500)10%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-red-500\/25{--tw-shadow-color:#ef444440}@supports (color:color-mix(in lab, red, red)){.shadow-red-500\/25{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-red-500)25%,transparent)var(--tw-shadow-alpha),transparent)}}.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-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.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\:shadow-lg:focus-within{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);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\:shadow-amber-500\/5:focus-within{--tw-shadow-color:#f59e0b0d}@supports (color:color-mix(in lab, red, red)){.focus-within\:shadow-amber-500\/5:focus-within{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-amber-500)5%,transparent)var(--tw-shadow-alpha),transparent)}}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:bg-amber-400:hover{background-color:var(--color-amber-400)}.hover\:bg-red-400:hover{background-color:var(--color-red-400)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:text-amber-400:hover{color:var(--color-amber-400)}.hover\:text-zinc-200:hover{color:var(--color-zinc-200)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (hover:hover){.disabled\:hover\:scale-100:disabled:hover{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}}}@keyframes wave{0%,to{transform:scaleY(.5)}50%{transform:scaleY(1)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{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}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}
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-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction: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;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@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-amber-400:#fbbf24;--color-amber-600:#d97706;--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)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{.absolute{position:absolute}.relative{position:relative}.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}}.mr-2{margin-right:calc(var(--spacing)*2)}.flex{display:flex}.inline-flex{display:inline-flex}.h-3{height:calc(var(--spacing)*3)}.h-5{height:calc(var(--spacing)*5)}.h-10{height:calc(var(--spacing)*10)}.h-full{height:100%}.min-h-\[28px\]{min-height:28px}.min-h-\[56px\]{min-height:56px}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-3{width:calc(var(--spacing)*3)}.w-5{width:calc(var(--spacing)*5)}.w-full{width:100%}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.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)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-transparent{background-color:#0000}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-amber-600{--tw-gradient-from:var(--color-amber-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-amber-400{--tw-gradient-to:var(--color-amber-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.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-white{color:var(--color-white)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-red-500\/25{--tw-shadow-color:#ef444440}@supports (color:color-mix(in lab, red, red)){.shadow-red-500\/25{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-red-500)25%,transparent)var(--tw-shadow-alpha),transparent)}}.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-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:bg-red-400:hover{background-color:var(--color-red-400)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (hover:hover){.disabled\:hover\:scale-100:disabled:hover{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}}}.ai-input-container{background-color:#fff;border-color:#e4e4e7}.ai-input-container:focus-within{border-color:#f59e0b80;box-shadow:0 10px 15px -3px #f59e0b0d}.ai-input-container.ai-input-recording{border-color:#ef444480;box-shadow:0 10px 15px -3px #ef44441a}.ai-input-textarea{color:#18181b}.ai-input-textarea::placeholder{color:#a1a1aa}.ai-input-text-muted{color:#71717a}.ai-input-text-error{color:#ef4444}.ai-input-text-warning{color:#d97706}.ai-input-btn-secondary{color:#71717a}.ai-input-btn-secondary:hover{color:#d97706;background-color:#f4f4f5}.ai-input-btn-primary{color:#fff;background-color:#f59e0b;box-shadow:0 10px 15px -3px #f59e0b40}.ai-input-btn-primary:hover{background-color:#fbbf24}.ai-input-btn-disabled{color:#a1a1aa;background-color:#f4f4f5}@media (prefers-color-scheme:dark){.ai-input-container{background-color:#18181b;border-color:#27272a}.ai-input-container:focus-within{border-color:#f59e0b80;box-shadow:0 10px 15px -3px #f59e0b0d}.ai-input-container.ai-input-recording{border-color:#ef444480;box-shadow:0 10px 15px -3px #ef44441a}.ai-input-textarea{color:#f4f4f5}.ai-input-textarea::placeholder{color:#71717a}.ai-input-text-muted{color:#a1a1aa}.ai-input-text-error{color:#f87171}.ai-input-text-warning{color:#fbbf24}.ai-input-btn-secondary{color:#a1a1aa}.ai-input-btn-secondary:hover{color:#fbbf24;background-color:#27272a}.ai-input-btn-primary{color:#18181b;background-color:#f59e0b;box-shadow:0 10px 15px -3px #f59e0b40}.ai-input-btn-primary:hover{background-color:#fbbf24}.ai-input-btn-disabled{color:#71717a;background-color:#27272a}}.dark .ai-input-container,[data-theme=dark] .ai-input-container,[data-mode=dark] .ai-input-container{background-color:#18181b;border-color:#27272a}.dark .ai-input-container:focus-within,[data-theme=dark] .ai-input-container:focus-within,[data-mode=dark] .ai-input-container:focus-within{border-color:#f59e0b80;box-shadow:0 10px 15px -3px #f59e0b0d}.dark .ai-input-container.ai-input-recording,[data-theme=dark] .ai-input-container.ai-input-recording,[data-mode=dark] .ai-input-container.ai-input-recording{border-color:#ef444480;box-shadow:0 10px 15px -3px #ef44441a}.dark .ai-input-textarea,[data-theme=dark] .ai-input-textarea,[data-mode=dark] .ai-input-textarea{color:#f4f4f5}.dark .ai-input-textarea::placeholder,[data-theme=dark] .ai-input-textarea::placeholder,[data-mode=dark] .ai-input-textarea::placeholder{color:#71717a}.dark .ai-input-text-muted,[data-theme=dark] .ai-input-text-muted,[data-mode=dark] .ai-input-text-muted{color:#a1a1aa}.dark .ai-input-text-error,[data-theme=dark] .ai-input-text-error,[data-mode=dark] .ai-input-text-error{color:#f87171}.dark .ai-input-text-warning,[data-theme=dark] .ai-input-text-warning,[data-mode=dark] .ai-input-text-warning{color:#fbbf24}.dark .ai-input-btn-secondary,[data-theme=dark] .ai-input-btn-secondary,[data-mode=dark] .ai-input-btn-secondary{color:#a1a1aa}.dark .ai-input-btn-secondary:hover,[data-theme=dark] .ai-input-btn-secondary:hover,[data-mode=dark] .ai-input-btn-secondary:hover{color:#fbbf24;background-color:#27272a}.dark .ai-input-btn-primary,[data-theme=dark] .ai-input-btn-primary,[data-mode=dark] .ai-input-btn-primary{color:#18181b;background-color:#f59e0b;box-shadow:0 10px 15px -3px #f59e0b40}.dark .ai-input-btn-primary:hover,[data-theme=dark] .ai-input-btn-primary:hover,[data-mode=dark] .ai-input-btn-primary:hover{background-color:#fbbf24}.dark .ai-input-btn-disabled,[data-theme=dark] .ai-input-btn-disabled,[data-mode=dark] .ai-input-btn-disabled{color:#71717a;background-color:#27272a}.light .ai-input-container,[data-theme=light] .ai-input-container,[data-mode=light] .ai-input-container{background-color:#fff;border-color:#e4e4e7}.light .ai-input-textarea,[data-theme=light] .ai-input-textarea,[data-mode=light] .ai-input-textarea{color:#18181b}.light .ai-input-textarea::placeholder,[data-theme=light] .ai-input-textarea::placeholder,[data-mode=light] .ai-input-textarea::placeholder{color:#a1a1aa}.light .ai-input-text-muted,[data-theme=light] .ai-input-text-muted,[data-mode=light] .ai-input-text-muted,.light .ai-input-btn-secondary,[data-theme=light] .ai-input-btn-secondary,[data-mode=light] .ai-input-btn-secondary{color:#71717a}.light .ai-input-btn-secondary:hover,[data-theme=light] .ai-input-btn-secondary:hover,[data-mode=light] .ai-input-btn-secondary:hover{color:#d97706;background-color:#f4f4f5}.light .ai-input-btn-disabled,[data-theme=light] .ai-input-btn-disabled,[data-mode=light] .ai-input-btn-disabled{color:#a1a1aa;background-color:#f4f4f5}@keyframes wave{0%,to{transform:scaleY(.5)}50%{transform:scaleY(1)}}@keyframes pulse{50%{opacity:.5}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{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}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-input-react",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
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
5
  "keywords": [
6
6
  "react",