'use client' import type { ReactNode } from 'react' import type { NewKnowledgeSourceDraft, NewKnowledgeSourceType } from './routes' import type { InstalledSourceProviderOption, SourceProviderOption } from './source-provider-options' import { Button, buttonVariants } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Field, FieldLabel } from '@langgenius/dify-ui/field' import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset' import { Input } from '@langgenius/dify-ui/input' import { RadioGroup, RadioItem } from '@langgenius/dify-ui/radio' import { useTranslation } from 'react-i18next' import { buildIntegrationPath } from '@/app/components/integrations/routes' import Link from '@/next/link' import { NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH } from './routes' import { SyncPolicyField } from './sync-policy-field' const sourceTypeOptions = [ { icon: 'i-ri-global-line', iconSize: 'size-4', value: 'websiteCrawl' }, { icon: 'i-ri-file-text-line', iconSize: 'size-3.5', value: 'onlineDocuments' }, { icon: 'i-ri-hard-drive-3-line', iconSize: 'size-3.5', value: 'onlineDrive' }, ] as const const defaultSyncPolicies = ['provider', 'daily', 'manual'] as const export function SourceTypeSelector({ appearance = 'page', disabled = false, disabledValues = [], value, onChange, }: { appearance?: 'embedded' | 'page' disabled?: boolean disabledValues?: readonly NewKnowledgeSourceType[] value: NewKnowledgeSourceType onChange: (value: NewKnowledgeSourceType) => void }) { const { t } = useTranslation('dataset') return (
{t(($) => $['newKnowledge.sourceTypeLabel'])} value={value} disabled={disabled} className="grid grid-cols-1 gap-0.5 rounded-lg bg-background-section p-0.5 sm:grid-cols-3" onValueChange={onChange} > {sourceTypeOptions.map((option) => ( key={option.value} value={option.value} disabled={disabled || disabledValues.includes(option.value)} className={cn( 'relative flex items-center justify-center gap-1.5 rounded-md system-xs-medium text-text-tertiary outline-hidden', appearance === 'embedded' ? 'min-h-7 px-2' : 'h-7', 'hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid', appearance === 'embedded' ? 'data-checked:bg-components-option-card-option-selected-bg data-checked:text-text-primary data-checked:shadow-xs data-disabled:cursor-not-allowed data-disabled:opacity-60' : 'data-checked:bg-background-default data-checked:text-text-primary data-checked:shadow-xs', )} > {t(($) => $[`newKnowledge.${option.value}`])} ))}
) } export function SourceProviderRadioGroup({ disabled = false, layout, options, size = 'medium', surface = 'transparent', value, onChange, }: { disabled?: boolean layout: 'grid-four' | 'grid-three' | 'wrap' options: Array<{ disabled?: boolean; icon: ReactNode; label?: ReactNode; value: T }> size?: 'medium' | 'small' surface?: 'default' | 'transparent' value: T onChange: (value: T) => void }) { return ( value={value} disabled={disabled} className={cn( layout === 'wrap' ? 'flex flex-wrap gap-2' : 'grid grid-cols-2 gap-2', layout === 'grid-three' && 'sm:grid-cols-3', layout === 'grid-four' && 'sm:grid-cols-4', )} onValueChange={onChange} > {options.map((option) => ( key={option.value} value={option.value} disabled={disabled || option.disabled} className={cn( 'relative flex items-center justify-center gap-1.5 rounded-lg border border-divider-subtle system-xs-medium text-text-secondary outline-hidden', size === 'medium' ? 'h-8.5 px-2.5' : 'h-7.5 px-3', surface === 'default' && 'bg-background-default', 'hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid', 'data-checked:border-[1.5px] data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:text-text-primary', 'data-disabled:cursor-not-allowed data-disabled:opacity-60', )} > {option.icon} {option.label ?? option.value} ))} ) } type SourceProviderIconValue = | InstalledSourceProviderOption['datasource']['identity']['icon'] | InstalledSourceProviderOption['plugin']['declaration']['identity']['icon'] export function SourceProviderIcon({ className, fallbackIcon, icon, }: { className?: string fallbackIcon: string icon?: SourceProviderIconValue }) { if (typeof icon === 'string' && icon) return ( ) if (icon && typeof icon !== 'string') return ( {icon.content} ) return } export function SourceProviderSelector({ appearance = 'page', disabled = false, layout = 'grid-three', options, providerKey, onChange, }: { appearance?: 'embedded' | 'page' disabled?: boolean layout?: 'grid-four' | 'grid-three' options: SourceProviderOption[] providerKey: string onChange: (providerKey: string) => void }) { const { t } = useTranslation('dataset') return (
{t(($) => $['newKnowledge.providerLabel'])} event.preventDefault() : undefined} > {t(($) => $['newKnowledge.moreProviders'])}
({ icon: ( ), label: option.label, value: option.key, }))} size="medium" surface="default" onChange={onChange} />
) } export function SourceNameField({ className, disabled = false, draft, labelClassName, name = 'sourceName', preventSubmitOnEnter = false, onDraftChange, }: { className?: string disabled?: boolean draft: NewKnowledgeSourceDraft labelClassName?: string name?: string preventSubmitOnEnter?: boolean onDraftChange: (draft: NewKnowledgeSourceDraft) => void }) { const { t } = useTranslation('dataset') return ( {t(($) => $['newKnowledge.sourceName'])} * $['newKnowledge.sourceNamePlaceholder'])} onValueChange={(value) => onDraftChange({ ...draft, sourceName: value })} onKeyDown={(event) => { if (preventSubmitOnEnter && event.key === 'Enter') event.preventDefault() }} /> ) } export function SourceSyncPolicyField({ availablePolicies = defaultSyncPolicies, className, disabled = false, draft, size, triggerClassName, onDraftChange, }: { availablePolicies?: readonly NewKnowledgeSourceDraft['syncPolicy'][] className?: string disabled?: boolean draft: NewKnowledgeSourceDraft size?: 'large' | 'medium' triggerClassName?: string onDraftChange: (draft: NewKnowledgeSourceDraft) => void }) { return ( onDraftChange({ ...draft, customIntervalSeconds: value.customIntervalSeconds, syncPolicy: value.mode === 'interval' ? 'daily' : value.mode, }) } /> ) } export function SourceConnectionRequiredCard({ actionLabel, description, disabled = false, icon, title, onConnect, }: { actionLabel: string description: string disabled?: boolean icon: ReactNode title: string onConnect: () => void }) { return (
{icon}

{title}

{description}

) } export function SourceProviderCredentialRequiredCard({ disabled = false, icon, provider, onConnect, }: { disabled?: boolean icon: ReactNode provider: string onConnect: () => void }) { const { t } = useTranslation('dataset') return ( $['newKnowledge.connectProvider'], { provider })} description={t(($) => $['newKnowledge.providerCredentialRequiredDescription'], { provider, })} disabled={disabled} icon={icon} title={t(($) => $['newKnowledge.providerNotConfigured'], { provider })} onConnect={onConnect} /> ) } export function SourceProviderNotInstalledCard({ icon, provider, onInstall, }: { icon: ReactNode provider: string onInstall: () => void }) { const { t: tPlugin } = useTranslation('plugin') const { t: tWorkflow } = useTranslation('workflow') return (
{icon}

{provider}

{tWorkflow(($) => $['nodes.common.pluginNotInstalled'])}

) }