chore: merge
This commit is contained in:
commit
371febf3cf
27
.github/workflows/style.yml
vendored
27
.github/workflows/style.yml
vendored
@ -82,6 +82,33 @@ jobs:
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: pnpm run lint
|
||||
|
||||
docker-compose-template:
|
||||
name: Docker Compose Template
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
files: |
|
||||
docker/generate_docker_compose
|
||||
docker/.env.example
|
||||
docker/docker-compose-template.yaml
|
||||
docker/docker-compose.yaml
|
||||
|
||||
- name: Generate Docker Compose
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
cd docker
|
||||
./generate_docker_compose
|
||||
|
||||
- name: Check for changes
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: git diff --exit-code
|
||||
|
||||
superlinter:
|
||||
name: SuperLinter
|
||||
|
@ -513,7 +513,7 @@ TENCENT_VECTOR_DB_SHARD=1
|
||||
TENCENT_VECTOR_DB_REPLICAS=2
|
||||
|
||||
# ElasticSearch configuration, only available when VECTOR_STORE is `elasticsearch`
|
||||
ELASTICSEARCH_HOST=elasticsearch
|
||||
ELASTICSEARCH_HOST=0.0.0.0
|
||||
ELASTICSEARCH_PORT=9200
|
||||
ELASTICSEARCH_USERNAME=elastic
|
||||
ELASTICSEARCH_PASSWORD=elastic
|
||||
|
@ -451,7 +451,7 @@ services:
|
||||
|
||||
milvus-standalone:
|
||||
container_name: milvus-standalone
|
||||
image: milvusdb/milvus:v2.3.1
|
||||
image: milvusdb/milvus:v2.5.0-beta
|
||||
profiles:
|
||||
- milvus
|
||||
command: [ 'milvus', 'run', 'standalone' ]
|
||||
@ -535,20 +535,28 @@ services:
|
||||
container_name: elasticsearch
|
||||
profiles:
|
||||
- elasticsearch
|
||||
- elasticsearch-ja
|
||||
restart: always
|
||||
volumes:
|
||||
- ./elasticsearch/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||
- dify_es01_data:/usr/share/elasticsearch/data
|
||||
environment:
|
||||
ELASTIC_PASSWORD: ${ELASTICSEARCH_PASSWORD:-elastic}
|
||||
VECTOR_STORE: ${VECTOR_STORE:-}
|
||||
cluster.name: dify-es-cluster
|
||||
node.name: dify-es0
|
||||
discovery.type: single-node
|
||||
xpack.license.self_generated.type: trial
|
||||
xpack.license.self_generated.type: basic
|
||||
xpack.security.enabled: 'true'
|
||||
xpack.security.enrollment.enabled: 'false'
|
||||
xpack.security.http.ssl.enabled: 'false'
|
||||
ports:
|
||||
- ${ELASTICSEARCH_PORT:-9200}:9200
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2g
|
||||
entrypoint: [ 'sh', '-c', "sh /docker-entrypoint-mount.sh" ]
|
||||
healthcheck:
|
||||
test: [ 'CMD', 'curl', '-s', 'http://localhost:9200/_cluster/health?pretty' ]
|
||||
interval: 30s
|
||||
|
@ -11,6 +11,8 @@ import ProviderConfigModal from './provider-config-modal'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const I18N_PREFIX = 'app.tracing'
|
||||
|
||||
@ -77,7 +79,6 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
className='ml-3'
|
||||
defaultValue={enabled}
|
||||
onChange={onStatusChange}
|
||||
size='l'
|
||||
disabled={providerAllNotConfigured}
|
||||
/>
|
||||
)
|
||||
@ -106,15 +107,15 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='w-[420px] p-4 rounded-2xl bg-white border-[0.5px] border-black/5 shadow-lg'>
|
||||
<div className='w-[420px] p-4 rounded-2xl bg-components-panel-bg border-[0.5px] border-components-panel-border shadow-xl'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center'>
|
||||
<TracingIcon size='md' className='mr-2' />
|
||||
<div className='leading-[120%] text-[18px] font-semibold text-gray-900'>{t(`${I18N_PREFIX}.tracing`)}</div>
|
||||
<div className='text-text-primary title-2xl-semibold'>{t(`${I18N_PREFIX}.tracing`)}</div>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Indicator color={enabled ? 'green' : 'gray'} />
|
||||
<div className='ml-1.5 text-xs font-semibold text-gray-500 uppercase'>
|
||||
<div className={cn('ml-1 system-xs-semibold-uppercase text-text-tertiary', enabled && 'text-util-colors-green-green-600')}>
|
||||
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
|
||||
</div>
|
||||
{!readOnly && (
|
||||
@ -130,19 +131,18 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
: switchContent}
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-2 leading-4 text-xs font-normal text-gray-500'>
|
||||
<div className='mt-2 system-xs-regular text-text-tertiary'>
|
||||
{t(`${I18N_PREFIX}.tracingDescription`)}
|
||||
</div>
|
||||
<div className='mt-3 h-px bg-gray-100'></div>
|
||||
<div className='mt-3'>
|
||||
<Divider className='my-3' />
|
||||
<div className='relative'>
|
||||
{(providerAllConfigured || providerAllNotConfigured)
|
||||
? (
|
||||
<>
|
||||
<div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}</div>
|
||||
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}</div>
|
||||
<div className='mt-2 space-y-2'>
|
||||
{langSmithPanel}
|
||||
{langfusePanel}
|
||||
@ -151,11 +151,11 @@ const ConfigPopup: FC<PopupProps> = ({
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className='leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.configured`)}</div>
|
||||
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.configured`)}</div>
|
||||
<div className='mt-2'>
|
||||
{langSmithConfig ? langSmithPanel : langfusePanel}
|
||||
</div>
|
||||
<div className='mt-3 leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div>
|
||||
<div className='mt-3 system-xs-medium-uppercase text-text-tertiary'>{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}</div>
|
||||
<div className='mt-2'>
|
||||
{!langSmithConfig ? langSmithPanel : langfusePanel}
|
||||
</div>
|
||||
|
@ -26,7 +26,7 @@ const Field: FC<Props> = ({
|
||||
return (
|
||||
<div className={cn(className)}>
|
||||
<div className='flex py-[7px]'>
|
||||
<div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div>
|
||||
<div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-text-primary')}>{label} </div>
|
||||
{isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>}
|
||||
</div>
|
||||
<Input
|
||||
|
@ -17,6 +17,7 @@ import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/gene
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
type Props = {
|
||||
appId: string
|
||||
@ -152,11 +153,11 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
? (
|
||||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-background-overlay'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='px-8 pt-8'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}</div>
|
||||
<div className='title-2xl-semibold text-text-primary'>{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}</div>
|
||||
</div>
|
||||
|
||||
<div className='space-y-4'>
|
||||
@ -230,16 +231,16 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
{isEdit && (
|
||||
<>
|
||||
<Button
|
||||
className='h-9 text-sm font-medium text-gray-700'
|
||||
className='h-9 text-sm font-medium text-text-secondary'
|
||||
onClick={showRemoveConfirm}
|
||||
>
|
||||
<span className='text-[#D92D20]'>{t('common.operation.remove')}</span>
|
||||
</Button>
|
||||
<div className='mx-3 w-px h-[18px] bg-gray-200'></div>
|
||||
<Divider className='mx-3 h-[18px]'/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
className='mr-2 h-9 text-sm font-medium text-gray-700'
|
||||
className='mr-2 h-9 text-sm font-medium text-text-secondary'
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
@ -256,9 +257,9 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-t-[0.5px] border-t-black/5'>
|
||||
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-gray-500' />
|
||||
<div className='border-t-[0.5px] border-divider-regular'>
|
||||
<div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-text-tertiary' />
|
||||
{t('common.modelProvider.encrypted.front')}
|
||||
<a
|
||||
className='text-primary-600 mx-1'
|
||||
|
@ -1,11 +1,13 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import {
|
||||
RiEqualizer2Line,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TracingProvider } from './type'
|
||||
import cn from '@/utils/classnames'
|
||||
import { LangfuseIconBig, LangsmithIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
const I18N_PREFIX = 'app.tracing'
|
||||
@ -61,34 +63,37 @@ const ProviderPanel: FC<Props> = ({
|
||||
}, [hasConfigured, isChosen, onChoose, readOnly])
|
||||
return (
|
||||
<div
|
||||
className={cn(isChosen ? 'border-primary-400' : 'border-transparent', !isChosen && hasConfigured && !readOnly && 'cursor-pointer', 'px-4 py-3 rounded-xl border-[1.5px] bg-gray-100')}
|
||||
className={cn(
|
||||
'px-4 py-3 rounded-xl border-[1.5px] bg-background-section-burn',
|
||||
isChosen ? 'bg-background-section border-components-option-card-option-selected-border' : 'border-transparent',
|
||||
!isChosen && hasConfigured && !readOnly && 'cursor-pointer',
|
||||
)}
|
||||
onClick={handleChosen}
|
||||
>
|
||||
<div className={'flex justify-between items-center space-x-1'}>
|
||||
<div className='flex items-center'>
|
||||
<Icon className='h-6' />
|
||||
{isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-primary-500 leading-4 text-xs font-medium text-primary-500 uppercase '>{t(`${I18N_PREFIX}.inUse`)}</div>}
|
||||
{isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-text-accent-secondary system-2xs-medium-uppercase text-text-accent-secondary'>{t(`${I18N_PREFIX}.inUse`)}</div>}
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div className={'flex justify-between items-center space-x-1'}>
|
||||
{hasConfigured && (
|
||||
<div className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1' onClick={viewBtnClick} >
|
||||
<div className='flex px-2 items-center h-6 bg-components-button-secondary-bg rounded-md border-[0.5px] border-components-button-secondary-border shadow-xs cursor-pointer text-text-secondary space-x-1' onClick={viewBtnClick} >
|
||||
<View className='w-3 h-3'/>
|
||||
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.view`)}</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1'
|
||||
className='flex px-2 items-center h-6 bg-components-button-secondary-bg rounded-md border-[0.5px] border-components-button-secondary-border shadow-xs cursor-pointer text-text-secondary space-x-1'
|
||||
onClick={handleConfigBtnClick}
|
||||
>
|
||||
<Settings04 className='w-3 h-3' />
|
||||
<RiEqualizer2Line className='w-3 h-3' />
|
||||
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.config`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className='mt-2 leading-4 text-xs font-normal text-gray-500'>
|
||||
<div className='mt-2 system-xs-regular text-text-tertiary'>
|
||||
{t(`${I18N_PREFIX}.${type}.description`)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,8 +27,8 @@ const APIKeyInfoPanel: FC = () => {
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={cn('bg-[#EFF4FF] border-[#D1E0FF]', 'mb-6 relative rounded-2xl shadow-md border p-8 ')}>
|
||||
<div className={cn('text-[24px] text-gray-800 font-semibold', isCloud ? 'flex items-center h-8 space-x-1' : 'leading-8 mb-6')}>
|
||||
<div className={cn('bg-components-panel-bg border-components-panel-border', 'mb-6 relative rounded-2xl shadow-md border p-8 ')}>
|
||||
<div className={cn('text-[24px] text-text-primary font-semibold', isCloud ? 'flex items-center h-8 space-x-1' : 'leading-8 mb-6')}>
|
||||
{isCloud && <em-emoji id={'😀'} />}
|
||||
{isCloud
|
||||
? (
|
||||
@ -42,11 +42,11 @@ const APIKeyInfoPanel: FC = () => {
|
||||
)}
|
||||
</div>
|
||||
{isCloud && (
|
||||
<div className='mt-1 text-sm text-gray-600 font-normal'>{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}</div>
|
||||
<div className='mt-1 text-sm text-text-tertiary font-normal'>{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}</div>
|
||||
)}
|
||||
<Button
|
||||
variant='primary'
|
||||
className='space-x-2'
|
||||
className='mt-2 space-x-2'
|
||||
onClick={() => setShowAccountSettingModal({ payload: 'provider' })}
|
||||
>
|
||||
<div className='text-sm font-medium'>{t('appOverview.apiKeyInfo.setAPIBtn')}</div>
|
||||
@ -65,7 +65,7 @@ const APIKeyInfoPanel: FC = () => {
|
||||
<div
|
||||
onClick={() => setIsShow(false)}
|
||||
className='absolute right-4 top-4 flex items-center justify-center w-8 h-8 cursor-pointer '>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,29 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type IProgressProps = {
|
||||
className?: string
|
||||
value: number // percent
|
||||
}
|
||||
|
||||
const Progress: FC<IProgressProps> = ({
|
||||
className,
|
||||
value,
|
||||
}) => {
|
||||
const exhausted = value === 100
|
||||
return (
|
||||
<div className={cn(className, 'relative grow h-2 flex bg-gray-200 rounded-md overflow-hidden')}>
|
||||
<div
|
||||
className={cn(s.bar, exhausted && s['bar-error'], 'absolute top-0 left-0 right-0 bottom-0')}
|
||||
style={{ width: `${value}%` }}
|
||||
/>
|
||||
{Array.from({ length: 10 }).fill(0).map((i, k) => (
|
||||
<div key={k} className={s['bar-item']} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Progress)
|
@ -1,16 +0,0 @@
|
||||
.bar {
|
||||
background: linear-gradient(90deg, rgba(41, 112, 255, 0.9) 0%, rgba(21, 94, 239, 0.9) 100%);
|
||||
}
|
||||
|
||||
.bar-error {
|
||||
background: linear-gradient(90deg, rgba(240, 68, 56, 0.72) 0%, rgba(217, 45, 32, 0.9) 100%);
|
||||
}
|
||||
|
||||
.bar-item {
|
||||
width: 10%;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.bar-item:last-of-type {
|
||||
border-right: 0;
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
'use client'
|
||||
import type { HTMLProps } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import {
|
||||
RiLoopLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
DocumentTextIcon,
|
||||
@ -16,24 +19,25 @@ import style from './style.module.css'
|
||||
import type { ConfigParams } from './settings'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import AppBasic from '@/app/components/app-sidebar/basic'
|
||||
import { asyncRunSafe, randomString } from '@/utils'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Tag from '@/app/components/base/tag'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import ShareQRCode from '@/app/components/base/qrcode'
|
||||
import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button'
|
||||
import type { AppDetailResponse } from '@/models/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import type { AppSSO } from '@/types/app'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type IAppCardProps = {
|
||||
className?: string
|
||||
appInfo: AppDetailResponse & Partial<AppSSO>
|
||||
cardType?: 'api' | 'webapp'
|
||||
customBgColor?: string
|
||||
onChangeStatus: (val: boolean) => Promise<void>
|
||||
onSaveSiteConfig?: (params: ConfigParams) => Promise<void>
|
||||
onGenerateCode?: () => Promise<void>
|
||||
@ -46,7 +50,6 @@ const EmbedIcon = ({ className = '' }: HTMLProps<HTMLDivElement>) => {
|
||||
function AppCard({
|
||||
appInfo,
|
||||
cardType = 'webapp',
|
||||
customBgColor,
|
||||
onChangeStatus,
|
||||
onSaveSiteConfig,
|
||||
onGenerateCode,
|
||||
@ -92,10 +95,6 @@ function AppCard({
|
||||
const appUrl = `${app_base_url}/${appMode}/${access_token}`
|
||||
const apiUrl = appInfo?.api_base_url
|
||||
|
||||
let bgColor = 'bg-primary-50 bg-opacity-40'
|
||||
if (cardType === 'api')
|
||||
bgColor = 'bg-purple-50'
|
||||
|
||||
const genClickFuncByName = (opName: string) => {
|
||||
switch (opName) {
|
||||
case t('appOverview.overview.appInfo.preview'):
|
||||
@ -133,11 +132,8 @@ function AppCard({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
`shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`}
|
||||
>
|
||||
<div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}>
|
||||
<div className={cn('rounded-xl border-effects-highlight border-t border-l-[0.5px] bg-background-default', className)}>
|
||||
<div className={cn('px-6 py-5')}>
|
||||
<div className="mb-2.5 flex flex-row items-start justify-between">
|
||||
<AppBasic
|
||||
iconType={cardType}
|
||||
@ -161,23 +157,20 @@ function AppCard({
|
||||
</div>
|
||||
<div className="flex flex-col justify-center py-2">
|
||||
<div className="py-1">
|
||||
<div className="pb-1 text-xs text-gray-500">
|
||||
<div className="pb-1 text-xs text-text-tertiary">
|
||||
{isApp
|
||||
? t('appOverview.overview.appInfo.accessibleAddress')
|
||||
: t('appOverview.overview.apiInfo.accessibleAddress')}
|
||||
</div>
|
||||
<div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-2 rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex">
|
||||
<div className="w-full h-9 px-2 py-0.5 bg-components-input-bg-normal rounded-lg justify-start items-center inline-flex">
|
||||
<div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0">
|
||||
<div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap">
|
||||
<div className="text-text-secondary system-xs-medium truncate">
|
||||
{isApp ? appUrl : apiUrl}
|
||||
</div>
|
||||
</div>
|
||||
<Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" />
|
||||
{isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} selectorId={randomString(8)} className={'hover:bg-gray-200'} />}
|
||||
<CopyFeedback
|
||||
content={isApp ? appUrl : apiUrl}
|
||||
className={'hover:bg-gray-200'}
|
||||
/>
|
||||
<Divider type="vertical" className="!h-3.5 shrink-0" />
|
||||
{isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} />}
|
||||
<CopyFeedback content={isApp ? appUrl : apiUrl}/>
|
||||
{/* button copy link/ button regenerate */}
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
@ -196,22 +189,16 @@ function AppCard({
|
||||
<Tooltip
|
||||
popupContent={t('appOverview.overview.appInfo.regenerate') || ''}
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 ml-0.5 cursor-pointer hover:bg-gray-200 rounded-lg"
|
||||
onClick={() => setShowConfirmDelete(true)}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
`w-full h-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''}`}
|
||||
></div>
|
||||
</div>
|
||||
<ActionButton onClick={() => setShowConfirmDelete(true)}>
|
||||
<RiLoopLeftLine className={cn('w-4 h-4', genLoading && 'animate-spin')} />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'pt-2 flex flex-row items-center flex-wrap gap-y-2'}>
|
||||
{!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />}
|
||||
{!isApp && <SecretKeyButton className='shrink-0 !h-8 mr-2' textCls='!text-text-secondary font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />}
|
||||
{OPERATIONS_MAP[cardType].map((op) => {
|
||||
const disabled
|
||||
= op.opName === t('appOverview.overview.appInfo.settings.entry')
|
||||
|
@ -1,10 +1,15 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiClipboardFill,
|
||||
RiClipboardLine,
|
||||
} from '@remixicon/react'
|
||||
import { debounce } from 'lodash-es'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import copyStyle from './style.module.css'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
type Props = {
|
||||
content: string
|
||||
@ -13,7 +18,7 @@ type Props = {
|
||||
|
||||
const prefixEmbedded = 'appOverview.overview.appInfo.embedded'
|
||||
|
||||
const CopyFeedback = ({ content, className }: Props) => {
|
||||
const CopyFeedback = ({ content }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false)
|
||||
|
||||
@ -34,19 +39,15 @@ const CopyFeedback = ({ content, className }: Props) => {
|
||||
: t(`${prefixEmbedded}.copy`)) || ''
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg ${
|
||||
className ?? ''
|
||||
}`}
|
||||
>
|
||||
<ActionButton>
|
||||
<div
|
||||
onClick={onClickCopy}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={`w-full h-full ${copyStyle.copyIcon} ${
|
||||
isCopied ? copyStyle.copied : ''
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
>
|
||||
{isCopied && <RiClipboardFill className='w-4 h-4' />}
|
||||
{!isCopied && <RiClipboardLine className='w-4 h-4' />}
|
||||
</div>
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
@ -1,19 +1,20 @@
|
||||
'use client'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiQrCodeLine,
|
||||
} from '@remixicon/react'
|
||||
import { QRCodeCanvas as QRCode } from 'qrcode.react'
|
||||
import QrcodeStyle from './style.module.css'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type Props = {
|
||||
content: string
|
||||
selectorId: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const prefixEmbedded = 'appOverview.overview.appInfo.qrcode.title'
|
||||
|
||||
const ShareQRCode = ({ content, selectorId, className }: Props) => {
|
||||
const ShareQRCode = ({ content }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShow, setIsShow] = useState<boolean>(false)
|
||||
const qrCodeRef = useRef<HTMLDivElement>(null)
|
||||
@ -53,22 +54,21 @@ const ShareQRCode = ({ content, selectorId, className }: Props) => {
|
||||
<Tooltip
|
||||
popupContent={t(`${prefixEmbedded}`) || ''}
|
||||
>
|
||||
<div
|
||||
className={`w-8 h-8 cursor-pointer rounded-lg ${className ?? ''}`}
|
||||
onClick={toggleQRCode}
|
||||
>
|
||||
<div className={`w-full h-full ${QrcodeStyle.QrcodeIcon} ${isShow ? QrcodeStyle.show : ''}`} />
|
||||
<div className='relative w-6 h-6' onClick={toggleQRCode}>
|
||||
<ActionButton>
|
||||
<RiQrCodeLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
{isShow && (
|
||||
<div
|
||||
ref={qrCodeRef}
|
||||
className={QrcodeStyle.qrcodeform}
|
||||
className='absolute top-8 -right-8 z-10 w-[232px] flex flex-col items-center bg-components-panel-bg shadow-xs rounded-lg p-4'
|
||||
onClick={handlePanelClick}
|
||||
>
|
||||
<QRCode size={160} value={content} className={QrcodeStyle.qrcodeimage}/>
|
||||
<div className={QrcodeStyle.text}>
|
||||
<div className={`text-gray-500 ${QrcodeStyle.scan}`}>{t('appOverview.overview.appInfo.qrcode.scan')}</div>
|
||||
<div className={`text-gray-500 ${QrcodeStyle.scan}`}>·</div>
|
||||
<div className={QrcodeStyle.download} onClick={downloadQR}>{t('appOverview.overview.appInfo.qrcode.download')}</div>
|
||||
<QRCode size={160} value={content} className='mb-2'/>
|
||||
<div className='flex items-center system-xs-regular'>
|
||||
<div className='text-text-tertiary'>{t('appOverview.overview.appInfo.qrcode.scan')}</div>
|
||||
<div className='text-text-tertiary'>·</div>
|
||||
<div className='text-text-accent-secondary cursor-pointer' onClick={downloadQR}>{t('appOverview.overview.appInfo.qrcode.download')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,62 +0,0 @@
|
||||
.QrcodeIcon {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/qrcode.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.QrcodeIcon:hover {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/qrcode-hover.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.QrcodeIcon.show {
|
||||
background-image: url(~@/app/components/develop/secret-key/assets/qrcode-hover.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.qrcodeimage {
|
||||
position: relative;
|
||||
object-fit: cover;
|
||||
}
|
||||
.scan {
|
||||
margin: 0;
|
||||
line-height: 1rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.download {
|
||||
position: relative;
|
||||
color: #155eef;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.text {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.qrcodeform {
|
||||
z-index: 50;
|
||||
border: 0.5px solid #eaecf0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 !important;
|
||||
margin-top: 4px !important;
|
||||
margin-left: -75px !important;
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 12px 16px -4px rgba(16, 24, 40, 0.08),
|
||||
0 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
gap: 8px;
|
||||
}
|
@ -393,7 +393,7 @@ const StepTwo = ({
|
||||
score_threshold_enabled: false,
|
||||
score_threshold: 0.5,
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rerankDefaultModel, isRerankDefaultModelValid])
|
||||
|
||||
const getCreationParams = () => {
|
||||
@ -412,7 +412,6 @@ const StepTwo = ({
|
||||
doc_form: currentDocForm,
|
||||
doc_language: docLanguage,
|
||||
process_rule: getProcessRule(),
|
||||
|
||||
retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page.
|
||||
embedding_model: embeddingModel.model, // Readonly
|
||||
embedding_model_provider: embeddingModel.provider, // Readonly
|
||||
@ -424,7 +423,6 @@ const StepTwo = ({
|
||||
if (
|
||||
!isReRankModelSelected({
|
||||
rerankModelList,
|
||||
|
||||
retrievalConfig,
|
||||
indexMethod: indexMethod as string,
|
||||
})
|
||||
|
@ -23,7 +23,7 @@ const SecretKeyButton = ({ className, appId, iconCls, textCls }: ISecretKeyButto
|
||||
<path d="M9 3.66672C9.35362 3.66672 9.69276 3.80719 9.94281 4.05724C10.1929 4.30729 10.3333 4.64643 10.3333 5.00005M13 5.00005C13.0002 5.62483 12.854 6.24097 12.5732 6.79908C12.2924 7.3572 11.8847 7.84177 11.3829 8.21397C10.8811 8.58617 10.2991 8.83564 9.68347 8.94239C9.06788 9.04915 8.43584 9.01022 7.838 8.82872L6.33333 10.3334H5V11.6667H3.66667V13.0001H1.66667C1.48986 13.0001 1.32029 12.9298 1.19526 12.8048C1.07024 12.6798 1 12.5102 1 12.3334V10.6094C1.00004 10.4326 1.0703 10.263 1.19533 10.1381L5.17133 6.16205C5.00497 5.61206 4.95904 5.03268 5.0367 4.46335C5.11435 3.89402 5.31375 3.3481 5.62133 2.86275C5.92891 2.3774 6.33744 1.96401 6.81913 1.65073C7.30082 1.33745 7.84434 1.13162 8.41272 1.04725C8.9811 0.96289 9.56098 1.00197 10.1129 1.16184C10.6648 1.32171 11.1758 1.59861 11.6111 1.97369C12.0464 2.34878 12.3958 2.81324 12.6354 3.33548C12.8751 3.85771 12.9994 4.42545 13 5.00005Z" stroke="#1F2A37" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className={`text-[13px] text-gray-800 ${textCls}`}>{t('appApi.apiKey')}</div>
|
||||
<div className={`text-[13px] text-text-secondary ${textCls}`}>{t('appApi.apiKey')}</div>
|
||||
</Button>
|
||||
<SecretKeyModal isShow={isVisible} onClose={() => setVisible(false)} appId={appId} />
|
||||
</>
|
||||
|
@ -27,7 +27,7 @@ const PluginsNav = ({
|
||||
|
||||
return (
|
||||
<Link href="/plugins" className={classNames(
|
||||
className, 'group',
|
||||
className, 'group', 'plugins-nav-button', // used for use-fold-anim-into.ts
|
||||
)}>
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { sleep } from '@/utils'
|
||||
|
||||
const animTime = 2000
|
||||
const animTime = 750
|
||||
const modalClassName = 'install-modal'
|
||||
const COUNT_DOWN_TIME = 15000 // 15s
|
||||
|
||||
@ -21,7 +21,7 @@ const useFoldAnimInto = (onClose: () => void) => {
|
||||
const foldIntoAnim = async () => {
|
||||
clearCountDown()
|
||||
const modalElem = document.querySelector(`.${modalClassName}`) as HTMLElement
|
||||
const pluginTaskTriggerElem = document.getElementById('plugin-task-trigger')
|
||||
const pluginTaskTriggerElem = document.getElementById('plugin-task-trigger') || document.querySelector('.plugins-nav-button')
|
||||
|
||||
if (!modalElem || !pluginTaskTriggerElem) {
|
||||
onClose()
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import useFoldAnimInto from './use-fold-anim-into'
|
||||
|
||||
const useHideLogic = (onClose: () => void) => {
|
||||
const {
|
||||
modalClassName,
|
||||
foldIntoAnim: doFoldAnimInto,
|
||||
clearCountDown,
|
||||
countDownFoldIntoAnim,
|
||||
} = useFoldAnimInto(onClose)
|
||||
|
||||
const [isInstalling, doSetIsInstalling] = useState(false)
|
||||
const setIsInstalling = useCallback((isInstalling: boolean) => {
|
||||
if (!isInstalling)
|
||||
clearCountDown()
|
||||
doSetIsInstalling(isInstalling)
|
||||
}, [clearCountDown])
|
||||
|
||||
const foldAnimInto = useCallback(() => {
|
||||
if (isInstalling) {
|
||||
doFoldAnimInto()
|
||||
return
|
||||
}
|
||||
onClose()
|
||||
}, [doFoldAnimInto, isInstalling, onClose])
|
||||
|
||||
const handleStartToInstall = useCallback(() => {
|
||||
setIsInstalling(true)
|
||||
countDownFoldIntoAnim()
|
||||
}, [countDownFoldIntoAnim, setIsInstalling])
|
||||
|
||||
return {
|
||||
modalClassName,
|
||||
foldAnimInto,
|
||||
setIsInstalling,
|
||||
handleStartToInstall,
|
||||
}
|
||||
}
|
||||
|
||||
export default useHideLogic
|
@ -6,6 +6,8 @@ import { InstallStep } from '../../types'
|
||||
import type { Dependency } from '../../types'
|
||||
import ReadyToInstall from './ready-to-install'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
@ -30,6 +32,13 @@ const InstallBundle: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const [step, setStep] = useState<InstallStep>(installType === InstallType.fromMarketplace ? InstallStep.readyToInstall : InstallStep.uploading)
|
||||
|
||||
const {
|
||||
modalClassName,
|
||||
foldAnimInto,
|
||||
setIsInstalling,
|
||||
handleStartToInstall,
|
||||
} = useHideLogic(onClose)
|
||||
|
||||
const getTitle = useCallback(() => {
|
||||
if (step === InstallStep.uploadFailed)
|
||||
return t(`${i18nPrefix}.uploadFailed`)
|
||||
@ -42,8 +51,8 @@ const InstallBundle: FC<Props> = ({
|
||||
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'
|
||||
onClose={foldAnimInto}
|
||||
className={cn(modalClassName, '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'>
|
||||
@ -54,6 +63,8 @@ const InstallBundle: FC<Props> = ({
|
||||
<ReadyToInstall
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
allPlugins={fromDSLPayload}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
@ -9,6 +9,8 @@ import type { Dependency, InstallStatusResponse, Plugin } from '../../types'
|
||||
type Props = {
|
||||
step: InstallStep
|
||||
onStepChange: (step: InstallStep) => void,
|
||||
onStartToInstall: () => void
|
||||
setIsInstalling: (isInstalling: boolean) => void
|
||||
allPlugins: Dependency[]
|
||||
onClose: () => void
|
||||
isFromMarketPlace?: boolean
|
||||
@ -17,6 +19,8 @@ type Props = {
|
||||
const ReadyToInstall: FC<Props> = ({
|
||||
step,
|
||||
onStepChange,
|
||||
onStartToInstall,
|
||||
setIsInstalling,
|
||||
allPlugins,
|
||||
onClose,
|
||||
isFromMarketPlace,
|
||||
@ -27,13 +31,15 @@ const ReadyToInstall: FC<Props> = ({
|
||||
setInstallStatus(installStatus)
|
||||
setInstalledPlugins(plugins)
|
||||
onStepChange(InstallStep.installed)
|
||||
}, [onStepChange])
|
||||
setIsInstalling(false)
|
||||
}, [onStepChange, setIsInstalling])
|
||||
return (
|
||||
<>
|
||||
{step === InstallStep.readyToInstall && (
|
||||
<Install
|
||||
allPlugins={allPlugins}
|
||||
onCancel={onClose}
|
||||
onStartToInstall={onStartToInstall}
|
||||
onInstalled={handleInstalled}
|
||||
isFromMarketPlace={isFromMarketPlace}
|
||||
/>
|
||||
|
@ -12,6 +12,7 @@ const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
type Props = {
|
||||
allPlugins: Dependency[]
|
||||
onStartToInstall?: () => void
|
||||
onInstalled: (plugins: Plugin[], installStatus: InstallStatusResponse[]) => void
|
||||
onCancel: () => void
|
||||
isFromMarketPlace?: boolean
|
||||
@ -20,6 +21,7 @@ type Props = {
|
||||
|
||||
const Install: FC<Props> = ({
|
||||
allPlugins,
|
||||
onStartToInstall,
|
||||
onInstalled,
|
||||
onCancel,
|
||||
isFromMarketPlace,
|
||||
@ -65,6 +67,7 @@ const Install: FC<Props> = ({
|
||||
},
|
||||
})
|
||||
const handleInstall = () => {
|
||||
onStartToInstall?.()
|
||||
installOrUpdate({
|
||||
payload: allPlugins.filter((_d, index) => selectedIndexes.includes(index)),
|
||||
plugin: selectedPlugins,
|
||||
|
@ -16,6 +16,8 @@ import Loaded from './steps/loaded'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useRefreshPluginList from '../hooks/use-refresh-plugin-list'
|
||||
import cn from '@/utils/classnames'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
|
||||
const i18nPrefix = 'plugin.installFromGitHub'
|
||||
|
||||
@ -31,6 +33,13 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
const { fetchReleases } = useGitHubReleases()
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
|
||||
const {
|
||||
modalClassName,
|
||||
foldAnimInto,
|
||||
setIsInstalling,
|
||||
handleStartToInstall,
|
||||
} = useHideLogic(onClose)
|
||||
|
||||
const [state, setState] = useState<InstallState>({
|
||||
step: updatePayload ? InstallStepFromGitHub.selectPackage : InstallStepFromGitHub.setUrl,
|
||||
repoUrl: updatePayload?.originalPackageInfo?.repo
|
||||
@ -77,13 +86,28 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
})
|
||||
return
|
||||
}
|
||||
await fetchReleases(owner, repo).then((fetchedReleases) => {
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
releases: fetchedReleases,
|
||||
step: InstallStepFromGitHub.selectPackage,
|
||||
}))
|
||||
})
|
||||
try {
|
||||
const fetchedReleases = await fetchReleases(owner, repo)
|
||||
if (fetchedReleases.length > 0) {
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
releases: fetchedReleases,
|
||||
step: InstallStepFromGitHub.selectPackage,
|
||||
}))
|
||||
}
|
||||
else {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('plugin.error.noReleasesFound'),
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('plugin.error.fetchReleasesError'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleError = (e: any, isInstall: boolean) => {
|
||||
@ -115,14 +139,16 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
const handleInstalled = useCallback(() => {
|
||||
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
|
||||
refreshPluginList(manifest)
|
||||
setIsInstalling(false)
|
||||
onSuccess()
|
||||
}, [manifest, onSuccess, refreshPluginList])
|
||||
}, [manifest, onSuccess, refreshPluginList, setIsInstalling])
|
||||
|
||||
const handleFailed = useCallback((errorMsg?: string) => {
|
||||
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installFailed }))
|
||||
setIsInstalling(false)
|
||||
if (errorMsg)
|
||||
setErrorMsg(errorMsg)
|
||||
}, [])
|
||||
}, [setIsInstalling])
|
||||
|
||||
const handleBack = () => {
|
||||
setState((prevState) => {
|
||||
@ -140,9 +166,9 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
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'
|
||||
onClose={foldAnimInto}
|
||||
className={cn(modalClassName, `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'>
|
||||
@ -195,6 +221,7 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
selectedVersion={state.selectedVersion}
|
||||
selectedPackage={state.selectedPackage}
|
||||
onBack={handleBack}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
/>
|
||||
|
@ -23,6 +23,7 @@ type LoadedProps = {
|
||||
selectedVersion: string
|
||||
selectedPackage: string
|
||||
onBack: () => void
|
||||
onStartToInstall?: () => void
|
||||
onInstalled: () => void
|
||||
onFailed: (message?: string) => void
|
||||
}
|
||||
@ -37,6 +38,7 @@ const Loaded: React.FC<LoadedProps> = ({
|
||||
selectedVersion,
|
||||
selectedPackage,
|
||||
onBack,
|
||||
onStartToInstall,
|
||||
onInstalled,
|
||||
onFailed,
|
||||
}) => {
|
||||
@ -59,11 +61,13 @@ const Loaded: React.FC<LoadedProps> = ({
|
||||
useEffect(() => {
|
||||
if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier)
|
||||
onInstalled()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hasInstalled])
|
||||
|
||||
const handleInstall = async () => {
|
||||
if (isInstalling) return
|
||||
setIsInstalling(true)
|
||||
onStartToInstall?.()
|
||||
|
||||
try {
|
||||
const { owner, repo } = parseGitHubUrl(repoUrl)
|
||||
|
@ -9,6 +9,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
import ReadyToInstallPackage from './ready-to-install'
|
||||
import ReadyToInstallBundle from '../install-bundle/ready-to-install'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
@ -31,6 +33,13 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
||||
const isBundle = file.name.endsWith('.difybndl')
|
||||
const [dependencies, setDependencies] = useState<Dependency[]>([])
|
||||
|
||||
const {
|
||||
modalClassName,
|
||||
foldAnimInto,
|
||||
setIsInstalling,
|
||||
handleStartToInstall,
|
||||
} = useHideLogic(onClose)
|
||||
|
||||
const getTitle = useCallback(() => {
|
||||
if (step === InstallStep.uploadFailed)
|
||||
return t(`${i18nPrefix}.uploadFailed`)
|
||||
@ -76,8 +85,8 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
||||
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'
|
||||
onClose={foldAnimInto}
|
||||
className={cn(modalClassName, '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'>
|
||||
@ -99,6 +108,8 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies}
|
||||
/>
|
||||
@ -106,6 +117,8 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
||||
<ReadyToInstallPackage
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
manifest={manifest}
|
||||
|
@ -10,6 +10,8 @@ import useRefreshPluginList from '../hooks/use-refresh-plugin-list'
|
||||
type Props = {
|
||||
step: InstallStep
|
||||
onStepChange: (step: InstallStep) => void,
|
||||
onStartToInstall: () => void
|
||||
setIsInstalling: (isInstalling: boolean) => void
|
||||
onClose: () => void
|
||||
uniqueIdentifier: string | null,
|
||||
manifest: PluginDeclaration | null,
|
||||
@ -20,6 +22,8 @@ type Props = {
|
||||
const ReadyToInstall: FC<Props> = ({
|
||||
step,
|
||||
onStepChange,
|
||||
onStartToInstall,
|
||||
setIsInstalling,
|
||||
onClose,
|
||||
uniqueIdentifier,
|
||||
manifest,
|
||||
@ -31,13 +35,15 @@ const ReadyToInstall: FC<Props> = ({
|
||||
const handleInstalled = useCallback(() => {
|
||||
onStepChange(InstallStep.installed)
|
||||
refreshPluginList(manifest)
|
||||
}, [manifest, onStepChange, refreshPluginList])
|
||||
setIsInstalling(false)
|
||||
}, [manifest, onStepChange, refreshPluginList, setIsInstalling])
|
||||
|
||||
const handleFailed = useCallback((errorMsg?: string) => {
|
||||
onStepChange(InstallStep.installFailed)
|
||||
setIsInstalling(false)
|
||||
if (errorMsg)
|
||||
onError(errorMsg)
|
||||
}, [onError, onStepChange])
|
||||
}, [onError, onStepChange, setIsInstalling])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -49,6 +55,7 @@ const ReadyToInstall: FC<Props> = ({
|
||||
onCancel={onClose}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
onStartToInstall={onStartToInstall}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ import Installed from '../base/installed'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useRefreshPluginList from '../hooks/use-refresh-plugin-list'
|
||||
import ReadyToInstallBundle from '../install-bundle/ready-to-install'
|
||||
import useFoldAnimInto from '../hooks/use-fold-anim-into'
|
||||
import cn from '@/utils/classnames'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
@ -39,25 +39,10 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
||||
|
||||
const {
|
||||
modalClassName,
|
||||
foldIntoAnim: doFoldAnimInto,
|
||||
clearCountDown,
|
||||
countDownFoldIntoAnim,
|
||||
} = useFoldAnimInto(onClose)
|
||||
|
||||
const [isInstalling, setIsInstalling] = useState(false)
|
||||
|
||||
const foldAnimInto = useCallback(() => {
|
||||
if (isInstalling) {
|
||||
doFoldAnimInto()
|
||||
return
|
||||
}
|
||||
onClose()
|
||||
}, [doFoldAnimInto, isInstalling, onClose])
|
||||
|
||||
const handleStartToInstall = useCallback(() => {
|
||||
setIsInstalling(true)
|
||||
countDownFoldIntoAnim()
|
||||
}, [countDownFoldIntoAnim])
|
||||
foldAnimInto,
|
||||
setIsInstalling,
|
||||
handleStartToInstall,
|
||||
} = useHideLogic(onClose)
|
||||
|
||||
const getTitle = useCallback(() => {
|
||||
if (isBundle && step === InstallStep.installed)
|
||||
@ -73,14 +58,14 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
||||
setStep(InstallStep.installed)
|
||||
refreshPluginList(manifest)
|
||||
setIsInstalling(false)
|
||||
}, [manifest, refreshPluginList])
|
||||
}, [manifest, refreshPluginList, setIsInstalling])
|
||||
|
||||
const handleFailed = useCallback((errorMsg?: string) => {
|
||||
setStep(InstallStep.installFailed)
|
||||
setIsInstalling(false)
|
||||
if (errorMsg)
|
||||
setErrorMsg(errorMsg)
|
||||
}, [])
|
||||
}, [setIsInstalling])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -100,6 +85,8 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies!}
|
||||
isFromMarketPlace
|
||||
|
@ -144,6 +144,7 @@ const AllTools = ({
|
||||
wrapElemRef={wrapElemRef}
|
||||
list={notInstalledPlugins as any} ref={pluginRef}
|
||||
searchText={searchText}
|
||||
toolContentClassName={toolContentClassName}
|
||||
tags={tags}
|
||||
/>
|
||||
</div>
|
||||
|
@ -15,6 +15,7 @@ type Props = {
|
||||
list: Plugin[]
|
||||
searchText: string
|
||||
tags: string[]
|
||||
toolContentClassName?: string
|
||||
disableMaxWidth?: boolean
|
||||
}
|
||||
|
||||
@ -23,6 +24,7 @@ const List = forwardRef<{ handleScroll: () => void }, Props>(({
|
||||
searchText,
|
||||
tags,
|
||||
list,
|
||||
toolContentClassName,
|
||||
disableMaxWidth = false,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation()
|
||||
@ -76,7 +78,7 @@ const List = forwardRef<{ handleScroll: () => void }, Props>(({
|
||||
)
|
||||
}
|
||||
|
||||
const maxWidthClassName = 'max-w-[300px]'
|
||||
const maxWidthClassName = toolContentClassName || 'max-w-[300px]'
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -17,17 +17,19 @@ export const InstallPluginButton = (props: InstallPluginButtonProps) => {
|
||||
pluginIds: [uniqueIdentifier],
|
||||
enabled: !!uniqueIdentifier,
|
||||
})
|
||||
const install = useInstallPackageFromMarketPlace({
|
||||
onSuccess() {
|
||||
manifest.refetch()
|
||||
onSuccess?.()
|
||||
},
|
||||
})
|
||||
const install = useInstallPackageFromMarketPlace()
|
||||
const isLoading = manifest.isLoading || install.isPending
|
||||
// await for refetch to get the new installed plugin, when manifest refetch, this component will unmount
|
||||
|| install.isSuccess
|
||||
const handleInstall: MouseEventHandler = (e) => {
|
||||
e.stopPropagation()
|
||||
install.mutate(uniqueIdentifier)
|
||||
install.mutate(uniqueIdentifier, {
|
||||
onSuccess: async () => {
|
||||
await manifest.refetch()
|
||||
onSuccess?.()
|
||||
},
|
||||
})
|
||||
}
|
||||
const isLoading = manifest.isLoading || install.isPending
|
||||
if (!manifest.data) return null
|
||||
if (manifest.data.plugins.some(plugin => plugin.id === uniqueIdentifier)) return null
|
||||
return <Button
|
||||
|
@ -3,17 +3,17 @@ import Indicator from '@/app/components/header/indicator'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { memo, useMemo, useRef } from 'react'
|
||||
import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools'
|
||||
import { getIconFromMarketPlace } from '@/utils/get-icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Status = 'not-installed' | 'not-authorized' | undefined
|
||||
|
||||
export type ToolIconProps = {
|
||||
status?: 'error' | 'warning'
|
||||
tooltip?: string
|
||||
providerName: string
|
||||
}
|
||||
|
||||
export const ToolIcon = memo(({ status, tooltip, providerName }: ToolIconProps) => {
|
||||
const indicator = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined
|
||||
export const ToolIcon = memo(({ providerName }: ToolIconProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const notSuccess = (['error', 'warning'] as Array<ToolIconProps['status']>).includes(status)
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
@ -22,7 +22,29 @@ export const ToolIcon = memo(({ status, tooltip, providerName }: ToolIconProps)
|
||||
return mergedTools.find((toolWithProvider) => {
|
||||
return toolWithProvider.name === providerName
|
||||
})
|
||||
}, [providerName, buildInTools, customTools, workflowTools])
|
||||
}, [buildInTools, customTools, providerName, workflowTools])
|
||||
const providerNameParts = providerName.split('/')
|
||||
const author = providerNameParts[0]
|
||||
const name = providerNameParts[1]
|
||||
const icon = useMemo(() => {
|
||||
if (currentProvider) return currentProvider.icon as string
|
||||
const iconFromMarketPlace = getIconFromMarketPlace(`${author}/${name}`)
|
||||
return iconFromMarketPlace
|
||||
}, [author, currentProvider, name])
|
||||
const status: Status = useMemo(() => {
|
||||
if (!currentProvider) return 'not-installed'
|
||||
if (currentProvider.is_team_authorization === false) return 'not-authorized'
|
||||
return undefined
|
||||
}, [currentProvider])
|
||||
const indicator = status === 'not-installed' ? 'red' : status === 'not-authorized' ? 'yellow' : undefined
|
||||
const notSuccess = (['not-installed', 'not-authorized'] as Array<Status>).includes(status)
|
||||
const { t } = useTranslation()
|
||||
const tooltip = useMemo(() => {
|
||||
if (!notSuccess) return undefined
|
||||
if (status === 'not-installed') return t('workflow.nodes.agent.toolNotInstallTooltip', { tool: name })
|
||||
if (status === 'not-authorized') return t('workflow.nodes.agent.toolNotAuthorizedTooltip', { tool: name })
|
||||
throw new Error('Unknown status')
|
||||
}, [name, notSuccess, status, t])
|
||||
return <Tooltip triggerMethod='hover' popupContent={tooltip} disabled={!notSuccess}>
|
||||
<div className={classNames(
|
||||
'size-5 border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge relative flex items-center justify-center rounded-[6px]',
|
||||
@ -31,7 +53,7 @@ export const ToolIcon = memo(({ status, tooltip, providerName }: ToolIconProps)
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={currentProvider?.icon as string}
|
||||
src={icon}
|
||||
alt='tool icon'
|
||||
className={classNames(
|
||||
'w-full h-full size-3.5 object-cover',
|
||||
|
@ -3,8 +3,8 @@ import produce from 'immer'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
|
||||
import type { ValueSelector, Var } from '../../types'
|
||||
import type { DocExtractorNodeType } from './types'
|
||||
import { InputVarType, VarType } from '../../types'
|
||||
import type { DocExtractorNodeType } from './types'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||
import {
|
||||
|
@ -172,6 +172,8 @@ const translation = {
|
||||
},
|
||||
error: {
|
||||
inValidGitHubUrl: 'Invalid GitHub URL. Please enter a valid URL in the format: https://github.com/owner/repo',
|
||||
fetchReleasesError: 'Unable to retrieve releases. Please try again later.',
|
||||
noReleasesFound: 'No releases found. Please check the GitHub repository or the input URL.',
|
||||
},
|
||||
marketplace: {
|
||||
empower: 'Empower your AI development',
|
||||
|
@ -172,6 +172,8 @@ const translation = {
|
||||
},
|
||||
error: {
|
||||
inValidGitHubUrl: '无效的 GitHub URL。请输入格式为 https://github.com/owner/repo 的有效 URL',
|
||||
fetchReleasesError: '无法获取发布版本。请稍后再试。',
|
||||
noReleasesFound: '未找到发布版本。请检查 GitHub 仓库或输入的 URL。',
|
||||
},
|
||||
marketplace: {
|
||||
empower: '助力您的 AI 开发',
|
||||
|
5
web/utils/get-icon.ts
Normal file
5
web/utils/get-icon.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
|
||||
export const getIconFromMarketPlace = (plugin_id: string) => {
|
||||
return `${MARKETPLACE_API_PREFIX}/plugins/${plugin_id}/icon`
|
||||
}
|
Loading…
Reference in New Issue
Block a user