mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 21:28:25 +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'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CustomDialog from '@/app/components/base/dialog'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -47,26 +47,34 @@ export default function FeedBack(props: DeleteAccountProps) {
|
||||
handleSuccess()
|
||||
}, [handleSuccess, props])
|
||||
return (
|
||||
<CustomDialog
|
||||
show={true}
|
||||
onClose={props.onCancel}
|
||||
title={t('account.feedbackTitle', { ns: 'common' })}
|
||||
className="max-w-[480px]"
|
||||
footer={false}
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
props.onCancel()
|
||||
}}
|
||||
>
|
||||
<label className="mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary">{t('account.feedbackLabel', { ns: 'common' })}</label>
|
||||
<Textarea
|
||||
rows={6}
|
||||
value={userFeedback}
|
||||
placeholder={t('account.feedbackPlaceholder', { ns: 'common' }) as string}
|
||||
onChange={(e) => {
|
||||
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>
|
||||
</CustomDialog>
|
||||
<DialogContent
|
||||
className="max-w-[480px] overflow-hidden!"
|
||||
backdropClassName="bg-background-overlay-backdrop backdrop-blur-[6px]"
|
||||
>
|
||||
<DialogTitle className="pr-8 pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t('account.feedbackTitle', { ns: 'common' })}
|
||||
</DialogTitle>
|
||||
<label className="mt-3 mb-1 flex items-center system-sm-semibold text-text-secondary">{t('account.feedbackLabel', { ns: 'common' })}</label>
|
||||
<Textarea
|
||||
rows={6}
|
||||
value={userFeedback}
|
||||
placeholder={t('account.feedbackPlaceholder', { ns: 'common' }) as string}
|
||||
onChange={(e) => {
|
||||
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'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { useCallback, useState } from 'react'
|
||||
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 CheckEmail from './components/check-email'
|
||||
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 (
|
||||
<CustomDialog
|
||||
show={true}
|
||||
onClose={props.onCancel}
|
||||
title={t('account.delete', { ns: 'common' })}
|
||||
className="max-w-[480px]"
|
||||
footer={false}
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
props.onCancel()
|
||||
}}
|
||||
>
|
||||
{!showVerifyEmail && <CheckEmail onCancel={props.onCancel} onConfirm={handleEmailCheckSuccess} />}
|
||||
{showVerifyEmail && (
|
||||
<VerifyEmail
|
||||
onCancel={props.onCancel}
|
||||
onConfirm={() => {
|
||||
setShowFeedbackDialog(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</CustomDialog>
|
||||
<DialogContent
|
||||
className="max-w-[480px] overflow-hidden!"
|
||||
backdropClassName="bg-background-overlay-backdrop backdrop-blur-[6px]"
|
||||
>
|
||||
<DialogTitle className="pr-8 pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t('account.delete', { ns: 'common' })}
|
||||
</DialogTitle>
|
||||
{!showVerifyEmail && <CheckEmail onCancel={props.onCancel} onConfirm={handleEmailCheckSuccess} />}
|
||||
{showVerifyEmail && (
|
||||
<VerifyEmail
|
||||
onCancel={props.onCancel}
|
||||
onConfirm={() => {
|
||||
setShowFeedbackDialog(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import AnnotationFull from '@/app/components/billing/annotation-full'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { annotationBatchImport, checkAnnotationBatchImportProgress } from '@/service/annotation'
|
||||
@ -88,37 +87,40 @@ const BatchModal: FC<IBatchModalProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={noop} className="max-w-[520px]! rounded-xl! px-8 py-6">
|
||||
<div className="relative pb-1 system-xl-medium text-text-primary">{t('batchModal.title', { ns: 'appAnnotation' })}</div>
|
||||
<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 />
|
||||
<Dialog open={isShow}>
|
||||
<DialogContent className="w-full max-w-[520px]! overflow-hidden! rounded-xl! border-none px-8 py-6 text-left align-middle">
|
||||
|
||||
{isAnnotationFull && (
|
||||
<div className="mt-4">
|
||||
<AnnotationFull />
|
||||
<div className="relative pb-1 system-xl-medium text-text-primary">{t('batchModal.title', { ns: 'appAnnotation' })}</div>
|
||||
<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 />
|
||||
|
||||
<div className="mt-[28px] flex justify-end pt-6">
|
||||
<Button className="mr-2 system-sm-medium text-text-tertiary" onClick={onCancel}>
|
||||
{t('batchModal.cancel', { ns: 'appAnnotation' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSend}
|
||||
disabled={isAnnotationFull || !currentCSV}
|
||||
loading={importStatus === ProcessStatus.PROCESSING || importStatus === ProcessStatus.WAITING}
|
||||
>
|
||||
{t('batchModal.run', { ns: 'appAnnotation' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
{isAnnotationFull && (
|
||||
<div className="mt-4">
|
||||
<AnnotationFull />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-[28px] flex justify-end pt-6">
|
||||
<Button className="mr-2 system-sm-medium text-text-tertiary" onClick={onCancel}>
|
||||
{t('batchModal.cancel', { ns: 'appAnnotation' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSend}
|
||||
disabled={isAnnotationFull || !currentCSV}
|
||||
loading={importStatus === ProcessStatus.PROCESSING || importStatus === ProcessStatus.WAITING}
|
||||
>
|
||||
{t('batchModal.run', { ns: 'appAnnotation' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(BatchModal)
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import type { FC } from 'react'
|
||||
import type { VersionHistory } from '@/types/workflow'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Input from '../../base/input'
|
||||
import Textarea from '../../base/textarea'
|
||||
|
||||
@ -66,46 +66,55 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Modal className="p-0" isShow={isOpen} onClose={onClose}>
|
||||
<div className="relative w-full p-6 pr-14 pb-4">
|
||||
<div className="title-2xl-semi-bold text-text-primary first-letter:capitalize">
|
||||
{versionInfo?.marked_name ? t('versionHistory.editVersionInfo', { ns: 'workflow' }) : t('versionHistory.nameThisVersion', { ns: 'workflow' })}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4 px-6 py-3">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
|
||||
{t('versionHistory.editField.title', { ns: 'workflow' })}
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none p-0 text-left align-middle">
|
||||
|
||||
<div className="relative w-full p-6 pr-14 pb-4">
|
||||
<div className="title-2xl-semi-bold text-text-primary first-letter:capitalize">
|
||||
{versionInfo?.marked_name ? t('versionHistory.editVersionInfo', { ns: 'workflow' }) : t('versionHistory.nameThisVersion', { ns: 'workflow' })}
|
||||
</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 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>
|
||||
<Textarea
|
||||
value={releaseNotes}
|
||||
placeholder={`${t('versionHistory.releaseNotesPlaceholder', { ns: 'workflow' })}${t('panel.optional', { ns: 'workflow' })}`}
|
||||
onChange={handleDescriptionChange}
|
||||
destructive={releaseNotesError}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end p-6 pt-5">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<Button onClick={onClose}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
<Button variant="primary" onClick={handlePublish}>{t('common.publish', { ns: 'workflow' })}</Button>
|
||||
<div className="flex flex-col gap-y-4 px-6 py-3">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex h-6 items-center system-sm-semibold text-text-secondary">
|
||||
{t('versionHistory.editField.title', { ns: 'workflow' })}
|
||||
</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>
|
||||
</Modal>
|
||||
<div className="flex justify-end p-6 pt-5">
|
||||
<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 EditModal from '../edit-modal'
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
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', () => {
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ConversationHistoriesRole } from '@/models/debug'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
|
||||
type Props = {
|
||||
isShow: boolean
|
||||
@ -25,37 +25,45 @@ const EditModal: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const [tempData, setTempData] = useState(data)
|
||||
return (
|
||||
<Modal
|
||||
title={t('feature.conversationHistory.editModal.title', { ns: 'appDebug' })}
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.userPrefix', { ns: 'appDebug' })}</div>
|
||||
<input
|
||||
className="mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10"
|
||||
value={tempData.user_prefix}
|
||||
onChange={e => setTempData({
|
||||
...tempData,
|
||||
user_prefix: e.target.value,
|
||||
})}
|
||||
/>
|
||||
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none p-6 text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t('feature.conversationHistory.editModal.title', { ns: 'appDebug' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.assistantPrefix', { ns: 'appDebug' })}</div>
|
||||
<input
|
||||
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}
|
||||
onChange={e => setTempData({
|
||||
...tempData,
|
||||
assistant_prefix: e.target.value,
|
||||
})}
|
||||
placeholder={t('chat.conversationNamePlaceholder', { ns: 'common' }) || ''}
|
||||
/>
|
||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.userPrefix', { ns: 'appDebug' })}</div>
|
||||
<input
|
||||
className="mt-2 box-border h-10 w-full rounded-lg bg-components-input-bg-normal px-3 text-sm leading-10"
|
||||
value={tempData.user_prefix}
|
||||
onChange={e => setTempData({
|
||||
...tempData,
|
||||
user_prefix: 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={() => onSave(tempData)} loading={saveLoading}>{t('operation.save', { ns: 'common' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('feature.conversationHistory.editModal.assistantPrefix', { ns: 'appDebug' })}</div>
|
||||
<input
|
||||
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}
|
||||
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 { Item as SelectItem } from './type-select'
|
||||
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 * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { checkKeys, getNewVarInWorkflow, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
|
||||
@ -141,35 +141,43 @@ const ConfigModal: FC<IConfigModalProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t(`variableConfig.${isCreate ? 'addModalTitle' : 'editModalTitle'}`, { ns: 'appDebug' })}
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="mb-8" ref={modalRef} tabIndex={-1}>
|
||||
<ConfigModalFormFields
|
||||
checkboxDefaultSelectValue={checkboxDefaultSelectValue}
|
||||
isStringInput={isStringInput}
|
||||
jsonSchemaStr={jsonSchemaStr}
|
||||
maxLength={max_length}
|
||||
modelId={modelConfig.model_id}
|
||||
onFilePayloadChange={payload => setTempPayload(payload as InputVar)}
|
||||
onJSONSchemaChange={handleJSONSchemaChange}
|
||||
onPayloadChange={handlePayloadChange}
|
||||
onTypeChange={handleTypeChange}
|
||||
onVarKeyBlur={handleVarKeyBlur}
|
||||
onVarNameChange={handleVarNameChange}
|
||||
options={options}
|
||||
selectOptions={selectOptions}
|
||||
tempPayload={tempPayload}
|
||||
t={t}
|
||||
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`variableConfig.${isCreate ? 'addModalTitle' : 'editModalTitle'}`, { ns: 'appDebug' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mb-8" ref={modalRef} tabIndex={-1}>
|
||||
<ConfigModalFormFields
|
||||
checkboxDefaultSelectValue={checkboxDefaultSelectValue}
|
||||
isStringInput={isStringInput}
|
||||
jsonSchemaStr={jsonSchemaStr}
|
||||
maxLength={max_length}
|
||||
modelId={modelConfig.model_id}
|
||||
onFilePayloadChange={payload => setTempPayload(payload as InputVar)}
|
||||
onJSONSchemaChange={handleJSONSchemaChange}
|
||||
onPayloadChange={handlePayloadChange}
|
||||
onTypeChange={handleTypeChange}
|
||||
onVarKeyBlur={handleVarKeyBlur}
|
||||
onVarNameChange={handleVarNameChange}
|
||||
options={options}
|
||||
selectOptions={selectOptions}
|
||||
tempPayload={tempPayload}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
<ModalFoot
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
</div>
|
||||
<ModalFoot
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(ConfigModal)
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
RiDatabase2Line,
|
||||
@ -32,9 +33,8 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
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 ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import { generateBasicAppFirstTimeRule, generateRule } from '@/service/debug'
|
||||
@ -282,143 +282,148 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className="min-w-[1140px] p-0!"
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="flex h-[680px] flex-wrap">
|
||||
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
||||
<div className="mb-5">
|
||||
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('generate.title', { ns: 'appDebug' })}</div>
|
||||
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('generate.description', { ns: 'appDebug' })}</div>
|
||||
</div>
|
||||
<div>
|
||||
<ModelParameterModal
|
||||
popupClassName="w-[520px]!"
|
||||
isAdvancedMode={true}
|
||||
provider={model.provider}
|
||||
completionParams={model.completion_params}
|
||||
modelId={model.name}
|
||||
setModel={handleModelChange}
|
||||
onCompletionParamsChange={handleCompletionParamsChange}
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
{isBasicMode && (
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-3 shrink-0 text-xs leading-[18px] font-semibold text-text-tertiary uppercase">{t('generate.tryIt', { ns: 'appDebug' })}</div>
|
||||
<div
|
||||
className="h-px grow"
|
||||
style={{
|
||||
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
|
||||
}}
|
||||
>
|
||||
<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="flex h-[680px] flex-wrap">
|
||||
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
||||
<div className="mb-5">
|
||||
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('generate.title', { ns: 'appDebug' })}</div>
|
||||
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('generate.description', { ns: 'appDebug' })}</div>
|
||||
</div>
|
||||
<div>
|
||||
<ModelParameterModal
|
||||
popupClassName="w-[520px]!"
|
||||
isAdvancedMode={true}
|
||||
provider={model.provider}
|
||||
completionParams={model.completion_params}
|
||||
modelId={model.name}
|
||||
setModel={handleModelChange}
|
||||
onCompletionParamsChange={handleCompletionParamsChange}
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
{isBasicMode && (
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-3 shrink-0 text-xs leading-[18px] font-semibold text-text-tertiary uppercase">{t('generate.tryIt', { ns: 'appDebug' })}</div>
|
||||
<div
|
||||
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 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)}
|
||||
/>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* inputs */}
|
||||
<div className="mt-4">
|
||||
<div>
|
||||
<div className="mb-1.5 system-sm-semibold-uppercase text-text-secondary">{t('generate.instruction', { ns: 'appDebug' })}</div>
|
||||
{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>
|
||||
|
||||
{(!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>
|
||||
)}
|
||||
|
||||
{/* inputs */}
|
||||
<div className="mt-4">
|
||||
<div>
|
||||
<div className="mb-1.5 system-sm-semibold-uppercase text-text-secondary">{t('generate.instruction', { ns: 'appDebug' })}</div>
|
||||
{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>
|
||||
{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>
|
||||
|
||||
{(!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}
|
||||
{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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(GetAutomaticRes)
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
useBoolean,
|
||||
@ -23,7 +24,6 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
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 { 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'
|
||||
@ -202,99 +202,104 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className="min-w-[1140px] p-0!"
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="relative flex h-[680px] flex-wrap">
|
||||
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
||||
<div className="mb-5">
|
||||
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('codegen.title', { ns: 'appDebug' })}</div>
|
||||
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('codegen.description', { ns: 'appDebug' })}</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<ModelParameterModal
|
||||
popupClassName="w-[520px]!"
|
||||
isAdvancedMode={true}
|
||||
provider={model.provider}
|
||||
completionParams={model.completion_params}
|
||||
modelId={model.name}
|
||||
setModel={handleModelChange}
|
||||
onCompletionParamsChange={handleCompletionParamsChange}
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
<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}
|
||||
<DialogContent className="max-h-none w-full min-w-[1140px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||
|
||||
<div className="relative flex h-[680px] flex-wrap">
|
||||
<div className="h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6">
|
||||
<div className="mb-5">
|
||||
<div className={`text-lg leading-[28px] font-bold ${s.textGradient}`}>{t('codegen.title', { ns: 'appDebug' })}</div>
|
||||
<div className="mt-1 text-[13px] font-normal text-text-tertiary">{t('codegen.description', { ns: 'appDebug' })}</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<ModelParameterModal
|
||||
popupClassName="w-[520px]!"
|
||||
isAdvancedMode={true}
|
||||
provider={model.provider}
|
||||
completionParams={model.completion_params}
|
||||
modelId={model.name}
|
||||
setModel={handleModelChange}
|
||||
onCompletionParamsChange={handleCompletionParamsChange}
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
<IdeaOutput
|
||||
value={ideaOutput}
|
||||
onChange={setIdeaOutput}
|
||||
/>
|
||||
<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>
|
||||
<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('codegen.generate', { ns: 'appDebug' })}</span>
|
||||
</Button>
|
||||
<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('codegen.generate', { ns: 'appDebug' })}</span>
|
||||
</Button>
|
||||
</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>
|
||||
{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>
|
||||
<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('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('codegen.overwriteConfirmMessage', { 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>
|
||||
</Modal>
|
||||
<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('codegen.overwriteConfirmTitle', { ns: 'appDebug' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('codegen.overwriteConfirmMessage', { 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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,12 +3,12 @@ import type { DataSet } from '@/models/datasets'
|
||||
import type { DatasetConfigs } from '@/models/debug'
|
||||
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 { RiEqualizer2Line } from '@remixicon/react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import {
|
||||
@ -123,32 +123,36 @@ const ParamsConfig = ({
|
||||
</Button>
|
||||
{
|
||||
rerankSettingModalOpen && (
|
||||
<Modal
|
||||
isShow={rerankSettingModalOpen}
|
||||
onClose={() => {
|
||||
setRerankSettingModalOpen(false)
|
||||
<Dialog
|
||||
open={rerankSettingModalOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setRerankSettingModalOpen(false)
|
||||
}
|
||||
}}
|
||||
className="sm:min-w-[528px]"
|
||||
>
|
||||
<ConfigContent
|
||||
datasetConfigs={tempDataSetConfigs}
|
||||
onChange={handleSetTempDataSetConfigs}
|
||||
selectedDatasets={selectedDatasets}
|
||||
/>
|
||||
<DialogContent className="w-full max-w-[480px] overflow-hidden! border-none text-left align-middle sm:min-w-[528px]">
|
||||
|
||||
<div className="mt-6 flex justify-end">
|
||||
<Button
|
||||
className="mr-2 shrink-0"
|
||||
onClick={() => {
|
||||
setTempDataSetConfigs(datasetConfigs)
|
||||
setRerankSettingModalOpen(false)
|
||||
}}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button variant="primary" className="shrink-0" onClick={handleSave}>{t('operation.save', { ns: 'common' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<ConfigContent
|
||||
datasetConfigs={tempDataSetConfigs}
|
||||
onChange={handleSetTempDataSetConfigs}
|
||||
selectedDatasets={selectedDatasets}
|
||||
/>
|
||||
|
||||
<div className="mt-6 flex justify-end">
|
||||
<Button
|
||||
className="mr-2 shrink-0"
|
||||
onClick={() => {
|
||||
setTempDataSetConfigs(datasetConfigs)
|
||||
setRerankSettingModalOpen(false)
|
||||
}}
|
||||
>
|
||||
{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 Modal from '@/app/components/base/modal'
|
||||
|
||||
type DSLConfirmModalProps = {
|
||||
versions?: {
|
||||
@ -20,32 +27,36 @@ const DSLConfirmModal = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={() => onCancel()}
|
||||
className="w-[480px]"
|
||||
<AlertDialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.systemVersion}</span>
|
||||
</div>
|
||||
<AlertDialogContent className="w-[480px] overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</AlertDialogTitle>
|
||||
<AlertDialogDescription render={<div />} className="flex grow flex-col system-md-regular text-text-secondary">
|
||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.systemVersion}</span>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
||||
<Button variant="secondary" onClick={() => onCancel()}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button variant="primary" tone="destructive" onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton variant="secondary">{t('newApp.Cancel', { ns: 'app' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,14 +3,13 @@
|
||||
import type { MouseEventHandler } from 'react'
|
||||
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 { RiCloseLine } from '@remixicon/react'
|
||||
import { useDebounceFn, useKeyPress } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
@ -219,108 +218,112 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
className="w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 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 h-8 w-8 cursor-pointer items-center"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
||||
<Dialog open={show}>
|
||||
<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">
|
||||
|
||||
<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 h-8 w-8 cursor-pointer items-center"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<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">
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative flex h-full cursor-pointer items-center',
|
||||
currentTab === tab.key && 'text-text-primary',
|
||||
)}
|
||||
onClick={() => setCurrentTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{
|
||||
currentTab === tab.key && (
|
||||
<div className="absolute bottom-0 h-[2px] w-full bg-util-colors-blue-brand-blue-brand-600"></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className="px-6 py-4">
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className="mt-0"
|
||||
file={currentFile}
|
||||
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 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 => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative flex h-full cursor-pointer items-center',
|
||||
currentTab === tab.key && 'text-text-primary',
|
||||
)}
|
||||
onClick={() => setCurrentTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{
|
||||
currentTab === tab.key && (
|
||||
<div className="absolute bottom-0 h-[2px] w-full bg-util-colors-blue-brand-blue-brand-600"></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className="px-6 py-4">
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className="mt-0"
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{isAppsFull && (
|
||||
<div className="px-6">
|
||||
<AppsFull className="mt-0" loc="app-create-dsl" />
|
||||
)
|
||||
}
|
||||
{
|
||||
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>
|
||||
)}
|
||||
<div className="flex justify-end px-6 py-5">
|
||||
<Button className="mr-2" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button
|
||||
disabled={buttonDisabled}
|
||||
variant="primary"
|
||||
onClick={handleCreateApp}
|
||||
className="gap-1"
|
||||
>
|
||||
<span>{t('newApp.Create', { ns: 'app' })}</span>
|
||||
<ShortcutsName keys={['ctrl', '↵']} bgColor="white" />
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
isShow={showErrorModal}
|
||||
onClose={() => setShowErrorModal(false)}
|
||||
className="w-[480px]"
|
||||
{isAppsFull && (
|
||||
<div className="px-6">
|
||||
<AppsFull className="mt-0" loc="app-create-dsl" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end px-6 py-5">
|
||||
<Button className="mr-2" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button
|
||||
disabled={buttonDisabled}
|
||||
variant="primary"
|
||||
onClick={handleCreateApp}
|
||||
className="gap-1"
|
||||
>
|
||||
<span>{t('newApp.Create', { ns: 'app' })}</span>
|
||||
<ShortcutsName keys={['ctrl', '↵']} bgColor="white" />
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={showErrorModal}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
setShowErrorModal(false)
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="primary" tone="destructive" onClick={onDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<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="primary" tone="destructive" onClick={onDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
'use client'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
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 { RiCloseLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
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 { useProviderContext } from '@/context/provider-context'
|
||||
import AppIconPicker from '../../base/app-icon-picker'
|
||||
@ -71,40 +69,39 @@ const DuplicateAppModal = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isShow={show}
|
||||
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>
|
||||
<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"
|
||||
/>
|
||||
<Dialog open={show}>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none px-8 text-left align-middle">
|
||||
|
||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onHide}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</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>
|
||||
</Modal>
|
||||
<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>
|
||||
{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 && (
|
||||
<AppIconPicker
|
||||
onSelect={(payload) => {
|
||||
|
||||
@ -12,9 +12,9 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
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 { RiCloseLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
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 { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
@ -109,69 +108,68 @@ const SwitchAppModal = ({ show, appDetail, inAppDetail = false, onSuccess, onClo
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
className={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>
|
||||
<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"
|
||||
/>
|
||||
<Dialog open={show}>
|
||||
<DialogContent className={cn('w-full overflow-hidden! border-none text-left align-middle', cn('w-[600px] max-w-[600px] p-8'))}>
|
||||
|
||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onClose}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</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 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="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 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>
|
||||
</Modal>
|
||||
<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>
|
||||
{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
|
||||
open={showConfirmDelete}
|
||||
onOpenChange={handleConfirmDeleteOpenChange}
|
||||
|
||||
@ -4,15 +4,14 @@ import type { OnImageInput } from './ImageInput'
|
||||
import type { AppIconType, ImageFile } from '@/types/app'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { RiImageCircleAiLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config'
|
||||
import Divider from '../divider'
|
||||
import EmojiPickerInner from '../emoji-picker/Inner'
|
||||
import { useLocalFileUploader } from '../image-uploader/hooks'
|
||||
import Modal from '../modal'
|
||||
import ImageInput from './ImageInput'
|
||||
import s from './style.module.css'
|
||||
import getCroppedImg from './utils'
|
||||
@ -45,7 +44,6 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
onSelect,
|
||||
onClose,
|
||||
initialEmoji,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -113,57 +111,54 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={noop}
|
||||
isShow
|
||||
closable={false}
|
||||
wrapperClassName={className}
|
||||
className={cn(s.container, 'h-[462px]! w-[362px]! p-0!')}
|
||||
>
|
||||
{!DISABLE_UPLOAD_IMAGE_AS_ICON && (
|
||||
<div className="w-full p-2 pb-0">
|
||||
<div className="flex items-center justify-center gap-2 rounded-xl bg-background-body p-1 text-text-primary">
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
type="button"
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'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',
|
||||
)}
|
||||
onClick={() => setActiveTab(tab.key as AppIconType)}
|
||||
>
|
||||
{tab.icon}
|
||||
{' '}
|
||||
<Dialog open>
|
||||
<DialogContent className={cn('max-h-none w-full overflow-hidden! border-none text-left align-middle', s.container, 'h-[462px]! w-[362px]! p-0!')}>
|
||||
|
||||
{!DISABLE_UPLOAD_IMAGE_AS_ICON && (
|
||||
<div className="w-full p-2 pb-0">
|
||||
<div className="flex items-center justify-center gap-2 rounded-xl bg-background-body p-1 text-text-primary">
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
type="button"
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'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',
|
||||
)}
|
||||
onClick={() => setActiveTab(tab.key as AppIconType)}
|
||||
>
|
||||
{tab.icon}
|
||||
{' '}
|
||||
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</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>
|
||||
)}
|
||||
|
||||
{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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -62,18 +62,15 @@ vi.mock('@/next/navigation', () => ({
|
||||
usePathname: () => '/test',
|
||||
}))
|
||||
|
||||
// Mock Modal to avoid Headless UI issues in tests
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children, isShow, title }: { children: React.ReactNode, isShow: boolean, title: React.ReactNode }) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="modal">
|
||||
{!!title && <div data-testid="modal-title">{title}</div>}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||
open === false ? null : <>{children}</>,
|
||||
DialogContent: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="modal">{children}</div>
|
||||
),
|
||||
DialogTitle: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="modal-title">{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('Sidebar Index', () => {
|
||||
|
||||
@ -4,25 +4,13 @@ import userEvent from '@testing-library/user-event'
|
||||
import * as ReactI18next from 'react-i18next'
|
||||
import RenameModal from '../rename-modal'
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({
|
||||
title,
|
||||
isShow,
|
||||
children,
|
||||
}: {
|
||||
title: ReactNode
|
||||
isShow: boolean
|
||||
children: ReactNode
|
||||
}) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div role="dialog">
|
||||
<h2>{title}</h2>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: ReactNode, open?: boolean }) =>
|
||||
open === false ? null : <>{children}</>,
|
||||
DialogContent: ({ children }: { children: ReactNode }) => (
|
||||
<div role="dialog">{children}</div>
|
||||
),
|
||||
DialogTitle: ({ children }: { children: ReactNode }) => <h2>{children}</h2>,
|
||||
}))
|
||||
|
||||
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 { Button } from '@langgenius/dify-ui/button'
|
||||
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 { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import EmojiPickerInner from './Inner'
|
||||
|
||||
type IEmojiPickerProps = {
|
||||
@ -21,7 +20,6 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
||||
isModal = true,
|
||||
onSelect,
|
||||
onClose,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [selectedEmoji, setSelectedEmoji] = useState('')
|
||||
@ -34,39 +32,36 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
||||
|
||||
return isModal
|
||||
? (
|
||||
<Modal
|
||||
onClose={noop}
|
||||
isShow
|
||||
closable={false}
|
||||
wrapperClassName={className}
|
||||
className={cn('flex max-h-[552px] flex-col rounded-xl border-[0.5px] border-divider-subtle p-0 shadow-xl')}
|
||||
>
|
||||
<EmojiPickerInner
|
||||
className="pt-3"
|
||||
onSelect={handleSelectEmoji}
|
||||
/>
|
||||
<Divider className="mt-3 mb-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
|
||||
disabled={selectedEmoji === '' || !selectedBackground}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
onSelect?.(selectedEmoji, selectedBackground!)
|
||||
}}
|
||||
>
|
||||
{t('iconPicker.ok', { ns: 'app' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Dialog open>
|
||||
<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')}>
|
||||
|
||||
<EmojiPickerInner
|
||||
className="pt-3"
|
||||
onSelect={handleSelectEmoji}
|
||||
/>
|
||||
<Divider className="mt-3 mb-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
|
||||
disabled={selectedEmoji === '' || !selectedBackground}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
onSelect?.(selectedEmoji, selectedBackground!)
|
||||
}}
|
||||
>
|
||||
{t('iconPicker.ok', { ns: 'app' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
: <></>
|
||||
}
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { AnnotationReplyConfig } from '@/models/debug'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
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 { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
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)
|
||||
}
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={onHide} className="!mt-14 !w-[640px] !max-w-none !p-6">
|
||||
<div className="mb-2 title-2xl-semi-bold text-text-primary">
|
||||
{t(`initSetup.${isInit ? 'title' : 'configTitle'}`, { ns: 'appAnnotation' })}
|
||||
</div>
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
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">
|
||||
<Item title={t('feature.annotation.scoreThreshold.title', { ns: 'appDebug' })} tooltip={t('feature.annotation.scoreThreshold.description', { ns: 'appDebug' })}>
|
||||
<ScoreSlider
|
||||
className="mt-1"
|
||||
value={(annotationConfig.score_threshold || ANNOTATION_DEFAULT.score_threshold) * 100}
|
||||
onChange={(val) => {
|
||||
setAnnotationConfig({
|
||||
...annotationConfig,
|
||||
score_threshold: val / 100,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Item>
|
||||
<div className="mb-2 title-2xl-semi-bold text-text-primary">
|
||||
{t(`initSetup.${isInit ? 'title' : 'configTitle'}`, { ns: 'appAnnotation' })}
|
||||
</div>
|
||||
|
||||
<Item title={t('modelProvider.embeddingModel.key', { ns: 'common' })} tooltip={t('embeddingModelSwitchTip', { ns: 'appAnnotation' })}>
|
||||
<div className="pt-1">
|
||||
<ModelSelector
|
||||
defaultModel={embeddingModel && {
|
||||
provider: embeddingModel.providerName,
|
||||
model: embeddingModel.modelName,
|
||||
}}
|
||||
modelList={embeddingsModelList}
|
||||
onSelect={(val) => {
|
||||
setEmbeddingModel({
|
||||
providerName: val.provider,
|
||||
modelName: val.model,
|
||||
<div className="mt-6 space-y-3">
|
||||
<Item title={t('feature.annotation.scoreThreshold.title', { ns: 'appDebug' })} tooltip={t('feature.annotation.scoreThreshold.description', { ns: 'appDebug' })}>
|
||||
<ScoreSlider
|
||||
className="mt-1"
|
||||
value={(annotationConfig.score_threshold || ANNOTATION_DEFAULT.score_threshold) * 100}
|
||||
onChange={(val) => {
|
||||
setAnnotationConfig({
|
||||
...annotationConfig,
|
||||
score_threshold: val / 100,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Item>
|
||||
</div>
|
||||
</Item>
|
||||
|
||||
<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>
|
||||
</Modal>
|
||||
<Item title={t('modelProvider.embeddingModel.key', { ns: 'common' })} tooltip={t('embeddingModelSwitchTip', { ns: 'appAnnotation' })}>
|
||||
<div className="pt-1">
|
||||
<ModelSelector
|
||||
defaultModel={embeddingModel && {
|
||||
provider: embeddingModel.providerName,
|
||||
model: embeddingModel.modelName,
|
||||
}}
|
||||
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)
|
||||
|
||||
@ -3,12 +3,11 @@ import type { CodeBasedExtensionItem } from '@/models/common'
|
||||
import type { ModerationConfig, ModerationContentConfig } from '@/models/debug'
|
||||
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 { noop } from 'es-toolkit/function'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@ -226,165 +225,164 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
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
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="cursor-pointer p-1"
|
||||
onClick={onCancel}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
onCancel()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="i-ri-close-line h-4 w-4 text-text-tertiary" />
|
||||
<Dialog open>
|
||||
<DialogContent className="mt-14! w-[600px]! max-w-none! overflow-hidden! border-none p-6! text-left align-middle">
|
||||
|
||||
<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
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="cursor-pointer p-1"
|
||||
onClick={onCancel}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
onCancel()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="i-ri-close-line h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||
{t('feature.moderation.modal.provider.title', { ns: 'appDebug' })}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2.5">
|
||||
{
|
||||
providers.map(provider => (
|
||||
<div
|
||||
key={provider.key}
|
||||
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',
|
||||
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 === 'openai_moderation' && provider.key === 'openai_moderation' && !isOpenAIProviderConfigured && 'text-text-disabled',
|
||||
)}
|
||||
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="py-2">
|
||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||
{t('feature.moderation.modal.provider.title', { ns: 'appDebug' })}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2.5">
|
||||
{
|
||||
providers.map(provider => (
|
||||
<div
|
||||
key={provider.key}
|
||||
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',
|
||||
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 === 'openai_moderation' && provider.key === 'openai_moderation' && !isOpenAIProviderConfigured && 'text-text-disabled',
|
||||
)}
|
||||
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>
|
||||
{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>
|
||||
{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' })}
|
||||
localeData.type === 'keywords' && (
|
||||
<div className="py-2">
|
||||
<div className="mb-1 text-sm font-medium text-text-primary">{t('feature.moderation.modal.provider.keywords', { ns: 'appDebug' })}</div>
|
||||
<div className="mb-2 text-xs text-text-tertiary">{t('feature.moderation.modal.keywords.tip', { ns: 'appDebug' })}</div>
|
||||
<div className="relative h-[88px] rounded-lg bg-components-input-bg-normal px-3 py-2">
|
||||
<textarea
|
||||
value={localeData.config?.keywords || ''}
|
||||
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' }) || ''}
|
||||
/>
|
||||
<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>{(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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
localeData.type === 'keywords' && (
|
||||
<div className="py-2">
|
||||
<div className="mb-1 text-sm font-medium text-text-primary">{t('feature.moderation.modal.provider.keywords', { ns: 'appDebug' })}</div>
|
||||
<div className="mb-2 text-xs text-text-tertiary">{t('feature.moderation.modal.keywords.tip', { ns: 'appDebug' })}</div>
|
||||
<div className="relative h-[88px] rounded-lg bg-components-input-bg-normal px-3 py-2">
|
||||
<textarea
|
||||
value={localeData.config?.keywords || ''}
|
||||
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' }) || ''}
|
||||
/>
|
||||
<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>{(localeData.config?.keywords || '').split('\n').filter(Boolean).length}</span>
|
||||
/
|
||||
<span className="text-text-tertiary">
|
||||
100
|
||||
{t('feature.moderation.modal.keywords.line', { ns: 'appDebug' })}
|
||||
</span>
|
||||
{
|
||||
localeData.type === 'api' && (
|
||||
<div className="py-2">
|
||||
<div className="flex h-9 items-center justify-between">
|
||||
<div className="text-sm font-medium text-text-primary">{t('apiBasedExtension.selector.title', { ns: 'common' })}</div>
|
||||
<a
|
||||
href={docLink('/use-dify/workspace/api-extension/api-extension')}
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
localeData.type === 'api' && (
|
||||
<div className="py-2">
|
||||
<div className="flex h-9 items-center justify-between">
|
||||
<div className="text-sm font-medium text-text-primary">{t('apiBasedExtension.selector.title', { ns: 'common' })}</div>
|
||||
<a
|
||||
href={docLink('/use-dify/workspace/api-extension/api-extension')}
|
||||
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}
|
||||
)
|
||||
}
|
||||
{
|
||||
systemTypes.findIndex(t => t === localeData.type) < 0
|
||||
&& currentProvider?.form_schema
|
||||
&& (
|
||||
<FormGeneration
|
||||
forms={currentProvider?.form_schema}
|
||||
value={localeData.config}
|
||||
onChange={handleDataExtraChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
systemTypes.findIndex(t => t === localeData.type) < 0
|
||||
&& currentProvider?.form_schema
|
||||
&& (
|
||||
<FormGeneration
|
||||
forms={currentProvider?.form_schema}
|
||||
value={localeData.config}
|
||||
onChange={handleDataExtraChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Divider bgStyle="gradient" className="my-3 h-px" />
|
||||
<ModerationContent
|
||||
title={t('feature.moderation.modal.content.input', { ns: 'appDebug' }) || ''}
|
||||
config={localeData.config?.inputs_config || { enabled: false, preset_response: '' }}
|
||||
onConfigChange={config => handleDataContentChange('inputs_config', config)}
|
||||
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
||||
showPreset={localeData.type !== 'api'}
|
||||
/>
|
||||
<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)}
|
||||
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
||||
showPreset={localeData.type !== 'api'}
|
||||
/>
|
||||
<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">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
className="mr-2"
|
||||
>
|
||||
{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>
|
||||
)
|
||||
}
|
||||
<Divider bgStyle="gradient" className="my-3 h-px" />
|
||||
<ModerationContent
|
||||
title={t('feature.moderation.modal.content.input', { ns: 'appDebug' }) || ''}
|
||||
config={localeData.config?.inputs_config || { enabled: false, preset_response: '' }}
|
||||
onConfigChange={config => handleDataContentChange('inputs_config', config)}
|
||||
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
||||
showPreset={localeData.type !== 'api'}
|
||||
/>
|
||||
<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)}
|
||||
info={(localeData.type === 'api' && t('feature.moderation.modal.content.fromApi', { ns: 'appDebug' })) || ''}
|
||||
showPreset={localeData.type !== 'api'}
|
||||
/>
|
||||
<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">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
className="mr-2"
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSave}
|
||||
disabled={localeData.type === 'openai_moderation' && !isOpenAIProviderConfigured}
|
||||
>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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 { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React 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 ActionButton from '../../../action-button'
|
||||
import { VariableX } from '../../../icons/src/vender/workflow'
|
||||
import Modal from '../../../modal'
|
||||
import InputField from './input-field'
|
||||
import VariableBlock from './variable-block'
|
||||
|
||||
@ -157,20 +157,24 @@ const HITLInputComponentUI: FC<HITLInputComponentUIProps> = ({
|
||||
</div>
|
||||
|
||||
{isShowEditModal && (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={hideEditModal}
|
||||
wrapperClassName="z-999"
|
||||
className="max-w-[372px] p-0!"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
hideEditModal()
|
||||
}}
|
||||
>
|
||||
<InputField
|
||||
nodeId={nodeId}
|
||||
isEdit
|
||||
payload={formInput}
|
||||
onChange={handleChange}
|
||||
onCancel={hideEditModal}
|
||||
/>
|
||||
</Modal>
|
||||
<DialogContent className="w-full max-w-[372px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||
|
||||
<InputField
|
||||
nodeId={nodeId}
|
||||
isEdit
|
||||
payload={formInput}
|
||||
onChange={handleChange}
|
||||
onCancel={hideEditModal}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { LexicalCommand } from 'lexical'
|
||||
import type { ShortcutPopupInsertHandler } from '../index'
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
|
||||
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
|
||||
@ -50,7 +50,7 @@ const CONTENT_EDITABLE_ID = 'ce'
|
||||
type MinimalEditorProps = {
|
||||
withContainer?: 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
|
||||
onOpen?: () => void
|
||||
onClose?: () => void
|
||||
@ -316,7 +316,7 @@ describe('ShortcutsPopupPlugin', () => {
|
||||
|
||||
it('renders children as render function and provides close/onInsert', async () => {
|
||||
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>
|
||||
<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>
|
||||
@ -346,7 +346,7 @@ describe('ShortcutsPopupPlugin', () => {
|
||||
const TEST_COMMAND = createCommand<unknown>('TEST_INSERT_COMMAND')
|
||||
render(
|
||||
<MinimalEditor>
|
||||
{(close: () => void, onInsert: (cmd: LexicalCommand<unknown>, params: unknown[]) => void) => (
|
||||
{(close: () => void, onInsert: ShortcutPopupInsertHandler) => (
|
||||
<div>
|
||||
<button type="button" data-testid="insert-btn" onClick={() => onInsert(TEST_COMMAND, ['value'])}>Insert</button>
|
||||
</div>
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
export const SHORTCUTS_EMPTY_CONTENT = 'shortcuts_empty_content'
|
||||
export type ShortcutPopupInsertHandler = <Payload>(command: LexicalCommand<Payload>, params: Payload) => void
|
||||
|
||||
// Hotkey can be:
|
||||
// - string: 'mod+/'
|
||||
@ -33,7 +34,7 @@ export type Hotkey = string | string[] | string[][] | ((e: KeyboardEvent) => boo
|
||||
|
||||
type ShortcutPopupPluginProps = {
|
||||
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
|
||||
container?: Element | null
|
||||
onOpen?: () => void
|
||||
@ -158,8 +159,9 @@ export default function ShortcutsPopupPlugin({
|
||||
apply({ availableWidth, availableHeight, elements }) {
|
||||
Object.assign(elements.floating.style, {
|
||||
maxWidth: `${Math.min(400, availableWidth)}px`,
|
||||
maxHeight: `${Math.min(300, availableHeight)}px`,
|
||||
overflow: 'auto',
|
||||
maxHeight: `${Math.max(0, availableHeight)}px`,
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
})
|
||||
},
|
||||
padding: 8,
|
||||
@ -236,7 +238,7 @@ export default function ShortcutsPopupPlugin({
|
||||
|
||||
setOpen(true)
|
||||
onOpen?.()
|
||||
}, [onOpen])
|
||||
}, [editor, onOpen, refs])
|
||||
|
||||
const closePortal = useCallback(() => {
|
||||
setOpen(false)
|
||||
@ -280,7 +282,7 @@ export default function ShortcutsPopupPlugin({
|
||||
return () => document.removeEventListener('mousedown', onMouseDown, false)
|
||||
}, [open, closePortal])
|
||||
|
||||
const handleInsert = useCallback((command: LexicalCommand<unknown>, params: any) => {
|
||||
const handleInsert = useCallback(<Payload,>(command: LexicalCommand<Payload>, params: Payload) => {
|
||||
editor.dispatchCommand(command, params)
|
||||
closePortal()
|
||||
}, [editor, closePortal])
|
||||
|
||||
@ -29,26 +29,26 @@ type ModalSnapshot = {
|
||||
className?: string
|
||||
}
|
||||
let mockModalProps: ModalSnapshot | null = null
|
||||
vi.mock('../../../base/modal', () => ({
|
||||
default: ({ isShow, children, onClose, closable, className }: { isShow: boolean, children: React.ReactNode, onClose: () => void, closable?: boolean, className?: string }) => {
|
||||
let mockOnOpenChange: ((open: boolean) => void) | undefined
|
||||
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 = {
|
||||
isShow,
|
||||
closable,
|
||||
isShow: true,
|
||||
closable: true,
|
||||
className,
|
||||
}
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="annotation-full-modal" data-classname={className ?? ''}>
|
||||
{closable && (
|
||||
<button type="button" data-testid="mock-modal-close" onClick={onClose}>
|
||||
close
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
DialogCloseButton: () => <button type="button" data-testid="mock-modal-close" onClick={() => mockOnOpenChange?.(false)}>close</button>,
|
||||
}))
|
||||
|
||||
describe('AnnotationFullModal', () => {
|
||||
@ -56,6 +56,7 @@ describe('AnnotationFullModal', () => {
|
||||
vi.clearAllMocks()
|
||||
mockUpgradeBtnProps = null
|
||||
mockModalProps = null
|
||||
mockOnOpenChange = undefined
|
||||
})
|
||||
|
||||
// Rendering marketing copy inside modal
|
||||
@ -71,8 +72,9 @@ describe('AnnotationFullModal', () => {
|
||||
expect(mockModalProps).toEqual(expect.objectContaining({
|
||||
isShow: true,
|
||||
closable: true,
|
||||
className: 'p-0!',
|
||||
className: expect.stringContaining('p-0!'),
|
||||
}))
|
||||
expect(mockModalProps?.className).toContain('w-full')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import GridMask from '@/app/components/base/grid-mask'
|
||||
import Modal from '../../base/modal'
|
||||
import UpgradeBtn from '../upgrade-btn'
|
||||
import s from './style.module.css'
|
||||
import Usage from './usage'
|
||||
@ -20,28 +20,33 @@ const AnnotationFullModal: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={onHide}
|
||||
closable
|
||||
className="p-0!"
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<DialogContent className="w-full overflow-hidden! border-none p-0! text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<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>
|
||||
<Usage className="mt-4" />
|
||||
<div className="mt-7 flex justify-end">
|
||||
<UpgradeBtn loc="annotation-create" />
|
||||
</div>
|
||||
</div>
|
||||
</GridMask>
|
||||
</Modal>
|
||||
</GridMask>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
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 Modal from '@/app/components/base/modal'
|
||||
|
||||
type DSLConfirmModalProps = {
|
||||
versions?: {
|
||||
@ -20,32 +27,36 @@ const DSLConfirmModal = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={() => onCancel()}
|
||||
className="w-[480px]"
|
||||
<AlertDialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.systemVersion}</span>
|
||||
</div>
|
||||
<AlertDialogContent className="w-[480px] overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</AlertDialogTitle>
|
||||
<AlertDialogDescription render={<div />} className="flex grow flex-col system-md-regular text-text-secondary">
|
||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions.systemVersion}</span>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
||||
<Button variant="secondary" onClick={() => onCancel()}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button variant="primary" tone="destructive" onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton variant="secondary">{t('newApp.Cancel', { ns: 'app' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm} disabled={confirmDisabled}>{t('newApp.Confirm', { ns: 'app' })}</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import DSLConfirmModal from './dsl-confirm-modal'
|
||||
import Header from './header'
|
||||
import { CreateFromDSLModalTab, useDSLImport } from './hooks/use-dsl-import'
|
||||
@ -58,51 +57,50 @@ const CreateFromDSLModal = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
className="w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl"
|
||||
isShow={show}
|
||||
onClose={noop}
|
||||
>
|
||||
<Header onClose={onClose} />
|
||||
<Tab
|
||||
currentTab={currentTab}
|
||||
setCurrentTab={setCurrentTab}
|
||||
/>
|
||||
<div className="px-6 py-4">
|
||||
{currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className="mt-0"
|
||||
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)}
|
||||
<Dialog open={show}>
|
||||
<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">
|
||||
|
||||
<Header onClose={onClose} />
|
||||
<Tab
|
||||
currentTab={currentTab}
|
||||
setCurrentTab={setCurrentTab}
|
||||
/>
|
||||
<div className="px-6 py-4">
|
||||
{currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className="mt-0"
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2 p-6 pt-5">
|
||||
<Button onClick={onClose}>
|
||||
{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>
|
||||
</Modal>
|
||||
)}
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2 p-6 pt-5">
|
||||
<Button onClick={onClose}>
|
||||
{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 && (
|
||||
<DSLConfirmModal
|
||||
versions={versions}
|
||||
|
||||
@ -8,12 +8,12 @@ import {
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useCreatePipelineDatasetFromCustomized } from '@/service/knowledge/use-create-dataset'
|
||||
@ -153,16 +153,21 @@ const TemplateCard = ({
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
{showEditModal && (
|
||||
<Modal
|
||||
isShow={showEditModal}
|
||||
onClose={closeEditModal}
|
||||
className="max-w-[520px] p-0"
|
||||
<Dialog
|
||||
open={showEditModal}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
closeEditModal()
|
||||
}}
|
||||
>
|
||||
<EditPipelineInfo
|
||||
pipeline={pipeline}
|
||||
onClose={closeEditModal}
|
||||
/>
|
||||
</Modal>
|
||||
<DialogContent className="w-full max-w-[520px] overflow-hidden! border-none p-0 text-left align-middle">
|
||||
|
||||
<EditPipelineInfo
|
||||
pipeline={pipeline}
|
||||
onClose={closeEditModal}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
<AlertDialog open={showDeleteConfirm} onOpenChange={open => !open && onCancelDelete()}>
|
||||
<AlertDialogContent>
|
||||
@ -183,18 +188,23 @@ const TemplateCard = ({
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{showDetailModal && (
|
||||
<Modal
|
||||
isShow={showDetailModal}
|
||||
onClose={closeDetailsModal}
|
||||
className="h-[calc(100vh-64px)] max-w-[1680px] rounded-3xl p-0"
|
||||
<Dialog
|
||||
open={showDetailModal}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
closeDetailsModal()
|
||||
}}
|
||||
>
|
||||
<Details
|
||||
id={pipeline.id}
|
||||
type={type}
|
||||
onClose={closeDetailsModal}
|
||||
onApplyTemplate={handleUseTemplate}
|
||||
/>
|
||||
</Modal>
|
||||
<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">
|
||||
|
||||
<Details
|
||||
id={pipeline.id}
|
||||
type={type}
|
||||
onClose={closeDetailsModal}
|
||||
onApplyTemplate={handleUseTemplate}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
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 * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { createEmptyDataset } from '@/service/datasets'
|
||||
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
||||
@ -46,21 +46,30 @@ const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Modal isShow={show} onClose={onHide} className={cn(s.modal, '!max-w-[520px]', 'px-8')}>
|
||||
<div className={s.modalHeader}>
|
||||
<div className={s.title}>{t('stepOne.modal.title', { ns: 'datasetCreation' })}</div>
|
||||
<span className={s.close} onClick={onHide} />
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
</Modal>
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<DialogContent className={cn('w-full overflow-hidden! border-none text-left align-middle', cn(s.modal, '!max-w-[520px]', 'px-8'))}>
|
||||
|
||||
<div className={s.modalHeader}>
|
||||
<div className={s.title}>{t('stepOne.modal.title', { ns: 'datasetCreation' })}</div>
|
||||
<span className={s.close} onClick={onHide} />
|
||||
</div>
|
||||
<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>
|
||||
<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
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
'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 * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import s from './index.module.css'
|
||||
|
||||
type IProps = {
|
||||
@ -25,20 +32,24 @@ const StopEmbeddingModal = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={onHide}
|
||||
className={cn(s.modal, 'max-w-[480px]!', 'px-8')}
|
||||
<AlertDialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div className={s.icon} />
|
||||
<span className={s.close} onClick={onHide} />
|
||||
<div className={s.title}>{t('stepThree.modelTitle', { ns: 'datasetCreation' })}</div>
|
||||
<div className={s.content}>{t('stepThree.modelContent', { ns: 'datasetCreation' })}</div>
|
||||
<div className="flex flex-row-reverse">
|
||||
<Button className="ml-2 w-24" variant="primary" onClick={submit}>{t('stepThree.modelButtonConfirm', { ns: 'datasetCreation' })}</Button>
|
||||
<Button className="w-24" onClick={onHide}>{t('stepThree.modelButtonCancel', { ns: 'datasetCreation' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<AlertDialogContent className={cn(s.modal, 'max-w-[480px]! overflow-hidden! border-none px-8 py-6 text-left align-middle shadow-xl')}>
|
||||
<div className={s.icon} />
|
||||
<span className={s.close} onClick={onHide} />
|
||||
<AlertDialogTitle className={s.title}>{t('stepThree.modelTitle', { ns: 'datasetCreation' })}</AlertDialogTitle>
|
||||
<AlertDialogDescription className={s.content}>{t('stepThree.modelContent', { ns: 'datasetCreation' })}</AlertDialogDescription>
|
||||
<AlertDialogActions className="flex-row-reverse gap-0 p-0">
|
||||
<AlertDialogConfirmButton className="ml-2 w-24" tone="default" onClick={submit}>{t('stepThree.modelButtonConfirm', { ns: 'datasetCreation' })}</AlertDialogConfirmButton>
|
||||
<AlertDialogCancelButton className="w-24" variant="secondary">{t('stepThree.modelButtonCancel', { ns: 'datasetCreation' })}</AlertDialogCancelButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { renameDocumentName } from '@/service/datasets'
|
||||
|
||||
type Props = {
|
||||
@ -55,23 +55,31 @@ const RenameModal: FC<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('list.table.rename', { ns: 'datasetDocuments' })}
|
||||
isShow
|
||||
onClose={onClose}
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('list.table.name', { ns: 'datasetDocuments' })}</div>
|
||||
<Input
|
||||
className="mt-2 h-10"
|
||||
value={newName}
|
||||
onChange={e => setNewName(e.target.value)}
|
||||
/>
|
||||
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t('list.table.rename', { ns: 'datasetDocuments' })}
|
||||
</DialogTitle>
|
||||
|
||||
<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>
|
||||
</Modal>
|
||||
<div className="mt-6 text-sm leading-[21px] font-medium text-text-primary">{t('list.table.name', { ns: 'datasetDocuments' })}</div>
|
||||
<Input
|
||||
className="mt-2 h-10"
|
||||
value={newName}
|
||||
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)
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ChunkingMode, FileItem } from '@/models/datasets'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import CSVDownloader from './csv-downloader'
|
||||
import CSVUploader from './csv-uploader'
|
||||
|
||||
@ -41,27 +40,30 @@ const BatchModal: FC<IBatchModalProps> = ({
|
||||
}, [isShow])
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={noop} className="max-w-[520px]! rounded-xl! px-8 py-6">
|
||||
<div className="relative pb-1 text-xl leading-[30px] font-medium text-text-primary">{t('list.batchModal.title', { ns: 'datasetDocuments' })}</div>
|
||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onCancel}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-secondary" />
|
||||
</div>
|
||||
<CSVUploader
|
||||
file={currentCSV}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
<CSVDownloader
|
||||
docForm={docForm}
|
||||
/>
|
||||
<div className="mt-[28px] flex justify-end pt-6">
|
||||
<Button className="mr-2" onClick={onCancel}>
|
||||
{t('list.batchModal.cancel', { ns: 'datasetDocuments' })}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSend} disabled={!currentCSV || !currentCSV.file || !currentCSV.file.id}>
|
||||
{t('list.batchModal.run', { ns: 'datasetDocuments' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Dialog open={isShow}>
|
||||
<DialogContent className="w-full max-w-[520px]! overflow-hidden! rounded-xl! border-none px-8 py-6 text-left align-middle">
|
||||
|
||||
<div className="relative pb-1 text-xl leading-[30px] font-medium text-text-primary">{t('list.batchModal.title', { ns: 'datasetDocuments' })}</div>
|
||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onCancel}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-secondary" />
|
||||
</div>
|
||||
<CSVUploader
|
||||
file={currentCSV}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
<CSVDownloader
|
||||
docForm={docForm}
|
||||
/>
|
||||
<div className="mt-[28px] flex justify-end pt-6">
|
||||
<Button className="mr-2" onClick={onCancel}>
|
||||
{t('list.batchModal.cancel', { ns: 'datasetDocuments' })}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSend} disabled={!currentCSV || !currentCSV.file || !currentCSV.file.id}>
|
||||
{t('list.batchModal.run', { ns: 'datasetDocuments' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(BatchModal)
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
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 { RiLoader2Line } from '@remixicon/react'
|
||||
import { useCountDown } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import * as React from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
type IDefaultContentProps = {
|
||||
@ -22,18 +29,18 @@ const DefaultContent: FC<IDefaultContentProps> = React.memo(({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pb-4">
|
||||
<span className="title-2xl-semi-bold text-text-primary">{t('segment.regenerationConfirmTitle', { ns: 'datasetDocuments' })}</span>
|
||||
<p className="system-md-regular text-text-secondary">{t('segment.regenerationConfirmMessage', { ns: 'datasetDocuments' })}</p>
|
||||
<div className="p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('segment.regenerationConfirmTitle', { ns: 'datasetDocuments' })}</AlertDialogTitle>
|
||||
<AlertDialogDescription className="system-md-regular text-text-secondary">{t('segment.regenerationConfirmMessage', { ns: 'datasetDocuments' })}</AlertDialogDescription>
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2 pt-6">
|
||||
<Button onClick={onCancel}>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton variant="secondary" onClick={onCancel}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button variant="primary" tone="destructive" onClick={onConfirm}>
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm}>
|
||||
{t('operation.regenerate', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</>
|
||||
)
|
||||
})
|
||||
@ -45,11 +52,11 @@ const RegeneratingContent: FC = React.memo(() => {
|
||||
|
||||
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>
|
||||
<p className="system-md-regular text-text-secondary">{t('segment.regeneratingMessage', { ns: 'datasetDocuments' })}</p>
|
||||
</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">
|
||||
<RiLoader2Line className="h-4 w-4 animate-spin text-components-button-destructive-primary-text-disabled" />
|
||||
<span>{t('operation.regenerate', { ns: 'common' })}</span>
|
||||
@ -79,11 +86,11 @@ const RegenerationCompletedContent: FC<IRegenerationCompletedContentProps> = Rea
|
||||
|
||||
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>
|
||||
<p className="system-md-regular text-text-secondary">{t('segment.regenerationSuccessMessage', { ns: 'datasetDocuments' })}</p>
|
||||
</div>
|
||||
<div className="flex justify-end pt-6">
|
||||
<div className="flex justify-end p-6">
|
||||
<Button variant="primary" onClick={onClose}>
|
||||
{`${t('operation.close', { ns: 'common' })}${countdown === 0 ? '' : `(${Math.round(countdown / 1000)})`}`}
|
||||
</Button>
|
||||
@ -123,11 +130,13 @@ const RegenerationModal: FC<IRegenerationModalProps> = ({
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={noop} className="max-w-[480px]! rounded-2xl!" wrapperClassName="z-10000!">
|
||||
{!loading && !updateSucceeded && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
|
||||
{loading && !updateSucceeded && <RegeneratingContent />}
|
||||
{!loading && updateSucceeded && <RegenerationCompletedContent onClose={onClose} />}
|
||||
</Modal>
|
||||
<AlertDialog open={isShow}>
|
||||
<AlertDialogContent className="max-w-[480px]! overflow-hidden! rounded-2xl border-none text-left align-middle shadow-xl">
|
||||
{!loading && !updateSucceeded && <DefaultContent onCancel={onCancel} onConfirm={onConfirm} />}
|
||||
{loading && !updateSucceeded && <RegeneratingContent />}
|
||||
{!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>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children, title, onClose }: { children: React.ReactNode, title: string, onClose: () => void }) => (
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
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-title">{title}</div>
|
||||
<button data-testid="modal-close" onClick={onClose}>close</button>
|
||||
{children}
|
||||
</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', () => ({
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
|
||||
import type { HitTesting } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FileIcon from '@/app/components/base/file-uploader/file-type-icon'
|
||||
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 ImageList from '../../common/image-list'
|
||||
import Dot from '../../documents/detail/completed/common/dot'
|
||||
@ -52,93 +52,100 @@ const ChunkDetailModal = ({
|
||||
const showKeywords = !isParentChildRetrieval && keywords && keywords.length > 0
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
||||
isShow
|
||||
closable
|
||||
onClose={onHide}
|
||||
className={cn(isParentChildRetrieval ? 'min-w-[1200px]!' : 'min-w-[800px]!')}
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div className="mt-4 flex">
|
||||
<div className={cn('flex-1', isParentChildRetrieval && 'pr-6')}>
|
||||
{/* Meta info */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex grow items-center space-x-2">
|
||||
<SegmentIndexTag
|
||||
labelPrefix={labelPrefix}
|
||||
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>
|
||||
<DialogContent className={cn('w-full overflow-hidden! border-none text-left align-middle', cn(isParentChildRetrieval ? 'min-w-[1200px]!' : 'min-w-[800px]!'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-4 flex">
|
||||
<div className={cn('flex-1', isParentChildRetrieval && 'pr-6')}>
|
||||
{/* Meta info */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex grow items-center space-x-2">
|
||||
<SegmentIndexTag
|
||||
labelPrefix={labelPrefix}
|
||||
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>
|
||||
<Score value={score} />
|
||||
</div>
|
||||
<Score value={score} />
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="relative">
|
||||
{!answer && (
|
||||
<Markdown
|
||||
className={cn('mt-2! text-text-secondary!', heighClassName)}
|
||||
content={sign_content || content}
|
||||
customDisallowedElements={['input']}
|
||||
/>
|
||||
)}
|
||||
{answer && (
|
||||
<div className="break-all">
|
||||
<div className="flex gap-x-1">
|
||||
<div className="w-4 shrink-0 text-[13px] leading-[20px] font-medium text-text-tertiary">Q</div>
|
||||
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
||||
{content}
|
||||
{/* Content */}
|
||||
<div className="relative">
|
||||
{!answer && (
|
||||
<Markdown
|
||||
className={cn('mt-2! text-text-secondary!', heighClassName)}
|
||||
content={sign_content || content}
|
||||
customDisallowedElements={['input']}
|
||||
/>
|
||||
)}
|
||||
{answer && (
|
||||
<div className="break-all">
|
||||
<div className="flex gap-x-1">
|
||||
<div className="w-4 shrink-0 text-[13px] leading-[20px] font-medium text-text-tertiary">Q</div>
|
||||
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
||||
{content}
|
||||
</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>
|
||||
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
||||
{answer}
|
||||
</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>
|
||||
<div className={cn('line-clamp-20 body-md-regular text-text-secondary')}>
|
||||
{answer}
|
||||
)}
|
||||
{/* Mask */}
|
||||
<Mask className="absolute inset-x-0 bottom-0" />
|
||||
</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>
|
||||
)}
|
||||
{/* Mask */}
|
||||
<Mask className="absolute inset-x-0 bottom-0" />
|
||||
</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>
|
||||
)}
|
||||
|
||||
{isParentChildRetrieval && (
|
||||
<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>
|
||||
|
||||
{isParentChildRetrieval && (
|
||||
<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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ExternalKnowledgeBaseHitTesting } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import ResultItemFooter from './result-item-footer'
|
||||
import ResultItemMeta from './result-item-meta'
|
||||
|
||||
@ -38,20 +38,27 @@ const ResultItemExternal: FC<Props> = ({ payload, positionId }) => {
|
||||
<ResultItemFooter docType={FileAppearanceTypeEnum.custom} docTitle={title} showDetailModal={showDetailModal} />
|
||||
|
||||
{isShowDetailModal && (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}chunkDetail`, { ns: 'datasetHitTesting' })}
|
||||
className="min-w-[800px]!"
|
||||
closable
|
||||
onClose={hideDetailModal}
|
||||
isShow={isShowDetailModal}
|
||||
<Dialog
|
||||
open={isShowDetailModal}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
hideDetailModal()
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
<DialogContent className="w-full min-w-[800px]! overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{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>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { BuiltInMetadataItem, MetadataItemInBatchEdit, MetadataItemWithEdit } from '../types'
|
||||
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 { RiQuestionLine } from '@remixicon/react'
|
||||
import { produce } from 'immer'
|
||||
@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { useCreateMetaData } from '@/service/knowledge/use-metadata'
|
||||
import Checkbox from '../../../base/checkbox'
|
||||
import Modal from '../../../base/modal'
|
||||
import Tooltip from '../../../base/tooltip'
|
||||
import AddMetadataButton from '../add-metadata-button'
|
||||
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)
|
||||
}, [templeList, addedList, isApplyToAllSelectDocument, onSave])
|
||||
return (
|
||||
<Modal title={t(`${i18nPrefix}.editMetadata`, { ns: 'dataset' })} isShow closable onClose={onHide} className="!max-w-[640px]">
|
||||
<div className="mt-1 system-xs-medium text-text-accent">{t(`${i18nPrefix}.editDocumentsNum`, { ns: 'dataset', num: documentNum })}</div>
|
||||
<div className="max-h-[305px] overflow-x-hidden overflow-y-auto">
|
||||
<div className="mt-4 space-y-2">
|
||||
{templeList.map(item => (<EditMetadataBatchItem key={item.id} payload={item} onChange={handleTemplesChange} onRemove={handleTempleItemRemove} onReset={handleItemReset} />))}
|
||||
</div>
|
||||
<div className="mt-4 pl-[18px]">
|
||||
<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 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>
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<DialogContent className="w-full !max-w-[640px] overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.editMetadata`, { ns: 'dataset' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="flex items-center select-none">
|
||||
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} id="apply-to-all" />
|
||||
<div className="mr-1 ml-2 system-xs-medium text-text-secondary">{t(`${i18nPrefix}.applyToAllSelectDocument`, { ns: 'dataset' })}</div>
|
||||
<Tooltip popupContent={<div className="max-w-[240px]">{t(`${i18nPrefix}.applyToAllSelectDocumentTip`, { ns: 'dataset' })}</div>}>
|
||||
<div className="cursor-pointer p-px">
|
||||
<RiQuestionLine className="size-3.5 text-text-tertiary" />
|
||||
<div className="mt-1 system-xs-medium text-text-accent">{t(`${i18nPrefix}.editDocumentsNum`, { ns: 'dataset', num: documentNum })}</div>
|
||||
<div className="max-h-[305px] overflow-x-hidden overflow-y-auto">
|
||||
<div className="mt-4 space-y-2">
|
||||
{templeList.map(item => (<EditMetadataBatchItem key={item.id} payload={item} onChange={handleTemplesChange} onRemove={handleTempleItemRemove} onReset={handleItemReset} />))}
|
||||
</div>
|
||||
<div className="mt-4 pl-[18px]">
|
||||
<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>
|
||||
</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 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 className="mt-4 flex items-center justify-between">
|
||||
<div className="flex items-center select-none">
|
||||
<Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} id="apply-to-all" />
|
||||
<div className="mr-1 ml-2 system-xs-medium text-text-secondary">{t(`${i18nPrefix}.applyToAllSelectDocument`, { ns: 'dataset' })}</div>
|
||||
<Tooltip popupContent={<div className="max-w-[240px]">{t(`${i18nPrefix}.applyToAllSelectDocumentTip`, { ns: 'dataset' })}</div>}>
|
||||
<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>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(EditMetadataBatchModal)
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
@ -21,7 +22,6 @@ import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import CreateModal from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
|
||||
import { getIcon } from '../utils/get-icon'
|
||||
@ -230,33 +230,45 @@ const DatasetMetadataDrawer: FC<Props> = ({
|
||||
</div>
|
||||
|
||||
{isShowRenameModal && (
|
||||
<Modal isShow title={t(`${i18nPrefix}.rename`, { ns: 'dataset' })} onClose={() => setIsShowRenameModal(false)}>
|
||||
<Field label={t(`${i18nPrefix}.name`, { ns: 'dataset' })} className="mt-4">
|
||||
<Input
|
||||
value={templeName}
|
||||
onChange={e => setTempleName(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.namePlaceholder`, { ns: 'dataset' })}
|
||||
/>
|
||||
</Field>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
className="mr-2"
|
||||
onClick={() => {
|
||||
setIsShowRenameModal(false)
|
||||
setTempleName(currPayload!.name)
|
||||
}}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleRenamed}
|
||||
variant="primary"
|
||||
disabled={!templeName}
|
||||
>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
setIsShowRenameModal(false)
|
||||
}}
|
||||
>
|
||||
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.rename`, { ns: 'dataset' })}
|
||||
</DialogTitle>
|
||||
|
||||
<Field label={t(`${i18nPrefix}.name`, { ns: 'dataset' })} className="mt-4">
|
||||
<Input
|
||||
value={templeName}
|
||||
onChange={e => setTempleName(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.namePlaceholder`, { ns: 'dataset' })}
|
||||
/>
|
||||
</Field>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
className="mr-2"
|
||||
onClick={() => {
|
||||
setIsShowRenameModal(false)
|
||||
setTempleName(currPayload!.name)
|
||||
}}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleRenamed}
|
||||
variant="primary"
|
||||
disabled={!templeName}
|
||||
>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
@ -4,13 +4,12 @@ import type { AppIconSelection } from '../../base/app-icon-picker'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
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 { RiCloseLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
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])
|
||||
return (
|
||||
<Modal className="w-[520px] max-w-[520px] rounded-xl px-8 py-6" isShow={show} onClose={noop}>
|
||||
<div className="flex items-center justify-between pb-2">
|
||||
<div className="text-xl leading-[30px] font-medium text-text-primary">{t('title', { ns: 'datasetSettings' })}</div>
|
||||
<div className="cursor-pointer p-2" onClick={onClose}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
<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' }) || ''} />
|
||||
<Dialog open={show}>
|
||||
<DialogContent className="w-full max-w-[520px] overflow-hidden! rounded-xl border-none px-8 py-6 text-left align-middle">
|
||||
|
||||
<div className="flex items-center justify-between pb-2">
|
||||
<div className="text-xl leading-[30px] font-medium text-text-primary">{t('title', { ns: 'datasetSettings' })}</div>
|
||||
<div className="cursor-pointer p-2" onClick={onClose}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
</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.desc', { ns: 'datasetSettings' })}
|
||||
<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 className="w-full">
|
||||
<Textarea value={description} onChange={e => setDescription(e.target.value)} className="resize-none" placeholder={t('form.descPlaceholder', { ns: 'datasetSettings' }) || ''} />
|
||||
<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.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 className="flex justify-end pt-6">
|
||||
<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>
|
||||
</div>
|
||||
{showAppIconPicker && (<AppIconPicker onSelect={handleSelectAppIcon} onClose={handleCloseAppIconPicker} />)}
|
||||
</Modal>
|
||||
<div className="flex justify-end pt-6">
|
||||
<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>
|
||||
</div>
|
||||
{showAppIconPicker && (<AppIconPicker onSelect={handleSelectAppIcon} onClose={handleCloseAppIconPicker} />)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default RenameDatasetModal
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
import type { CreateApiKeyResponse } from '@/models/app'
|
||||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
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 Modal from '@/app/components/base/modal'
|
||||
import InputCopy from './input-copy'
|
||||
import s from './style.module.css'
|
||||
|
||||
@ -22,21 +23,33 @@ const SecretKeyGenerateModal = ({
|
||||
}: ISecretKeyGenerateModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={onClose} title={`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`} className={`px-8 ${className}`}>
|
||||
<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>
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<DialogContent className={cn('w-full max-w-[480px] overflow-hidden! border-none px-8 text-left align-middle', className)}>
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`}
|
||||
</DialogTitle>
|
||||
|
||||
</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,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
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 {
|
||||
useState,
|
||||
@ -19,7 +21,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import {
|
||||
@ -104,77 +105,89 @@ const SecretKeyModal = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isShow={isShow} onClose={onClose} title={`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`} className={`${s.customModal} flex flex-col px-8`}>
|
||||
<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 shrink-0 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.apiSecretKeyTips', { ns: 'appApi' })}</p>
|
||||
{isApiKeysLoading && <div className="mt-4"><Loading /></div>}
|
||||
{
|
||||
!!apiKeysList?.data?.length && (
|
||||
<div className="mt-4 flex grow flex-col overflow-hidden">
|
||||
<div className="flex h-9 shrink-0 items-center border-b border-divider-regular text-xs font-semibold text-text-tertiary">
|
||||
<div className="w-64 shrink-0 px-3">{t('apiKeyModal.secretKey', { ns: 'appApi' })}</div>
|
||||
<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="grow px-3"></div>
|
||||
</div>
|
||||
<div className="grow overflow-auto">
|
||||
{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>
|
||||
<div className="w-[200px] shrink-0 truncate px-3">{formatTime(Number(api.created_at), t('dateTimeFormat', { ns: 'appLog' }) as string)}</div>
|
||||
<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>
|
||||
)}
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<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`)}>
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{`${t('apiKeyModal.apiSecretKey', { ns: 'appApi' })}`}
|
||||
</DialogTitle>
|
||||
|
||||
<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 shrink-0 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.apiSecretKeyTips', { ns: 'appApi' })}</p>
|
||||
{isApiKeysLoading && <div className="mt-4"><Loading /></div>}
|
||||
{
|
||||
!!apiKeysList?.data?.length && (
|
||||
<div className="mt-4 flex grow flex-col overflow-hidden">
|
||||
<div className="flex h-9 shrink-0 items-center border-b border-divider-regular text-xs font-semibold text-text-tertiary">
|
||||
<div className="w-64 shrink-0 px-3">{t('apiKeyModal.secretKey', { ns: 'appApi' })}</div>
|
||||
<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="grow px-3"></div>
|
||||
</div>
|
||||
<div className="grow overflow-auto">
|
||||
{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>
|
||||
<div className="w-[200px] shrink-0 truncate px-3">{formatTime(Number(api.created_at), t('dateTimeFormat', { ns: 'appLog' }) as string)}</div>
|
||||
<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 className="flex">
|
||||
<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" />
|
||||
<div className="text-xs font-medium text-text-secondary">{t('apiKeyModal.createNewSecretKey', { ns: 'appApi' })}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
||||
<AlertDialog
|
||||
open={showConfirmDelete}
|
||||
onOpenChange={handleDeleteConfirmOpenChange}
|
||||
>
|
||||
<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('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onDel}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
<div className="flex">
|
||||
<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" />
|
||||
<div className="text-xs font-medium text-text-secondary">{t('apiKeyModal.createNewSecretKey', { ns: 'appApi' })}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<SecretKeyGenerateModal className="shrink-0" isShow={isVisible} onClose={() => setVisible(false)} newKey={newKey} />
|
||||
<AlertDialog
|
||||
open={showConfirmDelete}
|
||||
onOpenChange={handleDeleteConfirmOpenChange}
|
||||
>
|
||||
<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('actionMsg.deleteConfirmTitle', { ns: 'appApi' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
|
||||
{t('actionMsg.deleteConfirmTips', { ns: 'appApi' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onDel}>
|
||||
{t('operation.confirm', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
import type { FC } from 'react'
|
||||
import type { App as AppType } from '@/models/explore'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import AppUnavailable from '@/app/components/base/app-unavailable'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal/index'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { useGetTryAppInfo } from '@/service/use-try-app'
|
||||
@ -40,54 +40,59 @@ const TryApp: FC<Props> = ({
|
||||
const { data: appDetail, isLoading, isError, error } = useGetTryAppInfo(appId)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={onClose}
|
||||
className="h-[calc(100vh-32px)] max-w-[calc(100vw-32px)] min-w-[1280px] overflow-x-auto p-2"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loading type="area" />
|
||||
</div>
|
||||
) : isError ? (
|
||||
<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>
|
||||
<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">
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loading type="area" />
|
||||
</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}
|
||||
/>
|
||||
) : isError ? (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
) : !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>
|
||||
{/* 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)
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
'use client'
|
||||
import type { LangGeniusVersionResponse } from '@/models/common'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import Link from '@/next/link'
|
||||
|
||||
import Link from '@/next/link'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
|
||||
type IAccountSettingProps = {
|
||||
@ -26,87 +26,92 @@ export default function AccountAbout({
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={onCancel}
|
||||
className="w-[480px]! max-w-[480px]! px-6! py-4!"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="relative">
|
||||
<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" />}
|
||||
<DialogContent className="w-[calc(100vw-2rem)]! max-w-[480px]! overflow-hidden! border-none px-6! py-4! text-left align-middle">
|
||||
|
||||
<div className="text-center text-xs font-normal text-text-tertiary">
|
||||
Version
|
||||
{langGeniusVersionInfo?.current_version}
|
||||
<div className="relative">
|
||||
<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-2 text-center text-xs font-normal text-text-secondary">
|
||||
<div>
|
||||
©
|
||||
{dayjs().year()}
|
||||
{' '}
|
||||
LangGenius, Inc., Contributors.
|
||||
<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">
|
||||
Version
|
||||
{langGeniusVersionInfo?.current_version}
|
||||
</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
|
||||
? <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>
|
||||
</>
|
||||
)
|
||||
isLatest
|
||||
? t('about.latestAvailable', { ns: 'common', version: langGeniusVersionInfo.latest_version })
|
||||
: t('about.nowAvailable', { ns: 'common', version: langGeniusVersionInfo.latest_version })
|
||||
}
|
||||
</div>
|
||||
<div className="flex shrink-0 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>
|
||||
<div className="-mx-8 mb-4 h-[0.5px] bg-divider-regular" />
|
||||
<div className="flex items-center justify-between">
|
||||
<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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ApiBasedExtension } from '@/models/common'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { addApiBasedExtension, updateApiBasedExtension } from '@/service/common'
|
||||
|
||||
@ -61,45 +60,48 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({ data, onCance
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Modal isShow onClose={noop} wrapperClassName="z-1002" className="w-[640px]! max-w-none! p-8! pb-6!">
|
||||
<div className="mb-2 text-xl font-semibold text-text-primary">
|
||||
{data.name
|
||||
? t('apiBasedExtension.modal.editTitle', { ns: 'common' })
|
||||
: t('apiBasedExtension.modal.title', { ns: 'common' })}
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||
{t('apiBasedExtension.modal.name.title', { ns: 'common' })}
|
||||
<Dialog open>
|
||||
<DialogContent className="w-[640px]! max-w-none! overflow-hidden! border-none p-8! pb-6! text-left align-middle">
|
||||
|
||||
<div className="mb-2 text-xl font-semibold text-text-primary">
|
||||
{data.name
|
||||
? t('apiBasedExtension.modal.editTitle', { ns: 'common' })
|
||||
: t('apiBasedExtension.modal.title', { ns: 'common' })}
|
||||
</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>
|
||||
<div className="py-2">
|
||||
<div className="flex h-9 items-center justify-between text-sm font-medium text-text-primary">
|
||||
{t('apiBasedExtension.modal.apiEndpoint.title', { 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 className="py-2">
|
||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||
{t('apiBasedExtension.modal.name.title', { ns: 'common' })}
|
||||
</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>
|
||||
<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 className="py-2">
|
||||
<div className="text-sm leading-9 font-medium text-text-primary">
|
||||
{t('apiBasedExtension.modal.apiKey.title', { ns: 'common' })}
|
||||
<div className="py-2">
|
||||
<div className="flex h-9 items-center justify-between text-sm font-medium text-text-primary">
|
||||
{t('apiBasedExtension.modal.apiEndpoint.title', { 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>
|
||||
<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 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 className="py-2">
|
||||
<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 className="mt-6 flex items-center justify-end">
|
||||
<Button onClick={onCancel} className="mr-2">
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button variant="primary" disabled={!localeData.name || !localeData.api_endpoint || !localeData.api_key || loading} onClick={handleSave}>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className="mt-6 flex items-center justify-end">
|
||||
<Button onClick={onCancel} className="mr-2">
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button variant="primary" disabled={!localeData.name || !localeData.api_endpoint || !localeData.api_key || loading} onClick={handleSave}>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default ApiBasedExtensionModal
|
||||
|
||||
@ -9,11 +9,11 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { useGetModelCredential, useUpdateModelLoadBalancingConfig } from '@/service/use-models'
|
||||
import { ConfigurationMethodEnum, FormTypeEnum } from '../declarations'
|
||||
@ -180,99 +180,103 @@ const ModelLoadBalancingModal = ({ provider, configurateMethod, currentCustomCon
|
||||
}, [refetch, onClose])
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isShow={Boolean(model) && open}
|
||||
onClose={onClose}
|
||||
wrapperClassName="z-1002"
|
||||
className="w-[640px] max-w-none px-8 pt-8"
|
||||
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>
|
||||
)}
|
||||
<Dialog
|
||||
open={Boolean(model) && open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose?.()
|
||||
}}
|
||||
>
|
||||
{!draftConfig
|
||||
? <Loading type="area" />
|
||||
: (
|
||||
<>
|
||||
<div className="py-2">
|
||||
<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,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DialogContent className="w-[640px] max-w-none overflow-hidden! border-none px-8 pt-8 text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
<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>
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-6 flex items-center justify-between gap-2">
|
||||
<div>
|
||||
{!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' })}
|
||||
</Button>
|
||||
{!draftConfig
|
||||
? <Loading type="area" />
|
||||
: (
|
||||
<>
|
||||
<div className="py-2">
|
||||
<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 className="space-x-2">
|
||||
<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 className="mt-6 flex items-center justify-between gap-2">
|
||||
<div>
|
||||
{!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' })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<AlertDialog open={!!deleteModel} onOpenChange={open => !open && closeConfirmDelete()}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
import type { FC } from 'react'
|
||||
import type { Dependency } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { InstallStep } from '../../types'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
import ReadyToInstall from './ready-to-install'
|
||||
@ -50,26 +50,31 @@ const InstallBundle: FC<Props> = ({
|
||||
}, [step, t])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
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')}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<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()}
|
||||
<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'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<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>
|
||||
<ReadyToInstall
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
allPlugins={fromDSLPayload}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</Modal>
|
||||
<ReadyToInstall
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
allPlugins={fromDSLPayload}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types'
|
||||
import type { InstallState } from '@/app/components/plugins/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
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 { InstallStepFromGitHub } from '../../types'
|
||||
import Installed from '../base/installed'
|
||||
@ -160,74 +160,80 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
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`)}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<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' })}
|
||||
<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`))}
|
||||
>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<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>
|
||||
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
|
||||
? (
|
||||
<Installed
|
||||
payload={manifest}
|
||||
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
|
||||
errMsg={errorMsg}
|
||||
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'}`}>
|
||||
{state.step === InstallStepFromGitHub.setUrl && (
|
||||
<SetURL
|
||||
repoUrl={state.repoUrl}
|
||||
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
||||
onNext={handleUrlSubmit}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.selectPackage && (
|
||||
<SelectPackage
|
||||
updatePayload={updatePayload!}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
versions={versions}
|
||||
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: String(item.value) }))}
|
||||
selectedPackage={state.selectedPackage}
|
||||
packages={packages}
|
||||
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: String(item.value) }))}
|
||||
onUploaded={handleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.readyToInstall && (
|
||||
<Loaded
|
||||
updatePayload={updatePayload!}
|
||||
uniqueIdentifier={uniqueIdentifier!}
|
||||
payload={manifest as any}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
selectedPackage={state.selectedPackage}
|
||||
onBack={handleBack}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
|
||||
? (
|
||||
<Installed
|
||||
payload={manifest}
|
||||
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
|
||||
errMsg={errorMsg}
|
||||
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'}`}>
|
||||
{state.step === InstallStepFromGitHub.setUrl && (
|
||||
<SetURL
|
||||
repoUrl={state.repoUrl}
|
||||
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
||||
onNext={handleUrlSubmit}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.selectPackage && (
|
||||
<SelectPackage
|
||||
updatePayload={updatePayload!}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
versions={versions}
|
||||
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: String(item.value) }))}
|
||||
selectedPackage={state.selectedPackage}
|
||||
packages={packages}
|
||||
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: String(item.value) }))}
|
||||
onUploaded={handleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.readyToInstall && (
|
||||
<Loaded
|
||||
updatePayload={updatePayload!}
|
||||
uniqueIdentifier={uniqueIdentifier!}
|
||||
payload={manifest as any}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
selectedPackage={state.selectedPackage}
|
||||
onBack={handleBack}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import type { Dependency, PluginDeclaration } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
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 { InstallStep } from '../../types'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
@ -86,52 +86,57 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
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')}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<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()}
|
||||
<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'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<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>
|
||||
{step === InstallStep.uploading && (
|
||||
<Uploading
|
||||
isBundle={isBundle}
|
||||
file={file}
|
||||
onCancel={onClose}
|
||||
onPackageUploaded={handlePackageUploaded}
|
||||
onBundleUploaded={handleBundleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
/>
|
||||
)}
|
||||
{isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ReadyToInstallPackage
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
manifest={manifest}
|
||||
errorMsg={errorMsg}
|
||||
onError={setErrorMsg}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
{step === InstallStep.uploading && (
|
||||
<Uploading
|
||||
isBundle={isBundle}
|
||||
file={file}
|
||||
onCancel={onClose}
|
||||
onPackageUploaded={handlePackageUploaded}
|
||||
onBundleUploaded={handleBundleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
/>
|
||||
)}
|
||||
{isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ReadyToInstallPackage
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
manifest={manifest}
|
||||
errorMsg={errorMsg}
|
||||
onError={setErrorMsg}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import type { Dependency, Plugin, PluginManifestInMarket } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { InstallStep } from '../../types'
|
||||
import Installed from '../base/installed'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
@ -70,60 +70,64 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
||||
}, [setIsInstalling])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
wrapperClassName="z-9999"
|
||||
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')}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<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()}
|
||||
<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'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<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>
|
||||
{
|
||||
isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies!}
|
||||
isFromMarketPlace
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{
|
||||
step === InstallStep.readyToInstall && (
|
||||
<Install
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
payload={manifest!}
|
||||
onCancel={onClose}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
|
||||
<Installed
|
||||
payload={manifest!}
|
||||
isMarketPayload
|
||||
isFailed={step === InstallStep.installFailed}
|
||||
errMsg={errorMsg}
|
||||
onCancel={onSuccess}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Modal>
|
||||
{
|
||||
isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies!}
|
||||
isFromMarketPlace
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{
|
||||
step === InstallStep.readyToInstall && (
|
||||
<Install
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
payload={manifest!}
|
||||
onCancel={onClose}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
|
||||
<Installed
|
||||
payload={manifest!}
|
||||
isMarketPayload
|
||||
isFailed={step === InstallStep.installFailed}
|
||||
errMsg={errorMsg}
|
||||
onCancel={onSuccess}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,14 +2,11 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import SchemaModal from '../schema-modal'
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({
|
||||
children,
|
||||
isShow,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
isShow: boolean
|
||||
}) => isShow ? <div data-testid="modal">{children}</div> : null,
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||
open === false ? null : <>{children}</>,
|
||||
DialogContent: ({ children }: { children: React.ReactNode }) => <div data-testid="modal">{children}</div>,
|
||||
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
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 { Plugin } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { memo } from 'react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
|
||||
type Props = {
|
||||
@ -33,45 +33,52 @@ const PluginMutationModal: FC<Props> = ({
|
||||
modalBottomLeft,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={onCancel}
|
||||
className="min-w-[560px]"
|
||||
closable
|
||||
title={modelTitle}
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="mt-3 mb-2 system-md-regular text-text-secondary">
|
||||
{description}
|
||||
</div>
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
installed={mutation.isSuccess}
|
||||
payload={plugin}
|
||||
className="w-full"
|
||||
titleLeft={cardTitleLeft}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 self-stretch pt-5">
|
||||
<div>
|
||||
{modalBottomLeft}
|
||||
<DialogContent className="w-full min-w-[560px] overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{modelTitle}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-3 mb-2 system-md-regular text-text-secondary">
|
||||
{description}
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
{!mutation.isPending && (
|
||||
<Button onClick={onCancel}>
|
||||
{cancelButtonText}
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
installed={mutation.isSuccess}
|
||||
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
|
||||
variant="primary"
|
||||
loading={mutation.isPending}
|
||||
onClick={mutate}
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{confirmButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,17 +2,19 @@ import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('../../../base/modal', () => ({
|
||||
default: ({ children, title, isShow }: { children: React.ReactNode, title: string, isShow: boolean }) => (
|
||||
isShow
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) => (
|
||||
open !== false
|
||||
? (
|
||||
<div data-testid="modal">
|
||||
<div data-testid="modal-title">{title}</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
: 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', () => ({
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '../../base/modal'
|
||||
import KeyValueItem from '../base/key-value-item'
|
||||
import { convertRepoToUrl } from '../install-plugin/utils'
|
||||
|
||||
@ -23,19 +23,26 @@ const PlugInfo: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const labelWidthClassName = 'w-[96px]'
|
||||
return (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}.title`, { ns: 'plugin' })}
|
||||
className="w-[480px]"
|
||||
isShow
|
||||
onClose={onHide}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</Modal>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.title`, { ns: 'plugin' })}
|
||||
</DialogTitle>
|
||||
|
||||
<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)
|
||||
|
||||
@ -14,28 +14,25 @@ const mockSystemFeatures = { enable_marketplace: true }
|
||||
const render = (ui: ReactElement) =>
|
||||
renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures })
|
||||
|
||||
// Mock Modal component
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children, isShow, onClose, closable, className }: {
|
||||
let mockDialogOnOpenChange: ((open: boolean) => void) | undefined
|
||||
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open, onOpenChange }: {
|
||||
children: React.ReactNode
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
closable?: boolean
|
||||
className?: string
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="modal" className={className}>
|
||||
{closable && (
|
||||
<button data-testid="modal-close" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
mockDialogOnOpenChange = onOpenChange
|
||||
return open === false ? null : <>{children}</>
|
||||
},
|
||||
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
|
||||
|
||||
@ -3,11 +3,11 @@ import type { FC } from 'react'
|
||||
import type { AutoUpdateConfig } from './auto-update-setting/types'
|
||||
import type { Permissions, ReferenceSetting } from '@/app/components/plugins/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { PermissionType } from '@/app/components/plugins/types'
|
||||
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
@ -53,59 +53,64 @@ const PluginSettingModal: FC<Props> = ({
|
||||
}, [onHide, onSave, tempAutoUpdateConfig, tempPrivilege])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={onHide}
|
||||
closable
|
||||
className="w-[620px] max-w-[620px] p-0!"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<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 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>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
||||
{[
|
||||
{ title: t(`${i18nPrefix}.whoCanInstall`, { ns: 'plugin' }), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
|
||||
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
||||
].map(({ title, key, value }) => (
|
||||
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
||||
<Label label={title} />
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
||||
<OptionCard
|
||||
key={option}
|
||||
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
||||
onSelect={() => handlePrivilegeChange(key)(option)}
|
||||
selected={value === option}
|
||||
className="flex-1"
|
||||
/>
|
||||
))}
|
||||
<DialogContent className="w-[620px] max-w-[620px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<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 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>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
||||
{[
|
||||
{ title: t(`${i18nPrefix}.whoCanInstall`, { ns: 'plugin' }), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
|
||||
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
||||
].map(({ title, key, value }) => (
|
||||
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
||||
<Label label={title} />
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
||||
<OptionCard
|
||||
key={option}
|
||||
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
||||
onSelect={() => handlePrivilegeChange(key)(option)}
|
||||
selected={value === option}
|
||||
className="flex-1"
|
||||
/>
|
||||
))}
|
||||
</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>
|
||||
{
|
||||
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>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -17,9 +17,12 @@ vi.mock('@/app/components/workflow/store', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children, isShow }: { children: React.ReactNode, isShow: boolean }) =>
|
||||
isShow ? <div data-testid="modal">{children}</div> : null,
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||
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', () => ({
|
||||
|
||||
@ -121,18 +121,14 @@ vi.mock('@langgenius/dify-ui/button', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children, isShow, _onClose, className }: PropsWithChildren<{
|
||||
isShow: boolean
|
||||
_onClose: () => void
|
||||
className?: string
|
||||
}>) => isShow
|
||||
? (
|
||||
<div data-testid="modal" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: PropsWithChildren<{ open?: boolean }>) =>
|
||||
open === false ? null : <>{children}</>,
|
||||
DialogContent: ({ children, className }: PropsWithChildren<{ className?: string }>) => (
|
||||
<div data-testid="modal" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/constants', () => ({
|
||||
|
||||
@ -31,13 +31,13 @@ describe('VersionMismatchModal', () => {
|
||||
it('should render dialog when isShow is true', () => {
|
||||
render(<VersionMismatchModal {...defaultProps} />)
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render dialog when isShow is false', () => {
|
||||
render(<VersionMismatchModal {...defaultProps} isShow={false} />)
|
||||
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render error title', () => {
|
||||
|
||||
@ -2,14 +2,13 @@
|
||||
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
||||
import type { IconInfo } from '@/models/datasets'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
@ -77,71 +76,70 @@ const PublishAsKnowledgePipelineModal = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isShow
|
||||
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
|
||||
data-testid="publish-modal-close-btn"
|
||||
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" />
|
||||
<Dialog open>
|
||||
<DialogContent className="relative w-full max-w-[480px]! overflow-hidden! border-none p-0! text-left align-middle">
|
||||
|
||||
<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
|
||||
data-testid="publish-modal-close-btn"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-3">
|
||||
<div className="mb-5 flex">
|
||||
<div className="mr-3 grow">
|
||||
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||
{t('common.publishAsPipeline.name', { ns: 'pipeline' })}
|
||||
<div className="px-6 py-3">
|
||||
<div className="mb-5 flex">
|
||||
<div className="mr-3 grow">
|
||||
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||
{t('common.publishAsPipeline.name', { ns: 'pipeline' })}
|
||||
</div>
|
||||
<Input
|
||||
value={pipelineName}
|
||||
onChange={e => setPipelineName(e.target.value)}
|
||||
placeholder={t('common.publishAsPipeline.namePlaceholder', { ns: 'pipeline' }) || ''}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
value={pipelineName}
|
||||
onChange={e => setPipelineName(e.target.value)}
|
||||
placeholder={t('common.publishAsPipeline.namePlaceholder', { ns: 'pipeline' }) || ''}
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
onClick={() => { setShowAppIconPicker(true) }}
|
||||
className="mt-2 shrink-0 cursor-pointer"
|
||||
iconType={pipelineIcon?.icon_type}
|
||||
icon={pipelineIcon?.icon}
|
||||
background={pipelineIcon?.icon_background}
|
||||
imageUrl={pipelineIcon?.icon_url}
|
||||
/>
|
||||
</div>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
onClick={() => { setShowAppIconPicker(true) }}
|
||||
className="mt-2 shrink-0 cursor-pointer"
|
||||
iconType={pipelineIcon?.icon_type}
|
||||
icon={pipelineIcon?.icon}
|
||||
background={pipelineIcon?.icon_background}
|
||||
imageUrl={pipelineIcon?.icon_url}
|
||||
/>
|
||||
</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 className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||
{t('common.publishAsPipeline.description', { ns: 'pipeline' })}
|
||||
</div>
|
||||
<Textarea
|
||||
className="resize-none"
|
||||
placeholder={t('common.publishAsPipeline.descriptionPlaceholder', { ns: 'pipeline' }) || ''}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
className="resize-none"
|
||||
placeholder={t('common.publishAsPipeline.descriptionPlaceholder', { ns: 'pipeline' }) || ''}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end px-6 py-5">
|
||||
<Button
|
||||
className="mr-2"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!pipelineName?.trim() || confirmDisabled}
|
||||
variant="primary"
|
||||
onClick={() => handleConfirm()}
|
||||
>
|
||||
{t('common.publish', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className="flex items-center justify-end px-6 py-5">
|
||||
<Button
|
||||
className="mr-2"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!pipelineName?.trim() || confirmDisabled}
|
||||
variant="primary"
|
||||
onClick={() => handleConfirm()}
|
||||
>
|
||||
{t('common.publish', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{showAppIconPicker && (
|
||||
<AppIconPicker
|
||||
onSelect={handleSelectIcon}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import {
|
||||
RiAlertFill,
|
||||
RiCloseLine,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 VersionMismatchModal from './version-mismatch-modal'
|
||||
|
||||
@ -39,66 +39,71 @@ const UpdateDSLModal = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
className="w-[520px] rounded-2xl p-6"
|
||||
isShow={show}
|
||||
onClose={onCancel}
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<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}>
|
||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||
</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 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>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! rounded-2xl border-none p-6 text-left align-middle">
|
||||
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<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}>
|
||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="pt-2 system-md-semibold text-text-primary">
|
||||
{t('common.chooseDSL', { ns: 'workflow' })}
|
||||
<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 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"
|
||||
accept=".pipeline"
|
||||
displayName="PIPELINE"
|
||||
/>
|
||||
<div>
|
||||
<div className="pt-2 system-md-semibold text-text-primary">
|
||||
{t('common.chooseDSL', { ns: 'workflow' })}
|
||||
</div>
|
||||
<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"
|
||||
accept=".pipeline"
|
||||
displayName="PIPELINE"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button
|
||||
disabled={!currentFile || loading}
|
||||
variant="primary"
|
||||
tone="destructive"
|
||||
onClick={handleImport}
|
||||
loading={loading}
|
||||
>
|
||||
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button
|
||||
disabled={!currentFile || loading}
|
||||
variant="primary"
|
||||
tone="destructive"
|
||||
onClick={handleImport}
|
||||
loading={loading}
|
||||
>
|
||||
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<VersionMismatchModal
|
||||
isShow={showErrorModal}
|
||||
versions={versions}
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
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 Modal from '@/app/components/base/modal'
|
||||
|
||||
type VersionMismatchModalProps = {
|
||||
isShow: boolean
|
||||
@ -22,32 +29,36 @@ const VersionMismatchModal = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className="w-[480px]"
|
||||
<AlertDialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||
</div>
|
||||
<AlertDialogContent className="w-[480px] overflow-hidden! border-none text-left align-middle shadow-xl">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}</AlertDialogTitle>
|
||||
<AlertDialogDescription render={<div />} className="flex grow flex-col system-md-regular text-text-secondary">
|
||||
<div>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start justify-end gap-2 self-stretch pt-6">
|
||||
<Button variant="secondary" onClick={onClose}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button variant="primary" tone="destructive" onClick={onConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton variant="secondary">{t('newApp.Cancel', { ns: 'app' })}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm}>{t('newApp.Confirm', { ns: 'app' })}</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { appDefaultIconBackground } from '@/config'
|
||||
|
||||
type Props = {
|
||||
@ -17,37 +17,42 @@ const InfoModal = ({
|
||||
data,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className="max-w-[400px] min-w-[400px] p-0!"
|
||||
closable
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className={cn('flex flex-col items-center gap-4 px-4 pt-10 pb-8')}>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
iconType={data?.icon_type}
|
||||
icon={data?.icon}
|
||||
background={data?.icon_background || appDefaultIconBackground}
|
||||
imageUrl={data?.icon_url}
|
||||
/>
|
||||
<div className="system-xl-semibold text-text-secondary">{data?.title}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{/* copyright */}
|
||||
{data?.copyright && (
|
||||
<div>
|
||||
©
|
||||
{(new Date()).getFullYear()}
|
||||
{' '}
|
||||
{data?.copyright}
|
||||
</div>
|
||||
)}
|
||||
{data?.custom_disclaimer && (
|
||||
<div className="mt-2">{data.custom_disclaimer}</div>
|
||||
)}
|
||||
<DialogContent className="w-full max-w-[400px] min-w-[400px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className={cn('flex flex-col items-center gap-4 px-4 pt-10 pb-8')}>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
iconType={data?.icon_type}
|
||||
icon={data?.icon}
|
||||
background={data?.icon_background || appDefaultIconBackground}
|
||||
imageUrl={data?.icon_url}
|
||||
/>
|
||||
<div className="system-xl-semibold text-text-secondary">{data?.title}</div>
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{/* copyright */}
|
||||
{data?.copyright && (
|
||||
<div>
|
||||
©
|
||||
{(new Date()).getFullYear()}
|
||||
{' '}
|
||||
{data?.copyright}
|
||||
</div>
|
||||
)}
|
||||
{data?.custom_disclaimer && (
|
||||
<div className="mt-2">{data.custom_disclaimer}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,12 +3,11 @@ import type {
|
||||
MCPServerDetail,
|
||||
} from '@/app/components/tools/types'
|
||||
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 * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import MCPServerParamItem from '@/app/components/tools/mcp/mcp-server-param-item'
|
||||
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
|
||||
@ -20,11 +19,19 @@ import {
|
||||
|
||||
type ModalProps = {
|
||||
appID: string
|
||||
latestParams?: any[]
|
||||
latestParams?: MCPServerParam[]
|
||||
data?: MCPServerDetail
|
||||
show: boolean
|
||||
onHide: () => void
|
||||
appInfo?: any
|
||||
appInfo?: {
|
||||
description?: string
|
||||
}
|
||||
}
|
||||
|
||||
type MCPServerParam = {
|
||||
variable?: string
|
||||
label?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
const MCPServerModal = ({
|
||||
@ -42,7 +49,7 @@ const MCPServerModal = ({
|
||||
|
||||
const defaultDescription = data?.description || appInfo?.description || ''
|
||||
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) => {
|
||||
setParams(prev => ({
|
||||
@ -52,10 +59,14 @@ const MCPServerModal = ({
|
||||
}
|
||||
|
||||
const getParamValue = () => {
|
||||
const res = {} as any
|
||||
latestParams.map((param) => {
|
||||
res[param.variable] = params[param.variable]
|
||||
return param
|
||||
const res: Record<string, string> = {}
|
||||
latestParams.forEach((param) => {
|
||||
if (!param.variable)
|
||||
return
|
||||
|
||||
const value = params[param.variable]
|
||||
if (value !== undefined)
|
||||
res[param.variable] = value
|
||||
})
|
||||
return res
|
||||
}
|
||||
@ -78,7 +89,11 @@ const MCPServerModal = ({
|
||||
|
||||
const submit = async () => {
|
||||
if (!data) {
|
||||
const payload: any = {
|
||||
const payload: {
|
||||
appID: string
|
||||
description?: string
|
||||
parameters: Record<string, string>
|
||||
} = {
|
||||
appID,
|
||||
parameters: getParamValue(),
|
||||
}
|
||||
@ -92,13 +107,18 @@ const MCPServerModal = ({
|
||||
onHide()
|
||||
}
|
||||
else {
|
||||
const payload: any = {
|
||||
const payload: {
|
||||
appID: string
|
||||
id: string
|
||||
description: string
|
||||
parameters: Record<string, string>
|
||||
} = {
|
||||
appID,
|
||||
id: data.id,
|
||||
parameters: getParamValue(),
|
||||
description,
|
||||
}
|
||||
|
||||
payload.description = description
|
||||
await updateMCPServer(payload)
|
||||
invalidateMCPServerDetail(appID)
|
||||
emitMcpServerUpdate('updated')
|
||||
@ -107,56 +127,67 @@ const MCPServerModal = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={onHide}
|
||||
className={cn('relative max-w-[520px]! p-0!')}
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div className="absolute top-5 right-5 z-10 cursor-pointer p-1.5" onClick={onHide}>
|
||||
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
||||
</div>
|
||||
<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>
|
||||
<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">
|
||||
<div className="absolute top-5 right-5 z-10 cursor-pointer p-1.5" onClick={onHide}>
|
||||
<RiCloseLine className="h-5 w-5 text-text-tertiary" />
|
||||
</div>
|
||||
{latestParams.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<div className="shrink-0 system-xs-medium-uppercase text-text-primary">{t('mcp.server.modal.parameters', { ns: 'tools' })}</div>
|
||||
<Divider type="horizontal" className="m-0! h-px! grow bg-divider-subtle" />
|
||||
</div>
|
||||
<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 => (
|
||||
<MCPServerParamItem
|
||||
key={paramItem.variable}
|
||||
data={paramItem}
|
||||
value={params[paramItem.variable] || ''}
|
||||
onChange={value => handleParamChange(paramItem.variable, value)}
|
||||
/>
|
||||
))}
|
||||
<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>
|
||||
<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>
|
||||
</Modal>
|
||||
{latestParams.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<div className="shrink-0 system-xs-medium-uppercase text-text-primary">{t('mcp.server.modal.parameters', { ns: 'tools' })}</div>
|
||||
<Divider type="horizontal" className="m-0! h-px! grow bg-divider-subtle" />
|
||||
</div>
|
||||
<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 { AppIconType } from '@/types/app'
|
||||
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 { RiCloseLine, RiEditLine } from '@remixicon/react'
|
||||
import { useHover } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||
import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import TabSlider from '@/app/components/base/tab-slider'
|
||||
import { MCPAuthMethod } from '@/app/components/tools/types'
|
||||
import { shouldUseMcpIconForAppIcon } from '@/utils/mcp'
|
||||
@ -281,18 +279,16 @@ const MCPModal: FC<DuplicateAppModalProps> = ({
|
||||
const formKey = data?.id ?? 'create'
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={noop}
|
||||
className={cn('relative max-w-[520px]!', 'p-6')}
|
||||
>
|
||||
<MCPModalContent
|
||||
key={formKey}
|
||||
data={data}
|
||||
onConfirm={onConfirm}
|
||||
onHide={onHide}
|
||||
/>
|
||||
</Modal>
|
||||
<Dialog open={show}>
|
||||
<DialogContent className="w-full max-w-[520px]! overflow-hidden! border-none p-6 text-left align-middle">
|
||||
<MCPModalContent
|
||||
key={formKey}
|
||||
data={data}
|
||||
onConfirm={onConfirm}
|
||||
onHide={onHide}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -4,12 +4,12 @@ import type { Authorization as AuthorizationPayloadType } from '../../types'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { produce } from 'immer'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
@ -115,70 +115,78 @@ const Authorization: FC<Props> = ({
|
||||
onHide()
|
||||
}, [tempPayload, onChange, onHide])
|
||||
return (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}.authorization`, { ns: 'workflow' })}
|
||||
isShow={isShow}
|
||||
onClose={onHide}
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Field title={t(`${i18nPrefix}.authorizationType`, { ns: 'workflow' })}>
|
||||
<RadioGroup
|
||||
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>
|
||||
<DialogContent className="overflow-hidden! border-none text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.authorization`, { ns: 'workflow' })}
|
||||
</DialogTitle>
|
||||
|
||||
{tempPayload.type === AuthorizationType.apiKey && (
|
||||
<>
|
||||
<Field title={t(`${i18nPrefix}.auth-type`, { ns: 'workflow' })}>
|
||||
<RadioGroup
|
||||
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>
|
||||
{tempPayload.config?.type === APIType.custom && (
|
||||
<Field title={t(`${i18nPrefix}.header`, { ns: 'workflow' })} isRequired>
|
||||
<BaseInput
|
||||
value={tempPayload.config?.header || ''}
|
||||
onChange={handleAPIKeyOrHeaderChange('header')}
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Field title={t(`${i18nPrefix}.authorizationType`, { ns: 'workflow' })}>
|
||||
<RadioGroup
|
||||
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 && (
|
||||
<>
|
||||
<Field title={t(`${i18nPrefix}.auth-type`, { ns: 'workflow' })}>
|
||||
<RadioGroup
|
||||
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>
|
||||
)}
|
||||
{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>
|
||||
<div className="flex">
|
||||
<Input
|
||||
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]')}
|
||||
value={tempPayload.config?.api_key || ''}
|
||||
onChange={handleAPIKeyChange}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
onFocusChange={setIsFocus}
|
||||
placeholder={' '}
|
||||
placeholderClassName="leading-[21px]!"
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
</>
|
||||
)}
|
||||
<Field title={t(`${i18nPrefix}.api-key-title`, { ns: 'workflow' })} isRequired>
|
||||
<div className="flex">
|
||||
<Input
|
||||
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]')}
|
||||
value={tempPayload.config?.api_key || ''}
|
||||
onChange={handleAPIKeyChange}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
onFocusChange={setIsFocus}
|
||||
placeholder={' '}
|
||||
placeholderClassName="leading-[21px]!"
|
||||
/>
|
||||
</div>
|
||||
</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 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>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(Authorization)
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { HttpNodeType } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
||||
import { parseCurl } from './curl-parser'
|
||||
@ -42,28 +42,35 @@ const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => {
|
||||
}, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('nodes.http.curl.title', { ns: 'workflow' })}
|
||||
isShow={isShow}
|
||||
onClose={onHide}
|
||||
className="w-[400px]! max-w-[400px]! p-4!"
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Textarea
|
||||
value={inputString}
|
||||
className="my-3 h-40 w-full grow"
|
||||
onChange={e => setInputString(e.target.value)}
|
||||
placeholder={t('nodes.http.curl.placeholder', { ns: 'workflow' })!}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end space-x-2">
|
||||
<Button className="w-[95px]!" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
<Button className="w-[95px]!" variant="primary" onClick={handleSave}>
|
||||
{' '}
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<DialogContent className="w-[400px]! max-w-[400px]! overflow-hidden! border-none p-4! text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t('nodes.http.curl.title', { ns: 'workflow' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
<Textarea
|
||||
value={inputString}
|
||||
className="my-3 h-40 w-full grow"
|
||||
onChange={e => setInputString(e.target.value)}
|
||||
placeholder={t('nodes.http.curl.placeholder', { ns: 'workflow' })!}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end space-x-2">
|
||||
<Button className="w-[95px]!" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
<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 { SchemaRoot } from '../../types'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import Modal from '../../../../../base/modal'
|
||||
import JsonSchemaConfig from './json-schema-config'
|
||||
|
||||
type JsonSchemaConfigModalProps = {
|
||||
@ -18,17 +18,22 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className="h-[800px] max-w-[960px] p-0"
|
||||
<Dialog
|
||||
open={isShow}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<JsonSchemaConfig
|
||||
defaultSchema={defaultSchema}
|
||||
onSave={onSave}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</Modal>
|
||||
<DialogContent className="h-[800px] max-h-none w-full max-w-[960px] overflow-hidden! border-none p-0 text-left align-middle">
|
||||
|
||||
<JsonSchemaConfig
|
||||
defaultSchema={defaultSchema}
|
||||
onSave={onSave}
|
||||
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,
|
||||
default: ({
|
||||
Dialog: ({
|
||||
children,
|
||||
isShow,
|
||||
title,
|
||||
open,
|
||||
}: {
|
||||
children: ReactNode
|
||||
isShow?: boolean
|
||||
title?: ReactNode
|
||||
}) => isShow
|
||||
open?: boolean
|
||||
}) => open !== false
|
||||
? (
|
||||
<div data-testid="base-modal">
|
||||
<div>{title}</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
DialogContent: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
DialogTitle: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
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 { MoreInfo } from '@/app/components/workflow/types'
|
||||
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 { Switch } from '@langgenius/dify-ui/switch'
|
||||
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 ConfigSelect from '@/app/components/app/configuration/config-var/config-select'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import { ChangeType } from '@/app/components/workflow/types'
|
||||
import { checkKeys } from '@/utils/var'
|
||||
@ -124,64 +124,71 @@ const AddExtractParameter: FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
{isShowModal && (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}.addExtractParameter`, { ns: 'workflow' })}
|
||||
isShow
|
||||
onClose={hideModal}
|
||||
className="w-[400px]! max-w-[400px]! p-4!"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
hideModal()
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.name`, { ns: 'workflow' })}>
|
||||
<Input
|
||||
value={param.name}
|
||||
onChange={e => handleParamChange('name')(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.addExtractParameterContent.namePlaceholder`, { ns: 'workflow' })!}
|
||||
/>
|
||||
</Field>
|
||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.type`, { ns: 'workflow' })}>
|
||||
<Select
|
||||
value={param.type}
|
||||
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')} />
|
||||
<DialogContent className="w-[400px]! max-w-[400px]! overflow-hidden! border-none p-4! text-left align-middle">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.addExtractParameter`, { ns: 'workflow' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.name`, { ns: 'workflow' })}>
|
||||
<Input
|
||||
value={param.name}
|
||||
onChange={e => handleParamChange('name')(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.addExtractParameterContent.namePlaceholder`, { ns: 'workflow' })!}
|
||||
/>
|
||||
</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>
|
||||
<Field title={t(`${i18nPrefix}.addExtractParameterContent.type`, { ns: 'workflow' })}>
|
||||
<Select
|
||||
value={param.type}
|
||||
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 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 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>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -3,10 +3,10 @@ import type {
|
||||
ConversationVariable,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useMount } from 'ahooks'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { capitalize } from 'es-toolkit/string'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
@ -16,7 +16,6 @@ import {
|
||||
CopyCheck,
|
||||
} from '@/app/components/base/icons/src/vender/line/files'
|
||||
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 { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
@ -76,87 +75,86 @@ const ConversationVariableModal = ({
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
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>
|
||||
<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>
|
||||
<Dialog open>
|
||||
<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'))}>
|
||||
|
||||
<div className="absolute top-4 right-4 cursor-pointer p-2" onClick={onHide}>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
{/* RIGHT */}
|
||||
<div className="flex h-full w-0 grow flex-col bg-components-panel-bg">
|
||||
<div className="shrink-0 p-4 pb-2">
|
||||
<div className="flex items-center gap-1 py-1">
|
||||
<div className="system-xl-semibold text-text-primary">{currentVar.name}</div>
|
||||
<div className="system-xs-medium text-text-tertiary">{capitalize(currentVar.value_type)}</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 className="flex h-0 grow flex-col p-4 pt-2">
|
||||
<div className="mb-2 flex shrink-0 items-center gap-2">
|
||||
<div className="shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('chatVariable.storedContent', { ns: 'workflow' }).toLocaleUpperCase()}</div>
|
||||
<div
|
||||
className="h-px grow"
|
||||
style={{
|
||||
background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)',
|
||||
}}
|
||||
>
|
||||
{/* RIGHT */}
|
||||
<div className="flex h-full w-0 grow flex-col bg-components-panel-bg">
|
||||
<div className="shrink-0 p-4 pb-2">
|
||||
<div className="flex items-center gap-1 py-1">
|
||||
<div className="system-xl-semibold text-text-primary">{currentVar.name}</div>
|
||||
<div className="system-xs-medium text-text-tertiary">{capitalize(currentVar.value_type)}</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 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 className="flex h-0 grow flex-col p-4 pt-2">
|
||||
<div className="mb-2 flex shrink-0 items-center gap-2">
|
||||
<div className="shrink-0 system-xs-medium-uppercase text-text-tertiary">{t('chatVariable.storedContent', { ns: 'workflow' }).toLocaleUpperCase()}</div>
|
||||
<div
|
||||
className="h-px grow"
|
||||
style={{
|
||||
background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)',
|
||||
}}
|
||||
>
|
||||
</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 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 className="grow pl-4">
|
||||
<CodeEditor
|
||||
readOnly
|
||||
noWrapper
|
||||
isExpand
|
||||
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>
|
||||
)}
|
||||
)}
|
||||
{(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>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,8 @@ const ActionMenu: FC<ActionMenuProps> = (props: ActionMenuProps) => {
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<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" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import type { FC } from 'react'
|
||||
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 { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
|
||||
type DeleteConfirmModalProps = {
|
||||
isOpen: boolean
|
||||
@ -21,24 +28,36 @@ const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal className="p-0" isShow={isOpen} onClose={onClose}>
|
||||
<div className="flex flex-col gap-y-2 p-6 pb-4">
|
||||
<div className="title-2xl-semi-bold text-text-primary">
|
||||
{`${t('operation.delete', { ns: 'common' })} ${versionInfo.marked_name || t('versionHistory.defaultName', { ns: 'workflow' })}`}
|
||||
<AlertDialog
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
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>
|
||||
<p className="system-md-regular text-text-secondary">
|
||||
{t('versionHistory.deletionTip', { ns: 'workflow' })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-x-2 p-6">
|
||||
<Button onClick={onClose}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button variant="primary" tone="destructive" onClick={onDelete.bind(null, versionInfo.id)}>
|
||||
{t('operation.delete', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton
|
||||
nativeButton={false}
|
||||
variant="secondary"
|
||||
closeProps={{ nativeButton: false }}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton nativeButton={false} onClick={onDelete.bind(null, versionInfo.id)}>
|
||||
{t('operation.delete', { ns: 'common' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ const Empty: FC<EmptyProps> = ({
|
||||
{t('versionHistory.filter.empty', { ns: 'workflow' })}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button size="small" onClick={onResetFilter}>
|
||||
<Button nativeButton={false} size="small" onClick={onResetFilter}>
|
||||
{t('versionHistory.filter.reset', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -42,6 +42,7 @@ const Filter: FC<FilterProps> = ({
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PopoverTrigger
|
||||
nativeButton={false}
|
||||
render={(
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import type { FC } from 'react'
|
||||
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 { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
|
||||
type RestoreConfirmModalProps = {
|
||||
isOpen: boolean
|
||||
@ -21,24 +28,36 @@ const RestoreConfirmModal: FC<RestoreConfirmModalProps> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Modal className="p-0" isShow={isOpen} onClose={onClose}>
|
||||
<div className="flex flex-col gap-y-2 p-6 pb-4">
|
||||
<div className="title-2xl-semi-bold text-text-primary">
|
||||
{`${t('common.restore', { ns: 'workflow' })} ${versionInfo.marked_name || t('versionHistory.defaultName', { ns: 'workflow' })}`}
|
||||
<AlertDialog
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
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>
|
||||
<p className="system-md-regular text-text-secondary">
|
||||
{t('versionHistory.restorationTip', { ns: 'workflow' })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-x-2 p-6">
|
||||
<Button onClick={onClose}>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onRestore.bind(null, versionInfo)}>
|
||||
{t('common.restore', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton
|
||||
nativeButton={false}
|
||||
variant="secondary"
|
||||
closeProps={{ nativeButton: false }}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton nativeButton={false} tone="default" onClick={onRestore.bind(null, versionInfo)}>
|
||||
{t('common.restore', { ns: 'workflow' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import type { MouseEventHandler } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
RiAlertFill,
|
||||
@ -17,7 +18,6 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
||||
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 { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import {
|
||||
@ -199,90 +199,100 @@ const UpdateDSLModal = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
className="w-[520px] rounded-2xl p-6"
|
||||
isShow={show}
|
||||
onClose={onCancel}
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<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}>
|
||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||
</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 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>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! rounded-2xl border-none p-6 text-left align-middle">
|
||||
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<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}>
|
||||
<RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="pt-2 system-md-semibold text-text-primary">
|
||||
{t('common.chooseDSL', { ns: 'workflow' })}
|
||||
<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 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 className="pt-2 system-md-semibold text-text-primary">
|
||||
{t('common.chooseDSL', { ns: 'workflow' })}
|
||||
</div>
|
||||
<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 className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button
|
||||
disabled={!currentFile || loading}
|
||||
variant="primary"
|
||||
tone="destructive"
|
||||
onClick={handleImport}
|
||||
loading={loading}
|
||||
>
|
||||
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
isShow={showErrorModal}
|
||||
onClose={() => setShowErrorModal(false)}
|
||||
className="w-[480px]"
|
||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||
<Button onClick={onCancel}>{t('newApp.Cancel', { ns: 'app' })}</Button>
|
||||
<Button
|
||||
disabled={!currentFile || loading}
|
||||
variant="primary"
|
||||
tone="destructive"
|
||||
onClick={handleImport}
|
||||
loading={loading}
|
||||
>
|
||||
{t('common.overwriteAndImport', { ns: 'workflow' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={showErrorModal}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
setShowErrorModal(false)
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||
|
||||
<div className="flex flex-col items-start gap-2 self-stretch pb-4">
|
||||
<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>{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}</div>
|
||||
<div>{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}</div>
|
||||
<br />
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart3', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.importedVersion}</span>
|
||||
</div>
|
||||
<div>
|
||||
{t('newApp.appCreateDSLErrorPart4', { ns: 'app' })}
|
||||
<span className="system-md-medium">{versions?.systemVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="primary" tone="destructive" onClick={onUpdateDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<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="primary" tone="destructive" onClick={onUpdateDSLConfirm}>{t('newApp.Confirm', { ns: 'app' })}</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { RiExternalLinkLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
@ -41,63 +41,70 @@ const ExpireNoticeModal: React.FC<Props> = ({ expireAt, expired, onClose }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={onClose}
|
||||
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 } })}
|
||||
closable
|
||||
className="max-w-[600px]"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<div className="mt-5 space-y-5 body-md-regular text-text-secondary">
|
||||
<div>
|
||||
{expired
|
||||
? (
|
||||
<>
|
||||
<div>{t(`${i18nPrefix}.expired.summary.line1`, { ns: 'education' })}</div>
|
||||
<div>{t(`${i18nPrefix}.expired.summary.line2`, { ns: 'education' })}</div>
|
||||
</>
|
||||
)
|
||||
: t(`${i18nPrefix}.isAboutToExpire.summary`, { ns: 'education' })}
|
||||
<DialogContent className="w-full max-w-[600px] overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<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 className="mt-5 space-y-5 body-md-regular text-text-secondary">
|
||||
<div>
|
||||
{expired
|
||||
? (
|
||||
<>
|
||||
<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>
|
||||
<strong className="block title-md-semi-bold">{t(`${i18nPrefix}.stillInEducation.title`, { ns: 'education' })}</strong>
|
||||
{t(`${i18nPrefix}.stillInEducation.${expired ? 'expired' : 'isAboutToExpire'}`, { ns: 'education' })}
|
||||
<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>
|
||||
<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 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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user