feat: add assistant emoji picker
This commit is contained in:
parent
ad962c2cf6
commit
974f7901e6
@ -37,6 +37,7 @@
|
|||||||
"@uiw/react-textarea-code-editor": "^3.1.1",
|
"@uiw/react-textarea-code-editor": "^3.1.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"culori": "^4.0.1",
|
"culori": "^4.0.1",
|
||||||
|
"emoji-picker-react": "^4.12.2",
|
||||||
"fzf": "^0.5.2",
|
"fzf": "^0.5.2",
|
||||||
"i18next": "^25.0.1",
|
"i18next": "^25.0.1",
|
||||||
"katex": "^0.16.22",
|
"katex": "^0.16.22",
|
||||||
|
|||||||
@ -36,6 +36,9 @@ const DropdownAssistant = () => {
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button className="bg-main-view-fg/5 py-0.5 hover:bg-main-view-fg/8 px-2 rounded font-medium cursor-pointer flex items-center gap-1.5 relative z-20 max-w-40">
|
<button className="bg-main-view-fg/5 py-0.5 hover:bg-main-view-fg/8 px-2 rounded font-medium cursor-pointer flex items-center gap-1.5 relative z-20 max-w-40">
|
||||||
<span className="text-main-view-fg/80 truncate">
|
<span className="text-main-view-fg/80 truncate">
|
||||||
|
{selectedAssistant?.avatar && (
|
||||||
|
<span className="mr-1">{selectedAssistant?.avatar}</span>
|
||||||
|
)}
|
||||||
{selectedAssistant?.name || 'Jan'}
|
{selectedAssistant?.name || 'Jan'}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@ -43,7 +46,6 @@ const DropdownAssistant = () => {
|
|||||||
<div
|
<div
|
||||||
className="size-5 cursor-pointer relative z-10 flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out "
|
className="size-5 cursor-pointer relative z-10 flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out "
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log('edit clicked', selectedAssistant)
|
|
||||||
if (selectedAssistant) {
|
if (selectedAssistant) {
|
||||||
setEditingAssistantId(selectedAssistant.id)
|
setEditingAssistantId(selectedAssistant.id)
|
||||||
setDialogOpen(true)
|
setDialogOpen(true)
|
||||||
@ -73,6 +75,7 @@ const DropdownAssistant = () => {
|
|||||||
updateCurrentThreadAssistant(assistant)
|
updateCurrentThreadAssistant(assistant)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<span className="mr-1">{assistant?.avatar}</span>
|
||||||
{assistant.name}
|
{assistant.name}
|
||||||
</span>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -9,6 +9,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { IconPlus, IconTrash, IconChevronDown } from '@tabler/icons-react'
|
import { IconPlus, IconTrash, IconChevronDown } from '@tabler/icons-react'
|
||||||
|
import EmojiPicker, { EmojiClickData, Theme } from 'emoji-picker-react'
|
||||||
|
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
import { useTheme } from '@/hooks/useTheme'
|
||||||
|
|
||||||
interface AddEditAssistantProps {
|
interface AddEditAssistantProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
@ -43,9 +45,32 @@ export default function AddEditAssistant({
|
|||||||
const [instructions, setInstructions] = useState(
|
const [instructions, setInstructions] = useState(
|
||||||
initialData?.instructions || ''
|
initialData?.instructions || ''
|
||||||
)
|
)
|
||||||
|
const { isDark } = useTheme()
|
||||||
const [paramsKeys, setParamsKeys] = useState<string[]>([''])
|
const [paramsKeys, setParamsKeys] = useState<string[]>([''])
|
||||||
const [paramsValues, setParamsValues] = useState<unknown[]>([''])
|
const [paramsValues, setParamsValues] = useState<unknown[]>([''])
|
||||||
const [paramsTypes, setParamsTypes] = useState<string[]>(['string'])
|
const [paramsTypes, setParamsTypes] = useState<string[]>(['string'])
|
||||||
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||||
|
const emojiPickerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
// Handle click outside emoji picker
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (
|
||||||
|
emojiPickerRef.current &&
|
||||||
|
!emojiPickerRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
setShowEmojiPicker(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showEmojiPicker) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [showEmojiPicker])
|
||||||
|
|
||||||
// Reset form when modal opens/closes or editing key changes
|
// Reset form when modal opens/closes or editing key changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -189,26 +214,80 @@ export default function AddEditAssistant({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="text-sm mb-2 inline-block">Name</label>
|
<div className="relative">
|
||||||
<Input
|
<label className="text-sm mb-2 inline-block">Emoji</label>
|
||||||
value={name}
|
<div
|
||||||
onChange={(e) => setName(e.target.value)}
|
className="border rounded-sm p-2 w-9 h-9 flex items-center justify-center border-main-view-fg/10 cursor-pointer"
|
||||||
placeholder="Enter name"
|
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
||||||
autoFocus
|
>
|
||||||
/>
|
{avatar || '😊'}
|
||||||
|
</div>
|
||||||
|
<div className="relative" ref={emojiPickerRef}>
|
||||||
|
<EmojiPicker
|
||||||
|
open={showEmojiPicker}
|
||||||
|
theme={isDark ? ('dark' as Theme) : ('light' as Theme)}
|
||||||
|
className="!absolute !z-40 !overflow-y-auto top-2"
|
||||||
|
height={350}
|
||||||
|
lazyLoadEmojis
|
||||||
|
previewConfig={{ showPreview: false }}
|
||||||
|
onEmojiClick={(emojiData: EmojiClickData) => {
|
||||||
|
setAvatar(emojiData.emoji)
|
||||||
|
setShowEmojiPicker(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 w-full">
|
||||||
|
<label className="text-sm mb-2 inline-block">Name</label>
|
||||||
|
<Input
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Enter name"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
{/* <div className="space-y-2">
|
||||||
<label className="text-sm mb-2 inline-block">
|
<label className="text-sm mb-2 inline-block">Emoji Avatar</label>
|
||||||
Avatar (optional)
|
<div className="flex items-center gap-2">
|
||||||
</label>
|
<div className="border rounded-md p-2 w-12 h-12 flex items-center justify-center text-2xl">
|
||||||
<Input
|
{avatar || '😊'}
|
||||||
value={avatar || ''}
|
</div>
|
||||||
onChange={(e) => setAvatar(e.target.value)}
|
<div className="relative">
|
||||||
placeholder="Enter avatar URL"
|
<Button
|
||||||
/>
|
onClick={() =>
|
||||||
</div>
|
document
|
||||||
|
.getElementById('emoji-picker-container')
|
||||||
|
?.classList.toggle('hidden')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Choose Emoji
|
||||||
|
</Button>
|
||||||
|
<div
|
||||||
|
id="emoji-picker-container"
|
||||||
|
className="absolute z-50 mt-1 hidden"
|
||||||
|
>
|
||||||
|
<EmojiPicker
|
||||||
|
open={true}
|
||||||
|
theme={isDark ? ('dark' as Theme) : ('light' as Theme)}
|
||||||
|
autoFocusSearch
|
||||||
|
previewConfig={{ showPreview: false }}
|
||||||
|
onEmojiClick={(emojiData: EmojiClickData) => {
|
||||||
|
setAvatar(emojiData.emoji)
|
||||||
|
document
|
||||||
|
.getElementById('emoji-picker-container')
|
||||||
|
?.classList.add('hidden')
|
||||||
|
}}
|
||||||
|
width={300}
|
||||||
|
height={400}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm mb-2 inline-block">
|
<label className="text-sm mb-2 inline-block">
|
||||||
|
|||||||
@ -12,7 +12,7 @@ interface AssistantState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAssistant: Assistant = {
|
export const defaultAssistant: Assistant = {
|
||||||
avatar: '',
|
avatar: '👋',
|
||||||
id: 'jan',
|
id: 'jan',
|
||||||
name: 'Jan',
|
name: 'Jan',
|
||||||
created_at: 1747029866.542,
|
created_at: 1747029866.542,
|
||||||
|
|||||||
@ -94,4 +94,12 @@
|
|||||||
@apply font-bold;
|
@apply font-bold;
|
||||||
color: color-mix(in srgb, currentColor 80%, white 20%);
|
color: color-mix(in srgb, currentColor 80%, white 20%);
|
||||||
}
|
}
|
||||||
}
|
.epr-main {
|
||||||
|
input {
|
||||||
|
@apply border !border-main-view-fg/10 !h-9 !text-sm !bg-main-view;
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
@apply !bg-main-view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -67,6 +67,9 @@ function Assistant() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<h3 className="text-base font-medium text-main-view-fg/80">
|
<h3 className="text-base font-medium text-main-view-fg/80">
|
||||||
|
{assistant.avatar && (
|
||||||
|
<span className="mr-1">{assistant.avatar}</span>
|
||||||
|
)}
|
||||||
{assistant.name}
|
{assistant.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center gap-0.5">
|
<div className="flex items-center gap-0.5">
|
||||||
|
|||||||
@ -71,12 +71,6 @@ function General() {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<CardItem
|
|
||||||
title={t('settings.general.autoDownload', {
|
|
||||||
ns: 'settings',
|
|
||||||
})}
|
|
||||||
actions={<Switch />}
|
|
||||||
/>
|
|
||||||
<CardItem
|
<CardItem
|
||||||
title={t('common.language')}
|
title={t('common.language')}
|
||||||
actions={<LanguageSwitcher />}
|
actions={<LanguageSwitcher />}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user