mrege
commit
ebaa94be15
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="46" height="24" viewBox="0 0 46 24" fill="none">
|
||||
<path opacity="0.5" d="M-6.5 8C-6.5 3.58172 -2.91828 0 1.5 0H45.5L33.0248 24H1.49999C-2.91829 24 -6.5 20.4183 -6.5 16V8Z" fill="url(#paint0_linear_6333_42118)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_6333_42118" x1="1.81679" y1="5.47784e-07" x2="101.257" y2="30.3866" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.12"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.3"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="sparkles-soft">
|
||||
<path id="Vector" opacity="0.5" d="M10.9963 1.36798C10.9839 1.25339 10.8909 1.16677 10.7802 1.16666C10.6695 1.16654 10.5763 1.25295 10.5636 1.36752C10.5045 1.90085 10.3525 2.26673 10.1143 2.5149C9.87599 2.76307 9.52476 2.92145 9.01275 2.98296C8.90277 2.99618 8.81983 3.09324 8.81995 3.20856C8.82006 3.32388 8.90322 3.42076 9.0132 3.43373C9.51653 3.49312 9.87583 3.65148 10.1201 3.90135C10.3631 4.14986 10.518 4.51523 10.563 5.04321C10.573 5.16035 10.6673 5.25012 10.7802 5.24999C10.8931 5.24986 10.9872 5.15987 10.9969 5.0427C11.0401 4.52364 11.1949 4.15004 11.4394 3.89528C11.684 3.64052 12.0426 3.47926 12.5409 3.43433C12.6534 3.42419 12.7398 3.32619 12.7399 3.20858C12.7401 3.09097 12.6539 2.99277 12.5414 2.98236C12.0346 2.93546 11.6838 2.77407 11.4452 2.52098C11.2054 2.2665 11.0533 1.89229 10.9963 1.36798Z" fill="#F5F8FF"/>
|
||||
<path id="Vector_2" d="M7.13646 2.85102C7.10442 2.55638 6.8653 2.33365 6.5806 2.33334C6.29595 2.33304 6.05633 2.55526 6.02374 2.84984C5.87186 4.22127 5.48089 5.1621 4.86827 5.80025C4.25565 6.43838 3.35245 6.84566 2.03587 7.00386C1.75307 7.03781 1.53975 7.28742 1.54004 7.58393C1.54033 7.88049 1.75415 8.12958 2.03701 8.16294C3.33132 8.31566 4.25509 8.72289 4.88328 9.36543C5.50807 10.0045 5.90647 10.9439 6.02222 12.3016C6.04793 12.6029 6.29035 12.8337 6.58066 12.8333C6.87102 12.833 7.11294 12.6016 7.13797 12.3003C7.24885 10.9656 7.64695 10.0049 8.27583 9.34979C8.90477 8.69471 9.82698 8.28002 11.1083 8.16452C11.3976 8.13844 11.6197 7.88644 11.62 7.58399C11.6204 7.28159 11.3988 7.02906 11.1096 7.00229C9.8062 6.88171 8.90432 6.46673 8.29084 5.81589C7.674 5.16152 7.28306 4.19926 7.13646 2.85102Z" fill="#F5F8FF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,67 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"width": "46",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 46 24",
|
||||
"fill": "none"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"opacity": "0.5",
|
||||
"d": "M-6.5 8C-6.5 3.58172 -2.91828 0 1.5 0H45.5L33.0248 24H1.49999C-2.91829 24 -6.5 20.4183 -6.5 16V8Z",
|
||||
"fill": "url(#paint0_linear_6333_42118)"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "linearGradient",
|
||||
"attributes": {
|
||||
"id": "paint0_linear_6333_42118",
|
||||
"x1": "1.81679",
|
||||
"y1": "5.47784e-07",
|
||||
"x2": "101.257",
|
||||
"y2": "30.3866",
|
||||
"gradientUnits": "userSpaceOnUse"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "stop",
|
||||
"attributes": {
|
||||
"stop-color": "white",
|
||||
"stop-opacity": "0.12"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "stop",
|
||||
"attributes": {
|
||||
"offset": "1",
|
||||
"stop-color": "white",
|
||||
"stop-opacity": "0.3"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Highlight"
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Highlight.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Highlight'
|
||||
|
||||
export default Icon
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "lock"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M8 1.75C6.27411 1.75 4.875 3.14911 4.875 4.875V6.125C3.83947 6.125 3 6.96444 3 8V12.375C3 13.4106 3.83947 14.25 4.875 14.25H11.125C12.1606 14.25 13 13.4106 13 12.375V8C13 6.96444 12.1606 6.125 11.125 6.125V4.875C11.125 3.14911 9.72587 1.75 8 1.75ZM9.875 6.125V4.875C9.875 3.83947 9.03556 3 8 3C6.96444 3 6.125 3.83947 6.125 4.875V6.125H9.875ZM8 8.625C8.34519 8.625 8.625 8.90481 8.625 9.25V11.125C8.625 11.4702 8.34519 11.75 8 11.75C7.65481 11.75 7.375 11.4702 7.375 11.125V9.25C7.375 8.90481 7.65481 8.625 8 8.625Z",
|
||||
"fill": "#155AEF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Lock"
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Lock.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Lock'
|
||||
|
||||
export default Icon
|
||||
@ -0,0 +1,47 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "14",
|
||||
"height": "14",
|
||||
"viewBox": "0 0 14 14",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "sparkles-soft"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"opacity": "0.5",
|
||||
"d": "M10.9963 1.36798C10.9839 1.25339 10.8909 1.16677 10.7802 1.16666C10.6695 1.16654 10.5763 1.25295 10.5636 1.36752C10.5045 1.90085 10.3525 2.26673 10.1143 2.5149C9.87599 2.76307 9.52476 2.92145 9.01275 2.98296C8.90277 2.99618 8.81983 3.09324 8.81995 3.20856C8.82006 3.32388 8.90322 3.42076 9.0132 3.43373C9.51653 3.49312 9.87583 3.65148 10.1201 3.90135C10.3631 4.14986 10.518 4.51523 10.563 5.04321C10.573 5.16035 10.6673 5.25012 10.7802 5.24999C10.8931 5.24986 10.9872 5.15987 10.9969 5.0427C11.0401 4.52364 11.1949 4.15004 11.4394 3.89528C11.684 3.64052 12.0426 3.47926 12.5409 3.43433C12.6534 3.42419 12.7398 3.32619 12.7399 3.20858C12.7401 3.09097 12.6539 2.99277 12.5414 2.98236C12.0346 2.93546 11.6838 2.77407 11.4452 2.52098C11.2054 2.2665 11.0533 1.89229 10.9963 1.36798Z",
|
||||
"fill": "#F5F8FF"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector_2",
|
||||
"d": "M7.13646 2.85102C7.10442 2.55638 6.8653 2.33365 6.5806 2.33334C6.29595 2.33304 6.05633 2.55526 6.02374 2.84984C5.87186 4.22127 5.48089 5.1621 4.86827 5.80025C4.25565 6.43838 3.35245 6.84566 2.03587 7.00386C1.75307 7.03781 1.53975 7.28742 1.54004 7.58393C1.54033 7.88049 1.75415 8.12958 2.03701 8.16294C3.33132 8.31566 4.25509 8.72289 4.88328 9.36543C5.50807 10.0045 5.90647 10.9439 6.02222 12.3016C6.04793 12.6029 6.29035 12.8337 6.58066 12.8333C6.87102 12.833 7.11294 12.6016 7.13797 12.3003C7.24885 10.9656 7.64695 10.0049 8.27583 9.34979C8.90477 8.69471 9.82698 8.28002 11.1083 8.16452C11.3976 8.13844 11.6197 7.88644 11.62 7.58399C11.6204 7.28159 11.3988 7.02906 11.1096 7.00229C9.8062 6.88171 8.90432 6.46673 8.29084 5.81589C7.674 5.16152 7.28306 4.19926 7.13646 2.85102Z",
|
||||
"fill": "#F5F8FF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "SparklesSoft"
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './SparklesSoft.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'SparklesSoft'
|
||||
|
||||
export default Icon
|
||||
@ -0,0 +1,48 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.premium-badge {
|
||||
@apply inline-flex justify-center items-center rounded-full border box-border border-[rgba(255,255,255,0.8)] text-white
|
||||
}
|
||||
|
||||
/* m is for the regular button */
|
||||
.premium-badge-m {
|
||||
@apply border shadow-lg !p-1 h-6 w-auto
|
||||
}
|
||||
|
||||
.premium-badge-s {
|
||||
@apply border-[0.5px] shadow-xs !px-1 !py-[3px] h-[18px] w-auto
|
||||
}
|
||||
|
||||
.premium-badge-blue {
|
||||
@apply bg-gradient-to-r from-[#5289ffe6] to-[#155aefe6] bg-util-colors-blue-blue-200
|
||||
}
|
||||
|
||||
.premium-badge-indigo {
|
||||
@apply bg-gradient-to-r from-[#8098f9e6] to-[#444ce7e6] bg-util-colors-indigo-indigo-200
|
||||
}
|
||||
|
||||
.premium-badge-gray {
|
||||
@apply bg-gradient-to-r from-[#98a2b2e6] to-[#676f83e6] bg-util-colors-gray-gray-200
|
||||
}
|
||||
|
||||
.premium-badge-orange {
|
||||
@apply bg-gradient-to-r from-[#ff692ee6] to-[#e04f16e6] bg-util-colors-orange-orange-200
|
||||
}
|
||||
|
||||
.premium-badge-blue.allowHover:hover {
|
||||
@apply bg-gradient-to-r from-[#296dffe6] to-[#004aebe6] bg-util-colors-blue-blue-300 cursor-pointer
|
||||
}
|
||||
|
||||
.premium-badge-indigo.allowHover:hover {
|
||||
@apply bg-gradient-to-r from-[#6172f3e6] to-[#2d31a6e6] bg-util-colors-indigo-indigo-300 cursor-pointer
|
||||
}
|
||||
|
||||
.premium-badge-gray.allowHover:hover {
|
||||
@apply bg-gradient-to-r from-[#676f83e6] to-[#354052e6] bg-util-colors-gray-gray-300 cursor-pointer
|
||||
}
|
||||
|
||||
.premium-badge-orange.allowHover:hover {
|
||||
@apply bg-gradient-to-r from-[#ff4405e6] to-[#b93815e6] bg-util-colors-orange-orange-300 cursor-pointer
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
import type { CSSProperties, ReactNode } from 'react'
|
||||
import React from 'react'
|
||||
import { type VariantProps, cva } from 'class-variance-authority'
|
||||
import classNames from '@/utils/classnames'
|
||||
import './index.css'
|
||||
import { Highlight } from '../icons/src/public/common'
|
||||
|
||||
const PremiumBadgeVariants = cva(
|
||||
'premium-badge',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
s: 'premium-badge-s',
|
||||
m: 'premium-badge-m',
|
||||
},
|
||||
color: {
|
||||
blue: 'premium-badge-blue',
|
||||
indigo: 'premium-badge-indigo',
|
||||
gray: 'premium-badge-gray',
|
||||
orange: 'premium-badge-orange',
|
||||
},
|
||||
allowHover: {
|
||||
true: 'allowHover',
|
||||
false: '',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'm',
|
||||
color: 'blue',
|
||||
allowHover: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
type PremiumBadgeProps = {
|
||||
size?: 's' | 'm'
|
||||
color?: 'blue' | 'indigo' | 'gray' | 'orange'
|
||||
allowHover?: boolean
|
||||
styleCss?: CSSProperties
|
||||
children?: ReactNode
|
||||
} & React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof PremiumBadgeVariants>
|
||||
|
||||
const PremiumBadge: React.FC<PremiumBadgeProps> = ({
|
||||
className,
|
||||
size,
|
||||
color,
|
||||
allowHover,
|
||||
styleCss,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
PremiumBadgeVariants({ size, color, allowHover, className }),
|
||||
'relative text-nowrap',
|
||||
)}
|
||||
style={styleCss}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<Highlight
|
||||
className={classNames(
|
||||
'absolute top-0 opacity-50 hover:opacity-80',
|
||||
size === 's' ? 'h-4.5 w-12' : 'h-6 w-12',
|
||||
)}
|
||||
style={{
|
||||
right: '50%',
|
||||
transform: 'translateX(10%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
PremiumBadge.displayName = 'PremiumBadge'
|
||||
|
||||
export default PremiumBadge
|
||||
export { PremiumBadge, PremiumBadgeVariants }
|
||||
@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { InstallStep } from '../../types'
|
||||
import type { Dependency } from '../../types'
|
||||
import Install from './steps/install'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
export enum InstallType {
|
||||
fromLocal = 'fromLocal',
|
||||
fromMarketplace = 'fromMarketplace',
|
||||
fromDSL = 'fromDSL',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
installType?: InstallType
|
||||
fromDSLPayload: Dependency[]
|
||||
// plugins?: PluginDeclaration[]
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const InstallBundle: FC<Props> = ({
|
||||
installType = InstallType.fromMarketplace,
|
||||
fromDSLPayload,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [step, setStep] = useState<InstallStep>(installType === InstallType.fromMarketplace ? InstallStep.readyToInstall : InstallStep.uploading)
|
||||
|
||||
const getTitle = useCallback(() => {
|
||||
if (step === InstallStep.uploadFailed)
|
||||
return t(`${i18nPrefix}.uploadFailed`)
|
||||
if (step === InstallStep.installed)
|
||||
return t(`${i18nPrefix}.installedSuccessfully`)
|
||||
if (step === InstallStep.installFailed)
|
||||
return t(`${i18nPrefix}.installFailed`)
|
||||
|
||||
return t(`${i18nPrefix}.installPlugin`)
|
||||
}, [step, t])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={onClose}
|
||||
className='flex min-w-[560px] p-0 flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadows-shadow-xl'
|
||||
closable
|
||||
>
|
||||
<div className='flex pt-6 pl-6 pb-3 pr-14 items-start gap-2 self-stretch'>
|
||||
<div className='self-stretch text-text-primary title-2xl-semi-bold'>
|
||||
{getTitle()}
|
||||
</div>
|
||||
</div>
|
||||
{step === InstallStep.readyToInstall && (
|
||||
<Install
|
||||
fromDSLPayload={fromDSLPayload}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(InstallBundle)
|
||||
@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import type { Dependency, Plugin } from '../../../types'
|
||||
import { pluginManifestToCardPluginProps } from '../../utils'
|
||||
import { useUploadGitHub } from '@/service/use-plugins'
|
||||
import Loading from './loading'
|
||||
import LoadedItem from './loaded-item'
|
||||
|
||||
type Props = {
|
||||
checked: boolean
|
||||
onCheckedChange: (plugin: Plugin) => void
|
||||
dependency: Dependency
|
||||
onFetchedPayload: (payload: Plugin) => void
|
||||
}
|
||||
|
||||
const Item: FC<Props> = ({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
dependency,
|
||||
onFetchedPayload,
|
||||
}) => {
|
||||
const info = dependency.value
|
||||
const { data } = useUploadGitHub({
|
||||
repo: info.repo!,
|
||||
version: info.version!,
|
||||
package: info.package!,
|
||||
})
|
||||
const [payload, setPayload] = React.useState<Plugin | null>(null)
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const payload = pluginManifestToCardPluginProps(data.manifest)
|
||||
onFetchedPayload(payload)
|
||||
setPayload(payload)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data])
|
||||
if (!payload) return <Loading />
|
||||
return (
|
||||
<LoadedItem
|
||||
payload={payload}
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(Item)
|
||||
@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { Plugin } from '../../../types'
|
||||
import Card from '../../../card'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
||||
|
||||
type Props = {
|
||||
checked: boolean
|
||||
onCheckedChange: (plugin: Plugin) => void
|
||||
payload: Plugin
|
||||
}
|
||||
|
||||
const LoadedItem: FC<Props> = ({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
payload,
|
||||
}) => {
|
||||
return (
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Checkbox
|
||||
className='shrink-0'
|
||||
checked={checked}
|
||||
onCheck={() => onCheckedChange(payload)}
|
||||
/>
|
||||
<Card
|
||||
className='grow'
|
||||
payload={payload}
|
||||
titleLeft={payload.version ? <Badge className='mx-1' size="s" state={BadgeState.Default}>{payload.version}</Badge> : null}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(LoadedItem)
|
||||
@ -0,0 +1,23 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import Placeholder from '../../../card/base/placeholder'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Checkbox
|
||||
className='shrink-0'
|
||||
checked={false}
|
||||
disabled
|
||||
/>
|
||||
<div className='grow relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs'>
|
||||
<Placeholder
|
||||
wrapClassName='w-full'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Loading)
|
||||
@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { Plugin } from '../../../types'
|
||||
import Loading from './loading'
|
||||
import LoadedItem from './loaded-item'
|
||||
|
||||
type Props = {
|
||||
checked: boolean
|
||||
onCheckedChange: (plugin: Plugin) => void
|
||||
payload?: Plugin
|
||||
}
|
||||
|
||||
const MarketPlaceItem: FC<Props> = ({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
payload,
|
||||
}) => {
|
||||
if (!payload) return <Loading />
|
||||
return (
|
||||
<LoadedItem
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
payload={payload}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(MarketPlaceItem)
|
||||
@ -0,0 +1,90 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import type { Dependency, Plugin } from '../../../types'
|
||||
import MarketplaceItem from '../item/marketplace-item'
|
||||
import GithubItem from '../item/github-item'
|
||||
import { useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins'
|
||||
import produce from 'immer'
|
||||
import { useGetState } from 'ahooks'
|
||||
|
||||
type Props = {
|
||||
fromDSLPayload: Dependency[]
|
||||
selectedPlugins: Plugin[]
|
||||
onSelect: (plugin: Plugin, selectedIndex: number) => void
|
||||
onLoadedAllPlugin: () => void
|
||||
}
|
||||
|
||||
const InstallByDSLList: FC<Props> = ({
|
||||
fromDSLPayload,
|
||||
selectedPlugins,
|
||||
onSelect,
|
||||
onLoadedAllPlugin,
|
||||
}) => {
|
||||
const { isLoading: isFetchingMarketplaceData, data: marketplaceRes } = useFetchPluginsInMarketPlaceByIds(fromDSLPayload.filter(d => d.type === 'marketplace').map(d => d.value.plugin_unique_identifier!))
|
||||
|
||||
const [plugins, setPlugins, getPlugins] = useGetState<Plugin[]>([])
|
||||
const handlePlugInFetched = useCallback((index: number) => {
|
||||
return (p: Plugin) => {
|
||||
setPlugins(plugins.map((item, i) => i === index ? p : item))
|
||||
}
|
||||
}, [plugins])
|
||||
|
||||
const marketPlaceInDSLIndex = useMemo(() => {
|
||||
const res: number[] = []
|
||||
fromDSLPayload.forEach((d, index) => {
|
||||
if (d.type === 'marketplace')
|
||||
res.push(index)
|
||||
})
|
||||
return res
|
||||
}, [fromDSLPayload])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetchingMarketplaceData && marketplaceRes?.data.plugins && marketplaceRes?.data.plugins.length > 0) {
|
||||
const payloads = marketplaceRes?.data.plugins
|
||||
|
||||
const nextPlugins = produce(getPlugins(), (draft) => {
|
||||
marketPlaceInDSLIndex.forEach((index, i) => {
|
||||
draft[index] = payloads[i]
|
||||
})
|
||||
})
|
||||
setPlugins(nextPlugins)
|
||||
// marketplaceRes?.data.plugins
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isFetchingMarketplaceData])
|
||||
|
||||
const isLoadedAllData = fromDSLPayload.length === plugins.length && plugins.every(p => !!p)
|
||||
useEffect(() => {
|
||||
if (isLoadedAllData)
|
||||
onLoadedAllPlugin()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoadedAllData])
|
||||
|
||||
const handleSelect = useCallback((index: number) => {
|
||||
return () => {
|
||||
onSelect(plugins[index], index)
|
||||
}
|
||||
}, [onSelect, plugins])
|
||||
return (
|
||||
<>
|
||||
{fromDSLPayload.map((d, index) => (
|
||||
d.type === 'github'
|
||||
? <GithubItem
|
||||
key={index}
|
||||
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
||||
onCheckedChange={handleSelect(index)}
|
||||
dependency={d}
|
||||
onFetchedPayload={handlePlugInFetched(index)}
|
||||
/>
|
||||
: <MarketplaceItem
|
||||
key={index}
|
||||
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
||||
onCheckedChange={handleSelect(index)}
|
||||
payload={plugins[index] as Plugin}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(InstallByDSLList)
|
||||
@ -0,0 +1,88 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import type { Dependency, Plugin } from '../../../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import InstallByDSLList from './install-by-dsl-list'
|
||||
import { useInstallFromMarketplaceAndGitHub } from '@/service/use-plugins'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
type Props = {
|
||||
fromDSLPayload: Dependency[]
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const Install: FC<Props> = ({
|
||||
fromDSLPayload,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([])
|
||||
const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([])
|
||||
const selectedPluginsNum = selectedPlugins.length
|
||||
|
||||
const handleSelect = (plugin: Plugin, selectedIndex: number) => {
|
||||
const isSelected = !!selectedPlugins.find(p => p.plugin_id === plugin.plugin_id)
|
||||
let nextSelectedPlugins
|
||||
if (isSelected)
|
||||
nextSelectedPlugins = selectedPlugins.filter(p => p.plugin_id !== plugin.plugin_id)
|
||||
else
|
||||
nextSelectedPlugins = [...selectedPlugins, plugin]
|
||||
setSelectedPlugins(nextSelectedPlugins)
|
||||
const nextSelectedIndexes = isSelected ? selectedIndexes.filter(i => i !== selectedIndex) : [...selectedIndexes, selectedIndex]
|
||||
setSelectedIndexes(nextSelectedIndexes)
|
||||
}
|
||||
const [canInstall, setCanInstall] = React.useState(false)
|
||||
const handleLoadedAllPlugin = useCallback(() => {
|
||||
setCanInstall(true)
|
||||
}, [selectedPlugins, selectedIndexes])
|
||||
|
||||
// Install from marketplace and github
|
||||
const { mutate: installFromMarketplaceAndGitHub, isPending: isInstalling } = useInstallFromMarketplaceAndGitHub({
|
||||
onSuccess: () => {
|
||||
console.log('success!')
|
||||
},
|
||||
})
|
||||
console.log(canInstall, !isInstalling, selectedPlugins.length === 0)
|
||||
const handleInstall = () => {
|
||||
installFromMarketplaceAndGitHub(fromDSLPayload.filter((_d, index) => selectedIndexes.includes(index)))
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col px-6 py-3 justify-center items-start gap-4 self-stretch'>
|
||||
<div className='text-text-secondary system-md-regular'>
|
||||
<p>{t(`${i18nPrefix}.${selectedPluginsNum > 1 ? 'readyToInstallPackages' : 'readyToInstallPackage'}`, { num: selectedPluginsNum })}</p>
|
||||
</div>
|
||||
<div className='w-full p-2 rounded-2xl bg-background-section-burn space-y-1'>
|
||||
<InstallByDSLList
|
||||
fromDSLPayload={fromDSLPayload}
|
||||
selectedPlugins={selectedPlugins}
|
||||
onSelect={handleSelect}
|
||||
onLoadedAllPlugin={handleLoadedAllPlugin}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Action Buttons */}
|
||||
<div className='flex p-6 pt-5 justify-end items-center gap-2 self-stretch'>
|
||||
{!canInstall && (
|
||||
<Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant='primary'
|
||||
className='min-w-[72px] flex space-x-0.5'
|
||||
disabled={!canInstall || isInstalling || selectedPlugins.length === 0}
|
||||
onClick={handleInstall}
|
||||
>
|
||||
{isInstalling && <RiLoader2Line className='w-4 h-4 animate-spin-slow' />}
|
||||
<span>{t(`${i18nPrefix}.${isInstalling ? 'installing' : 'install'}`)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(Install)
|
||||
@ -1,86 +0,0 @@
|
||||
import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningFill,
|
||||
RiInstallLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Button from '@/app/components/base/button'
|
||||
// import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
|
||||
import { useMemo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const InstallInfo = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const status = 'error'
|
||||
const statusError = useMemo(() => status === 'error', [status])
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 79,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<Tooltip popupContent='Installing 1/3 plugins...'>
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex items-center justify-center w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs hover:bg-components-button-secondary-bg-hover',
|
||||
statusError && 'border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
|
||||
)}
|
||||
>
|
||||
<RiInstallLine
|
||||
className={cn(
|
||||
'w-4 h-4 text-components-button-secondary-text',
|
||||
statusError && 'text-components-button-destructive-secondary-text',
|
||||
)}
|
||||
/>
|
||||
<div className='absolute -right-1 -top-1'>
|
||||
{/* <ProgressCircle
|
||||
percentage={33}
|
||||
circleFillColor='fill-components-progress-brand-bg'
|
||||
sectorFillColor='fill-components-progress-error-bg'
|
||||
circleStrokeColor='stroke-components-progress-error-bg'
|
||||
/> */}
|
||||
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<div className='p-1 pb-2 w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
<div className='flex items-center px-2 pt-1 h-7 system-sm-semibold-uppercase'>3 plugins failed to install</div>
|
||||
<div className='flex items-center p-1 pl-2 h-8 rounded-lg hover:bg-state-base-hover'>
|
||||
<div className='relative flex items-center justify-center mr-2 w-6 h-6 rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'>
|
||||
<RiErrorWarningFill className='absolute -right-0.5 -bottom-0.5 w-3 h-3 text-text-destructive' />
|
||||
</div>
|
||||
<div className='grow system-md-regular text-text-secondary truncate'>
|
||||
DuckDuckGo Search
|
||||
</div>
|
||||
<Button
|
||||
size='small'
|
||||
variant='ghost-accent'
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstallInfo
|
||||
@ -0,0 +1,47 @@
|
||||
import { useCallback } from 'react'
|
||||
import { TaskStatus } from '@/app/components/plugins/types'
|
||||
import type { PluginStatus } from '@/app/components/plugins/types'
|
||||
import {
|
||||
useMutationClearTaskPlugin,
|
||||
usePluginTaskList,
|
||||
} from '@/service/use-plugins'
|
||||
|
||||
export const usePluginTaskStatus = () => {
|
||||
const {
|
||||
pluginTasks,
|
||||
} = usePluginTaskList()
|
||||
const { mutate } = useMutationClearTaskPlugin()
|
||||
const allPlugins = pluginTasks.map(task => task.plugins.map((plugin) => {
|
||||
return {
|
||||
...plugin,
|
||||
taskId: task.id,
|
||||
}
|
||||
})).flat()
|
||||
const errorPlugins: PluginStatus[] = []
|
||||
const successPlugins: PluginStatus[] = []
|
||||
const runningPlugins: PluginStatus[] = []
|
||||
|
||||
allPlugins.forEach((plugin) => {
|
||||
if (plugin.status === TaskStatus.running)
|
||||
runningPlugins.push(plugin)
|
||||
if (plugin.status === TaskStatus.failed)
|
||||
errorPlugins.push(plugin)
|
||||
if (plugin.status === TaskStatus.success)
|
||||
successPlugins.push(plugin)
|
||||
})
|
||||
|
||||
const handleClearErrorPlugin = useCallback((taskId: string, pluginId: string) => {
|
||||
mutate({
|
||||
taskId,
|
||||
pluginId,
|
||||
})
|
||||
}, [mutate])
|
||||
|
||||
return {
|
||||
errorPlugins,
|
||||
successPlugins,
|
||||
runningPlugins,
|
||||
totalPluginsLength: allPlugins.length,
|
||||
handleClearErrorPlugin,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningFill,
|
||||
RiInstallLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePluginTaskStatus } from './hooks'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
|
||||
import CardIcon from '@/app/components/plugins/card/base/card-icon'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
|
||||
const PluginTasks = () => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const [open, setOpen] = useState(false)
|
||||
const {
|
||||
errorPlugins,
|
||||
runningPlugins,
|
||||
successPlugins,
|
||||
totalPluginsLength,
|
||||
handleClearErrorPlugin,
|
||||
} = usePluginTaskStatus()
|
||||
const { getIconUrl } = useGetIcon()
|
||||
|
||||
const isInstalling = runningPlugins.length > 0 && errorPlugins.length === 0 && successPlugins.length === 0
|
||||
const isInstallingWithError = errorPlugins.length > 0 && errorPlugins.length < totalPluginsLength
|
||||
const isSuccess = successPlugins.length === totalPluginsLength && totalPluginsLength > 0
|
||||
const isFailed = errorPlugins.length === totalPluginsLength && totalPluginsLength > 0
|
||||
|
||||
const tip = useMemo(() => {
|
||||
if (isInstalling)
|
||||
return t('plugin.task.installing', { installingLength: runningPlugins.length, totalLength: totalPluginsLength })
|
||||
|
||||
if (isInstallingWithError)
|
||||
return t('plugin.task.installingWithError', { installingLength: runningPlugins.length, totalLength: totalPluginsLength, errorLength: errorPlugins.length })
|
||||
|
||||
if (isFailed)
|
||||
return t('plugin.task.installError', { errorLength: errorPlugins.length })
|
||||
}, [isInstalling, isInstallingWithError, isFailed, errorPlugins, runningPlugins, totalPluginsLength, t])
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 79,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => {
|
||||
if (isFailed || isInstallingWithError)
|
||||
setOpen(v => !v)
|
||||
}}
|
||||
>
|
||||
<Tooltip popupContent={tip}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex items-center justify-center w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs hover:bg-components-button-secondary-bg-hover',
|
||||
(isInstallingWithError || isFailed) && 'border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
|
||||
)}
|
||||
>
|
||||
<RiInstallLine
|
||||
className={cn(
|
||||
'w-4 h-4 text-components-button-secondary-text',
|
||||
(isInstallingWithError || isFailed) && 'text-components-button-destructive-secondary-text',
|
||||
)}
|
||||
/>
|
||||
<div className='absolute -right-1 -top-1'>
|
||||
{
|
||||
isInstalling && (
|
||||
<ProgressCircle
|
||||
percentage={runningPlugins.length / totalPluginsLength * 100}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
isInstallingWithError && (
|
||||
<ProgressCircle
|
||||
percentage={runningPlugins.length / totalPluginsLength * 100}
|
||||
circleFillColor='fill-components-progress-brand-bg'
|
||||
sectorFillColor='fill-components-progress-error-border'
|
||||
circleStrokeColor='stroke-components-progress-error-border'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
isSuccess && (
|
||||
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
|
||||
)
|
||||
}
|
||||
{
|
||||
isFailed && (
|
||||
<RiErrorWarningFill className='w-3.5 h-3.5 text-text-destructive' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<div className='p-1 pb-2 w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
<div className='flex items-center px-2 pt-1 h-7 system-sm-semibold-uppercase'>{t('plugin.task.installedError')}</div>
|
||||
{
|
||||
errorPlugins.map(errorPlugin => (
|
||||
<div
|
||||
key={errorPlugin.plugin_unique_identifier}
|
||||
className='flex items-center p-1 pl-2 h-8 rounded-lg hover:bg-state-base-hover'
|
||||
>
|
||||
<div className='relative flex items-center justify-center mr-2 w-6 h-6 rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'>
|
||||
<RiErrorWarningFill className='absolute -right-0.5 -bottom-0.5 w-3 h-3 text-text-destructive' />
|
||||
<CardIcon
|
||||
size='tiny'
|
||||
src={getIconUrl(errorPlugin.icon)}
|
||||
/>
|
||||
</div>
|
||||
<div className='grow system-md-regular text-text-secondary truncate'>
|
||||
{errorPlugin.labels[language]}
|
||||
</div>
|
||||
<Button
|
||||
size='small'
|
||||
variant='ghost-accent'
|
||||
onClick={() => handleClearErrorPlugin(errorPlugin.taskId, errorPlugin.plugin_unique_identifier)}
|
||||
>
|
||||
{t('common.operation.clear')}
|
||||
</Button>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PluginTasks
|
||||
@ -1,40 +0,0 @@
|
||||
import { create } from 'zustand'
|
||||
import type { PluginTask } from '../types'
|
||||
import { fetchPluginTasks } from '@/service/plugins'
|
||||
|
||||
type PluginTasksStore = {
|
||||
pluginTasks: PluginTask[]
|
||||
setPluginTasks: (tasks: PluginTask[]) => void
|
||||
setPluginTasksWithPolling: () => void
|
||||
}
|
||||
|
||||
let pluginTasksTimer: NodeJS.Timeout | null = null
|
||||
|
||||
export const usePluginTasksStore = create<PluginTasksStore>(set => ({
|
||||
pluginTasks: [],
|
||||
setPluginTasks: (tasks: PluginTask[]) => set({ pluginTasks: tasks }),
|
||||
setPluginTasksWithPolling: async () => {
|
||||
if (pluginTasksTimer) {
|
||||
clearTimeout(pluginTasksTimer)
|
||||
pluginTasksTimer = null
|
||||
}
|
||||
const handleUpdatePluginTasks = async () => {
|
||||
const { tasks } = await fetchPluginTasks()
|
||||
set({ pluginTasks: tasks })
|
||||
|
||||
if (tasks.length && !tasks.every(task => task.status === 'success')) {
|
||||
pluginTasksTimer = setTimeout(() => {
|
||||
handleUpdatePluginTasks()
|
||||
}, 5000)
|
||||
}
|
||||
else {
|
||||
if (pluginTasksTimer) {
|
||||
clearTimeout(pluginTasksTimer)
|
||||
pluginTasksTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUpdatePluginTasks()
|
||||
},
|
||||
}))
|
||||
@ -0,0 +1,183 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import ToolTrigger from '@/app/components/tools/tool-selector/tool-trigger'
|
||||
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import ToolCredentialForm from '@/app/components/tools/tool-selector/tool-credentials-form'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import {
|
||||
useAllBuiltInTools,
|
||||
useAllCustomTools,
|
||||
useAllWorkflowTools,
|
||||
useInvalidateAllBuiltInTools,
|
||||
useUpdateProviderCredentials,
|
||||
} from '@/service/use-tools'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
|
||||
import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
value?: {
|
||||
provider: string
|
||||
tool_name: string
|
||||
}
|
||||
disabled?: boolean
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
onSelect: (tool: {
|
||||
provider: string
|
||||
tool_name: string
|
||||
}) => void
|
||||
supportAddCustomTool?: boolean
|
||||
}
|
||||
const ToolSelector: FC<Props> = ({
|
||||
value,
|
||||
disabled,
|
||||
placement = 'bottom',
|
||||
offset = 4,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShow, onShowChange] = useState(false)
|
||||
const handleTriggerClick = () => {
|
||||
if (disabled) return
|
||||
onShowChange(true)
|
||||
}
|
||||
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools()
|
||||
const currentProvider = useMemo(() => {
|
||||
const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])]
|
||||
return mergedTools.find((toolWithProvider) => {
|
||||
return toolWithProvider.id === value?.provider && toolWithProvider.tools.some(tool => tool.name === value?.tool_name)
|
||||
})
|
||||
}, [value, buildInTools, customTools, workflowTools])
|
||||
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
|
||||
const handleSelectTool = (tool: ToolDefaultValue) => {
|
||||
const toolValue = {
|
||||
provider: tool.provider_id,
|
||||
tool_name: tool.tool_name,
|
||||
}
|
||||
onSelect(toolValue)
|
||||
setIsShowChooseTool(false)
|
||||
if (tool.provider_type === CollectionType.builtIn && tool.is_team_authorization)
|
||||
onShowChange(false)
|
||||
}
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const [isShowSettingAuth, setShowSettingAuth] = useState(false)
|
||||
const handleCredentialSettingUpdate = () => {
|
||||
invalidateAllBuiltinTools()
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
setShowSettingAuth(false)
|
||||
onShowChange(false)
|
||||
}
|
||||
|
||||
const { mutate: updatePermission } = useUpdateProviderCredentials({
|
||||
onSuccess: handleCredentialSettingUpdate,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
placement={placement}
|
||||
offset={offset}
|
||||
open={isShow}
|
||||
onOpenChange={onShowChange}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
className='w-full'
|
||||
onClick={handleTriggerClick}
|
||||
>
|
||||
<ToolTrigger
|
||||
open={isShow}
|
||||
value={value}
|
||||
provider={currentProvider}
|
||||
/>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className="relative w-[389px] min-h-20 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg">
|
||||
<div className='px-4 py-3 flex flex-col gap-1'>
|
||||
<div className='h-6 flex items-center system-sm-semibold text-text-secondary'>{t('tools.toolSelector.label')}</div>
|
||||
<ToolPicker
|
||||
placement='bottom'
|
||||
offset={offset}
|
||||
trigger={
|
||||
<ToolTrigger
|
||||
open={isShowChooseTool}
|
||||
value={value}
|
||||
provider={currentProvider}
|
||||
/>
|
||||
}
|
||||
isShow={isShowChooseTool}
|
||||
onShowChange={setIsShowChooseTool}
|
||||
disabled={false}
|
||||
supportAddCustomTool
|
||||
onSelect={handleSelectTool}
|
||||
/>
|
||||
</div>
|
||||
{/* authorization panel */}
|
||||
{isShowSettingAuth && currentProvider && (
|
||||
<div className='px-4 pb-4 border-t border-divider-subtle'>
|
||||
<ToolCredentialForm
|
||||
collection={currentProvider}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async value => updatePermission({
|
||||
providerName: currentProvider.name,
|
||||
credentials: value,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!isShowSettingAuth && currentProvider && currentProvider.type === CollectionType.builtIn && currentProvider.is_team_authorization && currentProvider.allow_delete && (
|
||||
<div className='px-4 py-3 flex items-center border-t border-divider-subtle'>
|
||||
<div className='grow mr-3 h-6 flex items-center system-sm-semibold text-text-secondary'>{t('tools.toolSelector.auth')}</div>
|
||||
{isCurrentWorkspaceManager && (
|
||||
<Button
|
||||
variant='secondary'
|
||||
size='small'
|
||||
onClick={() => {}}
|
||||
>
|
||||
<Indicator className='mr-2' color={'green'} />
|
||||
{t('tools.auth.authorized')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isShowSettingAuth && currentProvider && currentProvider.type === CollectionType.builtIn && !currentProvider.is_team_authorization && currentProvider.allow_delete && (
|
||||
<div className='px-4 py-3 flex items-center border-t border-divider-subtle'>
|
||||
<Button
|
||||
variant='primary'
|
||||
className={cn('shrink-0 w-full')}
|
||||
onClick={() => setShowSettingAuth(true)}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>
|
||||
{t('tools.auth.unauthorized')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(ToolSelector)
|
||||
@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
} from '@remixicon/react'
|
||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import type { Collection } from '@/app/components/tools/types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { fetchBuiltInToolCredential, fetchBuiltInToolCredentialSchema } from '@/service/tools'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
collection: Collection
|
||||
onCancel: () => void
|
||||
onSaved: (value: Record<string, any>) => void
|
||||
}
|
||||
|
||||
const ToolCredentialForm: FC<Props> = ({
|
||||
collection,
|
||||
onCancel,
|
||||
onSaved,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const [credentialSchema, setCredentialSchema] = useState<any>(null)
|
||||
const { name: collectionName } = collection
|
||||
const [tempCredential, setTempCredential] = React.useState<any>({})
|
||||
useEffect(() => {
|
||||
fetchBuiltInToolCredentialSchema(collectionName).then(async (res) => {
|
||||
const toolCredentialSchemas = toolCredentialToFormSchemas(res)
|
||||
const credentialValue = await fetchBuiltInToolCredential(collectionName)
|
||||
setTempCredential(credentialValue)
|
||||
const defaultCredentials = addDefaultValue(credentialValue, toolCredentialSchemas)
|
||||
setCredentialSchema(toolCredentialSchemas)
|
||||
setTempCredential(defaultCredentials)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleSave = () => {
|
||||
for (const field of credentialSchema) {
|
||||
if (field.required && !tempCredential[field.name]) {
|
||||
Toast.notify({ type: 'error', message: t('common.errorMsg.fieldRequired', { field: field.label[language] || field.label.en_US }) })
|
||||
return
|
||||
}
|
||||
}
|
||||
onSaved(tempCredential)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full'>
|
||||
{!credentialSchema
|
||||
? <div className='pt-3'><Loading type='app' /></div>
|
||||
: (
|
||||
<>
|
||||
<Form
|
||||
value={tempCredential}
|
||||
onChange={(v) => {
|
||||
setTempCredential(v)
|
||||
}}
|
||||
formSchemas={credentialSchema}
|
||||
isEditMode={true}
|
||||
showOnVariableMap={{}}
|
||||
validating={false}
|
||||
inputClassName='bg-components-input-bg-normal hover:bg-state-base-hover-alt'
|
||||
fieldMoreInfo={item => item.url
|
||||
? (<a
|
||||
href={item.url}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
className='inline-flex items-center text-xs text-primary-600'
|
||||
>
|
||||
{t('tools.howToGet')}
|
||||
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
|
||||
</a>)
|
||||
: null}
|
||||
/>
|
||||
<div className={cn('mt-1 flex justify-end')} >
|
||||
<div className='flex space-x-2'>
|
||||
<Button onClick={onCancel}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
</div >
|
||||
)
|
||||
}
|
||||
export default React.memo(ToolCredentialForm)
|
||||
@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
provider?: ToolWithProvider
|
||||
value?: {
|
||||
provider: string
|
||||
tool_name: string
|
||||
}
|
||||
}
|
||||
|
||||
const ToolTrigger = ({
|
||||
open,
|
||||
provider,
|
||||
value,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className={cn(
|
||||
'group flex items-center p-2 pl-3 bg-components-input-bg-normal rounded-lg cursor-pointer hover:bg-state-base-hover-alt',
|
||||
open && 'bg-state-base-hover-alt',
|
||||
value && 'pl-1.5 py-1.5',
|
||||
)}>
|
||||
{value && provider && (
|
||||
<div className='shrink-0 mr-1 p-px rounded-lg bg-components-panel-bg border border-components-panel-border'>
|
||||
<BlockIcon
|
||||
className='!w-4 !h-4'
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={provider.icon}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{value && (
|
||||
<div className='grow system-sm-regular text-components-input-text-filled'>{value.tool_name}</div>
|
||||
)}
|
||||
{!value && (
|
||||
<div className='grow text-components-input-text-placeholder system-sm-regular'>{t('tools.toolSelector.placeholder')}</div>
|
||||
)}
|
||||
<RiArrowDownSLine className={cn('shrink-0 ml-0.5 w-4 h-4 text-text-quaternary group-hover:text-text-secondary', open && 'text-text-secondary')} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolTrigger
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue