chore: remove hard coded recommendation models and use cortexso featured tags (#4741)
* chore: remove hard coded recommendation models and use cortexso featured tags * chore: polish model detail page * chore: fix test
This commit is contained in:
parent
916b28044d
commit
d7329c7719
File diff suppressed because it is too large
Load Diff
@ -451,7 +451,7 @@ export default class JanModelExtension extends ModelExtension {
|
|||||||
|
|
||||||
return this.queue.add(() =>
|
return this.queue.add(() =>
|
||||||
ky
|
ky
|
||||||
.get(`${API_URL}/v1/models/hub?author=cortexso`)
|
.get(`${API_URL}/v1/models/hub?author=cortexso&tag=cortex.cpp`)
|
||||||
.json<Data<string>>()
|
.json<Data<string>>()
|
||||||
.then((e) => {
|
.then((e) => {
|
||||||
e.data?.forEach((model) => {
|
e.data?.forEach((model) => {
|
||||||
|
|||||||
@ -35,14 +35,16 @@ import useDownloadModel from '@/hooks/useDownloadModel'
|
|||||||
import { modelDownloadStateAtom } from '@/hooks/useDownloadState'
|
import { modelDownloadStateAtom } from '@/hooks/useDownloadState'
|
||||||
import { useGetEngines } from '@/hooks/useEngineManagement'
|
import { useGetEngines } from '@/hooks/useEngineManagement'
|
||||||
|
|
||||||
import { useGetModelSources } from '@/hooks/useModelSource'
|
import {
|
||||||
|
useGetModelSources,
|
||||||
|
useGetFeaturedSources,
|
||||||
|
} from '@/hooks/useModelSource'
|
||||||
import useRecommendedModel from '@/hooks/useRecommendedModel'
|
import useRecommendedModel from '@/hooks/useRecommendedModel'
|
||||||
|
|
||||||
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
||||||
|
|
||||||
import { formatDownloadPercentage, toGigabytes } from '@/utils/converter'
|
import { formatDownloadPercentage, toGigabytes } from '@/utils/converter'
|
||||||
|
|
||||||
import { manualRecommendationModel } from '@/utils/model'
|
|
||||||
import { getLogoEngine, getTitleByEngine } from '@/utils/modelEngine'
|
import { getLogoEngine, getTitleByEngine } from '@/utils/modelEngine'
|
||||||
|
|
||||||
import { extractModelName } from '@/utils/modelSource'
|
import { extractModelName } from '@/utils/modelSource'
|
||||||
@ -93,6 +95,7 @@ const ModelDropdown = ({
|
|||||||
const [dropdownOptions, setDropdownOptions] = useState<HTMLDivElement | null>(
|
const [dropdownOptions, setDropdownOptions] = useState<HTMLDivElement | null>(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
const { sources: featuredModels } = useGetFeaturedSources()
|
||||||
|
|
||||||
const { engines } = useGetEngines()
|
const { engines } = useGetEngines()
|
||||||
|
|
||||||
@ -103,9 +106,6 @@ const ModelDropdown = ({
|
|||||||
const configuredModels = useAtomValue(configuredModelsAtom)
|
const configuredModels = useAtomValue(configuredModelsAtom)
|
||||||
const { stopModel } = useActiveModel()
|
const { stopModel } = useActiveModel()
|
||||||
|
|
||||||
const featuredModels = sources?.filter((x) =>
|
|
||||||
manualRecommendationModel.includes(x.id)
|
|
||||||
)
|
|
||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
|
|
||||||
const engineList = useMemo(
|
const engineList = useMemo(
|
||||||
|
|||||||
@ -36,6 +36,22 @@ export function useGetModelSources() {
|
|||||||
return { sources, error, mutate }
|
return { sources, error, mutate }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A Promise that resolves to featured model sources.
|
||||||
|
*/
|
||||||
|
export function useGetFeaturedSources() {
|
||||||
|
const { sources, error, mutate } = useGetModelSources()
|
||||||
|
|
||||||
|
return {
|
||||||
|
sources: sources?.filter((e) => e.metadata?.tags.includes('featured')),
|
||||||
|
error,
|
||||||
|
mutate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A Promise that resolves to model source mutation.
|
||||||
|
*/
|
||||||
export const useModelSourcesMutation = () => {
|
export const useModelSourcesMutation = () => {
|
||||||
const extension = useMemo(
|
const extension = useMemo(
|
||||||
() => extensionManager.get<ModelExtension>(ExtensionTypeEnum.Model),
|
() => extensionManager.get<ModelExtension>(ExtensionTypeEnum.Model),
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { useRefreshModelList } from '@/hooks/useEngineManagement'
|
|||||||
import { MarkdownTextMessage } from '@/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage'
|
import { MarkdownTextMessage } from '@/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage'
|
||||||
|
|
||||||
import { toGigabytes } from '@/utils/converter'
|
import { toGigabytes } from '@/utils/converter'
|
||||||
import { extractModelName } from '@/utils/modelSource'
|
import { extractModelName, removeYamlFrontMatter } from '@/utils/modelSource'
|
||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
import {
|
import {
|
||||||
@ -239,7 +239,7 @@ const ModelPage = ({ model, onGoBack }: Props) => {
|
|||||||
{/* README */}
|
{/* README */}
|
||||||
<div className="mt-8 flex w-full flex-col items-start justify-between sm:flex-row">
|
<div className="mt-8 flex w-full flex-col items-start justify-between sm:flex-row">
|
||||||
<MarkdownTextMessage
|
<MarkdownTextMessage
|
||||||
text={model.metadata?.description ?? ''}
|
text={removeYamlFrontMatter(model.metadata?.description ?? '')}
|
||||||
className="h-full w-full text-[hsla(var(--text-secondary))]"
|
className="h-full w-full text-[hsla(var(--text-secondary))]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -72,6 +72,7 @@ describe('OnDeviceStarterScreen', () => {
|
|||||||
error: null,
|
error: null,
|
||||||
mutate: jest.fn(),
|
mutate: jest.fn(),
|
||||||
})
|
})
|
||||||
|
jest.spyOn(source, 'useGetFeaturedSources').mockReturnValue([])
|
||||||
render(
|
render(
|
||||||
<Provider>
|
<Provider>
|
||||||
<OnboardingScreen isShowStarterScreen={true} />
|
<OnboardingScreen isShowStarterScreen={true} />
|
||||||
@ -88,6 +89,7 @@ describe('OnDeviceStarterScreen', () => {
|
|||||||
error: null,
|
error: null,
|
||||||
mutate: jest.fn(),
|
mutate: jest.fn(),
|
||||||
})
|
})
|
||||||
|
jest.spyOn(source, 'useGetFeaturedSources').mockReturnValue([])
|
||||||
render(
|
render(
|
||||||
<Provider>
|
<Provider>
|
||||||
<OnboardingScreen isShowStarterScreen={true} />
|
<OnboardingScreen isShowStarterScreen={true} />
|
||||||
@ -108,6 +110,7 @@ describe('OnDeviceStarterScreen', () => {
|
|||||||
error: null,
|
error: null,
|
||||||
mutate: jest.fn(),
|
mutate: jest.fn(),
|
||||||
})
|
})
|
||||||
|
jest.spyOn(source, 'useGetFeaturedSources').mockReturnValue([])
|
||||||
render(
|
render(
|
||||||
<Provider>
|
<Provider>
|
||||||
<OnboardingScreen isShowStarterScreen={true} />
|
<OnboardingScreen isShowStarterScreen={true} />
|
||||||
@ -126,31 +129,31 @@ describe('OnDeviceStarterScreen', () => {
|
|||||||
id: 'cortexso/deepseek-r1',
|
id: 'cortexso/deepseek-r1',
|
||||||
name: 'DeepSeek R1',
|
name: 'DeepSeek R1',
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: ['Featured'],
|
|
||||||
author: 'Test Author',
|
author: 'Test Author',
|
||||||
size: 3000000000,
|
size: 3000000000,
|
||||||
|
tags: ['featured'],
|
||||||
},
|
},
|
||||||
models: [
|
models: [
|
||||||
{
|
{
|
||||||
id: 'cortexso/deepseek-r1',
|
id: 'cortexso/deepseek-r1',
|
||||||
name: 'DeepSeek R1',
|
name: 'DeepSeek R1',
|
||||||
metadata: {
|
metadata: {},
|
||||||
tags: ['Featured'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'cortexso/llama3.2',
|
id: 'cortexso/llama3.2',
|
||||||
name: 'Llama 3.1',
|
name: 'Llama 3.1',
|
||||||
metadata: { tags: [], author: 'Test Author', size: 2000000000 },
|
metadata: {
|
||||||
|
author: 'Test Author',
|
||||||
|
size: 2000000000,
|
||||||
|
tags: ['featured'],
|
||||||
|
},
|
||||||
models: [
|
models: [
|
||||||
{
|
{
|
||||||
id: 'cortexso/deepseek-r1',
|
id: 'cortexso/deepseek-r1',
|
||||||
name: 'DeepSeek R1',
|
name: 'DeepSeek R1',
|
||||||
metadata: {
|
metadata: {},
|
||||||
tags: ['Featured'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -161,6 +164,13 @@ describe('OnDeviceStarterScreen', () => {
|
|||||||
error: null,
|
error: null,
|
||||||
mutate: jest.fn(),
|
mutate: jest.fn(),
|
||||||
})
|
})
|
||||||
|
jest
|
||||||
|
.spyOn(source, 'useGetFeaturedSources')
|
||||||
|
.mockReturnValue({
|
||||||
|
sources: mockConfiguredModels,
|
||||||
|
error: null,
|
||||||
|
mutate: jest.fn(),
|
||||||
|
})
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Provider>
|
<Provider>
|
||||||
@ -182,6 +192,10 @@ describe('OnDeviceStarterScreen', () => {
|
|||||||
{ id: 'remote-model-2', name: 'Remote Model 2', engine: 'anthropic' },
|
{ id: 'remote-model-2', name: 'Remote Model 2', engine: 'anthropic' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(source, 'useGetFeaturedSources')
|
||||||
|
.mockReturnValue(mockRemoteModels)
|
||||||
|
|
||||||
mockAtomValue.mockImplementation((atom) => {
|
mockAtomValue.mockImplementation((atom) => {
|
||||||
if (atom === jotai.atom([])) {
|
if (atom === jotai.atom([])) {
|
||||||
return mockRemoteModels
|
return mockRemoteModels
|
||||||
|
|||||||
@ -26,23 +26,19 @@ import { modelDownloadStateAtom } from '@/hooks/useDownloadState'
|
|||||||
|
|
||||||
import { useGetEngines } from '@/hooks/useEngineManagement'
|
import { useGetEngines } from '@/hooks/useEngineManagement'
|
||||||
|
|
||||||
import { useGetModelSources } from '@/hooks/useModelSource'
|
import {
|
||||||
|
useGetFeaturedSources,
|
||||||
|
useGetModelSources,
|
||||||
|
} from '@/hooks/useModelSource'
|
||||||
|
|
||||||
import { formatDownloadPercentage, toGigabytes } from '@/utils/converter'
|
import { formatDownloadPercentage, toGigabytes } from '@/utils/converter'
|
||||||
import { manualRecommendationModel } from '@/utils/model'
|
|
||||||
import {
|
import { getLogoEngine, getTitleByEngine } from '@/utils/modelEngine'
|
||||||
getLogoEngine,
|
|
||||||
getTitleByEngine,
|
|
||||||
isLocalEngine,
|
|
||||||
} from '@/utils/modelEngine'
|
|
||||||
|
|
||||||
import { extractModelName } from '@/utils/modelSource'
|
import { extractModelName } from '@/utils/modelSource'
|
||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
import {
|
import { getDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
||||||
configuredModelsAtom,
|
|
||||||
getDownloadingModelAtom,
|
|
||||||
} from '@/helpers/atoms/Model.atom'
|
|
||||||
import {
|
import {
|
||||||
selectedSettingAtom,
|
selectedSettingAtom,
|
||||||
showScrollBarAtom,
|
showScrollBarAtom,
|
||||||
@ -65,9 +61,7 @@ function OnboardingScreen({ isShowStarterScreen }: Props) {
|
|||||||
const { sources } = useGetModelSources()
|
const { sources } = useGetModelSources()
|
||||||
const setMainViewState = useSetAtom(mainViewStateAtom)
|
const setMainViewState = useSetAtom(mainViewStateAtom)
|
||||||
|
|
||||||
const featuredModels = sources?.filter((x) =>
|
const { sources: featuredModels } = useGetFeaturedSources()
|
||||||
manualRecommendationModel.includes(x.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
const filteredModels = useMemo(
|
const filteredModels = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@ -7,13 +7,3 @@
|
|||||||
export const normalizeModelId = (downloadUrl: string): string => {
|
export const normalizeModelId = (downloadUrl: string): string => {
|
||||||
return downloadUrl.split('/').pop() ?? downloadUrl
|
return downloadUrl.split('/').pop() ?? downloadUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Default models to recommend to users when they first open the app.
|
|
||||||
* TODO: These will be replaced when we have a proper recommendation system
|
|
||||||
* AND cortexso repositories are updated with tags.
|
|
||||||
*/
|
|
||||||
export const manualRecommendationModel = [
|
|
||||||
'cortexso/deepseek-r1',
|
|
||||||
'cortexso/llama3.2',
|
|
||||||
]
|
|
||||||
|
|||||||
@ -4,12 +4,22 @@
|
|||||||
*/
|
*/
|
||||||
export const extractDescription = (text?: string) => {
|
export const extractDescription = (text?: string) => {
|
||||||
if (!text) return text
|
if (!text) return text
|
||||||
|
const normalizedText = removeYamlFrontMatter(text)
|
||||||
const overviewPattern = /(?:##\s*Overview\s*\n)([\s\S]*?)(?=\n\s*##|$)/
|
const overviewPattern = /(?:##\s*Overview\s*\n)([\s\S]*?)(?=\n\s*##|$)/
|
||||||
const matches = text?.match(overviewPattern)
|
const matches = normalizedText?.match(overviewPattern)
|
||||||
if (matches && matches[1]) {
|
if (matches && matches[1]) {
|
||||||
return matches[1].trim()
|
return matches[1].trim()
|
||||||
}
|
}
|
||||||
return text?.slice(0, 500).trim()
|
return normalizedText?.slice(0, 500).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove YAML (HF metadata) front matter from content
|
||||||
|
* @param content
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const removeYamlFrontMatter = (content: string): string => {
|
||||||
|
return content.replace(/^---\n([\s\S]*?)\n---\n/, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user