mirror of
https://github.com/langgenius/dify.git
synced 2026-09-03 15:27:49 +08:00
Signed-off-by: kenwoodjw <blackxin55+@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: 起岚 <155608604+xiaoyaoqilan@users.noreply.github.com> Co-authored-by: Parman Mohammadalizadeh <prmma23@gmail.com> Co-authored-by: Escape0707 <tothesong@gmail.com> Co-authored-by: Xin Zhang <zhangxin@dify.ai> Co-authored-by: iridescentWen <66297467+iridescentWen@users.noreply.github.com> Co-authored-by: Bond Zhu <37842169+MRZHUH@users.noreply.github.com> Co-authored-by: KVOJJJin <jzongcode@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> Co-authored-by: Asuka Minato <i@asukaminato.eu.org> Co-authored-by: Chester <superdiao6@gmail.com> Co-authored-by: WH-2099 <wh2099@pm.me> Co-authored-by: Jony <619963502@qq.com> Co-authored-by: Jony <13896935+zyz619963502zyz@users.noreply.github.com> Co-authored-by: Harsh Kashyap <harsh.kashyap2001@gmail.com> Co-authored-by: Harsh Kashyap <Harsh23Kashyap@users.noreply.github.com> Co-authored-by: kenwoodjw <blackxin55+@gmail.com> Co-authored-by: csurong <csurong1@gmail.com> Co-authored-by: caosurong <surong.cao@thinkingdata.cn> Co-authored-by: Taranum Wasu <81034301+Taranum01@users.noreply.github.com> Co-authored-by: Taranum01 <50813317+Taranum01@users.noreply.github.com> Co-authored-by: Pranav Agarwal <agarwalpranav0711@gmail.com> Co-authored-by: Stephen Zhou <hi@hyoban.cc> Co-authored-by: zl86790 <marshal_li_b@163.com> Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com> Co-authored-by: yunlu.wen <yunlu.wen@dify.ai> Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import type {
|
|
SkillFileCheckItemResponse,
|
|
SkillFileResponse,
|
|
} from '@dify/contracts/api/console/workspaces/types.gen'
|
|
import { getPathBaseName, getPathDirName, joinSkillPath } from './shared'
|
|
|
|
export type SkillUploadDecision = 'keep-both' | 'replace' | 'skip' | 'use-suggestion'
|
|
|
|
type SkillUploadReviewKind = 'conflict' | 'invalid-name' | 'ready' | 'skipped'
|
|
|
|
export type SkillUploadReviewItem = {
|
|
check: SkillFileCheckItemResponse
|
|
decision?: SkillUploadDecision
|
|
file: File
|
|
id: string
|
|
kind: SkillUploadReviewKind
|
|
originalPath: string
|
|
resolvedPath?: string
|
|
suggestedPath?: string
|
|
}
|
|
|
|
const conflictCodes = new Set(['duplicate_file_path', 'file_already_exists'])
|
|
|
|
function splitFileName(fileName: string) {
|
|
const extensionIndex = fileName.lastIndexOf('.')
|
|
if (extensionIndex <= 0) return { extension: '', stem: fileName }
|
|
return {
|
|
extension: fileName.slice(extensionIndex),
|
|
stem: fileName.slice(0, extensionIndex),
|
|
}
|
|
}
|
|
|
|
export function createAvailableUploadPath(path: string, unavailablePaths: Iterable<string>) {
|
|
const unavailable = new Set(unavailablePaths)
|
|
const directory = getPathDirName(path)
|
|
const { extension, stem } = splitFileName(getPathBaseName(path))
|
|
|
|
for (let index = 2; index < 1000; index += 1) {
|
|
const candidate = joinSkillPath(directory, `${stem}-${index}${extension}`)
|
|
if (!unavailable.has(candidate)) return candidate
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
export function createSuggestedUploadPath(path: string, unavailablePaths: Iterable<string>) {
|
|
const directory = getPathDirName(path)
|
|
const { extension, stem } = splitFileName(getPathBaseName(path))
|
|
const normalizedStem =
|
|
stem
|
|
.normalize('NFKD')
|
|
.replace(/[\u0300-\u036F]/g, '')
|
|
.replace(/[^\w-]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
.toLowerCase() || 'file'
|
|
const directPath = joinSkillPath(directory, `${normalizedStem}${extension.toLowerCase()}`)
|
|
const unavailable = new Set(unavailablePaths)
|
|
return unavailable.has(directPath)
|
|
? createAvailableUploadPath(directPath, unavailable)
|
|
: directPath
|
|
}
|
|
|
|
export function buildUploadReviewItems({
|
|
checks,
|
|
existingFiles,
|
|
files,
|
|
itemIds,
|
|
paths,
|
|
}: {
|
|
checks: Record<string, SkillFileCheckItemResponse>
|
|
existingFiles: SkillFileResponse[]
|
|
files: File[]
|
|
itemIds: string[]
|
|
paths: string[]
|
|
}) {
|
|
const unavailablePaths = new Set(existingFiles.map((file) => file.path))
|
|
|
|
return files.map((file, index): SkillUploadReviewItem => {
|
|
const originalPath = paths[index] ?? file.name
|
|
const check = checks[file.name] ?? {
|
|
errors: [],
|
|
extension: '',
|
|
filename: file.name,
|
|
mime_type: file.type,
|
|
path: originalPath,
|
|
size: file.size,
|
|
}
|
|
const errorCodes = new Set((check.errors ?? []).map((error) => error.code))
|
|
const base = {
|
|
check,
|
|
file,
|
|
id: itemIds[index] ?? `${file.name}-${index}`,
|
|
originalPath,
|
|
}
|
|
|
|
if (errorCodes.size === 0) {
|
|
unavailablePaths.add(check.path)
|
|
return { ...base, kind: 'ready', resolvedPath: check.path }
|
|
}
|
|
|
|
if ([...errorCodes].some((code) => conflictCodes.has(code))) {
|
|
const suggestedPath = createAvailableUploadPath(check.path, unavailablePaths)
|
|
if (suggestedPath) unavailablePaths.add(suggestedPath)
|
|
return { ...base, kind: 'conflict', suggestedPath }
|
|
}
|
|
|
|
if (errorCodes.has('invalid_filename')) {
|
|
const suggestedPath = createSuggestedUploadPath(check.path || originalPath, unavailablePaths)
|
|
if (suggestedPath) unavailablePaths.add(suggestedPath)
|
|
return { ...base, kind: 'invalid-name', suggestedPath }
|
|
}
|
|
|
|
return { ...base, decision: 'skip', kind: 'skipped' }
|
|
})
|
|
}
|
|
|
|
export function resolveUploadReviewItem(
|
|
item: SkillUploadReviewItem,
|
|
decision: SkillUploadDecision,
|
|
): SkillUploadReviewItem {
|
|
if (decision === 'skip') return { ...item, decision, resolvedPath: undefined }
|
|
if (decision === 'keep-both' || decision === 'use-suggestion')
|
|
return { ...item, decision, resolvedPath: item.suggestedPath }
|
|
return { ...item, decision, resolvedPath: item.check.path }
|
|
}
|
|
|
|
export function isUploadReviewResolved(item: SkillUploadReviewItem) {
|
|
return item.kind === 'ready' || item.kind === 'skipped' || item.decision !== undefined
|
|
}
|
|
|
|
export function isUploadReviewItemSkipped(item: SkillUploadReviewItem) {
|
|
return item.kind === 'skipped' || item.decision === 'skip'
|
|
}
|