mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-11 20:19:39 +00:00
Merge branch 'main' into fix/registration-race-condition
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user