forked from danny-avila/LibreChat
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
📧 feat: Mention "@" Command Popover (danny-avila#2635)
* feat: initial mockup * wip: activesetting, may use or not use * wip: mention with useCombobox usage * feat: connect textarea to new mention popover * refactor: consolidate icon logic for Landing/convos * refactor: cleanup URL logic * refactor(useTextarea): key up handler * wip: render desired mention options * refactor: improve mention detection * feat: modular chat the default option * WIP: first pass mention selection * feat: scroll mention items with keypad * chore(showMentionPopoverFamily): add typing to atomFamily * feat: removeAtSymbol * refactor(useListAssistantsQuery): use defaultOrderQuery as default param * feat: assistants mentioning * fix conversation switch errors * filter mention selections based on startup settings and available endpoints * fix: mentions model spec icon URL * style: archive icon * fix: convo renaming behavior on click * fix(Convo): toggle hover state * style: EditMenu refactor * fix: archive chats table * fix: errorsToString import * chore: remove comments * chore: remove comment * feat: mention descriptions * refactor: make sure continue hover button is always last, add correct fork button alt text
- Loading branch information
1 parent
89b1e33
commit b6d6343
Showing
35 changed files
with
1,038 additions
and
207 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export default function ActiveSetting() { | ||
return ( | ||
<div className="text-token-text-tertiary space-x-2 overflow-hidden text-ellipsis text-sm font-light"> | ||
Talking to{' '} | ||
<span className="text-token-text-secondary font-medium">[latest] Tailwind CSS GPT</span> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import { useState, useRef, useEffect } from 'react'; | ||
import { EModelEndpoint } from 'librechat-data-provider'; | ||
import type { SetterOrUpdater } from 'recoil'; | ||
import type { MentionOption } from '~/common'; | ||
import { useAssistantsMapContext } from '~/Providers'; | ||
import useMentions from '~/hooks/Input/useMentions'; | ||
import { useLocalize, useCombobox } from '~/hooks'; | ||
import { removeAtSymbolIfLast } from '~/utils'; | ||
import MentionItem from './MentionItem'; | ||
|
||
export default function Mention({ | ||
setShowMentionPopover, | ||
textAreaRef, | ||
}: { | ||
setShowMentionPopover: SetterOrUpdater<boolean>; | ||
textAreaRef: React.MutableRefObject<HTMLTextAreaElement | null>; | ||
}) { | ||
const localize = useLocalize(); | ||
const assistantMap = useAssistantsMapContext(); | ||
const { options, modelsConfig, assistants, onSelectMention } = useMentions({ assistantMap }); | ||
|
||
const [activeIndex, setActiveIndex] = useState(0); | ||
const timeoutRef = useRef<NodeJS.Timeout | null>(null); | ||
const inputRef = useRef<HTMLInputElement | null>(null); | ||
const [inputOptions, setInputOptions] = useState<MentionOption[]>(options); | ||
|
||
const { open, setOpen, searchValue, setSearchValue, matches } = useCombobox({ | ||
value: '', | ||
options: inputOptions, | ||
}); | ||
|
||
const handleSelect = (mention?: MentionOption) => { | ||
if (!mention) { | ||
return; | ||
} | ||
|
||
const defaultSelect = () => { | ||
setSearchValue(''); | ||
setOpen(false); | ||
setShowMentionPopover(false); | ||
onSelectMention(mention); | ||
|
||
if (textAreaRef.current) { | ||
removeAtSymbolIfLast(textAreaRef.current); | ||
} | ||
}; | ||
|
||
if (mention.type === 'endpoint' && mention.value === EModelEndpoint.assistants) { | ||
setSearchValue(''); | ||
setInputOptions(assistants); | ||
setActiveIndex(0); | ||
inputRef.current?.focus(); | ||
} else if (mention.type === 'endpoint') { | ||
const models = (modelsConfig?.[mention.value ?? ''] ?? []).map((model) => ({ | ||
value: mention.value, | ||
label: model, | ||
type: 'model', | ||
})); | ||
|
||
setActiveIndex(0); | ||
setSearchValue(''); | ||
setInputOptions(models); | ||
inputRef.current?.focus(); | ||
} else { | ||
defaultSelect(); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
if (!open) { | ||
setInputOptions(options); | ||
setActiveIndex(0); | ||
} | ||
}, [open, options]); | ||
|
||
useEffect(() => { | ||
const currentActiveItem = document.getElementById(`mention-item-${activeIndex}`); | ||
currentActiveItem?.scrollIntoView({ behavior: 'instant', block: 'nearest' }); | ||
}, [activeIndex]); | ||
|
||
return ( | ||
<div className="absolute bottom-16 z-10 w-full space-y-2"> | ||
<div className="popover border-token-border-light rounded-2xl border bg-white p-2 shadow-lg dark:bg-gray-700"> | ||
<input | ||
autoFocus | ||
ref={inputRef} | ||
placeholder={localize('com_ui_mention')} | ||
className="mb-1 w-full border-0 bg-white p-2 text-sm focus:outline-none dark:bg-gray-700 dark:text-gray-200" | ||
autoComplete="off" | ||
value={searchValue} | ||
onKeyDown={(e) => { | ||
if (e.key === 'Escape') { | ||
setOpen(false); | ||
setShowMentionPopover(false); | ||
textAreaRef.current?.focus(); | ||
} | ||
if (e.key === 'ArrowDown') { | ||
setActiveIndex((prevIndex) => (prevIndex + 1) % matches.length); | ||
} else if (e.key === 'ArrowUp') { | ||
setActiveIndex((prevIndex) => (prevIndex - 1 + matches.length) % matches.length); | ||
} else if (e.key === 'Enter' || e.key === 'Tab') { | ||
const mentionOption = matches[0] as MentionOption | undefined; | ||
if (mentionOption?.type === 'endpoint') { | ||
e.preventDefault(); | ||
} else if (e.key === 'Enter') { | ||
e.preventDefault(); | ||
} | ||
handleSelect(matches[activeIndex] as MentionOption); | ||
} else if (e.key === 'Backspace' && searchValue === '') { | ||
setOpen(false); | ||
setShowMentionPopover(false); | ||
textAreaRef.current?.focus(); | ||
} | ||
}} | ||
onChange={(e) => setSearchValue(e.target.value)} | ||
onFocus={() => setOpen(true)} | ||
onBlur={() => { | ||
timeoutRef.current = setTimeout(() => { | ||
setOpen(false); | ||
setShowMentionPopover(false); | ||
}, 150); | ||
}} | ||
/> | ||
{open && ( | ||
<div className="max-h-40 overflow-y-auto"> | ||
{(matches as MentionOption[]).map((mention, index) => ( | ||
<MentionItem | ||
index={index} | ||
key={`${mention.value}-${index}`} | ||
onClick={() => { | ||
if (timeoutRef.current) { | ||
clearTimeout(timeoutRef.current); | ||
} | ||
timeoutRef.current = null; | ||
handleSelect(mention); | ||
}} | ||
name={mention.label ?? ''} | ||
icon={mention.icon} | ||
description={mention.description} | ||
isActive={index === activeIndex} | ||
/> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import React from 'react'; | ||
import { Clock4 } from 'lucide-react'; | ||
import { cn } from '~/utils'; | ||
|
||
export default function MentionItem({ | ||
name, | ||
onClick, | ||
index, | ||
icon, | ||
isActive, | ||
description, | ||
}: { | ||
name: string; | ||
onClick: () => void; | ||
index: number; | ||
icon?: React.ReactNode; | ||
isActive?: boolean; | ||
description?: string; | ||
}) { | ||
return ( | ||
<div tabIndex={index} onClick={onClick} id={`mention-item-${index}`} className="cursor-pointer"> | ||
<div | ||
className={cn( | ||
'hover:bg-token-main-surface-secondary text-token-text-primary bg-token-main-surface-secondary group flex h-10 items-center gap-2 rounded-lg px-2 text-sm font-medium dark:hover:bg-gray-600', | ||
index === 0 ? 'dark:bg-gray-600' : '', | ||
isActive ? 'dark:bg-gray-600' : '', | ||
)} | ||
> | ||
{icon ? icon : null} | ||
<div className="flex h-fit grow flex-row justify-between space-x-2 overflow-hidden text-ellipsis whitespace-nowrap"> | ||
<div className="flex flex-row space-x-2"> | ||
<span className="shrink-0 truncate">{name}</span> | ||
{description ? ( | ||
<span className="text-token-text-tertiary flex-grow truncate text-sm font-light sm:max-w-xs lg:max-w-md"> | ||
{description} | ||
</span> | ||
) : null} | ||
</div> | ||
<span className="shrink-0 self-center"> | ||
<Clock4 size={16} className="icon-sm" /> | ||
</span> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.