Merge pull request #4360 from janhq/main
chore: sync latest main into dev
This commit is contained in:
commit
da55b46240
@ -1 +1 @@
|
|||||||
1.0.6
|
1.0.7
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export default function ModelHandler() {
|
|||||||
const activeModel = useAtomValue(activeModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const setActiveModel = useSetAtom(activeModelAtom)
|
const setActiveModel = useSetAtom(activeModelAtom)
|
||||||
const setStateModel = useSetAtom(stateModelAtom)
|
const setStateModel = useSetAtom(stateModelAtom)
|
||||||
const [subscribedGeneratingMessage, setSubscribedGeneratingMessage] = useAtom(
|
const subscribedGeneratingMessage = useAtomValue(
|
||||||
subscribedGeneratingMessageAtom
|
subscribedGeneratingMessageAtom
|
||||||
)
|
)
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Thread, ThreadContent, ThreadState } from '@janhq/core'
|
import { Thread, ThreadContent, ThreadState } from '@janhq/core'
|
||||||
|
|
||||||
import { atom } from 'jotai'
|
import { atom } from 'jotai'
|
||||||
import { atomWithStorage } from 'jotai/utils'
|
import { atomWithStorage, selectAtom } from 'jotai/utils'
|
||||||
|
|
||||||
import { ModelParams } from '@/types/model'
|
import { ModelParams } from '@/types/model'
|
||||||
|
|
||||||
@ -34,6 +34,22 @@ export const threadStatesAtom = atomWithStorage<Record<string, ThreadState>>(
|
|||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether there is a thread waiting for response or not
|
||||||
|
*/
|
||||||
|
const isWaitingForResponseAtom = selectAtom(threadStatesAtom, (threads) =>
|
||||||
|
Object.values(threads).some((t) => t.waitingForResponse)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine 2 states to reduce rerender
|
||||||
|
* 1. isWaitingForResponse
|
||||||
|
* 2. isGenerating
|
||||||
|
*/
|
||||||
|
export const isBlockingSendAtom = atom(
|
||||||
|
(get) => get(isWaitingForResponseAtom) || get(isGeneratingResponseAtom)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores all threads for the current user
|
* Stores all threads for the current user
|
||||||
*/
|
*/
|
||||||
@ -173,6 +189,29 @@ export const updateThreadWaitingForResponseAtom = atom(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the thread waiting for response state
|
||||||
|
*/
|
||||||
|
export const resetThreadWaitingForResponseAtom = atom(null, (get, set) => {
|
||||||
|
const currentState = { ...get(threadStatesAtom) }
|
||||||
|
Object.keys(currentState).forEach((threadId) => {
|
||||||
|
currentState[threadId] = {
|
||||||
|
...currentState[threadId],
|
||||||
|
waitingForResponse: false,
|
||||||
|
error: undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
set(threadStatesAtom, currentState)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all generating states
|
||||||
|
**/
|
||||||
|
export const resetGeneratingResponseAtom = atom(null, (get, set) => {
|
||||||
|
set(resetThreadWaitingForResponseAtom)
|
||||||
|
set(isGeneratingResponseAtom, false)
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the thread last message
|
* Update the thread last message
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -10,6 +10,10 @@ import { LAST_USED_MODEL_ID } from './useRecommendedModel'
|
|||||||
import { vulkanEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
import { vulkanEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
|
import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
|
||||||
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
import {
|
||||||
|
isGeneratingResponseAtom,
|
||||||
|
resetThreadWaitingForResponseAtom,
|
||||||
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export const activeModelAtom = atom<Model | undefined>(undefined)
|
export const activeModelAtom = atom<Model | undefined>(undefined)
|
||||||
export const loadModelErrorAtom = atom<string | undefined>(undefined)
|
export const loadModelErrorAtom = atom<string | undefined>(undefined)
|
||||||
|
|||||||
@ -67,7 +67,6 @@ describe('useCreateNewThread', () => {
|
|||||||
} as any)
|
} as any)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockSetAtom).toHaveBeenCalledTimes(1)
|
|
||||||
expect(extensionManager.get).toHaveBeenCalled()
|
expect(extensionManager.get).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -113,7 +112,6 @@ describe('useCreateNewThread', () => {
|
|||||||
} as any)
|
} as any)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockSetAtom).toHaveBeenCalledTimes(1) // Check if all the necessary atoms were set
|
|
||||||
expect(extensionManager.get).toHaveBeenCalled()
|
expect(extensionManager.get).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -158,7 +156,6 @@ describe('useCreateNewThread', () => {
|
|||||||
} as any)
|
} as any)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockSetAtom).toHaveBeenCalledTimes(1) // Check if all the necessary atoms were set
|
|
||||||
expect(extensionManager.get).toHaveBeenCalled()
|
expect(extensionManager.get).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -20,8 +20,6 @@ import { toaster } from '@/containers/Toast'
|
|||||||
|
|
||||||
import { isLocalEngine } from '@/utils/modelEngine'
|
import { isLocalEngine } from '@/utils/modelEngine'
|
||||||
|
|
||||||
import { useActiveModel } from './useActiveModel'
|
|
||||||
|
|
||||||
import useRecommendedModel from './useRecommendedModel'
|
import useRecommendedModel from './useRecommendedModel'
|
||||||
import useSetActiveThread from './useSetActiveThread'
|
import useSetActiveThread from './useSetActiveThread'
|
||||||
|
|
||||||
@ -52,10 +50,8 @@ export const useCreateNewThread = () => {
|
|||||||
const [activeAssistant, setActiveAssistant] = useAtom(activeAssistantAtom)
|
const [activeAssistant, setActiveAssistant] = useAtom(activeAssistantAtom)
|
||||||
|
|
||||||
const experimentalEnabled = useAtomValue(experimentalFeatureEnabledAtom)
|
const experimentalEnabled = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
|
||||||
|
|
||||||
const threads = useAtomValue(threadsAtom)
|
const threads = useAtomValue(threadsAtom)
|
||||||
const { stopInference } = useActiveModel()
|
|
||||||
|
|
||||||
const { recommendedModel } = useRecommendedModel()
|
const { recommendedModel } = useRecommendedModel()
|
||||||
|
|
||||||
@ -63,10 +59,6 @@ export const useCreateNewThread = () => {
|
|||||||
assistant: (ThreadAssistantInfo & { id: string; name: string }) | Assistant,
|
assistant: (ThreadAssistantInfo & { id: string; name: string }) | Assistant,
|
||||||
model?: Model | undefined
|
model?: Model | undefined
|
||||||
) => {
|
) => {
|
||||||
// Stop generating if any
|
|
||||||
setIsGeneratingResponse(false)
|
|
||||||
stopInference()
|
|
||||||
|
|
||||||
const defaultModel = model || recommendedModel
|
const defaultModel = model || recommendedModel
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
|
|||||||
@ -48,6 +48,7 @@ export default function useDeleteThread() {
|
|||||||
if (thread) {
|
if (thread) {
|
||||||
const updatedThread = {
|
const updatedThread = {
|
||||||
...thread,
|
...thread,
|
||||||
|
title: 'New Thread',
|
||||||
metadata: {
|
metadata: {
|
||||||
...thread.metadata,
|
...thread.metadata,
|
||||||
title: 'New Thread',
|
title: 'New Thread',
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
|
|||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
engineParamsUpdateAtom,
|
engineParamsUpdateAtom,
|
||||||
|
resetGeneratingResponseAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -24,6 +25,7 @@ const AssistantSetting: React.FC<Props> = ({ componentData }) => {
|
|||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
const { stopModel } = useActiveModel()
|
const { stopModel } = useActiveModel()
|
||||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
|
const resetGenerating = useSetAtom(resetGeneratingResponseAtom)
|
||||||
|
|
||||||
const onValueChanged = useCallback(
|
const onValueChanged = useCallback(
|
||||||
(key: string, value: string | number | boolean | string[]) => {
|
(key: string, value: string | number | boolean | string[]) => {
|
||||||
@ -32,6 +34,7 @@ const AssistantSetting: React.FC<Props> = ({ componentData }) => {
|
|||||||
componentData.find((x) => x.key === key)?.requireModelReload ?? false
|
componentData.find((x) => x.key === key)?.requireModelReload ?? false
|
||||||
if (shouldReloadModel) {
|
if (shouldReloadModel) {
|
||||||
setEngineParamsUpdate(true)
|
setEngineParamsUpdate(true)
|
||||||
|
resetGenerating()
|
||||||
stopModel()
|
stopModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,7 @@ import EmptyThread from './EmptyThread'
|
|||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
isGeneratingResponseAtom,
|
isBlockingSendAtom,
|
||||||
threadStatesAtom,
|
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const ChatConfigurator = memo(() => {
|
const ChatConfigurator = memo(() => {
|
||||||
@ -65,12 +64,7 @@ const ChatBody = memo(
|
|||||||
const prevScrollTop = useRef(0)
|
const prevScrollTop = useRef(0)
|
||||||
const isUserManuallyScrollingUp = useRef(false)
|
const isUserManuallyScrollingUp = useRef(false)
|
||||||
const currentThread = useAtomValue(activeThreadAtom)
|
const currentThread = useAtomValue(activeThreadAtom)
|
||||||
const threadStates = useAtomValue(threadStatesAtom)
|
const isBlockingSend = useAtomValue(isBlockingSendAtom)
|
||||||
const isGeneratingResponse = useAtomValue(isGeneratingResponseAtom)
|
|
||||||
|
|
||||||
const isStreamingResponse = Object.values(threadStates).some(
|
|
||||||
(threadState) => threadState.waitingForResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
const count = useMemo(
|
const count = useMemo(
|
||||||
() => (messages?.length ?? 0) + (loadModelError ? 1 : 0),
|
() => (messages?.length ?? 0) + (loadModelError ? 1 : 0),
|
||||||
@ -85,34 +79,13 @@ const ChatBody = memo(
|
|||||||
overscan: 5,
|
overscan: 5,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (parentRef.current) {
|
|
||||||
parentRef.current.scrollTo({ top: parentRef.current.scrollHeight })
|
|
||||||
virtualizer.scrollToIndex(count - 1)
|
|
||||||
}
|
|
||||||
}, [count, virtualizer])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (parentRef.current && isGeneratingResponse) {
|
|
||||||
parentRef.current.scrollTo({ top: parentRef.current.scrollHeight })
|
|
||||||
virtualizer.scrollToIndex(count - 1)
|
|
||||||
}
|
|
||||||
}, [count, virtualizer, isGeneratingResponse])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (parentRef.current && isGeneratingResponse) {
|
|
||||||
parentRef.current.scrollTo({ top: parentRef.current.scrollHeight })
|
|
||||||
virtualizer.scrollToIndex(count - 1)
|
|
||||||
}
|
|
||||||
}, [count, virtualizer, isGeneratingResponse, currentThread?.id])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isUserManuallyScrollingUp.current = false
|
isUserManuallyScrollingUp.current = false
|
||||||
if (parentRef.current) {
|
if (parentRef.current && isBlockingSend) {
|
||||||
parentRef.current.scrollTo({ top: parentRef.current.scrollHeight })
|
parentRef.current.scrollTo({ top: parentRef.current.scrollHeight })
|
||||||
virtualizer.scrollToIndex(count - 1)
|
virtualizer.scrollToIndex(count - 1)
|
||||||
}
|
}
|
||||||
}, [count, currentThread?.id, virtualizer])
|
}, [count, virtualizer, isBlockingSend, currentThread?.id])
|
||||||
|
|
||||||
const items = virtualizer.getVirtualItems()
|
const items = virtualizer.getVirtualItems()
|
||||||
|
|
||||||
@ -121,7 +94,7 @@ const ChatBody = memo(
|
|||||||
_,
|
_,
|
||||||
instance
|
instance
|
||||||
) => {
|
) => {
|
||||||
if (isUserManuallyScrollingUp.current === true && isStreamingResponse)
|
if (isUserManuallyScrollingUp.current === true && isBlockingSend)
|
||||||
return false
|
return false
|
||||||
return (
|
return (
|
||||||
// item.start < (instance.scrollOffset ?? 0) &&
|
// item.start < (instance.scrollOffset ?? 0) &&
|
||||||
@ -133,7 +106,7 @@ const ChatBody = memo(
|
|||||||
(event: React.UIEvent<HTMLElement>) => {
|
(event: React.UIEvent<HTMLElement>) => {
|
||||||
const currentScrollTop = event.currentTarget.scrollTop
|
const currentScrollTop = event.currentTarget.scrollTop
|
||||||
|
|
||||||
if (prevScrollTop.current > currentScrollTop && isStreamingResponse) {
|
if (prevScrollTop.current > currentScrollTop && isBlockingSend) {
|
||||||
isUserManuallyScrollingUp.current = true
|
isUserManuallyScrollingUp.current = true
|
||||||
} else {
|
} else {
|
||||||
const currentScrollTop = event.currentTarget.scrollTop
|
const currentScrollTop = event.currentTarget.scrollTop
|
||||||
@ -151,7 +124,7 @@ const ChatBody = memo(
|
|||||||
}
|
}
|
||||||
prevScrollTop.current = currentScrollTop
|
prevScrollTop.current = currentScrollTop
|
||||||
},
|
},
|
||||||
[isStreamingResponse]
|
[isBlockingSend]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -35,22 +35,19 @@ import RichTextEditor from './RichTextEditor'
|
|||||||
import { showRightPanelAtom } from '@/helpers/atoms/App.atom'
|
import { showRightPanelAtom } from '@/helpers/atoms/App.atom'
|
||||||
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
|
import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
|
||||||
import { selectedModelAtom } from '@/helpers/atoms/Model.atom'
|
import { selectedModelAtom } from '@/helpers/atoms/Model.atom'
|
||||||
import { spellCheckAtom } from '@/helpers/atoms/Setting.atom'
|
import { spellCheckAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
import {
|
import {
|
||||||
activeSettingInputBoxAtom,
|
activeSettingInputBoxAtom,
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
getActiveThreadIdAtom,
|
getActiveThreadIdAtom,
|
||||||
isGeneratingResponseAtom,
|
isBlockingSendAtom,
|
||||||
threadStatesAtom,
|
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
import { activeTabThreadRightPanelAtom } from '@/helpers/atoms/ThreadRightPanel.atom'
|
import { activeTabThreadRightPanelAtom } from '@/helpers/atoms/ThreadRightPanel.atom'
|
||||||
|
|
||||||
const ChatInput = () => {
|
const ChatInput = () => {
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const { stateModel } = useActiveModel()
|
const { stateModel } = useActiveModel()
|
||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
|
||||||
const spellCheck = useAtomValue(spellCheckAtom)
|
const spellCheck = useAtomValue(spellCheckAtom)
|
||||||
|
|
||||||
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
|
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
|
||||||
@ -67,8 +64,7 @@ const ChatInput = () => {
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const imageInputRef = useRef<HTMLInputElement>(null)
|
const imageInputRef = useRef<HTMLInputElement>(null)
|
||||||
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
const isGeneratingResponse = useAtomValue(isGeneratingResponseAtom)
|
const isBlockingSend = useAtomValue(isBlockingSendAtom)
|
||||||
const threadStates = useAtomValue(threadStatesAtom)
|
|
||||||
const activeAssistant = useAtomValue(activeAssistantAtom)
|
const activeAssistant = useAtomValue(activeAssistantAtom)
|
||||||
const { stopInference } = useActiveModel()
|
const { stopInference } = useActiveModel()
|
||||||
|
|
||||||
@ -77,10 +73,6 @@ const ChatInput = () => {
|
|||||||
activeTabThreadRightPanelAtom
|
activeTabThreadRightPanelAtom
|
||||||
)
|
)
|
||||||
|
|
||||||
const isStreamingResponse = Object.values(threadStates).some(
|
|
||||||
(threadState) => threadState.waitingForResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
const refAttachmentMenus = useClickOutside(() => setShowAttacmentMenus(false))
|
const refAttachmentMenus = useClickOutside(() => setShowAttacmentMenus(false))
|
||||||
const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom)
|
const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom)
|
||||||
|
|
||||||
@ -302,9 +294,7 @@ const ChatInput = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{messages[messages.length - 1]?.status !== MessageStatus.Pending &&
|
{!isBlockingSend ? (
|
||||||
!isGeneratingResponse &&
|
|
||||||
!isStreamingResponse ? (
|
|
||||||
<>
|
<>
|
||||||
{currentPrompt.length !== 0 && (
|
{currentPrompt.length !== 0 && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import {
|
|||||||
} from '@/helpers/atoms/ChatMessage.atom'
|
} from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
|
isBlockingSendAtom,
|
||||||
updateThreadAtom,
|
updateThreadAtom,
|
||||||
updateThreadStateLastMessageAtom,
|
updateThreadStateLastMessageAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
@ -43,6 +44,7 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
|||||||
const clipboard = useClipboard({ timeout: 1000 })
|
const clipboard = useClipboard({ timeout: 1000 })
|
||||||
const updateThreadLastMessage = useSetAtom(updateThreadStateLastMessageAtom)
|
const updateThreadLastMessage = useSetAtom(updateThreadStateLastMessageAtom)
|
||||||
const updateThread = useSetAtom(updateThreadAtom)
|
const updateThread = useSetAtom(updateThreadAtom)
|
||||||
|
const isBlockingSend = useAtomValue(isBlockingSendAtom)
|
||||||
|
|
||||||
const onDeleteClick = useCallback(async () => {
|
const onDeleteClick = useCallback(async () => {
|
||||||
deleteMessage(message.id ?? '')
|
deleteMessage(message.id ?? '')
|
||||||
@ -91,7 +93,8 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
|||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<div className="flex gap-1 bg-[hsla(var(--app-bg))]">
|
<div className="flex gap-1 bg-[hsla(var(--app-bg))]">
|
||||||
{message.role === ChatCompletionRole.User &&
|
{message.role === ChatCompletionRole.User &&
|
||||||
message.content[0]?.type === ContentType.Text && (
|
message.content[0]?.type === ContentType.Text &&
|
||||||
|
!isBlockingSend && (
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer rounded-lg border border-[hsla(var(--app-border))] p-2"
|
className="cursor-pointer rounded-lg border border-[hsla(var(--app-border))] p-2"
|
||||||
onClick={onEditClick}
|
onClick={onEditClick}
|
||||||
@ -110,7 +113,8 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
|||||||
|
|
||||||
{message.id === messages[messages.length - 1]?.id &&
|
{message.id === messages[messages.length - 1]?.id &&
|
||||||
!messages[messages.length - 1]?.metadata?.error &&
|
!messages[messages.length - 1]?.metadata?.error &&
|
||||||
!messages[messages.length - 1].attachments?.length && (
|
!messages[messages.length - 1].attachments?.length &&
|
||||||
|
!isBlockingSend && (
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer rounded-lg border border-[hsla(var(--app-border))] p-2"
|
className="cursor-pointer rounded-lg border border-[hsla(var(--app-border))] p-2"
|
||||||
onClick={resendChatMessage}
|
onClick={resendChatMessage}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user