diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b6e89afb..5356248d 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -104,6 +104,7 @@ "input.web_search.button.ok": "Go to Settings", "input.web_search.enable": "Enable web search", "input.web_search.enable_content": "Enable web search in Settings", + "input.auto_resize": "Auto resize height", "message.new.branch": "New Branch", "message.new.branch.created": "New Branch Created", "message.new.context": "New Context", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index e82c6d5d..67f642ea 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -104,6 +104,7 @@ "input.web_search.button.ok": "設定に移動", "input.web_search.enable": "ウェブ検索を有効にする", "input.web_search.enable_content": "ウェブ検索を有効にするには、設定でウェブ検索を有効にする必要があります", + "input.auto_resize": "高さを自動調整", "message.new.branch": "新しいブランチ", "message.new.branch.created": "新しいブランチが作成されました", "message.new.context": "新しいコンテキスト", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 6b12bcf4..8ea10879 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -104,6 +104,7 @@ "input.web_search.button.ok": "Перейти в Настройки", "input.web_search.enable": "Включить веб-поиск", "input.web_search.enable_content": "Необходимо включить веб-поиск в Настройки", + "input.auto_resize": "Автоматическая высота", "message.new.branch": "Новая ветка", "message.new.branch.created": "Новая ветка создана", "message.new.context": "Новый контекст", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 87fce320..16376811 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -104,6 +104,7 @@ "input.web_search.button.ok": "去设置", "input.web_search.enable": "开启网络搜索", "input.web_search.enable_content": "需要先在设置中开启网络搜索", + "input.auto_resize": "自动调整高度", "message.new.branch": "分支", "message.new.branch.created": "新分支已创建", "message.new.context": "清除上下文", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 2bd99d4c..15def1eb 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -104,6 +104,7 @@ "input.web_search.button.ok": "去設定", "input.web_search.enable": "開啟網路搜索", "input.web_search.enable_content": "需要先在設定中開啟網路搜索", + "input.auto_resize": "自動調整高度", "message.new.branch": "分支", "message.new.branch.created": "新分支已建立", "message.new.context": "新上下文", diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index cbfafd92..2fc23c35 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -1,9 +1,11 @@ import { ClearOutlined, + ColumnHeightOutlined, FormOutlined, FullscreenExitOutlined, FullscreenOutlined, GlobalOutlined, + HolderOutlined, PauseCircleOutlined, PicCenterOutlined, QuestionCircleOutlined @@ -87,6 +89,10 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]) const [mentionModels, setMentionModels] = useState([]) const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false) + const [isDragging, setIsDragging] = useState(false) + const [textareaHeight, setTextareaHeight] = useState() + const startDragY = useRef(0) + const startHeight = useRef(0) const currentMessageId = useRef() const isVision = useMemo(() => isVisionModel(model), [model]) const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision]) @@ -305,6 +311,10 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { const resizeTextArea = () => { const textArea = textareaRef.current?.resizableTextArea?.textArea if (textArea) { + // 如果已经手动设置了高度,则不自动调整 + if (textareaHeight) { + return + } textArea.style.height = 'auto' textArea.style.height = textArea?.scrollHeight > 400 ? '400px' : `${textArea?.scrollHeight}px` } @@ -319,7 +329,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { if (isExpended) { textArea.style.height = '70vh' } else { - resizeTextArea() + resetHeight() } } @@ -428,6 +438,50 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { setTimeout(() => resizeTextArea(), 0) } + const handleDragStart = (e: React.MouseEvent) => { + e.preventDefault() + setIsDragging(true) + startDragY.current = e.clientY + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + startHeight.current = textArea.offsetHeight + } + } + + const handleDrag = useCallback( + (e: MouseEvent) => { + if (!isDragging) return + + const delta = startDragY.current - e.clientY // 改变计算方向 + const viewportHeight = window.innerHeight + const maxHeightInPixels = viewportHeight * 0.7 + + const newHeight = Math.min(maxHeightInPixels, Math.max(startHeight.current + delta, 30)) + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + textArea.style.height = `${newHeight}px` + setExpend(newHeight == maxHeightInPixels) + setTextareaHeight(newHeight) + } + }, + [isDragging] + ) + + const handleDragEnd = useCallback(() => { + setIsDragging(false) + }, []) + + useEffect(() => { + if (isDragging) { + document.addEventListener('mousemove', handleDrag) + document.addEventListener('mouseup', handleDragEnd) + } + return () => { + document.removeEventListener('mousemove', handleDrag) + document.removeEventListener('mouseup', handleDragEnd) + } + }, [isDragging, handleDrag, handleDragEnd]) + useShortcut('new_topic', () => { if (!generating) { addNewTopic() @@ -552,6 +606,21 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { } }, [assistant, model, updateAssistant]) + const resetHeight = () => { + if (expended) { + setExpend(false) + } + setTextareaHeight(undefined) + requestAnimationFrame(() => { + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + textArea.style.height = 'auto' + const contentHeight = textArea.scrollHeight + textArea.style.height = contentHeight > 400 ? '400px' : `${contentHeight}px` + } + }) + } + return ( @@ -572,7 +641,10 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { spellCheck={false} rows={textareaRows} ref={textareaRef} - style={{ fontSize }} + style={{ + fontSize, + height: textareaHeight ? `${textareaHeight}px` : undefined + }} styles={{ textarea: TextareaStyle }} onFocus={(e: React.FocusEvent) => { setInputFocus(true) @@ -588,6 +660,9 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { onPaste={(e) => onPaste(e.nativeEvent)} onClick={() => searching && dispatch(setSearching(false))} /> + + + @@ -639,6 +714,13 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { {expended ? : } + {textareaHeight && ( + + + + + + )} = ({ assistant: _assistant, setActiveTopic }) => { ) } +// Add these styled components at the bottom +const DragHandle = styled.div` + position: absolute; + top: -3px; + left: 0; + right: 0; + height: 6px; + display: flex; + align-items: center; + justify-content: center; + cursor: row-resize; + color: var(--color-icon); + opacity: 0; + transition: opacity 0.2s; + z-index: 1; + + &:hover { + opacity: 1; + } + + .anticon { + transform: rotate(90deg); + font-size: 14px; + } +` + const Container = styled.div` display: flex; flex-direction: column; @@ -677,12 +785,13 @@ const InputBarContainer = styled.div` margin: 14px 20px; margin-top: 12px; border-radius: 15px; + padding-top: 6px; // 为拖动手柄留出空间 background-color: var(--color-background-opacity); ` const TextareaStyle: CSSProperties = { paddingLeft: 0, - padding: '10px 15px 8px' + padding: '4px 15px 8px' // 减小顶部padding } const Textarea = styled(TextArea)`