parent
d0f73264af
commit
6f7eb7dd5d
3 changed files with 191 additions and 58 deletions
@ -0,0 +1,54 @@ |
|||||||
|
import type { LucideIcon } from 'lucide-react'; |
||||||
|
import { useState, useRef } from 'react'; |
||||||
|
import { useOutsideClick } from '../../hooks/use-outside-click'; |
||||||
|
import { |
||||||
|
type PredefinedMessage, |
||||||
|
PredefinedMessageButton, |
||||||
|
} from './PredefinedMessages'; |
||||||
|
|
||||||
|
type PredefinedMessageGroupProps = { |
||||||
|
label: string; |
||||||
|
icon: LucideIcon; |
||||||
|
messages: Omit<PredefinedMessage, 'messages'>[]; |
||||||
|
onSelect: (message: Omit<PredefinedMessage, 'messages'>) => void; |
||||||
|
}; |
||||||
|
|
||||||
|
export function PredefinedMessageGroup(props: PredefinedMessageGroupProps) { |
||||||
|
const { label, icon: Icon, messages, onSelect } = props; |
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false); |
||||||
|
const containerRef = useRef<HTMLDivElement>(null); |
||||||
|
|
||||||
|
useOutsideClick(containerRef, () => { |
||||||
|
setIsOpen(false); |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="relative" ref={containerRef}> |
||||||
|
<PredefinedMessageButton |
||||||
|
label={label} |
||||||
|
icon={Icon} |
||||||
|
onClick={() => setIsOpen(!isOpen)} |
||||||
|
isGroup={true} |
||||||
|
/> |
||||||
|
|
||||||
|
{isOpen && ( |
||||||
|
<div className="absolute top-full left-0 z-20 mt-1 divide-y overflow-hidden rounded-md border border-gray-200 bg-white p-0"> |
||||||
|
{messages.map((m) => { |
||||||
|
return ( |
||||||
|
<PredefinedMessageButton |
||||||
|
key={m.message} |
||||||
|
{...m} |
||||||
|
className="h-7 w-full rounded-none bg-transparent hover:bg-gray-200" |
||||||
|
onClick={() => { |
||||||
|
onSelect(m); |
||||||
|
setIsOpen(false); |
||||||
|
}} |
||||||
|
/> |
||||||
|
); |
||||||
|
})} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
import { |
||||||
|
BabyIcon, |
||||||
|
BookOpenTextIcon, |
||||||
|
BrainIcon, |
||||||
|
ChevronDownIcon, |
||||||
|
NotebookPenIcon, |
||||||
|
type LucideIcon, |
||||||
|
} from 'lucide-react'; |
||||||
|
import { useMemo } from 'react'; |
||||||
|
import { cn } from '../../lib/classname'; |
||||||
|
import { PredefinedMessageGroup } from './PredefinedMessageGroup'; |
||||||
|
|
||||||
|
export const testMyKnowledgePrompt = |
||||||
|
'Act as an interviewer and test my understanding of this topic'; |
||||||
|
export const explainTopicPrompt = 'Explain this topic in detail'; |
||||||
|
|
||||||
|
export type PredefinedMessage = { |
||||||
|
icon: LucideIcon; |
||||||
|
label: string; |
||||||
|
} & ( |
||||||
|
| { |
||||||
|
message?: never; |
||||||
|
messages: PredefinedMessage[]; |
||||||
|
} |
||||||
|
| { |
||||||
|
message: string; |
||||||
|
messages?: never; |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
type PredefinedMessagesProps = { |
||||||
|
onSelect: (message: Omit<PredefinedMessage, 'messages'>) => void; |
||||||
|
}; |
||||||
|
|
||||||
|
export function PredefinedMessages(props: PredefinedMessagesProps) { |
||||||
|
const { onSelect } = props; |
||||||
|
|
||||||
|
const predefinedMessages: PredefinedMessage[] = useMemo( |
||||||
|
() => [ |
||||||
|
{ |
||||||
|
icon: BookOpenTextIcon, |
||||||
|
label: 'Explain', |
||||||
|
messages: [ |
||||||
|
{ |
||||||
|
icon: NotebookPenIcon, |
||||||
|
label: 'Explain Topic', |
||||||
|
message: 'Explain this topic in detail and include examples', |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: BabyIcon, |
||||||
|
label: 'Explain like I am five', |
||||||
|
message: 'Explain this topic like I am a 5 years old', |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: BrainIcon, |
||||||
|
label: 'Test my Knowledge', |
||||||
|
message: testMyKnowledgePrompt, |
||||||
|
}, |
||||||
|
], |
||||||
|
[], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex items-center gap-2 border-gray-200 px-3 py-1 text-sm"> |
||||||
|
{predefinedMessages.map((m) => { |
||||||
|
const isGroup = 'messages' in m && Array.isArray(m?.messages); |
||||||
|
if (!isGroup) { |
||||||
|
return ( |
||||||
|
<PredefinedMessageButton |
||||||
|
key={m.message} |
||||||
|
icon={m.icon} |
||||||
|
label={m.label} |
||||||
|
onClick={() => { |
||||||
|
onSelect(m); |
||||||
|
}} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<PredefinedMessageGroup |
||||||
|
key={m.label} |
||||||
|
label={m.label} |
||||||
|
icon={m.icon} |
||||||
|
messages={m.messages} |
||||||
|
onSelect={onSelect} |
||||||
|
/> |
||||||
|
); |
||||||
|
})} |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
type PredefinedMessageButtonProps = { |
||||||
|
label: string; |
||||||
|
icon?: LucideIcon; |
||||||
|
onClick: () => void; |
||||||
|
isGroup?: boolean; |
||||||
|
className?: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function PredefinedMessageButton(props: PredefinedMessageButtonProps) { |
||||||
|
const { label, icon: Icon, onClick, isGroup = false, className } = props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<button |
||||||
|
className={cn( |
||||||
|
'flex shrink-0 items-center gap-1.5 rounded-md bg-gray-200 px-2 py-1 text-sm whitespace-nowrap hover:bg-gray-300', |
||||||
|
className, |
||||||
|
)} |
||||||
|
onClick={onClick} |
||||||
|
> |
||||||
|
{Icon && <Icon className="size-3.5" />} |
||||||
|
{label} |
||||||
|
{isGroup && <ChevronDownIcon className="size-3.5" />} |
||||||
|
</button> |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue