diff --git a/web/containers/Layout/RibbonPanel/index.tsx b/web/containers/Layout/RibbonPanel/index.tsx index b9b1434ae4..41ceea8e32 100644 --- a/web/containers/Layout/RibbonPanel/index.tsx +++ b/web/containers/Layout/RibbonPanel/index.tsx @@ -12,9 +12,12 @@ import { twMerge } from 'tailwind-merge' import { MainViewState } from '@/constants/screens' +import { localEngines } from '@/utils/modelEngine' + import { mainViewStateAtom, showLeftPanelAtom } from '@/helpers/atoms/App.atom' import { editMessageAtom } from '@/helpers/atoms/ChatMessage.atom' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { reduceTransparentAtom, selectedSettingAtom, @@ -28,6 +31,7 @@ export default function RibbonPanel() { const matches = useMediaQuery('(max-width: 880px)') const reduceTransparent = useAtomValue(reduceTransparentAtom) const setSelectedSetting = useSetAtom(selectedSettingAtom) + const downloadedModels = useAtomValue(downloadedModelsAtom) const onMenuClick = (state: MainViewState) => { if (mainViewState === state) return @@ -37,6 +41,10 @@ export default function RibbonPanel() { setEditMessage('') } + const isDownloadALocalModel = downloadedModels.some((x) => + localEngines.includes(x.engine) + ) + const RibbonNavMenus = [ { name: 'Thread', @@ -77,7 +85,10 @@ export default function RibbonPanel() { 'border-none', !showLeftPanel && !reduceTransparent && 'border-none', matches && !reduceTransparent && 'border-none', - reduceTransparent && ' bg-[hsla(var(--ribbon-panel-bg))]' + reduceTransparent && ' bg-[hsla(var(--ribbon-panel-bg))]', + mainViewState === MainViewState.Thread && + !isDownloadALocalModel && + 'border-none' )} > {RibbonNavMenus.filter((menu) => !!menu).map((menu, i) => { diff --git a/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/index.tsx index 77913c991b..2b179e7447 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatBody/EmptyModel/index.tsx @@ -1,32 +1,317 @@ -import { memo } from 'react' +import React, { Fragment, useState } from 'react' -import { Button } from '@janhq/joi' -import { useSetAtom } from 'jotai' +import Image from 'next/image' + +import { InferenceEngine } from '@janhq/core/.' +import { Button, Input, Progress, ScrollArea } from '@janhq/joi' + +import { useAtomValue, useSetAtom } from 'jotai' +import { SearchIcon, DownloadCloudIcon } from 'lucide-react' + +import { twMerge } from 'tailwind-merge' import LogoMark from '@/containers/Brand/Logo/Mark' +import CenterPanelContainer from '@/containers/CenterPanelContainer' + +import ProgressCircle from '@/containers/Loader/ProgressCircle' + +import ModelLabel from '@/containers/ModelLabel' import { MainViewState } from '@/constants/screens' +import useDownloadModel from '@/hooks/useDownloadModel' + +import { modelDownloadStateAtom } from '@/hooks/useDownloadState' + +import { formatDownloadPercentage, toGibibytes } from '@/utils/converter' +import { + getLogoEngine, + getTitleByEngine, + localEngines, +} from '@/utils/modelEngine' + import { mainViewStateAtom } from '@/helpers/atoms/App.atom' +import { + configuredModelsAtom, + getDownloadingModelAtom, +} from '@/helpers/atoms/Model.atom' +import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom' + +type Props = { + extensionHasSettings: { + name?: string + setting: string + apiKey: string + provider: string + }[] +} + +const OnDeviceStarterScreen = ({ extensionHasSettings }: Props) => { + const [searchValue, setSearchValue] = useState('') + const downloadingModels = useAtomValue(getDownloadingModelAtom) + const { downloadModel } = useDownloadModel() + const downloadStates = useAtomValue(modelDownloadStateAtom) + const setSelectedSetting = useSetAtom(selectedSettingAtom) -const EmptyModel = () => { + const configuredModels = useAtomValue(configuredModelsAtom) const setMainViewState = useSetAtom(mainViewStateAtom) + const featuredModel = configuredModels.filter((x) => + x.metadata.tags.includes('Featured') + ) + + const remoteModel = configuredModels.filter( + (x) => !localEngines.includes(x.engine) + ) + + const filteredModels = configuredModels.filter((model) => { + return ( + localEngines.includes(model.engine) && + model.name.toLowerCase().includes(searchValue.toLowerCase()) + ) + }) + + const remoteModelEngine = remoteModel.map((x) => x.engine) + const groupByEngine = remoteModelEngine.filter(function (item, index) { + if (remoteModelEngine.indexOf(item) === index) return item + }) + + const itemsPerRow = 5 + + const getRows = (array: string[], itemsPerRow: number) => { + const rows = [] + for (let i = 0; i < array.length; i += itemsPerRow) { + rows.push(array.slice(i, i + itemsPerRow)) + } + return rows + } + + const rows = getRows(groupByEngine, itemsPerRow) + + const [visibleRows, setVisibleRows] = useState(1) + return ( -
- You need to download your first model -
- -+ No Result Found +
++ {model.name} +
+{ + setMainViewState(MainViewState.Hub) + }} + > + See All +
++ {featModel.metadata.author} +
++ {getTitleByEngine( + remoteEngine as InferenceEngine + )} +
+