import type { SkillAssistAttachmentPayload, SkillFileUploadResponse, } from '@dify/contracts/api/console/workspaces/types.gen' import type { DefaultModel, FormValue, } from '@/app/components/header/account-setting/model-provider-page/declarations' // oxlint-disable-next-line no-restricted-imports import type { IOnCompleted, IOnData, IOnError } from '@/service/base' // oxlint-disable-next-line no-restricted-imports import { get, post, ssePost, upload } from '@/service/base' function parseSkillUploadErrorMessage(message: string) { const trimmedMessage = message.trim() if (!trimmedMessage.startsWith('{')) return trimmedMessage try { const parsed: unknown = JSON.parse(trimmedMessage) if (parsed && typeof parsed === 'object') { const parsedMessage = (parsed as Record).message if (typeof parsedMessage === 'string' && parsedMessage.trim()) return parsedMessage.trim() } } catch { return trimmedMessage } return trimmedMessage } function readSkillUploadErrorMessage( error: unknown, visited = new Set(), ): string | undefined { if (!error || visited.has(error)) return undefined if (typeof error === 'string') return parseSkillUploadErrorMessage(error) if (typeof error !== 'object') return undefined visited.add(error) const record = error as Record for (const key of ['data', 'body', 'error', 'cause', 'response']) { const nestedMessage = readSkillUploadErrorMessage(record[key], visited) if (nestedMessage) return nestedMessage } const message = record.message if (typeof message === 'string' && message.trim()) return parseSkillUploadErrorMessage(message) return undefined } async function getSkillUploadResponseErrorMessage(response: Response) { try { const data: unknown = await response.clone().json() return readSkillUploadErrorMessage(data) } catch { try { const text = await response.clone().text() if (text.trim()) return parseSkillUploadErrorMessage(text) } catch {} } } export async function uploadSkillFile( file: File, options?: { onProgress?: (progress: number) => void }, ) { const body = new FormData() body.append('file', file) try { if (options?.onProgress) { const onProgress = (event: ProgressEvent) => { if (!event.lengthComputable) return options.onProgress?.(Math.floor((event.loaded / event.total) * 100)) } const response = await upload( { xhr: new XMLHttpRequest(), data: body, onprogress: onProgress, }, false, '/workspaces/current/skills/files/upload', ) return response as SkillFileUploadResponse } return await post( '/workspaces/current/skills/files/upload', { body }, { bodyStringify: false, deleteContentType: true, silent: true, }, ) } catch (error) { const message = error instanceof Response ? await getSkillUploadResponseErrorMessage(error) : readSkillUploadErrorMessage(error) if (message) { const normalizedError = new Error(message) normalizedError.cause = error throw normalizedError } throw error } } export async function fetchSkillFileBlob({ download = false, path, skillId, versionId, }: { download?: boolean path: string skillId: string versionId: string | null }) { const params = new URLSearchParams({ path }) if (versionId) params.set('version_id', versionId) if (download) params.set('download', '1') const response = await get( `/workspaces/current/skills/${encodeURIComponent(skillId)}/files/content?${params.toString()}`, {}, { needAllResponseContent: true }, ) return response.blob() } export async function fetchSkillArchiveBlob(skillId: string) { const response = await get( `/workspaces/current/skills/${encodeURIComponent(skillId)}/export`, {}, { needAllResponseContent: true }, ) return response.blob() } export function sendSkillAssistMessage({ attachments, getAbortController, message, model, onCompleted, onData, onError, onUnhandledEvent, skillId, targetPath, }: { attachments?: SkillAssistAttachmentPayload[] getAbortController?: (abortController: AbortController) => void message: string model?: DefaultModel & { model_settings?: FormValue } onCompleted?: IOnCompleted onData?: IOnData onError?: IOnError onUnhandledEvent?: (event: Record) => void skillId: string targetPath?: string }) { return ssePost( `/workspaces/current/skills/${encodeURIComponent(skillId)}/assist/messages`, { body: { attachments, message, model, target_path: targetPath, }, }, { getAbortController, onCompleted, onData, onError, onUnhandledEvent, }, ) }