✨enhancement: add hub detail page
This commit is contained in:
parent
0343c09704
commit
c34291237f
@ -17,7 +17,10 @@ export const route = {
|
|||||||
https_proxy: '/settings/https-proxy',
|
https_proxy: '/settings/https-proxy',
|
||||||
hardware: '/settings/hardware',
|
hardware: '/settings/hardware',
|
||||||
},
|
},
|
||||||
hub: '/hub',
|
hub: {
|
||||||
|
index: '/hub/',
|
||||||
|
model: '/hub/$modelId',
|
||||||
|
},
|
||||||
localApiServerlogs: '/local-api-server/logs',
|
localApiServerlogs: '/local-api-server/logs',
|
||||||
systemMonitor: '/system-monitor',
|
systemMonitor: '/system-monitor',
|
||||||
threadsDetail: '/threads/$threadId',
|
threadsDetail: '/threads/$threadId',
|
||||||
|
|||||||
@ -57,7 +57,7 @@ const mainMenus = [
|
|||||||
{
|
{
|
||||||
title: 'common:hub',
|
title: 'common:hub',
|
||||||
icon: IconAppsFilled,
|
icon: IconAppsFilled,
|
||||||
route: route.hub,
|
route: route.hub.index,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'common:settings',
|
title: 'common:settings',
|
||||||
|
|||||||
@ -29,7 +29,7 @@ function SetupScreen() {
|
|||||||
<Card
|
<Card
|
||||||
header={
|
header={
|
||||||
<Link
|
<Link
|
||||||
to={route.hub}
|
to={route.hub.index}
|
||||||
search={{
|
search={{
|
||||||
...(!isProd ? { step: 'setup_local_provider' } : {}),
|
...(!isProd ? { step: 'setup_local_provider' } : {}),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export function DataProvider() {
|
|||||||
const resource = params.slice(1).join('/')
|
const resource = params.slice(1).join('/')
|
||||||
// return { action, provider, resource }
|
// return { action, provider, resource }
|
||||||
navigate({
|
navigate({
|
||||||
to: route.hub,
|
to: route.hub.index,
|
||||||
search: {
|
search: {
|
||||||
repo: resource,
|
repo: resource,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,9 +13,9 @@
|
|||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
import { Route as SystemMonitorImport } from './routes/system-monitor'
|
import { Route as SystemMonitorImport } from './routes/system-monitor'
|
||||||
import { Route as LogsImport } from './routes/logs'
|
import { Route as LogsImport } from './routes/logs'
|
||||||
import { Route as HubImport } from './routes/hub'
|
|
||||||
import { Route as AssistantImport } from './routes/assistant'
|
import { Route as AssistantImport } from './routes/assistant'
|
||||||
import { Route as IndexImport } from './routes/index'
|
import { Route as IndexImport } from './routes/index'
|
||||||
|
import { Route as HubIndexImport } from './routes/hub/index'
|
||||||
import { Route as ThreadsThreadIdImport } from './routes/threads/$threadId'
|
import { Route as ThreadsThreadIdImport } from './routes/threads/$threadId'
|
||||||
import { Route as SettingsShortcutsImport } from './routes/settings/shortcuts'
|
import { Route as SettingsShortcutsImport } from './routes/settings/shortcuts'
|
||||||
import { Route as SettingsPrivacyImport } from './routes/settings/privacy'
|
import { Route as SettingsPrivacyImport } from './routes/settings/privacy'
|
||||||
@ -27,6 +27,7 @@ import { Route as SettingsGeneralImport } from './routes/settings/general'
|
|||||||
import { Route as SettingsExtensionsImport } from './routes/settings/extensions'
|
import { Route as SettingsExtensionsImport } from './routes/settings/extensions'
|
||||||
import { Route as SettingsAppearanceImport } from './routes/settings/appearance'
|
import { Route as SettingsAppearanceImport } from './routes/settings/appearance'
|
||||||
import { Route as LocalApiServerLogsImport } from './routes/local-api-server/logs'
|
import { Route as LocalApiServerLogsImport } from './routes/local-api-server/logs'
|
||||||
|
import { Route as HubModelIdImport } from './routes/hub/$modelId'
|
||||||
import { Route as SettingsProvidersIndexImport } from './routes/settings/providers/index'
|
import { Route as SettingsProvidersIndexImport } from './routes/settings/providers/index'
|
||||||
import { Route as SettingsProvidersProviderNameImport } from './routes/settings/providers/$providerName'
|
import { Route as SettingsProvidersProviderNameImport } from './routes/settings/providers/$providerName'
|
||||||
|
|
||||||
@ -44,12 +45,6 @@ const LogsRoute = LogsImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const HubRoute = HubImport.update({
|
|
||||||
id: '/hub',
|
|
||||||
path: '/hub',
|
|
||||||
getParentRoute: () => rootRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const AssistantRoute = AssistantImport.update({
|
const AssistantRoute = AssistantImport.update({
|
||||||
id: '/assistant',
|
id: '/assistant',
|
||||||
path: '/assistant',
|
path: '/assistant',
|
||||||
@ -62,6 +57,12 @@ const IndexRoute = IndexImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const HubIndexRoute = HubIndexImport.update({
|
||||||
|
id: '/hub/',
|
||||||
|
path: '/hub/',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const ThreadsThreadIdRoute = ThreadsThreadIdImport.update({
|
const ThreadsThreadIdRoute = ThreadsThreadIdImport.update({
|
||||||
id: '/threads/$threadId',
|
id: '/threads/$threadId',
|
||||||
path: '/threads/$threadId',
|
path: '/threads/$threadId',
|
||||||
@ -128,6 +129,12 @@ const LocalApiServerLogsRoute = LocalApiServerLogsImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const HubModelIdRoute = HubModelIdImport.update({
|
||||||
|
id: '/hub/$modelId',
|
||||||
|
path: '/hub/$modelId',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const SettingsProvidersIndexRoute = SettingsProvidersIndexImport.update({
|
const SettingsProvidersIndexRoute = SettingsProvidersIndexImport.update({
|
||||||
id: '/settings/providers/',
|
id: '/settings/providers/',
|
||||||
path: '/settings/providers/',
|
path: '/settings/providers/',
|
||||||
@ -159,13 +166,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AssistantImport
|
preLoaderRoute: typeof AssistantImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
'/hub': {
|
|
||||||
id: '/hub'
|
|
||||||
path: '/hub'
|
|
||||||
fullPath: '/hub'
|
|
||||||
preLoaderRoute: typeof HubImport
|
|
||||||
parentRoute: typeof rootRoute
|
|
||||||
}
|
|
||||||
'/logs': {
|
'/logs': {
|
||||||
id: '/logs'
|
id: '/logs'
|
||||||
path: '/logs'
|
path: '/logs'
|
||||||
@ -180,6 +180,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SystemMonitorImport
|
preLoaderRoute: typeof SystemMonitorImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/hub/$modelId': {
|
||||||
|
id: '/hub/$modelId'
|
||||||
|
path: '/hub/$modelId'
|
||||||
|
fullPath: '/hub/$modelId'
|
||||||
|
preLoaderRoute: typeof HubModelIdImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/local-api-server/logs': {
|
'/local-api-server/logs': {
|
||||||
id: '/local-api-server/logs'
|
id: '/local-api-server/logs'
|
||||||
path: '/local-api-server/logs'
|
path: '/local-api-server/logs'
|
||||||
@ -257,6 +264,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ThreadsThreadIdImport
|
preLoaderRoute: typeof ThreadsThreadIdImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/hub/': {
|
||||||
|
id: '/hub/'
|
||||||
|
path: '/hub'
|
||||||
|
fullPath: '/hub'
|
||||||
|
preLoaderRoute: typeof HubIndexImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/settings/providers/$providerName': {
|
'/settings/providers/$providerName': {
|
||||||
id: '/settings/providers/$providerName'
|
id: '/settings/providers/$providerName'
|
||||||
path: '/settings/providers/$providerName'
|
path: '/settings/providers/$providerName'
|
||||||
@ -279,9 +293,9 @@ declare module '@tanstack/react-router' {
|
|||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/assistant': typeof AssistantRoute
|
'/assistant': typeof AssistantRoute
|
||||||
'/hub': typeof HubRoute
|
|
||||||
'/logs': typeof LogsRoute
|
'/logs': typeof LogsRoute
|
||||||
'/system-monitor': typeof SystemMonitorRoute
|
'/system-monitor': typeof SystemMonitorRoute
|
||||||
|
'/hub/$modelId': typeof HubModelIdRoute
|
||||||
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
||||||
'/settings/appearance': typeof SettingsAppearanceRoute
|
'/settings/appearance': typeof SettingsAppearanceRoute
|
||||||
'/settings/extensions': typeof SettingsExtensionsRoute
|
'/settings/extensions': typeof SettingsExtensionsRoute
|
||||||
@ -293,6 +307,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/settings/privacy': typeof SettingsPrivacyRoute
|
'/settings/privacy': typeof SettingsPrivacyRoute
|
||||||
'/settings/shortcuts': typeof SettingsShortcutsRoute
|
'/settings/shortcuts': typeof SettingsShortcutsRoute
|
||||||
'/threads/$threadId': typeof ThreadsThreadIdRoute
|
'/threads/$threadId': typeof ThreadsThreadIdRoute
|
||||||
|
'/hub': typeof HubIndexRoute
|
||||||
'/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute
|
'/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute
|
||||||
'/settings/providers': typeof SettingsProvidersIndexRoute
|
'/settings/providers': typeof SettingsProvidersIndexRoute
|
||||||
}
|
}
|
||||||
@ -300,9 +315,9 @@ export interface FileRoutesByFullPath {
|
|||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/assistant': typeof AssistantRoute
|
'/assistant': typeof AssistantRoute
|
||||||
'/hub': typeof HubRoute
|
|
||||||
'/logs': typeof LogsRoute
|
'/logs': typeof LogsRoute
|
||||||
'/system-monitor': typeof SystemMonitorRoute
|
'/system-monitor': typeof SystemMonitorRoute
|
||||||
|
'/hub/$modelId': typeof HubModelIdRoute
|
||||||
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
||||||
'/settings/appearance': typeof SettingsAppearanceRoute
|
'/settings/appearance': typeof SettingsAppearanceRoute
|
||||||
'/settings/extensions': typeof SettingsExtensionsRoute
|
'/settings/extensions': typeof SettingsExtensionsRoute
|
||||||
@ -314,6 +329,7 @@ export interface FileRoutesByTo {
|
|||||||
'/settings/privacy': typeof SettingsPrivacyRoute
|
'/settings/privacy': typeof SettingsPrivacyRoute
|
||||||
'/settings/shortcuts': typeof SettingsShortcutsRoute
|
'/settings/shortcuts': typeof SettingsShortcutsRoute
|
||||||
'/threads/$threadId': typeof ThreadsThreadIdRoute
|
'/threads/$threadId': typeof ThreadsThreadIdRoute
|
||||||
|
'/hub': typeof HubIndexRoute
|
||||||
'/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute
|
'/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute
|
||||||
'/settings/providers': typeof SettingsProvidersIndexRoute
|
'/settings/providers': typeof SettingsProvidersIndexRoute
|
||||||
}
|
}
|
||||||
@ -322,9 +338,9 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRoute
|
__root__: typeof rootRoute
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/assistant': typeof AssistantRoute
|
'/assistant': typeof AssistantRoute
|
||||||
'/hub': typeof HubRoute
|
|
||||||
'/logs': typeof LogsRoute
|
'/logs': typeof LogsRoute
|
||||||
'/system-monitor': typeof SystemMonitorRoute
|
'/system-monitor': typeof SystemMonitorRoute
|
||||||
|
'/hub/$modelId': typeof HubModelIdRoute
|
||||||
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
'/local-api-server/logs': typeof LocalApiServerLogsRoute
|
||||||
'/settings/appearance': typeof SettingsAppearanceRoute
|
'/settings/appearance': typeof SettingsAppearanceRoute
|
||||||
'/settings/extensions': typeof SettingsExtensionsRoute
|
'/settings/extensions': typeof SettingsExtensionsRoute
|
||||||
@ -336,6 +352,7 @@ export interface FileRoutesById {
|
|||||||
'/settings/privacy': typeof SettingsPrivacyRoute
|
'/settings/privacy': typeof SettingsPrivacyRoute
|
||||||
'/settings/shortcuts': typeof SettingsShortcutsRoute
|
'/settings/shortcuts': typeof SettingsShortcutsRoute
|
||||||
'/threads/$threadId': typeof ThreadsThreadIdRoute
|
'/threads/$threadId': typeof ThreadsThreadIdRoute
|
||||||
|
'/hub/': typeof HubIndexRoute
|
||||||
'/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute
|
'/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute
|
||||||
'/settings/providers/': typeof SettingsProvidersIndexRoute
|
'/settings/providers/': typeof SettingsProvidersIndexRoute
|
||||||
}
|
}
|
||||||
@ -345,9 +362,9 @@ export interface FileRouteTypes {
|
|||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| '/assistant'
|
| '/assistant'
|
||||||
| '/hub'
|
|
||||||
| '/logs'
|
| '/logs'
|
||||||
| '/system-monitor'
|
| '/system-monitor'
|
||||||
|
| '/hub/$modelId'
|
||||||
| '/local-api-server/logs'
|
| '/local-api-server/logs'
|
||||||
| '/settings/appearance'
|
| '/settings/appearance'
|
||||||
| '/settings/extensions'
|
| '/settings/extensions'
|
||||||
@ -359,15 +376,16 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/privacy'
|
| '/settings/privacy'
|
||||||
| '/settings/shortcuts'
|
| '/settings/shortcuts'
|
||||||
| '/threads/$threadId'
|
| '/threads/$threadId'
|
||||||
|
| '/hub'
|
||||||
| '/settings/providers/$providerName'
|
| '/settings/providers/$providerName'
|
||||||
| '/settings/providers'
|
| '/settings/providers'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
| '/assistant'
|
| '/assistant'
|
||||||
| '/hub'
|
|
||||||
| '/logs'
|
| '/logs'
|
||||||
| '/system-monitor'
|
| '/system-monitor'
|
||||||
|
| '/hub/$modelId'
|
||||||
| '/local-api-server/logs'
|
| '/local-api-server/logs'
|
||||||
| '/settings/appearance'
|
| '/settings/appearance'
|
||||||
| '/settings/extensions'
|
| '/settings/extensions'
|
||||||
@ -379,15 +397,16 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/privacy'
|
| '/settings/privacy'
|
||||||
| '/settings/shortcuts'
|
| '/settings/shortcuts'
|
||||||
| '/threads/$threadId'
|
| '/threads/$threadId'
|
||||||
|
| '/hub'
|
||||||
| '/settings/providers/$providerName'
|
| '/settings/providers/$providerName'
|
||||||
| '/settings/providers'
|
| '/settings/providers'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/assistant'
|
| '/assistant'
|
||||||
| '/hub'
|
|
||||||
| '/logs'
|
| '/logs'
|
||||||
| '/system-monitor'
|
| '/system-monitor'
|
||||||
|
| '/hub/$modelId'
|
||||||
| '/local-api-server/logs'
|
| '/local-api-server/logs'
|
||||||
| '/settings/appearance'
|
| '/settings/appearance'
|
||||||
| '/settings/extensions'
|
| '/settings/extensions'
|
||||||
@ -399,6 +418,7 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/privacy'
|
| '/settings/privacy'
|
||||||
| '/settings/shortcuts'
|
| '/settings/shortcuts'
|
||||||
| '/threads/$threadId'
|
| '/threads/$threadId'
|
||||||
|
| '/hub/'
|
||||||
| '/settings/providers/$providerName'
|
| '/settings/providers/$providerName'
|
||||||
| '/settings/providers/'
|
| '/settings/providers/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
@ -407,9 +427,9 @@ export interface FileRouteTypes {
|
|||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AssistantRoute: typeof AssistantRoute
|
AssistantRoute: typeof AssistantRoute
|
||||||
HubRoute: typeof HubRoute
|
|
||||||
LogsRoute: typeof LogsRoute
|
LogsRoute: typeof LogsRoute
|
||||||
SystemMonitorRoute: typeof SystemMonitorRoute
|
SystemMonitorRoute: typeof SystemMonitorRoute
|
||||||
|
HubModelIdRoute: typeof HubModelIdRoute
|
||||||
LocalApiServerLogsRoute: typeof LocalApiServerLogsRoute
|
LocalApiServerLogsRoute: typeof LocalApiServerLogsRoute
|
||||||
SettingsAppearanceRoute: typeof SettingsAppearanceRoute
|
SettingsAppearanceRoute: typeof SettingsAppearanceRoute
|
||||||
SettingsExtensionsRoute: typeof SettingsExtensionsRoute
|
SettingsExtensionsRoute: typeof SettingsExtensionsRoute
|
||||||
@ -421,6 +441,7 @@ export interface RootRouteChildren {
|
|||||||
SettingsPrivacyRoute: typeof SettingsPrivacyRoute
|
SettingsPrivacyRoute: typeof SettingsPrivacyRoute
|
||||||
SettingsShortcutsRoute: typeof SettingsShortcutsRoute
|
SettingsShortcutsRoute: typeof SettingsShortcutsRoute
|
||||||
ThreadsThreadIdRoute: typeof ThreadsThreadIdRoute
|
ThreadsThreadIdRoute: typeof ThreadsThreadIdRoute
|
||||||
|
HubIndexRoute: typeof HubIndexRoute
|
||||||
SettingsProvidersProviderNameRoute: typeof SettingsProvidersProviderNameRoute
|
SettingsProvidersProviderNameRoute: typeof SettingsProvidersProviderNameRoute
|
||||||
SettingsProvidersIndexRoute: typeof SettingsProvidersIndexRoute
|
SettingsProvidersIndexRoute: typeof SettingsProvidersIndexRoute
|
||||||
}
|
}
|
||||||
@ -428,9 +449,9 @@ export interface RootRouteChildren {
|
|||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AssistantRoute: AssistantRoute,
|
AssistantRoute: AssistantRoute,
|
||||||
HubRoute: HubRoute,
|
|
||||||
LogsRoute: LogsRoute,
|
LogsRoute: LogsRoute,
|
||||||
SystemMonitorRoute: SystemMonitorRoute,
|
SystemMonitorRoute: SystemMonitorRoute,
|
||||||
|
HubModelIdRoute: HubModelIdRoute,
|
||||||
LocalApiServerLogsRoute: LocalApiServerLogsRoute,
|
LocalApiServerLogsRoute: LocalApiServerLogsRoute,
|
||||||
SettingsAppearanceRoute: SettingsAppearanceRoute,
|
SettingsAppearanceRoute: SettingsAppearanceRoute,
|
||||||
SettingsExtensionsRoute: SettingsExtensionsRoute,
|
SettingsExtensionsRoute: SettingsExtensionsRoute,
|
||||||
@ -442,6 +463,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
SettingsPrivacyRoute: SettingsPrivacyRoute,
|
SettingsPrivacyRoute: SettingsPrivacyRoute,
|
||||||
SettingsShortcutsRoute: SettingsShortcutsRoute,
|
SettingsShortcutsRoute: SettingsShortcutsRoute,
|
||||||
ThreadsThreadIdRoute: ThreadsThreadIdRoute,
|
ThreadsThreadIdRoute: ThreadsThreadIdRoute,
|
||||||
|
HubIndexRoute: HubIndexRoute,
|
||||||
SettingsProvidersProviderNameRoute: SettingsProvidersProviderNameRoute,
|
SettingsProvidersProviderNameRoute: SettingsProvidersProviderNameRoute,
|
||||||
SettingsProvidersIndexRoute: SettingsProvidersIndexRoute,
|
SettingsProvidersIndexRoute: SettingsProvidersIndexRoute,
|
||||||
}
|
}
|
||||||
@ -458,9 +480,9 @@ export const routeTree = rootRoute
|
|||||||
"children": [
|
"children": [
|
||||||
"/",
|
"/",
|
||||||
"/assistant",
|
"/assistant",
|
||||||
"/hub",
|
|
||||||
"/logs",
|
"/logs",
|
||||||
"/system-monitor",
|
"/system-monitor",
|
||||||
|
"/hub/$modelId",
|
||||||
"/local-api-server/logs",
|
"/local-api-server/logs",
|
||||||
"/settings/appearance",
|
"/settings/appearance",
|
||||||
"/settings/extensions",
|
"/settings/extensions",
|
||||||
@ -472,6 +494,7 @@ export const routeTree = rootRoute
|
|||||||
"/settings/privacy",
|
"/settings/privacy",
|
||||||
"/settings/shortcuts",
|
"/settings/shortcuts",
|
||||||
"/threads/$threadId",
|
"/threads/$threadId",
|
||||||
|
"/hub/",
|
||||||
"/settings/providers/$providerName",
|
"/settings/providers/$providerName",
|
||||||
"/settings/providers/"
|
"/settings/providers/"
|
||||||
]
|
]
|
||||||
@ -482,15 +505,15 @@ export const routeTree = rootRoute
|
|||||||
"/assistant": {
|
"/assistant": {
|
||||||
"filePath": "assistant.tsx"
|
"filePath": "assistant.tsx"
|
||||||
},
|
},
|
||||||
"/hub": {
|
|
||||||
"filePath": "hub.tsx"
|
|
||||||
},
|
|
||||||
"/logs": {
|
"/logs": {
|
||||||
"filePath": "logs.tsx"
|
"filePath": "logs.tsx"
|
||||||
},
|
},
|
||||||
"/system-monitor": {
|
"/system-monitor": {
|
||||||
"filePath": "system-monitor.tsx"
|
"filePath": "system-monitor.tsx"
|
||||||
},
|
},
|
||||||
|
"/hub/$modelId": {
|
||||||
|
"filePath": "hub/$modelId.tsx"
|
||||||
|
},
|
||||||
"/local-api-server/logs": {
|
"/local-api-server/logs": {
|
||||||
"filePath": "local-api-server/logs.tsx"
|
"filePath": "local-api-server/logs.tsx"
|
||||||
},
|
},
|
||||||
@ -524,6 +547,9 @@ export const routeTree = rootRoute
|
|||||||
"/threads/$threadId": {
|
"/threads/$threadId": {
|
||||||
"filePath": "threads/$threadId.tsx"
|
"filePath": "threads/$threadId.tsx"
|
||||||
},
|
},
|
||||||
|
"/hub/": {
|
||||||
|
"filePath": "hub/index.tsx"
|
||||||
|
},
|
||||||
"/settings/providers/$providerName": {
|
"/settings/providers/$providerName": {
|
||||||
"filePath": "settings/providers/$providerName.tsx"
|
"filePath": "settings/providers/$providerName.tsx"
|
||||||
},
|
},
|
||||||
|
|||||||
367
web-app/src/routes/hub/$modelId.tsx
Normal file
367
web-app/src/routes/hub/$modelId.tsx
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
import HeaderPage from '@/containers/HeaderPage'
|
||||||
|
import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router'
|
||||||
|
import {
|
||||||
|
IconArrowLeft,
|
||||||
|
IconDownload,
|
||||||
|
IconClock,
|
||||||
|
IconFileCode,
|
||||||
|
} from '@tabler/icons-react'
|
||||||
|
import { route } from '@/constants/routes'
|
||||||
|
import { useModelSources } from '@/hooks/useModelSources'
|
||||||
|
import { extractModelName, extractDescription } from '@/lib/models'
|
||||||
|
import { RenderMarkdown } from '@/containers/RenderMarkdown'
|
||||||
|
import { useEffect, useMemo, useCallback } from 'react'
|
||||||
|
import { useModelProvider } from '@/hooks/useModelProvider'
|
||||||
|
import { useDownloadStore } from '@/hooks/useDownloadStore'
|
||||||
|
import { pullModel } from '@/services/models'
|
||||||
|
import { Progress } from '@/components/ui/progress'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/hub/$modelId')({
|
||||||
|
component: HubModelDetail,
|
||||||
|
})
|
||||||
|
|
||||||
|
function HubModelDetail() {
|
||||||
|
const { modelId } = useParams({ from: Route.id })
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { sources, fetchSources } = useModelSources()
|
||||||
|
const { getProviderByName } = useModelProvider()
|
||||||
|
const llamaProvider = getProviderByName('llamacpp')
|
||||||
|
const { downloads, localDownloadingModels, addLocalDownloadingModel } =
|
||||||
|
useDownloadStore()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSources()
|
||||||
|
}, [fetchSources])
|
||||||
|
|
||||||
|
// Find the model data from sources
|
||||||
|
const modelData = useMemo(() => {
|
||||||
|
return sources.find((model) => model.model_name === modelId)
|
||||||
|
}, [sources, modelId])
|
||||||
|
|
||||||
|
// Download processes
|
||||||
|
const downloadProcesses = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.values(downloads).map((download) => ({
|
||||||
|
id: download.name,
|
||||||
|
name: download.name,
|
||||||
|
progress: download.progress,
|
||||||
|
current: download.current,
|
||||||
|
total: download.total,
|
||||||
|
})),
|
||||||
|
[downloads]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle model use
|
||||||
|
const handleUseModel = useCallback(
|
||||||
|
(modelId: string) => {
|
||||||
|
navigate({
|
||||||
|
to: route.home,
|
||||||
|
params: {},
|
||||||
|
search: {
|
||||||
|
model: {
|
||||||
|
id: modelId,
|
||||||
|
provider: 'llamacpp',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format the date
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
const date = new Date(dateString)
|
||||||
|
const now = new Date()
|
||||||
|
const diffTime = Math.abs(now.getTime() - date.getTime())
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
|
|
||||||
|
if (diffDays < 7) {
|
||||||
|
return `${diffDays} days ago`
|
||||||
|
} else if (diffDays < 30) {
|
||||||
|
const weeks = Math.floor(diffDays / 7)
|
||||||
|
return `${weeks} week${weeks > 1 ? 's' : ''} ago`
|
||||||
|
} else if (diffDays < 365) {
|
||||||
|
const months = Math.floor(diffDays / 30)
|
||||||
|
return `${months} month${months > 1 ? 's' : ''} ago`
|
||||||
|
} else {
|
||||||
|
const years = Math.floor(diffDays / 365)
|
||||||
|
return `${years} year${years > 1 ? 's' : ''} ago`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tags from quants (model variants)
|
||||||
|
const tags = useMemo(() => {
|
||||||
|
if (!modelData?.quants) return []
|
||||||
|
// Extract unique size indicators from quant names
|
||||||
|
const sizePattern = /(\d+b)/i
|
||||||
|
const uniqueSizes = new Set<string>()
|
||||||
|
|
||||||
|
modelData.quants.forEach((quant) => {
|
||||||
|
const match = quant.model_id.match(sizePattern)
|
||||||
|
if (match) {
|
||||||
|
uniqueSizes.add(match[1].toLowerCase())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.from(uniqueSizes).sort((a, b) => {
|
||||||
|
const numA = parseInt(a)
|
||||||
|
const numB = parseInt(b)
|
||||||
|
return numA - numB
|
||||||
|
})
|
||||||
|
}, [modelData])
|
||||||
|
|
||||||
|
if (!modelData) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full">
|
||||||
|
<div className="flex flex-col h-full w-full">
|
||||||
|
<HeaderPage>
|
||||||
|
<button
|
||||||
|
className="relative z-20 flex items-center gap-2 cursor-pointer"
|
||||||
|
onClick={() => navigate({ to: route.hub.index })}
|
||||||
|
aria-label="Go back"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center size-5 rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out">
|
||||||
|
<IconArrowLeft size={18} className="text-main-view-fg" />
|
||||||
|
</div>
|
||||||
|
<span className="text-main-view-fg">Back to Hub</span>
|
||||||
|
</button>
|
||||||
|
</HeaderPage>
|
||||||
|
<div className="flex-1 flex items-center justify-center">
|
||||||
|
<p className="text-main-view-fg/60">Model not found</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full">
|
||||||
|
<div className="flex flex-col h-full w-full ">
|
||||||
|
<HeaderPage>
|
||||||
|
<button
|
||||||
|
className="relative z-20 flex items-center gap-2 cursor-pointer"
|
||||||
|
onClick={() => navigate({ to: route.hub.index })}
|
||||||
|
aria-label="Go back"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center size-5 rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out">
|
||||||
|
<IconArrowLeft size={18} className="text-main-view-fg" />
|
||||||
|
</div>
|
||||||
|
<span className="text-main-view-fg">Back to Hub</span>
|
||||||
|
</button>
|
||||||
|
</HeaderPage>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto ">
|
||||||
|
<div className="md:w-4/5 mx-auto">
|
||||||
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
|
{/* Model Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1
|
||||||
|
className="text-2xl font-semibold text-main-view-fg mb-4 capitalize break-words line-clamp-2"
|
||||||
|
title={
|
||||||
|
extractModelName(modelData.model_name) ||
|
||||||
|
modelData.model_name
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{extractModelName(modelData.model_name) ||
|
||||||
|
modelData.model_name}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="flex items-center gap-4 text-sm text-main-view-fg/60 mb-4 flex-wrap">
|
||||||
|
{modelData.developer && (
|
||||||
|
<>
|
||||||
|
<span>By {modelData.developer}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<IconDownload size={16} />
|
||||||
|
<span>{modelData.downloads || 0} Downloads</span>
|
||||||
|
</div>
|
||||||
|
{modelData.created_at && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<IconClock size={16} />
|
||||||
|
<span>Updated {formatDate(modelData.created_at)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
{modelData.description && (
|
||||||
|
<div className="text-main-view-fg/80 mb-4">
|
||||||
|
<RenderMarkdown
|
||||||
|
enableRawHtml={true}
|
||||||
|
className="select-none reset-heading"
|
||||||
|
components={{
|
||||||
|
a: ({ ...props }) => (
|
||||||
|
<a
|
||||||
|
{...props}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
content={
|
||||||
|
extractDescription(modelData.description) ||
|
||||||
|
modelData.description
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tags */}
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="px-3 py-1 text-sm bg-main-view-fg/10 text-main-view-fg rounded-md"
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Variants Section */}
|
||||||
|
{modelData.quants && modelData.quants.length > 0 && (
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<IconFileCode size={20} className="text-main-view-fg/50" />
|
||||||
|
<h2 className="text-lg font-semibold text-main-view-fg">
|
||||||
|
Variants ({modelData.quants.length})
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full overflow-x-auto">
|
||||||
|
<table className="w-full min-w-[500px]">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-main-view-fg/10">
|
||||||
|
<th className="text-left py-3 px-2 text-sm font-medium text-main-view-fg/70">
|
||||||
|
Version
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-3 px-2 text-sm font-medium text-main-view-fg/70">
|
||||||
|
Format
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-3 px-2 text-sm font-medium text-main-view-fg/70">
|
||||||
|
Size
|
||||||
|
</th>
|
||||||
|
<th className="text-right py-3 px-2 text-sm font-medium text-main-view-fg/70">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{modelData.quants.map((variant) => {
|
||||||
|
const isDownloading =
|
||||||
|
localDownloadingModels.has(variant.model_id) ||
|
||||||
|
downloadProcesses.some(
|
||||||
|
(e) => e.id === variant.model_id
|
||||||
|
)
|
||||||
|
const downloadProgress =
|
||||||
|
downloadProcesses.find(
|
||||||
|
(e) => e.id === variant.model_id
|
||||||
|
)?.progress || 0
|
||||||
|
const isDownloaded = llamaProvider?.models.some(
|
||||||
|
(m: { id: string }) => m.id === variant.model_id
|
||||||
|
)
|
||||||
|
|
||||||
|
// Extract format from model_id
|
||||||
|
const format = variant.model_id
|
||||||
|
.toLowerCase()
|
||||||
|
.includes('tensorrt')
|
||||||
|
? 'TensorRT'
|
||||||
|
: 'GGUF'
|
||||||
|
|
||||||
|
// Extract version name (remove format suffix)
|
||||||
|
const versionName = variant.model_id
|
||||||
|
.replace(/_GGUF$/i, '')
|
||||||
|
.replace(/-GGUF$/i, '')
|
||||||
|
.replace(/_TensorRT$/i, '')
|
||||||
|
.replace(/-TensorRT$/i, '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={variant.model_id}
|
||||||
|
className="border-b border-main-view-fg/5 hover:bg-main-view-fg/5"
|
||||||
|
>
|
||||||
|
<td className="py-3 px-2">
|
||||||
|
<span className="text-sm text-main-view-fg/80 font-medium">
|
||||||
|
{versionName}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-2">
|
||||||
|
<span className="text-sm text-main-view-fg/60">
|
||||||
|
{format}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-2">
|
||||||
|
<span className="text-sm text-main-view-fg/60">
|
||||||
|
{variant.file_size}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-2 text-right">
|
||||||
|
{(() => {
|
||||||
|
if (isDownloading && !isDownloaded) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<Progress
|
||||||
|
value={downloadProgress * 100}
|
||||||
|
className="w-12"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-main-view-fg/70 text-right">
|
||||||
|
{Math.round(downloadProgress * 100)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloaded) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
handleUseModel(variant.model_id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Use
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
addLocalDownloadingModel(
|
||||||
|
variant.model_id
|
||||||
|
)
|
||||||
|
pullModel(
|
||||||
|
variant.model_id,
|
||||||
|
variant.path
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
className={cn(isDownloading && 'hidden')}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,10 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import {
|
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
|
||||||
createFileRoute,
|
|
||||||
Link,
|
|
||||||
useNavigate,
|
|
||||||
useSearch,
|
|
||||||
} from '@tanstack/react-router'
|
|
||||||
import { route } from '@/constants/routes'
|
import { route } from '@/constants/routes'
|
||||||
import { useModelSources } from '@/hooks/useModelSources'
|
import { useModelSources } from '@/hooks/useModelSources'
|
||||||
import { cn, fuzzySearch } from '@/lib/utils'
|
import { cn, fuzzySearch } from '@/lib/utils'
|
||||||
@ -46,7 +41,7 @@ type SearchParams = {
|
|||||||
}
|
}
|
||||||
const defaultModelQuantizations = ['iq4_xs.gguf', 'q4_k_m.gguf']
|
const defaultModelQuantizations = ['iq4_xs.gguf', 'q4_k_m.gguf']
|
||||||
|
|
||||||
export const Route = createFileRoute(route.hub as any)({
|
export const Route = createFileRoute(route.hub.index as any)({
|
||||||
component: Hub,
|
component: Hub,
|
||||||
validateSearch: (search: Record<string, unknown>): SearchParams => ({
|
validateSearch: (search: Record<string, unknown>): SearchParams => ({
|
||||||
repo: search.repo as SearchParams['repo'],
|
repo: search.repo as SearchParams['repo'],
|
||||||
@ -60,7 +55,7 @@ function Hub() {
|
|||||||
{ value: 'most-downloaded', name: t('hub:sortMostDownloaded') },
|
{ value: 'most-downloaded', name: t('hub:sortMostDownloaded') },
|
||||||
]
|
]
|
||||||
const { sources, fetchSources, addSource, loading } = useModelSources()
|
const { sources, fetchSources, addSource, loading } = useModelSources()
|
||||||
const search = useSearch({ from: route.hub as any })
|
const search = useSearch({ from: route.hub.index as any })
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
const [sortSelected, setSortSelected] = useState('newest')
|
const [sortSelected, setSortSelected] = useState('newest')
|
||||||
const [expandedModels, setExpandedModels] = useState<Record<string, boolean>>(
|
const [expandedModels, setExpandedModels] = useState<Record<string, boolean>>(
|
||||||
@ -328,7 +323,7 @@ function Hub() {
|
|||||||
|
|
||||||
if (status === STATUS.FINISHED) {
|
if (status === STATUS.FINISHED) {
|
||||||
navigate({
|
navigate({
|
||||||
to: route.hub,
|
to: route.hub.index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,16 +468,22 @@ function Hub() {
|
|||||||
<div className="flex items-center gap-2 justify-end sm:hidden">
|
<div className="flex items-center gap-2 justify-end sm:hidden">
|
||||||
{renderFilter()}
|
{renderFilter()}
|
||||||
</div>
|
</div>
|
||||||
{filteredModels.map((model) => (
|
{filteredModels.map((model, i) => (
|
||||||
<div key={model.model_name}>
|
<div key={`${model.model_name}-${i}`}>
|
||||||
<Card
|
<Card
|
||||||
header={
|
header={
|
||||||
<div className="flex items-center justify-between gap-x-2">
|
<div className="flex items-center justify-between gap-x-2">
|
||||||
<Link
|
<div
|
||||||
to={
|
className="cursor-pointer"
|
||||||
`https://huggingface.co/${model.model_name}` as string
|
onClick={() => {
|
||||||
}
|
console.log(model.model_name)
|
||||||
target="_blank"
|
navigate({
|
||||||
|
to: route.hub.model,
|
||||||
|
params: {
|
||||||
|
modelId: model.model_name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<h1
|
<h1
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -495,7 +496,7 @@ function Hub() {
|
|||||||
>
|
>
|
||||||
{extractModelName(model.model_name) || ''}
|
{extractModelName(model.model_name) || ''}
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</div>
|
||||||
<div className="shrink-0 space-x-3 flex items-center">
|
<div className="shrink-0 space-x-3 flex items-center">
|
||||||
<span className="text-main-view-fg/70 font-medium text-xs">
|
<span className="text-main-view-fg/70 font-medium text-xs">
|
||||||
{
|
{
|
||||||
@ -525,7 +525,7 @@ function ProviderDetail() {
|
|||||||
<p className="text-main-view-fg/70 mt-1 text-xs leading-relaxed">
|
<p className="text-main-view-fg/70 mt-1 text-xs leading-relaxed">
|
||||||
{t('providers:noModelFoundDesc')}
|
{t('providers:noModelFoundDesc')}
|
||||||
|
|
||||||
<Link to={route.hub}>{t('common:hub')}</Link>
|
<Link to={route.hub.index}>{t('common:hub')}</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user