fix: handle long thread title without space (#3107)
* fix: handle long thread title without space, and make searchbar autofocus inside model dropdown * feat: enable right click to show setting on thread items (#3108)
This commit is contained in:
parent
e77f651273
commit
a2a203b40d
@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo, useEffect, useCallback } from 'react'
|
import { useState, useMemo, useEffect, useCallback, useRef } from 'react'
|
||||||
|
|
||||||
import { InferenceEngine } from '@janhq/core'
|
import { InferenceEngine } from '@janhq/core'
|
||||||
import { Badge, Input, ScrollArea, Select, useClickOutside } from '@janhq/joi'
|
import { Badge, Input, ScrollArea, Select, useClickOutside } from '@janhq/joi'
|
||||||
@ -70,7 +70,7 @@ const ModelDropdown = ({
|
|||||||
const downloadStates = useAtomValue(modelDownloadStateAtom)
|
const downloadStates = useAtomValue(modelDownloadStateAtom)
|
||||||
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
||||||
const { updateModelParameter } = useUpdateModelParameters()
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||||
const configuredModels = useAtomValue(configuredModelsAtom)
|
const configuredModels = useAtomValue(configuredModelsAtom)
|
||||||
const featuredModel = configuredModels.filter((x) =>
|
const featuredModel = configuredModels.filter((x) =>
|
||||||
x.metadata.tags.includes('Featured')
|
x.metadata.tags.includes('Featured')
|
||||||
@ -108,6 +108,12 @@ const ModelDropdown = ({
|
|||||||
[configuredModels, searchText, searchFilter]
|
[configuredModels, searchText, searchFilter]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && searchInputRef.current) {
|
||||||
|
searchInputRef.current.focus()
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeThread) return
|
if (!activeThread) return
|
||||||
let model = downloadedModels.find(
|
let model = downloadedModels.find(
|
||||||
@ -258,6 +264,7 @@ const ModelDropdown = ({
|
|||||||
<Input
|
<Input
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
|
ref={searchInputRef}
|
||||||
className="rounded-none border-x-0 border-t-0 focus-within:ring-0 hover:border-b-[hsla(var(--app-border))]"
|
className="rounded-none border-x-0 border-t-0 focus-within:ring-0 hover:border-b-[hsla(var(--app-border))]"
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
suffixIcon={
|
suffixIcon={
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import useDeleteThread from '@/hooks/useDeleteThread'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
threadId: string
|
threadId: string
|
||||||
|
closeContextMenu?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalCleanThread = ({ threadId }: Props) => {
|
const ModalCleanThread = ({ threadId, closeContextMenu }: Props) => {
|
||||||
const { cleanThread } = useDeleteThread()
|
const { cleanThread } = useDeleteThread()
|
||||||
const onCleanThreadClick = useCallback(
|
const onCleanThreadClick = useCallback(
|
||||||
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
@ -22,6 +23,11 @@ const ModalCleanThread = ({ threadId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Clean Thread"
|
title="Clean Thread"
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (open && closeContextMenu) {
|
||||||
|
closeContextMenu()
|
||||||
|
}
|
||||||
|
}}
|
||||||
trigger={
|
trigger={
|
||||||
<div
|
<div
|
||||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
|
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
|
||||||
|
|||||||
@ -7,10 +7,12 @@ import useDeleteThread from '@/hooks/useDeleteThread'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
threadId: string
|
threadId: string
|
||||||
|
closeContextMenu?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalDeleteThread = ({ threadId }: Props) => {
|
const ModalDeleteThread = ({ threadId, closeContextMenu }: Props) => {
|
||||||
const { deleteThread } = useDeleteThread()
|
const { deleteThread } = useDeleteThread()
|
||||||
|
|
||||||
const onDeleteThreadClick = useCallback(
|
const onDeleteThreadClick = useCallback(
|
||||||
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@ -22,6 +24,11 @@ const ModalDeleteThread = ({ threadId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Delete Thread"
|
title="Delete Thread"
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (open && closeContextMenu) {
|
||||||
|
closeContextMenu()
|
||||||
|
}
|
||||||
|
}}
|
||||||
trigger={
|
trigger={
|
||||||
<div
|
<div
|
||||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
|
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
|
||||||
|
|||||||
@ -8,9 +8,10 @@ import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
thread: Thread
|
thread: Thread
|
||||||
|
closeContextMenu?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalEditTitleThread = ({ thread }: Props) => {
|
const ModalEditTitleThread = ({ thread, closeContextMenu }: Props) => {
|
||||||
const [title, setTitle] = useState(thread.title)
|
const [title, setTitle] = useState(thread.title)
|
||||||
|
|
||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
@ -30,6 +31,11 @@ const ModalEditTitleThread = ({ thread }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Edit title thread"
|
title="Edit title thread"
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (open && closeContextMenu) {
|
||||||
|
closeContextMenu()
|
||||||
|
}
|
||||||
|
}}
|
||||||
trigger={
|
trigger={
|
||||||
<div
|
<div
|
||||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
|
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Thread } from '@janhq/core'
|
import { Thread } from '@janhq/core'
|
||||||
|
|
||||||
@ -43,6 +43,14 @@ const ThreadLeftPanel = () => {
|
|||||||
const setEditMessage = useSetAtom(editMessageAtom)
|
const setEditMessage = useSetAtom(editMessageAtom)
|
||||||
const { recommendedModel, downloadedModels } = useRecommendedModel()
|
const { recommendedModel, downloadedModels } = useRecommendedModel()
|
||||||
|
|
||||||
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
|
visible: boolean
|
||||||
|
thread?: Thread
|
||||||
|
}>({
|
||||||
|
visible: false,
|
||||||
|
thread: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const onThreadClick = useCallback(
|
const onThreadClick = useCallback(
|
||||||
(thread: Thread) => {
|
(thread: Thread) => {
|
||||||
setActiveThread(thread)
|
setActiveThread(thread)
|
||||||
@ -91,6 +99,21 @@ const ThreadLeftPanel = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onContextMenu = (event: React.MouseEvent, thread: Thread) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setContextMenu({
|
||||||
|
visible: true,
|
||||||
|
thread,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeContextMenu = () => {
|
||||||
|
setContextMenu({
|
||||||
|
visible: false,
|
||||||
|
thread: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LeftPanelContainer>
|
<LeftPanelContainer>
|
||||||
{threads.length === 0 ? (
|
{threads.length === 0 ? (
|
||||||
@ -124,8 +147,10 @@ const ThreadLeftPanel = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onThreadClick(thread)
|
onThreadClick(thread)
|
||||||
}}
|
}}
|
||||||
|
onContextMenu={(e) => onContextMenu(e, thread)}
|
||||||
|
onMouseLeave={closeContextMenu}
|
||||||
>
|
>
|
||||||
<div className="relative z-10 p-2">
|
<div className="relative z-10 break-all p-2">
|
||||||
<h1
|
<h1
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'line-clamp-1 pr-2 font-medium group-hover/message:pr-6',
|
'line-clamp-1 pr-2 font-medium group-hover/message:pr-6',
|
||||||
@ -143,10 +168,26 @@ const ThreadLeftPanel = () => {
|
|||||||
<Button theme="icon" className="mt-2">
|
<Button theme="icon" className="mt-2">
|
||||||
<MoreHorizontalIcon />
|
<MoreHorizontalIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="invisible absolute -right-1 z-50 w-40 overflow-hidden rounded-lg border border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))] shadow-lg group-hover/icon:visible">
|
<div
|
||||||
<ModalEditTitleThread thread={thread} />
|
className={twMerge(
|
||||||
<ModalCleanThread threadId={thread.id} />
|
'invisible absolute -right-1 z-50 w-40 overflow-hidden rounded-lg border border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))] shadow-lg group-hover/icon:visible',
|
||||||
<ModalDeleteThread threadId={thread.id} />
|
contextMenu.visible &&
|
||||||
|
contextMenu.thread?.id === thread.id &&
|
||||||
|
'visible'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ModalEditTitleThread
|
||||||
|
thread={thread}
|
||||||
|
closeContextMenu={closeContextMenu}
|
||||||
|
/>
|
||||||
|
<ModalCleanThread
|
||||||
|
threadId={thread.id}
|
||||||
|
closeContextMenu={closeContextMenu}
|
||||||
|
/>
|
||||||
|
<ModalDeleteThread
|
||||||
|
threadId={thread.id}
|
||||||
|
closeContextMenu={closeContextMenu}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{activeThreadId === thread.id && (
|
{activeThreadId === thread.id && (
|
||||||
|
|||||||
@ -166,7 +166,7 @@ const Tools = () => {
|
|||||||
<div className="mb-2 flex items-center">
|
<div className="mb-2 flex items-center">
|
||||||
<label
|
<label
|
||||||
id="vector-database"
|
id="vector-database"
|
||||||
className="inline-block font-medium"
|
className="inline-flex items-center font-medium"
|
||||||
>
|
>
|
||||||
Vector Database
|
Vector Database
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user