mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 00:33:37 +08:00
refactor: replace CustomDialog with Dialog component across multiple files
- Updated DeleteAccount, FeedBack, BatchModal, VersionInfoModal, GetAutomaticRes, GetCodeGeneratorRes, EditModal, ConfigModal, ParamsConfig, DSLConfirmModal, and CreateFromDSLModal to use the new Dialog component from @langgenius/dify-ui/dialog instead of the previous CustomDialog and Modal components. - Enhanced dialog structure for better accessibility and styling consistency.
This commit is contained in:
parent
1b0d4637b3
commit
05481059ed
@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import CustomDialog from '@/app/components/base/dialog'
|
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
@ -47,26 +47,34 @@ export default function FeedBack(props: DeleteAccountProps) {
|
|||||||
handleSuccess()
|
handleSuccess()
|
||||||
}, [handleSuccess, props])
|
}, [handleSuccess, props])
|
||||||
return (
|
return (
|
||||||
<CustomDialog
|
<Dialog
|
||||||
show={true}
|
open
|
||||||
onClose={props.onCancel}
|
onOpenChange={(open) => {
|
||||||
title={t('account.feedbackTitle', { ns: 'common' })}
|
if (!open)
|
||||||
className="max-w-[480px]"
|
props.onCancel()
|
||||||
footer={false}
|
}}
|
||||||
>
|
>
|
||||||
<label className="mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary">{t('account.feedbackLabel', { ns: 'common' })}</label>
|
<DialogContent
|
||||||
<Textarea
|
className="max-w-[480px] overflow-hidden!"
|
||||||
rows={6}
|
backdropClassName="bg-background-overlay-backdrop backdrop-blur-[6px]"
|
||||||
value={userFeedback}
|
>
|
||||||
placeholder={t('account.feedbackPlaceholder', { ns: 'common' }) as string}
|
<DialogTitle className="pr-8 pb-3 title-2xl-semi-bold text-text-primary">
|
||||||
onChange={(e) => {
|
{t('account.feedbackTitle', { ns: 'common' })}
|
||||||
setUserFeedback(e.target.value)
|
</DialogTitle>
|
||||||
}}
|
<label className="mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary">{t('account.feedbackLabel', { ns: 'common' })}</label>
|
||||||
/>
|
<Textarea
|
||||||
<div className="mt-3 flex w-full flex-col gap-2">
|
rows={6}
|
||||||
<Button className="w-full" loading={isPending} variant="primary" onClick={handleSubmit}>{t('operation.submit', { ns: 'common' })}</Button>
|
value={userFeedback}
|
||||||
<Button className="w-full" onClick={handleSkip}>{t('operation.skip', { ns: 'common' })}</Button>
|
placeholder={t('account.feedbackPlaceholder', { ns: 'common' }) as string}
|
||||||
</div>
|
onChange={(e) => {
|
||||||
</CustomDialog>
|
setUserFeedback(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="mt-3 flex w-full flex-col gap-2">
|
||||||
|
<Button className="w-full" loading={isPending} variant="primary" onClick={handleSubmit}>{t('operation.submit', { ns: 'common' })}</Button>
|
||||||
|
<Button className="w-full" onClick={handleSkip}>{t('operation.skip', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import CustomDialog from '@/app/components/base/dialog'
|
|
||||||
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown'
|
import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown'
|
||||||
import CheckEmail from './components/check-email'
|
import CheckEmail from './components/check-email'
|
||||||
import FeedBack from './components/feed-back'
|
import FeedBack from './components/feed-back'
|
||||||
@ -30,22 +30,30 @@ export default function DeleteAccount(props: DeleteAccountProps) {
|
|||||||
return <FeedBack onCancel={props.onCancel} onConfirm={props.onConfirm} />
|
return <FeedBack onCancel={props.onCancel} onConfirm={props.onConfirm} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomDialog
|
<Dialog
|
||||||
show={true}
|
open
|
||||||
onClose={props.onCancel}
|
onOpenChange={(open) => {
|
||||||
title={t('account.delete', { ns: 'common' })}
|
if (!open)
|
||||||
className="max-w-[480px]"
|
props.onCancel()
|
||||||
footer={false}
|
}}
|
||||||
>
|
>
|
||||||
{!showVerifyEmail && <CheckEmail onCancel={props.onCancel} onConfirm={handleEmailCheckSuccess} />}
|
<DialogContent
|
||||||
{showVerifyEmail && (
|
className="max-w-[480px] overflow-hidden!"
|
||||||
<VerifyEmail
|
backdropClassName="bg-background-overlay-backdrop backdrop-blur-[6px]"
|
||||||
onCancel={props.onCancel}
|
>
|
||||||
onConfirm={() => {
|
<DialogTitle className="pr-8 pb-3 title-2xl-semi-bold text-text-primary">
|
||||||
setShowFeedbackDialog(true)
|
{t('account.delete', { ns: 'common' })}
|
||||||
}}
|
</DialogTitle>
|
||||||
/>
|
{!showVerifyEmail && <CheckEmail onCancel={props.onCancel} onConfirm={handleEmailCheckSuccess} />}
|
||||||
)}
|
{showVerifyEmail && (
|
||||||
</CustomDialog>
|
<VerifyEmail
|
||||||
|
onCancel={props.onCancel}
|
||||||
|
onConfirm={() => {
|
||||||
|
setShowFeedbackDialog(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import AnnotationFull from '@/app/components/billing/annotation-full'
|
import AnnotationFull from '@/app/components/billing/annotation-full'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { annotationBatchImport, checkAnnotationBatchImportProgress } from '@/service/annotation'
|
import { annotationBatchImport, checkAnnotationBatchImportProgress } from '@/service/annotation'
|
||||||
@ -88,37 +87,40 @@ const BatchModal: FC<IBatchModalProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={noop} className="max-w-[520px]! rounded-xl! px-8 py-6">
|
<Dialog open={isShow}>
|
||||||
<div className="relative pb-1 system-xl-medium text-text-primary">{t('batchModal.title', { ns: 'appAnnotation' })}</div>
|
<DialogContent className="w-full max-w-[520px]! overflow-hidden! rounded-xl! border-none px-8 py-6 text-left align-middle">
|
||||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onCancel}>
|
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
|
||||||
<CSVUploader
|
|
||||||
file={currentCSV}
|
|
||||||
updateFile={handleFile}
|
|
||||||
/>
|
|
||||||
<CSVDownloader />
|
|
||||||
|
|
||||||
{isAnnotationFull && (
|
<div className="relative pb-1 system-xl-medium text-text-primary">{t('batchModal.title', { ns: 'appAnnotation' })}</div>
|
||||||
<div className="mt-4">
|
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onCancel}>
|
||||||
<AnnotationFull />
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
<CSVUploader
|
||||||
|
file={currentCSV}
|
||||||
|
updateFile={handleFile}
|
||||||
|
/>
|
||||||
|
<CSVDownloader />
|
||||||
|
|
||||||
<div className="mt-[28px] flex justify-end pt-6">
|
{isAnnotationFull && (
|
||||||
<Button className="mr-2 system-sm-medium text-text-tertiary" onClick={onCancel}>
|
<div className="mt-4">
|
||||||
{t('batchModal.cancel', { ns: 'appAnnotation' })}
|
<AnnotationFull />
|
||||||
</Button>
|
</div>
|
||||||
<Button
|
)}
|
||||||
variant="primary"
|
|
||||||
onClick={handleSend}
|
<div className="mt-[28px] flex justify-end pt-6">
|
||||||
disabled={isAnnotationFull || !currentCSV}
|
<Button className="mr-2 system-sm-medium text-text-tertiary" onClick={onCancel}>
|
||||||
loading={importStatus === ProcessStatus.PROCESSING || importStatus === ProcessStatus.WAITING}
|
{t('batchModal.cancel', { ns: 'appAnnotation' })}
|
||||||
>
|
</Button>
|
||||||
{t('batchModal.run', { ns: 'appAnnotation' })}
|
<Button
|
||||||
</Button>
|
variant="primary"
|
||||||
</div>
|
onClick={handleSend}
|
||||||
</Modal>
|
disabled={isAnnotationFull || !currentCSV}
|
||||||
|
loading={importStatus === ProcessStatus.PROCESSING || importStatus === ProcessStatus.WAITING}
|
||||||
|
>
|
||||||
|
{t('batchModal.run', { ns: 'appAnnotation' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(BatchModal)
|
export default React.memo(BatchModal)
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { VersionHistory } from '@/types/workflow'
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Input from '../../base/input'
|
import Input from '../../base/input'
|
||||||
import Textarea from '../../base/textarea'
|
import Textarea from '../../base/textarea'
|
||||||
|
|
||||||
@ -66,46 +66,55 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal className="p-0" isShow={isOpen} onClose={onClose}>
|
<Dialog
|
||||||
<div className="relative w-full p-6 pr-14 pb-4">
|
open={isOpen}
|
||||||
<div className="title-2xl-semi-bold text-text-primary first-letter:capitalize">
|
onOpenChange={(open) => {
|
||||||
{versionInfo?.marked_name ? t('versionHistory.editVersionInfo', { ns: 'workflow' }) : t('versionHistory.nameThisVersion', { ns: 'workflow' })}
|
if (!open)
|
||||||
</div>
|
onClose()
|
||||||
<div className="absolute top-5 right-5 flex h-8 w-8 cursor-pointer items-center justify-center p-1.5" onClick={onClose}>
|
}}
|
||||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
>
|
||||||
</div>
|
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none p-0 text-left align-middle">
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-y-4 px-6 py-3">
|
<div className="relative w-full p-6 pr-14 pb-4">
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="title-2xl-semi-bold text-text-primary first-letter:capitalize">
|
||||||
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
|
{versionInfo?.marked_name ? t('versionHistory.editVersionInfo', { ns: 'workflow' }) : t('versionHistory.nameThisVersion', { ns: 'workflow' })}
|
||||||
{t('versionHistory.editField.title', { ns: 'workflow' })}
|
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<div className="absolute top-5 right-5 flex h-8 w-8 cursor-pointer items-center justify-center p-1.5" onClick={onClose}>
|
||||||
value={title}
|
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||||
placeholder={`${t('versionHistory.nameThisVersion', { ns: 'workflow' })}${t('panel.optional', { ns: 'workflow' })}`}
|
|
||||||
onChange={handleTitleChange}
|
|
||||||
destructive={titleError}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-y-1">
|
|
||||||
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
|
|
||||||
{t('versionHistory.editField.releaseNotes', { ns: 'workflow' })}
|
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
|
||||||
value={releaseNotes}
|
|
||||||
placeholder={`${t('versionHistory.releaseNotesPlaceholder', { ns: 'workflow' })}${t('panel.optional', { ns: 'workflow' })}`}
|
|
||||||
onChange={handleDescriptionChange}
|
|
||||||
destructive={releaseNotesError}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col gap-y-4 px-6 py-3">
|
||||||
<div className="flex justify-end p-6 pt-5">
|
<div className="flex flex-col gap-y-1">
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
|
||||||
<Button onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
{t('versionHistory.editField.title', { ns: 'workflow' })}
|
||||||
<Button variant="primary" onClick={handlePublish}>{t('common.publish', { ns: 'workflow' })}</Button>
|
</div>
|
||||||
|
<Input
|
||||||
|
value={title}
|
||||||
|
placeholder={`${t('versionHistory.nameThisVersion', { ns: 'workflow' })}${t('panel.optional', { ns: 'workflow' })}`}
|
||||||
|
onChange={handleTitleChange}
|
||||||
|
destructive={titleError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
|
||||||
|
{t('versionHistory.editField.releaseNotes', { ns: 'workflow' })}
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
value={releaseNotes}
|
||||||
|
placeholder={`${t('versionHistory.releaseNotesPlaceholder', { ns: 'workflow' })}${t('panel.optional', { ns: 'workflow' })}`}
|
||||||
|
onChange={handleDescriptionChange}
|
||||||
|
destructive={releaseNotesError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex justify-end p-6 pt-5">
|
||||||
</Modal>
|
<div className="flex items-center gap-x-3">
|
||||||
|
<Button nativeButton={false} onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button nativeButton={false} variant="primary" onClick={handlePublish}>{t('common.publish', { ns: 'workflow' })}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,11 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import EditModal from '../edit-modal'
|
import EditModal from '../edit-modal'
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||||
|
open === false ? null : <>{children}</>,
|
||||||
|
DialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('Conversation history edit modal', () => {
|
describe('Conversation history edit modal', () => {
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { ConversationHistoriesRole } from '@/models/debug'
|
import type { ConversationHistoriesRole } from '@/models/debug'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@ -25,37 +25,45 @@ const EditModal: FC<Props> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [tempData, setTempData] = useState(data)
|
const [tempData, setTempData] = useState(data)
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t('feature.conversationHistory.editModal.title', { ns: 'appDebug' })}
|
open={isShow}
|
||||||
isShow={isShow}
|
onOpenChange={(open) => {
|
||||||
onClose={onClose}
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.userPrefix', { ns: 'appDebug' })}</div>
|
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none p-6 text-left align-middle">
|
||||||
<input
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
className="mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10"
|
{t('feature.conversationHistory.editModal.title', { ns: 'appDebug' })}
|
||||||
value={tempData.user_prefix}
|
</DialogTitle>
|
||||||
onChange={e => setTempData({
|
|
||||||
...tempData,
|
|
||||||
user_prefix: e.target.value,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.assistantPrefix', { ns: 'appDebug' })}</div>
|
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.userPrefix', { ns: 'appDebug' })}</div>
|
||||||
<input
|
<input
|
||||||
className="mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10"
|
className="mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10"
|
||||||
value={tempData.assistant_prefix}
|
value={tempData.user_prefix}
|
||||||
onChange={e => setTempData({
|
onChange={e => setTempData({
|
||||||
...tempData,
|
...tempData,
|
||||||
assistant_prefix: e.target.value,
|
user_prefix: e.target.value,
|
||||||
})}
|
})}
|
||||||
placeholder={t('chat.conversationNamePlaceholder', { ns: 'common' }) || ''}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-10 flex justify-end">
|
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.assistantPrefix', { ns: 'appDebug' })}</div>
|
||||||
<Button className="mr-2 shrink-0" onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
<input
|
||||||
<Button variant="primary" className="shrink-0" onClick={() => onSave(tempData)} loading={saveLoading}>{t('operation.save', { ns: 'common' })}</Button>
|
className="mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10"
|
||||||
</div>
|
value={tempData.assistant_prefix}
|
||||||
</Modal>
|
onChange={e => setTempData({
|
||||||
|
...tempData,
|
||||||
|
assistant_prefix: e.target.value,
|
||||||
|
})}
|
||||||
|
placeholder={t('chat.conversationNamePlaceholder', { ns: 'common' }) || ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-10 flex justify-end">
|
||||||
|
<Button className="mr-2 shrink-0" onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button variant="primary" className="shrink-0" onClick={() => onSave(tempData)} loading={saveLoading}>{t('operation.save', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
import type { ChangeEvent, FC } from 'react'
|
import type { ChangeEvent, FC } from 'react'
|
||||||
import type { Item as SelectItem } from './type-select'
|
import type { Item as SelectItem } from './type-select'
|
||||||
import type { InputVar, InputVarType, MoreInfo } from '@/app/components/workflow/types'
|
import type { InputVar, InputVarType, MoreInfo } from '@/app/components/workflow/types'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import ConfigContext from '@/context/debug-configuration'
|
import ConfigContext from '@/context/debug-configuration'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
import { checkKeys, getNewVarInWorkflow, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
|
import { checkKeys, getNewVarInWorkflow, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
|
||||||
@ -141,35 +141,43 @@ const ConfigModal: FC<IConfigModalProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t(`variableConfig.${isCreate ? 'addModalTitle' : 'editModalTitle'}`, { ns: 'appDebug' })}
|
open={isShow}
|
||||||
isShow={isShow}
|
onOpenChange={(open) => {
|
||||||
onClose={onClose}
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-8" ref={modalRef} tabIndex={-1}>
|
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||||
<ConfigModalFormFields
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
checkboxDefaultSelectValue={checkboxDefaultSelectValue}
|
{t(`variableConfig.${isCreate ? 'addModalTitle' : 'editModalTitle'}`, { ns: 'appDebug' })}
|
||||||
isStringInput={isStringInput}
|
</DialogTitle>
|
||||||
jsonSchemaStr={jsonSchemaStr}
|
|
||||||
maxLength={max_length}
|
<div className="mb-8" ref={modalRef} tabIndex={-1}>
|
||||||
modelId={modelConfig.model_id}
|
<ConfigModalFormFields
|
||||||
onFilePayloadChange={payload => setTempPayload(payload as InputVar)}
|
checkboxDefaultSelectValue={checkboxDefaultSelectValue}
|
||||||
onJSONSchemaChange={handleJSONSchemaChange}
|
isStringInput={isStringInput}
|
||||||
onPayloadChange={handlePayloadChange}
|
jsonSchemaStr={jsonSchemaStr}
|
||||||
onTypeChange={handleTypeChange}
|
maxLength={max_length}
|
||||||
onVarKeyBlur={handleVarKeyBlur}
|
modelId={modelConfig.model_id}
|
||||||
onVarNameChange={handleVarNameChange}
|
onFilePayloadChange={payload => setTempPayload(payload as InputVar)}
|
||||||
options={options}
|
onJSONSchemaChange={handleJSONSchemaChange}
|
||||||
selectOptions={selectOptions}
|
onPayloadChange={handlePayloadChange}
|
||||||
tempPayload={tempPayload}
|
onTypeChange={handleTypeChange}
|
||||||
t={t}
|
onVarKeyBlur={handleVarKeyBlur}
|
||||||
|
onVarNameChange={handleVarNameChange}
|
||||||
|
options={options}
|
||||||
|
selectOptions={selectOptions}
|
||||||
|
tempPayload={tempPayload}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ModalFoot
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onCancel={onClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</DialogContent>
|
||||||
<ModalFoot
|
</Dialog>
|
||||||
onConfirm={handleConfirm}
|
|
||||||
onCancel={onClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(ConfigModal)
|
export default React.memo(ConfigModal)
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import {
|
import {
|
||||||
RiDatabase2Line,
|
RiDatabase2Line,
|
||||||
@ -32,9 +33,8 @@ import { useCallback, useEffect, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
|
||||||
|
|
||||||
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
import { generateBasicAppFirstTimeRule, generateRule } from '@/service/debug'
|
import { generateBasicAppFirstTimeRule, generateRule } from '@/service/debug'
|
||||||
@ -282,143 +282,148 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={isShow}
|
open={isShow}
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
className="min-w-[1140px] p-0!"
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex h-[680px] flex-wrap">
|
<DialogContent className="max-h-none w-[1140px] max-w-none! min-w-[1140px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
|
||||||
<div className="mb-5">
|
<div className="flex h-[680px] flex-wrap">
|
||||||
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('generate.title', { ns: 'appDebug' })}</div>
|
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
||||||
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('generate.description', { ns: 'appDebug' })}</div>
|
<div className="mb-5">
|
||||||
</div>
|
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('generate.title', { ns: 'appDebug' })}</div>
|
||||||
<div>
|
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('generate.description', { ns: 'appDebug' })}</div>
|
||||||
<ModelParameterModal
|
</div>
|
||||||
popupClassName="w-[520px]!"
|
<div>
|
||||||
isAdvancedMode={true}
|
<ModelParameterModal
|
||||||
provider={model.provider}
|
popupClassName="w-[520px]!"
|
||||||
completionParams={model.completion_params}
|
isAdvancedMode={true}
|
||||||
modelId={model.name}
|
provider={model.provider}
|
||||||
setModel={handleModelChange}
|
completionParams={model.completion_params}
|
||||||
onCompletionParamsChange={handleCompletionParamsChange}
|
modelId={model.name}
|
||||||
hideDebugWithMultipleModel
|
setModel={handleModelChange}
|
||||||
/>
|
onCompletionParamsChange={handleCompletionParamsChange}
|
||||||
</div>
|
hideDebugWithMultipleModel
|
||||||
{isBasicMode && (
|
/>
|
||||||
<div className="mt-4">
|
</div>
|
||||||
<div className="flex items-center">
|
{isBasicMode && (
|
||||||
<div className="mr-3 shrink-0 text-xs leading-[18px] font-semibold text-text-tertiary uppercase">{t('generate.tryIt', { ns: 'appDebug' })}</div>
|
<div className="mt-4">
|
||||||
<div
|
<div className="flex items-center">
|
||||||
className="h-px grow"
|
<div className="mr-3 shrink-0 text-xs leading-[18px] font-semibold text-text-tertiary uppercase">{t('generate.tryIt', { ns: 'appDebug' })}</div>
|
||||||
style={{
|
<div
|
||||||
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
|
className="h-px grow"
|
||||||
}}
|
style={{
|
||||||
>
|
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap">
|
||||||
|
{tryList.map(item => (
|
||||||
|
<TryLabel
|
||||||
|
key={item.key}
|
||||||
|
Icon={item.icon}
|
||||||
|
text={t(`generate.template.${item.key}.name`, { ns: 'appDebug' })}
|
||||||
|
onClick={handleChooseTemplate(item.key)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap">
|
)}
|
||||||
{tryList.map(item => (
|
|
||||||
<TryLabel
|
{/* inputs */}
|
||||||
key={item.key}
|
<div className="mt-4">
|
||||||
Icon={item.icon}
|
<div>
|
||||||
text={t(`generate.template.${item.key}.name`, { ns: 'appDebug' })}
|
<div className="mb-1.5 system-sm-semibold-uppercase text-text-secondary">{t('generate.instruction', { ns: 'appDebug' })}</div>
|
||||||
onClick={handleChooseTemplate(item.key)}
|
{isBasicMode
|
||||||
/>
|
? (
|
||||||
))}
|
<InstructionEditorInBasic
|
||||||
|
editorKey={editorKey}
|
||||||
|
generatorType={GeneratorType.prompt}
|
||||||
|
value={instruction}
|
||||||
|
onChange={setInstruction}
|
||||||
|
availableVars={[]}
|
||||||
|
availableNodes={[]}
|
||||||
|
isShowCurrentBlock={!!currentPrompt}
|
||||||
|
isShowLastRunBlock={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<InstructionEditorInWorkflow
|
||||||
|
editorKey={editorKey}
|
||||||
|
generatorType={GeneratorType.prompt}
|
||||||
|
value={instruction}
|
||||||
|
onChange={setInstruction}
|
||||||
|
nodeId={nodeId || ''}
|
||||||
|
isShowCurrentBlock={!!currentPrompt}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<IdeaOutput
|
||||||
|
value={ideaOutput}
|
||||||
|
onChange={setIdeaOutput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-7 flex justify-end space-x-2">
|
||||||
|
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`, { ns: 'appDebug' })}</Button>
|
||||||
|
<Button
|
||||||
|
className="flex space-x-1"
|
||||||
|
variant="primary"
|
||||||
|
onClick={onGenerate}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Generator className="h-4 w-4" />
|
||||||
|
<span className="text-xs font-semibold">{t('generate.generate', { ns: 'appDebug' })}</span>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(!isLoading && current) && (
|
||||||
|
<div className="h-full w-0 grow bg-background-default-subtle p-6 pb-0">
|
||||||
|
<Result
|
||||||
|
current={current!}
|
||||||
|
isBasicMode={isBasicMode}
|
||||||
|
nodeId={nodeId!}
|
||||||
|
currentVersionIndex={currentVersionIndex || 0}
|
||||||
|
setCurrentVersionIndex={setCurrentVersionIndex}
|
||||||
|
versions={versions || []}
|
||||||
|
onApply={showConfirmOverwrite}
|
||||||
|
generatorType={GeneratorType.prompt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isLoading && renderLoading}
|
||||||
{/* inputs */}
|
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
|
||||||
<div className="mt-4">
|
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
||||||
<div>
|
<AlertDialogContent>
|
||||||
<div className="mb-1.5 system-sm-semibold-uppercase text-text-secondary">{t('generate.instruction', { ns: 'appDebug' })}</div>
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
{isBasicMode
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
? (
|
{t('generate.overwriteTitle', { ns: 'appDebug' })}
|
||||||
<InstructionEditorInBasic
|
</AlertDialogTitle>
|
||||||
editorKey={editorKey}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
generatorType={GeneratorType.prompt}
|
{t('generate.overwriteMessage', { ns: 'appDebug' })}
|
||||||
value={instruction}
|
</AlertDialogDescription>
|
||||||
onChange={setInstruction}
|
</div>
|
||||||
availableVars={[]}
|
<AlertDialogActions>
|
||||||
availableNodes={[]}
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
isShowCurrentBlock={!!currentPrompt}
|
<AlertDialogConfirmButton
|
||||||
isShowLastRunBlock={false}
|
onClick={() => {
|
||||||
/>
|
hideShowConfirmOverwrite()
|
||||||
)
|
onFinished(current!)
|
||||||
: (
|
}}
|
||||||
<InstructionEditorInWorkflow
|
>
|
||||||
editorKey={editorKey}
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
generatorType={GeneratorType.prompt}
|
</AlertDialogConfirmButton>
|
||||||
value={instruction}
|
</AlertDialogActions>
|
||||||
onChange={setInstruction}
|
</AlertDialogContent>
|
||||||
nodeId={nodeId || ''}
|
</AlertDialog>
|
||||||
isShowCurrentBlock={!!currentPrompt}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<IdeaOutput
|
|
||||||
value={ideaOutput}
|
|
||||||
onChange={setIdeaOutput}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-7 flex justify-end space-x-2">
|
|
||||||
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`, { ns: 'appDebug' })}</Button>
|
|
||||||
<Button
|
|
||||||
className="flex space-x-1"
|
|
||||||
variant="primary"
|
|
||||||
onClick={onGenerate}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<Generator className="h-4 w-4" />
|
|
||||||
<span className="text-xs font-semibold">{t('generate.generate', { ns: 'appDebug' })}</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</DialogContent>
|
||||||
{(!isLoading && current) && (
|
</Dialog>
|
||||||
<div className="h-full w-0 grow bg-background-default-subtle p-6 pb-0">
|
|
||||||
<Result
|
|
||||||
current={current!}
|
|
||||||
isBasicMode={isBasicMode}
|
|
||||||
nodeId={nodeId!}
|
|
||||||
currentVersionIndex={currentVersionIndex || 0}
|
|
||||||
setCurrentVersionIndex={setCurrentVersionIndex}
|
|
||||||
versions={versions || []}
|
|
||||||
onApply={showConfirmOverwrite}
|
|
||||||
generatorType={GeneratorType.prompt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isLoading && renderLoading}
|
|
||||||
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
|
|
||||||
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
|
||||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
|
||||||
{t('generate.overwriteTitle', { ns: 'appDebug' })}
|
|
||||||
</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
|
||||||
{t('generate.overwriteMessage', { ns: 'appDebug' })}
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</div>
|
|
||||||
<AlertDialogActions>
|
|
||||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
|
||||||
<AlertDialogConfirmButton
|
|
||||||
onClick={() => {
|
|
||||||
hideShowConfirmOverwrite()
|
|
||||||
onFinished(current!)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('operation.confirm', { ns: 'common' })}
|
|
||||||
</AlertDialogConfirmButton>
|
|
||||||
</AlertDialogActions>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(GetAutomaticRes)
|
export default React.memo(GetAutomaticRes)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import {
|
import {
|
||||||
useBoolean,
|
useBoolean,
|
||||||
@ -23,7 +24,6 @@ import { useCallback, useEffect, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||||
@ -202,99 +202,104 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={isShow}
|
open={isShow}
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
className="min-w-[1140px] p-0!"
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative flex h-[680px] flex-wrap">
|
<DialogContent className="max-h-none w-full min-w-[1140px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
|
||||||
<div className="mb-5">
|
<div className="relative flex h-[680px] flex-wrap">
|
||||||
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('codegen.title', { ns: 'appDebug' })}</div>
|
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
||||||
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('codegen.description', { ns: 'appDebug' })}</div>
|
<div className="mb-5">
|
||||||
</div>
|
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('codegen.title', { ns: 'appDebug' })}</div>
|
||||||
<div className="mb-4">
|
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('codegen.description', { ns: 'appDebug' })}</div>
|
||||||
<ModelParameterModal
|
</div>
|
||||||
popupClassName="w-[520px]!"
|
<div className="mb-4">
|
||||||
isAdvancedMode={true}
|
<ModelParameterModal
|
||||||
provider={model.provider}
|
popupClassName="w-[520px]!"
|
||||||
completionParams={model.completion_params}
|
isAdvancedMode={true}
|
||||||
modelId={model.name}
|
provider={model.provider}
|
||||||
setModel={handleModelChange}
|
completionParams={model.completion_params}
|
||||||
onCompletionParamsChange={handleCompletionParamsChange}
|
modelId={model.name}
|
||||||
hideDebugWithMultipleModel
|
setModel={handleModelChange}
|
||||||
/>
|
onCompletionParamsChange={handleCompletionParamsChange}
|
||||||
</div>
|
hideDebugWithMultipleModel
|
||||||
<div>
|
|
||||||
<div className="text-[0px]">
|
|
||||||
<div className="mb-1.5 system-sm-semibold-uppercase text-text-secondary">{t('codegen.instruction', { ns: 'appDebug' })}</div>
|
|
||||||
<InstructionEditor
|
|
||||||
editorKey={editorKey}
|
|
||||||
value={instruction}
|
|
||||||
onChange={setInstruction}
|
|
||||||
nodeId={nodeId}
|
|
||||||
generatorType={GeneratorType.code}
|
|
||||||
isShowCurrentBlock={!!currentCode}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<IdeaOutput
|
<div>
|
||||||
value={ideaOutput}
|
<div className="text-[0px]">
|
||||||
onChange={setIdeaOutput}
|
<div className="mb-1.5 system-sm-semibold-uppercase text-text-secondary">{t('codegen.instruction', { ns: 'appDebug' })}</div>
|
||||||
/>
|
<InstructionEditor
|
||||||
|
editorKey={editorKey}
|
||||||
|
value={instruction}
|
||||||
|
onChange={setInstruction}
|
||||||
|
nodeId={nodeId}
|
||||||
|
generatorType={GeneratorType.code}
|
||||||
|
isShowCurrentBlock={!!currentCode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IdeaOutput
|
||||||
|
value={ideaOutput}
|
||||||
|
onChange={setIdeaOutput}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="mt-7 flex justify-end space-x-2">
|
<div className="mt-7 flex justify-end space-x-2">
|
||||||
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`, { ns: 'appDebug' })}</Button>
|
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`, { ns: 'appDebug' })}</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex space-x-1"
|
className="flex space-x-1"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={onGenerate}
|
onClick={onGenerate}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<Generator className="h-4 w-4" />
|
<Generator className="h-4 w-4" />
|
||||||
<span className="text-xs font-semibold">{t('codegen.generate', { ns: 'appDebug' })}</span>
|
<span className="text-xs font-semibold">{t('codegen.generate', { ns: 'appDebug' })}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isLoading && renderLoading}
|
||||||
|
{!isLoading && !current && <ResPlaceholder />}
|
||||||
|
{(!isLoading && current) && (
|
||||||
|
<div className="h-full w-0 grow bg-background-default-subtle p-6 pb-0">
|
||||||
|
<Result
|
||||||
|
current={current!}
|
||||||
|
currentVersionIndex={currentVersionIndex || 0}
|
||||||
|
setCurrentVersionIndex={setCurrentVersionIndex}
|
||||||
|
versions={versions || []}
|
||||||
|
onApply={showConfirmOverwrite}
|
||||||
|
generatorType={GeneratorType.code}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isLoading && renderLoading}
|
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
||||||
{!isLoading && !current && <ResPlaceholder />}
|
<AlertDialogContent>
|
||||||
{(!isLoading && current) && (
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
<div className="h-full w-0 grow bg-background-default-subtle p-6 pb-0">
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
<Result
|
{t('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
||||||
current={current!}
|
</AlertDialogTitle>
|
||||||
currentVersionIndex={currentVersionIndex || 0}
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
setCurrentVersionIndex={setCurrentVersionIndex}
|
{t('codegen.overwriteConfirmMessage', { ns: 'appDebug' })}
|
||||||
versions={versions || []}
|
</AlertDialogDescription>
|
||||||
onApply={showConfirmOverwrite}
|
</div>
|
||||||
generatorType={GeneratorType.code}
|
<AlertDialogActions>
|
||||||
/>
|
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
||||||
</div>
|
<AlertDialogConfirmButton
|
||||||
)}
|
onClick={() => {
|
||||||
</div>
|
hideShowConfirmOverwrite()
|
||||||
<AlertDialog open={isShowConfirmOverwrite} onOpenChange={open => !open && hideShowConfirmOverwrite()}>
|
onFinished(current!)
|
||||||
<AlertDialogContent>
|
}}
|
||||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
>
|
||||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
{t('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
</AlertDialogConfirmButton>
|
||||||
</AlertDialogTitle>
|
</AlertDialogActions>
|
||||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
</AlertDialogContent>
|
||||||
{t('codegen.overwriteConfirmMessage', { ns: 'appDebug' })}
|
</AlertDialog>
|
||||||
</AlertDialogDescription>
|
</DialogContent>
|
||||||
</div>
|
</Dialog>
|
||||||
<AlertDialogActions>
|
|
||||||
<AlertDialogCancelButton>{t('operation.cancel', { ns: 'common' })}</AlertDialogCancelButton>
|
|
||||||
<AlertDialogConfirmButton
|
|
||||||
onClick={() => {
|
|
||||||
hideShowConfirmOverwrite()
|
|
||||||
onFinished(current!)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('operation.confirm', { ns: 'common' })}
|
|
||||||
</AlertDialogConfirmButton>
|
|
||||||
</AlertDialogActions>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import type { DataSet } from '@/models/datasets'
|
|||||||
import type { DatasetConfigs } from '@/models/debug'
|
import type { DatasetConfigs } from '@/models/debug'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiEqualizer2Line } from '@remixicon/react'
|
import { RiEqualizer2Line } from '@remixicon/react'
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import {
|
import {
|
||||||
@ -123,32 +123,36 @@ const ParamsConfig = ({
|
|||||||
</Button>
|
</Button>
|
||||||
{
|
{
|
||||||
rerankSettingModalOpen && (
|
rerankSettingModalOpen && (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={rerankSettingModalOpen}
|
open={rerankSettingModalOpen}
|
||||||
onClose={() => {
|
onOpenChange={(open) => {
|
||||||
setRerankSettingModalOpen(false)
|
if (!open) {
|
||||||
|
setRerankSettingModalOpen(false)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="sm:min-w-[528px]"
|
|
||||||
>
|
>
|
||||||
<ConfigContent
|
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none text-left align-middle sm:min-w-[528px]">
|
||||||
datasetConfigs={tempDataSetConfigs}
|
|
||||||
onChange={handleSetTempDataSetConfigs}
|
|
||||||
selectedDatasets={selectedDatasets}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end">
|
<ConfigContent
|
||||||
<Button
|
datasetConfigs={tempDataSetConfigs}
|
||||||
className="mr-2 shrink-0"
|
onChange={handleSetTempDataSetConfigs}
|
||||||
onClick={() => {
|
selectedDatasets={selectedDatasets}
|
||||||
setTempDataSetConfigs(datasetConfigs)
|
/>
|
||||||
setRerankSettingModalOpen(false)
|
|
||||||
}}
|
<div className="mt-6 flex justify-end">
|
||||||
>
|
<Button
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
className="mr-2 shrink-0"
|
||||||
</Button>
|
onClick={() => {
|
||||||
<Button variant="primary" className="shrink-0" onClick={handleSave}>{t('operation.save', { ns: 'common' })}</Button>
|
setTempDataSetConfigs(datasetConfigs)
|
||||||
</div>
|
setRerankSettingModalOpen(false)
|
||||||
</Modal>
|
}}
|
||||||
|
>
|
||||||
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" className="shrink-0" onClick={handleSave}>{t('operation.save', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { Button } from '@langgenius/dify-ui/button'
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
|
|
||||||
type DSLConfirmModalProps = {
|
type DSLConfirmModalProps = {
|
||||||
versions?: {
|
versions?: {
|
||||||
@ -20,32 +27,36 @@ const DSLConfirmModal = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<AlertDialog
|
||||||
isShow
|
open
|
||||||
onClose={() => onCancel()}
|
onOpenChange={(open) => {
|
||||||
className="w-[480px]"
|
if (!open)
|
||||||
|
onCancel()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
<AlertDialogContent className="w-[480px] overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||||
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</AlertDialogTitle>
|
||||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
<AlertDialogDescription render={<div />} className="flex grow flex-col system-md-regular text-text-secondary">
|
||||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||||
<br />
|
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||||
<div>
|
<br />
|
||||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
<div>
|
||||||
<span className="system-md-medium">{versions.importedVersion}</span>
|
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||||
</div>
|
<span className="system-md-medium">{versions.importedVersion}</span>
|
||||||
<div>
|
</div>
|
||||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
<div>
|
||||||
<span className="system-md-medium">{versions.systemVersion}</span>
|
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||||
</div>
|
<span className="system-md-medium">{versions.systemVersion}</span>
|
||||||
|
</div>
|
||||||
|
</AlertDialogDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<AlertDialogActions>
|
||||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
<AlertDialogCancelButton variant="secondary">{t('newApp.Cancel', { ns: 'app' })}</AlertDialogCancelButton>
|
||||||
<Button variant="secondary" onClick={() => onCancel()}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<AlertDialogConfirmButton onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</AlertDialogConfirmButton>
|
||||||
<Button variant="primary" tone="destructive" onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
</AlertDialogActions>
|
||||||
</div>
|
</AlertDialogContent>
|
||||||
</Modal>
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,14 +3,13 @@
|
|||||||
import type { MouseEventHandler } from 'react'
|
import type { MouseEventHandler } from 'react'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { useDebounceFn, useKeyPress } from 'ahooks'
|
import { useDebounceFn, useKeyPress } from 'ahooks'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
@ -219,108 +218,112 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog open={show}>
|
||||||
className="w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl"
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0! text-left align-middle shadow-xl">
|
||||||
isShow={show}
|
|
||||||
onClose={noop}
|
<div className="flex items-center justify-between pt-6 pr-5 pb-3 pl-6 title-2xl-semi-bold text-text-primary">
|
||||||
>
|
{t('importApp', { ns: 'app' })}
|
||||||
<div className="flex items-center justify-between pt-6 pr-5 pb-3 pl-6 title-2xl-semi-bold text-text-primary">
|
<div
|
||||||
{t('importApp', { ns: 'app' })}
|
className="flex h-8 w-8 cursor-pointer items-center"
|
||||||
<div
|
onClick={() => onClose()}
|
||||||
className="flex h-8 w-8 cursor-pointer items-center"
|
>
|
||||||
onClick={() => onClose()}
|
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
||||||
>
|
</div>
|
||||||
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex h-9 items-center space-x-6 border-b border-divider-subtle px-6 system-md-semibold text-text-tertiary">
|
||||||
<div className="flex h-9 items-center space-x-6 border-b border-divider-subtle px-6 system-md-semibold text-text-tertiary">
|
{
|
||||||
{
|
tabs.map(tab => (
|
||||||
tabs.map(tab => (
|
<div
|
||||||
<div
|
key={tab.key}
|
||||||
key={tab.key}
|
className={cn(
|
||||||
className={cn(
|
'relative flex h-full cursor-pointer items-center',
|
||||||
'relative flex h-full cursor-pointer items-center',
|
currentTab === tab.key && 'text-text-primary',
|
||||||
currentTab === tab.key && 'text-text-primary',
|
)}
|
||||||
)}
|
onClick={() => setCurrentTab(tab.key)}
|
||||||
onClick={() => setCurrentTab(tab.key)}
|
>
|
||||||
>
|
{tab.label}
|
||||||
{tab.label}
|
{
|
||||||
{
|
currentTab === tab.key && (
|
||||||
currentTab === tab.key && (
|
<div className="absolute bottom-0 h-[2px] w-full bg-util-colors-blue-brand-blue-brand-600"></div>
|
||||||
<div className="absolute bottom-0 h-[2px] w-full bg-util-colors-blue-brand-blue-brand-600"></div>
|
)
|
||||||
)
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
))
|
||||||
))
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
<div className="px-6 py-4">
|
||||||
<div className="px-6 py-4">
|
{
|
||||||
{
|
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||||
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
<Uploader
|
||||||
<Uploader
|
className="mt-0"
|
||||||
className="mt-0"
|
file={currentFile}
|
||||||
file={currentFile}
|
updateFile={handleFile}
|
||||||
updateFile={handleFile}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
currentTab === CreateFromDSLModalTab.FROM_URL && (
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 system-md-semibold text-text-secondary">DSL URL</div>
|
|
||||||
<Input
|
|
||||||
placeholder={t('importFromDSLUrlPlaceholder', { ns: 'app' }) || ''}
|
|
||||||
value={dslUrlValue}
|
|
||||||
onChange={e => setDslUrlValue(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
}
|
{
|
||||||
</div>
|
currentTab === CreateFromDSLModalTab.FROM_URL && (
|
||||||
{isAppsFull && (
|
<div>
|
||||||
<div className="px-6">
|
<div className="mb-1 system-md-semibold text-text-secondary">DSL URL</div>
|
||||||
<AppsFull className="mt-0" loc="app-create-dsl" />
|
<Input
|
||||||
|
placeholder={t('importFromDSLUrlPlaceholder', { ns: 'app' }) || ''}
|
||||||
|
value={dslUrlValue}
|
||||||
|
onChange={e => setDslUrlValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
{isAppsFull && (
|
||||||
<div className="flex justify-end px-6 py-5">
|
<div className="px-6">
|
||||||
<Button className="mr-2" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<AppsFull className="mt-0" loc="app-create-dsl" />
|
||||||
<Button
|
</div>
|
||||||
disabled={buttonDisabled}
|
)}
|
||||||
variant="primary"
|
<div className="flex justify-end px-6 py-5">
|
||||||
onClick={handleCreateApp}
|
<Button className="mr-2" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||||
className="gap-1"
|
<Button
|
||||||
>
|
disabled={buttonDisabled}
|
||||||
<span>{t('newApp.Create', { ns: 'app' })}</span>
|
variant="primary"
|
||||||
<ShortcutsName keys={['ctrl', '↵']} bgColor="white" />
|
onClick={handleCreateApp}
|
||||||
</Button>
|
className="gap-1"
|
||||||
</div>
|
>
|
||||||
</Modal>
|
<span>{t('newApp.Create', { ns: 'app' })}</span>
|
||||||
<Modal
|
<ShortcutsName keys={['ctrl', '↵']} bgColor="white" />
|
||||||
isShow={showErrorModal}
|
</Button>
|
||||||
onClose={() => setShowErrorModal(false)}
|
</div>
|
||||||
className="w-[480px]"
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog
|
||||||
|
open={showErrorModal}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open)
|
||||||
|
setShowErrorModal(false)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
|
||||||
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
||||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
||||||
<br />
|
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||||
<div>
|
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
<br />
|
||||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
<div>
|
||||||
</div>
|
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||||
<div>
|
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
</div>
|
||||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
<div>
|
||||||
|
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||||
|
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
||||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
<Button variant="secondary" onClick={() => setShowErrorModal(false)}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||||
<Button variant="secondary" onClick={() => setShowErrorModal(false)}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<Button variant="primary" tone="destructive" onClick={onDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||||
<Button variant="primary" tone="destructive" onClick={onDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { AppIconType } from '@/types/app'
|
import type { AppIconType } from '@/types/app'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import AppIconPicker from '../../base/app-icon-picker'
|
import AppIconPicker from '../../base/app-icon-picker'
|
||||||
@ -71,40 +69,39 @@ const DuplicateAppModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog open={show}>
|
||||||
isShow={show}
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none px-8 text-left align-middle">
|
||||||
onClose={noop}
|
|
||||||
className={cn('relative max-w-[480px]!', 'px-8')}
|
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onHide}>
|
||||||
>
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onHide}>
|
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
|
||||||
<div className="relative mt-3 mb-9 text-xl leading-[30px] font-semibold text-text-primary">{t('duplicateTitle', { ns: 'app' })}</div>
|
|
||||||
<div className="mb-9 system-sm-regular text-text-secondary">
|
|
||||||
<div className="mb-2 system-md-medium">{t('appCustomize.subTitle', { ns: 'explore' })}</div>
|
|
||||||
<div className="flex items-center justify-between space-x-2">
|
|
||||||
<AppIcon
|
|
||||||
size="large"
|
|
||||||
onClick={() => { setShowAppIconPicker(true) }}
|
|
||||||
className="cursor-pointer"
|
|
||||||
iconType={appIcon.type}
|
|
||||||
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
|
||||||
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
|
||||||
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
value={name}
|
|
||||||
onChange={e => setName(e.target.value)}
|
|
||||||
className="h-10"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{isAppsFull && <AppsFull className="mt-4" loc="app-duplicate-create" />}
|
<div className="relative mt-3 mb-9 text-xl leading-[30px] font-semibold text-text-primary">{t('duplicateTitle', { ns: 'app' })}</div>
|
||||||
</div>
|
<div className="mb-9 system-sm-regular text-text-secondary">
|
||||||
<div className="flex flex-row-reverse">
|
<div className="mb-2 system-md-medium">{t('appCustomize.subTitle', { ns: 'explore' })}</div>
|
||||||
<Button disabled={isAppsFull} className="ml-2 w-24" variant="primary" onClick={submit}>{t('duplicate', { ns: 'app' })}</Button>
|
<div className="flex items-center justify-between space-x-2">
|
||||||
<Button className="w-24" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
<AppIcon
|
||||||
</div>
|
size="large"
|
||||||
</Modal>
|
onClick={() => { setShowAppIconPicker(true) }}
|
||||||
|
className="cursor-pointer"
|
||||||
|
iconType={appIcon.type}
|
||||||
|
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
||||||
|
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
||||||
|
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
className="h-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isAppsFull && <AppsFull className="mt-4" loc="app-duplicate-create" />}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row-reverse">
|
||||||
|
<Button disabled={isAppsFull} className="ml-2 w-24" variant="primary" onClick={submit}>{t('duplicate', { ns: 'app' })}</Button>
|
||||||
|
<Button className="w-24" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
{showAppIconPicker && (
|
{showAppIconPicker && (
|
||||||
<AppIconPicker
|
<AppIconPicker
|
||||||
onSelect={(payload) => {
|
onSelect={(payload) => {
|
||||||
|
|||||||
@ -12,9 +12,9 @@ import {
|
|||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
@ -22,7 +22,6 @@ import AppIcon from '@/app/components/base/app-icon'
|
|||||||
import Checkbox from '@/app/components/base/checkbox'
|
import Checkbox from '@/app/components/base/checkbox'
|
||||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
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 { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
@ -109,69 +108,68 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog open={show}>
|
||||||
className={cn('w-[600px] max-w-[600px] p-8')}
|
<DialogContent className={cn('w-full overflow-hidden! border-none text-left align-middle', cn('w-[600px] max-w-[600px] p-8'))}>
|
||||||
isShow={show}
|
|
||||||
onClose={noop}
|
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onClose}>
|
||||||
>
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onClose}>
|
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
|
||||||
<div className="h-12 w-12 rounded-xl border-[0.5px] border-divider-regular bg-background-default-burn p-3 shadow-xl">
|
|
||||||
<AlertTriangle className="h-6 w-6 text-[rgb(247,144,9)]" />
|
|
||||||
</div>
|
|
||||||
<div className="relative mt-3 text-xl leading-[30px] font-semibold text-text-primary">{t('switch', { ns: 'app' })}</div>
|
|
||||||
<div className="my-1 text-sm leading-5 text-text-tertiary">
|
|
||||||
<span>{t('switchTipStart', { ns: 'app' })}</span>
|
|
||||||
<span className="font-medium text-text-secondary">{t('switchTip', { ns: 'app' })}</span>
|
|
||||||
<span>{t('switchTipEnd', { ns: 'app' })}</span>
|
|
||||||
</div>
|
|
||||||
<div className="pb-4">
|
|
||||||
<div className="py-2 text-sm leading-[20px] font-medium text-text-primary">{t('switchLabel', { ns: 'app' })}</div>
|
|
||||||
<div className="flex items-center justify-between space-x-2">
|
|
||||||
<AppIcon
|
|
||||||
size="large"
|
|
||||||
onClick={() => { setShowAppIconPicker(true) }}
|
|
||||||
className="cursor-pointer"
|
|
||||||
iconType={appIcon.type}
|
|
||||||
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
|
||||||
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
|
||||||
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
value={name}
|
|
||||||
onChange={e => setName(e.target.value)}
|
|
||||||
placeholder={t('newApp.appNamePlaceholder', { ns: 'app' }) || ''}
|
|
||||||
className="h-10 grow"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{showAppIconPicker && (
|
<div className="h-12 w-12 rounded-xl border-[0.5px] border-divider-regular bg-background-default-burn p-3 shadow-xl">
|
||||||
<AppIconPicker
|
<AlertTriangle className="h-6 w-6 text-[rgb(247,144,9)]" />
|
||||||
onSelect={(payload) => {
|
|
||||||
setAppIcon(payload)
|
|
||||||
setShowAppIconPicker(false)
|
|
||||||
}}
|
|
||||||
onClose={() => {
|
|
||||||
setAppIcon(appDetail.icon_type === 'image'
|
|
||||||
? { type: 'image' as const, url: appDetail.icon_url, fileId: appDetail.icon }
|
|
||||||
: { type: 'emoji' as const, icon: appDetail.icon, background: appDetail.icon_background })
|
|
||||||
setShowAppIconPicker(false)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isAppsFull && <AppsFull loc="app-switch" />}
|
|
||||||
<div className="flex items-center justify-between pt-6">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox className="shrink-0" checked={removeOriginal} onCheck={() => setRemoveOriginal(!removeOriginal)} />
|
|
||||||
<div className="ml-2 cursor-pointer text-sm leading-5 text-text-secondary" onClick={() => setRemoveOriginal(!removeOriginal)}>{t('removeOriginal', { ns: 'app' })}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="relative mt-3 text-xl leading-[30px] font-semibold text-text-primary">{t('switch', { ns: 'app' })}</div>
|
||||||
<Button className="mr-2" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<div className="my-1 text-sm leading-5 text-text-tertiary">
|
||||||
<Button className="border-red-700" disabled={isAppsFull || !name} variant="primary" tone="destructive" onClick={goStart}>{t('switchStart', { ns: 'app' })}</Button>
|
<span>{t('switchTipStart', { ns: 'app' })}</span>
|
||||||
|
<span className="font-medium text-text-secondary">{t('switchTip', { ns: 'app' })}</span>
|
||||||
|
<span>{t('switchTipEnd', { ns: 'app' })}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="pb-4">
|
||||||
</Modal>
|
<div className="py-2 text-sm leading-[20px] font-medium text-text-primary">{t('switchLabel', { ns: 'app' })}</div>
|
||||||
|
<div className="flex items-center justify-between space-x-2">
|
||||||
|
<AppIcon
|
||||||
|
size="large"
|
||||||
|
onClick={() => { setShowAppIconPicker(true) }}
|
||||||
|
className="cursor-pointer"
|
||||||
|
iconType={appIcon.type}
|
||||||
|
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
||||||
|
background={appIcon.type === 'image' ? undefined : appIcon.background}
|
||||||
|
imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
placeholder={t('newApp.appNamePlaceholder', { ns: 'app' }) || ''}
|
||||||
|
className="h-10 grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{showAppIconPicker && (
|
||||||
|
<AppIconPicker
|
||||||
|
onSelect={(payload) => {
|
||||||
|
setAppIcon(payload)
|
||||||
|
setShowAppIconPicker(false)
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setAppIcon(appDetail.icon_type === 'image'
|
||||||
|
? { type: 'image' as const, url: appDetail.icon_url, fileId: appDetail.icon }
|
||||||
|
: { type: 'emoji' as const, icon: appDetail.icon, background: appDetail.icon_background })
|
||||||
|
setShowAppIconPicker(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isAppsFull && <AppsFull loc="app-switch" />}
|
||||||
|
<div className="flex items-center justify-between pt-6">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox className="shrink-0" checked={removeOriginal} onCheck={() => setRemoveOriginal(!removeOriginal)} />
|
||||||
|
<div className="ml-2 cursor-pointer text-sm leading-5 text-text-secondary" onClick={() => setRemoveOriginal(!removeOriginal)}>{t('removeOriginal', { ns: 'app' })}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button className="mr-2" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||||
|
<Button className="border-red-700" disabled={isAppsFull || !name} variant="primary" tone="destructive" onClick={goStart}>{t('switchStart', { ns: 'app' })}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
open={showConfirmDelete}
|
open={showConfirmDelete}
|
||||||
onOpenChange={handleConfirmDeleteOpenChange}
|
onOpenChange={handleConfirmDeleteOpenChange}
|
||||||
|
|||||||
@ -4,15 +4,14 @@ import type { OnImageInput } from './ImageInput'
|
|||||||
import type { AppIconType, ImageFile } from '@/types/app'
|
import type { AppIconType, ImageFile } from '@/types/app'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiImageCircleAiLine } from '@remixicon/react'
|
import { RiImageCircleAiLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config'
|
import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config'
|
||||||
import Divider from '../divider'
|
import Divider from '../divider'
|
||||||
import EmojiPickerInner from '../emoji-picker/Inner'
|
import EmojiPickerInner from '../emoji-picker/Inner'
|
||||||
import { useLocalFileUploader } from '../image-uploader/hooks'
|
import { useLocalFileUploader } from '../image-uploader/hooks'
|
||||||
import Modal from '../modal'
|
|
||||||
import ImageInput from './ImageInput'
|
import ImageInput from './ImageInput'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import getCroppedImg from './utils'
|
import getCroppedImg from './utils'
|
||||||
@ -45,7 +44,6 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
|||||||
onSelect,
|
onSelect,
|
||||||
onClose,
|
onClose,
|
||||||
initialEmoji,
|
initialEmoji,
|
||||||
className,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -113,57 +111,54 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog open>
|
||||||
onClose={noop}
|
<DialogContent className={cn('max-h-none w-full overflow-hidden! border-none text-left align-middle', s.container, 'h-[462px]! w-[362px]! p-0!')}>
|
||||||
isShow
|
|
||||||
closable={false}
|
{!DISABLE_UPLOAD_IMAGE_AS_ICON && (
|
||||||
wrapperClassName={className}
|
<div className="w-full p-2 pb-0">
|
||||||
className={cn(s.container, 'h-[462px]! w-[362px]! p-0!')}
|
<div className="flex items-center justify-center gap-2 rounded-xl bg-background-body p-1 text-text-primary">
|
||||||
>
|
{tabs.map(tab => (
|
||||||
{!DISABLE_UPLOAD_IMAGE_AS_ICON && (
|
<button
|
||||||
<div className="w-full p-2 pb-0">
|
type="button"
|
||||||
<div className="flex items-center justify-center gap-2 rounded-xl bg-background-body p-1 text-text-primary">
|
key={tab.key}
|
||||||
{tabs.map(tab => (
|
className={cn(
|
||||||
<button
|
'flex h-8 flex-1 shrink-0 items-center justify-center rounded-lg p-2 system-sm-medium text-text-tertiary',
|
||||||
type="button"
|
activeTab === tab.key && 'bg-components-main-nav-nav-button-bg-active text-text-accent shadow-md',
|
||||||
key={tab.key}
|
)}
|
||||||
className={cn(
|
onClick={() => setActiveTab(tab.key as AppIconType)}
|
||||||
'flex h-8 flex-1 shrink-0 items-center justify-center rounded-lg p-2 system-sm-medium text-text-tertiary',
|
>
|
||||||
activeTab === tab.key && 'bg-components-main-nav-nav-button-bg-active text-text-accent shadow-md',
|
{tab.icon}
|
||||||
)}
|
{' '}
|
||||||
onClick={() => setActiveTab(tab.key as AppIconType)}
|
|
||||||
>
|
|
||||||
{tab.icon}
|
|
||||||
{' '}
|
|
||||||
|
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'emoji' && (
|
||||||
|
<EmojiPickerInner
|
||||||
|
className={cn('flex-1 overflow-hidden pt-2')}
|
||||||
|
emoji={initialEmoji?.icon}
|
||||||
|
background={initialEmoji?.background ?? undefined}
|
||||||
|
onSelect={handleSelectEmoji}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeTab === 'image' && <ImageInput className={cn('flex-1 overflow-hidden')} onImageInput={handleImageInput} />}
|
||||||
|
|
||||||
|
<Divider className="m-0" />
|
||||||
|
<div className="flex w-full items-center justify-center gap-2 p-3">
|
||||||
|
<Button className="w-full" onClick={() => onClose?.()}>
|
||||||
|
{t('iconPicker.cancel', { ns: 'app' })}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button variant="primary" className="w-full" disabled={uploading} loading={uploading} onClick={handleSelect}>
|
||||||
|
{t('iconPicker.ok', { ns: 'app' })}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
{activeTab === 'emoji' && (
|
|
||||||
<EmojiPickerInner
|
|
||||||
className={cn('flex-1 overflow-hidden pt-2')}
|
|
||||||
emoji={initialEmoji?.icon}
|
|
||||||
background={initialEmoji?.background ?? undefined}
|
|
||||||
onSelect={handleSelectEmoji}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{activeTab === 'image' && <ImageInput className={cn('flex-1 overflow-hidden')} onImageInput={handleImageInput} />}
|
|
||||||
|
|
||||||
<Divider className="m-0" />
|
|
||||||
<div className="flex w-full items-center justify-center gap-2 p-3">
|
|
||||||
<Button className="w-full" onClick={() => onClose?.()}>
|
|
||||||
{t('iconPicker.cancel', { ns: 'app' })}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant="primary" className="w-full" disabled={uploading} loading={uploading} onClick={handleSelect}>
|
|
||||||
{t('iconPicker.ok', { ns: 'app' })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,18 +62,15 @@ vi.mock('@/next/navigation', () => ({
|
|||||||
usePathname: () => '/test',
|
usePathname: () => '/test',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Mock Modal to avoid Headless UI issues in tests
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||||
default: ({ children, isShow, title }: { children: React.ReactNode, isShow: boolean, title: React.ReactNode }) => {
|
open === false ? null : <>{children}</>,
|
||||||
if (!isShow)
|
DialogContent: ({ children }: { children: React.ReactNode }) => (
|
||||||
return null
|
<div data-testid="modal">{children}</div>
|
||||||
return (
|
),
|
||||||
<div data-testid="modal">
|
DialogTitle: ({ children }: { children: React.ReactNode }) => (
|
||||||
{!!title && <div data-testid="modal-title">{title}</div>}
|
<div data-testid="modal-title">{children}</div>
|
||||||
{children}
|
),
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('Sidebar Index', () => {
|
describe('Sidebar Index', () => {
|
||||||
|
|||||||
@ -4,25 +4,13 @@ import userEvent from '@testing-library/user-event'
|
|||||||
import * as ReactI18next from 'react-i18next'
|
import * as ReactI18next from 'react-i18next'
|
||||||
import RenameModal from '../rename-modal'
|
import RenameModal from '../rename-modal'
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({
|
Dialog: ({ children, open }: { children: ReactNode, open?: boolean }) =>
|
||||||
title,
|
open === false ? null : <>{children}</>,
|
||||||
isShow,
|
DialogContent: ({ children }: { children: ReactNode }) => (
|
||||||
children,
|
<div role="dialog">{children}</div>
|
||||||
}: {
|
),
|
||||||
title: ReactNode
|
DialogTitle: ({ children }: { children: ReactNode }) => <h2>{children}</h2>,
|
||||||
isShow: boolean
|
|
||||||
children: ReactNode
|
|
||||||
}) => {
|
|
||||||
if (!isShow)
|
|
||||||
return null
|
|
||||||
return (
|
|
||||||
<div role="dialog">
|
|
||||||
<h2>{title}</h2>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('RenameModal', () => {
|
describe('RenameModal', () => {
|
||||||
|
|||||||
@ -1,138 +0,0 @@
|
|||||||
import { act, render, screen } from '@testing-library/react'
|
|
||||||
import userEvent from '@testing-library/user-event'
|
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
|
||||||
import CustomDialog from '../index'
|
|
||||||
|
|
||||||
describe('CustomDialog Component', () => {
|
|
||||||
const setup = () => userEvent.setup()
|
|
||||||
|
|
||||||
it('should render children and title when show is true', async () => {
|
|
||||||
render(
|
|
||||||
<CustomDialog show={true} title="Modal Title">
|
|
||||||
<div data-testid="dialog-content">Main Content</div>
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
const title = await screen.findByText('Modal Title')
|
|
||||||
const content = screen.getByTestId('dialog-content')
|
|
||||||
|
|
||||||
expect(title).toBeInTheDocument()
|
|
||||||
expect(content).toBeInTheDocument()
|
|
||||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not render anything when show is false', async () => {
|
|
||||||
render(
|
|
||||||
<CustomDialog show={false} title="Hidden Title">
|
|
||||||
<div>Content</div>
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
||||||
expect(screen.queryByText('Hidden Title')).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should apply the correct semantic tag to title using titleAs', async () => {
|
|
||||||
render(
|
|
||||||
<CustomDialog show={true} title="Semantic Title" titleAs="h1">
|
|
||||||
Content
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
const title = await screen.findByRole('heading', { level: 1 })
|
|
||||||
expect(title).toHaveTextContent('Semantic Title')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render the footer only when the prop is provided', async () => {
|
|
||||||
const { rerender } = render(
|
|
||||||
<CustomDialog show={true}>Content</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
await screen.findByRole('dialog')
|
|
||||||
expect(screen.queryByText('Footer Content')).not.toBeInTheDocument()
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<CustomDialog show={true} footer={<div data-testid="footer-node">Footer Content</div>}>
|
|
||||||
Content
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(await screen.findByTestId('footer-node')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should call onClose when Escape key is pressed', async () => {
|
|
||||||
const user = setup()
|
|
||||||
const onCloseMock = vi.fn()
|
|
||||||
|
|
||||||
render(
|
|
||||||
<CustomDialog show={true} onClose={onCloseMock}>
|
|
||||||
Content
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
await screen.findByRole('dialog')
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await user.keyboard('{Escape}')
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(onCloseMock).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should call onClose when the backdrop is clicked', async () => {
|
|
||||||
const user = setup()
|
|
||||||
const onCloseMock = vi.fn()
|
|
||||||
|
|
||||||
render(
|
|
||||||
<CustomDialog show={true} onClose={onCloseMock}>
|
|
||||||
Content
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
await screen.findByRole('dialog')
|
|
||||||
|
|
||||||
const backdrop = document.querySelector('.bg-background-overlay-backdrop')
|
|
||||||
expect(backdrop).toBeInTheDocument()
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await user.click(backdrop!)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(onCloseMock).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should apply custom class names to internal elements', async () => {
|
|
||||||
render(
|
|
||||||
<CustomDialog
|
|
||||||
show={true}
|
|
||||||
title="Title"
|
|
||||||
className="custom-panel-container"
|
|
||||||
titleClassName="custom-title-style"
|
|
||||||
bodyClassName="custom-body-style"
|
|
||||||
footer="Footer"
|
|
||||||
footerClassName="custom-footer-style"
|
|
||||||
>
|
|
||||||
<div data-testid="content">Content</div>
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
await screen.findByRole('dialog')
|
|
||||||
|
|
||||||
expect(document.querySelector('.custom-panel-container')).toBeInTheDocument()
|
|
||||||
expect(document.querySelector('.custom-title-style')).toBeInTheDocument()
|
|
||||||
expect(document.querySelector('.custom-body-style')).toBeInTheDocument()
|
|
||||||
expect(document.querySelector('.custom-footer-style')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should maintain accessibility attributes (aria-modal)', async () => {
|
|
||||||
render(
|
|
||||||
<CustomDialog show={true} title="Accessibility Test">
|
|
||||||
<button>Focusable Item</button>
|
|
||||||
</CustomDialog>,
|
|
||||||
)
|
|
||||||
|
|
||||||
const dialog = await screen.findByRole('dialog')
|
|
||||||
// Headless UI should automatically set aria-modal="true"
|
|
||||||
expect(dialog).toHaveAttribute('aria-modal', 'true')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import Dialog from '.'
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: 'Base/Feedback/Dialog',
|
|
||||||
component: Dialog,
|
|
||||||
parameters: {
|
|
||||||
layout: 'fullscreen',
|
|
||||||
docs: {
|
|
||||||
description: {
|
|
||||||
component: 'Modal dialog built on Headless UI. Provides animated overlay, title slot, and optional footer region.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
className: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Additional classes applied to the panel.',
|
|
||||||
},
|
|
||||||
titleClassName: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Extra classes for the title element.',
|
|
||||||
},
|
|
||||||
bodyClassName: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Extra classes for the content area.',
|
|
||||||
},
|
|
||||||
footerClassName: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Extra classes for the footer container.',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Dialog title.',
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
control: 'boolean',
|
|
||||||
description: 'Controls visibility of the dialog.',
|
|
||||||
},
|
|
||||||
onClose: {
|
|
||||||
control: false,
|
|
||||||
description: 'Called when the dialog backdrop or close handler fires.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args: {
|
|
||||||
title: 'Manage API Keys',
|
|
||||||
show: false,
|
|
||||||
children: null,
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof Dialog>
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
const DialogDemo = (props: React.ComponentProps<typeof Dialog>) => {
|
|
||||||
const [open, setOpen] = useState(props.show)
|
|
||||||
useEffect(() => {
|
|
||||||
setOpen(props.show)
|
|
||||||
}, [props.show])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative flex h-[480px] items-center justify-center bg-gray-100">
|
|
||||||
<button
|
|
||||||
className="rounded-md bg-primary-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-primary-700"
|
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
>
|
|
||||||
Show dialog
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Dialog
|
|
||||||
{...props}
|
|
||||||
show={open}
|
|
||||||
onClose={() => {
|
|
||||||
props.onClose?.()
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="space-y-4 text-sm text-gray-600">
|
|
||||||
<p>
|
|
||||||
Centralize API key management for collaborators. You can revoke, rotate, or generate new keys directly from this dialog.
|
|
||||||
</p>
|
|
||||||
<div className="rounded-lg border border-dashed border-gray-200 bg-gray-50 p-4 text-xs text-gray-500">
|
|
||||||
This placeholder area represents a form or table that would live inside the dialog body.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: args => <DialogDemo {...args} />,
|
|
||||||
args: {
|
|
||||||
footer: (
|
|
||||||
<>
|
|
||||||
<button className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button className="rounded-md bg-primary-600 px-3 py-1.5 text-sm text-white hover:bg-primary-700">
|
|
||||||
Save changes
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WithoutFooter: Story = {
|
|
||||||
render: args => <DialogDemo {...args} />,
|
|
||||||
args: {
|
|
||||||
footer: undefined,
|
|
||||||
title: 'Read-only summary',
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
docs: {
|
|
||||||
description: {
|
|
||||||
story: 'Demonstrates the dialog when no footer actions are provided.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomStyling: Story = {
|
|
||||||
render: args => <DialogDemo {...args} />,
|
|
||||||
args: {
|
|
||||||
className: 'max-w-[560px] bg-white/95 backdrop-blur-sm',
|
|
||||||
bodyClassName: 'bg-gray-50 rounded-xl p-5',
|
|
||||||
footerClassName: 'justify-between px-4 pb-4 pt-4',
|
|
||||||
titleClassName: 'text-lg text-primary-600',
|
|
||||||
footer: (
|
|
||||||
<>
|
|
||||||
<span className="text-xs text-gray-400">Last synced 2 minutes ago</span>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50">
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button className="rounded-md bg-primary-600 px-3 py-1.5 text-sm text-white hover:bg-primary-700">
|
|
||||||
Refresh data
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
docs: {
|
|
||||||
description: {
|
|
||||||
story: 'Applies custom classes to the panel, body, title, and footer to match different surfaces.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import type { ElementType, ReactNode } from 'react'
|
|
||||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
|
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
|
||||||
import { Fragment, useCallback } from 'react'
|
|
||||||
|
|
||||||
// https://headlessui.com/react/dialog
|
|
||||||
|
|
||||||
type DialogProps = {
|
|
||||||
className?: string
|
|
||||||
titleClassName?: string
|
|
||||||
bodyClassName?: string
|
|
||||||
footerClassName?: string
|
|
||||||
titleAs?: ElementType
|
|
||||||
title?: ReactNode
|
|
||||||
children: ReactNode
|
|
||||||
footer?: ReactNode
|
|
||||||
show: boolean
|
|
||||||
onClose?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const CustomDialog = ({
|
|
||||||
className,
|
|
||||||
titleClassName,
|
|
||||||
bodyClassName,
|
|
||||||
footerClassName,
|
|
||||||
titleAs,
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
footer,
|
|
||||||
show,
|
|
||||||
onClose,
|
|
||||||
}: DialogProps) => {
|
|
||||||
const close = useCallback(() => onClose?.(), [onClose])
|
|
||||||
return (
|
|
||||||
<Transition appear show={show} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-40" onClose={close}>
|
|
||||||
<TransitionChild>
|
|
||||||
<div className={cn('fixed inset-0 bg-background-overlay-backdrop backdrop-blur-[6px]', 'duration-300 ease-in data-closed:opacity-0', 'data-enter:opacity-100', 'data-leave:opacity-0')} />
|
|
||||||
</TransitionChild>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
|
||||||
<div className="flex min-h-full items-center justify-center">
|
|
||||||
<TransitionChild>
|
|
||||||
<DialogPanel className={cn('w-full max-w-[800px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-6 shadow-xl transition-all', 'duration-100 ease-in data-closed:scale-95 data-closed:opacity-0', 'data-enter:scale-100 data-enter:opacity-100', 'data-enter:scale-95 data-leave:opacity-0', className)}>
|
|
||||||
{Boolean(title) && (
|
|
||||||
<DialogTitle
|
|
||||||
as={titleAs || 'h3'}
|
|
||||||
className={cn('pr-8 pb-3 title-2xl-semi-bold text-text-primary', titleClassName)}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</DialogTitle>
|
|
||||||
)}
|
|
||||||
<div className={cn(bodyClassName)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
{Boolean(footer) && (
|
|
||||||
<div className={cn('flex items-center justify-end gap-2 px-6 pt-3 pb-6', footerClassName)}>
|
|
||||||
{footer}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DialogPanel>
|
|
||||||
</TransitionChild>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CustomDialog
|
|
||||||
@ -2,12 +2,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { noop } from 'es-toolkit/function'
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import EmojiPickerInner from './Inner'
|
import EmojiPickerInner from './Inner'
|
||||||
|
|
||||||
type IEmojiPickerProps = {
|
type IEmojiPickerProps = {
|
||||||
@ -21,7 +20,6 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
isModal = true,
|
isModal = true,
|
||||||
onSelect,
|
onSelect,
|
||||||
onClose,
|
onClose,
|
||||||
className,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [selectedEmoji, setSelectedEmoji] = useState('')
|
const [selectedEmoji, setSelectedEmoji] = useState('')
|
||||||
@ -34,39 +32,36 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||||||
|
|
||||||
return isModal
|
return isModal
|
||||||
? (
|
? (
|
||||||
<Modal
|
<Dialog open>
|
||||||
onClose={noop}
|
<DialogContent className={cn('max-h-none w-full overflow-hidden! text-left align-middle', 'flex max-h-[552px] flex-col rounded-xl border-[0.5px] border-divider-subtle p-0 shadow-xl')}>
|
||||||
isShow
|
|
||||||
closable={false}
|
<EmojiPickerInner
|
||||||
wrapperClassName={className}
|
className="pt-3"
|
||||||
className={cn('flex max-h-[552px] flex-col rounded-xl border-[0.5px] border-divider-subtle p-0 shadow-xl')}
|
onSelect={handleSelectEmoji}
|
||||||
>
|
/>
|
||||||
<EmojiPickerInner
|
<Divider className="mt-3 mb-0" />
|
||||||
className="pt-3"
|
<div className="flex w-full items-center justify-center gap-2 p-3">
|
||||||
onSelect={handleSelectEmoji}
|
<Button
|
||||||
/>
|
className="w-full"
|
||||||
<Divider className="mt-3 mb-0" />
|
onClick={() => {
|
||||||
<div className="flex w-full items-center justify-center gap-2 p-3">
|
onClose?.()
|
||||||
<Button
|
}}
|
||||||
className="w-full"
|
>
|
||||||
onClick={() => {
|
{t('iconPicker.cancel', { ns: 'app' })}
|
||||||
onClose?.()
|
</Button>
|
||||||
}}
|
<Button
|
||||||
>
|
disabled={selectedEmoji === '' || !selectedBackground}
|
||||||
{t('iconPicker.cancel', { ns: 'app' })}
|
variant="primary"
|
||||||
</Button>
|
className="w-full"
|
||||||
<Button
|
onClick={() => {
|
||||||
disabled={selectedEmoji === '' || !selectedBackground}
|
onSelect?.(selectedEmoji, selectedBackground!)
|
||||||
variant="primary"
|
}}
|
||||||
className="w-full"
|
>
|
||||||
onClick={() => {
|
{t('iconPicker.ok', { ns: 'app' })}
|
||||||
onSelect?.(selectedEmoji, selectedBackground!)
|
</Button>
|
||||||
}}
|
</div>
|
||||||
>
|
</DialogContent>
|
||||||
{t('iconPicker.ok', { ns: 'app' })}
|
</Dialog>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { AnnotationReplyConfig } from '@/models/debug'
|
import type { AnnotationReplyConfig } from '@/models/debug'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||||
@ -58,52 +58,61 @@ const ConfigParamModal: FC<Props> = ({ isShow, onHide: doHide, onSave, isInit, a
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={onHide} className="!mt-14 !w-[640px] !max-w-none !p-6">
|
<Dialog
|
||||||
<div className="mb-2 title-2xl-semi-bold text-text-primary">
|
open={isShow}
|
||||||
{t(`initSetup.${isInit ? 'title' : 'configTitle'}`, { ns: 'appAnnotation' })}
|
onOpenChange={(open) => {
|
||||||
</div>
|
if (!open)
|
||||||
|
onHide()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="!mt-14 !w-[640px] !max-w-none overflow-hidden! border-none !p-6 text-left align-middle">
|
||||||
|
|
||||||
<div className="mt-6 space-y-3">
|
<div className="mb-2 title-2xl-semi-bold text-text-primary">
|
||||||
<Item title={t('feature.annotation.scoreThreshold.title', { ns: 'appDebug' })} tooltip={t('feature.annotation.scoreThreshold.description', { ns: 'appDebug' })}>
|
{t(`initSetup.${isInit ? 'title' : 'configTitle'}`, { ns: 'appAnnotation' })}
|
||||||
<ScoreSlider
|
</div>
|
||||||
className="mt-1"
|
|
||||||
value={(annotationConfig.score_threshold || ANNOTATION_DEFAULT.score_threshold) * 100}
|
|
||||||
onChange={(val) => {
|
|
||||||
setAnnotationConfig({
|
|
||||||
...annotationConfig,
|
|
||||||
score_threshold: val / 100,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
|
|
||||||
<Item title={t('modelProvider.embeddingModel.key', { ns: 'common' })} tooltip={t('embeddingModelSwitchTip', { ns: 'appAnnotation' })}>
|
<div className="mt-6 space-y-3">
|
||||||
<div className="pt-1">
|
<Item title={t('feature.annotation.scoreThreshold.title', { ns: 'appDebug' })} tooltip={t('feature.annotation.scoreThreshold.description', { ns: 'appDebug' })}>
|
||||||
<ModelSelector
|
<ScoreSlider
|
||||||
defaultModel={embeddingModel && {
|
className="mt-1"
|
||||||
provider: embeddingModel.providerName,
|
value={(annotationConfig.score_threshold || ANNOTATION_DEFAULT.score_threshold) * 100}
|
||||||
model: embeddingModel.modelName,
|
onChange={(val) => {
|
||||||
}}
|
setAnnotationConfig({
|
||||||
modelList={embeddingsModelList}
|
...annotationConfig,
|
||||||
onSelect={(val) => {
|
score_threshold: val / 100,
|
||||||
setEmbeddingModel({
|
|
||||||
providerName: val.provider,
|
|
||||||
modelName: val.model,
|
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Item>
|
||||||
</Item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end gap-2">
|
<Item title={t('modelProvider.embeddingModel.key', { ns: 'common' })} tooltip={t('embeddingModelSwitchTip', { ns: 'appAnnotation' })}>
|
||||||
<Button onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
<div className="pt-1">
|
||||||
<Button variant="primary" onClick={handleSave} loading={isLoading}>
|
<ModelSelector
|
||||||
<div></div>
|
defaultModel={embeddingModel && {
|
||||||
<div>{t(`initSetup.${isInit ? 'confirmBtn' : 'configConfirmBtn'}`, { ns: 'appAnnotation' })}</div>
|
provider: embeddingModel.providerName,
|
||||||
</Button>
|
model: embeddingModel.modelName,
|
||||||
</div>
|
}}
|
||||||
</Modal>
|
modelList={embeddingsModelList}
|
||||||
|
onSelect={(val) => {
|
||||||
|
setEmbeddingModel({
|
||||||
|
providerName: val.provider,
|
||||||
|
modelName: val.model,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 flex justify-end gap-2">
|
||||||
|
<Button onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button variant="primary" onClick={handleSave} loading={isLoading}>
|
||||||
|
<div></div>
|
||||||
|
<div>{t(`initSetup.${isInit ? 'confirmBtn' : 'configConfirmBtn'}`, { ns: 'appAnnotation' })}</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(ConfigParamModal)
|
export default React.memo(ConfigParamModal)
|
||||||
|
|||||||
@ -3,12 +3,11 @@ import type { CodeBasedExtensionItem } from '@/models/common'
|
|||||||
import type { ModerationConfig, ModerationContentConfig } from '@/models/debug'
|
import type { ModerationConfig, ModerationContentConfig } from '@/models/debug'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector'
|
import ApiBasedExtensionSelector from '@/app/components/header/account-setting/api-based-extension-page/selector'
|
||||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||||
import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
@ -226,165 +225,164 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog open>
|
||||||
isShow
|
<DialogContent className="mt-14! w-[600px]! max-w-none! overflow-hidden! border-none p-6! text-left align-middle">
|
||||||
onClose={noop}
|
|
||||||
className="mt-14! w-[600px]! max-w-none! p-6!"
|
<div className="flex items-center justify-between">
|
||||||
>
|
<div className="title-2xl-semi-bold text-text-primary">{t('feature.moderation.modal.title', { ns: 'appDebug' })}</div>
|
||||||
<div className="flex items-center justify-between">
|
<div
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('feature.moderation.modal.title', { ns: 'appDebug' })}</div>
|
role="button"
|
||||||
<div
|
tabIndex={0}
|
||||||
role="button"
|
className="cursor-pointer p-1"
|
||||||
tabIndex={0}
|
onClick={onCancel}
|
||||||
className="cursor-pointer p-1"
|
onKeyDown={(e) => {
|
||||||
onClick={onCancel}
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
onKeyDown={(e) => {
|
e.preventDefault()
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
onCancel()
|
||||||
e.preventDefault()
|
}
|
||||||
onCancel()
|
}}
|
||||||
}
|
>
|
||||||
}}
|
<span className="i-ri-close-line h-4 w-4 text-text-tertiary" />
|
||||||
>
|
</div>
|
||||||
<span className="i-ri-close-line h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="py-2">
|
||||||
<div className="py-2">
|
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
{t('feature.moderation.modal.provider.title', { ns: 'appDebug' })}
|
||||||
{t('feature.moderation.modal.provider.title', { ns: 'appDebug' })}
|
</div>
|
||||||
</div>
|
<div className="grid grid-cols-3 gap-2.5">
|
||||||
<div className="grid grid-cols-3 gap-2.5">
|
{
|
||||||
{
|
providers.map(provider => (
|
||||||
providers.map(provider => (
|
<div
|
||||||
<div
|
key={provider.key}
|
||||||
key={provider.key}
|
className={cn(
|
||||||
className={cn(
|
'flex h-8 cursor-default items-center rounded-md border border-components-option-card-option-border bg-components-option-card-option-bg px-2 system-sm-regular text-text-secondary',
|
||||||
'flex h-8 cursor-default items-center rounded-md border border-components-option-card-option-border bg-components-option-card-option-bg px-2 system-sm-regular text-text-secondary',
|
localeData.type !== provider.key && 'cursor-pointer hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs',
|
||||||
localeData.type !== provider.key && 'cursor-pointer hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs',
|
localeData.type === provider.key && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg system-sm-medium shadow-xs',
|
||||||
localeData.type === provider.key && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg system-sm-medium shadow-xs',
|
localeData.type === 'openai_moderation' && provider.key === 'openai_moderation' && !isOpenAIProviderConfigured && 'text-text-disabled',
|
||||||
localeData.type === 'openai_moderation' && provider.key === 'openai_moderation' && !isOpenAIProviderConfigured && 'text-text-disabled',
|
)}
|
||||||
)}
|
onClick={() => handleDataTypeChange(provider.key)}
|
||||||
onClick={() => handleDataTypeChange(provider.key)}
|
|
||||||
>
|
|
||||||
<div className={cn(
|
|
||||||
'mr-2 h-4 w-4 rounded-full border border-components-radio-border bg-components-radio-bg shadow-xs',
|
|
||||||
localeData.type === provider.key && 'border-[5px] border-components-radio-border-checked',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
|
<div className={cn(
|
||||||
|
'mr-2 h-4 w-4 rounded-full border border-components-radio-border bg-components-radio-bg shadow-xs',
|
||||||
|
localeData.type === provider.key && 'border-[5px] border-components-radio-border-checked',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{provider.name}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!isLoading && !isOpenAIProviderConfigured && localeData.type === 'openai_moderation' && (
|
||||||
|
<div className="mt-2 flex items-center rounded-lg border border-[#FEF0C7] bg-[#FFFAEB] px-3 py-2">
|
||||||
|
<span className="mr-1 i-custom-vender-line-general-info-circle h-4 w-4 text-[#F79009]" />
|
||||||
|
<div className="flex items-center text-xs font-medium text-gray-700">
|
||||||
|
{t('feature.moderation.modal.openaiNotConfig.before', { ns: 'appDebug' })}
|
||||||
|
<span
|
||||||
|
className="cursor-pointer text-primary-600"
|
||||||
|
onClick={handleOpenSettingsModal}
|
||||||
|
>
|
||||||
|
|
||||||
|
{t('settings.provider', { ns: 'common' })}
|
||||||
|
|
||||||
|
</span>
|
||||||
|
{t('feature.moderation.modal.openaiNotConfig.after', { ns: 'appDebug' })}
|
||||||
</div>
|
</div>
|
||||||
{provider.name}
|
|
||||||
</div>
|
</div>
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
!isLoading && !isOpenAIProviderConfigured && localeData.type === 'openai_moderation' && (
|
localeData.type === 'keywords' && (
|
||||||
<div className="mt-2 flex items-center rounded-lg border border-[#FEF0C7] bg-[#FFFAEB] px-3 py-2">
|
<div className="py-2">
|
||||||
<span className="mr-1 i-custom-vender-line-general-info-circle h-4 w-4 text-[#F79009]" />
|
<div className="mb-1 text-sm font-medium text-text-primary">{t('feature.moderation.modal.provider.keywords', { ns: 'appDebug' })}</div>
|
||||||
<div className="flex items-center text-xs font-medium text-gray-700">
|
<div className="mb-2 text-xs text-text-tertiary">{t('feature.moderation.modal.keywords.tip', { ns: 'appDebug' })}</div>
|
||||||
{t('feature.moderation.modal.openaiNotConfig.before', { ns: 'appDebug' })}
|
<div className="relative h-[88px] rounded-lg bg-components-input-bg-normal px-3 py-2">
|
||||||
<span
|
<textarea
|
||||||
className="cursor-pointer text-primary-600"
|
value={localeData.config?.keywords || ''}
|
||||||
onClick={handleOpenSettingsModal}
|
onChange={handleDataKeywordsChange}
|
||||||
>
|
className="block h-full w-full resize-none appearance-none bg-transparent text-sm text-text-secondary outline-hidden"
|
||||||
|
placeholder={t('feature.moderation.modal.keywords.placeholder', { ns: 'appDebug' }) || ''}
|
||||||
{t('settings.provider', { ns: 'common' })}
|
/>
|
||||||
|
<div className="absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 text-xs font-medium text-text-quaternary">
|
||||||
</span>
|
<span>{(localeData.config?.keywords || '').split('\n').filter(Boolean).length}</span>
|
||||||
{t('feature.moderation.modal.openaiNotConfig.after', { ns: 'appDebug' })}
|
/
|
||||||
|
<span className="text-text-tertiary">
|
||||||
|
100
|
||||||
|
{t('feature.moderation.modal.keywords.line', { ns: 'appDebug' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
{
|
||||||
{
|
localeData.type === 'api' && (
|
||||||
localeData.type === 'keywords' && (
|
<div className="py-2">
|
||||||
<div className="py-2">
|
<div className="flex h-9 items-center justify-between">
|
||||||
<div className="mb-1 text-sm font-medium text-text-primary">{t('feature.moderation.modal.provider.keywords', { ns: 'appDebug' })}</div>
|
<div className="text-sm font-medium text-text-primary">{t('apiBasedExtension.selector.title', { ns: 'common' })}</div>
|
||||||
<div className="mb-2 text-xs text-text-tertiary">{t('feature.moderation.modal.keywords.tip', { ns: 'appDebug' })}</div>
|
<a
|
||||||
<div className="relative h-[88px] rounded-lg bg-components-input-bg-normal px-3 py-2">
|
href={docLink('/use-dify/workspace/api-extension/api-extension')}
|
||||||
<textarea
|
target="_blank"
|
||||||
value={localeData.config?.keywords || ''}
|
rel="noopener noreferrer"
|
||||||
onChange={handleDataKeywordsChange}
|
className="group flex items-center text-xs text-text-tertiary hover:text-primary-600"
|
||||||
className="block h-full w-full resize-none appearance-none bg-transparent text-sm text-text-secondary outline-hidden"
|
>
|
||||||
placeholder={t('feature.moderation.modal.keywords.placeholder', { ns: 'appDebug' }) || ''}
|
<span className="mr-1 i-custom-vender-line-education-book-open-01 h-3 w-3 text-text-tertiary group-hover:text-primary-600" />
|
||||||
/>
|
{t('apiBasedExtension.link', { ns: 'common' })}
|
||||||
<div className="absolute right-2 bottom-2 flex h-5 items-center rounded-md bg-background-section px-1 text-xs font-medium text-text-quaternary">
|
</a>
|
||||||
<span>{(localeData.config?.keywords || '').split('\n').filter(Boolean).length}</span>
|
|
||||||
/
|
|
||||||
<span className="text-text-tertiary">
|
|
||||||
100
|
|
||||||
{t('feature.moderation.modal.keywords.line', { ns: 'appDebug' })}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ApiBasedExtensionSelector
|
||||||
|
value={localeData.config?.api_based_extension_id || ''}
|
||||||
|
onChange={handleDataApiBasedChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
}
|
{
|
||||||
{
|
systemTypes.findIndex(t => t === localeData.type) < 0
|
||||||
localeData.type === 'api' && (
|
&& currentProvider?.form_schema
|
||||||
<div className="py-2">
|
&& (
|
||||||
<div className="flex h-9 items-center justify-between">
|
<FormGeneration
|
||||||
<div className="text-sm font-medium text-text-primary">{t('apiBasedExtension.selector.title', { ns: 'common' })}</div>
|
forms={currentProvider?.form_schema}
|
||||||
<a
|
value={localeData.config}
|
||||||
href={docLink('/use-dify/workspace/api-extension/api-extension')}
|
onChange={handleDataExtraChange}
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="group flex items-center text-xs text-text-tertiary hover:text-primary-600"
|
|
||||||
>
|
|
||||||
<span className="mr-1 i-custom-vender-line-education-book-open-01 h-3 w-3 text-text-tertiary group-hover:text-primary-600" />
|
|
||||||
{t('apiBasedExtension.link', { ns: 'common' })}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<ApiBasedExtensionSelector
|
|
||||||
value={localeData.config?.api_based_extension_id || ''}
|
|
||||||
onChange={handleDataApiBasedChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
}
|
<Divider bgStyle="gradient" className="my-3 h-px" />
|
||||||
{
|
<ModerationContent
|
||||||
systemTypes.findIndex(t => t === localeData.type) < 0
|
title={t('feature.moderation.modal.content.input', { ns: 'appDebug' }) || ''}
|
||||||
&& currentProvider?.form_schema
|
config={localeData.config?.inputs_config || { enabled: false, preset_response: '' }}
|
||||||
&& (
|
onConfigChange={config => handleDataContentChange('inputs_config', config)}
|
||||||
<FormGeneration
|
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
||||||
forms={currentProvider?.form_schema}
|
showPreset={localeData.type !== 'api'}
|
||||||
value={localeData.config}
|
/>
|
||||||
onChange={handleDataExtraChange}
|
<ModerationContent
|
||||||
/>
|
title={t('feature.moderation.modal.content.output', { ns: 'appDebug' }) || ''}
|
||||||
)
|
config={localeData.config?.outputs_config || { enabled: false, preset_response: '' }}
|
||||||
}
|
onConfigChange={config => handleDataContentChange('outputs_config', config)}
|
||||||
<Divider bgStyle="gradient" className="my-3 h-px" />
|
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
||||||
<ModerationContent
|
showPreset={localeData.type !== 'api'}
|
||||||
title={t('feature.moderation.modal.content.input', { ns: 'appDebug' }) || ''}
|
/>
|
||||||
config={localeData.config?.inputs_config || { enabled: false, preset_response: '' }}
|
<div className="mt-1 mb-8 text-xs font-medium text-text-tertiary">{t('feature.moderation.modal.content.condition', { ns: 'appDebug' })}</div>
|
||||||
onConfigChange={config => handleDataContentChange('inputs_config', config)}
|
<div className="flex items-center justify-end">
|
||||||
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
<Button
|
||||||
showPreset={localeData.type !== 'api'}
|
onClick={onCancel}
|
||||||
/>
|
className="mr-2"
|
||||||
<ModerationContent
|
>
|
||||||
title={t('feature.moderation.modal.content.output', { ns: 'appDebug' }) || ''}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
config={localeData.config?.outputs_config || { enabled: false, preset_response: '' }}
|
</Button>
|
||||||
onConfigChange={config => handleDataContentChange('outputs_config', config)}
|
<Button
|
||||||
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
variant="primary"
|
||||||
showPreset={localeData.type !== 'api'}
|
onClick={handleSave}
|
||||||
/>
|
disabled={localeData.type === 'openai_moderation' && !isOpenAIProviderConfigured}
|
||||||
<div className="mt-1 mb-8 text-xs font-medium text-text-tertiary">{t('feature.moderation.modal.content.condition', { ns: 'appDebug' })}</div>
|
>
|
||||||
<div className="flex items-center justify-end">
|
{t('operation.save', { ns: 'common' })}
|
||||||
<Button
|
</Button>
|
||||||
onClick={onCancel}
|
</div>
|
||||||
className="mr-2"
|
</DialogContent>
|
||||||
>
|
</Dialog>
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={localeData.type === 'openai_moderation' && !isOpenAIProviderConfigured}
|
|
||||||
>
|
|
||||||
{t('operation.save', { ns: 'common' })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,172 +0,0 @@
|
|||||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
|
||||||
import Modal from '..'
|
|
||||||
|
|
||||||
describe('Modal', () => {
|
|
||||||
describe('Render', () => {
|
|
||||||
it('should not render content when isShow is false', () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={false} title="Test Modal">
|
|
||||||
<div>Modal Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(screen.queryByText('Test Modal')).not.toBeInTheDocument()
|
|
||||||
expect(screen.queryByText('Modal Content')).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render content when isShow is true', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal">
|
|
||||||
<div>Modal Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(screen.getByText('Test Modal')).toBeInTheDocument()
|
|
||||||
expect(screen.getByText('Modal Content')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render description when provided', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal" description="Test Description">
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(screen.getByText('Test Description')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Interaction', () => {
|
|
||||||
it('should call onClose when close button is clicked', async () => {
|
|
||||||
const handleClose = vi.fn()
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal" closable={true} onClose={handleClose}>
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const closeButton = screen.getByTestId('modal-close-button')
|
|
||||||
expect(closeButton).toBeInTheDocument()
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.click(closeButton!)
|
|
||||||
})
|
|
||||||
expect(handleClose).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should prevent propagation when clicking the scrollable container', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal">
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const wrapper = document.querySelector('.overflow-y-auto')
|
|
||||||
expect(wrapper).toBeInTheDocument()
|
|
||||||
|
|
||||||
const event = new MouseEvent('click', { bubbles: true, cancelable: true })
|
|
||||||
const stopPropagationSpy = vi.spyOn(event, 'stopPropagation')
|
|
||||||
const preventDefaultSpy = vi.spyOn(event, 'preventDefault')
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
wrapper!.dispatchEvent(event)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(stopPropagationSpy).toHaveBeenCalled()
|
|
||||||
expect(preventDefaultSpy).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle clickOutsideNotClose prop', async () => {
|
|
||||||
const handleClose = vi.fn()
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal" clickOutsideNotClose={true} onClose={handleClose}>
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.keyDown(screen.getByRole('dialog'), { key: 'Escape', code: 'Escape' })
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(handleClose).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Props', () => {
|
|
||||||
it('should apply custom className to the panel', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal" className="custom-panel-class">
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const panel = screen.getByText('Test Modal').parentElement
|
|
||||||
expect(panel).toHaveClass('custom-panel-class')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should apply wrapperClassName and containerClassName', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal
|
|
||||||
isShow={true}
|
|
||||||
title="Test Modal"
|
|
||||||
wrapperClassName="custom-wrapper"
|
|
||||||
containerClassName="custom-container"
|
|
||||||
>
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const dialog = document.querySelector('.custom-wrapper')
|
|
||||||
expect(dialog).toBeInTheDocument()
|
|
||||||
const container = document.querySelector('.custom-container')
|
|
||||||
expect(container).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should apply overlayOpacity background when overlayOpacity is true', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<Modal isShow={true} title="Test Modal" overlayOpacity={true}>
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const overlay = document.querySelector('.bg-workflow-canvas-canvas-overlay')
|
|
||||||
expect(overlay).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should toggle overflow-visible class based on overflowVisible prop', async () => {
|
|
||||||
const { rerender } = render(
|
|
||||||
<Modal isShow={true} title="Test Modal" overflowVisible={true}>
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
|
|
||||||
let panel = screen.getByText('Test Modal').parentElement
|
|
||||||
expect(panel).toHaveClass('overflow-visible')
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
rerender(
|
|
||||||
<Modal isShow={true} title="Test Modal" overflowVisible={false}>
|
|
||||||
<div>Content</div>
|
|
||||||
</Modal>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
panel = screen.getByText('Test Modal').parentElement
|
|
||||||
expect(panel).toHaveClass('overflow-hidden')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import Modal from '.'
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: 'Base/Feedback/Modal',
|
|
||||||
component: Modal,
|
|
||||||
parameters: {
|
|
||||||
layout: 'fullscreen',
|
|
||||||
docs: {
|
|
||||||
description: {
|
|
||||||
component: 'Lightweight modal wrapper with optional header/description and close icon.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
className: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Extra classes applied to the modal panel.',
|
|
||||||
},
|
|
||||||
wrapperClassName: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Additional wrapper classes for the dialog.',
|
|
||||||
},
|
|
||||||
isShow: {
|
|
||||||
control: 'boolean',
|
|
||||||
description: 'Controls whether the modal is visible.',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Heading displayed at the top of the modal.',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
control: 'text',
|
|
||||||
description: 'Secondary text beneath the title.',
|
|
||||||
},
|
|
||||||
closable: {
|
|
||||||
control: 'boolean',
|
|
||||||
description: 'Whether the close icon should be shown.',
|
|
||||||
},
|
|
||||||
overflowVisible: {
|
|
||||||
control: 'boolean',
|
|
||||||
description: 'Allows content to overflow the modal panel.',
|
|
||||||
},
|
|
||||||
onClose: {
|
|
||||||
control: false,
|
|
||||||
description: 'Callback invoked when the modal requests to close.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args: {
|
|
||||||
isShow: false,
|
|
||||||
title: 'Create new API key',
|
|
||||||
description: 'Generate a scoped key for this workspace. You can revoke it at any time.',
|
|
||||||
closable: true,
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof Modal>
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
const ModalDemo = (props: React.ComponentProps<typeof Modal>) => {
|
|
||||||
const [open, setOpen] = useState(props.isShow)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOpen(props.isShow)
|
|
||||||
}, [props.isShow])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative flex h-[480px] items-center justify-center bg-gray-100">
|
|
||||||
<button
|
|
||||||
className="rounded-md bg-primary-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-primary-700"
|
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
>
|
|
||||||
Show modal
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
{...props}
|
|
||||||
isShow={open}
|
|
||||||
onClose={() => {
|
|
||||||
props.onClose?.()
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="mt-6 space-y-4 text-sm text-gray-600">
|
|
||||||
<p>
|
|
||||||
Provide a descriptive name for this key so collaborators know its purpose. Restrict usage with scopes to limit access.
|
|
||||||
</p>
|
|
||||||
<div className="rounded-lg border border-dashed border-gray-200 bg-gray-50 p-4 text-xs text-gray-500">
|
|
||||||
Form fields and validation messaging would appear here. This placeholder keeps the story lightweight.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8 flex justify-end gap-3">
|
|
||||||
<button
|
|
||||||
className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50"
|
|
||||||
onClick={() => setOpen(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button className="rounded-md bg-primary-600 px-3 py-1.5 text-sm text-white hover:bg-primary-700">
|
|
||||||
Create key
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: args => <ModalDemo {...args} />,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OverflowVisible: Story = {
|
|
||||||
render: args => <ModalDemo {...args} />,
|
|
||||||
args: {
|
|
||||||
overflowVisible: true,
|
|
||||||
description: 'Demonstrates the modal configured to let the body content overflow.',
|
|
||||||
className: 'max-w-[540px]',
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
docs: {
|
|
||||||
description: {
|
|
||||||
story: 'Shows the modal with `overflowVisible` enabled for content that needs to escape the panel bounds.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
/**
|
|
||||||
* @deprecated Use `@langgenius/dify-ui/dialog` instead.
|
|
||||||
* This component will be removed after migration is complete.
|
|
||||||
* See: https://github.com/langgenius/dify/issues/32767
|
|
||||||
*/
|
|
||||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
|
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { Fragment } from 'react'
|
|
||||||
// https://headlessui.com/react/dialog
|
|
||||||
|
|
||||||
type IModal = {
|
|
||||||
className?: string
|
|
||||||
wrapperClassName?: string
|
|
||||||
containerClassName?: string
|
|
||||||
isShow: boolean
|
|
||||||
onClose?: () => void
|
|
||||||
title?: React.ReactNode
|
|
||||||
description?: React.ReactNode
|
|
||||||
children?: React.ReactNode
|
|
||||||
closable?: boolean
|
|
||||||
overflowVisible?: boolean
|
|
||||||
overlayOpacity?: boolean // For semi-transparent overlay instead of default
|
|
||||||
clickOutsideNotClose?: boolean // Prevent closing when clicking outside modal
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Modal({
|
|
||||||
className,
|
|
||||||
wrapperClassName,
|
|
||||||
containerClassName,
|
|
||||||
isShow,
|
|
||||||
onClose = noop,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
children,
|
|
||||||
closable = false,
|
|
||||||
overflowVisible = false,
|
|
||||||
overlayOpacity = false,
|
|
||||||
clickOutsideNotClose = false,
|
|
||||||
}: IModal) {
|
|
||||||
return (
|
|
||||||
<Transition appear show={isShow} as={Fragment}>
|
|
||||||
<Dialog as="div" className={cn('relative z-60', wrapperClassName)} onClose={clickOutsideNotClose ? noop : onClose}>
|
|
||||||
<TransitionChild>
|
|
||||||
<div className={cn('fixed inset-0', overlayOpacity ? 'bg-workflow-canvas-canvas-overlay' : 'bg-background-overlay', 'duration-300 ease-in data-closed:opacity-0', 'data-enter:opacity-100', 'data-leave:opacity-0')} />
|
|
||||||
</TransitionChild>
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 overflow-y-auto"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={cn('flex min-h-full items-center justify-center p-4 text-center', containerClassName)}>
|
|
||||||
<TransitionChild>
|
|
||||||
<DialogPanel className={cn('relative w-full max-w-[480px] rounded-2xl bg-components-panel-bg p-6 text-left align-middle shadow-xl transition-all', overflowVisible ? 'overflow-visible' : 'overflow-hidden', 'duration-100 ease-in data-closed:scale-95 data-closed:opacity-0', 'data-enter:scale-100 data-enter:opacity-100', 'data-enter:scale-95 data-leave:opacity-0', className)}>
|
|
||||||
{!!title && (
|
|
||||||
<DialogTitle
|
|
||||||
as="h3"
|
|
||||||
className="title-2xl-semi-bold text-text-primary"
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</DialogTitle>
|
|
||||||
)}
|
|
||||||
{!!description && (
|
|
||||||
<div className="mt-2 body-md-regular text-text-secondary">
|
|
||||||
{description}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{closable
|
|
||||||
&& (
|
|
||||||
<div className="absolute top-6 right-6 z-10 flex h-5 w-5 items-center justify-center rounded-2xl hover:cursor-pointer hover:bg-state-base-hover">
|
|
||||||
<span
|
|
||||||
className="i-ri-close-line h-4 w-4 text-text-tertiary"
|
|
||||||
onClick={
|
|
||||||
(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data-testid="modal-close-button"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</DialogPanel>
|
|
||||||
</TransitionChild>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -4,6 +4,7 @@ import type { WorkflowNodesMap } from '../workflow-variable-block/node'
|
|||||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||||
import type { Type } from '@/app/components/workflow/nodes/llm/types'
|
import type { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
import ActionButton from '../../../action-button'
|
import ActionButton from '../../../action-button'
|
||||||
import { VariableX } from '../../../icons/src/vender/workflow'
|
import { VariableX } from '../../../icons/src/vender/workflow'
|
||||||
import Modal from '../../../modal'
|
|
||||||
import InputField from './input-field'
|
import InputField from './input-field'
|
||||||
import VariableBlock from './variable-block'
|
import VariableBlock from './variable-block'
|
||||||
|
|
||||||
@ -157,20 +157,24 @@ const HITLInputComponentUI: FC<HITLInputComponentUIProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isShowEditModal && (
|
{isShowEditModal && (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow
|
open
|
||||||
onClose={hideEditModal}
|
onOpenChange={(open) => {
|
||||||
wrapperClassName="z-999"
|
if (!open)
|
||||||
className="max-w-[372px] p-0!"
|
hideEditModal()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<InputField
|
<DialogContent className="w-full max-w-[372px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
nodeId={nodeId}
|
|
||||||
isEdit
|
<InputField
|
||||||
payload={formInput}
|
nodeId={nodeId}
|
||||||
onChange={handleChange}
|
isEdit
|
||||||
onCancel={hideEditModal}
|
payload={formInput}
|
||||||
/>
|
onChange={handleChange}
|
||||||
</Modal>
|
onCancel={hideEditModal}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { LexicalCommand } from 'lexical'
|
import type { ShortcutPopupInsertHandler } from '../index'
|
||||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||||
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
|
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
|
||||||
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
|
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
|
||||||
@ -50,7 +50,7 @@ const CONTENT_EDITABLE_ID = 'ce'
|
|||||||
type MinimalEditorProps = {
|
type MinimalEditorProps = {
|
||||||
withContainer?: boolean
|
withContainer?: boolean
|
||||||
hotkey?: string | string[] | string[][] | ((e: KeyboardEvent) => boolean)
|
hotkey?: string | string[] | string[][] | ((e: KeyboardEvent) => boolean)
|
||||||
children?: React.ReactNode | ((close: () => void, onInsert: (command: LexicalCommand<unknown>, params: unknown[]) => void) => React.ReactNode)
|
children?: React.ReactNode | ((close: () => void, onInsert: ShortcutPopupInsertHandler) => React.ReactNode)
|
||||||
className?: string
|
className?: string
|
||||||
onOpen?: () => void
|
onOpen?: () => void
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
@ -316,7 +316,7 @@ describe('ShortcutsPopupPlugin', () => {
|
|||||||
|
|
||||||
it('renders children as render function and provides close/onInsert', async () => {
|
it('renders children as render function and provides close/onInsert', async () => {
|
||||||
const TEST_COMMAND = createCommand<unknown>('TEST_COMMAND')
|
const TEST_COMMAND = createCommand<unknown>('TEST_COMMAND')
|
||||||
const childrenFn = vi.fn((close: () => void, onInsert: (cmd: LexicalCommand<unknown>, params: unknown[]) => void) => (
|
const childrenFn = vi.fn((close: () => void, onInsert: ShortcutPopupInsertHandler) => (
|
||||||
<div>
|
<div>
|
||||||
<button type="button" data-testid="close-btn" onClick={close}>Close</button>
|
<button type="button" data-testid="close-btn" onClick={close}>Close</button>
|
||||||
<button type="button" data-testid="insert-btn" onClick={() => onInsert(TEST_COMMAND, ['param1'])}>Insert</button>
|
<button type="button" data-testid="insert-btn" onClick={() => onInsert(TEST_COMMAND, ['param1'])}>Insert</button>
|
||||||
@ -346,7 +346,7 @@ describe('ShortcutsPopupPlugin', () => {
|
|||||||
const TEST_COMMAND = createCommand<unknown>('TEST_INSERT_COMMAND')
|
const TEST_COMMAND = createCommand<unknown>('TEST_INSERT_COMMAND')
|
||||||
render(
|
render(
|
||||||
<MinimalEditor>
|
<MinimalEditor>
|
||||||
{(close: () => void, onInsert: (cmd: LexicalCommand<unknown>, params: unknown[]) => void) => (
|
{(close: () => void, onInsert: ShortcutPopupInsertHandler) => (
|
||||||
<div>
|
<div>
|
||||||
<button type="button" data-testid="insert-btn" onClick={() => onInsert(TEST_COMMAND, ['value'])}>Insert</button>
|
<button type="button" data-testid="insert-btn" onClick={() => onInsert(TEST_COMMAND, ['value'])}>Insert</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
export const SHORTCUTS_EMPTY_CONTENT = 'shortcuts_empty_content'
|
export const SHORTCUTS_EMPTY_CONTENT = 'shortcuts_empty_content'
|
||||||
|
export type ShortcutPopupInsertHandler = <Payload>(command: LexicalCommand<Payload>, params: Payload) => void
|
||||||
|
|
||||||
// Hotkey can be:
|
// Hotkey can be:
|
||||||
// - string: 'mod+/'
|
// - string: 'mod+/'
|
||||||
@ -33,7 +34,7 @@ export type Hotkey = string | string[] | string[][] | ((e: KeyboardEvent) => boo
|
|||||||
|
|
||||||
type ShortcutPopupPluginProps = {
|
type ShortcutPopupPluginProps = {
|
||||||
hotkey?: Hotkey
|
hotkey?: Hotkey
|
||||||
children?: React.ReactNode | ((close: () => void, onInsert: (command: LexicalCommand<unknown>, params: any[]) => void) => React.ReactNode)
|
children?: React.ReactNode | ((close: () => void, onInsert: ShortcutPopupInsertHandler) => React.ReactNode)
|
||||||
className?: string
|
className?: string
|
||||||
container?: Element | null
|
container?: Element | null
|
||||||
onOpen?: () => void
|
onOpen?: () => void
|
||||||
@ -158,8 +159,9 @@ export default function ShortcutsPopupPlugin({
|
|||||||
apply({ availableWidth, availableHeight, elements }) {
|
apply({ availableWidth, availableHeight, elements }) {
|
||||||
Object.assign(elements.floating.style, {
|
Object.assign(elements.floating.style, {
|
||||||
maxWidth: `${Math.min(400, availableWidth)}px`,
|
maxWidth: `${Math.min(400, availableWidth)}px`,
|
||||||
maxHeight: `${Math.min(300, availableHeight)}px`,
|
maxHeight: `${Math.max(0, availableHeight)}px`,
|
||||||
overflow: 'auto',
|
overflowX: 'hidden',
|
||||||
|
overflowY: 'auto',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
padding: 8,
|
padding: 8,
|
||||||
@ -236,7 +238,7 @@ export default function ShortcutsPopupPlugin({
|
|||||||
|
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
onOpen?.()
|
onOpen?.()
|
||||||
}, [onOpen])
|
}, [editor, onOpen, refs])
|
||||||
|
|
||||||
const closePortal = useCallback(() => {
|
const closePortal = useCallback(() => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -280,7 +282,7 @@ export default function ShortcutsPopupPlugin({
|
|||||||
return () => document.removeEventListener('mousedown', onMouseDown, false)
|
return () => document.removeEventListener('mousedown', onMouseDown, false)
|
||||||
}, [open, closePortal])
|
}, [open, closePortal])
|
||||||
|
|
||||||
const handleInsert = useCallback((command: LexicalCommand<unknown>, params: any) => {
|
const handleInsert = useCallback(<Payload,>(command: LexicalCommand<Payload>, params: Payload) => {
|
||||||
editor.dispatchCommand(command, params)
|
editor.dispatchCommand(command, params)
|
||||||
closePortal()
|
closePortal()
|
||||||
}, [editor, closePortal])
|
}, [editor, closePortal])
|
||||||
|
|||||||
@ -29,26 +29,26 @@ type ModalSnapshot = {
|
|||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
let mockModalProps: ModalSnapshot | null = null
|
let mockModalProps: ModalSnapshot | null = null
|
||||||
vi.mock('../../../base/modal', () => ({
|
let mockOnOpenChange: ((open: boolean) => void) | undefined
|
||||||
default: ({ isShow, children, onClose, closable, className }: { isShow: boolean, children: React.ReactNode, onClose: () => void, closable?: boolean, className?: string }) => {
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
|
Dialog: ({ open, onOpenChange, children }: { open?: boolean, onOpenChange?: (open: boolean) => void, children: React.ReactNode }) => {
|
||||||
|
mockOnOpenChange = onOpenChange
|
||||||
|
mockModalProps = { isShow: open !== false }
|
||||||
|
return open === false ? null : <>{children}</>
|
||||||
|
},
|
||||||
|
DialogContent: ({ children, className }: { children: React.ReactNode, className?: string }) => {
|
||||||
mockModalProps = {
|
mockModalProps = {
|
||||||
isShow,
|
isShow: true,
|
||||||
closable,
|
closable: true,
|
||||||
className,
|
className,
|
||||||
}
|
}
|
||||||
if (!isShow)
|
|
||||||
return null
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="annotation-full-modal" data-classname={className ?? ''}>
|
<div data-testid="annotation-full-modal" data-classname={className ?? ''}>
|
||||||
{closable && (
|
|
||||||
<button type="button" data-testid="mock-modal-close" onClick={onClose}>
|
|
||||||
close
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
DialogCloseButton: () => <button type="button" data-testid="mock-modal-close" onClick={() => mockOnOpenChange?.(false)}>close</button>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('AnnotationFullModal', () => {
|
describe('AnnotationFullModal', () => {
|
||||||
@ -56,6 +56,7 @@ describe('AnnotationFullModal', () => {
|
|||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
mockUpgradeBtnProps = null
|
mockUpgradeBtnProps = null
|
||||||
mockModalProps = null
|
mockModalProps = null
|
||||||
|
mockOnOpenChange = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// Rendering marketing copy inside modal
|
// Rendering marketing copy inside modal
|
||||||
@ -71,8 +72,9 @@ describe('AnnotationFullModal', () => {
|
|||||||
expect(mockModalProps).toEqual(expect.objectContaining({
|
expect(mockModalProps).toEqual(expect.objectContaining({
|
||||||
isShow: true,
|
isShow: true,
|
||||||
closable: true,
|
closable: true,
|
||||||
className: 'p-0!',
|
className: expect.stringContaining('p-0!'),
|
||||||
}))
|
}))
|
||||||
|
expect(mockModalProps?.className).toContain('w-full')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import GridMask from '@/app/components/base/grid-mask'
|
import GridMask from '@/app/components/base/grid-mask'
|
||||||
import Modal from '../../base/modal'
|
|
||||||
import UpgradeBtn from '../upgrade-btn'
|
import UpgradeBtn from '../upgrade-btn'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import Usage from './usage'
|
import Usage from './usage'
|
||||||
@ -20,28 +20,33 @@ const AnnotationFullModal: FC<Props> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={show}
|
open={show}
|
||||||
onClose={onHide}
|
onOpenChange={(open) => {
|
||||||
closable
|
if (!open)
|
||||||
className="p-0!"
|
onHide()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<GridMask wrapperClassName="rounded-lg" canvasClassName="rounded-lg" gradientClassName="rounded-lg">
|
<DialogContent className="w-full overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
<div className="mt-6 flex cursor-pointer flex-col rounded-lg border-2 border-solid border-transparent px-7 py-6 shadow-md transition-all duration-200 ease-in-out">
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className={cn(s.textGradient, 'text-[18px] leading-[27px] font-semibold')}>
|
|
||||||
<div>{t('annotatedResponse.fullTipLine1', { ns: 'billing' })}</div>
|
|
||||||
<div>{t('annotatedResponse.fullTipLine2', { ns: 'billing' })}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<GridMask wrapperClassName="rounded-lg" canvasClassName="rounded-lg" gradientClassName="rounded-lg">
|
||||||
|
<div className="mt-6 flex cursor-pointer flex-col rounded-lg border-2 border-solid border-transparent px-7 py-6 shadow-md transition-all duration-200 ease-in-out">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className={cn(s.textGradient, 'text-[18px] leading-[27px] font-semibold')}>
|
||||||
|
<div>{t('annotatedResponse.fullTipLine1', { ns: 'billing' })}</div>
|
||||||
|
<div>{t('annotatedResponse.fullTipLine2', { ns: 'billing' })}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Usage className="mt-4" />
|
||||||
|
<div className="mt-7 flex justify-end">
|
||||||
|
<UpgradeBtn loc="annotation-create" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Usage className="mt-4" />
|
</GridMask>
|
||||||
<div className="mt-7 flex justify-end">
|
</DialogContent>
|
||||||
<UpgradeBtn loc="annotation-create" />
|
</Dialog>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</GridMask>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(AnnotationFullModal)
|
export default React.memo(AnnotationFullModal)
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { Button } from '@langgenius/dify-ui/button'
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
|
|
||||||
type DSLConfirmModalProps = {
|
type DSLConfirmModalProps = {
|
||||||
versions?: {
|
versions?: {
|
||||||
@ -20,32 +27,36 @@ const DSLConfirmModal = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<AlertDialog
|
||||||
isShow
|
open
|
||||||
onClose={() => onCancel()}
|
onOpenChange={(open) => {
|
||||||
className="w-[480px]"
|
if (!open)
|
||||||
|
onCancel()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
<AlertDialogContent className="w-[480px] overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||||
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</AlertDialogTitle>
|
||||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
<AlertDialogDescription render={<div />} className="flex grow flex-col system-md-regular text-text-secondary">
|
||||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||||
<br />
|
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||||
<div>
|
<br />
|
||||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
<div>
|
||||||
<span className="system-md-medium">{versions.importedVersion}</span>
|
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||||
</div>
|
<span className="system-md-medium">{versions.importedVersion}</span>
|
||||||
<div>
|
</div>
|
||||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
<div>
|
||||||
<span className="system-md-medium">{versions.systemVersion}</span>
|
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||||
</div>
|
<span className="system-md-medium">{versions.systemVersion}</span>
|
||||||
|
</div>
|
||||||
|
</AlertDialogDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<AlertDialogActions>
|
||||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
<AlertDialogCancelButton variant="secondary">{t('newApp.Cancel', { ns: 'app' })}</AlertDialogCancelButton>
|
||||||
<Button variant="secondary" onClick={() => onCancel()}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<AlertDialogConfirmButton onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</AlertDialogConfirmButton>
|
||||||
<Button variant="primary" tone="destructive" onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
</AlertDialogActions>
|
||||||
</div>
|
</AlertDialogContent>
|
||||||
</Modal>
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { useKeyPress } from 'ahooks'
|
import { useKeyPress } from 'ahooks'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import DSLConfirmModal from './dsl-confirm-modal'
|
import DSLConfirmModal from './dsl-confirm-modal'
|
||||||
import Header from './header'
|
import Header from './header'
|
||||||
import { CreateFromDSLModalTab, useDSLImport } from './hooks/use-dsl-import'
|
import { CreateFromDSLModalTab, useDSLImport } from './hooks/use-dsl-import'
|
||||||
@ -58,51 +57,50 @@ const CreateFromDSLModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog open={show}>
|
||||||
className="w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl"
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0! text-left align-middle shadow-xl">
|
||||||
isShow={show}
|
|
||||||
onClose={noop}
|
<Header onClose={onClose} />
|
||||||
>
|
<Tab
|
||||||
<Header onClose={onClose} />
|
currentTab={currentTab}
|
||||||
<Tab
|
setCurrentTab={setCurrentTab}
|
||||||
currentTab={currentTab}
|
/>
|
||||||
setCurrentTab={setCurrentTab}
|
<div className="px-6 py-4">
|
||||||
/>
|
{currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||||
<div className="px-6 py-4">
|
<Uploader
|
||||||
{currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
className="mt-0"
|
||||||
<Uploader
|
file={currentFile}
|
||||||
className="mt-0"
|
updateFile={handleFile}
|
||||||
file={currentFile}
|
|
||||||
updateFile={handleFile}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{currentTab === CreateFromDSLModalTab.FROM_URL && (
|
|
||||||
<div>
|
|
||||||
<div className="leading6 mb-1 system-md-semibold text-text-secondary">
|
|
||||||
DSL URL
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
placeholder={t('importFromDSLUrlPlaceholder', { ns: 'app' }) || ''}
|
|
||||||
value={dslUrlValue}
|
|
||||||
onChange={e => setDslUrlValue(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)}
|
{currentTab === CreateFromDSLModalTab.FROM_URL && (
|
||||||
</div>
|
<div>
|
||||||
<div className="flex justify-end gap-x-2 p-6 pt-5">
|
<div className="leading6 mb-1 system-md-semibold text-text-secondary">
|
||||||
<Button onClick={onClose}>
|
DSL URL
|
||||||
{t('newApp.Cancel', { ns: 'app' })}
|
</div>
|
||||||
</Button>
|
<Input
|
||||||
<Button
|
placeholder={t('importFromDSLUrlPlaceholder', { ns: 'app' }) || ''}
|
||||||
disabled={buttonDisabled}
|
value={dslUrlValue}
|
||||||
variant="primary"
|
onChange={e => setDslUrlValue(e.target.value)}
|
||||||
onClick={handleCreateApp}
|
/>
|
||||||
className="gap-1"
|
</div>
|
||||||
>
|
)}
|
||||||
<span>{t('newApp.import', { ns: 'app' })}</span>
|
</div>
|
||||||
</Button>
|
<div className="flex justify-end gap-x-2 p-6 pt-5">
|
||||||
</div>
|
<Button onClick={onClose}>
|
||||||
</Modal>
|
{t('newApp.Cancel', { ns: 'app' })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={buttonDisabled}
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleCreateApp}
|
||||||
|
className="gap-1"
|
||||||
|
>
|
||||||
|
<span>{t('newApp.import', { ns: 'app' })}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
{showConfirmModal && (
|
{showConfirmModal && (
|
||||||
<DSLConfirmModal
|
<DSLConfirmModal
|
||||||
versions={versions}
|
versions={versions}
|
||||||
|
|||||||
@ -8,12 +8,12 @@ import {
|
|||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { trackEvent } from '@/app/components/base/amplitude'
|
import { trackEvent } from '@/app/components/base/amplitude'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
import { useCreatePipelineDatasetFromCustomized } from '@/service/knowledge/use-create-dataset'
|
import { useCreatePipelineDatasetFromCustomized } from '@/service/knowledge/use-create-dataset'
|
||||||
@ -153,16 +153,21 @@ const TemplateCard = ({
|
|||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
{showEditModal && (
|
{showEditModal && (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={showEditModal}
|
open={showEditModal}
|
||||||
onClose={closeEditModal}
|
onOpenChange={(open) => {
|
||||||
className="max-w-[520px] p-0"
|
if (!open)
|
||||||
|
closeEditModal()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<EditPipelineInfo
|
<DialogContent className="w-full max-w-[520px] overflow-hidden! border-none p-0 text-left align-middle">
|
||||||
pipeline={pipeline}
|
|
||||||
onClose={closeEditModal}
|
<EditPipelineInfo
|
||||||
/>
|
pipeline={pipeline}
|
||||||
</Modal>
|
onClose={closeEditModal}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && onCancelDelete()}>
|
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && onCancelDelete()}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
@ -183,18 +188,23 @@ const TemplateCard = ({
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
{showDetailModal && (
|
{showDetailModal && (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={showDetailModal}
|
open={showDetailModal}
|
||||||
onClose={closeDetailsModal}
|
onOpenChange={(open) => {
|
||||||
className="h-[calc(100vh-64px)] max-w-[1680px] rounded-3xl p-0"
|
if (!open)
|
||||||
|
closeDetailsModal()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Details
|
<DialogContent className="h-[calc(100vh-64px)] max-h-none w-full max-w-[1680px] overflow-hidden! rounded-3xl border-none p-0 text-left align-middle">
|
||||||
id={pipeline.id}
|
|
||||||
type={type}
|
<Details
|
||||||
onClose={closeDetailsModal}
|
id={pipeline.id}
|
||||||
onApplyTemplate={handleUseTemplate}
|
type={type}
|
||||||
/>
|
onClose={closeDetailsModal}
|
||||||
</Modal>
|
onApplyTemplate={handleUseTemplate}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { trackEvent } from '@/app/components/base/amplitude'
|
import { trackEvent } from '@/app/components/base/amplitude'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
import { createEmptyDataset } from '@/service/datasets'
|
import { createEmptyDataset } from '@/service/datasets'
|
||||||
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
||||||
@ -46,21 +46,30 @@ const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Modal isShow={show} onClose={onHide} className={cn(s.modal, '!max-w-[520px]', 'px-8')}>
|
<Dialog
|
||||||
<div className={s.modalHeader}>
|
open={show}
|
||||||
<div className={s.title}>{t('stepOne.modal.title', { ns: 'datasetCreation' })}</div>
|
onOpenChange={(open) => {
|
||||||
<span className={s.close} onClick={onHide} />
|
if (!open)
|
||||||
</div>
|
onHide()
|
||||||
<div className={s.tip}>{t('stepOne.modal.tip', { ns: 'datasetCreation' })}</div>
|
}}
|
||||||
<div className={s.form}>
|
>
|
||||||
<div className={s.label}>{t('stepOne.modal.input', { ns: 'datasetCreation' })}</div>
|
<DialogContent className={cn('w-full overflow-hidden! border-none text-left align-middle', cn(s.modal, '!max-w-[520px]', 'px-8'))}>
|
||||||
<Input value={inputValue} placeholder={t('stepOne.modal.placeholder', { ns: 'datasetCreation' }) || ''} onChange={e => setInputValue(e.target.value)} />
|
|
||||||
</div>
|
<div className={s.modalHeader}>
|
||||||
<div className="flex flex-row-reverse">
|
<div className={s.title}>{t('stepOne.modal.title', { ns: 'datasetCreation' })}</div>
|
||||||
<Button className="ml-2 w-24" variant="primary" onClick={submit}>{t('stepOne.modal.confirmButton', { ns: 'datasetCreation' })}</Button>
|
<span className={s.close} onClick={onHide} />
|
||||||
<Button className="w-24" onClick={onHide}>{t('stepOne.modal.cancelButton', { ns: 'datasetCreation' })}</Button>
|
</div>
|
||||||
</div>
|
<div className={s.tip}>{t('stepOne.modal.tip', { ns: 'datasetCreation' })}</div>
|
||||||
</Modal>
|
<div className={s.form}>
|
||||||
|
<div className={s.label}>{t('stepOne.modal.input', { ns: 'datasetCreation' })}</div>
|
||||||
|
<Input value={inputValue} placeholder={t('stepOne.modal.placeholder', { ns: 'datasetCreation' }) || ''} onChange={e => setInputValue(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row-reverse">
|
||||||
|
<Button className="ml-2 w-24" variant="primary" onClick={submit}>{t('stepOne.modal.confirmButton', { ns: 'datasetCreation' })}</Button>
|
||||||
|
<Button className="w-24" onClick={onHide}>{t('stepOne.modal.cancelButton', { ns: 'datasetCreation' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default EmptyDatasetCreationModal
|
export default EmptyDatasetCreationModal
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import s from './index.module.css'
|
import s from './index.module.css'
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
@ -25,20 +32,24 @@ const StopEmbeddingModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<AlertDialog
|
||||||
isShow={show}
|
open={show}
|
||||||
onClose={onHide}
|
onOpenChange={(open) => {
|
||||||
className={cn(s.modal, 'max-w-[480px]!', 'px-8')}
|
if (!open)
|
||||||
|
onHide()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className={s.icon} />
|
<AlertDialogContent className={cn(s.modal, 'max-w-[480px]! overflow-hidden! border-none px-8 py-6 text-left align-middle shadow-xl')}>
|
||||||
<span className={s.close} onClick={onHide} />
|
<div className={s.icon} />
|
||||||
<div className={s.title}>{t('stepThree.modelTitle', { ns: 'datasetCreation' })}</div>
|
<span className={s.close} onClick={onHide} />
|
||||||
<div className={s.content}>{t('stepThree.modelContent', { ns: 'datasetCreation' })}</div>
|
<AlertDialogTitle className={s.title}>{t('stepThree.modelTitle', { ns: 'datasetCreation' })}</AlertDialogTitle>
|
||||||
<div className="flex flex-row-reverse">
|
<AlertDialogDescription className={s.content}>{t('stepThree.modelContent', { ns: 'datasetCreation' })}</AlertDialogDescription>
|
||||||
<Button className="ml-2 w-24" variant="primary" onClick={submit}>{t('stepThree.modelButtonConfirm', { ns: 'datasetCreation' })}</Button>
|
<AlertDialogActions className="flex-row-reverse gap-0 p-0">
|
||||||
<Button className="w-24" onClick={onHide}>{t('stepThree.modelButtonCancel', { ns: 'datasetCreation' })}</Button>
|
<AlertDialogConfirmButton className="ml-2 w-24" tone="default" onClick={submit}>{t('stepThree.modelButtonConfirm', { ns: 'datasetCreation' })}</AlertDialogConfirmButton>
|
||||||
</div>
|
<AlertDialogCancelButton className="w-24" variant="secondary">{t('stepThree.modelButtonCancel', { ns: 'datasetCreation' })}</AlertDialogCancelButton>
|
||||||
</Modal>
|
</AlertDialogActions>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { renameDocumentName } from '@/service/datasets'
|
import { renameDocumentName } from '@/service/datasets'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -55,23 +55,31 @@ const RenameModal: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t('list.table.rename', { ns: 'datasetDocuments' })}
|
open
|
||||||
isShow
|
onOpenChange={(open) => {
|
||||||
onClose={onClose}
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('list.table.name', { ns: 'datasetDocuments' })}</div>
|
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||||
<Input
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
className="mt-2 h-10"
|
{t('list.table.rename', { ns: 'datasetDocuments' })}
|
||||||
value={newName}
|
</DialogTitle>
|
||||||
onChange={e => setNewName(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-10 flex justify-end">
|
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('list.table.name', { ns: 'datasetDocuments' })}</div>
|
||||||
<Button className="mr-2 shrink-0" onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
<Input
|
||||||
<Button variant="primary" className="shrink-0" onClick={handleSave} loading={saveLoading}>{t('operation.save', { ns: 'common' })}</Button>
|
className="mt-2 h-10"
|
||||||
</div>
|
value={newName}
|
||||||
</Modal>
|
onChange={e => setNewName(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-10 flex justify-end">
|
||||||
|
<Button className="mr-2 shrink-0" onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button variant="primary" className="shrink-0" onClick={handleSave} loading={saveLoading}>{t('operation.save', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(RenameModal)
|
export default React.memo(RenameModal)
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { ChunkingMode, FileItem } from '@/models/datasets'
|
import type { ChunkingMode, FileItem } from '@/models/datasets'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import CSVDownloader from './csv-downloader'
|
import CSVDownloader from './csv-downloader'
|
||||||
import CSVUploader from './csv-uploader'
|
import CSVUploader from './csv-uploader'
|
||||||
|
|
||||||
@ -41,27 +40,30 @@ const BatchModal: FC<IBatchModalProps> = ({
|
|||||||
}, [isShow])
|
}, [isShow])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={noop} className="max-w-[520px]! rounded-xl! px-8 py-6">
|
<Dialog open={isShow}>
|
||||||
<div className="relative pb-1 text-xl leading-[30px] font-medium text-text-primary">{t('list.batchModal.title', { ns: 'datasetDocuments' })}</div>
|
<DialogContent className="w-full max-w-[520px]! overflow-hidden! rounded-xl! border-none px-8 py-6 text-left align-middle">
|
||||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onCancel}>
|
|
||||||
<RiCloseLine className="h-4 w-4 text-text-secondary" />
|
<div className="relative pb-1 text-xl leading-[30px] font-medium text-text-primary">{t('list.batchModal.title', { ns: 'datasetDocuments' })}</div>
|
||||||
</div>
|
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onCancel}>
|
||||||
<CSVUploader
|
<RiCloseLine className="h-4 w-4 text-text-secondary" />
|
||||||
file={currentCSV}
|
</div>
|
||||||
updateFile={handleFile}
|
<CSVUploader
|
||||||
/>
|
file={currentCSV}
|
||||||
<CSVDownloader
|
updateFile={handleFile}
|
||||||
docForm={docForm}
|
/>
|
||||||
/>
|
<CSVDownloader
|
||||||
<div className="mt-[28px] flex justify-end pt-6">
|
docForm={docForm}
|
||||||
<Button className="mr-2" onClick={onCancel}>
|
/>
|
||||||
{t('list.batchModal.cancel', { ns: 'datasetDocuments' })}
|
<div className="mt-[28px] flex justify-end pt-6">
|
||||||
</Button>
|
<Button className="mr-2" onClick={onCancel}>
|
||||||
<Button variant="primary" onClick={handleSend} disabled={!currentCSV || !currentCSV.file || !currentCSV.file.id}>
|
{t('list.batchModal.cancel', { ns: 'datasetDocuments' })}
|
||||||
{t('list.batchModal.run', { ns: 'datasetDocuments' })}
|
</Button>
|
||||||
</Button>
|
<Button variant="primary" onClick={handleSend} disabled={!currentCSV || !currentCSV.file || !currentCSV.file.id}>
|
||||||
</div>
|
{t('list.batchModal.run', { ns: 'datasetDocuments' })}
|
||||||
</Modal>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(BatchModal)
|
export default React.memo(BatchModal)
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { RiLoader2Line } from '@remixicon/react'
|
import { RiLoader2Line } from '@remixicon/react'
|
||||||
import { useCountDown } from 'ahooks'
|
import { useCountDown } from 'ahooks'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
|
||||||
type IDefaultContentProps = {
|
type IDefaultContentProps = {
|
||||||
@ -22,18 +29,18 @@ const DefaultContent: FC<IDefaultContentProps> = React.memo(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="pb-4">
|
<div className="p-6 pb-4">
|
||||||
<span className="title-2xl-semi-bold text-text-primary">{t('segment.regenerationConfirmTitle', { ns: 'datasetDocuments' })}</span>
|
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('segment.regenerationConfirmTitle', { ns: 'datasetDocuments' })}</AlertDialogTitle>
|
||||||
<p className="system-md-regular text-text-secondary">{t('segment.regenerationConfirmMessage', { ns: 'datasetDocuments' })}</p>
|
<AlertDialogDescription className="system-md-regular text-text-secondary">{t('segment.regenerationConfirmMessage', { ns: 'datasetDocuments' })}</AlertDialogDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-x-2 pt-6">
|
<AlertDialogActions>
|
||||||
<Button onClick={onCancel}>
|
<AlertDialogCancelButton variant="secondary" onClick={onCancel}>
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
</Button>
|
</AlertDialogCancelButton>
|
||||||
<Button variant="primary" tone="destructive" onClick={onConfirm}>
|
<AlertDialogConfirmButton onClick={onConfirm}>
|
||||||
{t('operation.regenerate', { ns: 'common' })}
|
{t('operation.regenerate', { ns: 'common' })}
|
||||||
</Button>
|
</AlertDialogConfirmButton>
|
||||||
</div>
|
</AlertDialogActions>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -45,11 +52,11 @@ const RegeneratingContent: FC = React.memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="pb-4">
|
<div className="p-6 pb-4">
|
||||||
<span className="title-2xl-semi-bold text-text-primary">{t('segment.regeneratingTitle', { ns: 'datasetDocuments' })}</span>
|
<span className="title-2xl-semi-bold text-text-primary">{t('segment.regeneratingTitle', { ns: 'datasetDocuments' })}</span>
|
||||||
<p className="system-md-regular text-text-secondary">{t('segment.regeneratingMessage', { ns: 'datasetDocuments' })}</p>
|
<p className="system-md-regular text-text-secondary">{t('segment.regeneratingMessage', { ns: 'datasetDocuments' })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end pt-6">
|
<div className="flex justify-end p-6">
|
||||||
<Button variant="primary" tone="destructive" disabled className="inline-flex items-center gap-x-0.5">
|
<Button variant="primary" tone="destructive" disabled className="inline-flex items-center gap-x-0.5">
|
||||||
<RiLoader2Line className="h-4 w-4 animate-spin text-components-button-destructive-primary-text-disabled" />
|
<RiLoader2Line className="h-4 w-4 animate-spin text-components-button-destructive-primary-text-disabled" />
|
||||||
<span>{t('operation.regenerate', { ns: 'common' })}</span>
|
<span>{t('operation.regenerate', { ns: 'common' })}</span>
|
||||||
@ -79,11 +86,11 @@ const RegenerationCompletedContent: FC<IRegenerationCompletedContentProps> = Rea
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="pb-4">
|
<div className="p-6 pb-4">
|
||||||
<span className="title-2xl-semi-bold text-text-primary">{t('segment.regenerationSuccessTitle', { ns: 'datasetDocuments' })}</span>
|
<span className="title-2xl-semi-bold text-text-primary">{t('segment.regenerationSuccessTitle', { ns: 'datasetDocuments' })}</span>
|
||||||
<p className="system-md-regular text-text-secondary">{t('segment.regenerationSuccessMessage', { ns: 'datasetDocuments' })}</p>
|
<p className="system-md-regular text-text-secondary">{t('segment.regenerationSuccessMessage', { ns: 'datasetDocuments' })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end pt-6">
|
<div className="flex justify-end p-6">
|
||||||
<Button variant="primary" onClick={onClose}>
|
<Button variant="primary" onClick={onClose}>
|
||||||
{`${t('operation.close', { ns: 'common' })}${countdown === 0 ? '' : `(${Math.round(countdown / 1000)})`}`}
|
{`${t('operation.close', { ns: 'common' })}${countdown === 0 ? '' : `(${Math.round(countdown / 1000)})`}`}
|
||||||
</Button>
|
</Button>
|
||||||
@ -123,11 +130,13 @@ const RegenerationModal: FC<IRegenerationModalProps> = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={noop} className="max-w-[480px]! rounded-2xl!" wrapperClassName="z-10000!">
|
<AlertDialog open={isShow}>
|
||||||
{!loading && !updateSucceeded && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
|
<AlertDialogContent className="max-w-[480px]! overflow-hidden! rounded-2xl border-none text-left align-middle shadow-xl">
|
||||||
{loading && !updateSucceeded && <RegeneratingContent />}
|
{!loading && !updateSucceeded && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
|
||||||
{!loading && updateSucceeded && <RegenerationCompletedContent onClose={onClose} />}
|
{loading && !updateSucceeded && <RegeneratingContent />}
|
||||||
</Modal>
|
{!loading && updateSucceeded && <RegenerationCompletedContent onClose={onClose} />}
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,14 +11,16 @@ vi.mock('@/app/components/base/markdown', () => ({
|
|||||||
Markdown: ({ content }: { content: string }) => <div data-testid="markdown">{content}</div>,
|
Markdown: ({ content }: { content: string }) => <div data-testid="markdown">{content}</div>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({ children, title, onClose }: { children: React.ReactNode, title: string, onClose: () => void }) => (
|
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||||
|
open === false ? null : <>{children}</>,
|
||||||
|
DialogContent: ({ children }: { children: React.ReactNode }) => (
|
||||||
<div data-testid="modal">
|
<div data-testid="modal">
|
||||||
<div data-testid="modal-title">{title}</div>
|
|
||||||
<button data-testid="modal-close" onClick={onClose}>close</button>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
DialogTitle: ({ children }: { children: React.ReactNode }) => <div data-testid="modal-title">{children}</div>,
|
||||||
|
DialogCloseButton: () => <button data-testid="modal-close">close</button>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../../../common/image-list', () => ({
|
vi.mock('../../../common/image-list', () => ({
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
|
import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
|
||||||
import type { HitTesting } from '@/models/datasets'
|
import type { HitTesting } from '@/models/datasets'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import FileIcon from '@/app/components/base/file-uploader/file-type-icon'
|
import FileIcon from '@/app/components/base/file-uploader/file-type-icon'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { Markdown } from '@/app/components/base/markdown'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Tag from '@/app/components/datasets/documents/detail/completed/common/tag'
|
import Tag from '@/app/components/datasets/documents/detail/completed/common/tag'
|
||||||
import ImageList from '../../common/image-list'
|
import ImageList from '../../common/image-list'
|
||||||
import Dot from '../../documents/detail/completed/common/dot'
|
import Dot from '../../documents/detail/completed/common/dot'
|
||||||
@ -52,93 +52,100 @@ const ChunkDetailModal = ({
|
|||||||
const showKeywords = !isParentChildRetrieval && keywords && keywords.length > 0
|
const showKeywords = !isParentChildRetrieval && keywords && keywords.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
open
|
||||||
isShow
|
onOpenChange={(open) => {
|
||||||
closable
|
if (!open)
|
||||||
onClose={onHide}
|
onHide()
|
||||||
className={cn(isParentChildRetrieval ? 'min-w-[1200px]!' : 'min-w-[800px]!')}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-4 flex">
|
<DialogContent className={cn('w-full overflow-hidden! border-none text-left align-middle', cn(isParentChildRetrieval ? 'min-w-[1200px]!' : 'min-w-[800px]!'))}>
|
||||||
<div className={cn('flex-1', isParentChildRetrieval && 'pr-6')}>
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
{/* Meta info */}
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<div className="flex items-center justify-between">
|
{t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
||||||
<div className="flex grow items-center space-x-2">
|
</DialogTitle>
|
||||||
<SegmentIndexTag
|
|
||||||
labelPrefix={labelPrefix}
|
<div className="mt-4 flex">
|
||||||
positionId={position}
|
<div className={cn('flex-1', isParentChildRetrieval && 'pr-6')}>
|
||||||
className={cn('w-fit group-hover:opacity-100')}
|
{/* Meta info */}
|
||||||
/>
|
<div className="flex items-center justify-between">
|
||||||
<Dot />
|
<div className="flex grow items-center space-x-2">
|
||||||
<div className="flex grow items-center space-x-1">
|
<SegmentIndexTag
|
||||||
<FileIcon type={extension} size="sm" />
|
labelPrefix={labelPrefix}
|
||||||
<span className="w-0 grow truncate text-[13px] font-normal text-text-secondary">{document.name}</span>
|
positionId={position}
|
||||||
|
className={cn('w-fit group-hover:opacity-100')}
|
||||||
|
/>
|
||||||
|
<Dot />
|
||||||
|
<div className="flex grow items-center space-x-1">
|
||||||
|
<FileIcon type={extension} size="sm" />
|
||||||
|
<span className="w-0 grow truncate text-[13px] font-normal text-text-secondary">{document.name}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Score value={score} />
|
||||||
</div>
|
</div>
|
||||||
<Score value={score} />
|
{/* Content */}
|
||||||
</div>
|
<div className="relative">
|
||||||
{/* Content */}
|
{!answer && (
|
||||||
<div className="relative">
|
<Markdown
|
||||||
{!answer && (
|
className={cn('mt-2! text-text-secondary!', heighClassName)}
|
||||||
<Markdown
|
content={sign_content || content}
|
||||||
className={cn('mt-2! text-text-secondary!', heighClassName)}
|
customDisallowedElements={['input']}
|
||||||
content={sign_content || content}
|
/>
|
||||||
customDisallowedElements={['input']}
|
)}
|
||||||
/>
|
{answer && (
|
||||||
)}
|
<div className="break-all">
|
||||||
{answer && (
|
<div className="flex gap-x-1">
|
||||||
<div className="break-all">
|
<div className="w-4 shrink-0 text-[13px] leading-[20px] font-medium text-text-tertiary">Q</div>
|
||||||
<div className="flex gap-x-1">
|
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
||||||
<div className="w-4 shrink-0 text-[13px] leading-[20px] font-medium text-text-tertiary">Q</div>
|
{content}
|
||||||
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
</div>
|
||||||
{content}
|
</div>
|
||||||
|
<div className="flex gap-x-1">
|
||||||
|
<div className="w-4 shrink-0 text-[13px] leading-[20px] font-medium text-text-tertiary">A</div>
|
||||||
|
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
||||||
|
{answer}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-x-1">
|
)}
|
||||||
<div className="w-4 shrink-0 text-[13px] leading-[20px] font-medium text-text-tertiary">A</div>
|
{/* Mask */}
|
||||||
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
<Mask className="absolute inset-x-0 bottom-0" />
|
||||||
{answer}
|
</div>
|
||||||
|
{(showImages || showKeywords || !!summary) && (
|
||||||
|
<div className="flex flex-col gap-y-3 pt-3">
|
||||||
|
{showImages && (
|
||||||
|
<ImageList images={images} size="md" className="py-1" />
|
||||||
|
)}
|
||||||
|
{!!summary && (
|
||||||
|
<SummaryText value={summary} disabled />
|
||||||
|
)}
|
||||||
|
{showKeywords && (
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<div className="text-xs font-medium text-text-tertiary uppercase">{t(`${i18nPrefix}keyword`, { ns: 'datasetHitTesting' })}</div>
|
||||||
|
<div className="flex flex-wrap gap-x-2">
|
||||||
|
{keywords.map(keyword => (
|
||||||
|
<Tag key={keyword} text={keyword} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Mask */}
|
|
||||||
<Mask className="absolute inset-x-0 bottom-0" />
|
|
||||||
</div>
|
</div>
|
||||||
{(showImages || showKeywords || !!summary) && (
|
|
||||||
<div className="flex flex-col gap-y-3 pt-3">
|
{isParentChildRetrieval && (
|
||||||
{showImages && (
|
<div className="flex-1 pb-6 pl-6">
|
||||||
<ImageList images={images} size="md" className="py-1" />
|
<div className="system-xs-semibold-uppercase text-text-secondary">{t(`${i18nPrefix}hitChunks`, { ns: 'datasetHitTesting', num: child_chunks.length })}</div>
|
||||||
)}
|
<div className={cn('mt-1 space-y-2', heighClassName)}>
|
||||||
{!!summary && (
|
{child_chunks.map(item => (
|
||||||
<SummaryText value={summary} disabled />
|
<ChildChunksItem key={item.id} payload={item} isShowAll />
|
||||||
)}
|
))}
|
||||||
{showKeywords && (
|
</div>
|
||||||
<div className="flex flex-col gap-y-1">
|
|
||||||
<div className="text-xs font-medium text-text-tertiary uppercase">{t(`${i18nPrefix}keyword`, { ns: 'datasetHitTesting' })}</div>
|
|
||||||
<div className="flex flex-wrap gap-x-2">
|
|
||||||
{keywords.map(keyword => (
|
|
||||||
<Tag key={keyword} text={keyword} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</DialogContent>
|
||||||
{isParentChildRetrieval && (
|
</Dialog>
|
||||||
<div className="flex-1 pb-6 pl-6">
|
|
||||||
<div className="system-xs-semibold-uppercase text-text-secondary">{t(`${i18nPrefix}hitChunks`, { ns: 'datasetHitTesting', num: child_chunks.length })}</div>
|
|
||||||
<div className={cn('mt-1 space-y-2', heighClassName)}>
|
|
||||||
{child_chunks.map(item => (
|
|
||||||
<ChildChunksItem key={item.id} payload={item} isShowAll />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { ExternalKnowledgeBaseHitTesting } from '@/models/datasets'
|
import type { ExternalKnowledgeBaseHitTesting } from '@/models/datasets'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
|
import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import ResultItemFooter from './result-item-footer'
|
import ResultItemFooter from './result-item-footer'
|
||||||
import ResultItemMeta from './result-item-meta'
|
import ResultItemMeta from './result-item-meta'
|
||||||
|
|
||||||
@ -38,20 +38,27 @@ const ResultItemExternal: FC<Props> = ({ payload, positionId }) => {
|
|||||||
<ResultItemFooter docType={FileAppearanceTypeEnum.custom} docTitle={title} showDetailModal={showDetailModal} />
|
<ResultItemFooter docType={FileAppearanceTypeEnum.custom} docTitle={title} showDetailModal={showDetailModal} />
|
||||||
|
|
||||||
{isShowDetailModal && (
|
{isShowDetailModal && (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
open={isShowDetailModal}
|
||||||
className="min-w-[800px]!"
|
onOpenChange={(open) => {
|
||||||
closable
|
if (!open)
|
||||||
onClose={hideDetailModal}
|
hideDetailModal()
|
||||||
isShow={isShowDetailModal}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-4 flex-1">
|
<DialogContent className="w-full min-w-[800px]! overflow-hidden! border-none text-left align-middle">
|
||||||
<ResultItemMeta labelPrefix="Chunk" positionId={positionId} wordCount={content.length} score={score} />
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
<div className={cn('mt-2 body-md-regular break-all text-text-secondary', 'h-[min(539px,80vh)] overflow-y-auto')}>
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
{content}
|
{t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<div className="mt-4 flex-1">
|
||||||
|
<ResultItemMeta labelPrefix="Chunk" positionId={positionId} wordCount={content.length} score={score} />
|
||||||
|
<div className={cn('mt-2 body-md-regular break-all text-text-secondary', 'h-[min(539px,80vh)] overflow-y-auto')}>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { BuiltInMetadataItem, MetadataItemInBatchEdit, MetadataItemWithEdit } from '../types'
|
import type { BuiltInMetadataItem, MetadataItemInBatchEdit, MetadataItemWithEdit } from '../types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiQuestionLine } from '@remixicon/react'
|
import { RiQuestionLine } from '@remixicon/react'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import { useCreateMetaData } from '@/service/knowledge/use-metadata'
|
import { useCreateMetaData } from '@/service/knowledge/use-metadata'
|
||||||
import Checkbox from '../../../base/checkbox'
|
import Checkbox from '../../../base/checkbox'
|
||||||
import Modal from '../../../base/modal'
|
|
||||||
import Tooltip from '../../../base/tooltip'
|
import Tooltip from '../../../base/tooltip'
|
||||||
import AddMetadataButton from '../add-metadata-button'
|
import AddMetadataButton from '../add-metadata-button'
|
||||||
import useCheckMetadataName from '../hooks/use-check-metadata-name'
|
import useCheckMetadataName from '../hooks/use-check-metadata-name'
|
||||||
@ -91,46 +91,59 @@ const EditMetadataBatchModal: FC<Props> = ({ datasetId, documentNum, list, onSav
|
|||||||
onSave(templeList.filter(item => item.updateType !== UpdateType.delete), addedList, isApplyToAllSelectDocument)
|
onSave(templeList.filter(item => item.updateType !== UpdateType.delete), addedList, isApplyToAllSelectDocument)
|
||||||
}, [templeList, addedList, isApplyToAllSelectDocument, onSave])
|
}, [templeList, addedList, isApplyToAllSelectDocument, onSave])
|
||||||
return (
|
return (
|
||||||
<Modal title={t(`${i18nPrefix}.editMetadata`, { ns: 'dataset' })} isShow closable onClose={onHide} className="!max-w-[640px]">
|
<Dialog
|
||||||
<div className="mt-1 system-xs-medium text-text-accent">{t(`${i18nPrefix}.editDocumentsNum`, { ns: 'dataset', num: documentNum })}</div>
|
open
|
||||||
<div className="max-h-[305px] overflow-x-hidden overflow-y-auto">
|
onOpenChange={(open) => {
|
||||||
<div className="mt-4 space-y-2">
|
if (!open)
|
||||||
{templeList.map(item => (<EditMetadataBatchItem key={item.id} payload={item} onChange={handleTemplesChange} onRemove={handleTempleItemRemove} onReset={handleItemReset} />))}
|
onHide()
|
||||||
</div>
|
}}
|
||||||
<div className="mt-4 pl-[18px]">
|
>
|
||||||
<div className="flex items-center">
|
<DialogContent className="w-full !max-w-[640px] overflow-hidden! border-none text-left align-middle">
|
||||||
<div className="mr-2 shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('metadata.createMetadata.title', { ns: 'dataset' })}</div>
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
<Divider bgStyle="gradient" />
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
</div>
|
{t(`${i18nPrefix}.editMetadata`, { ns: 'dataset' })}
|
||||||
<div className="mt-2 space-y-2">
|
</DialogTitle>
|
||||||
{addedList.map((item, i) => (<AddedMetadataItem key={i} payload={item} onChange={handleAddedListChange} onRemove={handleAddedItemRemove(i)} />))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-3">
|
|
||||||
<SelectMetadataModal datasetId={datasetId} popupPlacement="top-start" popupOffset={{ mainAxis: 4, crossAxis: 0 }} trigger={<AddMetadataButton />} onSave={handleAddMetaData} onSelect={data => setAddedList([...addedList, data as MetadataItemWithEdit])} onManage={onShowManage} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex items-center justify-between">
|
<div className="mt-1 system-xs-medium text-text-accent">{t(`${i18nPrefix}.editDocumentsNum`, { ns: 'dataset', num: documentNum })}</div>
|
||||||
<div className="flex items-center select-none">
|
<div className="max-h-[305px] overflow-x-hidden overflow-y-auto">
|
||||||
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} id="apply-to-all" />
|
<div className="mt-4 space-y-2">
|
||||||
<div className="mr-1 ml-2 system-xs-medium text-text-secondary">{t(`${i18nPrefix}.applyToAllSelectDocument`, { ns: 'dataset' })}</div>
|
{templeList.map(item => (<EditMetadataBatchItem key={item.id} payload={item} onChange={handleTemplesChange} onRemove={handleTempleItemRemove} onReset={handleItemReset} />))}
|
||||||
<Tooltip popupContent={<div className="max-w-[240px]">{t(`${i18nPrefix}.applyToAllSelectDocumentTip`, { ns: 'dataset' })}</div>}>
|
</div>
|
||||||
<div className="cursor-pointer p-px">
|
<div className="mt-4 pl-[18px]">
|
||||||
<RiQuestionLine className="size-3.5 text-text-tertiary" />
|
<div className="flex items-center">
|
||||||
|
<div className="mr-2 shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('metadata.createMetadata.title', { ns: 'dataset' })}</div>
|
||||||
|
<Divider bgStyle="gradient" />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
<div className="mt-2 space-y-2">
|
||||||
|
{addedList.map((item, i) => (<AddedMetadataItem key={i} payload={item} onChange={handleAddedListChange} onRemove={handleAddedItemRemove(i)} />))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
<SelectMetadataModal datasetId={datasetId} popupPlacement="top-start" popupOffset={{ mainAxis: 4, crossAxis: 0 }} trigger={<AddMetadataButton />} onSave={handleAddMetaData} onSelect={data => setAddedList([...addedList, data as MetadataItemWithEdit])} onManage={onShowManage} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Button onClick={onHide}>
|
<div className="mt-4 flex items-center justify-between">
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
<div className="flex items-center select-none">
|
||||||
</Button>
|
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} id="apply-to-all" />
|
||||||
<Button onClick={handleSave} variant="primary">
|
<div className="mr-1 ml-2 system-xs-medium text-text-secondary">{t(`${i18nPrefix}.applyToAllSelectDocument`, { ns: 'dataset' })}</div>
|
||||||
{t('operation.save', { ns: 'common' })}
|
<Tooltip popupContent={<div className="max-w-[240px]">{t(`${i18nPrefix}.applyToAllSelectDocumentTip`, { ns: 'dataset' })}</div>}>
|
||||||
</Button>
|
<div className="cursor-pointer p-px">
|
||||||
|
<RiQuestionLine className="size-3.5 text-text-tertiary" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button onClick={onHide}>
|
||||||
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSave} variant="primary">
|
||||||
|
{t('operation.save', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(EditMetadataBatchModal)
|
export default React.memo(EditMetadataBatchModal)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { Switch } from '@langgenius/dify-ui/switch'
|
import { Switch } from '@langgenius/dify-ui/switch'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||||
@ -21,7 +22,6 @@ import { useCallback, useRef, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Drawer from '@/app/components/base/drawer'
|
import Drawer from '@/app/components/base/drawer'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import CreateModal from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
|
import CreateModal from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
|
||||||
import { getIcon } from '../utils/get-icon'
|
import { getIcon } from '../utils/get-icon'
|
||||||
@ -230,33 +230,45 @@ const DatasetMetadataDrawer: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isShowRenameModal && (
|
{isShowRenameModal && (
|
||||||
<Modal isShow title={t(`${i18nPrefix}.rename`, { ns: 'dataset' })} onClose={() => setIsShowRenameModal(false)}>
|
<Dialog
|
||||||
<Field label={t(`${i18nPrefix}.name`, { ns: 'dataset' })} className="mt-4">
|
open
|
||||||
<Input
|
onOpenChange={(open) => {
|
||||||
value={templeName}
|
if (!open)
|
||||||
onChange={e => setTempleName(e.target.value)}
|
setIsShowRenameModal(false)
|
||||||
placeholder={t(`${i18nPrefix}.namePlaceholder`, { ns: 'dataset' })}
|
}}
|
||||||
/>
|
>
|
||||||
</Field>
|
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||||
<div className="mt-4 flex justify-end">
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<Button
|
{t(`${i18nPrefix}.rename`, { ns: 'dataset' })}
|
||||||
className="mr-2"
|
</DialogTitle>
|
||||||
onClick={() => {
|
|
||||||
setIsShowRenameModal(false)
|
<Field label={t(`${i18nPrefix}.name`, { ns: 'dataset' })} className="mt-4">
|
||||||
setTempleName(currPayload!.name)
|
<Input
|
||||||
}}
|
value={templeName}
|
||||||
>
|
onChange={e => setTempleName(e.target.value)}
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
placeholder={t(`${i18nPrefix}.namePlaceholder`, { ns: 'dataset' })}
|
||||||
</Button>
|
/>
|
||||||
<Button
|
</Field>
|
||||||
onClick={handleRenamed}
|
<div className="mt-4 flex justify-end">
|
||||||
variant="primary"
|
<Button
|
||||||
disabled={!templeName}
|
className="mr-2"
|
||||||
>
|
onClick={() => {
|
||||||
{t('operation.save', { ns: 'common' })}
|
setIsShowRenameModal(false)
|
||||||
</Button>
|
setTempleName(currPayload!.name)
|
||||||
</div>
|
}}
|
||||||
</Modal>
|
>
|
||||||
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleRenamed}
|
||||||
|
variant="primary"
|
||||||
|
disabled={!templeName}
|
||||||
|
>
|
||||||
|
{t('operation.save', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@ -4,13 +4,12 @@ import type { AppIconSelection } from '../../base/app-icon-picker'
|
|||||||
import type { DataSet } from '@/models/datasets'
|
import type { DataSet } from '@/models/datasets'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import { updateDatasetSetting } from '@/service/datasets'
|
import { updateDatasetSetting } from '@/service/datasets'
|
||||||
import AppIcon from '../../base/app-icon'
|
import AppIcon from '../../base/app-icon'
|
||||||
@ -89,38 +88,41 @@ const RenameDatasetModal = ({ show, dataset, onSuccess, onClose }: RenameDataset
|
|||||||
}
|
}
|
||||||
}, [appIcon, description, dataset.id, externalKnowledgeApiId, externalKnowledgeId, name, onClose, onSuccess, t])
|
}, [appIcon, description, dataset.id, externalKnowledgeApiId, externalKnowledgeId, name, onClose, onSuccess, t])
|
||||||
return (
|
return (
|
||||||
<Modal className="w-[520px] max-w-[520px] rounded-xl px-8 py-6" isShow={show} onClose={noop}>
|
<Dialog open={show}>
|
||||||
<div className="flex items-center justify-between pb-2">
|
<DialogContent className="w-full max-w-[520px] overflow-hidden! rounded-xl border-none px-8 py-6 text-left align-middle">
|
||||||
<div className="text-xl leading-[30px] font-medium text-text-primary">{t('title', { ns: 'datasetSettings' })}</div>
|
|
||||||
<div className="cursor-pointer p-2" onClick={onClose}>
|
<div className="flex items-center justify-between pb-2">
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
<div className="text-xl leading-[30px] font-medium text-text-primary">{t('title', { ns: 'datasetSettings' })}</div>
|
||||||
</div>
|
<div className="cursor-pointer p-2" onClick={onClose}>
|
||||||
</div>
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
<div>
|
|
||||||
<div className={cn('flex flex-col py-4')}>
|
|
||||||
<div className="shrink-0 py-2 text-sm leading-[20px] font-medium text-text-primary">
|
|
||||||
{t('form.name', { ns: 'datasetSettings' })}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<AppIcon size="medium" onClick={handleOpenAppIconPicker} className="cursor-pointer" iconType={appIcon.type} icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon} background={appIcon.type === 'image' ? undefined : appIcon.background} imageUrl={appIcon.type === 'image' ? appIcon.url : undefined} showEditIcon />
|
|
||||||
<Input value={name} onChange={e => setName(e.target.value)} className="h-9 grow" placeholder={t('form.namePlaceholder', { ns: 'datasetSettings' }) || ''} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn('flex flex-col py-4')}>
|
<div>
|
||||||
<div className="shrink-0 py-2 text-sm leading-[20px] font-medium text-text-primary">
|
<div className={cn('flex flex-col py-4')}>
|
||||||
{t('form.desc', { ns: 'datasetSettings' })}
|
<div className="shrink-0 py-2 text-sm leading-[20px] font-medium text-text-primary">
|
||||||
|
{t('form.name', { ns: 'datasetSettings' })}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<AppIcon size="medium" onClick={handleOpenAppIconPicker} className="cursor-pointer" iconType={appIcon.type} icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon} background={appIcon.type === 'image' ? undefined : appIcon.background} imageUrl={appIcon.type === 'image' ? appIcon.url : undefined} showEditIcon />
|
||||||
|
<Input value={name} onChange={e => setName(e.target.value)} className="h-9 grow" placeholder={t('form.namePlaceholder', { ns: 'datasetSettings' }) || ''} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className={cn('flex flex-col py-4')}>
|
||||||
<Textarea value={description} onChange={e => setDescription(e.target.value)} className="resize-none" placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''} />
|
<div className="shrink-0 py-2 text-sm leading-[20px] font-medium text-text-primary">
|
||||||
|
{t('form.desc', { ns: 'datasetSettings' })}
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Textarea value={description} onChange={e => setDescription(e.target.value)} className="resize-none" placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex justify-end pt-6">
|
||||||
<div className="flex justify-end pt-6">
|
<Button className="mr-2" onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
<Button className="mr-2" onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
<Button disabled={loading} variant="primary" onClick={onConfirm}>{t('operation.save', { ns: 'common' })}</Button>
|
||||||
<Button disabled={loading} variant="primary" onClick={onConfirm}>{t('operation.save', { ns: 'common' })}</Button>
|
</div>
|
||||||
</div>
|
{showAppIconPicker && (<AppIconPicker onSelect={handleSelectAppIcon} onClose={handleCloseAppIconPicker} />)}
|
||||||
{showAppIconPicker && (<AppIconPicker onSelect={handleSelectAppIcon} onClose={handleCloseAppIconPicker} />)}
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default RenameDatasetModal
|
export default RenameDatasetModal
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
import type { CreateApiKeyResponse } from '@/models/app'
|
import type { CreateApiKeyResponse } from '@/models/app'
|
||||||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import InputCopy from './input-copy'
|
import InputCopy from './input-copy'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
|
|
||||||
@ -22,21 +23,33 @@ const SecretKeyGenerateModal = ({
|
|||||||
}: ISecretKeyGenerateModalProps) => {
|
}: ISecretKeyGenerateModalProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={onClose} title={`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`} className={`px-8 ${className}`}>
|
<Dialog
|
||||||
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
open={isShow}
|
||||||
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
onOpenChange={(open) => {
|
||||||
</div>
|
if (!open)
|
||||||
<p className="mt-1 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.generateTips', { ns: 'appApi' })}</p>
|
onClose()
|
||||||
<div className="my-4">
|
}}
|
||||||
<InputCopy className="w-full" value={newKey?.token} />
|
>
|
||||||
</div>
|
<DialogContent className={cn('w-full max-w-[480px] overflow-hidden! border-none px-8 text-left align-middle', className)}>
|
||||||
<div className="my-4 flex justify-end">
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<Button className={`shrink-0 ${s.w64}`} onClick={onClose}>
|
{`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`}
|
||||||
<span className="text-xs font-medium text-text-secondary">{t('actionMsg.ok', { ns: 'appApi' })}</span>
|
</DialogTitle>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Modal>
|
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
||||||
|
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.generateTips', { ns: 'appApi' })}</p>
|
||||||
|
<div className="my-4">
|
||||||
|
<InputCopy className="w-full" value={newKey?.token} />
|
||||||
|
</div>
|
||||||
|
<div className="my-4 flex justify-end">
|
||||||
|
<Button className={`shrink-0 ${s.w64}`} onClick={onClose}>
|
||||||
|
<span className="text-xs font-medium text-text-secondary">{t('actionMsg.ok', { ns: 'appApi' })}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiDeleteBinLine } from '@remixicon/react'
|
import { RiDeleteBinLine } from '@remixicon/react'
|
||||||
import {
|
import {
|
||||||
useState,
|
useState,
|
||||||
@ -19,7 +21,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import useTimestamp from '@/hooks/use-timestamp'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import {
|
import {
|
||||||
@ -104,77 +105,89 @@ const SecretKeyModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isShow={isShow} onClose={onClose} title={`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`} className={`${s.customModal} flex flex-col px-8`}>
|
<Dialog
|
||||||
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
open={isShow}
|
||||||
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
onOpenChange={(open) => {
|
||||||
</div>
|
if (!open)
|
||||||
<p className="mt-1 shrink-0 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.apiSecretKeyTips', { ns: 'appApi' })}</p>
|
onClose()
|
||||||
{isApiKeysLoading && <div className="mt-4"><Loading /></div>}
|
}}
|
||||||
{
|
>
|
||||||
!!apiKeysList?.data?.length && (
|
<DialogContent className={cn('max-h-[calc(100vh-80px)]! w-full max-w-[800px]! overflow-hidden! border-none text-left align-middle', `${s.customModal} flex flex-col px-8`)}>
|
||||||
<div className="mt-4 flex grow flex-col overflow-hidden">
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<div className="flex h-9 shrink-0 items-center border-b border-divider-regular text-xs font-semibold text-text-tertiary">
|
{`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`}
|
||||||
<div className="w-64 shrink-0 px-3">{t('apiKeyModal.secretKey', { ns: 'appApi' })}</div>
|
</DialogTitle>
|
||||||
<div className="w-[200px] shrink-0 px-3">{t('apiKeyModal.created', { ns: 'appApi' })}</div>
|
|
||||||
<div className="w-[200px] shrink-0 px-3">{t('apiKeyModal.lastUsed', { ns: 'appApi' })}</div>
|
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
||||||
<div className="grow px-3"></div>
|
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grow overflow-auto">
|
<p className="mt-1 shrink-0 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.apiSecretKeyTips', { ns: 'appApi' })}</p>
|
||||||
{apiKeysList.data.map(api => (
|
{isApiKeysLoading && <div className="mt-4"><Loading /></div>}
|
||||||
<div className="flex h-9 items-center border-b border-divider-regular text-sm font-normal text-text-secondary" key={api.id}>
|
{
|
||||||
<div className="w-64 shrink-0 truncate px-3 font-mono">{generateToken(api.token)}</div>
|
!!apiKeysList?.data?.length && (
|
||||||
<div className="w-[200px] shrink-0 truncate px-3">{formatTime(Number(api.created_at), t('dateTimeFormat', { ns: 'appLog' }) as string)}</div>
|
<div className="mt-4 flex grow flex-col overflow-hidden">
|
||||||
<div className="w-[200px] shrink-0 truncate px-3">{api.last_used_at ? formatTime(Number(api.last_used_at), t('dateTimeFormat', { ns: 'appLog' }) as string) : t('never', { ns: 'appApi' })}</div>
|
<div className="flex h-9 shrink-0 items-center border-b border-divider-regular text-xs font-semibold text-text-tertiary">
|
||||||
<div className="flex grow space-x-2 px-3">
|
<div className="w-64 shrink-0 px-3">{t('apiKeyModal.secretKey', { ns: 'appApi' })}</div>
|
||||||
<CopyFeedback content={api.token} />
|
<div className="w-[200px] shrink-0 px-3">{t('apiKeyModal.created', { ns: 'appApi' })}</div>
|
||||||
{isCurrentWorkspaceManager && (
|
<div className="w-[200px] shrink-0 px-3">{t('apiKeyModal.lastUsed', { ns: 'appApi' })}</div>
|
||||||
<ActionButton
|
<div className="grow px-3"></div>
|
||||||
onClick={() => {
|
</div>
|
||||||
setDelKeyId(api.id)
|
<div className="grow overflow-auto">
|
||||||
setShowConfirmDelete(true)
|
{apiKeysList.data.map(api => (
|
||||||
}}
|
<div className="flex h-9 items-center border-b border-divider-regular text-sm font-normal text-text-secondary" key={api.id}>
|
||||||
>
|
<div className="w-64 shrink-0 truncate px-3 font-mono">{generateToken(api.token)}</div>
|
||||||
<RiDeleteBinLine className="h-4 w-4" />
|
<div className="w-[200px] shrink-0 truncate px-3">{formatTime(Number(api.created_at), t('dateTimeFormat', { ns: 'appLog' }) as string)}</div>
|
||||||
</ActionButton>
|
<div className="w-[200px] shrink-0 truncate px-3">{api.last_used_at ? formatTime(Number(api.last_used_at), t('dateTimeFormat', { ns: 'appLog' }) as string) : t('never', { ns: 'appApi' })}</div>
|
||||||
)}
|
<div className="flex grow space-x-2 px-3">
|
||||||
|
<CopyFeedback content={api.token} />
|
||||||
|
{isCurrentWorkspaceManager && (
|
||||||
|
<ActionButton
|
||||||
|
onClick={() => {
|
||||||
|
setDelKeyId(api.id)
|
||||||
|
setShowConfirmDelete(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiDeleteBinLine className="h-4 w-4" />
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
}
|
<div className="flex">
|
||||||
<div className="flex">
|
<Button className={`mt-4 flex shrink-0 ${s.autoWidth}`} onClick={onCreate} disabled={!currentWorkspace || !isCurrentWorkspaceEditor}>
|
||||||
<Button className={`mt-4 flex shrink-0 ${s.autoWidth}`} onClick={onCreate} disabled={!currentWorkspace || !isCurrentWorkspaceEditor}>
|
<PlusIcon className="mr-1 flex h-4 w-4 shrink-0" />
|
||||||
<PlusIcon className="mr-1 flex h-4 w-4 shrink-0" />
|
<div className="text-xs font-medium text-text-secondary">{t('apiKeyModal.createNewSecretKey', { ns: 'appApi' })}</div>
|
||||||
<div className="text-xs font-medium text-text-secondary">{t('apiKeyModal.createNewSecretKey', { ns: 'appApi' })}</div>
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
||||||
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
<AlertDialog
|
||||||
<AlertDialog
|
open={showConfirmDelete}
|
||||||
open={showConfirmDelete}
|
onOpenChange={handleDeleteConfirmOpenChange}
|
||||||
onOpenChange={handleDeleteConfirmOpenChange}
|
>
|
||||||
>
|
<AlertDialogContent>
|
||||||
<AlertDialogContent>
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
||||||
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
|
{t('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}
|
||||||
{t('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}
|
</AlertDialogTitle>
|
||||||
</AlertDialogTitle>
|
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
{t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}
|
||||||
{t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}
|
</AlertDialogDescription>
|
||||||
</AlertDialogDescription>
|
</div>
|
||||||
</div>
|
<AlertDialogActions>
|
||||||
<AlertDialogActions>
|
<AlertDialogCancelButton>
|
||||||
<AlertDialogCancelButton>
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
</AlertDialogCancelButton>
|
||||||
</AlertDialogCancelButton>
|
<AlertDialogConfirmButton onClick={onDel}>
|
||||||
<AlertDialogConfirmButton onClick={onDel}>
|
{t('operation.confirm', { ns: 'common' })}
|
||||||
{t('operation.confirm', { ns: 'common' })}
|
</AlertDialogConfirmButton>
|
||||||
</AlertDialogConfirmButton>
|
</AlertDialogActions>
|
||||||
</AlertDialogActions>
|
</AlertDialogContent>
|
||||||
</AlertDialogContent>
|
</AlertDialog>
|
||||||
</AlertDialog>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { App as AppType } from '@/models/explore'
|
import type { App as AppType } from '@/models/explore'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import AppUnavailable from '@/app/components/base/app-unavailable'
|
import AppUnavailable from '@/app/components/base/app-unavailable'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal/index'
|
|
||||||
import { IS_CLOUD_EDITION } from '@/config'
|
import { IS_CLOUD_EDITION } from '@/config'
|
||||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||||
import { useGetTryAppInfo } from '@/service/use-try-app'
|
import { useGetTryAppInfo } from '@/service/use-try-app'
|
||||||
@ -40,54 +40,59 @@ const TryApp: FC<Props> = ({
|
|||||||
const { data: appDetail, isLoading, isError, error } = useGetTryAppInfo(appId)
|
const { data: appDetail, isLoading, isError, error } = useGetTryAppInfo(appId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow
|
open
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
className="h-[calc(100vh-32px)] max-w-[calc(100vw-32px)] min-w-[1280px] overflow-x-auto p-2"
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
<DialogContent className="h-[calc(100vh-32px)] max-h-none w-full max-w-[calc(100vw-32px)] min-w-[1280px] overflow-hidden overflow-x-auto border-none p-2 text-left align-middle">
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<Loading type="area" />
|
{isLoading ? (
|
||||||
</div>
|
<div className="flex h-full items-center justify-center">
|
||||||
) : isError ? (
|
<Loading type="area" />
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<AppUnavailable className="h-auto w-auto" isUnknownReason={!error} unknownReason={error instanceof Error ? error.message : undefined} />
|
|
||||||
</div>
|
|
||||||
) : !appDetail ? (
|
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<AppUnavailable className="h-auto w-auto" isUnknownReason />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex h-full flex-col">
|
|
||||||
<div className="flex shrink-0 justify-between pl-4">
|
|
||||||
<Tab
|
|
||||||
value={activeType}
|
|
||||||
onChange={setType}
|
|
||||||
disableTry={app ? !isTrialApp : false}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="large"
|
|
||||||
variant="tertiary"
|
|
||||||
className="flex size-7 items-center justify-center rounded-[10px] p-0 text-components-button-tertiary-text"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<span className="i-ri-close-line size-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Main content */}
|
) : isError ? (
|
||||||
<div className="mt-2 flex h-0 grow justify-between space-x-2">
|
<div className="flex h-full items-center justify-center">
|
||||||
{activeType === TypeEnum.TRY ? <App appId={appId} appDetail={appDetail} /> : <Preview appId={appId} appDetail={appDetail} />}
|
<AppUnavailable className="h-auto w-auto" isUnknownReason={!error} unknownReason={error instanceof Error ? error.message : undefined} />
|
||||||
<AppInfo
|
|
||||||
className="w-[360px] shrink-0"
|
|
||||||
appDetail={appDetail}
|
|
||||||
appId={appId}
|
|
||||||
category={category}
|
|
||||||
onCreate={onCreate}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : !appDetail ? (
|
||||||
)}
|
<div className="flex h-full items-center justify-center">
|
||||||
</Modal>
|
<AppUnavailable className="h-auto w-auto" isUnknownReason />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full flex-col">
|
||||||
|
<div className="flex shrink-0 justify-between pl-4">
|
||||||
|
<Tab
|
||||||
|
value={activeType}
|
||||||
|
onChange={setType}
|
||||||
|
disableTry={app ? !isTrialApp : false}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
variant="tertiary"
|
||||||
|
className="flex size-7 items-center justify-center rounded-[10px] p-0 text-components-button-tertiary-text"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<span className="i-ri-close-line size-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/* Main content */}
|
||||||
|
<div className="mt-2 flex h-0 grow justify-between space-x-2">
|
||||||
|
{activeType === TypeEnum.TRY ? <App appId={appId} appDetail={appDetail} /> : <Preview appId={appId} appDetail={appDetail} />}
|
||||||
|
<AppInfo
|
||||||
|
className="w-[360px] shrink-0"
|
||||||
|
appDetail={appDetail}
|
||||||
|
appId={appId}
|
||||||
|
category={category}
|
||||||
|
onCreate={onCreate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(TryApp)
|
export default React.memo(TryApp)
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { LangGeniusVersionResponse } from '@/models/common'
|
import type { LangGeniusVersionResponse } from '@/models/common'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { IS_CE_EDITION } from '@/config'
|
import { IS_CE_EDITION } from '@/config'
|
||||||
import Link from '@/next/link'
|
|
||||||
|
|
||||||
|
import Link from '@/next/link'
|
||||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||||
|
|
||||||
type IAccountSettingProps = {
|
type IAccountSettingProps = {
|
||||||
@ -26,87 +26,92 @@ export default function AccountAbout({
|
|||||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow
|
open
|
||||||
onClose={onCancel}
|
onOpenChange={(open) => {
|
||||||
className="w-[480px]! max-w-[480px]! px-6! py-4!"
|
if (!open)
|
||||||
|
onCancel()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<DialogContent className="w-[calc(100vw-2rem)]! max-w-[480px]! overflow-hidden! border-none px-6! py-4! text-left align-middle">
|
||||||
<div className="absolute top-0 right-0 flex h-8 w-8 cursor-pointer items-center justify-center" onClick={onCancel}>
|
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center gap-4 py-8">
|
|
||||||
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
|
||||||
? (
|
|
||||||
<img
|
|
||||||
src={systemFeatures.branding.workspace_logo}
|
|
||||||
className="block h-7 w-auto object-contain"
|
|
||||||
alt="logo"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: <DifyLogo size="large" className="mx-auto" />}
|
|
||||||
|
|
||||||
<div className="text-center text-xs font-normal text-text-tertiary">
|
<div className="relative">
|
||||||
Version
|
<div className="absolute top-0 right-0 flex h-8 w-8 cursor-pointer items-center justify-center" onClick={onCancel}>
|
||||||
{langGeniusVersionInfo?.current_version}
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary">
|
<div className="flex flex-col items-center gap-4 py-8">
|
||||||
<div>
|
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||||
©
|
? (
|
||||||
{dayjs().year()}
|
<img
|
||||||
{' '}
|
src={systemFeatures.branding.workspace_logo}
|
||||||
LangGenius, Inc., Contributors.
|
className="block h-7 w-auto object-contain"
|
||||||
|
alt="logo"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: <DifyLogo size="large" className="mx-auto" />}
|
||||||
|
|
||||||
|
<div className="text-center text-xs font-normal text-text-tertiary">
|
||||||
|
Version
|
||||||
|
{langGeniusVersionInfo?.current_version}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-text-accent">
|
<div className="flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary">
|
||||||
|
<div>
|
||||||
|
©
|
||||||
|
{dayjs().year()}
|
||||||
|
{' '}
|
||||||
|
LangGenius, Inc., Contributors.
|
||||||
|
</div>
|
||||||
|
<div className="text-text-accent">
|
||||||
|
{
|
||||||
|
IS_CE_EDITION
|
||||||
|
? <Link href="https://github.com/langgenius/dify/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">Open Source License</Link>
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<Link href="https://dify.ai/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</Link>
|
||||||
|
,
|
||||||
|
<Link href="https://dify.ai/terms" target="_blank" rel="noopener noreferrer">Terms of Service</Link>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="-mx-6 mb-4 h-[0.5px] bg-divider-regular" />
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<div className="min-w-0 text-xs font-medium text-text-tertiary">
|
||||||
{
|
{
|
||||||
IS_CE_EDITION
|
isLatest
|
||||||
? <Link href="https://github.com/langgenius/dify/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">Open Source License</Link>
|
? t('about.latestAvailable', { ns: 'common', version: langGeniusVersionInfo.latest_version })
|
||||||
: (
|
: t('about.nowAvailable', { ns: 'common', version: langGeniusVersionInfo.latest_version })
|
||||||
<>
|
}
|
||||||
<Link href="https://dify.ai/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</Link>
|
</div>
|
||||||
,
|
<div className="flex shrink-0 items-center">
|
||||||
<Link href="https://dify.ai/terms" target="_blank" rel="noopener noreferrer">Terms of Service</Link>
|
<Button className="mr-2" size="small">
|
||||||
</>
|
<Link
|
||||||
)
|
href="https://github.com/langgenius/dify/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t('about.changeLog', { ns: 'common' })}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
{
|
||||||
|
!isLatest && !IS_CE_EDITION && (
|
||||||
|
<Button variant="primary" size="small">
|
||||||
|
<Link
|
||||||
|
href={langGeniusVersionInfo.release_notes}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t('about.updateNow', { ns: 'common' })}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="-mx-8 mb-4 h-[0.5px] bg-divider-regular" />
|
</DialogContent>
|
||||||
<div className="flex items-center justify-between">
|
</Dialog>
|
||||||
<div className="text-xs font-medium text-text-tertiary">
|
|
||||||
{
|
|
||||||
isLatest
|
|
||||||
? t('about.latestAvailable', { ns: 'common', version: langGeniusVersionInfo.latest_version })
|
|
||||||
: t('about.nowAvailable', { ns: 'common', version: langGeniusVersionInfo.latest_version })
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button className="mr-2" size="small">
|
|
||||||
<Link
|
|
||||||
href="https://github.com/langgenius/dify/releases"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{t('about.changeLog', { ns: 'common' })}
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
{
|
|
||||||
!isLatest && !IS_CE_EDITION && (
|
|
||||||
<Button variant="primary" size="small">
|
|
||||||
<Link
|
|
||||||
href={langGeniusVersionInfo.release_notes}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{t('about.updateNow', { ns: 'common' })}
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { ApiBasedExtension } from '@/models/common'
|
import type { ApiBasedExtension } from '@/models/common'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
|
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
import { addApiBasedExtension, updateApiBasedExtension } from '@/service/common'
|
import { addApiBasedExtension, updateApiBasedExtension } from '@/service/common'
|
||||||
|
|
||||||
@ -61,45 +60,48 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({ data, onCance
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Modal isShow onClose={noop} wrapperClassName="z-1002" className="w-[640px]! max-w-none! p-8! pb-6!">
|
<Dialog open>
|
||||||
<div className="mb-2 text-xl font-semibold text-text-primary">
|
<DialogContent className="w-[640px]! max-w-none! overflow-hidden! border-none p-8! pb-6! text-left align-middle">
|
||||||
{data.name
|
|
||||||
? t('apiBasedExtension.modal.editTitle', { ns: 'common' })
|
<div className="mb-2 text-xl font-semibold text-text-primary">
|
||||||
: t('apiBasedExtension.modal.title', { ns: 'common' })}
|
{data.name
|
||||||
</div>
|
? t('apiBasedExtension.modal.editTitle', { ns: 'common' })
|
||||||
<div className="py-2">
|
: t('apiBasedExtension.modal.title', { ns: 'common' })}
|
||||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
|
||||||
{t('apiBasedExtension.modal.name.title', { ns: 'common' })}
|
|
||||||
</div>
|
</div>
|
||||||
<input value={localeData.name || ''} onChange={e => handleDataChange('name', e.target.value)} className="block h-9 w-full appearance-none rounded-lg bg-components-input-bg-normal px-3 text-sm text-text-primary outline-hidden" placeholder={t('apiBasedExtension.modal.name.placeholder', { ns: 'common' }) || ''} />
|
<div className="py-2">
|
||||||
</div>
|
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||||
<div className="py-2">
|
{t('apiBasedExtension.modal.name.title', { ns: 'common' })}
|
||||||
<div className="flex h-9 items-center justify-between text-sm font-medium text-text-primary">
|
</div>
|
||||||
{t('apiBasedExtension.modal.apiEndpoint.title', { ns: 'common' })}
|
<input value={localeData.name || ''} onChange={e => handleDataChange('name', e.target.value)} className="block h-9 w-full appearance-none rounded-lg bg-components-input-bg-normal px-3 text-sm text-text-primary outline-hidden" placeholder={t('apiBasedExtension.modal.name.placeholder', { ns: 'common' }) || ''} />
|
||||||
<a href={docLink('/use-dify/workspace/api-extension/api-extension')} target="_blank" rel="noopener noreferrer" className="group flex items-center text-xs font-normal text-text-accent">
|
|
||||||
<BookOpen01 className="mr-1 h-3 w-3" />
|
|
||||||
{t('apiBasedExtension.link', { ns: 'common' })}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<input value={localeData.api_endpoint || ''} onChange={e => handleDataChange('api_endpoint', e.target.value)} className="block h-9 w-full appearance-none rounded-lg bg-components-input-bg-normal px-3 text-sm text-text-primary outline-hidden" placeholder={t('apiBasedExtension.modal.apiEndpoint.placeholder', { ns: 'common' }) || ''} />
|
<div className="py-2">
|
||||||
</div>
|
<div className="flex h-9 items-center justify-between text-sm font-medium text-text-primary">
|
||||||
<div className="py-2">
|
{t('apiBasedExtension.modal.apiEndpoint.title', { ns: 'common' })}
|
||||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
<a href={docLink('/use-dify/workspace/api-extension/api-extension')} target="_blank" rel="noopener noreferrer" className="group flex items-center text-xs font-normal text-text-accent">
|
||||||
{t('apiBasedExtension.modal.apiKey.title', { ns: 'common' })}
|
<BookOpen01 className="mr-1 h-3 w-3" />
|
||||||
|
{t('apiBasedExtension.link', { ns: 'common' })}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<input value={localeData.api_endpoint || ''} onChange={e => handleDataChange('api_endpoint', e.target.value)} className="block h-9 w-full appearance-none rounded-lg bg-components-input-bg-normal px-3 text-sm text-text-primary outline-hidden" placeholder={t('apiBasedExtension.modal.apiEndpoint.placeholder', { ns: 'common' }) || ''} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="py-2">
|
||||||
<input value={localeData.api_key || ''} onChange={e => handleDataChange('api_key', e.target.value)} className="mr-2 block h-9 grow appearance-none rounded-lg bg-components-input-bg-normal px-3 text-sm text-text-primary outline-hidden" placeholder={t('apiBasedExtension.modal.apiKey.placeholder', { ns: 'common' }) || ''} />
|
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||||
|
{t('apiBasedExtension.modal.apiKey.title', { ns: 'common' })}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input value={localeData.api_key || ''} onChange={e => handleDataChange('api_key', e.target.value)} className="mr-2 block h-9 grow appearance-none rounded-lg bg-components-input-bg-normal px-3 text-sm text-text-primary outline-hidden" placeholder={t('apiBasedExtension.modal.apiKey.placeholder', { ns: 'common' }) || ''} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="mt-6 flex items-center justify-end">
|
||||||
<div className="mt-6 flex items-center justify-end">
|
<Button onClick={onCancel} className="mr-2">
|
||||||
<Button onClick={onCancel} className="mr-2">
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
</Button>
|
||||||
</Button>
|
<Button variant="primary" disabled={!localeData.name || !localeData.api_endpoint || !localeData.api_key || loading} onClick={handleSave}>
|
||||||
<Button variant="primary" disabled={!localeData.name || !localeData.api_endpoint || !localeData.api_key || loading} onClick={handleSave}>
|
{t('operation.save', { ns: 'common' })}
|
||||||
{t('operation.save', { ns: 'common' })}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default ApiBasedExtensionModal
|
export default ApiBasedExtensionModal
|
||||||
|
|||||||
@ -9,11 +9,11 @@ import {
|
|||||||
} from '@langgenius/dify-ui/alert-dialog'
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||||
import { useGetModelCredential, useUpdateModelLoadBalancingConfig } from '@/service/use-models'
|
import { useGetModelCredential, useUpdateModelLoadBalancingConfig } from '@/service/use-models'
|
||||||
import { ConfigurationMethodEnum, FormTypeEnum } from '../declarations'
|
import { ConfigurationMethodEnum, FormTypeEnum } from '../declarations'
|
||||||
@ -180,99 +180,103 @@ const ModelLoadBalancingModal = ({ provider, configurateMethod, currentCustomCon
|
|||||||
}, [refetch, onClose])
|
}, [refetch, onClose])
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={Boolean(model) && open}
|
open={Boolean(model) && open}
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
wrapperClassName="z-1002"
|
if (!open)
|
||||||
className="w-[640px] max-w-none px-8 pt-8"
|
onClose?.()
|
||||||
title={(
|
}}
|
||||||
<div className="pb-3 font-semibold">
|
|
||||||
<div className="h-[30px]">
|
|
||||||
{draftConfig?.enabled
|
|
||||||
? t('modelProvider.auth.configLoadBalancing', { ns: 'common' })
|
|
||||||
: t('modelProvider.auth.configModel', { ns: 'common' })}
|
|
||||||
</div>
|
|
||||||
{Boolean(model) && (
|
|
||||||
<div className="flex h-5 items-center">
|
|
||||||
<ModelIcon className="mr-2 shrink-0" provider={provider} modelName={model!.model} />
|
|
||||||
<ModelName className="grow system-md-regular text-text-secondary" modelItem={model!} showModelType showMode showContextSize />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{!draftConfig
|
<DialogContent className="w-[640px] max-w-none overflow-hidden! border-none px-8 pt-8 text-left align-middle">
|
||||||
? <Loading type="area" />
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
: (
|
<div className="pb-3 font-semibold">
|
||||||
<>
|
<div className="h-[30px]">
|
||||||
<div className="py-2">
|
{draftConfig?.enabled
|
||||||
<div className={cn('min-h-16 rounded-xl border bg-components-panel-bg transition-colors', draftConfig.enabled ? 'cursor-pointer border-components-panel-border' : 'cursor-default border-util-colors-blue-blue-600')} onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}>
|
? t('modelProvider.auth.configLoadBalancing', { ns: 'common' })
|
||||||
<div className="flex items-center gap-2 px-[15px] py-3 select-none">
|
: t('modelProvider.auth.configModel', { ns: 'common' })}
|
||||||
<div className="flex h-8 w-8 shrink-0 grow-0 items-center justify-center rounded-lg border border-components-card-border bg-components-card-bg">
|
</div>
|
||||||
{Boolean(model) && (<ModelIcon className="shrink-0" provider={provider} modelName={model!.model} />)}
|
{Boolean(model) && (
|
||||||
</div>
|
<div className="flex h-5 items-center">
|
||||||
<div className="grow">
|
<ModelIcon className="mr-2 shrink-0" provider={provider} modelName={model!.model} />
|
||||||
<div className="text-sm text-text-secondary">
|
<ModelName className="grow system-md-regular text-text-secondary" modelItem={model!} showModelType showMode showContextSize />
|
||||||
{providerFormSchemaPredefined
|
|
||||||
? t('modelProvider.auth.providerManaged', { ns: 'common' })
|
|
||||||
: t('modelProvider.auth.specifyModelCredential', { ns: 'common' })}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-text-tertiary">
|
|
||||||
{providerFormSchemaPredefined
|
|
||||||
? t('modelProvider.auth.providerManagedTip', { ns: 'common' })
|
|
||||||
: t('modelProvider.auth.specifyModelCredentialTip', { ns: 'common' })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!providerFormSchemaPredefined && (<SwitchCredentialInLoadBalancing provider={provider} customModelCredential={customModelCredential ?? initialCustomModelCredential} setCustomModelCredential={setCustomModelCredential} model={model} credentials={available_credentials} onUpdate={handleUpdateWhenSwitchCredential} onRemove={handleUpdateWhenSwitchCredential} />)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{modelCredential && (
|
|
||||||
<ModelLoadBalancingConfigs {...{
|
|
||||||
draftConfig,
|
|
||||||
setDraftConfig,
|
|
||||||
provider,
|
|
||||||
currentCustomConfigurationModelFixedFields: {
|
|
||||||
__model_name: model.model,
|
|
||||||
__model_type: model.model_type,
|
|
||||||
},
|
|
||||||
configurationMethod: model.fetch_from,
|
|
||||||
className: 'mt-2',
|
|
||||||
modelCredential,
|
|
||||||
onUpdate: handleUpdate,
|
|
||||||
onRemove: handleUpdateWhenSwitchCredential,
|
|
||||||
model: {
|
|
||||||
model: model.model,
|
|
||||||
model_type: model.model_type,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
<div className="mt-6 flex items-center justify-between gap-2">
|
{!draftConfig
|
||||||
<div>
|
? <Loading type="area" />
|
||||||
{!providerFormSchemaPredefined && (
|
: (
|
||||||
<Button onClick={() => openConfirmDelete(undefined, { model: model.model, model_type: model.model_type })} className="text-components-button-destructive-secondary-text">
|
<>
|
||||||
{t('modelProvider.auth.removeModel', { ns: 'common' })}
|
<div className="py-2">
|
||||||
</Button>
|
<div className={cn('min-h-16 rounded-xl border bg-components-panel-bg transition-colors', draftConfig.enabled ? 'cursor-pointer border-components-panel-border' : 'cursor-default border-util-colors-blue-blue-600')} onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}>
|
||||||
|
<div className="flex items-center gap-2 px-[15px] py-3 select-none">
|
||||||
|
<div className="flex h-8 w-8 shrink-0 grow-0 items-center justify-center rounded-lg border border-components-card-border bg-components-card-bg">
|
||||||
|
{Boolean(model) && (<ModelIcon className="shrink-0" provider={provider} modelName={model!.model} />)}
|
||||||
|
</div>
|
||||||
|
<div className="grow">
|
||||||
|
<div className="text-sm text-text-secondary">
|
||||||
|
{providerFormSchemaPredefined
|
||||||
|
? t('modelProvider.auth.providerManaged', { ns: 'common' })
|
||||||
|
: t('modelProvider.auth.specifyModelCredential', { ns: 'common' })}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-text-tertiary">
|
||||||
|
{providerFormSchemaPredefined
|
||||||
|
? t('modelProvider.auth.providerManagedTip', { ns: 'common' })
|
||||||
|
: t('modelProvider.auth.specifyModelCredentialTip', { ns: 'common' })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!providerFormSchemaPredefined && (<SwitchCredentialInLoadBalancing provider={provider} customModelCredential={customModelCredential ?? initialCustomModelCredential} setCustomModelCredential={setCustomModelCredential} model={model} credentials={available_credentials} onUpdate={handleUpdateWhenSwitchCredential} onRemove={handleUpdateWhenSwitchCredential} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{modelCredential && (
|
||||||
|
<ModelLoadBalancingConfigs {...{
|
||||||
|
draftConfig,
|
||||||
|
setDraftConfig,
|
||||||
|
provider,
|
||||||
|
currentCustomConfigurationModelFixedFields: {
|
||||||
|
__model_name: model.model,
|
||||||
|
__model_type: model.model_type,
|
||||||
|
},
|
||||||
|
configurationMethod: model.fetch_from,
|
||||||
|
className: 'mt-2',
|
||||||
|
modelCredential,
|
||||||
|
onUpdate: handleUpdate,
|
||||||
|
onRemove: handleUpdateWhenSwitchCredential,
|
||||||
|
model: {
|
||||||
|
model: model.model,
|
||||||
|
model_type: model.model_type,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-x-2">
|
|
||||||
<Button onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
<div className="mt-6 flex items-center justify-between gap-2">
|
||||||
<Button
|
<div>
|
||||||
variant="primary"
|
{!providerFormSchemaPredefined && (
|
||||||
onClick={handleSave}
|
<Button onClick={() => openConfirmDelete(undefined, { model: model.model, model_type: model.model_type })} className="text-components-button-destructive-secondary-text">
|
||||||
disabled={loading
|
{t('modelProvider.auth.removeModel', { ns: 'common' })}
|
||||||
|| (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
|
</Button>
|
||||||
|| isLoading}
|
)}
|
||||||
>
|
</div>
|
||||||
{t('operation.save', { ns: 'common' })}
|
<div className="space-x-2">
|
||||||
</Button>
|
<Button onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={loading
|
||||||
|
|| (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
|
||||||
|
|| isLoading}
|
||||||
|
>
|
||||||
|
{t('operation.save', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
<AlertDialog open={!!deleteModel} onOpenChange={open => !open && closeConfirmDelete()}>
|
<AlertDialog open={!!deleteModel} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { Dependency } from '../../types'
|
import type { Dependency } from '../../types'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { InstallStep } from '../../types'
|
import { InstallStep } from '../../types'
|
||||||
import useHideLogic from '../hooks/use-hide-logic'
|
import useHideLogic from '../hooks/use-hide-logic'
|
||||||
import ReadyToInstall from './ready-to-install'
|
import ReadyToInstall from './ready-to-install'
|
||||||
@ -50,26 +50,31 @@ const InstallBundle: FC<Props> = ({
|
|||||||
}, [step, t])
|
}, [step, t])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={true}
|
open
|
||||||
onClose={foldAnimInto}
|
onOpenChange={(open) => {
|
||||||
className={cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0')}
|
if (!open)
|
||||||
closable
|
foldAnimInto()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
<DialogContent className={cn('relative w-full max-w-[480px] overflow-hidden! text-left align-middle', cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0'))}>
|
||||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
{getTitle()}
|
|
||||||
|
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||||
|
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||||
|
{getTitle()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<ReadyToInstall
|
||||||
<ReadyToInstall
|
step={step}
|
||||||
step={step}
|
onStepChange={setStep}
|
||||||
onStepChange={setStep}
|
onStartToInstall={handleStartToInstall}
|
||||||
onStartToInstall={handleStartToInstall}
|
setIsInstalling={setIsInstalling}
|
||||||
setIsInstalling={setIsInstalling}
|
allPlugins={fromDSLPayload}
|
||||||
allPlugins={fromDSLPayload}
|
onClose={onClose}
|
||||||
onClose={onClose}
|
/>
|
||||||
/>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types'
|
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types'
|
||||||
import type { InstallState } from '@/app/components/plugins/types'
|
import type { InstallState } from '@/app/components/plugins/types'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||||
import { InstallStepFromGitHub } from '../../types'
|
import { InstallStepFromGitHub } from '../../types'
|
||||||
import Installed from '../base/installed'
|
import Installed from '../base/installed'
|
||||||
@ -160,74 +160,80 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={true}
|
open
|
||||||
onClose={foldAnimInto}
|
onOpenChange={(open) => {
|
||||||
className={cn(modalClassName, `shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px]
|
if (!open)
|
||||||
border-components-panel-border bg-components-panel-bg p-0`)}
|
foldAnimInto()
|
||||||
closable
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
<DialogContent className={cn('w-[560px] max-w-none! overflow-hidden! text-left align-middle', cn(modalClassName, `shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px]
|
||||||
<div className="flex grow flex-col items-start gap-1">
|
border-components-panel-border bg-components-panel-bg p-0`))}
|
||||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
>
|
||||||
{getTitle()}
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
</div>
|
|
||||||
<div className="self-stretch system-xs-regular text-text-tertiary">
|
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||||
{!([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step)) && t('installFromGitHub.installNote', { ns: 'plugin' })}
|
<div className="flex grow flex-col items-start gap-1">
|
||||||
|
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||||
|
{getTitle()}
|
||||||
|
</div>
|
||||||
|
<div className="self-stretch system-xs-regular text-text-tertiary">
|
||||||
|
{!([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step)) && t('installFromGitHub.installNote', { ns: 'plugin' })}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
|
||||||
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
|
? (
|
||||||
? (
|
<Installed
|
||||||
<Installed
|
payload={manifest}
|
||||||
payload={manifest}
|
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
|
||||||
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
|
errMsg={errorMsg}
|
||||||
errMsg={errorMsg}
|
onCancel={onClose}
|
||||||
onCancel={onClose}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
: (
|
||||||
: (
|
<div className={`flex flex-col items-start justify-center self-stretch px-6 py-3 ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
|
||||||
<div className={`flex flex-col items-start justify-center self-stretch px-6 py-3 ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
|
{state.step === InstallStepFromGitHub.setUrl && (
|
||||||
{state.step === InstallStepFromGitHub.setUrl && (
|
<SetURL
|
||||||
<SetURL
|
repoUrl={state.repoUrl}
|
||||||
repoUrl={state.repoUrl}
|
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
||||||
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
onNext={handleUrlSubmit}
|
||||||
onNext={handleUrlSubmit}
|
onCancel={onClose}
|
||||||
onCancel={onClose}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
{state.step === InstallStepFromGitHub.selectPackage && (
|
||||||
{state.step === InstallStepFromGitHub.selectPackage && (
|
<SelectPackage
|
||||||
<SelectPackage
|
updatePayload={updatePayload!}
|
||||||
updatePayload={updatePayload!}
|
repoUrl={state.repoUrl}
|
||||||
repoUrl={state.repoUrl}
|
selectedVersion={state.selectedVersion}
|
||||||
selectedVersion={state.selectedVersion}
|
versions={versions}
|
||||||
versions={versions}
|
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: String(item.value) }))}
|
||||||
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: String(item.value) }))}
|
selectedPackage={state.selectedPackage}
|
||||||
selectedPackage={state.selectedPackage}
|
packages={packages}
|
||||||
packages={packages}
|
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: String(item.value) }))}
|
||||||
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: String(item.value) }))}
|
onUploaded={handleUploaded}
|
||||||
onUploaded={handleUploaded}
|
onFailed={handleUploadFail}
|
||||||
onFailed={handleUploadFail}
|
onBack={handleBack}
|
||||||
onBack={handleBack}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
{state.step === InstallStepFromGitHub.readyToInstall && (
|
||||||
{state.step === InstallStepFromGitHub.readyToInstall && (
|
<Loaded
|
||||||
<Loaded
|
updatePayload={updatePayload!}
|
||||||
updatePayload={updatePayload!}
|
uniqueIdentifier={uniqueIdentifier!}
|
||||||
uniqueIdentifier={uniqueIdentifier!}
|
payload={manifest as any}
|
||||||
payload={manifest as any}
|
repoUrl={state.repoUrl}
|
||||||
repoUrl={state.repoUrl}
|
selectedVersion={state.selectedVersion}
|
||||||
selectedVersion={state.selectedVersion}
|
selectedPackage={state.selectedPackage}
|
||||||
selectedPackage={state.selectedPackage}
|
onBack={handleBack}
|
||||||
onBack={handleBack}
|
onStartToInstall={handleStartToInstall}
|
||||||
onStartToInstall={handleStartToInstall}
|
onInstalled={handleInstalled}
|
||||||
onInstalled={handleInstalled}
|
onFailed={handleFailed}
|
||||||
onFailed={handleFailed}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
import type { Dependency, PluginDeclaration } from '../../types'
|
import type { Dependency, PluginDeclaration } from '../../types'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||||
import { InstallStep } from '../../types'
|
import { InstallStep } from '../../types'
|
||||||
import useHideLogic from '../hooks/use-hide-logic'
|
import useHideLogic from '../hooks/use-hide-logic'
|
||||||
@ -86,52 +86,57 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={true}
|
open
|
||||||
onClose={foldAnimInto}
|
onOpenChange={(open) => {
|
||||||
className={cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0')}
|
if (!open)
|
||||||
closable
|
foldAnimInto()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
<DialogContent className={cn('w-[560px] max-w-none! overflow-hidden! text-left align-middle', cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0'))}>
|
||||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
{getTitle()}
|
|
||||||
|
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||||
|
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||||
|
{getTitle()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{step === InstallStep.uploading && (
|
||||||
{step === InstallStep.uploading && (
|
<Uploading
|
||||||
<Uploading
|
isBundle={isBundle}
|
||||||
isBundle={isBundle}
|
file={file}
|
||||||
file={file}
|
onCancel={onClose}
|
||||||
onCancel={onClose}
|
onPackageUploaded={handlePackageUploaded}
|
||||||
onPackageUploaded={handlePackageUploaded}
|
onBundleUploaded={handleBundleUploaded}
|
||||||
onBundleUploaded={handleBundleUploaded}
|
onFailed={handleUploadFail}
|
||||||
onFailed={handleUploadFail}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
{isBundle
|
||||||
{isBundle
|
? (
|
||||||
? (
|
<ReadyToInstallBundle
|
||||||
<ReadyToInstallBundle
|
step={step}
|
||||||
step={step}
|
onStepChange={setStep}
|
||||||
onStepChange={setStep}
|
onStartToInstall={handleStartToInstall}
|
||||||
onStartToInstall={handleStartToInstall}
|
setIsInstalling={setIsInstalling}
|
||||||
setIsInstalling={setIsInstalling}
|
onClose={onClose}
|
||||||
onClose={onClose}
|
allPlugins={dependencies}
|
||||||
allPlugins={dependencies}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
: (
|
||||||
: (
|
<ReadyToInstallPackage
|
||||||
<ReadyToInstallPackage
|
step={step}
|
||||||
step={step}
|
onStepChange={setStep}
|
||||||
onStepChange={setStep}
|
onStartToInstall={handleStartToInstall}
|
||||||
onStartToInstall={handleStartToInstall}
|
setIsInstalling={setIsInstalling}
|
||||||
setIsInstalling={setIsInstalling}
|
onClose={onClose}
|
||||||
onClose={onClose}
|
uniqueIdentifier={uniqueIdentifier}
|
||||||
uniqueIdentifier={uniqueIdentifier}
|
manifest={manifest}
|
||||||
manifest={manifest}
|
errorMsg={errorMsg}
|
||||||
errorMsg={errorMsg}
|
onError={setErrorMsg}
|
||||||
onError={setErrorMsg}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
import type { Dependency, Plugin, PluginManifestInMarket } from '../../types'
|
import type { Dependency, Plugin, PluginManifestInMarket } from '../../types'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { InstallStep } from '../../types'
|
import { InstallStep } from '../../types'
|
||||||
import Installed from '../base/installed'
|
import Installed from '../base/installed'
|
||||||
import useHideLogic from '../hooks/use-hide-logic'
|
import useHideLogic from '../hooks/use-hide-logic'
|
||||||
@ -70,60 +70,64 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
|||||||
}, [setIsInstalling])
|
}, [setIsInstalling])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={true}
|
open
|
||||||
onClose={foldAnimInto}
|
onOpenChange={(open) => {
|
||||||
wrapperClassName="z-9999"
|
if (!open)
|
||||||
className={cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0')}
|
foldAnimInto()
|
||||||
closable
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
<DialogContent className={cn('w-[560px] max-w-none! overflow-hidden! text-left align-middle', cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0'))}>
|
||||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
{getTitle()}
|
|
||||||
|
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||||
|
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||||
|
{getTitle()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{
|
||||||
{
|
isBundle
|
||||||
isBundle
|
? (
|
||||||
? (
|
<ReadyToInstallBundle
|
||||||
<ReadyToInstallBundle
|
step={step}
|
||||||
step={step}
|
onStepChange={setStep}
|
||||||
onStepChange={setStep}
|
onStartToInstall={handleStartToInstall}
|
||||||
onStartToInstall={handleStartToInstall}
|
setIsInstalling={setIsInstalling}
|
||||||
setIsInstalling={setIsInstalling}
|
onClose={onClose}
|
||||||
onClose={onClose}
|
allPlugins={dependencies!}
|
||||||
allPlugins={dependencies!}
|
isFromMarketPlace
|
||||||
isFromMarketPlace
|
/>
|
||||||
/>
|
)
|
||||||
)
|
: (
|
||||||
: (
|
<>
|
||||||
<>
|
{
|
||||||
{
|
step === InstallStep.readyToInstall && (
|
||||||
step === InstallStep.readyToInstall && (
|
<Install
|
||||||
<Install
|
uniqueIdentifier={uniqueIdentifier}
|
||||||
uniqueIdentifier={uniqueIdentifier}
|
payload={manifest!}
|
||||||
payload={manifest!}
|
onCancel={onClose}
|
||||||
onCancel={onClose}
|
onInstalled={handleInstalled}
|
||||||
onInstalled={handleInstalled}
|
onFailed={handleFailed}
|
||||||
onFailed={handleFailed}
|
onStartToInstall={handleStartToInstall}
|
||||||
onStartToInstall={handleStartToInstall}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
}
|
||||||
}
|
{
|
||||||
{
|
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
|
||||||
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
|
<Installed
|
||||||
<Installed
|
payload={manifest!}
|
||||||
payload={manifest!}
|
isMarketPayload
|
||||||
isMarketPayload
|
isFailed={step === InstallStep.installFailed}
|
||||||
isFailed={step === InstallStep.installFailed}
|
errMsg={errorMsg}
|
||||||
errMsg={errorMsg}
|
onCancel={onSuccess}
|
||||||
onCancel={onSuccess}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
}
|
||||||
}
|
</>
|
||||||
</>
|
)
|
||||||
)
|
}
|
||||||
}
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,11 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import SchemaModal from '../schema-modal'
|
import SchemaModal from '../schema-modal'
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({
|
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||||
children,
|
open === false ? null : <>{children}</>,
|
||||||
isShow,
|
DialogContent: ({ children }: { children: React.ReactNode }) => <div data-testid="modal">{children}</div>,
|
||||||
}: {
|
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
children: React.ReactNode
|
|
||||||
isShow: boolean
|
|
||||||
}) => isShow ? <div data-testid="modal">{children}</div> : null,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor', () => ({
|
vi.mock('@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor', () => ({
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import type { UseMutationResult } from '@tanstack/react-query'
|
|||||||
import type { FC, ReactNode } from 'react'
|
import type { FC, ReactNode } from 'react'
|
||||||
import type { Plugin } from '../types'
|
import type { Plugin } from '../types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Card from '@/app/components/plugins/card'
|
import Card from '@/app/components/plugins/card'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -33,45 +33,52 @@ const PluginMutationModal: FC<Props> = ({
|
|||||||
modalBottomLeft,
|
modalBottomLeft,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={true}
|
open
|
||||||
onClose={onCancel}
|
onOpenChange={(open) => {
|
||||||
className="min-w-[560px]"
|
if (!open)
|
||||||
closable
|
onCancel()
|
||||||
title={modelTitle}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-3 mb-2 system-md-regular text-text-secondary">
|
<DialogContent className="w-full min-w-[560px] overflow-hidden! border-none text-left align-middle">
|
||||||
{description}
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
</div>
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
{modelTitle}
|
||||||
<Card
|
</DialogTitle>
|
||||||
installed={mutation.isSuccess}
|
|
||||||
payload={plugin}
|
<div className="mt-3 mb-2 system-md-regular text-text-secondary">
|
||||||
className="w-full"
|
{description}
|
||||||
titleLeft={cardTitleLeft}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 self-stretch pt-5">
|
|
||||||
<div>
|
|
||||||
{modalBottomLeft}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto flex gap-2">
|
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||||
{!mutation.isPending && (
|
<Card
|
||||||
<Button onClick={onCancel}>
|
installed={mutation.isSuccess}
|
||||||
{cancelButtonText}
|
payload={plugin}
|
||||||
|
className="w-full"
|
||||||
|
titleLeft={cardTitleLeft}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 self-stretch pt-5">
|
||||||
|
<div>
|
||||||
|
{modalBottomLeft}
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto flex gap-2">
|
||||||
|
{!mutation.isPending && (
|
||||||
|
<Button onClick={onCancel}>
|
||||||
|
{cancelButtonText}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
loading={mutation.isPending}
|
||||||
|
onClick={mutate}
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
>
|
||||||
|
{confirmButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</div>
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
loading={mutation.isPending}
|
|
||||||
onClick={mutate}
|
|
||||||
disabled={mutation.isPending}
|
|
||||||
>
|
|
||||||
{confirmButtonText}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,17 +2,19 @@ import { render, screen } from '@testing-library/react'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
vi.mock('../../../base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({ children, title, isShow }: { children: React.ReactNode, title: string, isShow: boolean }) => (
|
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) => (
|
||||||
isShow
|
open !== false
|
||||||
? (
|
? (
|
||||||
<div data-testid="modal">
|
<div data-testid="modal">
|
||||||
<div data-testid="modal-title">{title}</div>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
),
|
),
|
||||||
|
DialogContent: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||||
|
DialogTitle: ({ children }: { children: React.ReactNode }) => <div data-testid="modal-title">{children}</div>,
|
||||||
|
DialogCloseButton: () => <button type="button">Close</button>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../../base/key-value-item', () => ({
|
vi.mock('../../base/key-value-item', () => ({
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '../../base/modal'
|
|
||||||
import KeyValueItem from '../base/key-value-item'
|
import KeyValueItem from '../base/key-value-item'
|
||||||
import { convertRepoToUrl } from '../install-plugin/utils'
|
import { convertRepoToUrl } from '../install-plugin/utils'
|
||||||
|
|
||||||
@ -23,19 +23,26 @@ const PlugInfo: FC<Props> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const labelWidthClassName = 'w-[96px]'
|
const labelWidthClassName = 'w-[96px]'
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t(`${i18nPrefix}.title`, { ns: 'plugin' })}
|
open
|
||||||
className="w-[480px]"
|
onOpenChange={(open) => {
|
||||||
isShow
|
if (!open)
|
||||||
onClose={onHide}
|
onHide()
|
||||||
closable
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-5 space-y-3">
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||||
{repository && <KeyValueItem label={t(`${i18nPrefix}.repository`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={`${convertRepoToUrl(repository)}`} valueMaxWidthClassName="max-w-[190px]" />}
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
{release && <KeyValueItem label={t(`${i18nPrefix}.release`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={release} />}
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
{packageName && <KeyValueItem label={t(`${i18nPrefix}.packageName`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={packageName} />}
|
{t(`${i18nPrefix}.title`, { ns: 'plugin' })}
|
||||||
</div>
|
</DialogTitle>
|
||||||
</Modal>
|
|
||||||
|
<div className="mt-5 space-y-3">
|
||||||
|
{repository && <KeyValueItem label={t(`${i18nPrefix}.repository`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={`${convertRepoToUrl(repository)}`} valueMaxWidthClassName="max-w-[190px]" />}
|
||||||
|
{release && <KeyValueItem label={t(`${i18nPrefix}.release`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={release} />}
|
||||||
|
{packageName && <KeyValueItem label={t(`${i18nPrefix}.packageName`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={packageName} />}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(PlugInfo)
|
export default React.memo(PlugInfo)
|
||||||
|
|||||||
@ -14,28 +14,25 @@ const mockSystemFeatures = { enable_marketplace: true }
|
|||||||
const render = (ui: ReactElement) =>
|
const render = (ui: ReactElement) =>
|
||||||
renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures })
|
renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures })
|
||||||
|
|
||||||
// Mock Modal component
|
let mockDialogOnOpenChange: ((open: boolean) => void) | undefined
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
|
||||||
default: ({ children, isShow, onClose, closable, className }: {
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
|
Dialog: ({ children, open, onOpenChange }: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
isShow: boolean
|
open?: boolean
|
||||||
onClose: () => void
|
onOpenChange?: (open: boolean) => void
|
||||||
closable?: boolean
|
|
||||||
className?: string
|
|
||||||
}) => {
|
}) => {
|
||||||
if (!isShow)
|
mockDialogOnOpenChange = onOpenChange
|
||||||
return null
|
return open === false ? null : <>{children}</>
|
||||||
return (
|
|
||||||
<div data-testid="modal" className={className}>
|
|
||||||
{closable && (
|
|
||||||
<button data-testid="modal-close" onClick={onClose}>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
DialogContent: ({ children, className }: { children: React.ReactNode, className?: string }) => (
|
||||||
|
<div data-testid="modal" className={className}>{children}</div>
|
||||||
|
),
|
||||||
|
DialogCloseButton: () => (
|
||||||
|
<button data-testid="modal-close" onClick={() => mockDialogOnOpenChange?.(false)}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Mock OptionCard component
|
// Mock OptionCard component
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import type { FC } from 'react'
|
|||||||
import type { AutoUpdateConfig } from './auto-update-setting/types'
|
import type { AutoUpdateConfig } from './auto-update-setting/types'
|
||||||
import type { Permissions, ReferenceSetting } from '@/app/components/plugins/types'
|
import type { Permissions, ReferenceSetting } from '@/app/components/plugins/types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { PermissionType } from '@/app/components/plugins/types'
|
import { PermissionType } from '@/app/components/plugins/types'
|
||||||
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||||
@ -53,59 +53,64 @@ const PluginSettingModal: FC<Props> = ({
|
|||||||
}, [onHide, onSave, tempAutoUpdateConfig, tempPrivilege])
|
}, [onHide, onSave, tempAutoUpdateConfig, tempPrivilege])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow
|
open
|
||||||
onClose={onHide}
|
onOpenChange={(open) => {
|
||||||
closable
|
if (!open)
|
||||||
className="w-[620px] max-w-[620px] p-0!"
|
onHide()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="shadows-shadow-xl flex w-full flex-col items-start rounded-2xl border border-components-panel-border bg-components-panel-bg">
|
<DialogContent className="w-[620px] max-w-[620px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
<span className="self-stretch title-2xl-semi-bold text-text-primary">{t(`${i18nPrefix}.title`, { ns: 'plugin' })}</span>
|
|
||||||
</div>
|
<div className="shadows-shadow-xl flex w-full flex-col items-start rounded-2xl border border-components-panel-border bg-components-panel-bg">
|
||||||
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||||
{[
|
<span className="self-stretch title-2xl-semi-bold text-text-primary">{t(`${i18nPrefix}.title`, { ns: 'plugin' })}</span>
|
||||||
{ title: t(`${i18nPrefix}.whoCanInstall`, { ns: 'plugin' }), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
|
</div>
|
||||||
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
||||||
].map(({ title, key, value }) => (
|
{[
|
||||||
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
{ title: t(`${i18nPrefix}.whoCanInstall`, { ns: 'plugin' }), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
|
||||||
<Label label={title} />
|
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
||||||
<div className="flex w-full items-start justify-between gap-2">
|
].map(({ title, key, value }) => (
|
||||||
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
||||||
<OptionCard
|
<Label label={title} />
|
||||||
key={option}
|
<div className="flex w-full items-start justify-between gap-2">
|
||||||
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
||||||
onSelect={() => handlePrivilegeChange(key)(option)}
|
<OptionCard
|
||||||
selected={value === option}
|
key={option}
|
||||||
className="flex-1"
|
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
||||||
/>
|
onSelect={() => handlePrivilegeChange(key)(option)}
|
||||||
))}
|
selected={value === option}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
|
{
|
||||||
|
enable_marketplace && (
|
||||||
|
<AutoUpdateSetting payload={tempAutoUpdateConfig} onChange={setTempAutoUpdateConfig} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div className="flex h-[76px] items-center justify-end gap-2 self-stretch p-6 pt-5">
|
||||||
|
<Button
|
||||||
|
className="min-w-[72px]"
|
||||||
|
onClick={onHide}
|
||||||
|
>
|
||||||
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="min-w-[72px]"
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
{t('operation.save', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
</DialogContent>
|
||||||
enable_marketplace && (
|
</Dialog>
|
||||||
<AutoUpdateSetting payload={tempAutoUpdateConfig} onChange={setTempAutoUpdateConfig} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<div className="flex h-[76px] items-center justify-end gap-2 self-stretch p-6 pt-5">
|
|
||||||
<Button
|
|
||||||
className="min-w-[72px]"
|
|
||||||
onClick={onHide}
|
|
||||||
>
|
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="min-w-[72px]"
|
|
||||||
variant="primary"
|
|
||||||
onClick={handleSave}
|
|
||||||
>
|
|
||||||
{t('operation.save', { ns: 'common' })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,12 @@ vi.mock('@/app/components/workflow/store', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({ children, isShow }: { children: React.ReactNode, isShow: boolean }) =>
|
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||||
isShow ? <div data-testid="modal">{children}</div> : null,
|
open === false ? null : <>{children}</>,
|
||||||
|
DialogContent: ({ children, className }: { children: React.ReactNode, className?: string }) => (
|
||||||
|
<div data-testid="modal" className={className}>{children}</div>
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@langgenius/dify-ui/button', () => ({
|
vi.mock('@langgenius/dify-ui/button', () => ({
|
||||||
|
|||||||
@ -121,18 +121,14 @@ vi.mock('@langgenius/dify-ui/button', () => ({
|
|||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
default: ({ children, isShow, _onClose, className }: PropsWithChildren<{
|
Dialog: ({ children, open }: PropsWithChildren<{ open?: boolean }>) =>
|
||||||
isShow: boolean
|
open === false ? null : <>{children}</>,
|
||||||
_onClose: () => void
|
DialogContent: ({ children, className }: PropsWithChildren<{ className?: string }>) => (
|
||||||
className?: string
|
<div data-testid="modal" className={className}>
|
||||||
}>) => isShow
|
{children}
|
||||||
? (
|
</div>
|
||||||
<div data-testid="modal" className={className}>
|
),
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/workflow/constants', () => ({
|
vi.mock('@/app/components/workflow/constants', () => ({
|
||||||
|
|||||||
@ -31,13 +31,13 @@ describe('VersionMismatchModal', () => {
|
|||||||
it('should render dialog when isShow is true', () => {
|
it('should render dialog when isShow is true', () => {
|
||||||
render(<VersionMismatchModal {...defaultProps} />)
|
render(<VersionMismatchModal {...defaultProps} />)
|
||||||
|
|
||||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not render dialog when isShow is false', () => {
|
it('should not render dialog when isShow is false', () => {
|
||||||
render(<VersionMismatchModal {...defaultProps} isShow={false} />)
|
render(<VersionMismatchModal {...defaultProps} isShow={false} />)
|
||||||
|
|
||||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render error title', () => {
|
it('should render error title', () => {
|
||||||
|
|||||||
@ -2,14 +2,13 @@
|
|||||||
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
||||||
import type { IconInfo } from '@/models/datasets'
|
import type { IconInfo } from '@/models/datasets'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
|
|
||||||
@ -77,71 +76,70 @@ const PublishAsKnowledgePipelineModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog open>
|
||||||
isShow
|
<DialogContent className="relative w-full max-w-[480px]! overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
onClose={noop}
|
|
||||||
className="relative w-[520px]! p-0!"
|
<div className="relative flex items-center p-6 pr-14 pb-3 title-2xl-semi-bold text-text-primary">
|
||||||
>
|
{t('common.publishAs', { ns: 'pipeline' })}
|
||||||
<div className="relative flex items-center p-6 pr-14 pb-3 title-2xl-semi-bold text-text-primary">
|
<div
|
||||||
{t('common.publishAs', { ns: 'pipeline' })}
|
data-testid="publish-modal-close-btn"
|
||||||
<div
|
className="absolute top-5 right-5 flex h-8 w-8 cursor-pointer items-center justify-center"
|
||||||
data-testid="publish-modal-close-btn"
|
onClick={onCancel}
|
||||||
className="absolute top-5 right-5 flex h-8 w-8 cursor-pointer items-center justify-center"
|
>
|
||||||
onClick={onCancel}
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
>
|
</div>
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="px-6 py-3">
|
||||||
<div className="px-6 py-3">
|
<div className="mb-5 flex">
|
||||||
<div className="mb-5 flex">
|
<div className="mr-3 grow">
|
||||||
<div className="mr-3 grow">
|
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||||
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
{t('common.publishAsPipeline.name', { ns: 'pipeline' })}
|
||||||
{t('common.publishAsPipeline.name', { ns: 'pipeline' })}
|
</div>
|
||||||
|
<Input
|
||||||
|
value={pipelineName}
|
||||||
|
onChange={e => setPipelineName(e.target.value)}
|
||||||
|
placeholder={t('common.publishAsPipeline.namePlaceholder', { ns: 'pipeline' }) || ''}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<AppIcon
|
||||||
value={pipelineName}
|
size="xxl"
|
||||||
onChange={e => setPipelineName(e.target.value)}
|
onClick={() => { setShowAppIconPicker(true) }}
|
||||||
placeholder={t('common.publishAsPipeline.namePlaceholder', { ns: 'pipeline' }) || ''}
|
className="mt-2 shrink-0 cursor-pointer"
|
||||||
|
iconType={pipelineIcon?.icon_type}
|
||||||
|
icon={pipelineIcon?.icon}
|
||||||
|
background={pipelineIcon?.icon_background}
|
||||||
|
imageUrl={pipelineIcon?.icon_url}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AppIcon
|
<div>
|
||||||
size="xxl"
|
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||||
onClick={() => { setShowAppIconPicker(true) }}
|
{t('common.publishAsPipeline.description', { ns: 'pipeline' })}
|
||||||
className="mt-2 shrink-0 cursor-pointer"
|
</div>
|
||||||
iconType={pipelineIcon?.icon_type}
|
<Textarea
|
||||||
icon={pipelineIcon?.icon}
|
className="resize-none"
|
||||||
background={pipelineIcon?.icon_background}
|
placeholder={t('common.publishAsPipeline.descriptionPlaceholder', { ns: 'pipeline' }) || ''}
|
||||||
imageUrl={pipelineIcon?.icon_url}
|
value={description}
|
||||||
/>
|
onChange={e => setDescription(e.target.value)}
|
||||||
</div>
|
/>
|
||||||
<div>
|
|
||||||
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
|
||||||
{t('common.publishAsPipeline.description', { ns: 'pipeline' })}
|
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
|
||||||
className="resize-none"
|
|
||||||
placeholder={t('common.publishAsPipeline.descriptionPlaceholder', { ns: 'pipeline' }) || ''}
|
|
||||||
value={description}
|
|
||||||
onChange={e => setDescription(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-center justify-end px-6 py-5">
|
||||||
<div className="flex items-center justify-end px-6 py-5">
|
<Button
|
||||||
<Button
|
className="mr-2"
|
||||||
className="mr-2"
|
onClick={onCancel}
|
||||||
onClick={onCancel}
|
>
|
||||||
>
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
disabled={!pipelineName?.trim() || confirmDisabled}
|
||||||
disabled={!pipelineName?.trim() || confirmDisabled}
|
variant="primary"
|
||||||
variant="primary"
|
onClick={() => handleConfirm()}
|
||||||
onClick={() => handleConfirm()}
|
>
|
||||||
>
|
{t('common.publish', { ns: 'workflow' })}
|
||||||
{t('common.publish', { ns: 'workflow' })}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
{showAppIconPicker && (
|
{showAppIconPicker && (
|
||||||
<AppIconPicker
|
<AppIconPicker
|
||||||
onSelect={handleSelectIcon}
|
onSelect={handleSelectIcon}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import {
|
import {
|
||||||
RiAlertFill,
|
RiAlertFill,
|
||||||
RiCloseLine,
|
RiCloseLine,
|
||||||
@ -9,7 +10,6 @@ import {
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { useUpdateDSLModal } from '../hooks/use-update-dsl-modal'
|
import { useUpdateDSLModal } from '../hooks/use-update-dsl-modal'
|
||||||
import VersionMismatchModal from './version-mismatch-modal'
|
import VersionMismatchModal from './version-mismatch-modal'
|
||||||
|
|
||||||
@ -39,66 +39,71 @@ const UpdateDSLModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog
|
||||||
className="w-[520px] rounded-2xl p-6"
|
open={show}
|
||||||
isShow={show}
|
onOpenChange={(open) => {
|
||||||
onClose={onCancel}
|
if (!open)
|
||||||
|
onCancel()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! rounded-2xl border-none p-6 text-left align-middle">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('common.importDSL', { ns: 'workflow' })}</div>
|
|
||||||
<div className="flex h-[22px] w-[22px] cursor-pointer items-center justify-center" onClick={onCancel}>
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
<div className="title-2xl-semi-bold text-text-primary">{t('common.importDSL', { ns: 'workflow' })}</div>
|
||||||
</div>
|
<div className="flex h-[22px] w-[22px] cursor-pointer items-center justify-center" onClick={onCancel}>
|
||||||
</div>
|
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||||
<div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs">
|
|
||||||
<div className="absolute top-0 left-0 h-full w-full bg-toast-warning-bg opacity-40" />
|
|
||||||
<div className="flex items-start justify-center p-1">
|
|
||||||
<RiAlertFill className="h-4 w-4 shrink-0 text-text-warning-secondary" />
|
|
||||||
</div>
|
|
||||||
<div className="flex grow flex-col items-start gap-0.5 py-1">
|
|
||||||
<div className="system-xs-medium whitespace-pre-line text-text-primary">{t('common.importDSLTip', { ns: 'workflow' })}</div>
|
|
||||||
<div className="flex items-start gap-1 self-stretch pt-1 pb-0.5">
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="secondary"
|
|
||||||
className="z-1000"
|
|
||||||
onClick={onBackup}
|
|
||||||
>
|
|
||||||
<RiFileDownloadLine className="h-3.5 w-3.5 text-components-button-secondary-text" />
|
|
||||||
<div className="flex items-center justify-center gap-1 px-[3px]">
|
|
||||||
{t('common.backupCurrentDraft', { ns: 'workflow' })}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs">
|
||||||
<div>
|
<div className="absolute top-0 left-0 h-full w-full bg-toast-warning-bg opacity-40" />
|
||||||
<div className="pt-2 system-md-semibold text-text-primary">
|
<div className="flex items-start justify-center p-1">
|
||||||
{t('common.chooseDSL', { ns: 'workflow' })}
|
<RiAlertFill className="h-4 w-4 shrink-0 text-text-warning-secondary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex grow flex-col items-start gap-0.5 py-1">
|
||||||
|
<div className="system-xs-medium whitespace-pre-line text-text-primary">{t('common.importDSLTip', { ns: 'workflow' })}</div>
|
||||||
|
<div className="flex items-start gap-1 self-stretch pt-1 pb-0.5">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="secondary"
|
||||||
|
className="z-1000"
|
||||||
|
onClick={onBackup}
|
||||||
|
>
|
||||||
|
<RiFileDownloadLine className="h-3.5 w-3.5 text-components-button-secondary-text" />
|
||||||
|
<div className="flex items-center justify-center gap-1 px-[3px]">
|
||||||
|
{t('common.backupCurrentDraft', { ns: 'workflow' })}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-col items-start justify-center gap-4 self-stretch py-4">
|
<div>
|
||||||
<Uploader
|
<div className="pt-2 system-md-semibold text-text-primary">
|
||||||
file={currentFile}
|
{t('common.chooseDSL', { ns: 'workflow' })}
|
||||||
updateFile={handleFile}
|
</div>
|
||||||
className="mt-0! w-full"
|
<div className="flex w-full flex-col items-start justify-center gap-4 self-stretch py-4">
|
||||||
accept=".pipeline"
|
<Uploader
|
||||||
displayName="PIPELINE"
|
file={currentFile}
|
||||||
/>
|
updateFile={handleFile}
|
||||||
|
className="mt-0! w-full"
|
||||||
|
accept=".pipeline"
|
||||||
|
displayName="PIPELINE"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||||
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<Button
|
||||||
<Button
|
disabled={!currentFile || loading}
|
||||||
disabled={!currentFile || loading}
|
variant="primary"
|
||||||
variant="primary"
|
tone="destructive"
|
||||||
tone="destructive"
|
onClick={handleImport}
|
||||||
onClick={handleImport}
|
loading={loading}
|
||||||
loading={loading}
|
>
|
||||||
>
|
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
||||||
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
<VersionMismatchModal
|
<VersionMismatchModal
|
||||||
isShow={showErrorModal}
|
isShow={showErrorModal}
|
||||||
versions={versions}
|
versions={versions}
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
import type { MouseEventHandler } from 'react'
|
import type { MouseEventHandler } from 'react'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
|
|
||||||
type VersionMismatchModalProps = {
|
type VersionMismatchModalProps = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@ -22,32 +29,36 @@ const VersionMismatchModal = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<AlertDialog
|
||||||
isShow={isShow}
|
open={isShow}
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
className="w-[480px]"
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
<AlertDialogContent className="w-[480px] overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||||
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</AlertDialogTitle>
|
||||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
<AlertDialogDescription render={<div />} className="flex grow flex-col system-md-regular text-text-secondary">
|
||||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||||
<br />
|
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||||
<div>
|
<br />
|
||||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
<div>
|
||||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||||
</div>
|
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||||
<div>
|
</div>
|
||||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
<div>
|
||||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||||
</div>
|
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||||
|
</div>
|
||||||
|
</AlertDialogDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<AlertDialogActions>
|
||||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
<AlertDialogCancelButton variant="secondary">{t('newApp.Cancel', { ns: 'app' })}</AlertDialogCancelButton>
|
||||||
<Button variant="secondary" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<AlertDialogConfirmButton onClick={onConfirm}>{t('newApp.Confirm', { ns: 'app' })}</AlertDialogConfirmButton>
|
||||||
<Button variant="primary" tone="destructive" onClick={onConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
</AlertDialogActions>
|
||||||
</div>
|
</AlertDialogContent>
|
||||||
</Modal>
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { SiteInfo } from '@/models/share'
|
import type { SiteInfo } from '@/models/share'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { appDefaultIconBackground } from '@/config'
|
import { appDefaultIconBackground } from '@/config'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -17,37 +17,42 @@ const InfoModal = ({
|
|||||||
data,
|
data,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={isShow}
|
open={isShow}
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
className="max-w-[400px] min-w-[400px] p-0!"
|
if (!open)
|
||||||
closable
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className={cn('flex flex-col items-center gap-4 px-4 pt-10 pb-8')}>
|
<DialogContent className="w-full max-w-[400px] min-w-[400px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||||
<AppIcon
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
size="xxl"
|
|
||||||
iconType={data?.icon_type}
|
<div className={cn('flex flex-col items-center gap-4 px-4 pt-10 pb-8')}>
|
||||||
icon={data?.icon}
|
<AppIcon
|
||||||
background={data?.icon_background || appDefaultIconBackground}
|
size="xxl"
|
||||||
imageUrl={data?.icon_url}
|
iconType={data?.icon_type}
|
||||||
/>
|
icon={data?.icon}
|
||||||
<div className="system-xl-semibold text-text-secondary">{data?.title}</div>
|
background={data?.icon_background || appDefaultIconBackground}
|
||||||
<div className="system-xs-regular text-text-tertiary">
|
imageUrl={data?.icon_url}
|
||||||
{/* copyright */}
|
/>
|
||||||
{data?.copyright && (
|
<div className="system-xl-semibold text-text-secondary">{data?.title}</div>
|
||||||
<div>
|
<div className="system-xs-regular text-text-tertiary">
|
||||||
©
|
{/* copyright */}
|
||||||
{(new Date()).getFullYear()}
|
{data?.copyright && (
|
||||||
{' '}
|
<div>
|
||||||
{data?.copyright}
|
©
|
||||||
</div>
|
{(new Date()).getFullYear()}
|
||||||
)}
|
{' '}
|
||||||
{data?.custom_disclaimer && (
|
{data?.copyright}
|
||||||
<div className="mt-2">{data.custom_disclaimer}</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{data?.custom_disclaimer && (
|
||||||
|
<div className="mt-2">{data.custom_disclaimer}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,11 @@ import type {
|
|||||||
MCPServerDetail,
|
MCPServerDetail,
|
||||||
} from '@/app/components/tools/types'
|
} from '@/app/components/tools/types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import MCPServerParamItem from '@/app/components/tools/mcp/mcp-server-param-item'
|
import MCPServerParamItem from '@/app/components/tools/mcp/mcp-server-param-item'
|
||||||
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||||
@ -20,11 +19,19 @@ import {
|
|||||||
|
|
||||||
type ModalProps = {
|
type ModalProps = {
|
||||||
appID: string
|
appID: string
|
||||||
latestParams?: any[]
|
latestParams?: MCPServerParam[]
|
||||||
data?: MCPServerDetail
|
data?: MCPServerDetail
|
||||||
show: boolean
|
show: boolean
|
||||||
onHide: () => void
|
onHide: () => void
|
||||||
appInfo?: any
|
appInfo?: {
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MCPServerParam = {
|
||||||
|
variable?: string
|
||||||
|
label?: string
|
||||||
|
type?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const MCPServerModal = ({
|
const MCPServerModal = ({
|
||||||
@ -42,7 +49,7 @@ const MCPServerModal = ({
|
|||||||
|
|
||||||
const defaultDescription = data?.description || appInfo?.description || ''
|
const defaultDescription = data?.description || appInfo?.description || ''
|
||||||
const [description, setDescription] = React.useState(defaultDescription)
|
const [description, setDescription] = React.useState(defaultDescription)
|
||||||
const [params, setParams] = React.useState(data?.parameters || {})
|
const [params, setParams] = React.useState<Record<string, string>>(data?.parameters || {})
|
||||||
|
|
||||||
const handleParamChange = (variable: string, value: string) => {
|
const handleParamChange = (variable: string, value: string) => {
|
||||||
setParams(prev => ({
|
setParams(prev => ({
|
||||||
@ -52,10 +59,14 @@ const MCPServerModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getParamValue = () => {
|
const getParamValue = () => {
|
||||||
const res = {} as any
|
const res: Record<string, string> = {}
|
||||||
latestParams.map((param) => {
|
latestParams.forEach((param) => {
|
||||||
res[param.variable] = params[param.variable]
|
if (!param.variable)
|
||||||
return param
|
return
|
||||||
|
|
||||||
|
const value = params[param.variable]
|
||||||
|
if (value !== undefined)
|
||||||
|
res[param.variable] = value
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -78,7 +89,11 @@ const MCPServerModal = ({
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
const payload: any = {
|
const payload: {
|
||||||
|
appID: string
|
||||||
|
description?: string
|
||||||
|
parameters: Record<string, string>
|
||||||
|
} = {
|
||||||
appID,
|
appID,
|
||||||
parameters: getParamValue(),
|
parameters: getParamValue(),
|
||||||
}
|
}
|
||||||
@ -92,13 +107,18 @@ const MCPServerModal = ({
|
|||||||
onHide()
|
onHide()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const payload: any = {
|
const payload: {
|
||||||
|
appID: string
|
||||||
|
id: string
|
||||||
|
description: string
|
||||||
|
parameters: Record<string, string>
|
||||||
|
} = {
|
||||||
appID,
|
appID,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
parameters: getParamValue(),
|
parameters: getParamValue(),
|
||||||
|
description,
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.description = description
|
|
||||||
await updateMCPServer(payload)
|
await updateMCPServer(payload)
|
||||||
invalidateMCPServerDetail(appID)
|
invalidateMCPServerDetail(appID)
|
||||||
emitMcpServerUpdate('updated')
|
emitMcpServerUpdate('updated')
|
||||||
@ -107,56 +127,67 @@ const MCPServerModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={show}
|
open={show}
|
||||||
onClose={onHide}
|
onOpenChange={(open) => {
|
||||||
className={cn('relative max-w-[520px]! p-0!')}
|
if (!open)
|
||||||
|
onHide()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="absolute top-5 right-5 z-10 cursor-pointer p-1.5" onClick={onHide}>
|
<DialogContent className="w-[calc(100vw-2rem)] max-w-[520px]! overflow-hidden! border-none p-0! text-left align-middle transition-all duration-100 ease-in">
|
||||||
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
<div className="absolute top-5 right-5 z-10 cursor-pointer p-1.5" onClick={onHide}>
|
||||||
</div>
|
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
||||||
<div className="relative p-6 pb-3 title-2xl-semi-bold text-xl text-text-primary">
|
|
||||||
{!data ? t('mcp.server.modal.addTitle', { ns: 'tools' }) : t('mcp.server.modal.editTitle', { ns: 'tools' })}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-5 px-6 py-3">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<div className="flex h-6 items-center gap-1">
|
|
||||||
<div className="system-sm-medium text-text-secondary">{t('mcp.server.modal.description', { ns: 'tools' })}</div>
|
|
||||||
<div className="system-xs-regular text-text-destructive-secondary">*</div>
|
|
||||||
</div>
|
|
||||||
<Textarea
|
|
||||||
className="h-[96px] resize-none"
|
|
||||||
value={description}
|
|
||||||
placeholder={t('mcp.server.modal.descriptionPlaceholder', { ns: 'tools' })}
|
|
||||||
onChange={e => setDescription(e.target.value)}
|
|
||||||
>
|
|
||||||
</Textarea>
|
|
||||||
</div>
|
</div>
|
||||||
{latestParams.length > 0 && (
|
<div className="relative p-6 pb-3 title-2xl-semi-bold text-xl text-text-primary">
|
||||||
<div>
|
{!data ? t('mcp.server.modal.addTitle', { ns: 'tools' }) : t('mcp.server.modal.editTitle', { ns: 'tools' })}
|
||||||
<div className="mb-1 flex items-center gap-2">
|
</div>
|
||||||
<div className="shrink-0 system-xs-medium-uppercase text-text-primary">{t('mcp.server.modal.parameters', { ns: 'tools' })}</div>
|
<div className="space-y-5 px-6 py-3">
|
||||||
<Divider type="horizontal" className="m-0! h-px! grow bg-divider-subtle" />
|
<div className="space-y-0.5">
|
||||||
</div>
|
<div className="flex h-6 items-center gap-1">
|
||||||
<div className="mb-2 body-xs-regular text-text-tertiary">{t('mcp.server.modal.parametersTip', { ns: 'tools' })}</div>
|
<div className="system-sm-medium text-text-secondary">{t('mcp.server.modal.description', { ns: 'tools' })}</div>
|
||||||
<div className="space-y-3">
|
<div className="system-xs-regular text-text-destructive-secondary">*</div>
|
||||||
{latestParams.map(paramItem => (
|
|
||||||
<MCPServerParamItem
|
|
||||||
key={paramItem.variable}
|
|
||||||
data={paramItem}
|
|
||||||
value={params[paramItem.variable] || ''}
|
|
||||||
onChange={value => handleParamChange(paramItem.variable, value)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
<Textarea
|
||||||
|
className="h-[96px] resize-none"
|
||||||
|
value={description}
|
||||||
|
placeholder={t('mcp.server.modal.descriptionPlaceholder', { ns: 'tools' })}
|
||||||
|
onChange={e => setDescription(e.target.value)}
|
||||||
|
>
|
||||||
|
</Textarea>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{latestParams.length > 0 && (
|
||||||
</div>
|
<div>
|
||||||
<div className="flex flex-row-reverse p-6 pt-5">
|
<div className="mb-1 flex items-center gap-2">
|
||||||
<Button disabled={!description || creating || updating} className="ml-2" variant="primary" onClick={submit}>{data ? t('mcp.modal.save', { ns: 'tools' }) : t('mcp.server.modal.confirm', { ns: 'tools' })}</Button>
|
<div className="shrink-0 system-xs-medium-uppercase text-text-primary">{t('mcp.server.modal.parameters', { ns: 'tools' })}</div>
|
||||||
<Button onClick={onHide}>{t('mcp.modal.cancel', { ns: 'tools' })}</Button>
|
<Divider type="horizontal" className="m-0! h-px! grow bg-divider-subtle" />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
<div className="mb-2 body-xs-regular text-text-tertiary">{t('mcp.server.modal.parametersTip', { ns: 'tools' })}</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{latestParams.map((paramItem) => {
|
||||||
|
if (!paramItem.variable)
|
||||||
|
return null
|
||||||
|
|
||||||
|
const { variable } = paramItem
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MCPServerParamItem
|
||||||
|
key={variable}
|
||||||
|
data={paramItem}
|
||||||
|
value={params[variable] || ''}
|
||||||
|
onChange={value => handleParamChange(variable, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row-reverse p-6 pt-5">
|
||||||
|
<Button disabled={!description || creating || updating} className="ml-2" variant="primary" onClick={submit}>{data ? t('mcp.modal.save', { ns: 'tools' }) : t('mcp.server.modal.confirm', { ns: 'tools' })}</Button>
|
||||||
|
<Button onClick={onHide}>{t('mcp.modal.cancel', { ns: 'tools' })}</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,17 +4,15 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
|||||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||||
import type { AppIconType } from '@/types/app'
|
import type { AppIconType } from '@/types/app'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { RiCloseLine, RiEditLine } from '@remixicon/react'
|
import { RiCloseLine, RiEditLine } from '@remixicon/react'
|
||||||
import { useHover } from 'ahooks'
|
import { useHover } from 'ahooks'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AppIcon from '@/app/components/base/app-icon'
|
import AppIcon from '@/app/components/base/app-icon'
|
||||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||||
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import TabSlider from '@/app/components/base/tab-slider'
|
import TabSlider from '@/app/components/base/tab-slider'
|
||||||
import { MCPAuthMethod } from '@/app/components/tools/types'
|
import { MCPAuthMethod } from '@/app/components/tools/types'
|
||||||
import { shouldUseMcpIconForAppIcon } from '@/utils/mcp'
|
import { shouldUseMcpIconForAppIcon } from '@/utils/mcp'
|
||||||
@ -281,18 +279,16 @@ const MCPModal: FC<DuplicateAppModalProps> = ({
|
|||||||
const formKey = data?.id ?? 'create'
|
const formKey = data?.id ?? 'create'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog open={show}>
|
||||||
isShow={show}
|
<DialogContent className="w-full max-w-[520px]! overflow-hidden! border-none p-6 text-left align-middle">
|
||||||
onClose={noop}
|
<MCPModalContent
|
||||||
className={cn('relative max-w-[520px]!', 'p-6')}
|
key={formKey}
|
||||||
>
|
data={data}
|
||||||
<MCPModalContent
|
onConfirm={onConfirm}
|
||||||
key={formKey}
|
onHide={onHide}
|
||||||
data={data}
|
/>
|
||||||
onConfirm={onConfirm}
|
</DialogContent>
|
||||||
onHide={onHide}
|
</Dialog>
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import type { Authorization as AuthorizationPayloadType } from '../../types'
|
|||||||
import type { Var } from '@/app/components/workflow/types'
|
import type { Var } from '@/app/components/workflow/types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import BaseInput from '@/app/components/base/input'
|
import BaseInput from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||||
import { VarType } from '@/app/components/workflow/types'
|
import { VarType } from '@/app/components/workflow/types'
|
||||||
@ -115,70 +115,78 @@ const Authorization: FC<Props> = ({
|
|||||||
onHide()
|
onHide()
|
||||||
}, [tempPayload, onChange, onHide])
|
}, [tempPayload, onChange, onHide])
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t(`${i18nPrefix}.authorization`, { ns: 'workflow' })}
|
open={isShow}
|
||||||
isShow={isShow}
|
onOpenChange={(open) => {
|
||||||
onClose={onHide}
|
if (!open)
|
||||||
|
onHide()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||||
<div className="space-y-2">
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<Field title={t(`${i18nPrefix}.authorizationType`, { ns: 'workflow' })}>
|
{t(`${i18nPrefix}.authorization`, { ns: 'workflow' })}
|
||||||
<RadioGroup
|
</DialogTitle>
|
||||||
options={[
|
|
||||||
{ value: AuthorizationType.none, label: t(`${i18nPrefix}.no-auth`, { ns: 'workflow' }) },
|
|
||||||
{ value: AuthorizationType.apiKey, label: t(`${i18nPrefix}.api-key`, { ns: 'workflow' }) },
|
|
||||||
]}
|
|
||||||
value={tempPayload.type}
|
|
||||||
onChange={handleAuthTypeChange}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
{tempPayload.type === AuthorizationType.apiKey && (
|
<div>
|
||||||
<>
|
<div className="space-y-2">
|
||||||
<Field title={t(`${i18nPrefix}.auth-type`, { ns: 'workflow' })}>
|
<Field title={t(`${i18nPrefix}.authorizationType`, { ns: 'workflow' })}>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
options={[
|
options={[
|
||||||
{ value: APIType.basic, label: t(`${i18nPrefix}.basic`, { ns: 'workflow' }) },
|
{ value: AuthorizationType.none, label: t(`${i18nPrefix}.no-auth`, { ns: 'workflow' }) },
|
||||||
{ value: APIType.bearer, label: t(`${i18nPrefix}.bearer`, { ns: 'workflow' }) },
|
{ value: AuthorizationType.apiKey, label: t(`${i18nPrefix}.api-key`, { ns: 'workflow' }) },
|
||||||
{ value: APIType.custom, label: t(`${i18nPrefix}.custom`, { ns: 'workflow' }) },
|
]}
|
||||||
]}
|
value={tempPayload.type}
|
||||||
value={tempPayload.config?.type || APIType.basic}
|
onChange={handleAuthTypeChange}
|
||||||
onChange={handleAuthAPITypeChange}
|
/>
|
||||||
/>
|
</Field>
|
||||||
</Field>
|
|
||||||
{tempPayload.config?.type === APIType.custom && (
|
{tempPayload.type === AuthorizationType.apiKey && (
|
||||||
<Field title={t(`${i18nPrefix}.header`, { ns: 'workflow' })} isRequired>
|
<>
|
||||||
<BaseInput
|
<Field title={t(`${i18nPrefix}.auth-type`, { ns: 'workflow' })}>
|
||||||
value={tempPayload.config?.header || ''}
|
<RadioGroup
|
||||||
onChange={handleAPIKeyOrHeaderChange('header')}
|
options={[
|
||||||
|
{ value: APIType.basic, label: t(`${i18nPrefix}.basic`, { ns: 'workflow' }) },
|
||||||
|
{ value: APIType.bearer, label: t(`${i18nPrefix}.bearer`, { ns: 'workflow' }) },
|
||||||
|
{ value: APIType.custom, label: t(`${i18nPrefix}.custom`, { ns: 'workflow' }) },
|
||||||
|
]}
|
||||||
|
value={tempPayload.config?.type || APIType.basic}
|
||||||
|
onChange={handleAuthAPITypeChange}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
)}
|
{tempPayload.config?.type === APIType.custom && (
|
||||||
|
<Field title={t(`${i18nPrefix}.header`, { ns: 'workflow' })} isRequired>
|
||||||
|
<BaseInput
|
||||||
|
value={tempPayload.config?.header || ''}
|
||||||
|
onChange={handleAPIKeyOrHeaderChange('header')}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
<Field title={t(`${i18nPrefix}.api-key-title`, { ns: 'workflow' })} isRequired>
|
<Field title={t(`${i18nPrefix}.api-key-title`, { ns: 'workflow' })} isRequired>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Input
|
<Input
|
||||||
instanceId="http-api-key"
|
instanceId="http-api-key"
|
||||||
className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')}
|
className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')}
|
||||||
value={tempPayload.config?.api_key || ''}
|
value={tempPayload.config?.api_key || ''}
|
||||||
onChange={handleAPIKeyChange}
|
onChange={handleAPIKeyChange}
|
||||||
nodesOutputVars={availableVars}
|
nodesOutputVars={availableVars}
|
||||||
availableNodes={availableNodesWithParent}
|
availableNodes={availableNodesWithParent}
|
||||||
onFocusChange={setIsFocus}
|
onFocusChange={setIsFocus}
|
||||||
placeholder={' '}
|
placeholder={' '}
|
||||||
placeholderClassName="leading-[21px]!"
|
placeholderClassName="leading-[21px]!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 flex justify-end space-x-2">
|
||||||
|
<Button onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button variant="primary" onClick={handleConfirm}>{t('operation.save', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex justify-end space-x-2">
|
</DialogContent>
|
||||||
<Button onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
</Dialog>
|
||||||
<Button variant="primary" onClick={handleConfirm}>{t('operation.save', { ns: 'common' })}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(Authorization)
|
export default React.memo(Authorization)
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { HttpNodeType } from '../types'
|
import type { HttpNodeType } from '../types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
||||||
import { parseCurl } from './curl-parser'
|
import { parseCurl } from './curl-parser'
|
||||||
@ -42,28 +42,35 @@ const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => {
|
|||||||
}, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport])
|
}, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t('nodes.http.curl.title', { ns: 'workflow' })}
|
open={isShow}
|
||||||
isShow={isShow}
|
onOpenChange={(open) => {
|
||||||
onClose={onHide}
|
if (!open)
|
||||||
className="w-[400px]! max-w-[400px]! p-4!"
|
onHide()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<DialogContent className="w-[400px]! max-w-[400px]! overflow-hidden! border-none p-4! text-left align-middle">
|
||||||
<Textarea
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
value={inputString}
|
{t('nodes.http.curl.title', { ns: 'workflow' })}
|
||||||
className="my-3 h-40 w-full grow"
|
</DialogTitle>
|
||||||
onChange={e => setInputString(e.target.value)}
|
|
||||||
placeholder={t('nodes.http.curl.placeholder', { ns: 'workflow' })!}
|
<div>
|
||||||
/>
|
<Textarea
|
||||||
</div>
|
value={inputString}
|
||||||
<div className="mt-4 flex justify-end space-x-2">
|
className="my-3 h-40 w-full grow"
|
||||||
<Button className="w-[95px]!" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
onChange={e => setInputString(e.target.value)}
|
||||||
<Button className="w-[95px]!" variant="primary" onClick={handleSave}>
|
placeholder={t('nodes.http.curl.placeholder', { ns: 'workflow' })!}
|
||||||
{' '}
|
/>
|
||||||
{t('operation.save', { ns: 'common' })}
|
</div>
|
||||||
</Button>
|
<div className="mt-4 flex justify-end space-x-2">
|
||||||
</div>
|
<Button className="w-[95px]!" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
</Modal>
|
<Button className="w-[95px]!" variant="primary" onClick={handleSave}>
|
||||||
|
{' '}
|
||||||
|
{t('operation.save', { ns: 'common' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { SchemaRoot } from '../../types'
|
import type { SchemaRoot } from '../../types'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Modal from '../../../../../base/modal'
|
|
||||||
import JsonSchemaConfig from './json-schema-config'
|
import JsonSchemaConfig from './json-schema-config'
|
||||||
|
|
||||||
type JsonSchemaConfigModalProps = {
|
type JsonSchemaConfigModalProps = {
|
||||||
@ -18,17 +18,22 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={isShow}
|
open={isShow}
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
className="h-[800px] max-w-[960px] p-0"
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<JsonSchemaConfig
|
<DialogContent className="h-[800px] max-h-none w-full max-w-[960px] overflow-hidden! border-none p-0 text-left align-middle">
|
||||||
defaultSchema={defaultSchema}
|
|
||||||
onSave={onSave}
|
<JsonSchemaConfig
|
||||||
onClose={onClose}
|
defaultSchema={defaultSchema}
|
||||||
/>
|
onSave={onSave}
|
||||||
</Modal>
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,24 +110,23 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-param
|
|||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/base/modal', () => ({
|
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: ({
|
Dialog: ({
|
||||||
children,
|
children,
|
||||||
isShow,
|
open,
|
||||||
title,
|
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
isShow?: boolean
|
open?: boolean
|
||||||
title?: ReactNode
|
}) => open !== false
|
||||||
}) => isShow
|
|
||||||
? (
|
? (
|
||||||
<div data-testid="base-modal">
|
<div data-testid="base-modal">
|
||||||
<div>{title}</div>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
DialogContent: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||||
|
DialogTitle: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/workflow/nodes/_base/components/collapse', () => ({
|
vi.mock('@/app/components/workflow/nodes/_base/components/collapse', () => ({
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { FC } from 'react'
|
|||||||
import type { Param } from '../../types'
|
import type { Param } from '../../types'
|
||||||
import type { MoreInfo } from '@/app/components/workflow/types'
|
import type { MoreInfo } from '@/app/components/workflow/types'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||||
import { Switch } from '@langgenius/dify-ui/switch'
|
import { Switch } from '@langgenius/dify-ui/switch'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
@ -13,7 +14,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import Field from '@/app/components/app/configuration/config-var/config-modal/field'
|
import Field from '@/app/components/app/configuration/config-var/config-modal/field'
|
||||||
import ConfigSelect from '@/app/components/app/configuration/config-var/config-select'
|
import ConfigSelect from '@/app/components/app/configuration/config-var/config-select'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
import { ChangeType } from '@/app/components/workflow/types'
|
import { ChangeType } from '@/app/components/workflow/types'
|
||||||
import { checkKeys } from '@/utils/var'
|
import { checkKeys } from '@/utils/var'
|
||||||
@ -124,64 +124,71 @@ const AddExtractParameter: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isShowModal && (
|
{isShowModal && (
|
||||||
<Modal
|
<Dialog
|
||||||
title={t(`${i18nPrefix}.addExtractParameter`, { ns: 'workflow' })}
|
open
|
||||||
isShow
|
onOpenChange={(open) => {
|
||||||
onClose={hideModal}
|
if (!open)
|
||||||
className="w-[400px]! max-w-[400px]! p-4!"
|
hideModal()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<DialogContent className="w-[400px]! max-w-[400px]! overflow-hidden! border-none p-4! text-left align-middle">
|
||||||
<div className="space-y-2">
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.name`, { ns: 'workflow' })}>
|
{t(`${i18nPrefix}.addExtractParameter`, { ns: 'workflow' })}
|
||||||
<Input
|
</DialogTitle>
|
||||||
value={param.name}
|
|
||||||
onChange={e => handleParamChange('name')(e.target.value)}
|
<div>
|
||||||
placeholder={t(`${i18nPrefix}.addExtractParameterContent.namePlaceholder`, { ns: 'workflow' })!}
|
<div className="space-y-2">
|
||||||
/>
|
<Field title={t(`${i18nPrefix}.addExtractParameterContent.name`, { ns: 'workflow' })}>
|
||||||
</Field>
|
<Input
|
||||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.type`, { ns: 'workflow' })}>
|
value={param.name}
|
||||||
<Select
|
onChange={e => handleParamChange('name')(e.target.value)}
|
||||||
value={param.type}
|
placeholder={t(`${i18nPrefix}.addExtractParameterContent.namePlaceholder`, { ns: 'workflow' })!}
|
||||||
onValueChange={value => value && handleParamChange('type')(value)}
|
/>
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full capitalize">
|
|
||||||
{param.type}
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{TYPES.map(type => (
|
|
||||||
<SelectItem key={type} value={type} className="capitalize">
|
|
||||||
<SelectItemText className="capitalize">{type}</SelectItemText>
|
|
||||||
<SelectItemIndicator />
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</Field>
|
|
||||||
{param.type === ParamType.select && (
|
|
||||||
<Field title={t('variableConfig.options', { ns: 'appDebug' })}>
|
|
||||||
<ConfigSelect options={param.options || []} onChange={handleParamChange('options')} />
|
|
||||||
</Field>
|
</Field>
|
||||||
)}
|
<Field title={t(`${i18nPrefix}.addExtractParameterContent.type`, { ns: 'workflow' })}>
|
||||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.description`, { ns: 'workflow' })}>
|
<Select
|
||||||
<Textarea
|
value={param.type}
|
||||||
value={param.description}
|
onValueChange={value => value && handleParamChange('type')(value)}
|
||||||
onChange={e => handleParamChange('description')(e.target.value)}
|
>
|
||||||
placeholder={t(`${i18nPrefix}.addExtractParameterContent.descriptionPlaceholder`, { ns: 'workflow' })!}
|
<SelectTrigger className="w-full capitalize">
|
||||||
/>
|
{param.type}
|
||||||
</Field>
|
</SelectTrigger>
|
||||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.required`, { ns: 'workflow' })}>
|
<SelectContent>
|
||||||
<>
|
{TYPES.map(type => (
|
||||||
<div className="mb-1.5 text-xs leading-[18px] font-normal text-text-tertiary">{t(`${i18nPrefix}.addExtractParameterContent.requiredContent`, { ns: 'workflow' })}</div>
|
<SelectItem key={type} value={type} className="capitalize">
|
||||||
<Switch size="lg" checked={param.required ?? false} onCheckedChange={handleParamChange('required')} />
|
<SelectItemText className="capitalize">{type}</SelectItemText>
|
||||||
</>
|
<SelectItemIndicator />
|
||||||
</Field>
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
{param.type === ParamType.select && (
|
||||||
|
<Field title={t('variableConfig.options', { ns: 'appDebug' })}>
|
||||||
|
<ConfigSelect options={param.options || []} onChange={handleParamChange('options')} />
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
<Field title={t(`${i18nPrefix}.addExtractParameterContent.description`, { ns: 'workflow' })}>
|
||||||
|
<Textarea
|
||||||
|
value={param.description}
|
||||||
|
onChange={e => handleParamChange('description')(e.target.value)}
|
||||||
|
placeholder={t(`${i18nPrefix}.addExtractParameterContent.descriptionPlaceholder`, { ns: 'workflow' })!}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field title={t(`${i18nPrefix}.addExtractParameterContent.required`, { ns: 'workflow' })}>
|
||||||
|
<>
|
||||||
|
<div className="mb-1.5 text-xs leading-[18px] font-normal text-text-tertiary">{t(`${i18nPrefix}.addExtractParameterContent.requiredContent`, { ns: 'workflow' })}</div>
|
||||||
|
<Switch size="lg" checked={param.required ?? false} onCheckedChange={handleParamChange('required')} />
|
||||||
|
</>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex justify-end space-x-2">
|
||||||
|
<Button className="w-[95px]!" onClick={hideModal}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||||
|
<Button className="w-[95px]!" variant="primary" onClick={handleSave}>{isAdd ? t('operation.add', { ns: 'common' }) : t('operation.save', { ns: 'common' })}</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex justify-end space-x-2">
|
</DialogContent>
|
||||||
<Button className="w-[95px]!" onClick={hideModal}>{t('operation.cancel', { ns: 'common' })}</Button>
|
</Dialog>
|
||||||
<Button className="w-[95px]!" variant="primary" onClick={handleSave}>{isAdd ? t('operation.add', { ns: 'common' }) : t('operation.save', { ns: 'common' })}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import type {
|
|||||||
ConversationVariable,
|
ConversationVariable,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { useMount } from 'ahooks'
|
import { useMount } from 'ahooks'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { capitalize } from 'es-toolkit/string'
|
import { capitalize } from 'es-toolkit/string'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
@ -16,7 +16,6 @@ import {
|
|||||||
CopyCheck,
|
CopyCheck,
|
||||||
} from '@/app/components/base/icons/src/vender/line/files'
|
} from '@/app/components/base/icons/src/vender/line/files'
|
||||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||||
@ -76,87 +75,86 @@ const ConversationVariableModal = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog open>
|
||||||
isShow
|
<DialogContent className={cn('max-h-none w-full overflow-hidden! border-none text-left align-middle', cn('h-[640px] w-[920px] max-w-[920px] p-0'))}>
|
||||||
onClose={noop}
|
|
||||||
className={cn('h-[640px] w-[920px] max-w-[920px] p-0')}
|
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onHide}>
|
||||||
>
|
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onHide}>
|
|
||||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
|
||||||
</div>
|
|
||||||
<div className="flex h-full w-full">
|
|
||||||
{/* LEFT */}
|
|
||||||
<div className="flex h-full w-[224px] shrink-0 flex-col border-r border-divider-burn bg-background-sidenav-bg">
|
|
||||||
<div className="shrink-0 pt-5 pr-4 pb-3 pl-5 system-xl-semibold text-text-primary">{t('chatVariable.panelTitle', { ns: 'workflow' })}</div>
|
|
||||||
<div className="grow overflow-y-auto px-3 py-2">
|
|
||||||
{varList.map(chatVar => (
|
|
||||||
<div key={chatVar.id} className={cn('group mb-0.5 flex cursor-pointer items-center rounded-lg p-2 hover:bg-state-base-hover', currentVar.id === chatVar.id && 'bg-state-base-hover')} onClick={() => setCurrentVar(chatVar)}>
|
|
||||||
<BubbleX className={cn('mr-1 h-4 w-4 shrink-0 text-text-tertiary group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')} />
|
|
||||||
<div title={chatVar.name} className={cn('truncate system-sm-medium text-text-tertiary group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')}>{chatVar.name}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* RIGHT */}
|
<div className="flex h-full w-full">
|
||||||
<div className="flex h-full w-0 grow flex-col bg-components-panel-bg">
|
{/* LEFT */}
|
||||||
<div className="shrink-0 p-4 pb-2">
|
<div className="flex h-full w-[224px] shrink-0 flex-col border-r border-divider-burn bg-background-sidenav-bg">
|
||||||
<div className="flex items-center gap-1 py-1">
|
<div className="shrink-0 pt-5 pr-4 pb-3 pl-5 system-xl-semibold text-text-primary">{t('chatVariable.panelTitle', { ns: 'workflow' })}</div>
|
||||||
<div className="system-xl-semibold text-text-primary">{currentVar.name}</div>
|
<div className="grow overflow-y-auto px-3 py-2">
|
||||||
<div className="system-xs-medium text-text-tertiary">{capitalize(currentVar.value_type)}</div>
|
{varList.map(chatVar => (
|
||||||
|
<div key={chatVar.id} className={cn('group mb-0.5 flex cursor-pointer items-center rounded-lg p-2 hover:bg-state-base-hover', currentVar.id === chatVar.id && 'bg-state-base-hover')} onClick={() => setCurrentVar(chatVar)}>
|
||||||
|
<BubbleX className={cn('mr-1 h-4 w-4 shrink-0 text-text-tertiary group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')} />
|
||||||
|
<div title={chatVar.name} className={cn('truncate system-sm-medium text-text-tertiary group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')}>{chatVar.name}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-0 grow flex-col p-4 pt-2">
|
{/* RIGHT */}
|
||||||
<div className="mb-2 flex shrink-0 items-center gap-2">
|
<div className="flex h-full w-0 grow flex-col bg-components-panel-bg">
|
||||||
<div className="shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('chatVariable.storedContent', { ns: 'workflow' }).toLocaleUpperCase()}</div>
|
<div className="shrink-0 p-4 pb-2">
|
||||||
<div
|
<div className="flex items-center gap-1 py-1">
|
||||||
className="h-px grow"
|
<div className="system-xl-semibold text-text-primary">{currentVar.name}</div>
|
||||||
style={{
|
<div className="system-xs-medium text-text-tertiary">{capitalize(currentVar.value_type)}</div>
|
||||||
background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
{!!latestValueTimestampMap[currentVar.id] && (
|
|
||||||
<div className="shrink-0 system-xs-regular text-text-tertiary">
|
|
||||||
{t('chatVariable.updatedAt', { ns: 'workflow' })}
|
|
||||||
{formatTime(latestValueTimestampMap[currentVar.id]!, t('dateTimeFormat', { ns: 'appLog' }) as string)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grow overflow-y-auto">
|
<div className="flex h-0 grow flex-col p-4 pt-2">
|
||||||
{currentVar.value_type !== ChatVarType.Number && currentVar.value_type !== ChatVarType.String && (
|
<div className="mb-2 flex shrink-0 items-center gap-2">
|
||||||
<div className="flex h-full flex-col rounded-lg bg-components-input-bg-normal px-2 pb-2">
|
<div className="shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('chatVariable.storedContent', { ns: 'workflow' }).toLocaleUpperCase()}</div>
|
||||||
<div className="flex h-7 shrink-0 items-center justify-between pt-1 pr-2 pl-3">
|
<div
|
||||||
<div className="system-xs-semibold text-text-secondary">JSON</div>
|
className="h-px grow"
|
||||||
<div className="flex items-center p-1">
|
style={{
|
||||||
{!isCopied
|
background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)',
|
||||||
? (
|
}}
|
||||||
<Copy className="h-4 w-4 cursor-pointer text-text-tertiary" onClick={handleCopy} />
|
>
|
||||||
)
|
</div>
|
||||||
: (
|
{!!latestValueTimestampMap[currentVar.id] && (
|
||||||
<CopyCheck className="h-4 w-4 text-text-tertiary" />
|
<div className="shrink-0 system-xs-regular text-text-tertiary">
|
||||||
)}
|
{t('chatVariable.updatedAt', { ns: 'workflow' })}
|
||||||
|
{formatTime(latestValueTimestampMap[currentVar.id]!, t('dateTimeFormat', { ns: 'appLog' }) as string)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="grow overflow-y-auto">
|
||||||
|
{currentVar.value_type !== ChatVarType.Number && currentVar.value_type !== ChatVarType.String && (
|
||||||
|
<div className="flex h-full flex-col rounded-lg bg-components-input-bg-normal px-2 pb-2">
|
||||||
|
<div className="flex h-7 shrink-0 items-center justify-between pt-1 pr-2 pl-3">
|
||||||
|
<div className="system-xs-semibold text-text-secondary">JSON</div>
|
||||||
|
<div className="flex items-center p-1">
|
||||||
|
{!isCopied
|
||||||
|
? (
|
||||||
|
<Copy className="h-4 w-4 cursor-pointer text-text-tertiary" onClick={handleCopy} />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<CopyCheck className="h-4 w-4 text-text-tertiary" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grow pl-4">
|
||||||
|
<CodeEditor
|
||||||
|
readOnly
|
||||||
|
noWrapper
|
||||||
|
isExpand
|
||||||
|
language={CodeLanguage.json}
|
||||||
|
value={latestValueMap[currentVar.id] || ''}
|
||||||
|
isJSONStringifyBeauty
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grow pl-4">
|
)}
|
||||||
<CodeEditor
|
{(currentVar.value_type === ChatVarType.Number || currentVar.value_type === ChatVarType.String) && (
|
||||||
readOnly
|
<div className="h-full overflow-x-hidden overflow-y-auto rounded-lg bg-components-input-bg-normal px-4 py-3 system-md-regular text-components-input-text-filled">{latestValueMap[currentVar.id] || ''}</div>
|
||||||
noWrapper
|
)}
|
||||||
isExpand
|
</div>
|
||||||
language={CodeLanguage.json}
|
|
||||||
value={latestValueMap[currentVar.id] || ''}
|
|
||||||
isJSONStringifyBeauty
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{(currentVar.value_type === ChatVarType.Number || currentVar.value_type === ChatVarType.String) && (
|
|
||||||
<div className="h-full overflow-x-hidden overflow-y-auto rounded-lg bg-components-input-bg-normal px-4 py-3 system-md-regular text-components-input-text-filled">{latestValueMap[currentVar.id] || ''}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,8 @@ const ActionMenu: FC<ActionMenuProps> = (props: ActionMenuProps) => {
|
|||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
>
|
>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={<Button size="small" className="px-1" onClick={e => e.stopPropagation()} />}
|
nativeButton={false}
|
||||||
|
render={<Button nativeButton={false} size="small" className="px-1" onClick={e => e.stopPropagation()} />}
|
||||||
>
|
>
|
||||||
<RiMoreFill className="h-4 w-4" />
|
<RiMoreFill className="h-4 w-4" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { VersionHistory } from '@/types/workflow'
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
|
|
||||||
type DeleteConfirmModalProps = {
|
type DeleteConfirmModalProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@ -21,24 +28,36 @@ const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal className="p-0" isShow={isOpen} onClose={onClose}>
|
<AlertDialog
|
||||||
<div className="flex flex-col gap-y-2 p-6 pb-4">
|
open={isOpen}
|
||||||
<div className="title-2xl-semi-bold text-text-primary">
|
onOpenChange={(open) => {
|
||||||
{`${t('operation.delete', { ns: 'common' })} ${versionInfo.marked_name || t('versionHistory.defaultName', { ns: 'workflow' })}`}
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AlertDialogContent className="overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||||
|
<div className="flex flex-col gap-y-2 p-6 pb-4">
|
||||||
|
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
|
{`${t('operation.delete', { ns: 'common' })} ${versionInfo.marked_name || t('versionHistory.defaultName', { ns: 'workflow' })}`}
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="system-md-regular text-text-secondary">
|
||||||
|
{t('versionHistory.deletionTip', { ns: 'workflow' })}
|
||||||
|
</AlertDialogDescription>
|
||||||
</div>
|
</div>
|
||||||
<p className="system-md-regular text-text-secondary">
|
<AlertDialogActions>
|
||||||
{t('versionHistory.deletionTip', { ns: 'workflow' })}
|
<AlertDialogCancelButton
|
||||||
</p>
|
nativeButton={false}
|
||||||
</div>
|
variant="secondary"
|
||||||
<div className="flex items-center justify-end gap-x-2 p-6">
|
closeProps={{ nativeButton: false }}
|
||||||
<Button onClick={onClose}>
|
>
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
</Button>
|
</AlertDialogCancelButton>
|
||||||
<Button variant="primary" tone="destructive" onClick={onDelete.bind(null, versionInfo.id)}>
|
<AlertDialogConfirmButton nativeButton={false} onClick={onDelete.bind(null, versionInfo.id)}>
|
||||||
{t('operation.delete', { ns: 'common' })}
|
{t('operation.delete', { ns: 'common' })}
|
||||||
</Button>
|
</AlertDialogConfirmButton>
|
||||||
</div>
|
</AlertDialogActions>
|
||||||
</Modal>
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const Empty: FC<EmptyProps> = ({
|
|||||||
{t('versionHistory.filter.empty', { ns: 'workflow' })}
|
{t('versionHistory.filter.empty', { ns: 'workflow' })}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<Button size="small" onClick={onResetFilter}>
|
<Button nativeButton={false} size="small" onClick={onResetFilter}>
|
||||||
{t('versionHistory.filter.reset', { ns: 'workflow' })}
|
{t('versionHistory.filter.reset', { ns: 'workflow' })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -42,6 +42,7 @@ const Filter: FC<FilterProps> = ({
|
|||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
>
|
>
|
||||||
<PopoverTrigger
|
<PopoverTrigger
|
||||||
|
nativeButton={false}
|
||||||
render={(
|
render={(
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { VersionHistory } from '@/types/workflow'
|
import type { VersionHistory } from '@/types/workflow'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogActions,
|
||||||
|
AlertDialogCancelButton,
|
||||||
|
AlertDialogConfirmButton,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@langgenius/dify-ui/alert-dialog'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
|
|
||||||
type RestoreConfirmModalProps = {
|
type RestoreConfirmModalProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@ -21,24 +28,36 @@ const RestoreConfirmModal: FC<RestoreConfirmModalProps> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal className="p-0" isShow={isOpen} onClose={onClose}>
|
<AlertDialog
|
||||||
<div className="flex flex-col gap-y-2 p-6 pb-4">
|
open={isOpen}
|
||||||
<div className="title-2xl-semi-bold text-text-primary">
|
onOpenChange={(open) => {
|
||||||
{`${t('common.restore', { ns: 'workflow' })} ${versionInfo.marked_name || t('versionHistory.defaultName', { ns: 'workflow' })}`}
|
if (!open)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AlertDialogContent className="overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||||
|
<div className="flex flex-col gap-y-2 p-6 pb-4">
|
||||||
|
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
|
{`${t('common.restore', { ns: 'workflow' })} ${versionInfo.marked_name || t('versionHistory.defaultName', { ns: 'workflow' })}`}
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="system-md-regular text-text-secondary">
|
||||||
|
{t('versionHistory.restorationTip', { ns: 'workflow' })}
|
||||||
|
</AlertDialogDescription>
|
||||||
</div>
|
</div>
|
||||||
<p className="system-md-regular text-text-secondary">
|
<AlertDialogActions>
|
||||||
{t('versionHistory.restorationTip', { ns: 'workflow' })}
|
<AlertDialogCancelButton
|
||||||
</p>
|
nativeButton={false}
|
||||||
</div>
|
variant="secondary"
|
||||||
<div className="flex items-center justify-end gap-x-2 p-6">
|
closeProps={{ nativeButton: false }}
|
||||||
<Button onClick={onClose}>
|
>
|
||||||
{t('operation.cancel', { ns: 'common' })}
|
{t('operation.cancel', { ns: 'common' })}
|
||||||
</Button>
|
</AlertDialogCancelButton>
|
||||||
<Button variant="primary" onClick={onRestore.bind(null, versionInfo)}>
|
<AlertDialogConfirmButton nativeButton={false} tone="default" onClick={onRestore.bind(null, versionInfo)}>
|
||||||
{t('common.restore', { ns: 'workflow' })}
|
{t('common.restore', { ns: 'workflow' })}
|
||||||
</Button>
|
</AlertDialogConfirmButton>
|
||||||
</div>
|
</AlertDialogActions>
|
||||||
</Modal>
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import type { MouseEventHandler } from 'react'
|
import type { MouseEventHandler } from 'react'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import {
|
import {
|
||||||
RiAlertFill,
|
RiAlertFill,
|
||||||
@ -17,7 +18,6 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import {
|
import {
|
||||||
@ -199,90 +199,100 @@ const UpdateDSLModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Dialog
|
||||||
className="w-[520px] rounded-2xl p-6"
|
open={show}
|
||||||
isShow={show}
|
onOpenChange={(open) => {
|
||||||
onClose={onCancel}
|
if (!open)
|
||||||
|
onCancel()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! rounded-2xl border-none p-6 text-left align-middle">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('importApp', { ns: 'app' })}</div>
|
|
||||||
<div className="flex h-[22px] w-[22px] cursor-pointer items-center justify-center" onClick={onCancel}>
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
<div className="title-2xl-semi-bold text-text-primary">{t('importApp', { ns: 'app' })}</div>
|
||||||
</div>
|
<div className="flex h-[22px] w-[22px] cursor-pointer items-center justify-center" onClick={onCancel}>
|
||||||
</div>
|
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||||
<div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs">
|
|
||||||
<div className="absolute top-0 left-0 h-full w-full bg-toast-warning-bg opacity-40" />
|
|
||||||
<div className="flex items-start justify-center p-1">
|
|
||||||
<RiAlertFill className="h-4 w-4 shrink-0 text-text-warning-secondary" />
|
|
||||||
</div>
|
|
||||||
<div className="flex grow flex-col items-start gap-0.5 py-1">
|
|
||||||
<div className="system-xs-medium whitespace-pre-line text-text-primary">{t('common.importDSLTip', { ns: 'workflow' })}</div>
|
|
||||||
<div className="flex items-start gap-1 self-stretch pt-1 pb-0.5">
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="secondary"
|
|
||||||
className="z-1000"
|
|
||||||
onClick={onBackup}
|
|
||||||
>
|
|
||||||
<RiFileDownloadLine className="h-3.5 w-3.5 text-components-button-secondary-text" />
|
|
||||||
<div className="flex items-center justify-center gap-1 px-[3px]">
|
|
||||||
{t('common.backupCurrentDraft', { ns: 'workflow' })}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs">
|
||||||
<div>
|
<div className="absolute top-0 left-0 h-full w-full bg-toast-warning-bg opacity-40" />
|
||||||
<div className="pt-2 system-md-semibold text-text-primary">
|
<div className="flex items-start justify-center p-1">
|
||||||
{t('common.chooseDSL', { ns: 'workflow' })}
|
<RiAlertFill className="h-4 w-4 shrink-0 text-text-warning-secondary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex grow flex-col items-start gap-0.5 py-1">
|
||||||
|
<div className="system-xs-medium whitespace-pre-line text-text-primary">{t('common.importDSLTip', { ns: 'workflow' })}</div>
|
||||||
|
<div className="flex items-start gap-1 self-stretch pt-1 pb-0.5">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="secondary"
|
||||||
|
className="z-1000"
|
||||||
|
onClick={onBackup}
|
||||||
|
>
|
||||||
|
<RiFileDownloadLine className="h-3.5 w-3.5 text-components-button-secondary-text" />
|
||||||
|
<div className="flex items-center justify-center gap-1 px-[3px]">
|
||||||
|
{t('common.backupCurrentDraft', { ns: 'workflow' })}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-col items-start justify-center gap-4 self-stretch py-4">
|
<div>
|
||||||
<Uploader
|
<div className="pt-2 system-md-semibold text-text-primary">
|
||||||
file={currentFile}
|
{t('common.chooseDSL', { ns: 'workflow' })}
|
||||||
updateFile={handleFile}
|
</div>
|
||||||
className="mt-0! w-full"
|
<div className="flex w-full flex-col items-start justify-center gap-4 self-stretch py-4">
|
||||||
/>
|
<Uploader
|
||||||
|
file={currentFile}
|
||||||
|
updateFile={handleFile}
|
||||||
|
className="mt-0! w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||||
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<Button
|
||||||
<Button
|
disabled={!currentFile || loading}
|
||||||
disabled={!currentFile || loading}
|
variant="primary"
|
||||||
variant="primary"
|
tone="destructive"
|
||||||
tone="destructive"
|
onClick={handleImport}
|
||||||
onClick={handleImport}
|
loading={loading}
|
||||||
loading={loading}
|
>
|
||||||
>
|
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
||||||
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
<Modal
|
<Dialog
|
||||||
isShow={showErrorModal}
|
open={showErrorModal}
|
||||||
onClose={() => setShowErrorModal(false)}
|
onOpenChange={(open) => {
|
||||||
className="w-[480px]"
|
if (!open)
|
||||||
|
setShowErrorModal(false)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||||
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
|
||||||
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
<div className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</div>
|
||||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
<div className="flex grow flex-col system-md-regular text-text-secondary">
|
||||||
<br />
|
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||||
<div>
|
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
<br />
|
||||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
<div>
|
||||||
</div>
|
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||||
<div>
|
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
</div>
|
||||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
<div>
|
||||||
|
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||||
|
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
||||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
<Button variant="secondary" onClick={() => setShowErrorModal(false)}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||||
<Button variant="secondary" onClick={() => setShowErrorModal(false)}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
<Button variant="primary" tone="destructive" onClick={onUpdateDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||||
<Button variant="primary" tone="destructive" onClick={onUpdateDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</Modal>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Button } from '@langgenius/dify-ui/button'
|
import { Button } from '@langgenius/dify-ui/button'
|
||||||
|
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||||
import { RiExternalLinkLine } from '@remixicon/react'
|
import { RiExternalLinkLine } from '@remixicon/react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Modal from '@/app/components/base/modal'
|
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
import { useModalContextSelector } from '@/context/modal-context'
|
import { useModalContextSelector } from '@/context/modal-context'
|
||||||
import useTimestamp from '@/hooks/use-timestamp'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
@ -41,63 +41,70 @@ const ExpireNoticeModal: React.FC<Props> = ({ expireAt, expired, onClose }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Dialog
|
||||||
isShow
|
open
|
||||||
onClose={onClose}
|
onOpenChange={(open) => {
|
||||||
title={expired ? t(`${i18nPrefix}.expired.title`, { ns: 'education' }) : t(`${i18nPrefix}.isAboutToExpire.title`, { ns: 'education', date: formatTime(expireAt, t(`${i18nPrefix}.dateFormat`, { ns: 'education' }) as string), interpolation: { escapeValue: false } })}
|
if (!open)
|
||||||
closable
|
onClose()
|
||||||
className="max-w-[600px]"
|
}}
|
||||||
>
|
>
|
||||||
<div className="mt-5 space-y-5 body-md-regular text-text-secondary">
|
<DialogContent className="w-full max-w-[600px] overflow-hidden! border-none text-left align-middle">
|
||||||
<div>
|
<DialogCloseButton data-testid="modal-close-button" />
|
||||||
{expired
|
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||||
? (
|
{expired ? t(`${i18nPrefix}.expired.title`, { ns: 'education' }) : t(`${i18nPrefix}.isAboutToExpire.title`, { ns: 'education', date: formatTime(expireAt, t(`${i18nPrefix}.dateFormat`, { ns: 'education' }) as string), interpolation: { escapeValue: false } })}
|
||||||
<>
|
</DialogTitle>
|
||||||
<div>{t(`${i18nPrefix}.expired.summary.line1`, { ns: 'education' })}</div>
|
|
||||||
<div>{t(`${i18nPrefix}.expired.summary.line2`, { ns: 'education' })}</div>
|
<div className="mt-5 space-y-5 body-md-regular text-text-secondary">
|
||||||
</>
|
<div>
|
||||||
)
|
{expired
|
||||||
: t(`${i18nPrefix}.isAboutToExpire.summary`, { ns: 'education' })}
|
? (
|
||||||
|
<>
|
||||||
|
<div>{t(`${i18nPrefix}.expired.summary.line1`, { ns: 'education' })}</div>
|
||||||
|
<div>{t(`${i18nPrefix}.expired.summary.line2`, { ns: 'education' })}</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: t(`${i18nPrefix}.isAboutToExpire.summary`, { ns: 'education' })}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong className="block title-md-semi-bold">{t(`${i18nPrefix}.stillInEducation.title`, { ns: 'education' })}</strong>
|
||||||
|
{t(`${i18nPrefix}.stillInEducation.${expired ? 'expired' : 'isAboutToExpire'}`, { ns: 'education' })}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong className="block title-md-semi-bold">{t(`${i18nPrefix}.alreadyGraduated.title`, { ns: 'education' })}</strong>
|
||||||
|
{t(`${i18nPrefix}.alreadyGraduated.${expired ? 'expired' : 'isAboutToExpire'}`, { ns: 'education' })}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="mt-7 flex items-center justify-between space-x-2">
|
||||||
<strong className="block title-md-semi-bold">{t(`${i18nPrefix}.stillInEducation.title`, { ns: 'education' })}</strong>
|
<Link className="flex items-center space-x-1 system-xs-regular text-text-accent" href={eduDocLink} target="_blank" rel="noopener noreferrer">
|
||||||
{t(`${i18nPrefix}.stillInEducation.${expired ? 'expired' : 'isAboutToExpire'}`, { ns: 'education' })}
|
<div>{t('learn', { ns: 'education' })}</div>
|
||||||
|
<RiExternalLinkLine className="size-3" />
|
||||||
|
</Link>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{expired
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onClose()
|
||||||
|
setShowPricingModal()
|
||||||
|
}}
|
||||||
|
className="flex items-center space-x-1"
|
||||||
|
>
|
||||||
|
<SparklesSoftAccent className="size-4" />
|
||||||
|
<div className="text-components-button-secondary-accent-text">{t(`${i18nPrefix}.action.upgrade`, { ns: 'education' })}</div>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Button onClick={onClose}>
|
||||||
|
{t(`${i18nPrefix}.action.dismiss`, { ns: 'education' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button variant="primary" onClick={handleConfirm}>
|
||||||
|
{t(`${i18nPrefix}.action.reVerify`, { ns: 'education' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</DialogContent>
|
||||||
<strong className="block title-md-semi-bold">{t(`${i18nPrefix}.alreadyGraduated.title`, { ns: 'education' })}</strong>
|
</Dialog>
|
||||||
{t(`${i18nPrefix}.alreadyGraduated.${expired ? 'expired' : 'isAboutToExpire'}`, { ns: 'education' })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-7 flex items-center justify-between space-x-2">
|
|
||||||
<Link className="flex items-center space-x-1 system-xs-regular text-text-accent" href={eduDocLink} target="_blank" rel="noopener noreferrer">
|
|
||||||
<div>{t('learn', { ns: 'education' })}</div>
|
|
||||||
<RiExternalLinkLine className="size-3" />
|
|
||||||
</Link>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
{expired
|
|
||||||
? (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
onClose()
|
|
||||||
setShowPricingModal()
|
|
||||||
}}
|
|
||||||
className="flex items-center space-x-1"
|
|
||||||
>
|
|
||||||
<SparklesSoftAccent className="size-4" />
|
|
||||||
<div className="text-components-button-secondary-accent-text">{t(`${i18nPrefix}.action.upgrade`, { ns: 'education' })}</div>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
{t(`${i18nPrefix}.action.dismiss`, { ns: 'education' })}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button variant="primary" onClick={handleConfirm}>
|
|
||||||
{t(`${i18nPrefix}.action.reVerify`, { ns: 'education' })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user