From 5e8d7682f5f75893e7f88c59fd623b2dd1e56315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=A2=E5=A5=8B=E7=8C=AB?= Date: Wed, 12 Feb 2025 14:08:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E4=BD=BF=E7=94=A8@?= =?UTF-8?q?=E5=91=BC=E5=87=BA=E6=A8=A1=E5=9E=8B=E9=80=89=E6=8B=A9=E5=88=97?= =?UTF-8?q?=E8=A1=A8=20#1317=20#1324=20(#1458)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 使用@呼出模型选择列表 输入第一个字符为@符号的时候可以呼出选择模型的列表 * feat: 🎸 Only one can be chosen at a time 一次只能选择一个模型,选择后自动关闭。选择过的模型不在出现在列表,避免删除模型的时候显示异常。 * fix: 🐛 When choosing the model, Enter will send a message * feat: 🎸 选中的模型显示供应商 * feat: 🎸 pinned module show privoder * feat: 🎸 only selected modle show provider * feat: 🎸 删除@符号以后自动关闭 * feat: 🎸 增加模糊搜索 --------- Co-authored-by: duanyongcheng77 --- .../src/pages/home/Inputbar/Inputbar.tsx | 78 ++++- .../home/Inputbar/MentionModelsButton.tsx | 325 +++++++++++++++--- .../home/Inputbar/MentionModelsInput.tsx | 12 +- src/renderer/src/services/EventService.ts | 3 +- 4 files changed, 350 insertions(+), 68 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 4f89b148..9b441596 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -85,6 +85,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { const [isTranslating, setIsTranslating] = useState(false) const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState(_base) const [mentionModels, setMentionModels] = useState([]) + const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false) const isVision = useMemo(() => isVisionModel(model), [model]) const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision]) @@ -165,6 +166,24 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { const handleKeyDown = (event: React.KeyboardEvent) => { const isEnterPressed = event.keyCode == 13 + if (event.key === '@') { + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + const cursorPosition = textArea.selectionStart + const textBeforeCursor = text.substring(0, cursorPosition) + if (cursorPosition === 0 || textBeforeCursor.endsWith(' ')) { + EventEmitter.emit(EVENT_NAMES.SHOW_MODEL_SELECTOR) + setIsMentionPopupOpen(true) + return + } + } + } + + if (event.key === 'Escape' && isMentionPopupOpen) { + setIsMentionPopupOpen(false) + return + } + if (autoTranslateWithSpace) { if (event.key === ' ') { setSpaceClickCount((prev) => prev + 1) @@ -193,25 +212,34 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { } } - if (sendMessageShortcut === 'Enter' && isEnterPressed) { - if (event.shiftKey) { - return + if (isEnterPressed && !event.shiftKey && sendMessageShortcut === 'Enter') { + if (isMentionPopupOpen) { + return event.preventDefault() } sendMessage() return event.preventDefault() } if (sendMessageShortcut === 'Shift+Enter' && isEnterPressed && event.shiftKey) { + if (isMentionPopupOpen) { + return event.preventDefault() + } sendMessage() return event.preventDefault() } if (sendMessageShortcut === 'Ctrl+Enter' && isEnterPressed && event.ctrlKey) { + if (isMentionPopupOpen) { + return event.preventDefault() + } sendMessage() return event.preventDefault() } if (sendMessageShortcut === 'Command+Enter' && isEnterPressed && event.metaKey) { + if (isMentionPopupOpen) { + return event.preventDefault() + } sendMessage() return event.preventDefault() } @@ -280,6 +308,23 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { const onInput = () => !expended && resizeTextArea() + const onChange = (e: React.ChangeEvent) => { + const newText = e.target.value + setText(newText) + + // Check if @ was deleted + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + const cursorPosition = textArea.selectionStart + const textBeforeCursor = newText.substring(0, cursorPosition) + const lastAtIndex = textBeforeCursor.lastIndexOf('@') + + if (lastAtIndex === -1 || textBeforeCursor.slice(lastAtIndex + 1).includes(' ')) { + setIsMentionPopupOpen(false) + } + } + } + const onPaste = useCallback( async (event: ClipboardEvent) => { const clipboardText = event.clipboardData?.getData('text') @@ -420,17 +465,22 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => { setSelectedKnowledgeBase(base) } - const onMentionModel = useCallback( - (model: Model) => { - const isSelected = mentionModels.some((m) => m.id === model.id) - if (isSelected) { - setMentionModels(mentionModels.filter((m) => m.id !== model.id)) - } else { - setMentionModels([...mentionModels, model]) + const onMentionModel = (model: Model) => { + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + const cursorPosition = textArea.selectionStart + const textBeforeCursor = text.substring(0, cursorPosition) + const lastAtIndex = textBeforeCursor.lastIndexOf('@') + + if (lastAtIndex !== -1) { + const newText = text.substring(0, lastAtIndex) + text.substring(cursorPosition) + setText(newText) } - }, - [mentionModels] - ) + + setMentionModels((prev) => [...prev, model]) + setIsMentionPopupOpen(false) + } + } const handleRemoveModel = (model: Model) => { setMentionModels(mentionModels.filter((m) => m.id !== model.id)) @@ -447,7 +497,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic }) => {