Merge branch 'main' into fix/registration-race-condition

This commit is contained in:
viper151
2025-07-13 11:37:21 +02:00
committed by GitHub

View File

@@ -1985,19 +1985,34 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
const spaceIndex = textAfterAtQuery.indexOf(' '); const spaceIndex = textAfterAtQuery.indexOf(' ');
const textAfterQuery = spaceIndex !== -1 ? textAfterAtQuery.slice(spaceIndex) : ''; const textAfterQuery = spaceIndex !== -1 ? textAfterAtQuery.slice(spaceIndex) : '';
const newInput = textBeforeAt + '@' + file.path + textAfterQuery; const newInput = textBeforeAt + '@' + file.path + ' ' + textAfterQuery;
const newCursorPos = textBeforeAt.length + 1 + file.path.length + 1;
// Immediately ensure focus is maintained
if (textareaRef.current && !textareaRef.current.matches(':focus')) {
textareaRef.current.focus();
}
// Update input and cursor position
setInput(newInput); setInput(newInput);
setCursorPosition(newCursorPos);
// Hide dropdown
setShowFileDropdown(false); setShowFileDropdown(false);
setAtSymbolPosition(-1); setAtSymbolPosition(-1);
// Focus back to textarea and set cursor position // Set cursor position synchronously
if (textareaRef.current) { if (textareaRef.current) {
textareaRef.current.focus(); // Use requestAnimationFrame for smoother updates
const newCursorPos = textBeforeAt.length + 1 + file.path.length; requestAnimationFrame(() => {
setTimeout(() => { if (textareaRef.current) {
textareaRef.current.setSelectionRange(newCursorPos, newCursorPos); textareaRef.current.setSelectionRange(newCursorPos, newCursorPos);
setCursorPosition(newCursorPos); // Ensure focus is maintained
}, 0); if (!textareaRef.current.matches(':focus')) {
textareaRef.current.focus();
}
}
});
} }
}; };
@@ -2238,6 +2253,37 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
</div> </div>
)} )}
{/* File dropdown - positioned outside dropzone to avoid conflicts */}
{showFileDropdown && filteredFiles.length > 0 && (
<div className="absolute bottom-full left-0 right-0 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg max-h-48 overflow-y-auto z-50 backdrop-blur-sm">
{filteredFiles.map((file, index) => (
<div
key={file.path}
className={`px-4 py-3 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 touch-manipulation ${
index === selectedFileIndex
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300'
}`}
onMouseDown={(e) => {
// Prevent textarea from losing focus on mobile
e.preventDefault();
e.stopPropagation();
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
selectFile(file);
}}
>
<div className="font-medium text-sm">{file.name}</div>
<div className="text-xs text-gray-500 dark:text-gray-400 font-mono">
{file.path}
</div>
</div>
))}
</div>
)}
<div {...getRootProps()} className={`relative bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 focus-within:ring-2 focus-within:ring-blue-500 dark:focus-within:ring-blue-500 focus-within:border-blue-500 transition-all duration-200 ${isTextareaExpanded ? 'chat-input-expanded' : ''}`}> <div {...getRootProps()} className={`relative bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 focus-within:ring-2 focus-within:ring-blue-500 dark:focus-within:ring-blue-500 focus-within:border-blue-500 transition-all duration-200 ${isTextareaExpanded ? 'chat-input-expanded' : ''}`}>
<input {...getInputProps()} /> <input {...getInputProps()} />
<textarea <textarea
@@ -2355,28 +2401,6 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
/> />
</svg> </svg>
</button> </button>
{/* File dropdown */}
{showFileDropdown && filteredFiles.length > 0 && (
<div className="absolute bottom-full left-0 right-0 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg max-h-48 overflow-y-auto z-50">
{filteredFiles.map((file, index) => (
<div
key={file.path}
className={`px-4 py-2 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 ${
index === selectedFileIndex
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300'
}`}
onClick={() => selectFile(file)}
>
<div className="font-medium text-sm">{file.name}</div>
<div className="text-xs text-gray-500 dark:text-gray-400 font-mono">
{file.path}
</div>
</div>
))}
</div>
)}
</div> </div>
{/* Hint text */} {/* Hint text */}
<div className="text-xs text-gray-500 dark:text-gray-400 text-center mt-2 hidden sm:block"> <div className="text-xs text-gray-500 dark:text-gray-400 text-center mt-2 hidden sm:block">