feat: marketplace install

This commit is contained in:
Joel 2024-10-25 16:46:02 +08:00
parent cd27ae4319
commit c777d55a1c
8 changed files with 201 additions and 40 deletions

View File

@ -0,0 +1,55 @@
import { checkTaskStatus as fetchCheckTaskStatus } from '@/service/plugins'
import type { PluginStatus } from '../../types'
import { TaskStatus } from '../../types'
const INTERVAL = 10 * 1000 // 10 seconds
interface Params {
taskId: string
pluginUniqueIdentifier: string
}
function checkTaskStatus() {
let nextStatus = TaskStatus.running
let isStop = false
const doCheckStatus = async ({
taskId,
pluginUniqueIdentifier,
}: Params) => {
if (isStop) return
const { plugins } = await fetchCheckTaskStatus(taskId)
const plugin = plugins.find((p: PluginStatus) => p.plugin_unique_identifier === pluginUniqueIdentifier)
if (!plugin) {
nextStatus = TaskStatus.failed
Promise.reject(new Error('Plugin package not found'))
return
}
nextStatus = plugin.status
if (nextStatus === TaskStatus.running) {
setTimeout(async () => {
await doCheckStatus({
taskId,
pluginUniqueIdentifier,
})
}, INTERVAL)
return
}
if (nextStatus === TaskStatus.failed) {
Promise.reject(plugin.message)
return
}
return ({
status: nextStatus,
})
}
return {
check: doCheckStatus,
stop: () => {
isStop = true
},
}
}
export default checkTaskStatus

View File

@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'
const i18nPrefix = 'plugin.installModal'
type InstallFromLocalPackageProps = {
interface InstallFromLocalPackageProps {
file: File
onSuccess: () => void
onClose: () => void
@ -56,8 +56,10 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
setStep(InstallStep.installed)
}, [])
const handleFailed = useCallback(() => {
const handleFailed = useCallback((errorMsg?: string) => {
setStep(InstallStep.installFailed)
if (errorMsg)
setErrorMsg(errorMsg)
}, [])
return (

View File

@ -5,20 +5,20 @@ import type { PluginDeclaration } from '../../../types'
import Card from '../../../card'
import { pluginManifestToCardPluginProps } from '../../utils'
import Button from '@/app/components/base/button'
import { sleep } from '@/utils'
import { Trans, useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { installPackageFromLocal } from '@/service/plugins'
import checkTaskStatus from '../../base/check-task-status'
const i18nPrefix = 'plugin.installModal'
type Props = {
interface Props {
uniqueIdentifier: string
payload: PluginDeclaration
onCancel: () => void
onInstalled: () => void
onFailed: () => void
onFailed: (message?: string) => void
}
const Installed: FC<Props> = ({
@ -30,18 +30,41 @@ const Installed: FC<Props> = ({
}) => {
const { t } = useTranslation()
const [isInstalling, setIsInstalling] = React.useState(false)
const {
check,
stop,
} = checkTaskStatus()
const handleCancel = () => {
stop()
onCancel()
}
const handleInstall = async () => {
if (isInstalling) return
setIsInstalling(true)
try {
await installPackageFromLocal(uniqueIdentifier)
const {
all_installed: isInstalled,
task_id: taskId,
} = await installPackageFromLocal(uniqueIdentifier)
if (isInstalled) {
onInstalled()
return
}
await check({
taskId,
pluginUniqueIdentifier: uniqueIdentifier,
})
onInstalled()
}
catch (e) {
if (typeof e === 'string') {
onFailed(e)
return
}
onFailed()
}
await sleep(1500)
}
return (
@ -67,7 +90,7 @@ const Installed: FC<Props> = ({
{/* Action Buttons */}
<div className='flex p-6 pt-5 justify-end items-center gap-2 self-stretch'>
{!isInstalling && (
<Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
<Button variant='secondary' className='min-w-[72px]' onClick={handleCancel}>
{t('common.operation.cancel')}
</Button>
)}

View File

@ -10,15 +10,15 @@ import { useTranslation } from 'react-i18next'
const i18nPrefix = 'plugin.installModal'
type InstallFromMarketplaceProps = {
packageId: string
interface InstallFromMarketplaceProps {
uniqueIdentifier: string
manifest: PluginDeclaration
onSuccess: () => void
onClose: () => void
}
const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
packageId,
uniqueIdentifier,
manifest,
onSuccess,
onClose,
@ -26,6 +26,7 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
const { t } = useTranslation()
// readyToInstall -> check installed -> installed/failed
const [step, setStep] = useState<InstallStep>(InstallStep.readyToInstall)
const [errorMsg, setErrorMsg] = useState<string | null>(null)
// TODO: check installed in beta version.
@ -41,8 +42,10 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
setStep(InstallStep.installed)
}, [])
const handleFailed = useCallback(() => {
const handleFailed = useCallback((errorMsg?: string) => {
setStep(InstallStep.installFailed)
if (errorMsg)
setErrorMsg(errorMsg)
}, [])
return (
@ -60,6 +63,7 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
{
step === InstallStep.readyToInstall && (
<Install
uniqueIdentifier={uniqueIdentifier}
payload={manifest!}
onCancel={onClose}
onInstalled={handleInstalled}
@ -72,6 +76,7 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
<Installed
payload={manifest!}
isFailed={step === InstallStep.installFailed}
errMsg={errorMsg}
onCancel={onSuccess}
/>
)

View File

@ -6,21 +6,24 @@ import type { PluginDeclaration } from '../../../types'
import Card from '../../../card'
import { pluginManifestToCardPluginProps } from '../../utils'
import Button from '@/app/components/base/button'
import { sleep } from '@/utils'
import { useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { installPackageFromMarketPlace } from '@/service/plugins'
import checkTaskStatus from '../../base/check-task-status'
const i18nPrefix = 'plugin.installModal'
type Props = {
interface Props {
uniqueIdentifier: string
payload: PluginDeclaration
onCancel: () => void
onInstalled: () => void
onFailed: () => void
onFailed: (message?: string) => void
}
const Installed: FC<Props> = ({
uniqueIdentifier,
payload,
onCancel,
onInstalled,
@ -28,13 +31,42 @@ const Installed: FC<Props> = ({
}) => {
const { t } = useTranslation()
const [isInstalling, setIsInstalling] = React.useState(false)
const {
check,
stop,
} = checkTaskStatus()
const handleCancel = () => {
stop()
onCancel()
}
const handleInstall = async () => {
if (isInstalling) return
setIsInstalling(true)
await sleep(1500)
onInstalled()
// onFailed()
try {
const {
all_installed: isInstalled,
task_id: taskId,
} = await installPackageFromMarketPlace(uniqueIdentifier)
if (isInstalled) {
onInstalled()
return
}
await check({
taskId,
pluginUniqueIdentifier: uniqueIdentifier,
})
onInstalled()
}
catch (e) {
if (typeof e === 'string') {
onFailed(e)
return
}
onFailed()
}
}
const toInstallVersion = '1.3.0'
@ -77,7 +109,7 @@ const Installed: FC<Props> = ({
{/* Action Buttons */}
<div className='flex p-6 pt-5 justify-end items-center gap-2 self-stretch'>
{!isInstalling && (
<Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
<Button variant='secondary' className='min-w-[72px]' onClick={handleCancel}>
{t('common.operation.cancel')}
</Button>
)}

View File

@ -35,7 +35,7 @@ import { sleep } from '@/utils'
const PACKAGE_IDS_KEY = 'package-ids'
export type PluginPageProps = {
export interface PluginPageProps {
plugins: React.ReactNode
marketplace: React.ReactNode
}
@ -74,6 +74,9 @@ const PluginPage = ({
(async () => {
await sleep(100)
if (packageId) {
// setManifest(toolNotionManifest)
// TODO
// const data = await fetchManifest(encodeURIComponent(packageId))
setManifest(toolNotionManifest)
showInstallFromMarketplace()
}
@ -229,7 +232,7 @@ const PluginPage = ({
isShowInstallFromMarketplace && (
<InstallFromMarketplace
manifest={manifest!}
packageId={packageId}
uniqueIdentifier={packageId}
onClose={hideInstallFromMarketplace}
onSuccess={hideInstallFromMarketplace}
/>

View File

@ -15,7 +15,7 @@ export enum PluginSource {
debugging = 'remote',
}
export type PluginToolDeclaration = {
export interface PluginToolDeclaration {
identity: {
author: string
name: string
@ -27,17 +27,17 @@ export type PluginToolDeclaration = {
credentials_schema: ToolCredential[] // TODO
}
export type PluginEndpointDeclaration = {
export interface PluginEndpointDeclaration {
settings: ToolCredential[]
endpoints: EndpointItem[]
}
export type EndpointItem = {
export interface EndpointItem {
path: string
method: string
}
export type EndpointListItem = {
export interface EndpointListItem {
id: string
created_at: string
updated_at: string
@ -53,7 +53,7 @@ export type EndpointListItem = {
}
// Plugin manifest
export type PluginDeclaration = {
export interface PluginDeclaration {
version: string
author: string
icon: string
@ -70,7 +70,7 @@ export type PluginDeclaration = {
model: any // TODO
}
export type PluginDetail = {
export interface PluginDetail {
id: string
created_at: string
updated_at: string
@ -87,7 +87,7 @@ export type PluginDetail = {
meta?: any
}
export type Plugin = {
export interface Plugin {
type: PluginType
org: string
name: string
@ -113,7 +113,7 @@ export enum PermissionType {
noOne = 'noOne',
}
export type Permissions = {
export interface Permissions {
canManagement: PermissionType
canDebugger: PermissionType
}
@ -125,7 +125,7 @@ export enum InstallStepFromGitHub {
installed = 'installed',
}
export type InstallState = {
export interface InstallState {
step: InstallStepFromGitHub
repoUrl: string
selectedVersion: string
@ -133,34 +133,34 @@ export type InstallState = {
releases: GitHubRepoReleaseResponse[]
}
export type GitHubUrlInfo = {
export interface GitHubUrlInfo {
isValid: boolean
owner?: string
repo?: string
}
// endpoint
export type CreateEndpointRequest = {
export interface CreateEndpointRequest {
plugin_unique_identifier: string
settings: Record<string, any>
name: string
}
export type EndpointOperationResponse = {
export interface EndpointOperationResponse {
result: 'success' | 'error'
}
export type EndpointsRequest = {
export interface EndpointsRequest {
limit: number
page: number
plugin_id: string
}
export type EndpointsResponse = {
export interface EndpointsResponse {
endpoints: EndpointListItem[]
has_more: boolean
limit: number
total: number
page: number
}
export type UpdateEndpointRequest = {
export interface UpdateEndpointRequest {
endpoint_id: string
settings: Record<string, any>
name: string
@ -175,23 +175,48 @@ export enum InstallStep {
installFailed = 'failed',
}
export type GitHubAsset = {
export interface GitHubAsset {
id: number
name: string
browser_download_url: string
}
export type GitHubRepoReleaseResponse = {
export interface GitHubRepoReleaseResponse {
tag_name: string
assets: GitHubAsset[]
}
export type InstallPackageResponse = {
export interface InstallPackageResponse {
plugin_unique_identifier: string
all_installed: boolean
task_id: string
}
export type DebugInfo = {
export interface DebugInfo {
key: string
host: string
port: number
}
export enum TaskStatus {
running = 'running',
success = 'success',
failed = 'failed',
}
export interface PluginStatus {
plugin_unique_identifier: string
plugin_id: string
status: TaskStatus
message: string
}
export interface TaskStatusResponse {
id: string
created_at: string
updated_at: string
status: string
total_plugins: number
completed_plugins: number
plugins: PluginStatus[]
}

View File

@ -6,6 +6,8 @@ import type {
EndpointsRequest,
EndpointsResponse,
InstallPackageResponse,
PluginDeclaration,
TaskStatusResponse,
UpdateEndpointRequest,
} from '@/app/components/plugins/types'
import type { DebugInfo as DebugInfoTypes } from '@/app/components/plugins/types'
@ -69,6 +71,16 @@ export const installPackageFromLocal = async (uniqueIdentifier: string) => {
})
}
export const fetchManifest = async (uniqueIdentifier: string) => {
return get<PluginDeclaration>(`/workspaces/current/plugin/fetch-manifest?plugin_unique_identifier=${uniqueIdentifier}`)
}
export const installPackageFromMarketPlace = async (uniqueIdentifier: string) => {
return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
body: { plugin_unique_identifiers: [uniqueIdentifier] },
})
}
export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => {
return get<MarketplaceCollectionsResponse>(url)
}
@ -76,3 +88,7 @@ export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse
export const fetchMarketplaceCollectionPlugins: Fetcher<MarketplaceCollectionPluginsResponse, { url: string }> = ({ url }) => {
return get<MarketplaceCollectionPluginsResponse>(url)
}
export const checkTaskStatus = async (taskId: string) => {
return get<TaskStatusResponse>(`/workspaces/current/plugin/tasks/${taskId}`)
}