From 1d4199a27080e452185b369667b8a76004d24f26 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 17 Dec 2024 15:31:57 +0700 Subject: [PATCH 1/2] feat: handle case thread when original model deleted --- web/containers/ModelDropdown/index.tsx | 51 +++++++-- web/hooks/useCreateNewThread.ts | 5 +- web/hooks/useSendChatMessage.ts | 7 ++ .../ChatInput/RichTextEditor.tsx | 7 +- .../ThreadCenterPanel/ChatInput/index.tsx | 105 +++++++++--------- 5 files changed, 111 insertions(+), 64 deletions(-) diff --git a/web/containers/ModelDropdown/index.tsx b/web/containers/ModelDropdown/index.tsx index 6244162bb8..7359cb27af 100644 --- a/web/containers/ModelDropdown/index.tsx +++ b/web/containers/ModelDropdown/index.tsx @@ -12,7 +12,7 @@ import { useClickOutside, } from '@janhq/joi' -import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { ChevronDownIcon, @@ -65,16 +65,21 @@ type Props = { disabled?: boolean } +export const modelDropdownStateAtom = atom(false) + const ModelDropdown = ({ disabled, chatInputMode, strictedThread = true, }: Props) => { const { downloadModel } = useDownloadModel() + const [modelDropdownState, setModelDropdownState] = useAtom( + modelDropdownStateAtom + ) const [searchFilter, setSearchFilter] = useState('local') const [searchText, setSearchText] = useState('') - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(modelDropdownState) const activeThread = useAtomValue(activeThreadAtom) const activeAssistant = useAtomValue(activeAssistantAtom) const downloadingModels = useAtomValue(getDownloadingModelAtom) @@ -84,22 +89,39 @@ const ModelDropdown = ({ const [dropdownOptions, setDropdownOptions] = useState( null ) + const downloadStates = useAtomValue(modelDownloadStateAtom) const setThreadModelParams = useSetAtom(setThreadModelParamsAtom) const { updateModelParameter } = useUpdateModelParameters() const searchInputRef = useRef(null) const configuredModels = useAtomValue(configuredModelsAtom) - const featuredModel = configuredModels.filter((x) => - x.metadata?.tags?.includes('Featured') + + const recommendModel = ['llama3.2-1b-instruct', 'llama3.2-3b-instruct'] + const featuredModel = configuredModels.filter( + (x) => + recommendModel.includes(x.id) && + x.metadata?.tags?.includes('Featured') && + x.metadata?.size < 5000000000 ) const { updateThreadMetadata } = useCreateNewThread() - useClickOutside(() => setOpen(false), null, [dropdownOptions, toggle]) + useClickOutside(() => handleChangeStateOpen(false), null, [ + dropdownOptions, + toggle, + ]) const [showEngineListModel, setShowEngineListModel] = useAtom( showEngineListModelAtom ) + const handleChangeStateOpen = useCallback( + (state: boolean) => { + setOpen(state) + setModelDropdownState(state) + }, + [setModelDropdownState] + ) + const isModelSupportRagAndTools = useCallback((model: Model) => { return ( model?.engine === InferenceEngine.openai || @@ -145,6 +167,12 @@ const ModelDropdown = ({ [configuredModels, searchText, searchFilter, downloadedModels] ) + useEffect(() => { + if (modelDropdownState && chatInputMode) { + setOpen(modelDropdownState) + } + }, [chatInputMode, modelDropdownState]) + useEffect(() => { if (open && searchInputRef.current) { searchInputRef.current.focus() @@ -157,7 +185,7 @@ const ModelDropdown = ({ let model = downloadedModels.find((model) => model.id === modelId) if (!model) { - model = recommendedModel + model = undefined } setSelectedModel(model) }, [ @@ -343,14 +371,21 @@ const ModelDropdown = ({ 'inline-block max-w-[200px] cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap', open && 'border border-transparent' )} - onClick={() => setOpen(!open)} + onClick={() => handleChangeStateOpen(!open)} > - {selectedModel?.name} + + {selectedModel?.name || 'Select Model'} + ) : ( { const experimentalEnabled = useAtomValue(experimentalFeatureEnabledAtom) const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom) - const { recommendedModel, downloadedModels } = useRecommendedModel() - const threads = useAtomValue(threadsAtom) const { stopInference } = useActiveModel() @@ -84,7 +81,7 @@ export const useCreateNewThread = () => { setIsGeneratingResponse(false) stopInference() - const defaultModel = model ?? recommendedModel ?? downloadedModels[0] + const defaultModel = model if (!model) { // if we have model, which means user wants to create new thread from Model hub. Allow them. diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index bbe5e3cd71..8f48261cc0 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -15,6 +15,7 @@ import { import { extractInferenceParams, extractModelLoadParams } from '@janhq/core' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' +import { modelDropdownStateAtom } from '@/containers/ModelDropdown' import { currentPromptAtom, editPromptAtom, @@ -73,6 +74,7 @@ export default function useSendChatMessage() { const activeThreadRef = useRef() const activeAssistantRef = useRef() const setTokenSpeed = useSetAtom(tokenSpeedAtom) + const setModelDropdownState = useSetAtom(modelDropdownStateAtom) const selectedModelRef = useRef() @@ -122,6 +124,11 @@ export default function useSendChatMessage() { return } + if (selectedModelRef.current?.id === undefined) { + setModelDropdownState(true) + return + } + if (engineParamsUpdate) setReloadModel(true) setTokenSpeed(undefined) diff --git a/web/screens/Thread/ThreadCenterPanel/ChatInput/RichTextEditor.tsx b/web/screens/Thread/ThreadCenterPanel/ChatInput/RichTextEditor.tsx index 66c0d890ea..2049fedf64 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatInput/RichTextEditor.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatInput/RichTextEditor.tsx @@ -23,6 +23,7 @@ import useSendChatMessage from '@/hooks/useSendChatMessage' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' +import { selectedModelAtom } from '@/helpers/atoms/Model.atom' import { getActiveThreadIdAtom, activeSettingInputBoxAtom, @@ -78,7 +79,7 @@ const RichTextEditor = ({ const messages = useAtomValue(getCurrentChatMessagesAtom) const { sendChatMessage } = useSendChatMessage() const { stopInference } = useActiveModel() - + const selectedModel = useAtomValue(selectedModelAtom) const largeContentThreshold = 1000 // The decorate function identifies code blocks and marks the ranges @@ -233,7 +234,9 @@ const RichTextEditor = ({ event.preventDefault() if (messages[messages.length - 1]?.status !== MessageStatus.Pending) { sendChatMessage(currentPrompt) - resetEditor() + if (selectedModel) { + resetEditor() + } } else onStopInferenceClick() } }, diff --git a/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx index b3246a26b7..98069f3e9c 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx @@ -90,6 +90,12 @@ const ChatInput = () => { } }, [activeThreadId]) + useEffect(() => { + if (!selectedModel && !activeSettingInputBox) { + setActiveSettingInputBox(true) + } + }, [activeSettingInputBox, selectedModel, setActiveSettingInputBox]) + const onStopInferenceClick = async () => { stopInference() } @@ -297,6 +303,7 @@ const ChatInput = () => { )} + {messages[messages.length - 1]?.status !== MessageStatus.Pending && !isGeneratingResponse && !isStreamingResponse ? ( @@ -340,55 +347,53 @@ const ChatInput = () => { - {activeSettingInputBox && ( -
-
- - +
+ + { + // TODO @faisal: should be refactor later and better experience beetwen tab and toggle button + if (showRightPanel && activeTabThreadRightPanel !== 'model') { + setShowRightPanel(true) + setActiveTabThreadRightPanel('model') } - onClick={() => { - // TODO @faisal: should be refactor later and better experience beetwen tab and toggle button - if (showRightPanel && activeTabThreadRightPanel !== 'model') { - setShowRightPanel(true) - setActiveTabThreadRightPanel('model') - } - if (showRightPanel && activeTabThreadRightPanel === 'model') { - setShowRightPanel(false) - setActiveTabThreadRightPanel(undefined) - } - if (activeTabThreadRightPanel === undefined) { - setShowRightPanel(true) - setActiveTabThreadRightPanel('model') - } - if ( - !showRightPanel && - activeTabThreadRightPanel !== 'model' - ) { - setShowRightPanel(true) - setActiveTabThreadRightPanel('model') - } - }} - > - - -
+ if (showRightPanel && activeTabThreadRightPanel === 'model') { + setShowRightPanel(false) + setActiveTabThreadRightPanel(undefined) + } + if (activeTabThreadRightPanel === undefined) { + setShowRightPanel(true) + setActiveTabThreadRightPanel('model') + } + if (!showRightPanel && activeTabThreadRightPanel !== 'model') { + setShowRightPanel(true) + setActiveTabThreadRightPanel('model') + } + }} + > + +
+
+ {selectedModel && ( -
- )} + )} + Date: Tue, 17 Dec 2024 21:34:35 +0700 Subject: [PATCH 2/2] chore: make a utils for manualRecommendModel --- web/containers/ModelDropdown/index.tsx | 4 ++-- .../ChatBody/OnDeviceStarterScreen/index.tsx | 10 ++++++---- web/utils/model.ts | 5 +++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/web/containers/ModelDropdown/index.tsx b/web/containers/ModelDropdown/index.tsx index 7359cb27af..09240845d3 100644 --- a/web/containers/ModelDropdown/index.tsx +++ b/web/containers/ModelDropdown/index.tsx @@ -37,6 +37,7 @@ import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' import { formatDownloadPercentage, toGibibytes } from '@/utils/converter' +import { manualRecommendationModel } from '@/utils/model' import { getLogoEngine, getTitleByEngine, @@ -96,10 +97,9 @@ const ModelDropdown = ({ const searchInputRef = useRef(null) const configuredModels = useAtomValue(configuredModelsAtom) - const recommendModel = ['llama3.2-1b-instruct', 'llama3.2-3b-instruct'] const featuredModel = configuredModels.filter( (x) => - recommendModel.includes(x.id) && + manualRecommendationModel.includes(x.id) && x.metadata?.tags?.includes('Featured') && x.metadata?.size < 5000000000 ) diff --git a/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx index af5853a3a9..041c37b184 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx @@ -27,6 +27,7 @@ import { modelDownloadStateAtom } from '@/hooks/useDownloadState' import { useStarterScreen } from '@/hooks/useStarterScreen' import { formatDownloadPercentage, toGibibytes } from '@/utils/converter' +import { manualRecommendationModel } from '@/utils/model' import { getLogoEngine, getTitleByEngine, @@ -56,15 +57,16 @@ const OnDeviceStarterScreen = ({ isShowStarterScreen }: Props) => { const configuredModels = useAtomValue(configuredModelsAtom) const setMainViewState = useSetAtom(mainViewStateAtom) - const recommendModel = ['llama3.2-1b-instruct', 'llama3.2-3b-instruct'] - const featuredModel = configuredModels.filter((x) => { const manualRecommendModel = configuredModels.filter((x) => - recommendModel.includes(x.id) + manualRecommendationModel.includes(x.id) ) if (manualRecommendModel.length === 2) { - return x.id === recommendModel[0] || x.id === recommendModel[1] + return ( + x.id === manualRecommendationModel[0] || + x.id === manualRecommendationModel[1] + ) } else { return ( x.metadata?.tags?.includes('Featured') && x.metadata?.size < 5000000000 diff --git a/web/utils/model.ts b/web/utils/model.ts index cb0f0ff31b..00bf80c126 100644 --- a/web/utils/model.ts +++ b/web/utils/model.ts @@ -7,3 +7,8 @@ export const normalizeModelId = (downloadUrl: string): string => { return downloadUrl.split('/').pop() ?? downloadUrl } + +export const manualRecommendationModel = [ + 'llama3.2-1b-instruct', + 'llama3.2-3b-instruct', +]