diff --git a/plugins/inference-plugin/src/index.ts b/plugins/inference-plugin/src/index.ts index 5841b7abc..8cabf0343 100644 --- a/plugins/inference-plugin/src/index.ts +++ b/plugins/inference-plugin/src/index.ts @@ -7,7 +7,6 @@ */ import { - ChatCompletionMessage, ChatCompletionRole, EventName, MessageRequest, diff --git a/plugins/inference-plugin/src/module.ts b/plugins/inference-plugin/src/module.ts index 613ad9fca..a9e60e4cd 100644 --- a/plugins/inference-plugin/src/module.ts +++ b/plugins/inference-plugin/src/module.ts @@ -109,10 +109,10 @@ async function validateModelStatus(): Promise { return { error: undefined }; } } - return { error: "Model is not loaded successfully" }; + return { error: "Model loading failed" }; }) .catch((err) => { - return { error: `Model is not loaded successfully. ${err.message}` }; + return { error: `Model loading failed. ${err.message}` }; }); } diff --git a/plugins/model-plugin/src/@types/schema.ts b/plugins/model-plugin/src/@types/schema.ts new file mode 100644 index 000000000..1d3c3a7d1 --- /dev/null +++ b/plugins/model-plugin/src/@types/schema.ts @@ -0,0 +1,21 @@ +interface Version { + name: string + quantMethod: string + bits: number + size: number + maxRamRequired: number + usecase: string + downloadLink: string +} +interface ModelSchema { + id: string + name: string + shortDescription: string + avatarUrl: string + longDescription: string + author: string + version: string + modelUrl: string + tags: string[] + versions: Version[] +} diff --git a/plugins/model-plugin/src/helpers/modelParser.ts b/plugins/model-plugin/src/helpers/modelParser.ts index 826a2afba..242dc7e63 100644 --- a/plugins/model-plugin/src/helpers/modelParser.ts +++ b/plugins/model-plugin/src/helpers/modelParser.ts @@ -1,8 +1,9 @@ -export const parseToModel = (model) => { +import { ModelCatalog } from '@janhq/core' + +export function parseToModel(schema: ModelSchema): ModelCatalog { const modelVersions = [] - model.versions.forEach((v) => { + schema.versions.forEach((v) => { const version = { - id: `${model.author}-${v.name}`, name: v.name, quantMethod: v.quantMethod, bits: v.bits, @@ -10,28 +11,22 @@ export const parseToModel = (model) => { maxRamRequired: v.maxRamRequired, usecase: v.usecase, downloadLink: v.downloadLink, - productId: model.id, } modelVersions.push(version) }) - const product = { - id: model.id, - name: model.name, - shortDescription: model.shortDescription, - avatarUrl: model.avatarUrl, - author: model.author, - version: model.version, - modelUrl: model.modelUrl, - nsfw: model.nsfw, - tags: model.tags, - greeting: model.defaultGreeting, - type: model.type, - createdAt: model.createdAt, - longDescription: model.longDescription, - status: 'Downloadable', + const model: ModelCatalog = { + id: schema.id, + name: schema.name, + shortDescription: schema.shortDescription, + avatarUrl: schema.avatarUrl, + author: schema.author, + version: schema.version, + modelUrl: schema.modelUrl, + tags: schema.tags, + longDescription: schema.longDescription, releaseDate: 0, availableVersions: modelVersions, } - return product + return model } diff --git a/web/containers/Layout/TopBar/index.tsx b/web/containers/Layout/TopBar/index.tsx index e5429297e..5ab4ebc84 100644 --- a/web/containers/Layout/TopBar/index.tsx +++ b/web/containers/Layout/TopBar/index.tsx @@ -10,7 +10,9 @@ const TopBar = () => {
- {viewStateName} + + {viewStateName.replace(/([A-Z])/g, ' $1').trim()} +
{/* Command without trigger interface */} diff --git a/web/helpers/atoms/Modal.atom.ts b/web/helpers/atoms/Modal.atom.ts deleted file mode 100644 index 5f13e1f9e..000000000 --- a/web/helpers/atoms/Modal.atom.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { atom } from 'jotai' - -export const showConfirmDeleteConversationModalAtom = atom(false) -export const showConfirmSignOutModalAtom = atom(false) -export const showConfirmDeleteModalAtom = atom(false) -export const showingAdvancedPromptAtom = atom(false) -export const showingProductDetailAtom = atom(false) -export const showingMobilePaneAtom = atom(false) -export const showingBotListModalAtom = atom(false) -export const showingCancelDownloadModalAtom = atom(false) - -export const showingModalNoActiveModel = atom(false) diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 3496f8396..57e3a0118 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -33,7 +33,10 @@ export function useActiveModel() { const model = downloadedModels.find((e) => e.id === modelId) if (!model) { - alert(`Model ${modelId} not found! Please re-download the model first.`) + toaster({ + title: `Model ${modelId} not found!`, + description: `Please download the model first.`, + }) setStateModel(() => ({ state: 'start', loading: false, diff --git a/web/hooks/useCreateConversation.ts b/web/hooks/useCreateConversation.ts index 8984c0bfc..5f42ee7ce 100644 --- a/web/hooks/useCreateConversation.ts +++ b/web/hooks/useCreateConversation.ts @@ -20,11 +20,10 @@ export const useCreateConversation = () => { const addNewConvoState = useSetAtom(addNewConversationStateAtom) const requestCreateConvo = async (model: Model) => { - const summary = model.name const mappedConvo: Thread = { id: generateConversationId(), modelId: model.id, - summary, + summary: model.name, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), messages: [], @@ -35,7 +34,7 @@ export const useCreateConversation = () => { waitingForResponse: false, }) - pluginManager + await pluginManager .get(PluginType.Conversational) ?.saveConversation(mappedConvo) setUserConversations([mappedConvo, ...userConversations]) diff --git a/web/hooks/useDeleteConversation.ts b/web/hooks/useDeleteConversation.ts index 459b527b5..4b2aa9859 100644 --- a/web/hooks/useDeleteConversation.ts +++ b/web/hooks/useDeleteConversation.ts @@ -16,10 +16,6 @@ import { getActiveConvoIdAtom, setActiveConvoIdAtom, } from '@/helpers/atoms/Conversation.atom' -import { - showingProductDetailAtom, - showingAdvancedPromptAtom, -} from '@/helpers/atoms/Modal.atom' export default function useDeleteConversation() { const { activeModel } = useActiveModel() @@ -27,8 +23,6 @@ export default function useDeleteConversation() { userConversationsAtom ) const setCurrentPrompt = useSetAtom(currentPromptAtom) - const setShowingProductDetail = useSetAtom(showingProductDetailAtom) - const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom) const activeConvoId = useAtomValue(getActiveConvoIdAtom) const setActiveConvoId = useSetAtom(setActiveConvoIdAtom) @@ -45,18 +39,16 @@ export default function useDeleteConversation() { ) setUserConversations(currentConversations) deleteMessages(activeConvoId) + setCurrentPrompt('') toaster({ - title: 'Succes delete a chat', - description: `Delete chat with ${activeModel?.name} has been completed`, + title: 'Chat successfully deleted.', + description: `Chat with ${activeModel?.name} has been successfully deleted.`, }) if (currentConversations.length > 0) { setActiveConvoId(currentConversations[0].id) } else { setActiveConvoId(undefined) } - setCurrentPrompt('') - setShowingProductDetail(false) - setShowingAdvancedPrompt(false) } catch (err) { console.error(err) } diff --git a/web/hooks/useDeleteModel.ts b/web/hooks/useDeleteModel.ts index f59860719..c3b03c61a 100644 --- a/web/hooks/useDeleteModel.ts +++ b/web/hooks/useDeleteModel.ts @@ -20,8 +20,8 @@ export default function useDeleteModel() { // reload models setDownloadedModels(downloadedModels.filter((e) => e.id !== model.id)) toaster({ - title: 'Delete a Model', - description: `Model ${model.id} has been deleted.`, + title: 'Model Deletion Successful', + description: `The model ${model.id} has been successfully deleted.`, }) } diff --git a/web/hooks/useGetConfiguredModels.ts b/web/hooks/useGetConfiguredModels.ts index 308d6a2e9..59ae86399 100644 --- a/web/hooks/useGetConfiguredModels.ts +++ b/web/hooks/useGetConfiguredModels.ts @@ -20,14 +20,13 @@ export function useGetConfiguredModels() { const [models, setModels] = useState([]) async function getConfiguredModels(): Promise { - return ( - ((await pluginManager - .get(PluginType.Model) - ?.getConfiguredModels()) as ModelCatalog[]) ?? [] - ) + const models = await pluginManager + .get(PluginType.Model) + ?.getConfiguredModels() + return models ?? [] } - const fetchModels = async () => { + async function fetchModels() { setLoading(true) let models = await getConfiguredModels() if (process.env.NODE_ENV === 'development') { @@ -37,10 +36,8 @@ export function useGetConfiguredModels() { setModels(models) } - // TODO allow user for filter useEffect(() => { fetchModels() - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return { loading, models } diff --git a/web/hooks/useGetDownloadedModels.ts b/web/hooks/useGetDownloadedModels.ts index 42a695d58..d9621230f 100644 --- a/web/hooks/useGetDownloadedModels.ts +++ b/web/hooks/useGetDownloadedModels.ts @@ -12,11 +12,10 @@ export function useGetDownloadedModels() { const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelAtom) async function getDownloadedModels(): Promise { - const models = - ((await pluginManager - .get(PluginType.Model) - ?.getDownloadedModels()) as Model[]) ?? [] - return models + const models = await pluginManager + .get(PluginType.Model) + ?.getDownloadedModels() + return models ?? [] } useEffect(() => { diff --git a/web/hooks/useGetInputState.ts b/web/hooks/useGetInputState.ts index f7934ae2c..2ecd36cd7 100644 --- a/web/hooks/useGetInputState.ts +++ b/web/hooks/useGetInputState.ts @@ -10,15 +10,15 @@ import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom' export default function useGetInputState() { const [inputState, setInputState] = useState('loading') - const currentConvo = useAtomValue(currentConversationAtom) + const currentThread = useAtomValue(currentConversationAtom) const { activeModel } = useActiveModel() const { downloadedModels } = useGetDownloadedModels() const handleInputState = ( - convo: Thread | undefined, + thread: Thread | undefined, currentModel: Model | undefined ) => { - if (convo == null) return + if (thread == null) return if (currentModel == null) { setInputState('loading') return @@ -26,7 +26,7 @@ export default function useGetInputState() { // check if convo model id is in downloaded models const isModelAvailable = downloadedModels.some( - (model) => model.id === convo.modelId + (model) => model.id === thread.modelId ) if (!isModelAvailable) { @@ -35,7 +35,7 @@ export default function useGetInputState() { return } - if (convo.modelId !== currentModel.id) { + if (thread.modelId !== currentModel.id) { // in case convo model and active model is different, // ask user to init the required model setInputState('model-mismatch') @@ -46,11 +46,11 @@ export default function useGetInputState() { } useEffect(() => { - handleInputState(currentConvo, activeModel) + handleInputState(currentThread, activeModel) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - return { inputState, currentConvo } + return { inputState, currentThread } } type InputType = 'available' | 'loading' | 'model-mismatch' | 'model-not-found' diff --git a/web/hooks/useGetSystemResources.ts b/web/hooks/useGetSystemResources.ts index d7bae79c7..f61473148 100644 --- a/web/hooks/useGetSystemResources.ts +++ b/web/hooks/useGetSystemResources.ts @@ -34,14 +34,15 @@ export default function useGetSystemResources() { useEffect(() => { getSystemResources() - // Fetch interval - every 3s + // Fetch interval - every 5s + // TODO: Will we really need this? + // There is a possibility that this will be removed and replaced by the process event hook? const intervalId = setInterval(() => { getSystemResources() }, 5000) - // clean up + // clean up interval return () => clearInterval(intervalId) - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return { diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 676a75c2f..12f54abd0 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -16,6 +16,8 @@ import { ulid } from 'ulid' import { currentPromptAtom } from '@/containers/Providers/Jotai' +import { useActiveModel } from './useActiveModel' + import { addNewMessageAtom, getCurrentChatMessagesAtom, @@ -34,54 +36,51 @@ export default function useSendChatMessage() { const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom) const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom) const currentMessages = useAtomValue(getCurrentChatMessagesAtom) - - let timeout: NodeJS.Timeout | undefined = undefined + const { activeModel } = useActiveModel() function updateConvSummary(newMessage: MessageRequest) { - if (timeout) { - clearTimeout(timeout) - } - timeout = setTimeout(() => { - const conv = currentConvo - if ( - !currentConvo?.summary || + if ( + currentConvo && + newMessage.messages && + newMessage.messages.length > 2 && + (!currentConvo.summary || currentConvo.summary === '' || - currentConvo.summary.startsWith('Prompt:') - ) { - const summaryMsg: ChatCompletionMessage = { - role: ChatCompletionRole.User, - content: - 'summary this conversation in 5 words, the response should just include the summary', - } - // Request convo summary - setTimeout(async () => { - const result = await pluginManager - .get(PluginType.Inference) - ?.inferenceRequest({ - ...newMessage, - messages: newMessage.messages?.concat([summaryMsg]), - }) - - if ( - result?.message && - result.message.split(' ').length <= 10 && - conv?.id - ) { - const updatedConv = { - ...conv, - summary: result.message, - } - updateConversation(updatedConv) - pluginManager - .get(PluginType.Conversational) - ?.saveConversation({ - ...updatedConv, - messages: currentMessages, - }) - } - }, 1000) + currentConvo.summary === activeModel?.name) + ) { + const summaryMsg: ChatCompletionMessage = { + role: ChatCompletionRole.User, + content: + 'summary this conversation in a few words, the response should just include the summary', } - }, 100) + // Request convo summary + setTimeout(async () => { + const result = await pluginManager + .get(PluginType.Inference) + ?.inferenceRequest({ + ...newMessage, + messages: newMessage.messages?.slice(0, -1).concat([summaryMsg]), + }) + if ( + currentConvo && + currentConvo.id === newMessage.threadId && + result?.message && + result?.message?.trim().length > 0 && + result.message.split(' ').length <= 10 + ) { + const updatedConv = { + ...currentConvo, + summary: result.message, + } + updateConversation(updatedConv) + pluginManager + .get(PluginType.Conversational) + ?.saveConversation({ + ...updatedConv, + messages: currentMessages, + }) + } + }, 1000) + } } const sendChatMessage = async () => { @@ -123,21 +122,7 @@ export default function useSendChatMessage() { } addNewMessage(threadMessage) - // delay randomly from 50 - 100ms - // to prevent duplicate message id - const delay = Math.floor(Math.random() * 50) + 50 - await new Promise((resolve) => setTimeout(resolve, delay)) - events.emit(EventName.OnNewMessageRequest, messageRequest) - if (!currentConvo?.summary && currentConvo) { - const updatedConv: Thread = { - ...currentConvo, - summary: `Prompt: ${prompt}`, - } - - updateConversation(updatedConv) - } - updateConvSummary(messageRequest) } diff --git a/web/screens/ExploreModels/ExploreModelItem/index.tsx b/web/screens/ExploreModels/ExploreModelItem/index.tsx index 6682c6385..9cbbc248c 100644 --- a/web/screens/ExploreModels/ExploreModelItem/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItem/index.tsx @@ -58,10 +58,18 @@ const ExploreModelItem = forwardRef(({ model }, ref) => {
Compatibility
- + {usecase} - + {toGigabytes(maxRamRequired)} RAM required
diff --git a/web/screens/ExploreModels/ModelVersionItem/index.tsx b/web/screens/ExploreModels/ModelVersionItem/index.tsx index f3120543f..952f364e2 100644 --- a/web/screens/ExploreModels/ModelVersionItem/index.tsx +++ b/web/screens/ExploreModels/ModelVersionItem/index.tsx @@ -2,8 +2,8 @@ import React, { useMemo } from 'react' import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types' -import { Button } from '@janhq/uikit' -import { Badge } from '@janhq/uikit' +import { Button, Badge } from '@janhq/uikit' + import { atom, useAtomValue } from 'jotai' import ModalCancelDownload from '@/containers/ModalCancelDownload' @@ -73,16 +73,25 @@ const ModelVersionItem: React.FC = ({ model, modelVersion }) => { return (
- {modelVersion.name} + + {modelVersion.name} +
- + {usecase} - {`${toGigabytes( - maxRamRequired - )} RAM required`} + + {`${toGigabytes(maxRamRequired)} RAM required`} {toGigabytes(modelVersion.size)}
{downloadButton} diff --git a/web/services/cloudNativeService.ts b/web/services/cloudNativeService.ts index 40cd05c1f..55164751b 100644 --- a/web/services/cloudNativeService.ts +++ b/web/services/cloudNativeService.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { toast } from 'react-toastify' const API_BASE_PATH: string = '/api/v1' @@ -48,7 +47,7 @@ export async function fetchApi( method: pluginFunc, args: args, }), - headers: { 'Content-Type': 'application/json', 'Authorization': '' }, + headers: { contentType: 'application/json', Authorization: '' }, }) if (!response.ok) { diff --git a/web/services/eventsService.ts b/web/services/eventsService.ts index c43f4497e..8a518a442 100644 --- a/web/services/eventsService.ts +++ b/web/services/eventsService.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-types */ export class EventEmitter { private handlers: Map @@ -28,6 +27,7 @@ export class EventEmitter { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public emit(eventName: string, args: any): void { if (!this.handlers.has(eventName)) { return diff --git a/web/utils/dummy.ts b/web/utils/dummy.ts index 989393202..647de1937 100644 --- a/web/utils/dummy.ts +++ b/web/utils/dummy.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { ModelCatalog, ModelVersion } from '@janhq/core' +import { ModelCatalog } from '@janhq/core' export const dummyModel: ModelCatalog = { id: 'aladar/TinyLLama-v0-GGUF',