feat: new frontend with model download function (#5008)
This commit is contained in:
parent
1b2a29565f
commit
c1091ce812
@ -41,6 +41,7 @@ import { listen } from '@tauri-apps/api/event'
|
|||||||
import { SystemEvent } from '@/types/events'
|
import { SystemEvent } from '@/types/events'
|
||||||
import { CompletionMessagesBuilder } from '@/lib/messages'
|
import { CompletionMessagesBuilder } from '@/lib/messages'
|
||||||
import { ChatCompletionMessageToolCall } from 'openai/resources'
|
import { ChatCompletionMessageToolCall } from 'openai/resources'
|
||||||
|
import { getTools } from '@/services/mcp'
|
||||||
|
|
||||||
type ChatInputProps = {
|
type ChatInputProps = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -93,16 +94,15 @@ const ChatInput = ({ className, showSpeedToken = true }: ChatInputProps) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.core?.api?.getTools().then((data: MCPTool[]) => {
|
function updateTools() {
|
||||||
|
getTools().then((data: MCPTool[]) => {
|
||||||
setTools(data)
|
setTools(data)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
updateTools()
|
||||||
|
|
||||||
let unsubscribe = () => {}
|
let unsubscribe = () => {}
|
||||||
listen(SystemEvent.MCP_UPDATE, () => {
|
listen(SystemEvent.MCP_UPDATE, updateTools).then((unsub) => {
|
||||||
window.core?.api?.getTools().then((data: MCPTool[]) => {
|
|
||||||
setTools(data)
|
|
||||||
})
|
|
||||||
}).then((unsub) => {
|
|
||||||
// Unsubscribe from the event when the component unmounts
|
// Unsubscribe from the event when the component unmounts
|
||||||
unsubscribe = unsub
|
unsubscribe = unsub
|
||||||
})
|
})
|
||||||
@ -199,8 +199,11 @@ const ChatInput = ({ className, showSpeedToken = true }: ChatInputProps) => {
|
|||||||
accumulatedText
|
accumulatedText
|
||||||
)
|
)
|
||||||
builder.addAssistantMessage(accumulatedText, undefined, toolCalls)
|
builder.addAssistantMessage(accumulatedText, undefined, toolCalls)
|
||||||
const updatedMessage = await postMessageProcessing(toolCalls, builder, finalContent)
|
const updatedMessage = await postMessageProcessing(
|
||||||
console.log(updatedMessage)
|
toolCalls,
|
||||||
|
builder,
|
||||||
|
finalContent
|
||||||
|
)
|
||||||
addMessage(updatedMessage ?? finalContent)
|
addMessage(updatedMessage ?? finalContent)
|
||||||
|
|
||||||
isCompleted = !toolCalls.length
|
isCompleted = !toolCalls.length
|
||||||
|
|||||||
@ -4,25 +4,122 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover'
|
} from '@/components/ui/popover'
|
||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
|
import { useDownloadStore } from '@/hooks/useDownloadStore'
|
||||||
|
import { abortDownload } from '@/services/models'
|
||||||
|
import { DownloadEvent, DownloadState, events } from '@janhq/core'
|
||||||
import { IconPlayerPauseFilled, IconX } from '@tabler/icons-react'
|
import { IconPlayerPauseFilled, IconX } from '@tabler/icons-react'
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
|
|
||||||
export function DownloadManagement() {
|
export function DownloadManagement() {
|
||||||
|
const { downloads, updateProgress, removeDownload } = useDownloadStore()
|
||||||
|
const downloadCount = useMemo(
|
||||||
|
() => Object.keys(downloads).length,
|
||||||
|
[downloads]
|
||||||
|
)
|
||||||
|
const downloadProcesses = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.values(downloads).map((download) => ({
|
||||||
|
id: download.id,
|
||||||
|
name: download.name,
|
||||||
|
progress: download.progress,
|
||||||
|
current: download.current,
|
||||||
|
total: download.total,
|
||||||
|
})),
|
||||||
|
[downloads]
|
||||||
|
)
|
||||||
|
|
||||||
|
const overallProgress = useMemo(() => {
|
||||||
|
const total = downloadProcesses.reduce((acc, download) => {
|
||||||
|
return acc + download.total
|
||||||
|
}, 0)
|
||||||
|
const current = downloadProcesses.reduce((acc, download) => {
|
||||||
|
return acc + download.current
|
||||||
|
}, 0)
|
||||||
|
return total > 0 ? current / total : 0
|
||||||
|
}, [downloadProcesses])
|
||||||
|
|
||||||
|
const onFileDownloadUpdate = useCallback(
|
||||||
|
async (state: DownloadState) => {
|
||||||
|
console.debug('onFileDownloadUpdate', state)
|
||||||
|
updateProgress(
|
||||||
|
state.modelId,
|
||||||
|
state.percent,
|
||||||
|
state.modelId,
|
||||||
|
state.size?.transferred,
|
||||||
|
state.size?.total
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[updateProgress]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onFileDownloadError = useCallback(
|
||||||
|
(state: DownloadState) => {
|
||||||
|
console.debug('onFileDownloadError', state)
|
||||||
|
removeDownload(state.modelId)
|
||||||
|
},
|
||||||
|
[removeDownload]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onFileDownloadStopped = useCallback(
|
||||||
|
(state: DownloadState) => {
|
||||||
|
console.debug('onFileDownloadError', state)
|
||||||
|
removeDownload(state.modelId)
|
||||||
|
},
|
||||||
|
[removeDownload]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onFileDownloadSuccess = useCallback(
|
||||||
|
async (state: DownloadState) => {
|
||||||
|
console.debug('onFileDownloadSuccess', state)
|
||||||
|
removeDownload(state.modelId)
|
||||||
|
},
|
||||||
|
[removeDownload]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.debug('DownloadListener: registering event listeners...')
|
||||||
|
events.on(DownloadEvent.onFileDownloadUpdate, onFileDownloadUpdate)
|
||||||
|
events.on(DownloadEvent.onFileDownloadError, onFileDownloadError)
|
||||||
|
events.on(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess)
|
||||||
|
events.on(DownloadEvent.onFileDownloadStopped, onFileDownloadStopped)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.debug('DownloadListener: unregistering event listeners...')
|
||||||
|
events.off(DownloadEvent.onFileDownloadUpdate, onFileDownloadUpdate)
|
||||||
|
events.off(DownloadEvent.onFileDownloadError, onFileDownloadError)
|
||||||
|
events.off(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess)
|
||||||
|
events.off(DownloadEvent.onFileDownloadStopped, onFileDownloadStopped)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
onFileDownloadUpdate,
|
||||||
|
onFileDownloadError,
|
||||||
|
onFileDownloadSuccess,
|
||||||
|
onFileDownloadStopped,
|
||||||
|
])
|
||||||
|
|
||||||
|
function renderGB(bytes: number): string {
|
||||||
|
const gb = bytes / 1024 ** 3
|
||||||
|
return ((gb * 100) / 100).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
|
{downloadCount > 0 && (
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<div className="bg-left-panel-fg/10 hover:bg-left-panel-fg/12 p-2 rounded-md my-1 relative border border-left-panel-fg/10 cursor-pointer text-left">
|
<div className="bg-left-panel-fg/10 hover:bg-left-panel-fg/12 p-2 rounded-md my-1 relative border border-left-panel-fg/10 cursor-pointer text-left">
|
||||||
<div className="bg-primary font-bold size-5 rounded-full absolute -top-2 -right-1 flex items-center justify-center text-primary-fg">
|
<div className="bg-primary font-bold size-5 rounded-full absolute -top-2 -right-1 flex items-center justify-center text-primary-fg">
|
||||||
2
|
{downloadCount}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-left-panel-fg/80 font-medium">Downloads</p>
|
<p className="text-left-panel-fg/80 font-medium">Downloads</p>
|
||||||
<div className="mt-2 flex items-center justify-between space-x-2">
|
<div className="mt-2 flex items-center justify-between space-x-2">
|
||||||
<Progress value={20} />
|
<Progress value={overallProgress * 100} />
|
||||||
<span className="text-xs font-medium text-main-view-fg/80 shrink-0">
|
<span className="text-xs font-medium text-main-view-fg/80 shrink-0">
|
||||||
20%
|
{overallProgress.toFixed(2)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
)}
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
side="right"
|
side="right"
|
||||||
align="end"
|
align="end"
|
||||||
@ -34,51 +131,33 @@ export function DownloadManagement() {
|
|||||||
<p className="text-xs text-main-view-fg/70">Downloading</p>
|
<p className="text-xs text-main-view-fg/70">Downloading</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 max-h-[300px] overflow-y-auto space-y-2">
|
<div className="p-2 max-h-[300px] overflow-y-auto space-y-2">
|
||||||
<div className="bg-main-view-fg/4 rounded-md p-2">
|
{downloadProcesses.map((download) => (
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<p className="truncate text-main-view-fg/80">llama3.2:1b</p>
|
|
||||||
<div className="shrink-0 flex items-center space-x-0.5">
|
|
||||||
<IconPlayerPauseFilled
|
|
||||||
size={16}
|
|
||||||
className="text-main-view-fg/70 cursor-pointer"
|
|
||||||
title="Pause download"
|
|
||||||
/>
|
|
||||||
<IconX
|
|
||||||
size={16}
|
|
||||||
className="text-main-view-fg/70 cursor-pointer"
|
|
||||||
title="Cancel download"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Progress value={25} className="my-2" />
|
|
||||||
<p className="text-main-view-fg/60 text-xs">
|
|
||||||
1065.28 MB/4.13 GB (25%)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-main-view-fg/4 rounded-md p-2">
|
<div className="bg-main-view-fg/4 rounded-md p-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="truncate text-main-view-fg/80">
|
<p className="truncate text-main-view-fg/80">
|
||||||
deepseek-r1:1.5b
|
{download.name}
|
||||||
</p>
|
</p>
|
||||||
<div className="shrink-0 flex items-center space-x-0.5">
|
<div className="shrink-0 flex items-center space-x-0.5">
|
||||||
<IconPlayerPauseFilled
|
{/* <IconPlayerPauseFilled
|
||||||
size={16}
|
size={16}
|
||||||
className="text-main-view-fg/70 cursor-pointer"
|
className="text-main-view-fg/70 cursor-pointer"
|
||||||
title="Pause download"
|
title="Pause download"
|
||||||
/>
|
/> */}
|
||||||
<IconX
|
<IconX
|
||||||
size={16}
|
size={16}
|
||||||
className="text-main-view-fg/70 cursor-pointer"
|
className="text-main-view-fg/70 cursor-pointer"
|
||||||
title="Cancel download"
|
title="Cancel download"
|
||||||
|
onClick={() => abortDownload(download.name)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={80} className="my-2" />
|
<Progress value={download.progress * 100} className="my-2" />
|
||||||
<p className="text-main-view-fg/60 text-xs">
|
<p className="text-main-view-fg/60 text-xs">
|
||||||
1065.28 MB/4.13 GB (80%)
|
{`${renderGB(download.current)} / ${renderGB(download.total)}`}{' '}
|
||||||
|
GB ({download.progress.toFixed(2)}%)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|||||||
49
web-app/src/hooks/useDownloadStore.ts
Normal file
49
web-app/src/hooks/useDownloadStore.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
export interface DownloadProgressProps {
|
||||||
|
id: string
|
||||||
|
progress: number
|
||||||
|
name: string
|
||||||
|
current: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zustand store for thinking block state
|
||||||
|
export type DownloadState = {
|
||||||
|
downloads: { [id: string]: DownloadProgressProps }
|
||||||
|
removeDownload: (id: string) => void
|
||||||
|
updateProgress: (
|
||||||
|
id: string,
|
||||||
|
progress: number,
|
||||||
|
name?: string,
|
||||||
|
current?: number,
|
||||||
|
total?: number
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This store is used to manage the download progress of files.
|
||||||
|
*/
|
||||||
|
export const useDownloadStore = create<DownloadState>((set) => ({
|
||||||
|
downloads: {},
|
||||||
|
removeDownload: (id: string) =>
|
||||||
|
set((state) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { [id]: _, ...rest } = state.downloads
|
||||||
|
return { downloads: rest }
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateProgress: (id, progress, name, current, total) =>
|
||||||
|
set((state) => ({
|
||||||
|
downloads: {
|
||||||
|
...state.downloads,
|
||||||
|
[id]: {
|
||||||
|
...state.downloads[id],
|
||||||
|
name: name || state.downloads[id]?.name || '',
|
||||||
|
progress,
|
||||||
|
current: current || state.downloads[id]?.current || 0,
|
||||||
|
total: total || state.downloads[id]?.total || 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}))
|
||||||
@ -20,6 +20,7 @@ import { normalizeProvider } from './models'
|
|||||||
import { MCPTool } from '@/types/completion'
|
import { MCPTool } from '@/types/completion'
|
||||||
import { CompletionMessagesBuilder } from './messages'
|
import { CompletionMessagesBuilder } from './messages'
|
||||||
import { ChatCompletionMessageToolCall } from 'openai/resources'
|
import { ChatCompletionMessageToolCall } from 'openai/resources'
|
||||||
|
import { callTool } from '@/services/mcp'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview Helper functions for creating thread content.
|
* @fileoverview Helper functions for creating thread content.
|
||||||
@ -224,8 +225,7 @@ export const extractToolCall = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (deltaToolCalls[0]?.function?.arguments) {
|
if (deltaToolCalls[0]?.function?.arguments) {
|
||||||
currentCall!.function.arguments +=
|
currentCall!.function.arguments += deltaToolCalls[0].function.arguments
|
||||||
deltaToolCalls[0].function.arguments
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,7 +268,7 @@ export const postMessageProcessing = async (
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await window.core.api.callTool({
|
const result = await callTool({
|
||||||
toolName: toolCall.function.name,
|
toolName: toolCall.function.name,
|
||||||
arguments: toolCall.function.arguments.length
|
arguments: toolCall.function.arguments.length
|
||||||
? JSON.parse(toolCall.function.arguments)
|
? JSON.parse(toolCall.function.arguments)
|
||||||
|
|||||||
@ -65,5 +65,5 @@ export const extractModelRepo = (model?: string) => {
|
|||||||
*/
|
*/
|
||||||
export const normalizeProvider = (provider: string) => {
|
export const normalizeProvider = (provider: string) => {
|
||||||
// TODO: After migrating to the new provider extension, remove this function
|
// TODO: After migrating to the new provider extension, remove this function
|
||||||
return provider === 'llama.cpp' ? 'llama-cpp' : provider
|
return provider === 'llama.cpp' ? 'cortex' : provider
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ExtensionManager } from '@/lib/extension'
|
import { ExtensionManager } from '@/lib/extension'
|
||||||
import { APIs } from '@/lib/service'
|
import { APIs } from '@/lib/service'
|
||||||
|
import { EventEmitter } from '@/services/eventsService'
|
||||||
import { EngineManager, ModelManager } from '@janhq/core'
|
import { EngineManager, ModelManager } from '@janhq/core'
|
||||||
import { PropsWithChildren, useCallback, useEffect, useState } from 'react'
|
import { PropsWithChildren, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ export function ExtensionProvider({ children }: PropsWithChildren) {
|
|||||||
api: APIs,
|
api: APIs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.core.events = new EventEmitter()
|
||||||
window.core.extensionManager = new ExtensionManager()
|
window.core.extensionManager = new ExtensionManager()
|
||||||
window.core.engineManager = new EngineManager()
|
window.core.engineManager = new EngineManager()
|
||||||
window.core.modelManager = new ModelManager()
|
window.core.modelManager = new ModelManager()
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
import { downloadModel } from '@/services/models'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const Route = createFileRoute(route.hub as any)({
|
export const Route = createFileRoute(route.hub as any)({
|
||||||
@ -153,7 +154,13 @@ function Hub() {
|
|||||||
<span className="text-main-view-fg/70 font-medium text-xs">
|
<span className="text-main-view-fg/70 font-medium text-xs">
|
||||||
{toGigabytes(model.models?.[0]?.size)}
|
{toGigabytes(model.models?.[0]?.size)}
|
||||||
</span>
|
</span>
|
||||||
<Button>Download</Button>
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
downloadModel(model.models[0]?.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -232,6 +239,9 @@ function Hub() {
|
|||||||
<div
|
<div
|
||||||
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
|
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
|
||||||
title="Edit All Servers JSON"
|
title="Edit All Servers JSON"
|
||||||
|
onClick={() =>
|
||||||
|
downloadModel(variant.id)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<IconDownload
|
<IconDownload
|
||||||
size={16}
|
size={16}
|
||||||
|
|||||||
42
web-app/src/services/eventsService.ts
Normal file
42
web-app/src/services/eventsService.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||||
|
export class EventEmitter {
|
||||||
|
private handlers: Map<string, Function[]>
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.handlers = new Map<string, Function[]>()
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(eventName: string, handler: Function): void {
|
||||||
|
if (!this.handlers.has(eventName)) {
|
||||||
|
this.handlers.set(eventName, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handlers.get(eventName)?.push(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
public off(eventName: string, handler: Function): void {
|
||||||
|
if (!this.handlers.has(eventName)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = this.handlers.get(eventName)
|
||||||
|
const index = handlers?.indexOf(handler)
|
||||||
|
|
||||||
|
if (index !== undefined && index !== -1) {
|
||||||
|
handlers?.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public emit(eventName: string, args: any): void {
|
||||||
|
if (!this.handlers.has(eventName)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = this.handlers.get(eventName)
|
||||||
|
|
||||||
|
handlers?.forEach((handler) => {
|
||||||
|
handler(args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { MCPTool } from '@/types/completion'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description This file contains the functions to interact with the MCP API.
|
* @description This file contains the functions to interact with the MCP API.
|
||||||
@ -8,3 +9,24 @@ export const updateMCPConfig = async (configs: string) => {
|
|||||||
await window.core?.api?.saveMcpConfigs({ configs })
|
await window.core?.api?.saveMcpConfigs({ configs })
|
||||||
await window.core?.api?.restartMcpServers()
|
await window.core?.api?.restartMcpServers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description This function gets the MCP configuration.
|
||||||
|
* @returns {Promise<string>} The MCP configuration.
|
||||||
|
*/
|
||||||
|
export const getTools = (): Promise<MCPTool[]> => {
|
||||||
|
return window.core?.api?.getTools()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description This function invoke an MCP tool
|
||||||
|
* @param tool
|
||||||
|
* @param params
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const callTool = (args: {
|
||||||
|
toolName: string
|
||||||
|
arguments: object
|
||||||
|
}): Promise<unknown> => {
|
||||||
|
return window.core?.api?.callTool(args)
|
||||||
|
}
|
||||||
|
|||||||
@ -97,3 +97,43 @@ export const updateModel = async (
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a model.
|
||||||
|
* @param model The model to download.
|
||||||
|
* @returns A promise that resolves when the model download task is created.
|
||||||
|
*/
|
||||||
|
export const downloadModel = async (id: string) => {
|
||||||
|
const extension = ExtensionManager.getInstance().get<ModelExtension>(
|
||||||
|
ExtensionTypeEnum.Model
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!extension) throw new Error('Model extension not found')
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await extension.pullModel(id)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download model:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts a model download.
|
||||||
|
* @param id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const abortDownload = async (id: string) => {
|
||||||
|
const extension = ExtensionManager.getInstance().get<ModelExtension>(
|
||||||
|
ExtensionTypeEnum.Model
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!extension) throw new Error('Model extension not found')
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await extension.cancelModelPull(id)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to abort model download:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user