PageSourceSearch

https://sistur.lovable.app/assets/ChatInput-BlPRMTqo.js

js sistur.lovable.app collected 2026-10-03 03:47:07 UTC 4,018 bytes, 97 lines download raw bytes

1const e=`import { useEffect, useRef } from 'react';
2import { Button } from '@/components/ui/button';
3import { Textarea } from '@/components/ui/textarea';
4import { Send, Loader2, Mic, MicOff, Paperclip, X, FileText } from 'lucide-react';
5import { cn } from '@/lib/utils';
6
7import { tx } from "@/i18n/t";
8interface ChatInputProps {
9  input: string;
10  onInputChange: (value: string) => void;
11  onSubmit: (e?: React.FormEvent) => void;
12  onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
13  onToggleVoiceInput: () => void;
14  isLoading: boolean;
15  isListening: boolean;
16  inputRef: React.RefObject<HTMLInputElement>;
17  pendingFile?: File | null;
18  onFileSelected?: (file: File | null) => void;
19  accept?: string;
20}
21
22export function ChatInput({
23  input, onInputChange, onSubmit, onKeyDown, onToggleVoiceInput,
24  isLoading, isListening, inputRef, pendingFile, onFileSelected, accept,
25}: ChatInputProps) {
26  const fileRef = useRef<HTMLInputElement>(null);
27
28  // Auto-grow: 3 a ~10 linhas
29  useEffect(() => {
30    const el = inputRef.current as unknown as HTMLTextAreaElement | null;
31    if (!el) return;
32    el.style.height = 'auto';
33    el.style.height = \`\${Math.min(el.scrollHeight, 260)}px\`;
34  }, [input, inputRef]);
35
36  return (
37    <div className="p-3 border-t shrink-0">
38      <form onSubmit={onSubmit} className="rounded-xl border bg-background focus-within:ring-2 focus-within:ring-ring">
39        {pendingFile && (
40          <div className="px-3 pt-3">
41            <span className="inline-flex items-center gap-2 rounded-md bg-muted px-2 py-1 text-xs">
42              <FileText className="h-3 w-3" />
43              <span className="max-w-[220px] truncate">{pendingFile.name}</span>
44              <button type="button" onClick={() => onFileSelected?.(null)} aria-label={tx("Remover anexo")}>
45                <X className="h-3 w-3" />
46              </button>
47            </span>
48          </div>
49        )}
50        <Textarea
51          ref={inputRef as any}
52          value={input}
53          onChange={(e) => onInputChange(e.target.value)}
54          onKeyDown={onKeyDown}
55          placeholder={isListening ? 'Ouvindo...' : tx('Pergunte ao Professor Beni... (Enter envia, Shift+Enter quebra linha)')}
56          disabled={isLoading}
57          className="min-h-[84px] max-h-[260px] resize-none border-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
58          rows={3}
59        />
60        <div className="flex items-center justify-between gap-2 px-2 pb-2">
61          <div className="flex items-center gap-1">
62            {onFileSelected && (
63              <>
64                <input
65                  ref={fileRef}
66                  type="file"
67                  accept={accept}
68                  className="hidden"
69                  onChange={(e) => { onFileSelected(e.target.files?.[0] ?? null); e.target.value = ''; }}
70                />
71                <Button type="button" variant="ghost" size="icon" className="h-8 w-8" disabled={isLoading}
72                  onClick={() => fileRef.current?.click()} title={tx("Anexar arquivo (até 10 MB)")}>
73                  <Paperclip className="h-4 w-4" />
74                </Button>
75              </>
76            )}
77            <Button
78              type="button"
79              variant={isListening ? 'destructive' : 'ghost'}
80              size="icon"
81              onClick={onToggleVoiceInput}
82              disabled={isLoading}
83              className={cn('h-8 w-8', isListening && 'animate-pulse')}
84              title={isListening ? tx('Parar gravação') : 'Falar'}
85            >
86              {isListening ? <MicOff className="h-4 w-4" /> : <Mic className="h-4 w-4" />}
87            </Button>
88          </div>
89          <Button type="submit" size="icon" className="h-8 w-8" disabled={isLoading || (!input.trim() && !pendingFile)} title={tx("Enviar")}>
90            {isLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
91          </Button>
92        </div>
93      </form>
94    </div>
95  );
96}
97`;export{e as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.