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