11// Copyright 2025, Command Line Inc.
22// SPDX-License-Identifier: Apache-2.0
33
4+ import { formatFileSizeError , isAcceptableFile , validateFileSize } from "@/app/aipanel/ai-utils" ;
5+ import { type WaveAIModel } from "@/app/aipanel/waveai-model" ;
46import { atoms , globalStore } from "@/app/store/global" ;
57import { workspaceLayoutModel } from "@/app/workspace/workspace-layout-model" ;
68import { cn } from "@/util/util" ;
@@ -12,6 +14,7 @@ interface AIPanelInputProps {
1214 setInput : ( value : string ) => void ;
1315 onSubmit : ( e : React . FormEvent ) => void ;
1416 status : string ;
17+ model : WaveAIModel ;
1518}
1619
1720export interface AIPanelInputRef {
@@ -20,9 +23,10 @@ export interface AIPanelInputRef {
2023}
2124
2225export const AIPanelInput = memo (
23- forwardRef < AIPanelInputRef , AIPanelInputProps > ( ( { input, setInput, onSubmit, status } , ref ) => {
26+ forwardRef < AIPanelInputRef , AIPanelInputProps > ( ( { input, setInput, onSubmit, status, model } , ref ) => {
2427 const isFocused = useAtomValue ( atoms . waveAIFocusedAtom ) ;
2528 const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
29+ const fileInputRef = useRef < HTMLInputElement > ( null ) ;
2630 const isPanelOpen = useAtomValue ( workspaceLayoutModel . panelVisibleAtom ) ;
2731
2832 const resizeTextarea = useCallback ( ( ) => {
@@ -68,8 +72,47 @@ export const AIPanelInput = memo(
6872 }
6973 } , [ isPanelOpen , resizeTextarea ] ) ;
7074
75+ const handleUploadClick = ( ) => {
76+ fileInputRef . current ?. click ( ) ;
77+ } ;
78+
79+ const handleFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
80+ const files = Array . from ( e . target . files || [ ] ) ;
81+ const acceptableFiles = files . filter ( isAcceptableFile ) ;
82+
83+ for ( const file of acceptableFiles ) {
84+ const sizeError = validateFileSize ( file ) ;
85+ if ( sizeError ) {
86+ model . setError ( formatFileSizeError ( sizeError ) ) ;
87+ if ( e . target ) {
88+ e . target . value = "" ;
89+ }
90+ return ;
91+ }
92+ model . addFile ( file ) ;
93+ }
94+
95+ if ( acceptableFiles . length < files . length ) {
96+ console . warn (
97+ `${ files . length - acceptableFiles . length } files were rejected due to unsupported file types`
98+ ) ;
99+ }
100+
101+ if ( e . target ) {
102+ e . target . value = "" ;
103+ }
104+ } ;
105+
71106 return (
72107 < div className = { cn ( "border-t" , isFocused ? "border-accent/50" : "border-gray-600" ) } >
108+ < input
109+ ref = { fileInputRef }
110+ type = "file"
111+ multiple
112+ accept = "image/*,.pdf,.txt,.md,.js,.jsx,.ts,.tsx,.go,.py,.java,.c,.cpp,.h,.hpp,.html,.css,.scss,.sass,.json,.xml,.yaml,.yml,.sh,.bat,.sql"
113+ onChange = { handleFileChange }
114+ className = "hidden"
115+ />
73116 < form onSubmit = { onSubmit } >
74117 < div className = "relative" >
75118 < textarea
@@ -81,12 +124,21 @@ export const AIPanelInput = memo(
81124 onBlur = { handleBlur }
82125 placeholder = "Ask Wave AI anything..."
83126 className = { cn (
84- "w-full text-white px-2 py-2 pr-6 focus:outline-none resize-none overflow-hidden" ,
127+ "w-full text-white px-2 py-2 pr-12 focus:outline-none resize-none overflow-hidden" ,
85128 isFocused ? "bg-accent-900/50" : "bg-gray-800"
86129 ) }
87130 style = { { fontSize : "13px" } }
88131 rows = { 2 }
89132 />
133+ < button
134+ type = "button"
135+ onClick = { handleUploadClick }
136+ className = { cn (
137+ "absolute bottom-6 right-1 w-3.5 h-3.5 transition-colors flex items-center justify-center text-gray-400 hover:text-accent cursor-pointer"
138+ ) }
139+ >
140+ < i className = "fa fa-paperclip text-xs" > </ i >
141+ </ button >
90142 < button
91143 type = "submit"
92144 disabled = { status !== "ready" || ! input . trim ( ) }
0 commit comments