Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: shortcut delete and clean thread #3423

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions web/containers/Providers/KeyListener.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Fragment, ReactNode, useEffect } from 'react'

import { useAtomValue, useSetAtom } from 'jotai'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'

import { MainViewState } from '@/constants/screens'

Expand All @@ -14,6 +14,11 @@ import {
showRightPanelAtom,
} from '@/helpers/atoms/App.atom'
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
import {
activeThreadAtom,
modalActionThreadAtom,
ThreadModalAction,
} from '@/helpers/atoms/Thread.atom'

type Props = {
children: ReactNode
Expand All @@ -22,9 +27,11 @@ type Props = {
export default function KeyListener({ children }: Props) {
const setShowLeftPanel = useSetAtom(showLeftPanelAtom)
const setShowRightPanel = useSetAtom(showRightPanelAtom)
const setMainViewState = useSetAtom(mainViewStateAtom)
const [mainViewState, setMainViewState] = useAtom(mainViewStateAtom)
const { requestCreateNewThread } = useCreateNewThread()
const assistants = useAtomValue(assistantsAtom)
const activeThread = useAtomValue(activeThreadAtom)
const setModalActionThread = useSetAtom(modalActionThreadAtom)

useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
Expand All @@ -35,7 +42,26 @@ export default function KeyListener({ children }: Props) {
return
}

if (e.key === 'Backspace' && prefixKey && e.shiftKey) {
if (!activeThread || mainViewState !== MainViewState.Thread) return
setModalActionThread({
showModal: ThreadModalAction.Delete,
thread: activeThread,
})
return
}

if (e.key === 'c' && prefixKey && e.shiftKey) {
if (!activeThread || mainViewState !== MainViewState.Thread) return
setModalActionThread({
showModal: ThreadModalAction.Clean,
thread: activeThread,
})
return
}

if (e.key === 'n' && prefixKey) {
if (mainViewState !== MainViewState.Thread) return
requestCreateNewThread(assistants[0])
setMainViewState(MainViewState.Thread)
return
Expand All @@ -54,9 +80,12 @@ export default function KeyListener({ children }: Props) {
document.addEventListener('keydown', onKeyDown)
return () => document.removeEventListener('keydown', onKeyDown)
}, [
activeThread,
assistants,
mainViewState,
requestCreateNewThread,
setMainViewState,
setModalActionThread,
setShowLeftPanel,
setShowRightPanel,
])
Expand Down
14 changes: 14 additions & 0 deletions web/helpers/atoms/Thread.atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import {
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'

export enum ThreadModalAction {
Clean = 'clean',
Delete = 'delete',
EditTitle = 'edit-title',
}

export const engineParamsUpdateAtom = atom<boolean>(false)

/**
Expand Down Expand Up @@ -138,3 +144,11 @@ export const activeSettingInputBoxAtom = atomWithStorage<boolean>(
ACTIVE_SETTING_INPUT_BOX,
false
)

export const modalActionThreadAtom = atom<{
showModal: ThreadModalAction | undefined
thread: Thread | undefined
}>({
showModal: undefined,
thread: undefined,
})
10 changes: 10 additions & 0 deletions web/screens/Settings/Hotkeys/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ const availableHotkeys = [
modifierKeys: [isMac ? '⌘' : 'Ctrl'],
description: 'Toggle right panel',
},
{
combination: 'Shift Backspace',
modifierKeys: [isMac ? '⌘' : 'Ctrl'],
description: 'Delete current active thread',
},
{
combination: 'Shift C',
modifierKeys: [isMac ? '⌘' : 'Ctrl'],
description: 'Clean current active thread',
},
{
combination: ',',
modifierKeys: [isMac ? '⌘' : 'Ctrl'],
Expand Down
56 changes: 28 additions & 28 deletions web/screens/Thread/ThreadLeftPanel/ModalCleanThread/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
import { useCallback, memo } from 'react'

import { Button, Modal, ModalClose } from '@janhq/joi'
import { Paintbrush } from 'lucide-react'
import { useAtom } from 'jotai'

import useDeleteThread from '@/hooks/useDeleteThread'

type Props = {
threadId: string
closeContextMenu?: () => void
}
import {
modalActionThreadAtom,
ThreadModalAction,
} from '@/helpers/atoms/Thread.atom'

const ModalCleanThread = ({ threadId, closeContextMenu }: Props) => {
const ModalCleanThread = () => {
const { cleanThread } = useDeleteThread()
const [modalActionThread, setModalActionThread] = useAtom(
modalActionThreadAtom
)

const onCleanThreadClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation()
cleanThread(threadId)
cleanThread(modalActionThread.thread?.id as string)
},
[cleanThread, threadId]
[cleanThread, modalActionThread.thread?.id]
)

const onCloseModal = useCallback(() => {
setModalActionThread({
showModal: undefined,
thread: undefined,
})
}, [setModalActionThread])

return (
<Modal
title="Clean Thread"
onOpenChange={(open) => {
if (open && closeContextMenu) {
closeContextMenu()
}
}}
trigger={
<div
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
onClick={(e) => e.stopPropagation()}
>
<Paintbrush
size={16}
className="text-[hsla(var(--text-secondary))]"
/>
<span className="text-bold text-[hsla(var(--app-text-primary))]">
Clean thread
</span>
</div>
}
open={modalActionThread.showModal === ThreadModalAction.Clean}
onOpenChange={onCloseModal}
content={
<div>
<p className="text-[hsla(var(--text-secondary))]">
Are you sure you want to clean this thread?
</p>
<div className="mt-4 flex justify-end gap-x-2">
<ModalClose asChild onClick={(e) => e.stopPropagation()}>
<ModalClose
asChild
onClick={(e) => {
onCloseModal()
e.stopPropagation()
}}
>
<Button theme="ghost">No</Button>
</ModalClose>
<ModalClose asChild>
Expand Down
47 changes: 20 additions & 27 deletions web/screens/Thread/ThreadLeftPanel/ModalDeleteThread/index.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,41 @@
import { useCallback, memo } from 'react'

import { Modal, ModalClose, Button } from '@janhq/joi'
import { Trash2Icon } from 'lucide-react'
import { useAtom } from 'jotai'

import useDeleteThread from '@/hooks/useDeleteThread'

type Props = {
threadId: string
closeContextMenu?: () => void
}
import {
modalActionThreadAtom,
ThreadModalAction,
} from '@/helpers/atoms/Thread.atom'

const ModalDeleteThread = ({ threadId, closeContextMenu }: Props) => {
const ModalDeleteThread = () => {
const { deleteThread } = useDeleteThread()
const [modalActionThread, setModalActionThread] = useAtom(
modalActionThreadAtom
)

const onDeleteThreadClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation()
deleteThread(threadId)
deleteThread(modalActionThread.thread?.id as string)
},
[deleteThread, threadId]
[deleteThread, modalActionThread.thread?.id]
)

const onCloseModal = useCallback(() => {
setModalActionThread({
showModal: undefined,
thread: undefined,
})
}, [setModalActionThread])

return (
<Modal
title="Delete Thread"
onOpenChange={(open) => {
if (open && closeContextMenu) {
closeContextMenu()
}
}}
trigger={
<div
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
onClick={(e) => e.stopPropagation()}
>
<Trash2Icon
size={16}
className="text-[hsla(var(--destructive-bg))]"
/>
<span className="text-bold text-[hsla(var(--destructive-bg))]">
Delete thread
</span>
</div>
}
onOpenChange={onCloseModal}
open={modalActionThread.showModal === ThreadModalAction.Delete}
content={
<div>
<p className="text-[hsla(var(--text-secondary))]">
Expand Down
61 changes: 26 additions & 35 deletions web/screens/Thread/ThreadLeftPanel/ModalEditTitleThread/index.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,52 @@
import { useCallback, useLayoutEffect, memo, useState } from 'react'

import { Thread } from '@janhq/core'
import { Modal, ModalClose, Button, Input } from '@janhq/joi'
import { PencilIcon } from 'lucide-react'
import { useAtom } from 'jotai'

import { useCreateNewThread } from '@/hooks/useCreateNewThread'

type Props = {
thread: Thread
closeContextMenu?: () => void
}
import {
modalActionThreadAtom,
ThreadModalAction,
} from '@/helpers/atoms/Thread.atom'

const ModalEditTitleThread = ({ thread, closeContextMenu }: Props) => {
const [title, setTitle] = useState(thread.title)
const ModalEditTitleThread = () => {
const { updateThreadMetadata } = useCreateNewThread()
const [modalActionThread, setModalActionThread] = useAtom(
modalActionThreadAtom
)
const [title, setTitle] = useState(modalActionThread.thread?.title as string)

useLayoutEffect(() => {
if (thread.title) {
setTitle(thread.title)
if (modalActionThread.thread?.title) {
setTitle(modalActionThread.thread?.title)
}
}, [thread.title])
}, [modalActionThread.thread?.title])

const onUpdateTitle = useCallback(
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation()

if (!modalActionThread.thread) return null
updateThreadMetadata({
...thread,
...modalActionThread?.thread,
title: title || 'New Thread',
})
},
[thread, title, updateThreadMetadata]
[modalActionThread?.thread, title, updateThreadMetadata]
)

const onCloseModal = useCallback(() => {
setModalActionThread({
showModal: undefined,
thread: undefined,
})
}, [setModalActionThread])

return (
<Modal
title="Edit title thread"
onOpenChange={(open) => {
if (open && closeContextMenu) {
closeContextMenu()
}
}}
trigger={
<div
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-[hsla(var(--dropdown-menu-hover-bg))]"
onClick={(e) => e.stopPropagation()}
>
<PencilIcon size={16} className="text-[hsla(var(--secondary))]" />
<span className="text-bold text-[hsla(var(--secondary))]">
Edit title
</span>
</div>
}
onOpenChange={onCloseModal}
open={modalActionThread.showModal === ThreadModalAction.EditTitle}
content={
<form className="mt-4">
<Input
Expand All @@ -64,11 +59,7 @@ const ModalEditTitleThread = ({ thread, closeContextMenu }: Props) => {
<Button theme="ghost">Cancel</Button>
</ModalClose>
<ModalClose asChild>
<Button
type="submit"
onClick={onUpdateTitle}
disabled={title.length === 0}
>
<Button type="submit" onClick={onUpdateTitle} disabled={!title}>
Save
</Button>
</ModalClose>
Expand Down
Loading
Loading