feat: message actions tooltip

This commit is contained in:
Louis 2023-11-24 15:05:11 +07:00
parent 26bd092f03
commit 5ae319db33
3 changed files with 115 additions and 4 deletions

View File

@ -3,6 +3,7 @@ import { atom } from 'jotai'
import {
conversationStatesAtom,
currentConversationAtom,
getActiveConvoIdAtom,
updateThreadStateLastMessageAtom,
} from './Conversation.atom'
@ -102,6 +103,17 @@ export const cleanConversationMessages = atom(null, (get, set, id: string) => {
set(chatMessages, newData)
})
export const deleteMessage = atom(null, (get, set, id: string) => {
const newData: Record<string, ThreadMessage[]> = {
...get(chatMessages),
}
const threadId = get(currentConversationAtom)?.id
if (threadId) {
newData[threadId] = newData[threadId].filter((e) => e.id !== id)
set(chatMessages, newData)
}
})
export const updateMessageAtom = atom(
null,
(

View File

@ -0,0 +1,85 @@
import {
ChatCompletionRole,
EventName,
MessageStatus,
PluginType,
ThreadMessage,
events,
} from '@janhq/core'
import { ConversationalPlugin, InferencePlugin } from '@janhq/core/lib/plugins'
import { useAtomValue, useSetAtom } from 'jotai'
import { RefreshCcw, ClipboardCopy, Trash2Icon, StopCircle } from 'lucide-react'
import { toaster } from '@/containers/Toast'
import {
deleteMessage,
getCurrentChatMessagesAtom,
} from '@/helpers/atoms/ChatMessage.atom'
import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom'
import { pluginManager } from '@/plugin'
const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
const deleteAMessage = useSetAtom(deleteMessage)
const thread = useAtomValue(currentConversationAtom)
const messages = useAtomValue(getCurrentChatMessagesAtom)
const stopInference = async () => {
await pluginManager
.get<InferencePlugin>(PluginType.Inference)
?.stopInference()
setTimeout(() => {
events.emit(EventName.OnMessageResponseFinished, message)
}, 300)
}
return (
<div className="flex flex-row items-center">
{message.status === MessageStatus.Pending && (
<StopCircle
className="mx-1 cursor-pointer rounded-sm bg-gray-800 px-[3px]"
size={20}
onClick={() => stopInference()}
/>
)}
{message.status !== MessageStatus.Pending &&
message.id === messages[0]?.id && (
<RefreshCcw
className="mx-1 cursor-pointer rounded-sm bg-gray-800 px-[3px]"
size={20}
onClick={() => {
const messageRequest = messages[1]
if (message.role === ChatCompletionRole.Assistant) {
deleteAMessage(message.id ?? '')
}
events.emit(EventName.OnNewMessageRequest, messageRequest)
}}
/>
)}
<ClipboardCopy
className="mx-1 cursor-pointer rounded-sm bg-gray-800 px-[3px]"
size={20}
onClick={() => {
navigator.clipboard.writeText(message.content ?? '')
toaster({
title: 'Copied to clipboard',
})
}}
/>
<Trash2Icon
className="mx-1 cursor-pointer rounded-sm bg-gray-800 px-[3px]"
size={20}
onClick={async () => {
deleteAMessage(message.id ?? '')
if (thread)
await pluginManager
.get<ConversationalPlugin>(PluginType.Conversational)
?.saveConversation({
...thread,
messages: messages.filter((e) => e.id !== message.id),
})
}}
/>
</div>
)
}
export default MessageToolbar

View File

@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
import React, { useContext } from 'react'
import { ChatCompletionRole, MessageStatus, ThreadMessage } from '@janhq/core'
import { ChatCompletionRole, ThreadMessage } from '@janhq/core'
import hljs from 'highlight.js'
import { MoreVertical } from 'lucide-react'
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
@ -14,8 +16,12 @@ import LogoMark from '@/containers/Brand/Logo/Mark'
import BubbleLoader from '@/containers/Loader/Bubble'
import { FeatureToggleContext } from '@/context/FeatureToggle'
import { displayDate } from '@/utils/datetime'
import MessageToolbar from '../MessageToolbar'
const marked = new Marked(
markedHighlight({
langPrefix: 'hljs',
@ -42,12 +48,13 @@ const marked = new Marked(
)
const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
const { experimentalFeatureEnabed } = useContext(FeatureToggleContext)
const parsedText = marked.parse(props.content ?? '')
const isUser = props.role === ChatCompletionRole.User
const isSystem = props.role === ChatCompletionRole.System
return (
<div className="mx-auto rounded-xl px-4 lg:w-3/4">
<div className="group mx-auto rounded-xl px-4 lg:w-3/4">
<div
className={twMerge(
'mb-1 flex items-center justify-start gap-2',
@ -57,10 +64,17 @@ const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
{!isUser && !isSystem && <LogoMark width={20} />}
<div className="text-sm font-extrabold capitalize">{props.role}</div>
<p className="text-xs font-medium">{displayDate(props.createdAt)}</p>
{experimentalFeatureEnabed && (
<div className="hidden cursor-pointer group-hover:flex">
<MessageToolbar message={props} />
</div>
)}
</div>
<div className={twMerge('w-full')}>
{!props.content || props.content === '' ? (
{props.status === MessageStatus.Pending &&
(!props.content || props.content === '') ? (
<BubbleLoader />
) : (
<>