mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
mrege
This commit is contained in:
commit
ebaa94be15
|
@ -324,6 +324,8 @@ services:
|
|||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
APP_API_URL: ${APP_API_URL:-}
|
||||
MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-}
|
||||
MARKETPLACE_URL: ${MARKETPLACE_URL:-}
|
||||
SENTRY_DSN: ${WEB_SENTRY_DSN:-}
|
||||
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
|
||||
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
|
||||
|
|
|
@ -41,6 +41,8 @@ ENV EDITION=SELF_HOSTED
|
|||
ENV DEPLOY_ENV=PRODUCTION
|
||||
ENV CONSOLE_API_URL=http://127.0.0.1:5001
|
||||
ENV APP_API_URL=http://127.0.0.1:5001
|
||||
ENV MARKETPLACE_API_URL=http://127.0.0.1:5001
|
||||
ENV MARKETPLACE_URL=http://127.0.0.1:5001
|
||||
ENV PORT=3000
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
|
|
|
@ -1,19 +1,43 @@
|
|||
import { handleDelete } from './actions'
|
||||
'use client'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import { customTool, extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock'
|
||||
import PluginItem from '@/app/components/plugins/plugin-item'
|
||||
// import PluginItem from '@/app/components/plugins/plugin-item'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
// import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import InstallBundle from '@/app/components/plugins/install-plugin/install-bundle'
|
||||
|
||||
const PluginList = async () => {
|
||||
const PluginList = () => {
|
||||
const pluginList = [toolNotion, extensionDallE, modelGPT4, customTool]
|
||||
|
||||
return (
|
||||
<div className='pb-3 bg-white'>
|
||||
<InstallBundle onClose={() => { }} fromDSLPayload={[
|
||||
{
|
||||
type: 'marketplace',
|
||||
value: {
|
||||
plugin_unique_identifier: 'langgenius/google:0.0.2@dcb354c9d0fee60e6e9c9eb996e1e485bbef343ba8cd545c0cfb3ec80970f6f1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'github',
|
||||
value: {
|
||||
repo: 'YIXIAO0/test',
|
||||
version: '1.11.5',
|
||||
package: 'test.difypkg',
|
||||
github_plugin_unique_identifier: 'yixiao0/test:0.0.1@3592166c87afcf944b4f13f27467a5c8f9e00bd349cb42033a072734a37431b4',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'marketplace',
|
||||
value: {
|
||||
plugin_unique_identifier: 'langgenius/openai:0.0.1@f88fdb98d104466db16a425bfe3af8c1bcad45047a40fb802d98a989ac57a5a3',
|
||||
},
|
||||
},
|
||||
]} />
|
||||
<div className='mx-3 '>
|
||||
<h2 className='my-3'>Dify Plugin list</h2>
|
||||
<div className='grid grid-cols-2 gap-3'>
|
||||
{/* <h2 className='my-3'>Dify Plugin list</h2> */}
|
||||
{/* <div className='grid grid-cols-2 gap-3'>
|
||||
{pluginList.map((plugin, index) => (
|
||||
<PluginItem
|
||||
key={index}
|
||||
|
@ -21,7 +45,7 @@ const PluginList = async () => {
|
|||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<h2 className='my-3'>Install Plugin / Package under bundle</h2>
|
||||
<div className='w-[512px] rounded-2xl bg-background-section-burn p-2'>
|
||||
|
@ -33,21 +57,21 @@ const PluginList = async () => {
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
<h3 className='my-1'>Installed</h3>
|
||||
{/* <h3 className='my-1'>Installed</h3>
|
||||
<div className='w-[512px] rounded-2xl bg-background-section-burn p-2'>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
descriptionLineRows={1}
|
||||
installed
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<h3 className='my-1'>Install model provide</h3>
|
||||
{/* <h3 className='my-1'>Install model provide</h3>
|
||||
<div className='grid grid-cols-2 gap-3'>
|
||||
{pluginList.map((plugin, index) => (
|
||||
<ProviderCard key={index} payload={plugin as any} />
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='my-3 h-[px] bg-gray-50'></div>
|
||||
<h2 className='my-3'>Marketplace Plugin list</h2>
|
||||
|
@ -67,8 +91,8 @@ const PluginList = async () => {
|
|||
)
|
||||
}
|
||||
|
||||
export const metadata = {
|
||||
title: 'Plugins - Card',
|
||||
}
|
||||
// export const metadata = {
|
||||
// title: 'Plugins - Card',
|
||||
// }
|
||||
|
||||
export default PluginList
|
||||
|
|
|
@ -23,7 +23,7 @@ const FeaturePanel: FC<IFeaturePanelProps> = ({
|
|||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn pb-3', noBodySpacing && '!pb-0', className)}>
|
||||
<div className={cn('rounded-xl border-t-[0.5px] border-l-[0.5px] bg-background-section-burn pb-3', noBodySpacing && 'pb-0', className)}>
|
||||
{/* Header */}
|
||||
<div className={cn('px-3 pt-2', hasHeaderBottomBorder && 'border-b border-divider-subtle')}>
|
||||
<div className='flex justify-between items-center h-8'>
|
||||
|
|
|
@ -92,6 +92,7 @@ const AgentTools: FC = () => {
|
|||
tool_name: tool.tool_name,
|
||||
tool_label: tool.tool_label,
|
||||
tool_parameters: tool.params,
|
||||
notAuthor: !tool.is_team_authorization,
|
||||
enabled: true,
|
||||
})
|
||||
})
|
||||
|
@ -101,7 +102,7 @@ const AgentTools: FC = () => {
|
|||
return (
|
||||
<>
|
||||
<Panel
|
||||
className="mt-2"
|
||||
className={cn('mt-2', tools.length === 0 && 'pb-2')}
|
||||
noBodySpacing={tools.length === 0}
|
||||
headerIcon={
|
||||
<RiHammerFill className='w-4 h-4 text-primary-500' />
|
||||
|
|
|
@ -21,6 +21,7 @@ import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
|||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { getRedirection } from '@/utils/app-redirection'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useMutationCheckDependenciesBeforeImportDSL } from '@/service/use-plugins'
|
||||
|
||||
type CreateFromDSLModalProps = {
|
||||
show: boolean
|
||||
|
@ -43,6 +44,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
const [fileContent, setFileContent] = useState<string>()
|
||||
const [currentTab, setCurrentTab] = useState(activeTab)
|
||||
const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
|
||||
const { mutateAsync } = useMutationCheckDependenciesBeforeImportDSL()
|
||||
|
||||
const readFile = (file: File) => {
|
||||
const reader = new FileReader()
|
||||
|
@ -78,11 +80,21 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
let app
|
||||
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
|
||||
const leakedData = await mutateAsync({ dslString: fileContent })
|
||||
if (leakedData?.leaked.length) {
|
||||
isCreatingRef.current = false
|
||||
return
|
||||
}
|
||||
app = await importApp({
|
||||
data: fileContent || '',
|
||||
})
|
||||
}
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_URL) {
|
||||
const leakedData = await mutateAsync({ url: dslUrlValue })
|
||||
if (leakedData?.leaked.length) {
|
||||
isCreatingRef.current = false
|
||||
return
|
||||
}
|
||||
app = await importAppFromUrl({
|
||||
url: dslUrlValue || '',
|
||||
})
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--color-components-chat-input-audio-bg-alt);
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
min-width: 240px;
|
||||
max-width: 420px;
|
||||
max-height: 40px;
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(16, 24, 40, 0.08);
|
||||
box-shadow: 0 1px 2px rgba(9, 9, 11, 0.05);
|
||||
border: 1px solid var(--color-components-panel-border-subtle);
|
||||
box-shadow: 0 1px 2px var(--color-shadow-shadow-3);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,8 @@
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #296DFF;
|
||||
color: white;
|
||||
background-color: var(--color-components-button-primary-bg);
|
||||
color: var(--color-components-chat-input-audio-bg-alt);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
|
@ -30,16 +30,15 @@
|
|||
}
|
||||
|
||||
.playButton:hover {
|
||||
background-color: #3367d6;
|
||||
background-color: var(--color-components-button-primary-bg-hover);
|
||||
}
|
||||
|
||||
.playButton:disabled {
|
||||
background-color: #bdbdbf;
|
||||
background-color: var(--color-components-button-primary-bg-disabled);
|
||||
}
|
||||
|
||||
.audioControls {
|
||||
flex-grow: 1;
|
||||
|
||||
}
|
||||
|
||||
.progressBarContainer {
|
||||
|
@ -76,8 +75,8 @@
|
|||
|
||||
.timeDisplay {
|
||||
/* position: absolute; */
|
||||
color: #296DFF;
|
||||
border-radius: 2px;
|
||||
color: var(--color-text-accent-secondary);
|
||||
font-size: 12px;
|
||||
order: 0;
|
||||
height: 100%;
|
||||
width: 50px;
|
||||
|
@ -97,7 +96,6 @@
|
|||
} */
|
||||
|
||||
.duration {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 4px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
@ -114,6 +112,6 @@
|
|||
}
|
||||
|
||||
.playButton svg path,
|
||||
.playButton svg rect{
|
||||
fill:currentColor;
|
||||
.playButton svg rect {
|
||||
fill: currentColor;
|
||||
}
|
|
@ -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 |
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './BaichuanTextCn.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './BaichuanTextCn.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './Minimax.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './Minimax.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './MinimaxText.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './MinimaxText.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './Tongyi.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './Tongyi.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './TongyiText.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './TongyiText.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './TongyiTextCn.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './TongyiTextCn.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './Wxyy.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './Wxyy.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './WxyyText.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './WxyyText.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import s from './WxyyTextCn.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import s from './WxyyTextCn.module.css'
|
||||
|
||||
const Icon = React.forwardRef<HTMLSpanElement, React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>((
|
||||
{ className, ...restProps },
|
||||
|
|
|
@ -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
|
38
web/app/components/base/icons/src/public/common/Lock.json
Normal file
38
web/app/components/base/icons/src/public/common/Lock.json
Normal file
|
@ -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"
|
||||
}
|
16
web/app/components/base/icons/src/public/common/Lock.tsx
Normal file
16
web/app/components/base/icons/src/public/common/Lock.tsx
Normal file
|
@ -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
|
|
@ -2,8 +2,11 @@ export { default as D } from './D'
|
|||
export { default as DiagonalDividingLine } from './DiagonalDividingLine'
|
||||
export { default as Dify } from './Dify'
|
||||
export { default as Github } from './Github'
|
||||
export { default as Highlight } from './Highlight'
|
||||
export { default as Line3 } from './Line3'
|
||||
export { default as Lock } from './Lock'
|
||||
export { default as MessageChatSquare } from './MessageChatSquare'
|
||||
export { default as MultiPathRetrieval } from './MultiPathRetrieval'
|
||||
export { default as NTo1Retrieval } from './NTo1Retrieval'
|
||||
export { default as Notion } from './Notion'
|
||||
export { default as SparklesSoft } from './SparklesSoft'
|
||||
|
|
48
web/app/components/base/premium-badge/index.css
Normal file
48
web/app/components/base/premium-badge/index.css
Normal file
|
@ -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
|
||||
}
|
||||
}
|
78
web/app/components/base/premium-badge/index.tsx
Normal file
78
web/app/components/base/premium-badge/index.tsx
Normal file
|
@ -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 }
|
|
@ -6,7 +6,7 @@ type Props = {
|
|||
}
|
||||
|
||||
const VideoGallery: React.FC<Props> = ({ srcs }) => {
|
||||
return (<><br/>{srcs.map((src, index) => (<><br/><VideoPlayer key={`video_${index}`} src={src}/></>))}</>)
|
||||
return (<><br/>{srcs.map((src, index) => (<React.Fragment key={`video_${index}`}><br/><VideoPlayer src={src}/></React.Fragment>))}</>)
|
||||
}
|
||||
|
||||
export default React.memo(VideoGallery)
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { GoldCoin } from '../../base/icons/src/vender/solid/FinanceAndECommerce'
|
||||
import { Sparkles } from '../../base/icons/src/public/billing'
|
||||
import s from './style.module.css'
|
||||
import PremiumBadge from '../../base/premium-badge'
|
||||
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
|
||||
|
@ -36,9 +35,7 @@ const PlainBtn = ({ className, onClick }: { className?: string; onClick: () => v
|
|||
const UpgradeBtn: FC<Props> = ({
|
||||
className,
|
||||
isPlain = false,
|
||||
isFull = false,
|
||||
isShort = false,
|
||||
size = 'md',
|
||||
onClick: _onClick,
|
||||
loc,
|
||||
}) => {
|
||||
|
@ -63,22 +60,19 @@ const UpgradeBtn: FC<Props> = ({
|
|||
return <PlainBtn onClick={onClick} className={className} />
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
s.upgradeBtn,
|
||||
className,
|
||||
isFull ? 'justify-center' : 'px-3',
|
||||
size === 'lg' ? 'h-10' : 'h-9',
|
||||
'relative flex items-center cursor-pointer border rounded-[20px] border-[#0096EA] text-white',
|
||||
)}
|
||||
<PremiumBadge
|
||||
size="m"
|
||||
color="blue"
|
||||
allowHover={true}
|
||||
onClick={onClick}
|
||||
>
|
||||
<GoldCoin className='mr-1 w-3.5 h-3.5' />
|
||||
<div className='text-xs font-normal text-nowrap'>{t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}`)}</div>
|
||||
<Sparkles
|
||||
className='absolute -right-1 -top-2 w-4 h-5 bg-cover'
|
||||
/>
|
||||
</div>
|
||||
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
|
||||
<div className='system-xs-medium'>
|
||||
<span className='p-1'>
|
||||
{t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}`)}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
)
|
||||
}
|
||||
export default React.memo(UpgradeBtn)
|
||||
|
|
|
@ -6,13 +6,17 @@ import { RiArrowDownSLine } from '@remixicon/react'
|
|||
import cn from '@/utils/classnames'
|
||||
import { switchWorkspace } from '@/service/common'
|
||||
import { useWorkspacesContext } from '@/context/workspace-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
|
||||
const WorkplaceSelector = () => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { workspaces } = useWorkspacesContext()
|
||||
const currentWorkspace = workspaces.find(v => v.current)
|
||||
const isFreePlan = plan.type === 'sandbox'
|
||||
const handleSwitchWorkspace = async (tenant_id: string) => {
|
||||
try {
|
||||
if (currentWorkspace?.id === tenant_id)
|
||||
|
@ -57,7 +61,7 @@ const WorkplaceSelector = () => {
|
|||
`,
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col p-1 pb-2 items-start self-stretch w-full rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadows-shadow-lg ">
|
||||
<div className="flex flex-col p-1 pb-2 items-start self-stretch w-full rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg ">
|
||||
<div className='flex px-3 pt-1 pb-0.5 items-start self-stretch'>
|
||||
<span className='flex-1 text-text-tertiary system-xs-medium-uppercase'>{t('common.userProfile.workspace')}</span>
|
||||
</div>
|
||||
|
@ -65,7 +69,16 @@ const WorkplaceSelector = () => {
|
|||
workspaces.map(workspace => (
|
||||
<div className='flex py-1 pl-3 pr-2 items-center gap-2 self-stretch hover:bg-state-base-hover rounded-lg' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
|
||||
<div className='flex items-center justify-center w-7 h-7 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div>
|
||||
<div className='line-clamp-1 flex-grow overflow-hidden text-text-secondary text-ellipsis system-md-regular cursor-pointer'>{workspace.name}</div>
|
||||
<div className='line-clamp-1 grow overflow-hidden text-text-secondary text-ellipsis system-md-regular cursor-pointer'>{workspace.name}</div>
|
||||
{
|
||||
<PremiumBadge size='s' color='gray' allowHover={false}>
|
||||
<div className='system-2xs-medium'>
|
||||
<span className='p-[2px]'>
|
||||
{plan.type === 'professional' ? 'PRO' : plan.type.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react'
|
|||
import {
|
||||
RiBrain2Fill,
|
||||
RiBrain2Line,
|
||||
RiCloseLine,
|
||||
RiColorFilterFill,
|
||||
RiColorFilterLine,
|
||||
RiDatabase2Fill,
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
RiPuzzle2Line,
|
||||
RiTranslate2,
|
||||
} from '@remixicon/react'
|
||||
import Button from '../../base/button'
|
||||
import MembersPage from './members-page'
|
||||
import LanguagePage from './language-page'
|
||||
import ApiBasedExtensionPage from './api-based-extension-page'
|
||||
|
@ -178,34 +180,47 @@ export default function AccountSetting({
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
<div ref={scrollRef} className='relative w-[824px] pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b')}>
|
||||
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
|
||||
{
|
||||
activeItem?.description && (
|
||||
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
||||
)
|
||||
}
|
||||
{activeItem?.key === 'provider' && (
|
||||
<div className='grow flex justify-end'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
wrapperClassName='!w-[200px]'
|
||||
className='!h-8 !text-[13px]'
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='relative flex w-[824px]'>
|
||||
<div className='absolute top-6 -right-11 flex flex-col items-center z-[9999]'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
size='large'
|
||||
className='px-2'
|
||||
onClick={onCancel}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5' />
|
||||
</Button>
|
||||
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
|
||||
</div>
|
||||
<div className='px-4 sm:px-8 pt-2'>
|
||||
{activeMenu === 'provider' && <ModelProviderPage searchText={searchValue} />}
|
||||
{activeMenu === 'members' && <MembersPage />}
|
||||
{activeMenu === 'billing' && <BillingPage />}
|
||||
{activeMenu === 'data-source' && <DataSourcePage />}
|
||||
{activeMenu === 'api-based-extension' && <ApiBasedExtensionPage />}
|
||||
{activeMenu === 'custom' && <CustomPage />}
|
||||
{activeMenu === 'language' && <LanguagePage />}
|
||||
<div ref={scrollRef} className='w-full pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b')}>
|
||||
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
|
||||
{
|
||||
activeItem?.description && (
|
||||
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
||||
)
|
||||
}
|
||||
{activeItem?.key === 'provider' && (
|
||||
<div className='grow flex justify-end'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
wrapperClassName='!w-[200px]'
|
||||
className='!h-8 !text-[13px]'
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='px-4 sm:px-8 pt-2'>
|
||||
{activeMenu === 'provider' && <ModelProviderPage searchText={searchValue} />}
|
||||
{activeMenu === 'members' && <MembersPage />}
|
||||
{activeMenu === 'billing' && <BillingPage />}
|
||||
{activeMenu === 'data-source' && <DataSourcePage />}
|
||||
{activeMenu === 'api-based-extension' && <ApiBasedExtensionPage />}
|
||||
{activeMenu === 'custom' && <CustomPage />}
|
||||
{activeMenu === 'language' && <LanguagePage />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Fragment, useCallback, useEffect } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type DialogProps = {
|
||||
|
@ -47,18 +45,7 @@ const MenuDialog = ({
|
|||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className={cn('grow relative w-full h-full p-0 overflow-hidden text-left align-middle transition-all transform bg-background-sidenav-bg backdrop-blur-md', className)}>
|
||||
<div className='absolute right-0 top-0 h-full w-1/2 bg-components-panel-bg'/>
|
||||
<div className='absolute top-6 right-6 flex flex-col items-center'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
size='large'
|
||||
className='px-2'
|
||||
onClick={close}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5' />
|
||||
</Button>
|
||||
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
|
||||
</div>
|
||||
<div className='absolute top-0 right-0 h-full w-1/2 bg-components-panel-bg' />
|
||||
{children}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export type FormValue = Record<string, any>
|
||||
|
||||
export interface TypeWithI18N<T = string> {
|
||||
export type TypeWithI18N<T = string> = {
|
||||
en_US: T
|
||||
zh_Hans: T
|
||||
[key: string]: T
|
||||
|
@ -15,9 +15,12 @@ export enum FormTypeEnum {
|
|||
boolean = 'boolean',
|
||||
files = 'files',
|
||||
file = 'file',
|
||||
modelSelector = 'model-selector',
|
||||
toolSelector = 'tool-selector',
|
||||
appSelector = 'app-selector',
|
||||
}
|
||||
|
||||
export interface FormOption {
|
||||
export type FormOption = {
|
||||
label: TypeWithI18N
|
||||
value: string
|
||||
show_on: FormShowOnObject[]
|
||||
|
@ -89,12 +92,12 @@ export enum CustomConfigurationStatusEnum {
|
|||
noConfigure = 'no-configure',
|
||||
}
|
||||
|
||||
export interface FormShowOnObject {
|
||||
export type FormShowOnObject = {
|
||||
variable: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface CredentialFormSchemaBase {
|
||||
export type CredentialFormSchemaBase = {
|
||||
variable: string
|
||||
label: TypeWithI18N
|
||||
type: FormTypeEnum
|
||||
|
@ -112,7 +115,7 @@ export type CredentialFormSchemaRadio = CredentialFormSchemaBase & { options: Fo
|
|||
export type CredentialFormSchemaSecretInput = CredentialFormSchemaBase & { placeholder?: TypeWithI18N }
|
||||
export type CredentialFormSchema = CredentialFormSchemaTextInput | CredentialFormSchemaSelect | CredentialFormSchemaRadio | CredentialFormSchemaSecretInput
|
||||
|
||||
export interface ModelItem {
|
||||
export type ModelItem = {
|
||||
model: string
|
||||
label: TypeWithI18N
|
||||
model_type: ModelTypeEnum
|
||||
|
@ -141,7 +144,7 @@ export enum QuotaUnitEnum {
|
|||
credits = 'credits',
|
||||
}
|
||||
|
||||
export interface QuotaConfiguration {
|
||||
export type QuotaConfiguration = {
|
||||
quota_type: CurrentSystemQuotaTypeEnum
|
||||
quota_unit: QuotaUnitEnum
|
||||
quota_limit: number
|
||||
|
@ -150,7 +153,7 @@ export interface QuotaConfiguration {
|
|||
is_valid: boolean
|
||||
}
|
||||
|
||||
export interface ModelProvider {
|
||||
export type ModelProvider = {
|
||||
provider: string
|
||||
label: TypeWithI18N
|
||||
description?: TypeWithI18N
|
||||
|
@ -184,7 +187,7 @@ export interface ModelProvider {
|
|||
}
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
export type Model = {
|
||||
provider: string
|
||||
icon_large: TypeWithI18N
|
||||
icon_small: TypeWithI18N
|
||||
|
@ -193,7 +196,7 @@ export interface Model {
|
|||
status: ModelStatusEnum
|
||||
}
|
||||
|
||||
export interface DefaultModelResponse {
|
||||
export type DefaultModelResponse = {
|
||||
model: string
|
||||
model_type: ModelTypeEnum
|
||||
provider: {
|
||||
|
@ -203,17 +206,17 @@ export interface DefaultModelResponse {
|
|||
}
|
||||
}
|
||||
|
||||
export interface DefaultModel {
|
||||
export type DefaultModel = {
|
||||
provider: string
|
||||
model: string
|
||||
}
|
||||
|
||||
export interface CustomConfigurationModelFixedFields {
|
||||
export type CustomConfigurationModelFixedFields = {
|
||||
__model_name: string
|
||||
__model_type: ModelTypeEnum
|
||||
}
|
||||
|
||||
export interface ModelParameterRule {
|
||||
export type ModelParameterRule = {
|
||||
default?: number | string | boolean | string[]
|
||||
help?: TypeWithI18N
|
||||
label: TypeWithI18N
|
||||
|
@ -228,7 +231,7 @@ export interface ModelParameterRule {
|
|||
tagPlaceholder?: TypeWithI18N
|
||||
}
|
||||
|
||||
export interface ModelLoadBalancingConfigEntry {
|
||||
export type ModelLoadBalancingConfigEntry = {
|
||||
/** model balancing config entry id */
|
||||
id?: string
|
||||
/** is config entry enabled */
|
||||
|
@ -243,7 +246,7 @@ export interface ModelLoadBalancingConfigEntry {
|
|||
ttl?: number
|
||||
}
|
||||
|
||||
export interface ModelLoadBalancingConfig {
|
||||
export type ModelLoadBalancingConfig = {
|
||||
enabled: boolean
|
||||
configs: ModelLoadBalancingConfigEntry[]
|
||||
}
|
||||
|
|
|
@ -19,11 +19,12 @@ const ModelIcon: FC<ModelIconProps> = ({
|
|||
}) => {
|
||||
const language = useLanguage()
|
||||
|
||||
if (provider?.provider === 'openai' && (modelName?.startsWith('gpt-4') || modelName?.includes('4o')))
|
||||
if (provider?.provider.includes('openai') && (modelName?.startsWith('gpt-4') || modelName?.includes('4o')))
|
||||
return <OpenaiViolet className={`w-4 h-4 ${className}`}/>
|
||||
|
||||
if (provider?.icon_small) {
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
alt='model-icon'
|
||||
src={`${provider.icon_small[language] || provider.icon_small.en_US}`}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { ValidatingTip } from '../../key-validator/ValidateStatus'
|
||||
import type {
|
||||
|
@ -17,6 +17,9 @@ import cn from '@/utils/classnames'
|
|||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Radio from '@/app/components/base/radio'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import ToolSelector from '@/app/components/tools/tool-selector'
|
||||
|
||||
type FormProps = {
|
||||
className?: string
|
||||
itemClassName?: string
|
||||
|
@ -67,6 +70,24 @@ const Form: FC<FormProps> = ({
|
|||
onChange({ ...value, [key]: val, ...shouldClearVariable })
|
||||
}
|
||||
|
||||
const handleModelChanged = useCallback((key: string, model: { provider: string; modelId: string; mode?: string }) => {
|
||||
const newValue = {
|
||||
...value[key],
|
||||
provider: model.provider,
|
||||
model: model.modelId,
|
||||
mode: model.mode,
|
||||
}
|
||||
onChange({ ...value, [key]: newValue })
|
||||
}, [onChange, value])
|
||||
|
||||
const handleCompletionParamsChange = useCallback((key: string, newParams: Record<string, any>) => {
|
||||
const newValue = {
|
||||
...value[key],
|
||||
completion_params: newParams,
|
||||
}
|
||||
onChange({ ...value, [key]: newValue })
|
||||
}, [onChange, value])
|
||||
|
||||
const renderField = (formSchema: CredentialFormSchema) => {
|
||||
const tooltip = formSchema.tooltip
|
||||
const tooltipContent = (tooltip && (
|
||||
|
@ -94,7 +115,7 @@ const Form: FC<FormProps> = ({
|
|||
const disabled = readonly || (isEditMode && (variable === '__model_type' || variable === '__model_name'))
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 text-sm text-gray-900')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
|
||||
{label[language] || label.en_US}
|
||||
{
|
||||
required && (
|
||||
|
@ -135,7 +156,7 @@ const Form: FC<FormProps> = ({
|
|||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 text-sm text-gray-900')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
|
||||
{label[language] || label.en_US}
|
||||
{
|
||||
required && (
|
||||
|
@ -165,7 +186,7 @@ const Form: FC<FormProps> = ({
|
|||
flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
|
||||
${value[variable] === option.value && 'border-[5px] border-primary-600'}
|
||||
`} />
|
||||
<div className='text-sm text-gray-900'>{option.label[language] || option.label.en_US}</div>
|
||||
<div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
@ -176,7 +197,7 @@ const Form: FC<FormProps> = ({
|
|||
)
|
||||
}
|
||||
|
||||
if (formSchema.type === 'select') {
|
||||
if (formSchema.type === FormTypeEnum.select) {
|
||||
const {
|
||||
options,
|
||||
variable,
|
||||
|
@ -191,7 +212,7 @@ const Form: FC<FormProps> = ({
|
|||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 text-sm text-gray-900')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
|
||||
{label[language] || label.en_US}
|
||||
|
||||
{
|
||||
|
@ -202,6 +223,7 @@ const Form: FC<FormProps> = ({
|
|||
{tooltipContent}
|
||||
</div>
|
||||
<SimpleSelect
|
||||
wrapperClassName='h-8'
|
||||
className={cn(inputClassName)}
|
||||
disabled={readonly}
|
||||
defaultValue={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
|
||||
|
@ -220,7 +242,7 @@ const Form: FC<FormProps> = ({
|
|||
)
|
||||
}
|
||||
|
||||
if (formSchema.type === 'boolean') {
|
||||
if (formSchema.type === FormTypeEnum.boolean) {
|
||||
const {
|
||||
variable,
|
||||
label,
|
||||
|
@ -233,9 +255,9 @@ const Form: FC<FormProps> = ({
|
|||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<div className='flex items-center justify-between py-2 text-sm text-gray-900'>
|
||||
<div className='flex items-center justify-between py-2 system-sm-semibold text-text-secondary'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<span className={cn(fieldLabelClassName, 'flex items-center py-2 text-sm text-gray-900')}>{label[language] || label.en_US}</span>
|
||||
<span className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-regular text-text-secondary')}>{label[language] || label.en_US}</span>
|
||||
{
|
||||
required && (
|
||||
<span className='ml-1 text-red-500'>*</span>
|
||||
|
@ -256,6 +278,77 @@ const Form: FC<FormProps> = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (formSchema.type === FormTypeEnum.modelSelector) {
|
||||
const {
|
||||
variable,
|
||||
label,
|
||||
required,
|
||||
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
|
||||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
|
||||
{label[language] || label.en_US}
|
||||
{
|
||||
required && (
|
||||
<span className='ml-1 text-red-500'>*</span>
|
||||
)
|
||||
}
|
||||
{tooltipContent}
|
||||
</div>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
isAdvancedMode
|
||||
isInWorkflow
|
||||
provider={value[variable]?.provider}
|
||||
modelId={value[variable]?.name}
|
||||
mode={value[variable]?.mode}
|
||||
completionParams={value[variable]?.completion_params}
|
||||
setModel={model => handleModelChanged(variable, model)}
|
||||
onCompletionParamsChange={params => handleCompletionParamsChange(variable, params)}
|
||||
hideDebugWithMultipleModel
|
||||
debugWithMultipleModel={false}
|
||||
readonly={readonly}
|
||||
/>
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
{validating && changeKey === variable && <ValidatingTip />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (formSchema.type === FormTypeEnum.toolSelector) {
|
||||
const {
|
||||
variable,
|
||||
label,
|
||||
required,
|
||||
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
|
||||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
|
||||
{label[language] || label.en_US}
|
||||
{
|
||||
required && (
|
||||
<span className='ml-1 text-red-500'>*</span>
|
||||
)
|
||||
}
|
||||
{tooltipContent}
|
||||
</div>
|
||||
<ToolSelector
|
||||
disabled={readonly}
|
||||
value={value[variable]}
|
||||
onSelect={item => handleFormChange(variable, item as any)}
|
||||
/>
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
{validating && changeKey === variable && <ValidatingTip />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (formSchema.type === FormTypeEnum.appSelector) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -26,14 +26,14 @@ const Input: FC<InputProps> = ({
|
|||
max,
|
||||
}) => {
|
||||
const toLimit = (v: string) => {
|
||||
const minNum = parseFloat(`${min}`)
|
||||
const maxNum = parseFloat(`${max}`)
|
||||
if (!isNaN(minNum) && parseFloat(v) < minNum) {
|
||||
const minNum = Number.parseFloat(`${min}`)
|
||||
const maxNum = Number.parseFloat(`${max}`)
|
||||
if (!isNaN(minNum) && Number.parseFloat(v) < minNum) {
|
||||
onChange(`${min}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isNaN(maxNum) && parseFloat(v) > maxNum)
|
||||
if (!isNaN(maxNum) && Number.parseFloat(v) > maxNum)
|
||||
onChange(`${max}`)
|
||||
}
|
||||
return (
|
||||
|
@ -41,9 +41,9 @@ const Input: FC<InputProps> = ({
|
|||
<input
|
||||
tabIndex={0}
|
||||
className={`
|
||||
block px-3 w-full h-9 bg-gray-100 text-sm rounded-lg border border-transparent
|
||||
block px-3 w-full h-8 bg-components-input-bg-normal text-sm rounded-lg border border-transparent
|
||||
appearance-none outline-none caret-primary-600
|
||||
hover:border-[rgba(0,0,0,0.08)] hover:bg-gray-50
|
||||
hover:border-[rgba(0,0,0,0.08)] hover:bg-state-hover-alt
|
||||
focus:bg-white focus:border-gray-300 focus:shadow-xs
|
||||
placeholder:text-sm placeholder:text-gray-400
|
||||
${validated && 'pr-[30px]'}
|
||||
|
|
|
@ -68,7 +68,7 @@ const stopParameterRule: ModelParameterRule = {
|
|||
},
|
||||
}
|
||||
|
||||
const PROVIDER_WITH_PRESET_TONE = ['openai', 'azure_openai']
|
||||
const PROVIDER_WITH_PRESET_TONE = ['langgenius/openai/openai', 'langgenius/azure_openai/azure_openai']
|
||||
const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
popupClassName,
|
||||
portalToFollowElemContentClassName,
|
||||
|
@ -190,26 +190,22 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
|||
)
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className={cn(portalToFollowElemContentClassName, 'z-[60]')}>
|
||||
<div className={cn(popupClassName, 'w-[496px] rounded-xl border border-gray-100 bg-white shadow-xl')}>
|
||||
<div className={cn(
|
||||
'max-h-[480px] overflow-y-auto',
|
||||
!isInWorkflow && 'px-10 pt-6 pb-8',
|
||||
isInWorkflow && 'p-4')}>
|
||||
<div className='flex items-center justify-between h-8'>
|
||||
<div className={cn('font-semibold text-gray-900 shrink-0', isInWorkflow && 'text-[13px]')}>
|
||||
<PortalToFollowElemContent className={cn('z-[60]', portalToFollowElemContentClassName)}>
|
||||
<div className={cn(popupClassName, 'w-[389px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg')}>
|
||||
<div className={cn('max-h-[420px] p-4 pt-3 overflow-y-auto')}>
|
||||
<div className='relative'>
|
||||
<div className={cn('mb-1 h-6 flex items-center text-text-secondary system-sm-semibold')}>
|
||||
{t('common.modelProvider.model').toLocaleUpperCase()}
|
||||
</div>
|
||||
<ModelSelector
|
||||
defaultModel={(provider || modelId) ? { provider, model: modelId } : undefined}
|
||||
modelList={activeTextGenerationModelList}
|
||||
onSelect={handleChangeModel}
|
||||
triggerClassName='max-w-[295px]'
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
!!parameterRules.length && (
|
||||
<div className='my-5 h-[1px] bg-gray-100' />
|
||||
<div className='my-3 h-[1px] bg-divider-subtle' />
|
||||
)
|
||||
}
|
||||
{
|
||||
|
@ -219,8 +215,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
|||
}
|
||||
{
|
||||
!isLoading && !!parameterRules.length && (
|
||||
<div className='flex items-center justify-between mb-4'>
|
||||
<div className={cn('font-semibold text-gray-900', isInWorkflow && 'text-[13px]')}>{t('common.modelProvider.parameters')}</div>
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<div className={cn('h-6 flex items-center text-text-secondary system-sm-semibold')}>{t('common.modelProvider.parameters')}</div>
|
||||
{
|
||||
PROVIDER_WITH_PRESET_TONE.includes(provider) && (
|
||||
<PresetsParameter onSelect={handleSelectPresetParameter} />
|
||||
|
@ -237,7 +233,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
|||
].map(parameter => (
|
||||
<ParameterItem
|
||||
key={`${modelId}-${parameter.name}`}
|
||||
className='mb-4'
|
||||
parameterRule={parameter}
|
||||
value={completionParams?.[parameter.name]}
|
||||
onChange={v => handleParamChange(parameter.name, v)}
|
||||
|
@ -250,7 +245,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
|||
</div>
|
||||
{!hideDebugWithMultipleModel && (
|
||||
<div
|
||||
className='flex items-center justify-between px-6 h-[50px] bg-gray-50 border-t border-t-gray-100 text-xs font-medium text-primary-600 cursor-pointer rounded-b-xl'
|
||||
className='flex items-center justify-between px-4 h-[50px] bg-components-section-burn border-t border-t-divider-subtle system-sm-regular text-text-accent cursor-pointer rounded-b-xl'
|
||||
onClick={() => onDebugWithMultipleModelChange?.()}
|
||||
>
|
||||
{
|
||||
|
|
|
@ -17,7 +17,6 @@ type ParameterItemProps = {
|
|||
parameterRule: ModelParameterRule
|
||||
value?: ParameterValue
|
||||
onChange?: (value: ParameterValue) => void
|
||||
className?: string
|
||||
onSwitch?: (checked: boolean, assignValue: ParameterValue) => void
|
||||
isInWorkflow?: boolean
|
||||
}
|
||||
|
@ -25,7 +24,6 @@ const ParameterItem: FC<ParameterItemProps> = ({
|
|||
parameterRule,
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
onSwitch,
|
||||
isInWorkflow,
|
||||
}) => {
|
||||
|
@ -249,9 +247,20 @@ const ParameterItem: FC<ParameterItemProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`flex items-center justify-between ${className}`}>
|
||||
<div>
|
||||
<div className={cn(isInWorkflow ? 'w-[140px]' : 'w-full', 'ml-4 shrink-0 flex items-center')}>
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<div className='shrink-0 basis-1/2'>
|
||||
<div className={cn('shrink-0 w-full flex items-center')}>
|
||||
{
|
||||
!parameterRule.required && parameterRule.name !== 'stop' && (
|
||||
<div className='mr-2 w-7'>
|
||||
<Switch
|
||||
defaultValue={!isNullOrUndefined(value)}
|
||||
onChange={handleSwitch}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div
|
||||
className='mr-0.5 text-[13px] font-medium text-gray-700 truncate'
|
||||
title={parameterRule.label[language] || parameterRule.label.en_US}
|
||||
|
@ -269,16 +278,6 @@ const ParameterItem: FC<ParameterItemProps> = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!parameterRule.required && parameterRule.name !== 'stop' && (
|
||||
<Switch
|
||||
className='mr-1'
|
||||
defaultValue={!isNullOrUndefined(value)}
|
||||
onChange={handleSwitch}
|
||||
size='md'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
parameterRule.type === 'tag' && (
|
||||
|
|
|
@ -2,12 +2,13 @@ import type { FC } from 'react'
|
|||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Dropdown from '@/app/components/base/dropdown'
|
||||
import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor'
|
||||
import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce'
|
||||
import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { TONE_LIST } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type PresetsParameterProps = {
|
||||
onSelect: (toneId: number) => void
|
||||
|
@ -18,19 +19,16 @@ const PresetsParameter: FC<PresetsParameterProps> = ({
|
|||
const { t } = useTranslation()
|
||||
const renderTrigger = useCallback((open: boolean) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-[7px] h-7 rounded-md border-[0.5px] border-gray-200 shadow-xs
|
||||
text-xs font-medium text-gray-700 cursor-pointer
|
||||
${open && 'bg-gray-100'}
|
||||
`}
|
||||
<Button
|
||||
size={'small'}
|
||||
variant={'secondary'}
|
||||
className={cn(open && 'bg-state-base-hover')}
|
||||
>
|
||||
<SlidersH className='mr-[5px] w-3.5 h-3.5 text-gray-500' />
|
||||
{t('common.modelProvider.loadPresets')}
|
||||
<RiArrowDownSLine className='ml-0.5 w-3.5 h-3.5 text-gray-500' />
|
||||
</div>
|
||||
<RiArrowDownSLine className='ml-0.5 w-3.5 h-3.5' />
|
||||
</Button>
|
||||
)
|
||||
}, [])
|
||||
}, [t])
|
||||
const getToneIcon = (toneId: number) => {
|
||||
const className = 'mr-2 w-[14px] h-[14px]'
|
||||
const res = ({
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
validateModelProvider,
|
||||
} from '@/service/common'
|
||||
|
||||
export const MODEL_PROVIDER_QUOTA_GET_PAID = ['anthropic', 'openai', 'azure_openai']
|
||||
export const MODEL_PROVIDER_QUOTA_GET_PAID = ['langgenius/anthropic/anthropic', 'langgenius/openai/openai', 'langgenius/azure_openai/azure_openai']
|
||||
|
||||
export const DEFAULT_BACKGROUND_COLOR = '#F3F4F6'
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ import Link from 'next/link'
|
|||
import { useBoolean } from 'ahooks'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
import { Bars3Icon } from '@heroicons/react/20/solid'
|
||||
import HeaderBillingBtn from '../billing/header-billing-btn'
|
||||
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
|
||||
import PremiumBadge from '../base/premium-badge'
|
||||
import AccountDropdown from './account-dropdown'
|
||||
import AppNav from './app-nav'
|
||||
import DatasetNav from './dataset-nav'
|
||||
|
@ -20,6 +21,7 @@ import WorkplaceSelector from '@/app/components/header/account-dropdown/workplac
|
|||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const navClassName = `
|
||||
flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
|
||||
|
@ -29,6 +31,7 @@ const navClassName = `
|
|||
|
||||
const Header = () => {
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const media = useBreakpoints()
|
||||
|
@ -69,7 +72,14 @@ const Header = () => {
|
|||
</WorkspaceProvider>
|
||||
{enableBilling && (
|
||||
<div className='select-none'>
|
||||
<HeaderBillingBtn onClick={handlePlanClick} />
|
||||
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
|
||||
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
|
||||
<div className='system-xs-medium'>
|
||||
<span className='p-1'>
|
||||
{t('billing.upgradeBtn.encourage')}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -84,7 +94,14 @@ const Header = () => {
|
|||
<div className='font-light text-divider-deep'>/</div>
|
||||
{enableBilling && (
|
||||
<div className='select-none'>
|
||||
<HeaderBillingBtn onClick={handlePlanClick} />
|
||||
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
|
||||
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
|
||||
<div className='system-xs-medium'>
|
||||
<span className='p-1'>
|
||||
{t('billing.upgradeBtn.encourage')}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
</div>
|
||||
)}
|
||||
<GithubStar />
|
||||
|
@ -98,7 +115,7 @@ const Header = () => {
|
|||
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center flex-shrink-0'>
|
||||
<div className='flex items-center shrink-0'>
|
||||
<EnvNav />
|
||||
<div className='mr-3'>
|
||||
<PluginsNav />
|
||||
|
|
|
@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next'
|
|||
import Link from 'next/link'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
type PluginsNavProps = {
|
||||
className?: string
|
||||
}
|
||||
|
@ -12,12 +14,17 @@ const PluginsNav = ({
|
|||
className,
|
||||
}: PluginsNavProps) => {
|
||||
const { t } = useTranslation()
|
||||
const selectedSegment = useSelectedLayoutSegment()
|
||||
const activated = selectedSegment === 'plugins'
|
||||
|
||||
return (
|
||||
<Link href="/plugins" className={classNames(
|
||||
className, 'group',
|
||||
)}>
|
||||
<div className='flex flex-row h-8 p-1.5 gap-0.5 items-center justify-center rounded-xl system-sm-medium-uppercase hover:bg-state-base-hover text-text-tertiary hover:text-text-secondary'>
|
||||
<div className={`flex flex-row h-8 p-1.5 gap-0.5 items-center justify-center
|
||||
rounded-xl system-sm-medium-uppercase ${activated
|
||||
? 'border border-components-main-nav-nav-button-border bg-components-main-nav-nav-button-bg-active shadow-md text-components-main-nav-nav-button-text'
|
||||
: 'text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary'}`}>
|
||||
<div className='flex w-4 h-4 justify-center items-center'>
|
||||
<Group />
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@ type Props = {
|
|||
label: string
|
||||
labelWidthClassName?: string
|
||||
value: string
|
||||
maskedValue?: string
|
||||
valueMaxWidthClassName?: string
|
||||
}
|
||||
|
||||
|
@ -22,6 +23,7 @@ const KeyValueItem: FC<Props> = ({
|
|||
label,
|
||||
labelWidthClassName = 'w-10',
|
||||
value,
|
||||
maskedValue,
|
||||
valueMaxWidthClassName = 'max-w-[162px]',
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
@ -49,7 +51,7 @@ const KeyValueItem: FC<Props> = ({
|
|||
<span className={cn('flex flex-col justify-center items-start text-text-tertiary system-xs-medium', labelWidthClassName)}>{label}</span>
|
||||
<div className='flex justify-center items-center gap-0.5'>
|
||||
<span className={cn(valueMaxWidthClassName, ' truncate system-xs-medium text-text-secondary')}>
|
||||
{value}
|
||||
{maskedValue || value}
|
||||
</span>
|
||||
<Tooltip popupContent={t(`common.operation.${isCopied ? 'copied' : 'copy'}`)} position='top'>
|
||||
<ActionButton onClick={handleCopy}>
|
||||
|
|
|
@ -4,7 +4,7 @@ import cn from '@/utils/classnames'
|
|||
|
||||
type Props = {
|
||||
wrapClassName: string
|
||||
loadingFileName: string
|
||||
loadingFileName?: string
|
||||
}
|
||||
|
||||
export const LoadingPlaceholder = ({ className }: { className?: string }) => (
|
||||
|
@ -27,7 +27,11 @@ const Placeholder = ({
|
|||
</div>
|
||||
<div className="ml-3 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={loadingFileName} />
|
||||
{loadingFileName ? (
|
||||
<Title title={loadingFileName} />
|
||||
) : (
|
||||
<LoadingPlaceholder className="w-[260px]" />
|
||||
)}
|
||||
</div>
|
||||
<div className={cn('flex items-center h-4 space-x-0.5')}>
|
||||
<LoadingPlaceholder className="w-[41px]" />
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { PluginDeclaration } from '../types'
|
|||
import { PluginType } from '../types'
|
||||
|
||||
export const toolNeko: PluginDeclaration = {
|
||||
plugin_unique_identifier: 'xxxxxx',
|
||||
version: '0.0.1',
|
||||
author: 'langgenius',
|
||||
name: 'neko',
|
||||
|
|
|
@ -87,3 +87,42 @@ export const useTags = (translateFromOut?: TFunction) => {
|
|||
tagsMap,
|
||||
}
|
||||
}
|
||||
|
||||
type Category = {
|
||||
name: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export const useCategories = (translateFromOut?: TFunction) => {
|
||||
const { t: translation } = useTranslation()
|
||||
const t = translateFromOut || translation
|
||||
|
||||
const categories = [
|
||||
{
|
||||
name: 'model',
|
||||
label: t('plugin.category.models'),
|
||||
},
|
||||
{
|
||||
name: 'tool',
|
||||
label: t('plugin.category.tools'),
|
||||
},
|
||||
{
|
||||
name: 'extension',
|
||||
label: t('plugin.category.extensions'),
|
||||
},
|
||||
{
|
||||
name: 'bundle',
|
||||
label: t('plugin.category.bundles'),
|
||||
},
|
||||
]
|
||||
|
||||
const categoriesMap = categories.reduce((acc, category) => {
|
||||
acc[category.name] = category
|
||||
return acc
|
||||
}, {} as Record<string, Category>)
|
||||
|
||||
return {
|
||||
categories,
|
||||
categoriesMap,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
import { useCallback } from 'react'
|
||||
import { apiPrefix } from '@/config'
|
||||
import { fetchWorkspaces } from '@/service/common'
|
||||
|
||||
let tenantId: string | null | undefined = null
|
||||
import { useSelector } from '@/context/app-context'
|
||||
|
||||
const useGetIcon = () => {
|
||||
const getIconUrl = async (fileName: string) => {
|
||||
if (!tenantId) {
|
||||
const { workspaces } = await fetchWorkspaces({
|
||||
url: '/workspaces',
|
||||
params: {},
|
||||
})
|
||||
tenantId = workspaces.find(v => v.current)?.id
|
||||
}
|
||||
return `${apiPrefix}/workspaces/current/plugin/icon?tenant_id=${tenantId}&filename=${fileName}`
|
||||
}
|
||||
const currentWorkspace = useSelector(s => s.currentWorkspace)
|
||||
const getIconUrl = useCallback((fileName: string) => {
|
||||
return `${apiPrefix}/workspaces/current/plugin/icon?tenant_id=${currentWorkspace.id}&filename=${fileName}`
|
||||
}, [currentWorkspace.id])
|
||||
|
||||
return {
|
||||
getIconUrl,
|
||||
|
|
|
@ -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)
|
|
@ -7,9 +7,10 @@ import Card from '../../../card'
|
|||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
||||
import { pluginManifestToCardPluginProps } from '../../utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { installPackageFromGitHub, uninstallPlugin } from '@/service/plugins'
|
||||
import { updateFromGitHub } from '@/service/plugins'
|
||||
import { useInstallPackageFromGitHub } from '@/service/use-plugins'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
|
||||
import { usePluginTaskList } from '@/service/use-plugins'
|
||||
import checkTaskStatus from '../../base/check-task-status'
|
||||
import { parseGitHubUrl } from '../../utils'
|
||||
|
||||
|
@ -40,7 +41,8 @@ const Loaded: React.FC<LoadedProps> = ({
|
|||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
|
||||
const { mutateAsync: installPackageFromGitHub } = useInstallPackageFromGitHub()
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
const { check } = checkTaskStatus()
|
||||
|
||||
const handleInstall = async () => {
|
||||
|
@ -49,28 +51,49 @@ const Loaded: React.FC<LoadedProps> = ({
|
|||
|
||||
try {
|
||||
const { owner, repo } = parseGitHubUrl(repoUrl)
|
||||
const { all_installed: isInstalled, task_id: taskId } = await installPackageFromGitHub(
|
||||
`${owner}/${repo}`,
|
||||
selectedVersion,
|
||||
selectedPackage,
|
||||
uniqueIdentifier,
|
||||
)
|
||||
if (updatePayload) {
|
||||
const { all_installed: isInstalled, task_id: taskId } = await updateFromGitHub(
|
||||
`${owner}/${repo}`,
|
||||
selectedVersion,
|
||||
selectedPackage,
|
||||
updatePayload.originalPackageInfo.id,
|
||||
uniqueIdentifier,
|
||||
)
|
||||
|
||||
if (updatePayload && isInstalled)
|
||||
await uninstallPlugin(updatePayload.originalPackageInfo.id)
|
||||
if (isInstalled) {
|
||||
onInstalled()
|
||||
return
|
||||
}
|
||||
|
||||
handleRefetch()
|
||||
await check({
|
||||
taskId,
|
||||
pluginUniqueIdentifier: uniqueIdentifier,
|
||||
})
|
||||
|
||||
if (isInstalled) {
|
||||
onInstalled()
|
||||
return
|
||||
}
|
||||
else {
|
||||
const { all_installed: isInstalled, task_id: taskId } = await installPackageFromGitHub({
|
||||
repoUrl: `${owner}/${repo}`,
|
||||
selectedVersion,
|
||||
selectedPackage,
|
||||
uniqueIdentifier,
|
||||
})
|
||||
|
||||
setPluginTasksWithPolling()
|
||||
await check({
|
||||
taskId,
|
||||
pluginUniqueIdentifier: uniqueIdentifier,
|
||||
})
|
||||
if (isInstalled) {
|
||||
onInstalled()
|
||||
return
|
||||
}
|
||||
|
||||
onInstalled()
|
||||
handleRefetch()
|
||||
await check({
|
||||
taskId,
|
||||
pluginUniqueIdentifier: uniqueIdentifier,
|
||||
})
|
||||
|
||||
onInstalled()
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (typeof e === 'string') {
|
||||
|
|
|
@ -48,7 +48,6 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
|||
const handleUploadPackage = async () => {
|
||||
if (isUploading) return
|
||||
setIsUploading(true)
|
||||
|
||||
try {
|
||||
const repo = repoUrl.replace('https://github.com/', '')
|
||||
await handleUpload(repo, selectedVersion, selectedPackage, (GitHubPackage) => {
|
||||
|
|
|
@ -8,9 +8,9 @@ import Button from '@/app/components/base/button'
|
|||
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 { useInstallPackageFromLocal } from '@/service/use-plugins'
|
||||
import checkTaskStatus from '../../base/check-task-status'
|
||||
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
|
||||
import { usePluginTaskList } from '@/service/use-plugins'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
|
@ -33,6 +33,8 @@ const Installed: FC<Props> = ({
|
|||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isInstalling, setIsInstalling] = React.useState(false)
|
||||
const { mutateAsync: installPackageFromLocal } = useInstallPackageFromLocal()
|
||||
|
||||
const {
|
||||
check,
|
||||
stop,
|
||||
|
@ -43,7 +45,7 @@ const Installed: FC<Props> = ({
|
|||
onCancel()
|
||||
}
|
||||
|
||||
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
const handleInstall = async () => {
|
||||
if (isInstalling) return
|
||||
setIsInstalling(true)
|
||||
|
@ -58,7 +60,7 @@ const Installed: FC<Props> = ({
|
|||
onInstalled()
|
||||
return
|
||||
}
|
||||
setPluginTasksWithPolling()
|
||||
handleRefetch()
|
||||
await check({
|
||||
taskId,
|
||||
pluginUniqueIdentifier: uniqueIdentifier,
|
||||
|
|
|
@ -48,6 +48,7 @@ const Uploading: FC<Props> = ({
|
|||
|
||||
React.useEffect(() => {
|
||||
handleUpload()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -41,6 +41,7 @@ export type MarketplaceContextValue = {
|
|||
setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void
|
||||
marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]>
|
||||
setMarketplaceCollectionPluginsMapFromClient: (map: Record<string, Plugin[]>) => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
||||
|
@ -60,6 +61,7 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
|||
setMarketplaceCollectionsFromClient: () => {},
|
||||
marketplaceCollectionPluginsMapFromClient: {},
|
||||
setMarketplaceCollectionPluginsMapFromClient: () => {},
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
type MarketplaceContextProviderProps = {
|
||||
|
@ -88,12 +90,14 @@ export const MarketplaceContextProvider = ({
|
|||
marketplaceCollectionPluginsMap: marketplaceCollectionPluginsMapFromClient,
|
||||
setMarketplaceCollectionPluginsMap: setMarketplaceCollectionPluginsMapFromClient,
|
||||
queryMarketplaceCollectionsAndPlugins,
|
||||
isLoading,
|
||||
} = useMarketplaceCollectionsAndPlugins()
|
||||
const {
|
||||
plugins,
|
||||
resetPlugins,
|
||||
queryPlugins,
|
||||
queryPluginsWithDebounced,
|
||||
isLoading: isPluginsLoading,
|
||||
} = useMarketplacePlugins()
|
||||
|
||||
const handleSearchPluginTextChange = useCallback((text: string) => {
|
||||
|
@ -194,6 +198,7 @@ export const MarketplaceContextProvider = ({
|
|||
setMarketplaceCollectionsFromClient,
|
||||
marketplaceCollectionPluginsMapFromClient,
|
||||
setMarketplaceCollectionPluginsMapFromClient,
|
||||
isLoading: isLoading || isPluginsLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -11,6 +11,7 @@ const Description = async ({
|
|||
}: DescriptionProps) => {
|
||||
const localeDefault = getLocaleOnServer()
|
||||
const { t } = await translate(localeFromProps || localeDefault, 'plugin')
|
||||
const { t: tCommon } = await translate(localeFromProps || localeDefault, 'common')
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -19,22 +20,23 @@ const Description = async ({
|
|||
</h1>
|
||||
<h2 className='shrink-0 flex justify-center items-center text-center body-md-regular text-text-tertiary'>
|
||||
{t('marketplace.discover')}
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.models')}
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected z-[1]">
|
||||
<span className='relative z-[2] lowercase'>{t('category.models')}</span>
|
||||
</span>
|
||||
,
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.tools')}
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected z-[1]">
|
||||
<span className='relative z-[2] lowercase'>{t('category.tools')}</span>
|
||||
</span>
|
||||
,
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.extensions')}
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected z-[1]">
|
||||
<span className='relative z-[2] lowercase'>{t('category.extensions')}</span>
|
||||
</span>
|
||||
{t('marketplace.and')}
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('category.bundles')}
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected z-[1]">
|
||||
<span className='relative z-[2] lowercase'>{t('category.bundles')}</span>
|
||||
</span>
|
||||
{t('marketplace.inDifyMarketplace')}
|
||||
<span className='mr-1'>{tCommon('operation.in')}</span>
|
||||
{t('marketplace.difyMarketplace')}
|
||||
</h2>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import Line from './line'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className='grow relative h-0 grid grid-cols-4 grid-rows-4 gap-3 p-2 overflow-hidden'
|
||||
className='grow relative h-0 flex flex-wrap p-2 overflow-hidden'
|
||||
>
|
||||
{
|
||||
Array.from({ length: 16 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='h-[144px] rounded-xl bg-background-section-burn'
|
||||
className={cn(
|
||||
'mr-3 mb-3 h-[144px] w-[calc((100%-36px)/4)] rounded-xl bg-background-section-burn',
|
||||
index % 4 === 3 && 'mr-0',
|
||||
index > 11 && 'mb-0',
|
||||
)}
|
||||
>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<div
|
||||
className='absolute inset-0 z-[1]'
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(180deg, rgba(255,255,255,0.01), #FCFCFD)',
|
||||
}}
|
||||
className='absolute inset-0 bg-marketplace-plugin-empty z-[1]'
|
||||
></div>
|
||||
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[2] flex flex-col items-center'>
|
||||
<div className='relative flex items-center justify-center mb-3 w-14 h-14 rounded-xl border border-divider-subtle bg-components-card-bg shadow-lg'>
|
||||
|
@ -30,7 +36,7 @@ const Empty = () => {
|
|||
<Line className='absolute top-full left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
|
||||
</div>
|
||||
<div className='text-center system-md-regular text-text-tertiary'>
|
||||
No plugin found
|
||||
{t('plugin.marketplace.noPluginFound')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,13 +25,61 @@ const CardWrapper = ({
|
|||
setFalse: hideInstallFromMarketplace,
|
||||
}] = useBoolean(false)
|
||||
|
||||
if (showInstallButton) {
|
||||
return (
|
||||
<div
|
||||
className='group relative rounded-xl cursor-pointer'
|
||||
>
|
||||
<Card
|
||||
key={plugin.name}
|
||||
payload={plugin}
|
||||
locale={locale}
|
||||
footer={
|
||||
<CardMoreInfo
|
||||
downloadCount={plugin.install_count}
|
||||
tags={plugin.tags.map(tag => tag.name)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{
|
||||
showInstallButton && (
|
||||
<div className='hidden absolute bottom-0 group-hover:flex items-center space-x-2 px-4 pt-8 pb-4 w-full bg-gradient-to-tr from-[#f9fafb] to-[rgba(249,250,251,0)] rounded-b-xl'>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='flex-1'
|
||||
onClick={showInstallFromMarketplace}
|
||||
>
|
||||
{t('plugin.detailPanel.operation.install')}
|
||||
</Button>
|
||||
<Button
|
||||
className='flex-1'
|
||||
>
|
||||
<a href={`${MARKETPLACE_URL_PREFIX}/plugin/${plugin.org}/${plugin.name}`} target='_blank' className='flex items-center gap-0.5'>
|
||||
{t('plugin.detailPanel.operation.detail')}
|
||||
<RiArrowRightUpLine className='ml-1 w-4 h-4' />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
isShowInstallFromMarketplace && (
|
||||
<InstallFromMarketplace
|
||||
manifest={plugin as any}
|
||||
uniqueIdentifier={plugin.latest_package_identifier}
|
||||
onClose={hideInstallFromMarketplace}
|
||||
onSuccess={hideInstallFromMarketplace}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='group relative rounded-xl cursor-pointer'
|
||||
onClick={() => {
|
||||
if (!showInstallButton)
|
||||
window.open(`${MARKETPLACE_URL_PREFIX}/plugin/${plugin.org}/${plugin.name}`)
|
||||
}}
|
||||
<a
|
||||
className='group inline-block relative rounded-xl cursor-pointer'
|
||||
href={`${MARKETPLACE_URL_PREFIX}/plugins/${plugin.org}/${plugin.name}`}
|
||||
>
|
||||
<Card
|
||||
key={plugin.name}
|
||||
|
@ -65,17 +113,7 @@ const CardWrapper = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
isShowInstallFromMarketplace && (
|
||||
<InstallFromMarketplace
|
||||
manifest={plugin as any}
|
||||
uniqueIdentifier={plugin.latest_package_identifier}
|
||||
onClose={hideInstallFromMarketplace}
|
||||
onSuccess={hideInstallFromMarketplace}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Plugin } from '../../types'
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import List from './index'
|
||||
import SortDropdown from '../sort-dropdown'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
|
||||
type ListWrapperProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
|
@ -17,28 +19,41 @@ const ListWrapper = ({
|
|||
showInstallButton,
|
||||
locale,
|
||||
}: ListWrapperProps) => {
|
||||
const { t } = useTranslation()
|
||||
const plugins = useMarketplaceContext(v => v.plugins)
|
||||
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
|
||||
const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient)
|
||||
const isLoading = useMarketplaceContext(v => v.isLoading)
|
||||
|
||||
return (
|
||||
<div className='flex flex-col grow px-12 py-2 bg-background-default-subtle'>
|
||||
<div className='relative flex flex-col grow px-12 py-2 bg-background-default-subtle'>
|
||||
{
|
||||
plugins && (
|
||||
<div className='flex items-center mb-4 pt-3'>
|
||||
<div className='title-xl-semi-bold text-text-primary'>{plugins.length} results</div>
|
||||
<div className='title-xl-semi-bold text-text-primary'>{t('plugin.marketplace.pluginsResult', { num: plugins.length })}</div>
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
|
||||
<SortDropdown />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollectionsFromClient || marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}
|
||||
plugins={plugins}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
/>
|
||||
{
|
||||
isLoading && (
|
||||
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isLoading && (
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollectionsFromClient || marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}
|
||||
plugins={plugins}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
|
@ -11,30 +12,30 @@ import {
|
|||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: 'install_count',
|
||||
order: 'DESC',
|
||||
text: 'Most Popular',
|
||||
},
|
||||
{
|
||||
value: 'version_updated_at',
|
||||
order: 'DESC',
|
||||
text: 'Recently Updated',
|
||||
},
|
||||
{
|
||||
value: 'created_at',
|
||||
order: 'DESC',
|
||||
text: 'Newly Released',
|
||||
},
|
||||
{
|
||||
value: 'created_at',
|
||||
order: 'ASC',
|
||||
text: 'First Released',
|
||||
},
|
||||
]
|
||||
|
||||
const SortDropdown = () => {
|
||||
const { t } = useTranslation()
|
||||
const options = [
|
||||
{
|
||||
value: 'install_count',
|
||||
order: 'DESC',
|
||||
text: t('plugin.marketplace.sortOption.mostPopular'),
|
||||
},
|
||||
{
|
||||
value: 'version_updated_at',
|
||||
order: 'DESC',
|
||||
text: t('plugin.marketplace.sortOption.recentlyUpdated'),
|
||||
},
|
||||
{
|
||||
value: 'created_at',
|
||||
order: 'DESC',
|
||||
text: t('plugin.marketplace.sortOption.newlyReleased'),
|
||||
},
|
||||
{
|
||||
value: 'created_at',
|
||||
order: 'ASC',
|
||||
text: t('plugin.marketplace.sortOption.firstReleased'),
|
||||
},
|
||||
]
|
||||
const sort = useMarketplaceContext(v => v.sort)
|
||||
const handleSortChange = useMarketplaceContext(v => v.handleSortChange)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
@ -53,7 +54,7 @@ const SortDropdown = () => {
|
|||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className='flex items-center px-2 pr-3 h-8 rounded-lg bg-state-base-hover-alt cursor-pointer'>
|
||||
<span className='mr-1 system-sm-regular'>
|
||||
Sort by
|
||||
{t('plugin.marketplace.sortBy')}
|
||||
</span>
|
||||
<span className='mr-1 system-sm-medium'>
|
||||
{selectedOption.text}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
@ -9,28 +8,39 @@ import Indicator from '@/app/components/header/indicator'
|
|||
import ToolItem from '@/app/components/tools/provider/tool-item'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import {
|
||||
fetchBuiltInToolList,
|
||||
fetchCollectionDetail,
|
||||
removeBuiltInToolCredential,
|
||||
updateBuiltInToolCredential,
|
||||
} from '@/service/tools'
|
||||
useBuiltinProviderInfo,
|
||||
useBuiltinTools,
|
||||
useInvalidateBuiltinProviderInfo,
|
||||
useRemoveProviderCredentials,
|
||||
useUpdateProviderCredentials,
|
||||
} from '@/service/use-tools'
|
||||
|
||||
const ActionList = () => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
const { data: provider } = useSWR(
|
||||
`builtin/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
|
||||
fetchCollectionDetail,
|
||||
)
|
||||
const { data } = useSWR(
|
||||
`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
|
||||
fetchBuiltInToolList,
|
||||
)
|
||||
const { data: provider } = useBuiltinProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
const invalidateProviderInfo = useInvalidateBuiltinProviderInfo()
|
||||
const { data } = useBuiltinTools(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
|
||||
const [showSettingAuth, setShowSettingAuth] = useState(false)
|
||||
|
||||
const handleCredentialSettingUpdate = () => {}
|
||||
const handleCredentialSettingUpdate = () => {
|
||||
invalidateProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
setShowSettingAuth(false)
|
||||
}
|
||||
|
||||
const { mutate: updatePermission } = useUpdateProviderCredentials({
|
||||
onSuccess: handleCredentialSettingUpdate,
|
||||
})
|
||||
|
||||
const { mutate: removePermission } = useRemoveProviderCredentials({
|
||||
onSuccess: handleCredentialSettingUpdate,
|
||||
})
|
||||
|
||||
if (!data || !provider)
|
||||
return null
|
||||
|
@ -77,24 +87,11 @@ const ActionList = () => {
|
|||
<ConfigCredential
|
||||
collection={provider}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async (value) => {
|
||||
await updateBuiltInToolCredential(provider.name, value)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
handleCredentialSettingUpdate()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onRemove={async () => {
|
||||
await removeBuiltInToolCredential(provider.name)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
handleCredentialSettingUpdate()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onSaved={async value => updatePermission({
|
||||
providerName: provider.name,
|
||||
credentials: value,
|
||||
})}
|
||||
onRemove={async () => removePermission(provider.name)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,7 @@ import { Github } from '@/app/components/base/icons/src/public/common'
|
|||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
|
||||
import UpdateFromMarketplace from '@/app/components/plugins/update-plugin/from-market-place'
|
||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
|
@ -55,17 +55,35 @@ const DetailHeader = ({
|
|||
source,
|
||||
tenant_id,
|
||||
version,
|
||||
latest_unique_identifier,
|
||||
latest_version,
|
||||
meta,
|
||||
} = detail
|
||||
const { author, name, label, description, icon, verified } = detail.declaration
|
||||
const isFromGitHub = source === PluginSource.github
|
||||
const isFromMarketplace = source === PluginSource.marketplace
|
||||
|
||||
const hasNewVersion = useMemo(() => {
|
||||
return source === PluginSource.github && latest_version !== version
|
||||
}, [source, latest_version, version])
|
||||
if (isFromGitHub)
|
||||
return latest_version !== version
|
||||
|
||||
if (isFromMarketplace)
|
||||
return !!latest_version && latest_version !== version
|
||||
|
||||
return false
|
||||
}, [isFromGitHub, isFromMarketplace, latest_version, version])
|
||||
|
||||
const [isShowUpdateModal, {
|
||||
setTrue: showUpdateModal,
|
||||
setFalse: hideUpdateModal,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleUpdate = async () => {
|
||||
if (isFromMarketplace) {
|
||||
showUpdateModal()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const fetchedReleases = await fetchReleases(author, name)
|
||||
if (fetchedReleases.length === 0)
|
||||
|
@ -106,6 +124,11 @@ const DetailHeader = ({
|
|||
}
|
||||
}
|
||||
|
||||
const handleUpdatedFromMarketplace = () => {
|
||||
onUpdate()
|
||||
hideUpdateModal()
|
||||
}
|
||||
|
||||
const [isShowPluginInfo, {
|
||||
setTrue: showPluginInfo,
|
||||
setFalse: hidePluginInfo,
|
||||
|
@ -222,6 +245,24 @@ const DetailHeader = ({
|
|||
isDisabled={deleting}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
isShowUpdateModal && (
|
||||
<UpdateFromMarketplace
|
||||
payload={{
|
||||
originalPackageInfo: {
|
||||
id: detail.plugin_unique_identifier,
|
||||
payload: detail.declaration,
|
||||
},
|
||||
targetPackageInfo: {
|
||||
id: latest_unique_identifier,
|
||||
version: latest_version,
|
||||
},
|
||||
}}
|
||||
onCancel={hideUpdateModal}
|
||||
onSave={handleUpdatedFromMarketplace}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ import Indicator from '@/app/components/header/indicator'
|
|||
import Switch from '@/app/components/base/switch'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import {
|
||||
deleteEndpoint,
|
||||
disableEndpoint,
|
||||
enableEndpoint,
|
||||
updateEndpoint,
|
||||
} from '@/service/plugins'
|
||||
useDeleteEndpoint,
|
||||
useDisableEndpoint,
|
||||
useEnableEndpoint,
|
||||
useUpdateEndpoint,
|
||||
} from '@/service/use-endpoints'
|
||||
|
||||
type Props = {
|
||||
data: EndpointListItem
|
||||
|
@ -32,43 +32,34 @@ const EndpointCard = ({
|
|||
const [active, setActive] = useState(data.enabled)
|
||||
const endpointID = data.id
|
||||
|
||||
// switch
|
||||
const [isShowDisableConfirm, {
|
||||
setTrue: showDisableConfirm,
|
||||
setFalse: hideDisableConfirm,
|
||||
}] = useBoolean(false)
|
||||
const activeEndpoint = async () => {
|
||||
try {
|
||||
await enableEndpoint({
|
||||
url: '/workspaces/current/endpoints/enable',
|
||||
endpointID,
|
||||
})
|
||||
const { mutate: enableEndpoint } = useEnableEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error(error)
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
setActive(false)
|
||||
}
|
||||
}
|
||||
const inactiveEndpoint = async () => {
|
||||
try {
|
||||
await disableEndpoint({
|
||||
url: '/workspaces/current/endpoints/disable',
|
||||
endpointID,
|
||||
})
|
||||
},
|
||||
})
|
||||
const { mutate: disableEndpoint } = useDisableEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
hideDisableConfirm()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
setActive(true)
|
||||
}
|
||||
}
|
||||
setActive(false)
|
||||
},
|
||||
})
|
||||
const handleSwitch = (state: boolean) => {
|
||||
if (state) {
|
||||
setActive(true)
|
||||
activeEndpoint()
|
||||
enableEndpoint(endpointID)
|
||||
}
|
||||
else {
|
||||
setActive(false)
|
||||
|
@ -76,30 +67,26 @@ const EndpointCard = ({
|
|||
}
|
||||
}
|
||||
|
||||
// delete
|
||||
const [isShowDeleteConfirm, {
|
||||
setTrue: showDeleteConfirm,
|
||||
setFalse: hideDeleteConfirm,
|
||||
}] = useBoolean(false)
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteEndpoint({
|
||||
url: '/workspaces/current/endpoints/delete',
|
||||
endpointID,
|
||||
})
|
||||
const { mutate: deleteEndpoint } = useDeleteEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
hideDeleteConfirm()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// update
|
||||
const [isShowEndpointModal, {
|
||||
setTrue: showEndpointModalConfirm,
|
||||
setFalse: hideEndpointModalConfirm,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return toolCredentialToFormSchemas([NAME_FIELD, ...data.declaration.settings])
|
||||
}, [data.declaration.settings])
|
||||
|
@ -110,27 +97,19 @@ const EndpointCard = ({
|
|||
}
|
||||
return addDefaultValue(formValue, formSchemas)
|
||||
}, [data.name, data.settings, formSchemas])
|
||||
|
||||
const handleUpdate = async (state: any) => {
|
||||
const newName = state.name
|
||||
delete state.name
|
||||
try {
|
||||
await updateEndpoint({
|
||||
url: '/workspaces/current/endpoints/update',
|
||||
body: {
|
||||
endpoint_id: data.id,
|
||||
settings: state,
|
||||
name: newName,
|
||||
},
|
||||
})
|
||||
const { mutate: updateEndpoint } = useUpdateEndpoint({
|
||||
onSuccess: async () => {
|
||||
await handleChange()
|
||||
hideEndpointModalConfirm()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
const handleUpdate = (state: any) => updateEndpoint({
|
||||
endpointID,
|
||||
state,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
||||
|
@ -192,7 +171,7 @@ const EndpointCard = ({
|
|||
hideDisableConfirm()
|
||||
setActive(true)
|
||||
}}
|
||||
onConfirm={inactiveEndpoint}
|
||||
onConfirm={() => disableEndpoint(endpointID)}
|
||||
/>
|
||||
)}
|
||||
{isShowDeleteConfirm && (
|
||||
|
@ -201,7 +180,7 @@ const EndpointCard = ({
|
|||
title={t('plugin.detailPanel.endpointDeleteTip')}
|
||||
content={<div>{t('plugin.detailPanel.endpointDeleteContent', { name: data.name })}</div>}
|
||||
onCancel={hideDeleteConfirm}
|
||||
onConfirm={handleDelete}
|
||||
onConfirm={() => deleteEndpoint(endpointID)}
|
||||
/>
|
||||
)}
|
||||
{isShowEndpointModal && (
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import EndpointModal from './endpoint-modal'
|
||||
|
@ -12,9 +11,10 @@ import Tooltip from '@/app/components/base/tooltip'
|
|||
import Toast from '@/app/components/base/toast'
|
||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||
import {
|
||||
createEndpoint,
|
||||
fetchEndpointList,
|
||||
} from '@/service/plugins'
|
||||
useCreateEndpoint,
|
||||
useEndpointList,
|
||||
useInvalidateEndpointList,
|
||||
} from '@/service/use-endpoints'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
|
@ -25,17 +25,9 @@ const EndpointList = ({ showTopBorder }: Props) => {
|
|||
const pluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
const pluginUniqueID = pluginDetail.plugin_unique_identifier
|
||||
const declaration = pluginDetail.declaration.endpoint
|
||||
const { data, mutate } = useSWR(
|
||||
{
|
||||
url: '/workspaces/current/endpoints/list/plugin',
|
||||
params: {
|
||||
plugin_id: pluginDetail.plugin_id,
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
},
|
||||
},
|
||||
fetchEndpointList,
|
||||
)
|
||||
const { data } = useEndpointList(pluginDetail.plugin_id)
|
||||
const invalidateEndpointList = useInvalidateEndpointList()
|
||||
|
||||
const [isShowEndpointModal, {
|
||||
setTrue: showEndpointModal,
|
||||
setFalse: hideEndpointModal,
|
||||
|
@ -45,26 +37,20 @@ const EndpointList = ({ showTopBorder }: Props) => {
|
|||
return toolCredentialToFormSchemas([NAME_FIELD, ...declaration.settings])
|
||||
}, [declaration.settings])
|
||||
|
||||
const handleCreate = async (state: any) => {
|
||||
const newName = state.name
|
||||
delete state.name
|
||||
try {
|
||||
await createEndpoint({
|
||||
url: '/workspaces/current/endpoints/create',
|
||||
body: {
|
||||
plugin_unique_identifier: pluginUniqueID,
|
||||
settings: state,
|
||||
name: newName,
|
||||
},
|
||||
})
|
||||
await mutate()
|
||||
const { mutate: createEndpoint } = useCreateEndpoint({
|
||||
onSuccess: async () => {
|
||||
await invalidateEndpointList(pluginDetail.plugin_id)
|
||||
hideEndpointModal()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
},
|
||||
onError: () => {
|
||||
Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const handleCreate = (state: any) => createEndpoint({
|
||||
pluginUniqueID,
|
||||
state,
|
||||
})
|
||||
|
||||
if (!data)
|
||||
return null
|
||||
|
@ -92,7 +78,7 @@ const EndpointList = ({ showTopBorder }: Props) => {
|
|||
<EndpointCard
|
||||
key={index}
|
||||
data={item}
|
||||
handleChange={mutate}
|
||||
handleChange={() => invalidateEndpointList(pluginDetail.plugin_id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -69,7 +69,7 @@ const EndpointModal: FC<Props> = ({
|
|||
isEditMode={true}
|
||||
showOnVariableMap={{}}
|
||||
validating={false}
|
||||
inputClassName='!bg-gray-50'
|
||||
inputClassName='bg-components-input-bg-normal hover:bg-state-base-hover-alt'
|
||||
fieldMoreInfo={item => item.url
|
||||
? (<a
|
||||
href={item.url}
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context'
|
||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { useModelProviderModelList } from '@/service/use-models'
|
||||
|
||||
const ModelList = () => {
|
||||
const { t } = useTranslation()
|
||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
|
||||
const { data: res } = useSWR(
|
||||
`/workspaces/current/model-providers/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}/models`,
|
||||
fetchModelProviderModelList,
|
||||
)
|
||||
const { data: res } = useModelProviderModelList(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
|
||||
|
||||
if (!res)
|
||||
return null
|
||||
|
|
|
@ -21,8 +21,8 @@ const i18nPrefix = 'plugin.action'
|
|||
type Props = {
|
||||
author: string
|
||||
installationId: string
|
||||
pluginUniqueIdentifier: string
|
||||
pluginName: string
|
||||
version: string
|
||||
usedInApps: number
|
||||
isShowFetchNewVersion: boolean
|
||||
isShowInfo: boolean
|
||||
|
@ -33,8 +33,8 @@ type Props = {
|
|||
const Action: FC<Props> = ({
|
||||
author,
|
||||
installationId,
|
||||
pluginUniqueIdentifier,
|
||||
pluginName,
|
||||
version,
|
||||
isShowFetchNewVersion,
|
||||
isShowInfo,
|
||||
isShowDelete,
|
||||
|
@ -61,7 +61,7 @@ const Action: FC<Props> = ({
|
|||
return
|
||||
const versions = fetchedReleases.map(release => release.tag_name)
|
||||
const latestVersion = getLatestVersion(versions)
|
||||
if (compareVersion(latestVersion, version) === 1) {
|
||||
if (compareVersion(latestVersion, meta!.version) === 1) {
|
||||
setShowUpdatePluginModal({
|
||||
onSaveCallback: () => {
|
||||
invalidateInstalledPluginList()
|
||||
|
@ -70,7 +70,7 @@ const Action: FC<Props> = ({
|
|||
type: PluginSource.github,
|
||||
github: {
|
||||
originalPackageInfo: {
|
||||
id: installationId,
|
||||
id: pluginUniqueIdentifier,
|
||||
repo: meta!.repo,
|
||||
version: meta!.version,
|
||||
package: meta!.package,
|
||||
|
|
|
@ -22,6 +22,7 @@ import cn from '@/utils/classnames'
|
|||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { useLanguage } from '../../header/account-setting/model-provider-page/hooks'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useCategories } from '../hooks'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
|
@ -34,6 +35,7 @@ const PluginItem: FC<Props> = ({
|
|||
}) => {
|
||||
const locale = useLanguage()
|
||||
const { t } = useTranslation()
|
||||
const { categoriesMap } = useCategories()
|
||||
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
|
||||
const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail)
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
|
@ -42,10 +44,10 @@ const PluginItem: FC<Props> = ({
|
|||
source,
|
||||
tenant_id,
|
||||
installation_id,
|
||||
plugin_unique_identifier,
|
||||
endpoints_active,
|
||||
meta,
|
||||
plugin_id,
|
||||
version,
|
||||
} = plugin
|
||||
const { category, author, name, label, description, icon, verified } = plugin.declaration
|
||||
|
||||
|
@ -61,17 +63,19 @@ const PluginItem: FC<Props> = ({
|
|||
? 'bg-[repeating-linear-gradient(-45deg,rgba(16,24,40,0.04),rgba(16,24,40,0.04)_5px,rgba(0,0,0,0.02)_5px,rgba(0,0,0,0.02)_10px)]'
|
||||
: 'bg-background-section-burn',
|
||||
)}
|
||||
onClick={() => setCurrentPluginDetail(plugin)}
|
||||
onClick={() => {
|
||||
setCurrentPluginDetail(plugin)
|
||||
}}
|
||||
>
|
||||
<div className={cn('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', className)}>
|
||||
<CornerMark text={category} />
|
||||
<CornerMark text={categoriesMap[category].label} />
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<div className='flex items-center justify-center w-10 h-10 overflow-hidden border-components-panel-border-subtle border-[1px] rounded-xl'>
|
||||
<img
|
||||
className='w-full h-full'
|
||||
src={`${API_PREFIX}/workspaces/current/plugin/icon?tenant_id=${tenant_id}&filename=${icon}`}
|
||||
alt={`plugin-${installation_id}-logo`}
|
||||
alt={`plugin-${plugin_unique_identifier}-logo`}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 w-0 grow">
|
||||
|
@ -84,10 +88,10 @@ const PluginItem: FC<Props> = ({
|
|||
<Description text={description[locale]} descriptionLineRows={1}></Description>
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
<Action
|
||||
pluginUniqueIdentifier={plugin_unique_identifier}
|
||||
installationId={installation_id}
|
||||
author={author}
|
||||
pluginName={name}
|
||||
version={version}
|
||||
usedInApps={5}
|
||||
isShowFetchNewVersion={source === PluginSource.github}
|
||||
isShowInfo={source === PluginSource.github}
|
||||
|
|
|
@ -68,7 +68,7 @@ export const PluginPageContextProvider = ({
|
|||
{ value: 'plugins', text: t('common.menus.plugins') },
|
||||
...(
|
||||
enable_marketplace
|
||||
? [{ value: 'discover', text: 'Explore Marketplace' }]
|
||||
? [{ value: 'discover', text: t('common.menus.exploreMarketplace') }]
|
||||
: []
|
||||
),
|
||||
]
|
||||
|
|
|
@ -17,6 +17,9 @@ const DebugInfo: FC = () => {
|
|||
const { t } = useTranslation()
|
||||
const { data: info, isLoading } = useDebugKey()
|
||||
|
||||
// info.key likes 4580bdb7-b878-471c-a8a4-bfd760263a53 mask the middle part using *.
|
||||
const maskedKey = info?.key?.replace(/(.{8})(.*)(.{8})/, '$1********$3')
|
||||
|
||||
if (isLoading) return null
|
||||
|
||||
return (
|
||||
|
@ -26,7 +29,7 @@ const DebugInfo: FC = () => {
|
|||
popupContent={
|
||||
<>
|
||||
<div className='flex items-center gap-1 self-stretch'>
|
||||
<span className='flex flex-col justify-center items-start flex-grow flex-shrink-0 basis-0 text-text-secondary system-sm-semibold'>{t(`${i18nPrefix}.title`)}</span>
|
||||
<span className='flex flex-col justify-center items-start grow shrink-0 basis-0 text-text-secondary system-sm-semibold'>{t(`${i18nPrefix}.title`)}</span>
|
||||
<a href='' target='_blank' className='flex items-center gap-0.5 text-text-accent-light-mode-only cursor-pointer'>
|
||||
<span className='system-xs-medium'>{t(`${i18nPrefix}.viewDocs`)}</span>
|
||||
<RiArrowRightUpLine className='w-3 h-3' />
|
||||
|
@ -40,6 +43,7 @@ const DebugInfo: FC = () => {
|
|||
<KeyValueItem
|
||||
label={'Key'}
|
||||
value={info?.key || ''}
|
||||
maskedValue={maskedKey}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -9,8 +9,10 @@ import { Group } from '@/app/components/base/icons/src/vender/other'
|
|||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import Line from '../../marketplace/empty/line'
|
||||
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [selectedAction, setSelectedAction] = useState<string | null>(null)
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
|
@ -30,9 +32,9 @@ const Empty = () => {
|
|||
|
||||
const text = useMemo(() => {
|
||||
if (pluginList?.plugins.length === 0)
|
||||
return 'No plugins installed'
|
||||
return t('plugin.list.noInstalled')
|
||||
if (filters.categories.length > 0 || filters.tags.length > 0 || filters.searchQuery)
|
||||
return 'No plugins found'
|
||||
return t('plugin.list.notFound')
|
||||
}, [pluginList, filters])
|
||||
|
||||
return (
|
||||
|
@ -70,11 +72,11 @@ const Empty = () => {
|
|||
{[
|
||||
...(
|
||||
(enable_marketplace || true)
|
||||
? [{ icon: MagicBox, text: 'Marketplace', action: 'marketplace' }]
|
||||
? [{ icon: MagicBox, text: t('plugin.list.source.marketplace'), action: 'marketplace' }]
|
||||
: []
|
||||
),
|
||||
{ icon: Github, text: 'GitHub', action: 'github' },
|
||||
{ icon: FileZip, text: 'Local Package File', action: 'local' },
|
||||
{ icon: Github, text: t('plugin.list.source.github'), action: 'github' },
|
||||
{ icon: FileZip, text: t('plugin.list.source.local'), action: 'local' },
|
||||
].map(({ icon: Icon, text, action }) => (
|
||||
<div
|
||||
key={action}
|
||||
|
@ -90,7 +92,7 @@ const Empty = () => {
|
|||
}}
|
||||
>
|
||||
<Icon className="w-4 h-4 text-text-tertiary" />
|
||||
<span className='text-text-secondary system-md-regular'>{`Install from ${text}`}</span>
|
||||
<span className='text-text-secondary system-md-regular'>{text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useCategories } from '../../hooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type CategoriesFilterProps = {
|
||||
value: string[]
|
||||
|
@ -22,27 +24,11 @@ const CategoriesFilter = ({
|
|||
value,
|
||||
onChange,
|
||||
}: CategoriesFilterProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const options = [
|
||||
{
|
||||
value: 'model',
|
||||
text: 'Model',
|
||||
},
|
||||
{
|
||||
value: 'tool',
|
||||
text: 'Tool',
|
||||
},
|
||||
{
|
||||
value: 'extension',
|
||||
text: 'Extension',
|
||||
},
|
||||
{
|
||||
value: 'bundle',
|
||||
text: 'Bundle',
|
||||
},
|
||||
]
|
||||
const filteredOptions = options.filter(option => option.text.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const { categories: options, categoriesMap } = useCategories()
|
||||
const filteredOptions = options.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const handleCheck = (id: string) => {
|
||||
if (value.includes(id))
|
||||
onChange(value.filter(tag => tag !== id))
|
||||
|
@ -70,10 +56,10 @@ const CategoriesFilter = ({
|
|||
'flex items-center p-1 system-sm-medium',
|
||||
)}>
|
||||
{
|
||||
!selectedTagsLength && 'All Categories'
|
||||
!selectedTagsLength && t('plugin.allCategories')
|
||||
}
|
||||
{
|
||||
!!selectedTagsLength && value.slice(0, 2).join(',')
|
||||
!!selectedTagsLength && value.map(val => categoriesMap[val].label).slice(0, 2).join(',')
|
||||
}
|
||||
{
|
||||
selectedTagsLength > 2 && (
|
||||
|
@ -110,23 +96,23 @@ const CategoriesFilter = ({
|
|||
showLeftIcon
|
||||
value={searchText}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
placeholder='Search categories'
|
||||
placeholder={t('plugin.searchCategories')}
|
||||
/>
|
||||
</div>
|
||||
<div className='p-1 max-h-[448px] overflow-y-auto'>
|
||||
{
|
||||
filteredOptions.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
key={option.name}
|
||||
className='flex items-center px-2 py-1.5 h-7 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => handleCheck(option.value)}
|
||||
onClick={() => handleCheck(option.name)}
|
||||
>
|
||||
<Checkbox
|
||||
className='mr-1'
|
||||
checked={value.includes(option.value)}
|
||||
checked={value.includes(option.name)}
|
||||
/>
|
||||
<div className='px-1 system-sm-medium text-text-secondary'>
|
||||
{option.text}
|
||||
{option.label}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
type SearchBoxProps = {
|
||||
searchQuery: string
|
||||
onChange: (query: string) => void
|
||||
|
@ -10,13 +11,15 @@ const SearchBox: React.FC<SearchBoxProps> = ({
|
|||
searchQuery,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Input
|
||||
wrapperClassName='flex w-[200px] items-center rounded-lg'
|
||||
className='bg-components-input-bg-normal'
|
||||
showLeftIcon
|
||||
value={searchQuery}
|
||||
placeholder='Search'
|
||||
placeholder={t('plugin.search')}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value)
|
||||
}}
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useTags } from '../../hooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type TagsFilterProps = {
|
||||
value: string[]
|
||||
|
@ -22,19 +24,11 @@ const TagsFilter = ({
|
|||
value,
|
||||
onChange,
|
||||
}: TagsFilterProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const options = [
|
||||
{
|
||||
value: 'search',
|
||||
text: 'Search',
|
||||
},
|
||||
{
|
||||
value: 'image',
|
||||
text: 'Image',
|
||||
},
|
||||
]
|
||||
const filteredOptions = options.filter(option => option.text.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const { tags: options, tagsMap } = useTags()
|
||||
const filteredOptions = options.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const handleCheck = (id: string) => {
|
||||
if (value.includes(id))
|
||||
onChange(value.filter(tag => tag !== id))
|
||||
|
@ -62,10 +56,10 @@ const TagsFilter = ({
|
|||
'flex items-center p-1 system-sm-medium',
|
||||
)}>
|
||||
{
|
||||
!selectedTagsLength && 'All Tags'
|
||||
!selectedTagsLength && t('pluginTags.allTags')
|
||||
}
|
||||
{
|
||||
!!selectedTagsLength && value.slice(0, 2).join(',')
|
||||
!!selectedTagsLength && value.map(val => tagsMap[val].label).slice(0, 2).join(',')
|
||||
}
|
||||
{
|
||||
selectedTagsLength > 2 && (
|
||||
|
@ -97,23 +91,23 @@ const TagsFilter = ({
|
|||
showLeftIcon
|
||||
value={searchText}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
placeholder='Search tags'
|
||||
placeholder={t('pluginTags.searchTags')}
|
||||
/>
|
||||
</div>
|
||||
<div className='p-1 max-h-[448px] overflow-y-auto'>
|
||||
{
|
||||
filteredOptions.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
key={option.name}
|
||||
className='flex items-center px-2 py-1.5 h-7 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => handleCheck(option.value)}
|
||||
onClick={() => handleCheck(option.name)}
|
||||
>
|
||||
<Checkbox
|
||||
className='mr-1'
|
||||
checked={value.includes(option.value)}
|
||||
checked={value.includes(option.name)}
|
||||
/>
|
||||
<div className='px-1 system-sm-medium text-text-secondary'>
|
||||
{option.text}
|
||||
{option.label}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
|
@ -16,8 +16,7 @@ import InstallPluginDropdown from './install-plugin-dropdown'
|
|||
import { useUploader } from './use-uploader'
|
||||
import usePermission from './use-permission'
|
||||
import DebugInfo from './debug-info'
|
||||
import { usePluginTasksStore } from './store'
|
||||
import InstallInfo from './install-info'
|
||||
import PluginTasks from './plugin-tasks'
|
||||
import Button from '@/app/components/base/button'
|
||||
import TabSlider from '@/app/components/base/tab-slider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
@ -102,8 +101,6 @@ const PluginPage = ({
|
|||
const options = usePluginPageContext(v => v.options)
|
||||
const [activeTab, setActiveTab] = usePluginPageContext(v => [v.activeTab, v.setActiveTab])
|
||||
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||
const [installed, total] = [2, 3] // Replace this with the actual progress
|
||||
const progressPercentage = (installed / total) * 100
|
||||
|
||||
const uploaderProps = useUploader({
|
||||
onFileChange: setCurrentFile,
|
||||
|
@ -113,12 +110,6 @@ const PluginPage = ({
|
|||
|
||||
const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps
|
||||
|
||||
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
|
||||
|
||||
useEffect(() => {
|
||||
setPluginTasksWithPolling()
|
||||
}, [setPluginTasksWithPolling])
|
||||
|
||||
return (
|
||||
<div
|
||||
id='marketplace-container'
|
||||
|
@ -141,8 +132,8 @@ const PluginPage = ({
|
|||
options={options}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-shrink-0 items-center gap-1'>
|
||||
<InstallInfo />
|
||||
<div className='flex shrink-0 items-center gap-1'>
|
||||
<PluginTasks />
|
||||
{canManagement && (
|
||||
<InstallPluginDropdown
|
||||
onSwitchToMarketplaceTab={() => setActiveTab('discover')}
|
||||
|
@ -181,7 +172,7 @@ const PluginPage = ({
|
|||
)}
|
||||
<div className={`flex py-4 justify-center items-center gap-2 ${dragging ? 'text-text-accent' : 'text-text-quaternary'}`}>
|
||||
<RiDragDropLine className="w-4 h-4" />
|
||||
<span className="system-xs-regular">Drop plugin package here to install</span>
|
||||
<span className="system-xs-regular">{t('plugin.installModal.dropPluginToInstall')}</span>
|
||||
</div>
|
||||
{currentFile && (
|
||||
<InstallFromLocalPackage
|
||||
|
|
|
@ -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
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
onSwitchToMarketplaceTab: () => void
|
||||
|
@ -23,6 +24,7 @@ type Props = {
|
|||
const InstallPluginDropdown = ({
|
||||
onSwitchToMarketplaceTab,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [selectedAction, setSelectedAction] = useState<string | null>(null)
|
||||
|
@ -65,14 +67,14 @@ const InstallPluginDropdown = ({
|
|||
className={cn('w-full h-full p-2 text-components-button-secondary-text', isMenuOpen && 'bg-state-base-hover')}
|
||||
>
|
||||
<RiAddLine className='w-4 h-4' />
|
||||
<span className='pl-1'>Install plugin</span>
|
||||
<span className='pl-1'>{t('plugin.installPlugin')}</span>
|
||||
<RiArrowDownSLine className='w-4 h-4 ml-1' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='flex flex-col p-1 pb-2 items-start w-[200px] bg-components-panel-bg-blur border border-components-panel-border rounded-xl shadows-shadow-lg'>
|
||||
<span className='flex pt-1 pb-0.5 pl-2 pr-3 items-start self-stretch text-text-tertiary system-xs-medium-uppercase'>
|
||||
Install From
|
||||
{t('plugin.installFrom')}
|
||||
</span>
|
||||
<input
|
||||
type='file'
|
||||
|
@ -85,11 +87,11 @@ const InstallPluginDropdown = ({
|
|||
{[
|
||||
...(
|
||||
(enable_marketplace || true)
|
||||
? [{ icon: MagicBox, text: 'Marketplace', action: 'marketplace' }]
|
||||
? [{ icon: MagicBox, text: t('plugin.source.marketplace'), action: 'marketplace' }]
|
||||
: []
|
||||
),
|
||||
{ icon: Github, text: 'GitHub', action: 'github' },
|
||||
{ icon: FileZip, text: 'Local Package File', action: 'local' },
|
||||
{ icon: Github, text: t('plugin.source.github'), action: 'github' },
|
||||
{ icon: FileZip, text: t('plugin.source.local'), action: 'local' },
|
||||
].map(({ icon: Icon, text, action }) => (
|
||||
<div
|
||||
key={action}
|
||||
|
|
47
web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts
Normal file
47
web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
152
web/app/components/plugins/plugin-page/plugin-tasks/index.tsx
Normal file
152
web/app/components/plugins/plugin-page/plugin-tasks/index.tsx
Normal file
|
@ -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()
|
||||
},
|
||||
}))
|
|
@ -100,6 +100,7 @@ export type PluginDetail = {
|
|||
endpoints_active: number
|
||||
version: string
|
||||
latest_version: string
|
||||
latest_unique_identifier: string
|
||||
source: PluginSource
|
||||
meta?: MetaData
|
||||
}
|
||||
|
@ -194,19 +195,10 @@ export type GitHubUrlInfo = {
|
|||
}
|
||||
|
||||
// endpoint
|
||||
export type CreateEndpointRequest = {
|
||||
plugin_unique_identifier: string
|
||||
settings: Record<string, any>
|
||||
name: string
|
||||
}
|
||||
export type EndpointOperationResponse = {
|
||||
result: 'success' | 'error'
|
||||
}
|
||||
export type EndpointsRequest = {
|
||||
page_size: number
|
||||
page: number
|
||||
plugin_id: string
|
||||
}
|
||||
|
||||
export type EndpointsResponse = {
|
||||
endpoints: EndpointListItem[]
|
||||
has_more: boolean
|
||||
|
@ -246,6 +238,11 @@ export type InstallPackageResponse = {
|
|||
task_id: string
|
||||
}
|
||||
|
||||
export type updatePackageResponse = {
|
||||
all_installed: boolean
|
||||
task_id: string
|
||||
}
|
||||
|
||||
export type uploadGitHubResponse = {
|
||||
unique_identifier: string
|
||||
manifest: PluginDeclaration
|
||||
|
@ -268,6 +265,9 @@ export type PluginStatus = {
|
|||
plugin_id: string
|
||||
status: TaskStatus
|
||||
message: string
|
||||
icon: string
|
||||
labels: Record<Locale, string>
|
||||
taskId: string
|
||||
}
|
||||
|
||||
export type PluginTask = {
|
||||
|
@ -305,3 +305,15 @@ export type UninstallPluginResponse = {
|
|||
export type PluginsFromMarketplaceResponse = {
|
||||
plugins: Plugin[]
|
||||
}
|
||||
|
||||
export type Dependency = {
|
||||
type: 'github' | 'marketplace' | 'package'
|
||||
value: {
|
||||
repo?: string
|
||||
version?: string
|
||||
package?: string
|
||||
github_plugin_unique_identifier?: string
|
||||
marketplace_plugin_unique_identifier?: string
|
||||
plugin_unique_identifier?: string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { pluginManifestToCardPluginProps } from '@/app/components/plugins/instal
|
|||
import useGetIcon from '../install-plugin/base/use-get-icon'
|
||||
import { updateFromMarketPlace } from '@/service/plugins'
|
||||
import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status'
|
||||
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
|
||||
import { usePluginTaskList } from '@/service/use-plugins'
|
||||
|
||||
const i18nPrefix = 'plugin.upgrade'
|
||||
|
||||
|
@ -56,7 +56,7 @@ const UpdatePluginModal: FC<Props> = ({
|
|||
}
|
||||
|
||||
const [uploadStep, setUploadStep] = useState<UploadStep>(UploadStep.notStarted)
|
||||
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
|
||||
const configBtnText = useMemo(() => {
|
||||
return ({
|
||||
|
@ -69,29 +69,36 @@ const UpdatePluginModal: FC<Props> = ({
|
|||
const handleConfirm = useCallback(async () => {
|
||||
if (uploadStep === UploadStep.notStarted) {
|
||||
setUploadStep(UploadStep.upgrading)
|
||||
const {
|
||||
all_installed: isInstalled,
|
||||
task_id: taskId,
|
||||
} = await updateFromMarketPlace({
|
||||
original_plugin_unique_identifier: originalPackageInfo.id,
|
||||
new_plugin_unique_identifier: targetPackageInfo.id,
|
||||
})
|
||||
if (isInstalled) {
|
||||
try {
|
||||
const {
|
||||
all_installed: isInstalled,
|
||||
task_id: taskId,
|
||||
} = await updateFromMarketPlace({
|
||||
original_plugin_unique_identifier: originalPackageInfo.id,
|
||||
new_plugin_unique_identifier: targetPackageInfo.id,
|
||||
})
|
||||
|
||||
if (isInstalled) {
|
||||
onSave()
|
||||
return
|
||||
}
|
||||
handleRefetch()
|
||||
await check({
|
||||
taskId,
|
||||
pluginUniqueIdentifier: targetPackageInfo.id,
|
||||
})
|
||||
onSave()
|
||||
return
|
||||
}
|
||||
setPluginTasksWithPolling()
|
||||
await check({
|
||||
taskId,
|
||||
pluginUniqueIdentifier: targetPackageInfo.id,
|
||||
})
|
||||
onSave()
|
||||
catch (e) {
|
||||
setUploadStep(UploadStep.notStarted)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (uploadStep === UploadStep.installed) {
|
||||
onSave()
|
||||
onCancel()
|
||||
}
|
||||
}, [onCancel, onSave, uploadStep, check, originalPackageInfo.id, setPluginTasksWithPolling, targetPackageInfo.id])
|
||||
}, [onCancel, onSave, uploadStep, check, originalPackageInfo.id, handleRefetch, targetPackageInfo.id])
|
||||
const usedInAppInfo = useMemo(() => {
|
||||
return (
|
||||
<div className='flex px-0.5 justify-center items-center gap-0.5'>
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
useMarketplaceCollectionsAndPlugins,
|
||||
useMarketplacePlugins,
|
||||
} from '@/app/components/plugins/marketplace/hooks'
|
||||
import { PluginType } from '@/app/components/plugins/types'
|
||||
|
||||
export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => {
|
||||
const {
|
||||
|
@ -25,18 +26,20 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
|
|||
if (searchPluginText || filterPluginTags.length) {
|
||||
if (searchPluginText) {
|
||||
queryPluginsWithDebounced({
|
||||
category: PluginType.tool,
|
||||
query: searchPluginText,
|
||||
tags: filterPluginTags,
|
||||
})
|
||||
return
|
||||
}
|
||||
queryPlugins({
|
||||
category: PluginType.tool,
|
||||
query: searchPluginText,
|
||||
tags: filterPluginTags,
|
||||
})
|
||||
}
|
||||
else {
|
||||
queryMarketplaceCollectionsAndPlugins()
|
||||
queryMarketplaceCollectionsAndPlugins({ category: PluginType.tool })
|
||||
resetPlugins()
|
||||
}
|
||||
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins])
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { RiArrowUpDoubleLine } from '@remixicon/react'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
RiArrowUpDoubleLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMarketplace } from './hooks'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
import { MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
|
||||
type MarketplaceProps = {
|
||||
searchPluginText: string
|
||||
|
@ -25,7 +29,7 @@ const Marketplace = ({
|
|||
} = useMarketplace(searchPluginText, filterPluginTags)
|
||||
|
||||
return (
|
||||
<div className='shrink-0 sticky -bottom-[442px] h-[530px] overflow-y-auto px-12 py-2 pt-0 bg-background-default-subtle'>
|
||||
<div className='flex flex-col shrink-0 sticky -bottom-[442px] h-[530px] overflow-y-auto px-12 py-2 pt-0 bg-background-default-subtle'>
|
||||
<RiArrowUpDoubleLine
|
||||
className='absolute top-2 left-1/2 -translate-x-1/2 w-4 h-4 text-text-quaternary cursor-pointer'
|
||||
onClick={() => onMarketplaceScroll()}
|
||||
|
@ -51,7 +55,15 @@ const Marketplace = ({
|
|||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
{t('plugin.category.bundles')}
|
||||
</span>
|
||||
{t('plugin.marketplace.inDifyMarketplace')}
|
||||
{t('common.operation.in')}
|
||||
<a
|
||||
href={`${MARKETPLACE_URL_PREFIX}`}
|
||||
className='flex items-center ml-1 system-sm-medium text-text-accent'
|
||||
target='_blank'
|
||||
>
|
||||
{t('plugin.marketplace.difyMarketplace')}
|
||||
<RiArrowRightUpLine className='w-4 h-4' />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
|
|
|
@ -10,10 +10,10 @@ import LabelFilter from '@/app/components/tools/labels/filter'
|
|||
import Input from '@/app/components/base/input'
|
||||
import ProviderDetail from '@/app/components/tools/provider/detail'
|
||||
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
||||
import { fetchCollectionList } from '@/service/tools'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useAllToolProviders } from '@/service/use-tools'
|
||||
|
||||
const ProviderList = () => {
|
||||
const { t } = useTranslation()
|
||||
|
@ -36,8 +36,7 @@ const ProviderList = () => {
|
|||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
}
|
||||
|
||||
const [collectionList, setCollectionList] = useState<Collection[]>([])
|
||||
const { data: collectionList, refetch } = useAllToolProviders()
|
||||
const filteredCollectionList = useMemo(() => {
|
||||
return collectionList.filter((collection) => {
|
||||
if (collection.type !== activeTab)
|
||||
|
@ -49,13 +48,6 @@ const ProviderList = () => {
|
|||
return true
|
||||
})
|
||||
}, [activeTab, tagFilterValue, keywords, collectionList])
|
||||
const getProviderList = async () => {
|
||||
const list = await fetchCollectionList()
|
||||
setCollectionList([...list])
|
||||
}
|
||||
useEffect(() => {
|
||||
getProviderList()
|
||||
}, [])
|
||||
|
||||
const [currentProvider, setCurrentProvider] = useState<Collection | undefined>()
|
||||
useEffect(() => {
|
||||
|
@ -106,7 +98,7 @@ const ProviderList = () => {
|
|||
>
|
||||
<Card
|
||||
className={cn(
|
||||
'border-[1.5px] border-transparent',
|
||||
'border-[1.5px] border-transparent cursor-pointer',
|
||||
currentProvider?.id === collection.id && 'border-components-option-card-option-selected-border',
|
||||
)}
|
||||
hideCornerMark
|
||||
|
@ -140,7 +132,7 @@ const ProviderList = () => {
|
|||
<ProviderDetail
|
||||
collection={currentProvider}
|
||||
onHide={() => setCurrentProvider(undefined)}
|
||||
onRefreshData={getProviderList}
|
||||
onRefreshData={refetch}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -44,6 +44,7 @@ import { useProviderContext } from '@/context/provider-context'
|
|||
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useInvalidateAllWorkflowTools } from '@/service/use-tools'
|
||||
|
||||
type Props = {
|
||||
collection: Collection
|
||||
|
@ -65,7 +66,7 @@ const ProviderDetail = ({
|
|||
const isBuiltIn = collection.type === CollectionType.builtIn
|
||||
const isModel = collection.type === CollectionType.model
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
|
||||
const invalidateAllWorkflowTools = useInvalidateAllWorkflowTools()
|
||||
const [isDetailLoading, setIsDetailLoading] = useState(false)
|
||||
|
||||
// built in provider
|
||||
|
@ -164,6 +165,7 @@ const ProviderDetail = ({
|
|||
workflow_tool_id: string
|
||||
}>) => {
|
||||
await saveWorkflowToolProvider(data)
|
||||
invalidateAllWorkflowTools()
|
||||
onRefreshData()
|
||||
getWorkflowToolProvider()
|
||||
Toast.notify({
|
||||
|
|
183
web/app/components/tools/tool-selector/index.tsx
Normal file
183
web/app/components/tools/tool-selector/index.tsx
Normal file
|
@ -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)
|
53
web/app/components/tools/tool-selector/tool-trigger.tsx
Normal file
53
web/app/components/tools/tool-selector/tool-trigger.tsx
Normal file
|
@ -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
|
|
@ -14,6 +14,7 @@ import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflo
|
|||
import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types'
|
||||
import type { InputVar } from '@/app/components/workflow/types'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useInvalidateAllWorkflowTools } from '@/service/use-tools'
|
||||
|
||||
type Props = {
|
||||
disabled: boolean
|
||||
|
@ -46,6 +47,7 @@ const WorkflowToolConfigureButton = ({
|
|||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [detail, setDetail] = useState<WorkflowToolProviderResponse>()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const invalidateAllWorkflowTools = useInvalidateAllWorkflowTools()
|
||||
|
||||
const outdated = useMemo(() => {
|
||||
if (!detail)
|
||||
|
@ -135,6 +137,7 @@ const WorkflowToolConfigureButton = ({
|
|||
const createHandle = async (data: WorkflowToolProviderRequest & { workflow_app_id: string }) => {
|
||||
try {
|
||||
await createWorkflowToolProvider(data)
|
||||
invalidateAllWorkflowTools()
|
||||
onRefreshData?.()
|
||||
getDetail(workflowAppId)
|
||||
Toast.notify({
|
||||
|
@ -156,6 +159,7 @@ const WorkflowToolConfigureButton = ({
|
|||
await handlePublish()
|
||||
await saveWorkflowToolProvider(data)
|
||||
onRefreshData?.()
|
||||
invalidateAllWorkflowTools()
|
||||
getDetail(workflowAppId)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
|
|
|
@ -23,6 +23,7 @@ import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
|
|||
type AllToolsProps = {
|
||||
className?: string
|
||||
searchText: string
|
||||
tags: string[]
|
||||
buildInTools: ToolWithProvider[]
|
||||
customTools: ToolWithProvider[]
|
||||
workflowTools: ToolWithProvider[]
|
||||
|
@ -34,6 +35,7 @@ type AllToolsProps = {
|
|||
const AllTools = ({
|
||||
className,
|
||||
searchText,
|
||||
tags = [],
|
||||
onSelect,
|
||||
buildInTools,
|
||||
workflowTools,
|
||||
|
@ -50,6 +52,7 @@ const AllTools = ({
|
|||
return text.toLowerCase().includes(keywords.toLowerCase())
|
||||
}
|
||||
|
||||
const hasFilter = searchText || tags.length > 0
|
||||
const tools = useMemo(() => {
|
||||
let mergedTools: ToolWithProvider[] = []
|
||||
if (activeTab === ToolTypeEnum.All)
|
||||
|
@ -61,18 +64,15 @@ const AllTools = ({
|
|||
if (activeTab === ToolTypeEnum.Workflow)
|
||||
mergedTools = workflowTools
|
||||
|
||||
if (!searchText)
|
||||
if (!hasFilter)
|
||||
return mergedTools.filter(toolWithProvider => toolWithProvider.tools.length > 0)
|
||||
|
||||
return mergedTools.filter((toolWithProvider) => {
|
||||
return isMatchingKeywords(toolWithProvider.name, searchText)
|
||||
|| toolWithProvider.tools.some((tool) => {
|
||||
return Object.values(tool.label).some((label) => {
|
||||
return isMatchingKeywords(label, searchText)
|
||||
})
|
||||
})
|
||||
return isMatchingKeywords(toolWithProvider.name, searchText) || toolWithProvider.tools.some((tool) => {
|
||||
return tool.label[language].toLowerCase().includes(searchText.toLowerCase()) || tool.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
})
|
||||
})
|
||||
}, [activeTab, buildInTools, customTools, workflowTools, searchText, language])
|
||||
}, [activeTab, buildInTools, customTools, workflowTools, searchText, language, hasFilter])
|
||||
|
||||
const {
|
||||
queryPluginsWithDebounced: fetchPlugins,
|
||||
|
@ -80,14 +80,15 @@ const AllTools = ({
|
|||
} = useMarketplacePlugins()
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText) {
|
||||
if (searchText || tags.length > 0) {
|
||||
fetchPlugins({
|
||||
query: searchText,
|
||||
tags,
|
||||
category: PluginType.tool,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchText])
|
||||
}, [searchText, tags])
|
||||
|
||||
const pluginRef = useRef(null)
|
||||
const wrapElemRef = useRef<HTMLDivElement>(null)
|
||||
|
@ -135,12 +136,14 @@ const AllTools = ({
|
|||
tools={tools}
|
||||
onSelect={onSelect}
|
||||
viewType={activeView}
|
||||
hasSearchText={!!searchText}
|
||||
/>
|
||||
{/* Plugins from marketplace */}
|
||||
<PluginList
|
||||
wrapElemRef={wrapElemRef}
|
||||
list={notInstalledPlugins as any} ref={pluginRef}
|
||||
searchText={searchText}
|
||||
tags={tags}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,16 +14,19 @@ type Props = {
|
|||
wrapElemRef: React.RefObject<HTMLElement>
|
||||
list: Plugin[]
|
||||
searchText: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
const List = ({
|
||||
wrapElemRef,
|
||||
searchText,
|
||||
tags,
|
||||
list,
|
||||
}: Props, ref: any) => {
|
||||
const { t } = useTranslation()
|
||||
const hasSearchText = !searchText
|
||||
const urlWithSearchText = `${marketplaceUrlPrefix}/plugins?q=${searchText}`
|
||||
const hasFilter = !searchText
|
||||
const hasRes = list.length > 0
|
||||
const urlWithSearchText = `${marketplaceUrlPrefix}/marketplace?q=${searchText}&tags=${tags.join(',')}`
|
||||
const nextToStickyELemRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { handleScroll, scrollPosition } = useStickyScroll({
|
||||
|
@ -58,7 +61,7 @@ const List = ({
|
|||
window.open(urlWithSearchText, '_blank')
|
||||
}
|
||||
|
||||
if (hasSearchText) {
|
||||
if (hasFilter) {
|
||||
return (
|
||||
<Link
|
||||
className='sticky bottom-0 z-10 flex h-8 px-4 py-1 system-sm-medium items-center border-t border-[0.5px] border-components-panel-border bg-components-panel-bg-blur rounded-b-lg shadow-lg text-text-accent-light-mode-only cursor-pointer'
|
||||
|
@ -73,21 +76,23 @@ const List = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('sticky z-10 flex justify-between h-8 px-4 py-1 text-text-primary system-sm-medium cursor-pointer', stickyClassName)}
|
||||
onClick={handleHeadClick}
|
||||
>
|
||||
<span>{t('plugin.fromMarketplace')}</span>
|
||||
<Link
|
||||
href={urlWithSearchText}
|
||||
target='_blank'
|
||||
className='flex items-center text-text-accent-light-mode-only'
|
||||
onClick={e => e.stopPropagation()}
|
||||
{hasRes && (
|
||||
<div
|
||||
className={cn('sticky z-10 flex justify-between h-8 px-4 py-1 text-text-primary system-sm-medium cursor-pointer', stickyClassName)}
|
||||
onClick={handleHeadClick}
|
||||
>
|
||||
<span>{t('plugin.searchInMarketplace')}</span>
|
||||
<RiArrowRightUpLine className='ml-0.5 w-3 h-3' />
|
||||
</Link>
|
||||
</div>
|
||||
<span>{t('plugin.fromMarketplace')}</span>
|
||||
<Link
|
||||
href={urlWithSearchText}
|
||||
target='_blank'
|
||||
className='flex items-center text-text-accent-light-mode-only'
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<span>{t('plugin.searchInMarketplace')}</span>
|
||||
<RiArrowRightUpLine className='ml-0.5 w-3 h-3' />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className='p-1' ref={nextToStickyELemRef}>
|
||||
{list.map((item, index) => (
|
||||
<Item
|
||||
|
|
|
@ -48,6 +48,7 @@ const ToolPicker: FC<Props> = ({
|
|||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
|
@ -105,20 +106,20 @@ const ToolPicker: FC<Props> = ({
|
|||
</PortalToFollowElemTrigger>
|
||||
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
{ }
|
||||
<div className="relative w-[320px] min-h-20 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg">
|
||||
<div className="relative w-[356px] min-h-20 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg">
|
||||
<div className='p-2 pb-1'>
|
||||
<SearchBox
|
||||
search={searchText}
|
||||
onSearchChange={setSearchText}
|
||||
tags={[]}
|
||||
onTagsChange={() => { }}
|
||||
tags={tags}
|
||||
onTagsChange={setTags}
|
||||
size='small'
|
||||
placeholder={t('plugin.searchTools')!}
|
||||
/>
|
||||
</div>
|
||||
<AllTools
|
||||
className='mt-1'
|
||||
tags={tags}
|
||||
searchText={searchText}
|
||||
onSelect={handleSelect}
|
||||
buildInTools={buildInTools || []}
|
||||
|
|
|
@ -57,6 +57,7 @@ const ToolItem: FC<Props> = ({
|
|||
tool_name: payload.name,
|
||||
tool_label: payload.label[language],
|
||||
title: payload.label[language],
|
||||
is_team_authorization: provider.is_team_authorization,
|
||||
params,
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -10,12 +10,14 @@ import { ViewType } from '../../view-type-select'
|
|||
type Props = {
|
||||
payload: ToolWithProvider[]
|
||||
isShowLetterIndex: boolean
|
||||
hasSearchText: boolean
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
}
|
||||
|
||||
const ToolViewFlatView: FC<Props> = ({
|
||||
payload,
|
||||
isShowLetterIndex,
|
||||
hasSearchText,
|
||||
onSelect,
|
||||
}) => {
|
||||
return (
|
||||
|
@ -26,6 +28,7 @@ const ToolViewFlatView: FC<Props> = ({
|
|||
payload={tool}
|
||||
viewType={ViewType.flat}
|
||||
isShowLetterIndex={isShowLetterIndex}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -10,17 +10,19 @@ import type { ToolDefaultValue } from '../../types'
|
|||
type Props = {
|
||||
groupName: string
|
||||
toolList: ToolWithProvider[]
|
||||
hasSearchText: boolean
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
}
|
||||
|
||||
const Item: FC<Props> = ({
|
||||
groupName,
|
||||
toolList,
|
||||
hasSearchText,
|
||||
onSelect,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
{groupName}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -29,7 +31,8 @@ const Item: FC<Props> = ({
|
|||
key={tool.id}
|
||||
payload={tool}
|
||||
viewType={ViewType.tree}
|
||||
isShowLetterIndex
|
||||
isShowLetterIndex={false}
|
||||
hasSearchText={hasSearchText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user