'use client' import type { SkillUploadQueueItem } from './shared' import type { SkillUploadDecision, SkillUploadReviewItem } from './upload-workflow' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle, } from '@langgenius/dify-ui/dialog' import { IconButton } from '@langgenius/dify-ui/icon-button' import { useTranslation } from 'react-i18next' import { getPathBaseName } from './shared' import { isUploadReviewItemSkipped, isUploadReviewResolved } from './upload-workflow' function getUploadIconClass(name: string) { const extension = name.split('.').at(-1)?.toLowerCase() if (extension === 'pdf') return 'i-ri-file-pdf-2-fill text-util-colors-red-red-600' if (extension === 'md') return 'i-ri-markdown-fill text-util-colors-blue-blue-600' if (['xls', 'xlsx'].includes(extension ?? '')) return 'i-ri-file-excel-2-fill text-util-colors-green-green-600' return 'i-ri-file-fill text-text-quaternary' } function UploadRowShell({ actions, message, name, success = false, }: { actions?: React.ReactNode message: string name: string success?: boolean }) { return (
{name}
{message}
{!!actions &&
{actions}
}
) } function DecisionButton({ children, onClick }: { children: React.ReactNode; onClick: () => void }) { return ( ) } function getCheckMessage( item: SkillUploadReviewItem, t: ReturnType>['t'], ) { const code = item.check.errors?.[0]?.code if (code === 'file_already_exists' || code === 'duplicate_file_path') return t(($) => $['skillManagement.detail.uploadConflict']) if (code === 'invalid_filename') { const suggestion = item.suggestedPath ? ` Suggested: ${getPathBaseName(item.suggestedPath)}` : '' return `${t(($) => $['skillManagement.detail.uploadInvalidName'])}${suggestion}` } if (code === 'invalid_file_extension') return t(($) => $['skillManagement.detail.uploadInvalidExtension']) if (code === 'missing_file_extension') return t(($) => $['skillManagement.detail.uploadMissingExtension']) return t(($) => $['skillManagement.detail.uploadInvalidPath']) } function getResolvedMessage( item: SkillUploadReviewItem, t: ReturnType>['t'], ) { if (item.decision === 'replace') return t(($) => $['skillManagement.detail.uploadWillReplace']) return t(($) => $['skillManagement.detail.uploadWillUseName'], { name: getPathBaseName(item.resolvedPath ?? item.originalPath), }) } function UploadReviewRow({ item, onDecision, }: { item: SkillUploadReviewItem onDecision: (id: string, decision: SkillUploadDecision) => void }) { const { t } = useTranslation('skill') const resolved = item.kind === 'ready' || (item.decision && item.decision !== 'skip') const actions = !item.decision && item.kind === 'conflict' ? ( <> onDecision(item.id, 'replace')}> {t(($) => $['skillManagement.detail.uploadReplace'])} onDecision(item.id, 'keep-both')}> {t(($) => $['skillManagement.detail.uploadKeepBoth'])} onDecision(item.id, 'skip')}> {t(($) => $['skillManagement.detail.uploadSkip'])} ) : !item.decision && item.kind === 'invalid-name' ? ( <> onDecision(item.id, 'use-suggestion')}> {t(($) => $['skillManagement.detail.uploadSuggestion'])} onDecision(item.id, 'skip')}> {t(($) => $['skillManagement.detail.uploadSkip'])} ) : undefined return ( ) } export function SkillUploadReviewDialog({ items, onDecision, onOpenChange, onUpload, open, }: { items: SkillUploadReviewItem[] onDecision: (id: string, decision: SkillUploadDecision) => void onOpenChange: (open: boolean) => void onUpload: () => void open: boolean }) { const { t } = useTranslation('skill') const { t: tCommon } = useTranslation('common') const skippedItems = items.filter(isUploadReviewItemSkipped) const candidateItems = items.filter((item) => !isUploadReviewItemSkipped(item)) const unresolvedCount = candidateItems.filter((item) => !isUploadReviewResolved(item)).length const readyCount = candidateItems.filter((item) => isUploadReviewResolved(item)).length const summary = unresolvedCount > 0 ? t(($) => $['skillManagement.detail.uploadReadySummary'], { decisions: unresolvedCount, ready: readyCount, skipped: skippedItems.length, }) : t(($) => $['skillManagement.detail.uploadResolvedSummary'], { ready: readyCount, skipped: skippedItems.length, }) return ( $['operation.close'])} size="lg" className="absolute inset-e-5 top-5" > } />
{t(($) => $['skillManagement.detail.uploadReviewTitle'])} {summary}
{candidateItems.length > 0 && (
{candidateItems.map((item) => ( ))}
)} {skippedItems.length > 0 && (
{t(($) => $['skillManagement.detail.uploadSkippedGroup'], { count: skippedItems.length, })}
{skippedItems.map((item) => ( ))}
)}
}> {tCommon(($) => $['operation.cancel'])}
) } export function SkillUploadFailuresDialog({ items, onDecision, onDismiss, onRetry, onRetryItem, open, }: { items: SkillUploadQueueItem[] onDecision: (id: string, decision: SkillUploadDecision) => void onDismiss: () => void onRetry: () => void onRetryItem: (id: string) => void open: boolean }) { const { t } = useTranslation('skill') const { t: tCommon } = useTranslation('common') const failedItems = items.filter((item) => item.status === 'failed') const retryableItems = failedItems.filter((item) => item.failureKind !== 'conflict') return ( !nextOpen && onDismiss()}> $['operation.close'])} size="lg" className="absolute inset-e-5 top-5" > } />
{t(($) => $['skillManagement.detail.uploadFailureTitle'])} {t(($) => $['skillManagement.detail.uploadFailureSummary'], { failed: failedItems.length, total: items.length, })}
{failedItems.map((item) => ( $['skillManagement.detail.uploadNetworkFailure'])} actions={ item.failureKind === 'conflict' ? ( <> onDecision(item.id, 'replace')}> {t(($) => $['skillManagement.detail.uploadReplace'])} onDecision(item.id, 'keep-both')}> {t(($) => $['skillManagement.detail.uploadKeepBoth'])} onDecision(item.id, 'skip')}> {t(($) => $['skillManagement.detail.uploadSkip'])} ) : ( onRetryItem(item.id)}> {tCommon(($) => $['operation.retry'])} ) } /> ))}
) }