fix: display error message on model load fail (#1894)
This commit is contained in:
parent
36cd5988d4
commit
5ce2e422f8
@ -305,7 +305,7 @@ const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> =>
|
|||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
logServer(`[NITRO]::Error: Load model failed with error ${err}`)
|
logServer(`[NITRO]::Error: Load model failed with error ${err}`)
|
||||||
return Promise.reject()
|
return Promise.reject(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -134,6 +134,7 @@ export default class JanInferenceNitroExtension extends InferenceExtension {
|
|||||||
|
|
||||||
const modelFullPath = await joinPath(["models", model.id]);
|
const modelFullPath = await joinPath(["models", model.id]);
|
||||||
|
|
||||||
|
this._currentModel = model;
|
||||||
const nitroInitResult = await executeOnMain(NODE, "runModel", {
|
const nitroInitResult = await executeOnMain(NODE, "runModel", {
|
||||||
modelFullPath,
|
modelFullPath,
|
||||||
model,
|
model,
|
||||||
@ -144,7 +145,6 @@ export default class JanInferenceNitroExtension extends InferenceExtension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._currentModel = model;
|
|
||||||
events.emit(ModelEvent.OnModelReady, model);
|
events.emit(ModelEvent.OnModelReady, model);
|
||||||
|
|
||||||
this.getNitroProcesHealthIntervalId = setInterval(
|
this.getNitroProcesHealthIntervalId = setInterval(
|
||||||
@ -226,6 +226,9 @@ export default class JanInferenceNitroExtension extends InferenceExtension {
|
|||||||
*/
|
*/
|
||||||
private async onMessageRequest(data: MessageRequest) {
|
private async onMessageRequest(data: MessageRequest) {
|
||||||
if (data.model?.engine !== InferenceEngine.nitro || !this._currentModel) {
|
if (data.model?.engine !== InferenceEngine.nitro || !this._currentModel) {
|
||||||
|
console.log(
|
||||||
|
`Model is not nitro or no model loaded ${data.model?.engine} ${this._currentModel}`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ function stopModel(): Promise<void> {
|
|||||||
* TODO: Should pass absolute of the model file instead of just the name - So we can modurize the module.ts to npm package
|
* TODO: Should pass absolute of the model file instead of just the name - So we can modurize the module.ts to npm package
|
||||||
*/
|
*/
|
||||||
async function runModel(
|
async function runModel(
|
||||||
wrapper: ModelInitOptions
|
wrapper: ModelInitOptions,
|
||||||
): Promise<ModelOperationResponse | void> {
|
): Promise<ModelOperationResponse | void> {
|
||||||
if (wrapper.model.engine !== InferenceEngine.nitro) {
|
if (wrapper.model.engine !== InferenceEngine.nitro) {
|
||||||
// Not a nitro model
|
// Not a nitro model
|
||||||
@ -85,7 +85,7 @@ async function runModel(
|
|||||||
const ggufBinFile = files.find(
|
const ggufBinFile = files.find(
|
||||||
(file) =>
|
(file) =>
|
||||||
file === path.basename(currentModelFile) ||
|
file === path.basename(currentModelFile) ||
|
||||||
file.toLowerCase().includes(SUPPORTED_MODEL_FORMAT)
|
file.toLowerCase().includes(SUPPORTED_MODEL_FORMAT),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!ggufBinFile) return Promise.reject("No GGUF model file found");
|
if (!ggufBinFile) return Promise.reject("No GGUF model file found");
|
||||||
@ -180,10 +180,10 @@ function promptTemplateConverter(promptTemplate: string): PromptTemplate {
|
|||||||
const system_prompt = promptTemplate.substring(0, systemIndex);
|
const system_prompt = promptTemplate.substring(0, systemIndex);
|
||||||
const user_prompt = promptTemplate.substring(
|
const user_prompt = promptTemplate.substring(
|
||||||
systemIndex + systemMarker.length,
|
systemIndex + systemMarker.length,
|
||||||
promptIndex
|
promptIndex,
|
||||||
);
|
);
|
||||||
const ai_prompt = promptTemplate.substring(
|
const ai_prompt = promptTemplate.substring(
|
||||||
promptIndex + promptMarker.length
|
promptIndex + promptMarker.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return the split parts
|
// Return the split parts
|
||||||
@ -193,7 +193,7 @@ function promptTemplateConverter(promptTemplate: string): PromptTemplate {
|
|||||||
const promptIndex = promptTemplate.indexOf(promptMarker);
|
const promptIndex = promptTemplate.indexOf(promptMarker);
|
||||||
const user_prompt = promptTemplate.substring(0, promptIndex);
|
const user_prompt = promptTemplate.substring(0, promptIndex);
|
||||||
const ai_prompt = promptTemplate.substring(
|
const ai_prompt = promptTemplate.substring(
|
||||||
promptIndex + promptMarker.length
|
promptIndex + promptMarker.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return the split parts
|
// Return the split parts
|
||||||
@ -225,14 +225,14 @@ function loadLLMModel(settings: any): Promise<Response> {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
log(
|
log(
|
||||||
`[NITRO]::Debug: Load model success with response ${JSON.stringify(
|
`[NITRO]::Debug: Load model success with response ${JSON.stringify(
|
||||||
res
|
res,
|
||||||
)}`
|
)}`,
|
||||||
);
|
);
|
||||||
return Promise.resolve(res);
|
return Promise.resolve(res);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
log(`[NITRO]::Error: Load model failed with error ${err}`);
|
log(`[NITRO]::Error: Load model failed with error ${err}`);
|
||||||
return Promise.reject();
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,8 +254,8 @@ async function validateModelStatus(): Promise<void> {
|
|||||||
retryDelay: 500,
|
retryDelay: 500,
|
||||||
}).then(async (res: Response) => {
|
}).then(async (res: Response) => {
|
||||||
log(
|
log(
|
||||||
`[NITRO]::Debug: Validate model state success with response ${JSON.stringify(
|
`[NITRO]::Debug: Validate model state with response ${JSON.stringify(
|
||||||
res
|
res.status
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
// If the response is OK, check model_loaded status.
|
// If the response is OK, check model_loaded status.
|
||||||
@ -264,9 +264,19 @@ async function validateModelStatus(): Promise<void> {
|
|||||||
// If the model is loaded, return an empty object.
|
// If the model is loaded, return an empty object.
|
||||||
// Otherwise, return an object with an error message.
|
// Otherwise, return an object with an error message.
|
||||||
if (body.model_loaded) {
|
if (body.model_loaded) {
|
||||||
|
log(
|
||||||
|
`[NITRO]::Debug: Validate model state success with response ${JSON.stringify(
|
||||||
|
body
|
||||||
|
)}`
|
||||||
|
);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log(
|
||||||
|
`[NITRO]::Debug: Validate model state failed with response ${JSON.stringify(
|
||||||
|
res.statusText
|
||||||
|
)}`
|
||||||
|
);
|
||||||
return Promise.reject("Validate model status failed");
|
return Promise.reject("Validate model status failed");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -307,7 +317,7 @@ function spawnNitroProcess(): Promise<any> {
|
|||||||
const args: string[] = ["1", LOCAL_HOST, PORT.toString()];
|
const args: string[] = ["1", LOCAL_HOST, PORT.toString()];
|
||||||
// Execute the binary
|
// Execute the binary
|
||||||
log(
|
log(
|
||||||
`[NITRO]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
`[NITRO]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`,
|
||||||
);
|
);
|
||||||
subprocess = spawn(
|
subprocess = spawn(
|
||||||
executableOptions.executablePath,
|
executableOptions.executablePath,
|
||||||
@ -318,7 +328,7 @@ function spawnNitroProcess(): Promise<any> {
|
|||||||
...process.env,
|
...process.env,
|
||||||
CUDA_VISIBLE_DEVICES: executableOptions.cudaVisibleDevices,
|
CUDA_VISIBLE_DEVICES: executableOptions.cudaVisibleDevices,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle subprocess output
|
// Handle subprocess output
|
||||||
|
|||||||
@ -13,9 +13,15 @@ import {
|
|||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { activeModelAtom, stateModelAtom } from '@/hooks/useActiveModel'
|
import {
|
||||||
|
activeModelAtom,
|
||||||
|
loadModelErrorAtom,
|
||||||
|
stateModelAtom,
|
||||||
|
} from '@/hooks/useActiveModel'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
|
|
||||||
|
import { queuedMessageAtom } from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
import { toaster } from '../Toast'
|
import { toaster } from '../Toast'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
@ -26,6 +32,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
updateThreadWaitingForResponseAtom,
|
updateThreadWaitingForResponseAtom,
|
||||||
threadsAtom,
|
threadsAtom,
|
||||||
|
isGeneratingResponseAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export default function EventHandler({ children }: { children: ReactNode }) {
|
export default function EventHandler({ children }: { children: ReactNode }) {
|
||||||
@ -34,11 +41,14 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const setActiveModel = useSetAtom(activeModelAtom)
|
const setActiveModel = useSetAtom(activeModelAtom)
|
||||||
const setStateModel = useSetAtom(stateModelAtom)
|
const setStateModel = useSetAtom(stateModelAtom)
|
||||||
|
const setQueuedMessage = useSetAtom(queuedMessageAtom)
|
||||||
|
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
||||||
|
|
||||||
const updateThreadWaiting = useSetAtom(updateThreadWaitingForResponseAtom)
|
const updateThreadWaiting = useSetAtom(updateThreadWaitingForResponseAtom)
|
||||||
const threads = useAtomValue(threadsAtom)
|
const threads = useAtomValue(threadsAtom)
|
||||||
const modelsRef = useRef(downloadedModels)
|
const modelsRef = useRef(downloadedModels)
|
||||||
const threadsRef = useRef(threads)
|
const threadsRef = useRef(threads)
|
||||||
|
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
threadsRef.current = threads
|
threadsRef.current = threads
|
||||||
@ -51,8 +61,9 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
const onNewMessageResponse = useCallback(
|
const onNewMessageResponse = useCallback(
|
||||||
(message: ThreadMessage) => {
|
(message: ThreadMessage) => {
|
||||||
addNewMessage(message)
|
addNewMessage(message)
|
||||||
|
setIsGeneratingResponse(false)
|
||||||
},
|
},
|
||||||
[addNewMessage]
|
[addNewMessage, setIsGeneratingResponse]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onModelReady = useCallback(
|
const onModelReady = useCallback(
|
||||||
@ -83,13 +94,15 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
(res: any) => {
|
(res: any) => {
|
||||||
const errorMessage = `${res.error}`
|
const errorMessage = `${res.error}`
|
||||||
console.error('Failed to load model: ' + errorMessage)
|
console.error('Failed to load model: ' + errorMessage)
|
||||||
|
setLoadModelError(errorMessage)
|
||||||
setStateModel(() => ({
|
setStateModel(() => ({
|
||||||
state: 'start',
|
state: 'start',
|
||||||
loading: false,
|
loading: false,
|
||||||
model: res.modelId,
|
model: res.modelId,
|
||||||
}))
|
}))
|
||||||
|
setQueuedMessage(false)
|
||||||
},
|
},
|
||||||
[setStateModel]
|
[setStateModel, setQueuedMessage, setLoadModelError]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onMessageResponseUpdate = useCallback(
|
const onMessageResponseUpdate = useCallback(
|
||||||
@ -108,6 +121,8 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
// Mark the thread as not waiting for response
|
// Mark the thread as not waiting for response
|
||||||
updateThreadWaiting(message.thread_id, false)
|
updateThreadWaiting(message.thread_id, false)
|
||||||
|
|
||||||
|
setIsGeneratingResponse(false)
|
||||||
|
|
||||||
const thread = threadsRef.current?.find((e) => e.id == message.thread_id)
|
const thread = threadsRef.current?.find((e) => e.id == message.thread_id)
|
||||||
if (thread) {
|
if (thread) {
|
||||||
const messageContent = message.content[0]?.text.value ?? ''
|
const messageContent = message.content[0]?.text.value ?? ''
|
||||||
|
|||||||
@ -2,5 +2,6 @@ import { atom } from 'jotai'
|
|||||||
|
|
||||||
export const totalRamAtom = atom<number>(0)
|
export const totalRamAtom = atom<number>(0)
|
||||||
export const usedRamAtom = atom<number>(0)
|
export const usedRamAtom = atom<number>(0)
|
||||||
|
export const availableRamAtom = atom<number>(0)
|
||||||
|
|
||||||
export const cpuUsageAtom = atom<number>(0)
|
export const cpuUsageAtom = atom<number>(0)
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export const setActiveThreadIdAtom = atom(
|
|||||||
|
|
||||||
export const waitingToSendMessage = atom<boolean | undefined>(undefined)
|
export const waitingToSendMessage = atom<boolean | undefined>(undefined)
|
||||||
|
|
||||||
|
export const isGeneratingResponseAtom = atom<boolean | undefined>(undefined)
|
||||||
/**
|
/**
|
||||||
* Stores all thread states for the current user
|
* Stores all thread states for the current user
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { events, Model, ModelEvent } from '@janhq/core'
|
import { events, Model, ModelEvent } from '@janhq/core'
|
||||||
import { atom, useAtom, useAtomValue } from 'jotai'
|
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { toaster } from '@/containers/Toast'
|
import { toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ import { LAST_USED_MODEL_ID } from './useRecommendedModel'
|
|||||||
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
import { activeThreadAtom } 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 stateModelAtom = atom({
|
export const stateModelAtom = atom({
|
||||||
state: 'start',
|
state: 'start',
|
||||||
@ -21,6 +22,7 @@ export function useActiveModel() {
|
|||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const [stateModel, setStateModel] = useAtom(stateModelAtom)
|
const [stateModel, setStateModel] = useAtom(stateModelAtom)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
|
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
||||||
|
|
||||||
const startModel = async (modelId: string) => {
|
const startModel = async (modelId: string) => {
|
||||||
if (
|
if (
|
||||||
@ -31,6 +33,7 @@ export function useActiveModel() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: incase we have multiple assistants, the configuration will be from assistant
|
// TODO: incase we have multiple assistants, the configuration will be from assistant
|
||||||
|
setLoadModelError(undefined)
|
||||||
|
|
||||||
setActiveModel(undefined)
|
setActiveModel(undefined)
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useSetAtom } from 'jotai'
|
|||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
import {
|
import {
|
||||||
|
availableRamAtom,
|
||||||
cpuUsageAtom,
|
cpuUsageAtom,
|
||||||
totalRamAtom,
|
totalRamAtom,
|
||||||
usedRamAtom,
|
usedRamAtom,
|
||||||
@ -16,6 +17,7 @@ export default function useGetSystemResources() {
|
|||||||
const [cpu, setCPU] = useState<number>(0)
|
const [cpu, setCPU] = useState<number>(0)
|
||||||
const setTotalRam = useSetAtom(totalRamAtom)
|
const setTotalRam = useSetAtom(totalRamAtom)
|
||||||
const setUsedRam = useSetAtom(usedRamAtom)
|
const setUsedRam = useSetAtom(usedRamAtom)
|
||||||
|
const setAvailableRam = useSetAtom(availableRamAtom)
|
||||||
const setCpuUsage = useSetAtom(cpuUsageAtom)
|
const setCpuUsage = useSetAtom(cpuUsageAtom)
|
||||||
|
|
||||||
const getSystemResources = async () => {
|
const getSystemResources = async () => {
|
||||||
@ -40,6 +42,10 @@ export default function useGetSystemResources() {
|
|||||||
setTotalRam(resourceInfor.mem.totalMemory)
|
setTotalRam(resourceInfor.mem.totalMemory)
|
||||||
|
|
||||||
setRam(Math.round(ram * 100))
|
setRam(Math.round(ram * 100))
|
||||||
|
if (resourceInfor.mem.totalMemory && resourceInfor.mem.usedMemory)
|
||||||
|
setAvailableRam(
|
||||||
|
resourceInfor.mem.totalMemory - resourceInfor.mem.usedMemory
|
||||||
|
)
|
||||||
setCPU(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
setCPU(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
||||||
setCpuUsage(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
setCpuUsage(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
import { useAtomValue } from 'jotai'
|
|
||||||
|
|
||||||
import { threadStatesAtom } from '@/helpers/atoms/Thread.atom'
|
|
||||||
|
|
||||||
export default function useInference() {
|
|
||||||
const threadStates = useAtomValue(threadStatesAtom)
|
|
||||||
|
|
||||||
const isGeneratingResponse = Object.values(threadStates).some(
|
|
||||||
(threadState) => threadState.waitingForResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
isGeneratingResponse,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -25,12 +25,10 @@ import { ulid } from 'ulid'
|
|||||||
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
||||||
import { currentPromptAtom, fileUploadAtom } from '@/containers/Providers/Jotai'
|
import { currentPromptAtom, fileUploadAtom } from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
import { toaster } from '@/containers/Toast'
|
|
||||||
|
|
||||||
import { getBase64 } from '@/utils/base64'
|
import { getBase64 } from '@/utils/base64'
|
||||||
import { toRuntimeParams, toSettingParams } from '@/utils/modelParam'
|
import { toRuntimeParams, toSettingParams } from '@/utils/modelParam'
|
||||||
|
|
||||||
import { useActiveModel } from './useActiveModel'
|
import { loadModelErrorAtom, useActiveModel } from './useActiveModel'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
import {
|
import {
|
||||||
@ -59,9 +57,11 @@ export default function useSendChatMessage() {
|
|||||||
const { activeModel } = useActiveModel()
|
const { activeModel } = useActiveModel()
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
const { startModel } = useActiveModel()
|
const { startModel } = useActiveModel()
|
||||||
const setQueuedMessage = useSetAtom(queuedMessageAtom)
|
const [queuedMessage, setQueuedMessage] = useAtom(queuedMessageAtom)
|
||||||
|
const loadModelFailed = useAtomValue(loadModelErrorAtom)
|
||||||
|
|
||||||
const modelRef = useRef<Model | undefined>()
|
const modelRef = useRef<Model | undefined>()
|
||||||
|
const loadModelFailedRef = useRef<string | undefined>()
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
||||||
|
|
||||||
@ -73,6 +73,10 @@ export default function useSendChatMessage() {
|
|||||||
modelRef.current = activeModel
|
modelRef.current = activeModel
|
||||||
}, [activeModel])
|
}, [activeModel])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadModelFailedRef.current = loadModelFailed
|
||||||
|
}, [loadModelFailed])
|
||||||
|
|
||||||
const resendChatMessage = async (currentMessage: ThreadMessage) => {
|
const resendChatMessage = async (currentMessage: ThreadMessage) => {
|
||||||
if (!activeThread) {
|
if (!activeThread) {
|
||||||
console.error('No active thread')
|
console.error('No active thread')
|
||||||
@ -121,21 +125,6 @@ export default function useSendChatMessage() {
|
|||||||
events.emit(MessageEvent.OnMessageSent, messageRequest)
|
events.emit(MessageEvent.OnMessageSent, messageRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor @louis
|
|
||||||
const waitForModelStarting = async (modelId: string) => {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
setTimeout(async () => {
|
|
||||||
if (modelRef.current?.id !== modelId) {
|
|
||||||
console.debug('waiting for model to start')
|
|
||||||
await waitForModelStarting(modelId)
|
|
||||||
resolve()
|
|
||||||
} else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}, 200)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendChatMessage = async (message: string) => {
|
const sendChatMessage = async (message: string) => {
|
||||||
if (!message || message.trim().length === 0) return
|
if (!message || message.trim().length === 0) return
|
||||||
|
|
||||||
@ -304,6 +293,19 @@ export default function useSendChatMessage() {
|
|||||||
setEngineParamsUpdate(false)
|
setEngineParamsUpdate(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const waitForModelStarting = async (modelId: string) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (modelRef.current?.id !== modelId && !loadModelFailedRef.current) {
|
||||||
|
await waitForModelStarting(modelId)
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
resendChatMessage,
|
resendChatMessage,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
|||||||
import {
|
import {
|
||||||
ModelParams,
|
ModelParams,
|
||||||
getActiveThreadIdAtom,
|
getActiveThreadIdAtom,
|
||||||
|
isGeneratingResponseAtom,
|
||||||
setActiveThreadIdAtom,
|
setActiveThreadIdAtom,
|
||||||
setThreadModelParamsAtom,
|
setThreadModelParamsAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
@ -22,6 +23,7 @@ export default function useSetActiveThread() {
|
|||||||
const setActiveThreadId = useSetAtom(setActiveThreadIdAtom)
|
const setActiveThreadId = useSetAtom(setActiveThreadIdAtom)
|
||||||
const setThreadMessage = useSetAtom(setConvoMessagesAtom)
|
const setThreadMessage = useSetAtom(setConvoMessagesAtom)
|
||||||
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
||||||
|
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
||||||
|
|
||||||
const setActiveThread = async (thread: Thread) => {
|
const setActiveThread = async (thread: Thread) => {
|
||||||
if (activeThreadId === thread.id) {
|
if (activeThreadId === thread.id) {
|
||||||
@ -29,6 +31,7 @@ export default function useSetActiveThread() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsGeneratingResponse(false)
|
||||||
events.emit(InferenceEvent.OnInferenceStopped, thread.id)
|
events.emit(InferenceEvent.OnInferenceStopped, thread.id)
|
||||||
|
|
||||||
// load the corresponding messages
|
// load the corresponding messages
|
||||||
|
|||||||
@ -8,14 +8,11 @@ import { useAtomValue } from 'jotai'
|
|||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
import GenerateResponse from '@/containers/Loader/GenerateResponse'
|
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { activeModelAtom } from '@/hooks/useActiveModel'
|
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
|
|
||||||
import useInference from '@/hooks/useInference'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import ChatItem from '../ChatItem'
|
import ChatItem from '../ChatItem'
|
||||||
@ -26,10 +23,9 @@ import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
|||||||
|
|
||||||
const ChatBody: React.FC = () => {
|
const ChatBody: React.FC = () => {
|
||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
const activeModel = useAtomValue(activeModelAtom)
|
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const { isGeneratingResponse } = useInference()
|
const loadModelError = useAtomValue(loadModelErrorAtom)
|
||||||
|
|
||||||
if (downloadedModels.length === 0)
|
if (downloadedModels.length === 0)
|
||||||
return (
|
return (
|
||||||
@ -90,15 +86,14 @@ const ChatBody: React.FC = () => {
|
|||||||
message.content.length > 0) && (
|
message.content.length > 0) && (
|
||||||
<ChatItem {...message} key={message.id} />
|
<ChatItem {...message} key={message.id} />
|
||||||
)}
|
)}
|
||||||
{(message.status === MessageStatus.Error ||
|
{!loadModelError &&
|
||||||
message.status === MessageStatus.Stopped) &&
|
(message.status === MessageStatus.Error ||
|
||||||
|
message.status === MessageStatus.Stopped) &&
|
||||||
index === messages.length - 1 && (
|
index === messages.length - 1 && (
|
||||||
<ErrorMessage message={message} />
|
<ErrorMessage message={message} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{activeModel && isGeneratingResponse && <GenerateResponse />}
|
|
||||||
</ScrollToBottom>
|
</ScrollToBottom>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
deleteMessageAtom,
|
deleteMessageAtom,
|
||||||
getCurrentChatMessagesAtom,
|
getCurrentChatMessagesAtom,
|
||||||
} from '@/helpers/atoms/ChatMessage.atom'
|
} from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
|
|
||||||
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
||||||
@ -25,8 +24,6 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
const thread = useAtomValue(activeThreadAtom)
|
const thread = useAtomValue(activeThreadAtom)
|
||||||
const deleteMessage = useSetAtom(deleteMessageAtom)
|
const deleteMessage = useSetAtom(deleteMessageAtom)
|
||||||
const { resendChatMessage } = useSendChatMessage()
|
const { resendChatMessage } = useSendChatMessage()
|
||||||
const { activeModel } = useActiveModel()
|
|
||||||
const totalRam = useAtomValue(totalRamAtom)
|
|
||||||
|
|
||||||
const regenerateMessage = async () => {
|
const regenerateMessage = async () => {
|
||||||
const lastMessageIndex = messages.length - 1
|
const lastMessageIndex = messages.length - 1
|
||||||
@ -70,33 +67,26 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
{message.status === MessageStatus.Error && (
|
{message.status === MessageStatus.Error && (
|
||||||
<div key={message.id} className="mt-10 flex flex-col items-center">
|
<div key={message.id} className="mt-10 flex flex-col items-center">
|
||||||
<span className="mb-3 text-center text-sm font-medium text-gray-500">
|
<span className="mb-3 text-center text-sm font-medium text-gray-500">
|
||||||
{Number(activeModel?.metadata.size) > totalRam ? (
|
<>
|
||||||
<>
|
<p>Apologies, something's amiss!</p>
|
||||||
Oops! Model size exceeds available RAM. Consider selecting a
|
Jan's in beta. Find troubleshooting guides{' '}
|
||||||
smaller model or upgrading your RAM for smoother performance.
|
<a
|
||||||
</>
|
href="https://jan.ai/guides/troubleshooting"
|
||||||
) : (
|
target="_blank"
|
||||||
<>
|
className="text-blue-600 hover:underline dark:text-blue-300"
|
||||||
<p>Apologies, something's amiss!</p>
|
>
|
||||||
Jan's in beta. Find troubleshooting guides{' '}
|
here
|
||||||
<a
|
</a>{' '}
|
||||||
href="https://jan.ai/guides/troubleshooting"
|
or reach out to us on{' '}
|
||||||
target="_blank"
|
<a
|
||||||
className="text-blue-600 hover:underline dark:text-blue-300"
|
href="https://discord.gg/AsJ8krTT3N"
|
||||||
>
|
target="_blank"
|
||||||
here
|
className="text-blue-600 hover:underline dark:text-blue-300"
|
||||||
</a>{' '}
|
>
|
||||||
or reach out to us on{' '}
|
Discord
|
||||||
<a
|
</a>{' '}
|
||||||
href="https://discord.gg/AsJ8krTT3N"
|
for assistance.
|
||||||
target="_blank"
|
</>
|
||||||
className="text-blue-600 hover:underline dark:text-blue-300"
|
|
||||||
>
|
|
||||||
Discord
|
|
||||||
</a>{' '}
|
|
||||||
for assistance.
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
48
web/screens/Chat/LoadModelErrorMessage/index.tsx
Normal file
48
web/screens/Chat/LoadModelErrorMessage/index.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { MessageStatus, ThreadMessage } from '@janhq/core'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
|
||||||
|
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
|
||||||
|
|
||||||
|
const LoadModelErrorMessage = () => {
|
||||||
|
const { activeModel } = useActiveModel()
|
||||||
|
const availableRam = useAtomValue(totalRamAtom)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mt-10 flex flex-col items-center">
|
||||||
|
<span className="mb-3 text-center text-sm font-medium text-gray-500">
|
||||||
|
{Number(activeModel?.metadata.size) > availableRam ? (
|
||||||
|
<>
|
||||||
|
Oops! Model size exceeds available RAM. Consider selecting a
|
||||||
|
smaller model or upgrading your RAM for smoother performance.
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>Apologies, something's amiss!</p>
|
||||||
|
Jan's in beta. Find troubleshooting guides{' '}
|
||||||
|
<a
|
||||||
|
href="https://jan.ai/guides/troubleshooting"
|
||||||
|
target="_blank"
|
||||||
|
className="text-blue-600 hover:underline dark:text-blue-300"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>{' '}
|
||||||
|
or reach out to us on{' '}
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/AsJ8krTT3N"
|
||||||
|
target="_blank"
|
||||||
|
className="text-blue-600 hover:underline dark:text-blue-300"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>{' '}
|
||||||
|
for assistance.
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default LoadModelErrorMessage
|
||||||
@ -9,6 +9,7 @@ import { UploadCloudIcon } from 'lucide-react'
|
|||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import GenerateResponse from '@/containers/Loader/GenerateResponse'
|
||||||
import ModelReload from '@/containers/Loader/ModelReload'
|
import ModelReload from '@/containers/Loader/ModelReload'
|
||||||
import ModelStart from '@/containers/Loader/ModelStart'
|
import ModelStart from '@/containers/Loader/ModelStart'
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import { snackbar } from '@/containers/Toast'
|
|||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
||||||
|
|
||||||
|
import { activeModelAtom, loadModelErrorAtom } from '@/hooks/useActiveModel'
|
||||||
import { queuedMessageAtom, reloadModelAtom } from '@/hooks/useSendChatMessage'
|
import { queuedMessageAtom, reloadModelAtom } from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
import ChatBody from '@/screens/Chat/ChatBody'
|
import ChatBody from '@/screens/Chat/ChatBody'
|
||||||
@ -26,12 +28,14 @@ import ChatBody from '@/screens/Chat/ChatBody'
|
|||||||
import ThreadList from '@/screens/Chat/ThreadList'
|
import ThreadList from '@/screens/Chat/ThreadList'
|
||||||
|
|
||||||
import ChatInput from './ChatInput'
|
import ChatInput from './ChatInput'
|
||||||
|
import LoadModelErrorMessage from './LoadModelErrorMessage'
|
||||||
import RequestDownloadModel from './RequestDownloadModel'
|
import RequestDownloadModel from './RequestDownloadModel'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
engineParamsUpdateAtom,
|
engineParamsUpdateAtom,
|
||||||
|
isGeneratingResponseAtom,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const renderError = (code: string) => {
|
const renderError = (code: string) => {
|
||||||
@ -63,6 +67,11 @@ const ChatScreen: React.FC = () => {
|
|||||||
const setFileUpload = useSetAtom(fileUploadAtom)
|
const setFileUpload = useSetAtom(fileUploadAtom)
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
const { experimentalFeature } = useContext(FeatureToggleContext)
|
||||||
|
|
||||||
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
|
|
||||||
|
const isGeneratingResponse = useAtomValue(isGeneratingResponseAtom)
|
||||||
|
const loadModelError = useAtomValue(loadModelErrorAtom)
|
||||||
|
|
||||||
const { getRootProps, isDragReject } = useDropzone({
|
const { getRootProps, isDragReject } = useDropzone({
|
||||||
noClick: true,
|
noClick: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
@ -202,6 +211,9 @@ const ChatScreen: React.FC = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{activeModel && isGeneratingResponse && <GenerateResponse />}
|
||||||
|
{loadModelError && <LoadModelErrorMessage />}
|
||||||
<ChatInput />
|
<ChatInput />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user