Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions frontend/src/features/tasks/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

'use client';

import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { useTranslation } from '@/hooks/useTranslation';
import { useIsMobile } from '@/features/layout/hooks/useMediaQuery';
Expand All @@ -29,6 +29,7 @@ export default function ChatInput({
const placeholderKey = taskType === 'chat' ? 'chat.placeholder_chat' : 'chat.placeholder_code';
const [isComposing, setIsComposing] = useState(false);
const isMobile = useIsMobile();
const textareaRef = useRef<HTMLTextAreaElement>(null);

const handleCompositionStart = () => {
setIsComposing(true);
Expand All @@ -51,13 +52,52 @@ export default function ChatInput({
}
};

const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
const textarea = e.target as HTMLTextAreaElement;
const value = textarea.value;

// Handle line formatting similar to markdown
// When user presses Enter, maintain the indentation and formatting
if (value.includes('\n')) {
const lines = value.split('\n');
const formattedLines = lines.map((line, index) => {
// For lines after the first one, preserve leading spaces/tabs
if (index > 0) {
// Count leading whitespace in the previous line
const prevLine = lines[index - 1];
const leadingWhitespace = prevLine.match(/^\s*/)?.[0] || '';

// If current line is empty and previous line has content, preserve indentation
if (line.trim() === '' && prevLine.trim() !== '') {
return leadingWhitespace;
}
}
return line;
});

const formattedValue = formattedLines.join('\n');
if (formattedValue !== value) {
setMessage(formattedValue);
return;
}
}

setMessage(value);
};

// Auto-focus on mount
useEffect(() => {
if (textareaRef.current && !disabled) {
textareaRef.current.focus();
}
}, [disabled]);

return (
<div className="w-full">
<TextareaAutosize
ref={textareaRef}
value={message}
onChange={e => {
if (!disabled) setMessage(e.target.value);
}}
onInput={handleInput}
onKeyDown={handleKeyPress}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
Expand All @@ -66,7 +106,7 @@ export default function ChatInput({
disabled={disabled}
minRows={isMobile ? 2 : 3}
maxRows={isMobile ? 6 : 8}
style={{ resize: 'none', overflow: 'auto' }}
style={{ resize: 'none', overflow: 'auto', whiteSpace: 'pre-wrap', wordWrap: 'break-word' }}
/>
</div>
);
Expand Down