dify
This commit is contained in:
52
dify/web/service/_tools_util.spec.ts
Normal file
52
dify/web/service/_tools_util.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { buildProviderQuery } from './_tools_util'
|
||||
|
||||
describe('makeProviderQuery', () => {
|
||||
test('collectionName without special chars', () => {
|
||||
expect(buildProviderQuery('ABC')).toBe('provider=ABC')
|
||||
})
|
||||
test('should escape &', () => {
|
||||
expect(buildProviderQuery('ABC&DEF')).toBe('provider=ABC%26DEF')
|
||||
})
|
||||
test('should escape /', () => {
|
||||
expect(buildProviderQuery('ABC/DEF')).toBe('provider=ABC%2FDEF')
|
||||
})
|
||||
test('should escape ?', () => {
|
||||
expect(buildProviderQuery('ABC?DEF')).toBe('provider=ABC%3FDEF')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tools Utilities', () => {
|
||||
describe('buildProviderQuery', () => {
|
||||
it('should build query string with provider parameter', () => {
|
||||
const result = buildProviderQuery('openai')
|
||||
expect(result).toBe('provider=openai')
|
||||
})
|
||||
|
||||
it('should handle provider names with special characters', () => {
|
||||
const result = buildProviderQuery('provider-name')
|
||||
expect(result).toBe('provider=provider-name')
|
||||
})
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const result = buildProviderQuery('')
|
||||
expect(result).toBe('provider=')
|
||||
})
|
||||
|
||||
it('should URL encode special characters', () => {
|
||||
const result = buildProviderQuery('provider name')
|
||||
expect(result).toBe('provider=provider+name')
|
||||
})
|
||||
|
||||
it('should handle Unicode characters', () => {
|
||||
const result = buildProviderQuery('提供者')
|
||||
expect(result).toContain('provider=')
|
||||
expect(decodeURIComponent(result)).toBe('provider=提供者')
|
||||
})
|
||||
|
||||
it('should handle provider names with slashes', () => {
|
||||
const result = buildProviderQuery('langgenius/openai/gpt-4')
|
||||
expect(result).toContain('provider=')
|
||||
expect(decodeURIComponent(result)).toBe('provider=langgenius/openai/gpt-4')
|
||||
})
|
||||
})
|
||||
})
|
||||
5
dify/web/service/_tools_util.ts
Normal file
5
dify/web/service/_tools_util.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const buildProviderQuery = (collectionName: string): string => {
|
||||
const query = new URLSearchParams()
|
||||
query.set('provider', collectionName)
|
||||
return query.toString()
|
||||
}
|
||||
87
dify/web/service/access-control.ts
Normal file
87
dify/web/service/access-control.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { get, post } from './base'
|
||||
import { getUserCanAccess } from './share'
|
||||
import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
|
||||
import type { App } from '@/types/app'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
|
||||
const NAME_SPACE = 'access-control'
|
||||
|
||||
export const useAppWhiteListSubjects = (appId: string | undefined, enabled: boolean) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'app-whitelist-subjects', appId],
|
||||
queryFn: () => get<{ groups: AccessControlGroup[]; members: AccessControlAccount[] }>(`/enterprise/webapp/app/subjects?appId=${appId}`),
|
||||
enabled: !!appId && enabled,
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
type SearchResults = {
|
||||
currPage: number
|
||||
totalPages: number
|
||||
subjects: Subject[]
|
||||
hasMore: boolean
|
||||
}
|
||||
|
||||
export const useSearchForWhiteListCandidates = (query: { keyword?: string; groupId?: AccessControlGroup['id']; resultsPerPage?: number }, enabled: boolean) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: [NAME_SPACE, 'app-whitelist-candidates', query],
|
||||
queryFn: ({ pageParam }) => {
|
||||
const params = new URLSearchParams()
|
||||
Object.keys(query).forEach((key) => {
|
||||
const typedKey = key as keyof typeof query
|
||||
if (query[typedKey])
|
||||
params.append(key, `${query[typedKey]}`)
|
||||
})
|
||||
params.append('pageNumber', `${pageParam}`)
|
||||
return get<SearchResults>(`/enterprise/webapp/app/subject/search?${new URLSearchParams(params).toString()}`)
|
||||
},
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage) => {
|
||||
if (lastPage.hasMore)
|
||||
return lastPage.currPage + 1
|
||||
return undefined
|
||||
},
|
||||
gcTime: 0,
|
||||
staleTime: 0,
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateAccessModeParams = {
|
||||
appId: App['id']
|
||||
subjects?: Pick<Subject, 'subjectId' | 'subjectType'>[]
|
||||
accessMode: AccessMode
|
||||
}
|
||||
|
||||
export const useUpdateAccessMode = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-access-mode'],
|
||||
mutationFn: (params: UpdateAccessModeParams) => {
|
||||
return post('/enterprise/webapp/app/access-mode', { body: params })
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'app-whitelist-subjects'],
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled }: { appId?: string; isInstalledApp?: boolean; enabled?: boolean }) => {
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'user-can-access-app', appId],
|
||||
queryFn: () => {
|
||||
if (systemFeatures.webapp_auth.enabled)
|
||||
return getUserCanAccess(appId!, isInstalledApp)
|
||||
else
|
||||
return { result: true }
|
||||
},
|
||||
enabled: enabled !== undefined ? enabled : !!appId,
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
74
dify/web/service/annotation.ts
Normal file
74
dify/web/service/annotation.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { del, get, post } from './base'
|
||||
import type { AnnotationEnableStatus, AnnotationItemBasic, EmbeddingModelConfig } from '@/app/components/app/annotation/type'
|
||||
import { ANNOTATION_DEFAULT } from '@/config'
|
||||
|
||||
export const fetchAnnotationConfig = (appId: string) => {
|
||||
return get(`apps/${appId}/annotation-setting`)
|
||||
}
|
||||
export const updateAnnotationStatus = (appId: string, action: AnnotationEnableStatus, embeddingModel?: EmbeddingModelConfig, score?: number) => {
|
||||
let body: any = {
|
||||
score_threshold: score || ANNOTATION_DEFAULT.score_threshold,
|
||||
}
|
||||
if (embeddingModel) {
|
||||
body = {
|
||||
...body,
|
||||
...embeddingModel,
|
||||
}
|
||||
}
|
||||
|
||||
return post(`apps/${appId}/annotation-reply/${action}`, {
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateAnnotationScore = (appId: string, settingId: string, score: number) => {
|
||||
return post(`apps/${appId}/annotation-settings/${settingId}`, {
|
||||
body: { score_threshold: score },
|
||||
})
|
||||
}
|
||||
|
||||
export const queryAnnotationJobStatus = (appId: string, action: AnnotationEnableStatus, jobId: string) => {
|
||||
return get(`apps/${appId}/annotation-reply/${action}/status/${jobId}`)
|
||||
}
|
||||
|
||||
export const fetchAnnotationList = (appId: string, params: Record<string, any>) => {
|
||||
return get(`apps/${appId}/annotations`, { params })
|
||||
}
|
||||
|
||||
export const fetchExportAnnotationList = (appId: string) => {
|
||||
return get(`apps/${appId}/annotations/export`)
|
||||
}
|
||||
|
||||
export const addAnnotation = (appId: string, body: AnnotationItemBasic) => {
|
||||
return post(`apps/${appId}/annotations`, { body })
|
||||
}
|
||||
|
||||
export const annotationBatchImport: Fetcher<{ job_id: string; job_status: string }, { url: string; body: FormData }> = ({ url, body }) => {
|
||||
return post<{ job_id: string; job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true })
|
||||
}
|
||||
|
||||
export const checkAnnotationBatchImportProgress: Fetcher<{ job_id: string; job_status: string }, { jobID: string; appId: string }> = ({ jobID, appId }) => {
|
||||
return get<{ job_id: string; job_status: string }>(`/apps/${appId}/annotations/batch-import-status/${jobID}`)
|
||||
}
|
||||
|
||||
export const editAnnotation = (appId: string, annotationId: string, body: AnnotationItemBasic) => {
|
||||
return post(`apps/${appId}/annotations/${annotationId}`, { body })
|
||||
}
|
||||
|
||||
export const delAnnotation = (appId: string, annotationId: string) => {
|
||||
return del(`apps/${appId}/annotations/${annotationId}`)
|
||||
}
|
||||
|
||||
export const delAnnotations = (appId: string, annotationIds: string[]) => {
|
||||
const params = annotationIds.map(id => `annotation_id=${id}`).join('&')
|
||||
return del(`/apps/${appId}/annotations?${params}`)
|
||||
}
|
||||
|
||||
export const fetchHitHistoryList = (appId: string, annotationId: string, params: Record<string, any>) => {
|
||||
return get(`apps/${appId}/annotations/${annotationId}/hit-histories`, { params })
|
||||
}
|
||||
|
||||
export const clearAllAnnotations = (appId: string): Promise<any> => {
|
||||
return del(`apps/${appId}/annotations`)
|
||||
}
|
||||
188
dify/web/service/apps.ts
Normal file
188
dify/web/service/apps.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { del, get, patch, post, put } from './base'
|
||||
import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app'
|
||||
import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
|
||||
|
||||
export const fetchAppList: Fetcher<AppListResponse, { url: string; params?: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppListResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const fetchAppDetail: Fetcher<AppDetailResponse, { url: string; id: string }> = ({ url, id }) => {
|
||||
return get<AppDetailResponse>(`${url}/${id}`)
|
||||
}
|
||||
|
||||
// Direct API call function for non-SWR usage
|
||||
export const fetchAppDetailDirect = async ({ url, id }: { url: string; id: string }): Promise<AppDetailResponse> => {
|
||||
return get<AppDetailResponse>(`${url}/${id}`)
|
||||
}
|
||||
|
||||
export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> = ({ url }) => {
|
||||
return get<AppTemplatesResponse>(url)
|
||||
}
|
||||
|
||||
export const createApp: Fetcher<AppDetailResponse, { name: string; icon_type?: AppIconType; icon?: string; icon_background?: string; mode: AppModeEnum; description?: string; config?: ModelConfig }> = ({ name, icon_type, icon, icon_background, mode, description, config }) => {
|
||||
return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } })
|
||||
}
|
||||
|
||||
export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string; description: string; use_icon_as_answer_icon?: boolean; max_active_requests?: number | null }> = ({ appID, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests }) => {
|
||||
const body = { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests }
|
||||
return put<AppDetailResponse>(`apps/${appID}`, { body })
|
||||
}
|
||||
|
||||
export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppModeEnum; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => {
|
||||
return post<AppDetailResponse>(`apps/${appID}/copy`, { body: { name, icon_type, icon, icon_background, mode, description } })
|
||||
}
|
||||
|
||||
export const exportAppConfig: Fetcher<{ data: string }, { appID: string; include?: boolean; workflowID?: string }> = ({ appID, include = false, workflowID }) => {
|
||||
const params = new URLSearchParams({
|
||||
include_secret: include.toString(),
|
||||
})
|
||||
if (workflowID)
|
||||
params.append('workflow_id', workflowID)
|
||||
return get<{ data: string }>(`apps/${appID}/export?${params.toString()}`)
|
||||
}
|
||||
|
||||
// TODO: delete
|
||||
export const importApp: Fetcher<AppDetailResponse, { data: string; name?: string; description?: string; icon_type?: AppIconType; icon?: string; icon_background?: string }> = ({ data, name, description, icon_type, icon, icon_background }) => {
|
||||
return post<AppDetailResponse>('apps/import', { body: { data, name, description, icon_type, icon, icon_background } })
|
||||
}
|
||||
|
||||
// TODO: delete
|
||||
export const importAppFromUrl: Fetcher<AppDetailResponse, { url: string; name?: string; description?: string; icon?: string; icon_background?: string }> = ({ url, name, description, icon, icon_background }) => {
|
||||
return post<AppDetailResponse>('apps/import/url', { body: { url, name, description, icon, icon_background } })
|
||||
}
|
||||
|
||||
export const importDSL: Fetcher<DSLImportResponse, { mode: DSLImportMode; yaml_content?: string; yaml_url?: string; app_id?: string; name?: string; description?: string; icon_type?: AppIconType; icon?: string; icon_background?: string }> = ({ mode, yaml_content, yaml_url, app_id, name, description, icon_type, icon, icon_background }) => {
|
||||
return post<DSLImportResponse>('apps/imports', { body: { mode, yaml_content, yaml_url, app_id, name, description, icon, icon_type, icon_background } })
|
||||
}
|
||||
|
||||
export const importDSLConfirm: Fetcher<DSLImportResponse, { import_id: string }> = ({ import_id }) => {
|
||||
return post<DSLImportResponse>(`apps/imports/${import_id}/confirm`, { body: {} })
|
||||
}
|
||||
|
||||
export const switchApp: Fetcher<{ new_app_id: string }, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null }> = ({ appID, name, icon_type, icon, icon_background }) => {
|
||||
return post<{ new_app_id: string }>(`apps/${appID}/convert-to-workflow`, { body: { name, icon_type, icon, icon_background } })
|
||||
}
|
||||
|
||||
export const deleteApp: Fetcher<CommonResponse, string> = (appID) => {
|
||||
return del<CommonResponse>(`apps/${appID}`)
|
||||
}
|
||||
|
||||
export const updateAppSiteStatus: Fetcher<AppDetailResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<AppDetailResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const updateAppApiStatus: Fetcher<AppDetailResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<AppDetailResponse>(url, { body })
|
||||
}
|
||||
|
||||
// path: /apps/{appId}/rate-limit
|
||||
export const updateAppRateLimit: Fetcher<AppDetailResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<AppDetailResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const updateAppSiteAccessToken: Fetcher<UpdateAppSiteCodeResponse, { url: string }> = ({ url }) => {
|
||||
return post<UpdateAppSiteCodeResponse>(url)
|
||||
}
|
||||
|
||||
export const updateAppSiteConfig = ({ url, body }: { url: string; body: Record<string, any> }) => {
|
||||
return post<AppDetailResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const getAppDailyMessages: Fetcher<AppDailyMessagesResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppDailyMessagesResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getAppDailyConversations: Fetcher<AppDailyConversationsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppDailyConversationsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getWorkflowDailyConversations: Fetcher<WorkflowDailyConversationsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<WorkflowDailyConversationsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getAppStatistics: Fetcher<AppStatisticsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppStatisticsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getAppDailyEndUsers: Fetcher<AppDailyEndUsersResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppDailyEndUsersResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const getAppTokenCosts: Fetcher<AppTokenCostsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppTokenCostsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const updateAppModelConfig: Fetcher<UpdateAppModelConfigResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<UpdateAppModelConfigResponse>(url, { body })
|
||||
}
|
||||
|
||||
// For temp testing
|
||||
export const fetchAppListNoMock: Fetcher<AppListResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<AppListResponse>(url, params)
|
||||
}
|
||||
|
||||
export const fetchApiKeysList: Fetcher<ApiKeysListResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<ApiKeysListResponse>(url, params)
|
||||
}
|
||||
|
||||
export const delApikey: Fetcher<CommonResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return del<CommonResponse>(url, params)
|
||||
}
|
||||
|
||||
export const createApikey: Fetcher<CreateApiKeyResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<CreateApiKeyResponse>(url, body)
|
||||
}
|
||||
|
||||
export const validateOpenAIKey: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
|
||||
return post<ValidateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const updateOpenAIKey: Fetcher<UpdateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
|
||||
return post<UpdateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const generationIntroduction: Fetcher<GenerationIntroductionResponse, { url: string; body: { prompt_template: string } }> = ({ url, body }) => {
|
||||
return post<GenerationIntroductionResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchAppVoices: Fetcher<AppVoicesListResponse, { appId: string; language?: string }> = ({ appId, language }) => {
|
||||
language = language || 'en-US'
|
||||
return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`)
|
||||
}
|
||||
|
||||
// Tracing
|
||||
export const fetchTracingStatus: Fetcher<TracingStatus, { appId: string }> = ({ appId }) => {
|
||||
return get(`/apps/${appId}/trace`)
|
||||
}
|
||||
|
||||
export const updateTracingStatus: Fetcher<CommonResponse, { appId: string; body: Record<string, any> }> = ({ appId, body }) => {
|
||||
return post(`/apps/${appId}/trace`, { body })
|
||||
}
|
||||
|
||||
// Webhook Trigger
|
||||
export const fetchWebhookUrl: Fetcher<WebhookTriggerResponse, { appId: string; nodeId: string }> = ({ appId, nodeId }) => {
|
||||
return get<WebhookTriggerResponse>(`apps/${appId}/workflows/triggers/webhook`, { params: { node_id: nodeId } })
|
||||
}
|
||||
|
||||
export const fetchTracingConfig: Fetcher<TracingConfig & { has_not_configured: true }, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
||||
return get(`/apps/${appId}/trace-config`, {
|
||||
params: {
|
||||
tracing_provider: provider,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const addTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
||||
return post(`/apps/${appId}/trace-config`, { body })
|
||||
}
|
||||
|
||||
export const updateTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
||||
return patch(`/apps/${appId}/trace-config`, { body })
|
||||
}
|
||||
|
||||
export const removeTracingConfig: Fetcher<CommonResponse, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
||||
return del(`/apps/${appId}/trace-config?tracing_provider=${provider}`)
|
||||
}
|
||||
671
dify/web/service/base.ts
Normal file
671
dify/web/service/base.ts
Normal file
@@ -0,0 +1,671 @@
|
||||
import { API_PREFIX, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_CE_EDITION, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
|
||||
import { refreshAccessTokenOrRelogin } from './refresh-token'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { basePath } from '@/utils/var'
|
||||
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
|
||||
import type { VisionFile } from '@/types/app'
|
||||
import type {
|
||||
AgentLogResponse,
|
||||
IterationFinishedResponse,
|
||||
IterationNextResponse,
|
||||
IterationStartedResponse,
|
||||
LoopFinishedResponse,
|
||||
LoopNextResponse,
|
||||
LoopStartedResponse,
|
||||
NodeFinishedResponse,
|
||||
NodeStartedResponse,
|
||||
ParallelBranchFinishedResponse,
|
||||
ParallelBranchStartedResponse,
|
||||
TextChunkResponse,
|
||||
TextReplaceResponse,
|
||||
WorkflowFinishedResponse,
|
||||
WorkflowStartedResponse,
|
||||
} from '@/types/workflow'
|
||||
import type { FetchOptionType, ResponseError } from './fetch'
|
||||
import { ContentType, base, getBaseOptions } from './fetch'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
import type {
|
||||
DataSourceNodeCompletedResponse,
|
||||
DataSourceNodeErrorResponse,
|
||||
DataSourceNodeProcessingResponse,
|
||||
} from '@/types/pipeline'
|
||||
import Cookies from 'js-cookie'
|
||||
import { getWebAppPassport } from './webapp-auth'
|
||||
const TIME_OUT = 100000
|
||||
|
||||
export type IOnDataMoreInfo = {
|
||||
conversationId?: string
|
||||
taskId?: string
|
||||
messageId: string
|
||||
errorMessage?: string
|
||||
errorCode?: string
|
||||
}
|
||||
|
||||
export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void
|
||||
export type IOnThought = (though: ThoughtItem) => void
|
||||
export type IOnFile = (file: VisionFile) => void
|
||||
export type IOnMessageEnd = (messageEnd: MessageEnd) => void
|
||||
export type IOnMessageReplace = (messageReplace: MessageReplace) => void
|
||||
export type IOnAnnotationReply = (messageReplace: AnnotationReply) => void
|
||||
export type IOnCompleted = (hasError?: boolean, errorMessage?: string) => void
|
||||
export type IOnError = (msg: string, code?: string) => void
|
||||
|
||||
export type IOnWorkflowStarted = (workflowStarted: WorkflowStartedResponse) => void
|
||||
export type IOnWorkflowFinished = (workflowFinished: WorkflowFinishedResponse) => void
|
||||
export type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => void
|
||||
export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void
|
||||
export type IOnIterationStarted = (workflowStarted: IterationStartedResponse) => void
|
||||
export type IOnIterationNext = (workflowStarted: IterationNextResponse) => void
|
||||
export type IOnNodeRetry = (nodeFinished: NodeFinishedResponse) => void
|
||||
export type IOnIterationFinished = (workflowFinished: IterationFinishedResponse) => void
|
||||
export type IOnParallelBranchStarted = (parallelBranchStarted: ParallelBranchStartedResponse) => void
|
||||
export type IOnParallelBranchFinished = (parallelBranchFinished: ParallelBranchFinishedResponse) => void
|
||||
export type IOnTextChunk = (textChunk: TextChunkResponse) => void
|
||||
export type IOnTTSChunk = (messageId: string, audioStr: string, audioType?: string) => void
|
||||
export type IOnTTSEnd = (messageId: string, audioStr: string, audioType?: string) => void
|
||||
export type IOnTextReplace = (textReplace: TextReplaceResponse) => void
|
||||
export type IOnLoopStarted = (workflowStarted: LoopStartedResponse) => void
|
||||
export type IOnLoopNext = (workflowStarted: LoopNextResponse) => void
|
||||
export type IOnLoopFinished = (workflowFinished: LoopFinishedResponse) => void
|
||||
export type IOnAgentLog = (agentLog: AgentLogResponse) => void
|
||||
|
||||
export type IOnDataSourceNodeProcessing = (dataSourceNodeProcessing: DataSourceNodeProcessingResponse) => void
|
||||
export type IOnDataSourceNodeCompleted = (dataSourceNodeCompleted: DataSourceNodeCompletedResponse) => void
|
||||
export type IOnDataSourceNodeError = (dataSourceNodeError: DataSourceNodeErrorResponse) => void
|
||||
|
||||
export type IOtherOptions = {
|
||||
isPublicAPI?: boolean
|
||||
isMarketplaceAPI?: boolean
|
||||
bodyStringify?: boolean
|
||||
needAllResponseContent?: boolean
|
||||
deleteContentType?: boolean
|
||||
silent?: boolean
|
||||
onData?: IOnData // for stream
|
||||
onThought?: IOnThought
|
||||
onFile?: IOnFile
|
||||
onMessageEnd?: IOnMessageEnd
|
||||
onMessageReplace?: IOnMessageReplace
|
||||
onError?: IOnError
|
||||
onCompleted?: IOnCompleted // for stream
|
||||
getAbortController?: (abortController: AbortController) => void
|
||||
|
||||
onWorkflowStarted?: IOnWorkflowStarted
|
||||
onWorkflowFinished?: IOnWorkflowFinished
|
||||
onNodeStarted?: IOnNodeStarted
|
||||
onNodeFinished?: IOnNodeFinished
|
||||
onIterationStart?: IOnIterationStarted
|
||||
onIterationNext?: IOnIterationNext
|
||||
onIterationFinish?: IOnIterationFinished
|
||||
onNodeRetry?: IOnNodeRetry
|
||||
onParallelBranchStarted?: IOnParallelBranchStarted
|
||||
onParallelBranchFinished?: IOnParallelBranchFinished
|
||||
onTextChunk?: IOnTextChunk
|
||||
onTTSChunk?: IOnTTSChunk
|
||||
onTTSEnd?: IOnTTSEnd
|
||||
onTextReplace?: IOnTextReplace
|
||||
onLoopStart?: IOnLoopStarted
|
||||
onLoopNext?: IOnLoopNext
|
||||
onLoopFinish?: IOnLoopFinished
|
||||
onAgentLog?: IOnAgentLog
|
||||
|
||||
// Pipeline data source node run
|
||||
onDataSourceNodeProcessing?: IOnDataSourceNodeProcessing
|
||||
onDataSourceNodeCompleted?: IOnDataSourceNodeCompleted
|
||||
onDataSourceNodeError?: IOnDataSourceNodeError
|
||||
}
|
||||
|
||||
function jumpTo(url: string) {
|
||||
if (!url)
|
||||
return
|
||||
const targetPath = new URL(url, globalThis.location.origin).pathname
|
||||
if (targetPath === globalThis.location.pathname)
|
||||
return
|
||||
globalThis.location.href = url
|
||||
}
|
||||
|
||||
function unicodeToChar(text: string) {
|
||||
if (!text)
|
||||
return ''
|
||||
|
||||
return text.replace(/\\u([0-9a-f]{4})/g, (_match, p1) => {
|
||||
return String.fromCharCode(Number.parseInt(p1, 16))
|
||||
})
|
||||
}
|
||||
|
||||
const WBB_APP_LOGIN_PATH = '/webapp-signin'
|
||||
function requiredWebSSOLogin(message?: string, code?: number) {
|
||||
const params = new URLSearchParams()
|
||||
// prevent redirect loop
|
||||
if (globalThis.location.pathname === WBB_APP_LOGIN_PATH)
|
||||
return
|
||||
|
||||
params.append('redirect_url', encodeURIComponent(`${globalThis.location.pathname}${globalThis.location.search}`))
|
||||
if (message)
|
||||
params.append('message', message)
|
||||
if (code)
|
||||
params.append('code', String(code))
|
||||
globalThis.location.href = `${globalThis.location.origin}${basePath}/${WBB_APP_LOGIN_PATH}?${params.toString()}`
|
||||
}
|
||||
|
||||
export function format(text: string) {
|
||||
let res = text.trim()
|
||||
if (res.startsWith('\n'))
|
||||
res = res.replace('\n', '')
|
||||
|
||||
return res.replaceAll('\n', '<br/>').replaceAll('```', '')
|
||||
}
|
||||
|
||||
export const handleStream = (
|
||||
response: Response,
|
||||
onData: IOnData,
|
||||
onCompleted?: IOnCompleted,
|
||||
onThought?: IOnThought,
|
||||
onMessageEnd?: IOnMessageEnd,
|
||||
onMessageReplace?: IOnMessageReplace,
|
||||
onFile?: IOnFile,
|
||||
onWorkflowStarted?: IOnWorkflowStarted,
|
||||
onWorkflowFinished?: IOnWorkflowFinished,
|
||||
onNodeStarted?: IOnNodeStarted,
|
||||
onNodeFinished?: IOnNodeFinished,
|
||||
onIterationStart?: IOnIterationStarted,
|
||||
onIterationNext?: IOnIterationNext,
|
||||
onIterationFinish?: IOnIterationFinished,
|
||||
onLoopStart?: IOnLoopStarted,
|
||||
onLoopNext?: IOnLoopNext,
|
||||
onLoopFinish?: IOnLoopFinished,
|
||||
onNodeRetry?: IOnNodeRetry,
|
||||
onParallelBranchStarted?: IOnParallelBranchStarted,
|
||||
onParallelBranchFinished?: IOnParallelBranchFinished,
|
||||
onTextChunk?: IOnTextChunk,
|
||||
onTTSChunk?: IOnTTSChunk,
|
||||
onTTSEnd?: IOnTTSEnd,
|
||||
onTextReplace?: IOnTextReplace,
|
||||
onAgentLog?: IOnAgentLog,
|
||||
onDataSourceNodeProcessing?: IOnDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted?: IOnDataSourceNodeCompleted,
|
||||
onDataSourceNodeError?: IOnDataSourceNodeError,
|
||||
) => {
|
||||
if (!response.ok)
|
||||
throw new Error('Network response was not ok')
|
||||
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
let buffer = ''
|
||||
let bufferObj: Record<string, any>
|
||||
let isFirstMessage = true
|
||||
function read() {
|
||||
let hasError = false
|
||||
reader?.read().then((result: ReadableStreamReadResult<Uint8Array>) => {
|
||||
if (result.done) {
|
||||
onCompleted?.()
|
||||
return
|
||||
}
|
||||
buffer += decoder.decode(result.value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
try {
|
||||
lines.forEach((message) => {
|
||||
if (message.startsWith('data: ')) { // check if it starts with data:
|
||||
try {
|
||||
bufferObj = JSON.parse(message.substring(6)) as Record<string, any>// remove data: and parse as json
|
||||
}
|
||||
catch {
|
||||
// mute handle message cut off
|
||||
onData('', isFirstMessage, {
|
||||
conversationId: bufferObj?.conversation_id,
|
||||
messageId: bufferObj?.message_id,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (bufferObj.status === 400 || !bufferObj.event) {
|
||||
onData('', false, {
|
||||
conversationId: undefined,
|
||||
messageId: '',
|
||||
errorMessage: bufferObj?.message,
|
||||
errorCode: bufferObj?.code,
|
||||
})
|
||||
hasError = true
|
||||
onCompleted?.(true, bufferObj?.message)
|
||||
return
|
||||
}
|
||||
if (bufferObj.event === 'message' || bufferObj.event === 'agent_message') {
|
||||
// can not use format here. Because message is splitted.
|
||||
onData(unicodeToChar(bufferObj.answer), isFirstMessage, {
|
||||
conversationId: bufferObj.conversation_id,
|
||||
taskId: bufferObj.task_id,
|
||||
messageId: bufferObj.id,
|
||||
})
|
||||
isFirstMessage = false
|
||||
}
|
||||
else if (bufferObj.event === 'agent_thought') {
|
||||
onThought?.(bufferObj as ThoughtItem)
|
||||
}
|
||||
else if (bufferObj.event === 'message_file') {
|
||||
onFile?.(bufferObj as VisionFile)
|
||||
}
|
||||
else if (bufferObj.event === 'message_end') {
|
||||
onMessageEnd?.(bufferObj as MessageEnd)
|
||||
}
|
||||
else if (bufferObj.event === 'message_replace') {
|
||||
onMessageReplace?.(bufferObj as MessageReplace)
|
||||
}
|
||||
else if (bufferObj.event === 'workflow_started') {
|
||||
onWorkflowStarted?.(bufferObj as WorkflowStartedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'workflow_finished') {
|
||||
onWorkflowFinished?.(bufferObj as WorkflowFinishedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'node_started') {
|
||||
onNodeStarted?.(bufferObj as NodeStartedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'node_finished') {
|
||||
onNodeFinished?.(bufferObj as NodeFinishedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'iteration_started') {
|
||||
onIterationStart?.(bufferObj as IterationStartedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'iteration_next') {
|
||||
onIterationNext?.(bufferObj as IterationNextResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'iteration_completed') {
|
||||
onIterationFinish?.(bufferObj as IterationFinishedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'loop_started') {
|
||||
onLoopStart?.(bufferObj as LoopStartedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'loop_next') {
|
||||
onLoopNext?.(bufferObj as LoopNextResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'loop_completed') {
|
||||
onLoopFinish?.(bufferObj as LoopFinishedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'node_retry') {
|
||||
onNodeRetry?.(bufferObj as NodeFinishedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'parallel_branch_started') {
|
||||
onParallelBranchStarted?.(bufferObj as ParallelBranchStartedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'parallel_branch_finished') {
|
||||
onParallelBranchFinished?.(bufferObj as ParallelBranchFinishedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'text_chunk') {
|
||||
onTextChunk?.(bufferObj as TextChunkResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'text_replace') {
|
||||
onTextReplace?.(bufferObj as TextReplaceResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'agent_log') {
|
||||
onAgentLog?.(bufferObj as AgentLogResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'tts_message') {
|
||||
onTTSChunk?.(bufferObj.message_id, bufferObj.audio, bufferObj.audio_type)
|
||||
}
|
||||
else if (bufferObj.event === 'tts_message_end') {
|
||||
onTTSEnd?.(bufferObj.message_id, bufferObj.audio)
|
||||
}
|
||||
else if (bufferObj.event === 'datasource_processing') {
|
||||
onDataSourceNodeProcessing?.(bufferObj as DataSourceNodeProcessingResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'datasource_completed') {
|
||||
onDataSourceNodeCompleted?.(bufferObj as DataSourceNodeCompletedResponse)
|
||||
}
|
||||
else if (bufferObj.event === 'datasource_error') {
|
||||
onDataSourceNodeError?.(bufferObj as DataSourceNodeErrorResponse)
|
||||
}
|
||||
else {
|
||||
console.warn(`Unknown event: ${bufferObj.event}`, bufferObj)
|
||||
}
|
||||
}
|
||||
})
|
||||
buffer = lines[lines.length - 1]
|
||||
}
|
||||
catch (e) {
|
||||
onData('', false, {
|
||||
conversationId: undefined,
|
||||
messageId: '',
|
||||
errorMessage: `${e}`,
|
||||
})
|
||||
hasError = true
|
||||
onCompleted?.(true, e as string)
|
||||
return
|
||||
}
|
||||
if (!hasError)
|
||||
read()
|
||||
})
|
||||
}
|
||||
read()
|
||||
}
|
||||
|
||||
const baseFetch = base
|
||||
|
||||
type UploadOptions = {
|
||||
xhr: XMLHttpRequest
|
||||
method?: string
|
||||
url?: string
|
||||
headers?: Record<string, string>
|
||||
data: FormData
|
||||
onprogress?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void
|
||||
}
|
||||
|
||||
type UploadResponse = {
|
||||
id: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export const upload = async (options: UploadOptions, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<UploadResponse> => {
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||
const shareCode = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const defaultOptions = {
|
||||
method: 'POST',
|
||||
url: (url ? `${urlPrefix}${url}` : `${urlPrefix}/files/upload`) + (searchParams || ''),
|
||||
headers: {
|
||||
[CSRF_HEADER_NAME]: Cookies.get(CSRF_COOKIE_NAME()) || '',
|
||||
[PASSPORT_HEADER_NAME]: getWebAppPassport(shareCode),
|
||||
[WEB_APP_SHARE_CODE_HEADER_NAME]: shareCode,
|
||||
},
|
||||
}
|
||||
const mergedOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
url: options.url || defaultOptions.url,
|
||||
headers: { ...defaultOptions.headers, ...options.headers } as Record<string, string>,
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = mergedOptions.xhr
|
||||
xhr.open(mergedOptions.method, mergedOptions.url)
|
||||
for (const key in mergedOptions.headers)
|
||||
xhr.setRequestHeader(key, mergedOptions.headers[key])
|
||||
|
||||
xhr.withCredentials = true
|
||||
xhr.responseType = 'json'
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 201)
|
||||
resolve(xhr.response)
|
||||
else
|
||||
reject(xhr)
|
||||
}
|
||||
}
|
||||
if (mergedOptions.onprogress)
|
||||
xhr.upload.onprogress = mergedOptions.onprogress
|
||||
xhr.send(mergedOptions.data)
|
||||
})
|
||||
}
|
||||
|
||||
export const ssePost = async (
|
||||
url: string,
|
||||
fetchOptions: FetchOptionType,
|
||||
otherOptions: IOtherOptions,
|
||||
) => {
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
onData,
|
||||
onCompleted,
|
||||
onThought,
|
||||
onFile,
|
||||
onMessageEnd,
|
||||
onMessageReplace,
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onNodeRetry,
|
||||
onParallelBranchStarted,
|
||||
onParallelBranchFinished,
|
||||
onTextChunk,
|
||||
onTTSChunk,
|
||||
onTTSEnd,
|
||||
onTextReplace,
|
||||
onAgentLog,
|
||||
onError,
|
||||
getAbortController,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
} = otherOptions
|
||||
const abortController = new AbortController()
|
||||
|
||||
// No need to get token from localStorage, cookies will be sent automatically
|
||||
|
||||
const baseOptions = getBaseOptions()
|
||||
const shareCode = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const options = Object.assign({}, baseOptions, {
|
||||
method: 'POST',
|
||||
signal: abortController.signal,
|
||||
headers: new Headers({
|
||||
[CSRF_HEADER_NAME]: Cookies.get(CSRF_COOKIE_NAME()) || '',
|
||||
[WEB_APP_SHARE_CODE_HEADER_NAME]: shareCode,
|
||||
[PASSPORT_HEADER_NAME]: getWebAppPassport(shareCode),
|
||||
}),
|
||||
} as RequestInit, fetchOptions)
|
||||
|
||||
const contentType = (options.headers as Headers).get('Content-Type')
|
||||
if (!contentType)
|
||||
(options.headers as Headers).set('Content-Type', ContentType.json)
|
||||
|
||||
getAbortController?.(abortController)
|
||||
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||
const urlWithPrefix = (url.startsWith('http://') || url.startsWith('https://'))
|
||||
? url
|
||||
: `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
||||
|
||||
const { body } = options
|
||||
if (body)
|
||||
options.body = JSON.stringify(body)
|
||||
|
||||
globalThis.fetch(urlWithPrefix, options as RequestInit)
|
||||
.then((res) => {
|
||||
if (!/^[23]\d{2}$/.test(String(res.status))) {
|
||||
if (res.status === 401) {
|
||||
if (isPublicAPI) {
|
||||
res.json().then((data: { code?: string; message?: string }) => {
|
||||
if (isPublicAPI) {
|
||||
if (data.code === 'web_app_access_denied')
|
||||
requiredWebSSOLogin(data.message, 403)
|
||||
|
||||
if (data.code === 'web_sso_auth_required')
|
||||
requiredWebSSOLogin()
|
||||
|
||||
if (data.code === 'unauthorized')
|
||||
requiredWebSSOLogin()
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||
ssePost(url, fetchOptions, otherOptions)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.json().then((data) => {
|
||||
Toast.notify({ type: 'error', message: data.message || 'Server Error' })
|
||||
})
|
||||
onError?.('Server Error')
|
||||
}
|
||||
return
|
||||
}
|
||||
return handleStream(
|
||||
res,
|
||||
(str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => {
|
||||
if (moreInfo.errorMessage) {
|
||||
onError?.(moreInfo.errorMessage, moreInfo.errorCode)
|
||||
// TypeError: Cannot assign to read only property ... will happen in page leave, so it should be ignored.
|
||||
if (moreInfo.errorMessage !== 'AbortError: The user aborted a request.' && !moreInfo.errorMessage.includes('TypeError: Cannot assign to read only property'))
|
||||
Toast.notify({ type: 'error', message: moreInfo.errorMessage })
|
||||
return
|
||||
}
|
||||
onData?.(str, isFirstMessage, moreInfo)
|
||||
},
|
||||
onCompleted,
|
||||
onThought,
|
||||
onMessageEnd,
|
||||
onMessageReplace,
|
||||
onFile,
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onNodeRetry,
|
||||
onParallelBranchStarted,
|
||||
onParallelBranchFinished,
|
||||
onTextChunk,
|
||||
onTTSChunk,
|
||||
onTTSEnd,
|
||||
onTextReplace,
|
||||
onAgentLog,
|
||||
onDataSourceNodeProcessing,
|
||||
onDataSourceNodeCompleted,
|
||||
onDataSourceNodeError,
|
||||
)
|
||||
}).catch((e) => {
|
||||
if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().includes('TypeError: Cannot assign to read only property'))
|
||||
Toast.notify({ type: 'error', message: e })
|
||||
onError?.(e)
|
||||
})
|
||||
}
|
||||
|
||||
// base request
|
||||
export const request = async<T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
try {
|
||||
const otherOptionsForBaseFetch = otherOptions || {}
|
||||
const [err, resp] = await asyncRunSafe<T>(baseFetch(url, options, otherOptionsForBaseFetch))
|
||||
if (err === null)
|
||||
return resp
|
||||
const errResp: Response = err as any
|
||||
if (errResp.status === 401) {
|
||||
const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json())
|
||||
const loginUrl = `${globalThis.location.origin}${basePath}/signin`
|
||||
if (parseErr) {
|
||||
globalThis.location.href = loginUrl
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (/\/login/.test(url))
|
||||
return Promise.reject(errRespData)
|
||||
// special code
|
||||
const { code, message } = errRespData
|
||||
// webapp sso
|
||||
if (code === 'web_app_access_denied') {
|
||||
requiredWebSSOLogin(message, 403)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (code === 'web_sso_auth_required') {
|
||||
requiredWebSSOLogin()
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (code === 'unauthorized_and_force_logout') {
|
||||
// Cookies will be cleared by the backend
|
||||
globalThis.location.reload()
|
||||
return Promise.reject(err)
|
||||
}
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
silent,
|
||||
} = otherOptionsForBaseFetch
|
||||
if (isPublicAPI && code === 'unauthorized') {
|
||||
requiredWebSSOLogin()
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (code === 'init_validate_failed' && IS_CE_EDITION && !silent) {
|
||||
Toast.notify({ type: 'error', message, duration: 4000 })
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (code === 'not_init_validated' && IS_CE_EDITION) {
|
||||
jumpTo(`${globalThis.location.origin}${basePath}/init`)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (code === 'not_setup' && IS_CE_EDITION) {
|
||||
jumpTo(`${globalThis.location.origin}${basePath}/install`)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
// refresh token
|
||||
const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT))
|
||||
if (refreshErr === null)
|
||||
return baseFetch<T>(url, options, otherOptionsForBaseFetch)
|
||||
if (location.pathname !== `${basePath}/signin` || !IS_CE_EDITION) {
|
||||
jumpTo(loginUrl)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (!silent) {
|
||||
Toast.notify({ type: 'error', message })
|
||||
return Promise.reject(err)
|
||||
}
|
||||
jumpTo(loginUrl)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
else {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// request methods
|
||||
export const get = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return request<T>(url, Object.assign({}, options, { method: 'GET' }), otherOptions)
|
||||
}
|
||||
|
||||
// For public API
|
||||
export const getPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return get<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
||||
}
|
||||
|
||||
// For Marketplace API
|
||||
export const getMarketplace = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return get<T>(url, options, { ...otherOptions, isMarketplaceAPI: true })
|
||||
}
|
||||
|
||||
export const post = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return request<T>(url, Object.assign({}, options, { method: 'POST' }), otherOptions)
|
||||
}
|
||||
|
||||
// For Marketplace API
|
||||
export const postMarketplace = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return post<T>(url, options, { ...otherOptions, isMarketplaceAPI: true })
|
||||
}
|
||||
|
||||
export const postPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return post<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
||||
}
|
||||
|
||||
export const put = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return request<T>(url, Object.assign({}, options, { method: 'PUT' }), otherOptions)
|
||||
}
|
||||
|
||||
export const putPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return put<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
||||
}
|
||||
|
||||
export const del = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return request<T>(url, Object.assign({}, options, { method: 'DELETE' }), otherOptions)
|
||||
}
|
||||
|
||||
export const delPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return del<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
||||
}
|
||||
|
||||
export const patch = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return request<T>(url, Object.assign({}, options, { method: 'PATCH' }), otherOptions)
|
||||
}
|
||||
|
||||
export const patchPublic = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
return patch<T>(url, options, { ...otherOptions, isPublicAPI: true })
|
||||
}
|
||||
14
dify/web/service/billing.ts
Normal file
14
dify/web/service/billing.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { get } from './base'
|
||||
import type { CurrentPlanInfoBackend, SubscriptionUrlsBackend } from '@/app/components/billing/type'
|
||||
|
||||
export const fetchCurrentPlanInfo = () => {
|
||||
return get<CurrentPlanInfoBackend>('/features')
|
||||
}
|
||||
|
||||
export const fetchSubscriptionUrls = (plan: string, interval: string) => {
|
||||
return get<SubscriptionUrlsBackend>(`/billing/subscription?plan=${plan}&interval=${interval}`)
|
||||
}
|
||||
|
||||
export const fetchBillingUrl = () => {
|
||||
return get<{ url: string }>('/billing/invoices')
|
||||
}
|
||||
391
dify/web/service/common.ts
Normal file
391
dify/web/service/common.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { del, get, patch, post, put } from './base'
|
||||
import type {
|
||||
AccountIntegrate,
|
||||
ApiBasedExtension,
|
||||
CodeBasedExtension,
|
||||
CommonResponse,
|
||||
DataSourceNotion,
|
||||
FileUploadConfigResponse,
|
||||
ICurrentWorkspace,
|
||||
IWorkspace,
|
||||
InitValidateStatusResponse,
|
||||
InvitationResponse,
|
||||
LangGeniusVersionResponse,
|
||||
Member,
|
||||
ModerateResponse,
|
||||
OauthResponse,
|
||||
PluginProvider,
|
||||
Provider,
|
||||
ProviderAnthropicToken,
|
||||
ProviderAzureToken,
|
||||
SetupStatusResponse,
|
||||
UserProfileOriginResponse,
|
||||
} from '@/models/common'
|
||||
import type {
|
||||
UpdateOpenAIKeyResponse,
|
||||
ValidateOpenAIKeyResponse,
|
||||
} from '@/models/app'
|
||||
import type {
|
||||
DefaultModelResponse,
|
||||
Model,
|
||||
ModelItem,
|
||||
ModelLoadBalancingConfig,
|
||||
ModelParameterRule,
|
||||
ModelProvider,
|
||||
ModelTypeEnum,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
|
||||
type LoginSuccess = {
|
||||
result: 'success'
|
||||
data: { access_token: string }
|
||||
}
|
||||
type LoginFail = {
|
||||
result: 'fail'
|
||||
data: string
|
||||
code: string
|
||||
message: string
|
||||
}
|
||||
type LoginResponse = LoginSuccess | LoginFail
|
||||
export const login: Fetcher<LoginResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post(url, { body }) as Promise<LoginResponse>
|
||||
}
|
||||
export const webAppLogin: Fetcher<LoginResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post(url, { body }, { isPublicAPI: true }) as Promise<LoginResponse>
|
||||
}
|
||||
|
||||
export const setup: Fetcher<CommonResponse, { body: Record<string, any> }> = ({ body }) => {
|
||||
return post<CommonResponse>('/setup', { body })
|
||||
}
|
||||
|
||||
export const initValidate: Fetcher<CommonResponse, { body: Record<string, any> }> = ({ body }) => {
|
||||
return post<CommonResponse>('/init', { body })
|
||||
}
|
||||
|
||||
export const fetchInitValidateStatus = () => {
|
||||
return get<InitValidateStatusResponse>('/init')
|
||||
}
|
||||
|
||||
export const fetchSetupStatus = () => {
|
||||
return get<SetupStatusResponse>('/setup')
|
||||
}
|
||||
|
||||
export const fetchUserProfile: Fetcher<UserProfileOriginResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<UserProfileOriginResponse>(url, params, { needAllResponseContent: true })
|
||||
}
|
||||
|
||||
export const updateUserProfile: Fetcher<CommonResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchLangGeniusVersion: Fetcher<LangGeniusVersionResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<LangGeniusVersionResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const oauth: Fetcher<OauthResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<OauthResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const oneMoreStep: Fetcher<CommonResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchMembers: Fetcher<{ accounts: Member[] | null }, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<{ accounts: Member[] | null }>(url, { params })
|
||||
}
|
||||
|
||||
export const fetchProviders: Fetcher<Provider[] | null, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<Provider[] | null>(url, { params })
|
||||
}
|
||||
|
||||
export const validateProviderKey: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
|
||||
return post<ValidateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
export const updateProviderAIKey: Fetcher<UpdateOpenAIKeyResponse, { url: string; body: { token: string | ProviderAzureToken | ProviderAnthropicToken } }> = ({ url, body }) => {
|
||||
return post<UpdateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchAccountIntegrates: Fetcher<{ data: AccountIntegrate[] | null }, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<{ data: AccountIntegrate[] | null }>(url, { params })
|
||||
}
|
||||
|
||||
export const inviteMember: Fetcher<InvitationResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<InvitationResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const updateMemberRole: Fetcher<CommonResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return put<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const deleteMemberOrCancelInvitation: Fetcher<CommonResponse, { url: string }> = ({ url }) => {
|
||||
return del<CommonResponse>(url)
|
||||
}
|
||||
|
||||
export const sendOwnerEmail = (body: { language?: string }) =>
|
||||
post<CommonResponse & { data: string }>('/workspaces/current/members/send-owner-transfer-confirm-email', { body })
|
||||
|
||||
export const verifyOwnerEmail = (body: { code: string; token: string }) =>
|
||||
post<CommonResponse & { is_valid: boolean; email: string; token: string }>('/workspaces/current/members/owner-transfer-check', { body })
|
||||
|
||||
export const ownershipTransfer = (memberID: string, body: { token: string }) =>
|
||||
post<CommonResponse & { is_valid: boolean; email: string; token: string }>(`/workspaces/current/members/${memberID}/owner-transfer`, { body })
|
||||
|
||||
export const fetchFilePreview: Fetcher<{ content: string }, { fileID: string }> = ({ fileID }) => {
|
||||
return get<{ content: string }>(`/files/${fileID}/preview`)
|
||||
}
|
||||
|
||||
export const fetchCurrentWorkspace: Fetcher<ICurrentWorkspace, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return post<ICurrentWorkspace>(url, { body: params })
|
||||
}
|
||||
|
||||
export const updateCurrentWorkspace: Fetcher<ICurrentWorkspace, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<ICurrentWorkspace>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchWorkspaces: Fetcher<{ workspaces: IWorkspace[] }, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<{ workspaces: IWorkspace[] }>(url, { params })
|
||||
}
|
||||
|
||||
export const switchWorkspace: Fetcher<CommonResponse & { new_tenant: IWorkspace }, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<CommonResponse & { new_tenant: IWorkspace }>(url, { body })
|
||||
}
|
||||
|
||||
export const updateWorkspaceInfo: Fetcher<ICurrentWorkspace, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<ICurrentWorkspace>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchDataSource: Fetcher<{ data: DataSourceNotion[] }, { url: string }> = ({ url }) => {
|
||||
return get<{ data: DataSourceNotion[] }>(url)
|
||||
}
|
||||
|
||||
export const syncDataSourceNotion: Fetcher<CommonResponse, { url: string }> = ({ url }) => {
|
||||
return get<CommonResponse>(url)
|
||||
}
|
||||
|
||||
export const updateDataSourceNotionAction: Fetcher<CommonResponse, { url: string }> = ({ url }) => {
|
||||
return patch<CommonResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchPluginProviders: Fetcher<PluginProvider[] | null, string> = (url) => {
|
||||
return get<PluginProvider[] | null>(url)
|
||||
}
|
||||
|
||||
export const validatePluginProviderKey: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: { credentials: any } }> = ({ url, body }) => {
|
||||
return post<ValidateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
export const updatePluginProviderAIKey: Fetcher<UpdateOpenAIKeyResponse, { url: string; body: { credentials: any } }> = ({ url, body }) => {
|
||||
return post<UpdateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const invitationCheck: Fetcher<CommonResponse & { is_valid: boolean; data: { workspace_name: string; email: string; workspace_id: string } }, { url: string; params: { workspace_id?: string; email?: string; token: string } }> = ({ url, params }) => {
|
||||
return get<CommonResponse & { is_valid: boolean; data: { workspace_name: string; email: string; workspace_id: string } }>(url, { params })
|
||||
}
|
||||
|
||||
export const activateMember: Fetcher<LoginResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<LoginResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchModelProviders: Fetcher<{ data: ModelProvider[] }, string> = (url) => {
|
||||
return get<{ data: ModelProvider[] }>(url)
|
||||
}
|
||||
|
||||
export type ModelProviderCredentials = {
|
||||
credentials?: Record<string, string | undefined | boolean>
|
||||
load_balancing: ModelLoadBalancingConfig
|
||||
}
|
||||
export const fetchModelProviderCredentials: Fetcher<ModelProviderCredentials, string> = (url) => {
|
||||
return get<ModelProviderCredentials>(url)
|
||||
}
|
||||
|
||||
export const fetchModelLoadBalancingConfig: Fetcher<{
|
||||
credentials?: Record<string, string | undefined | boolean>
|
||||
load_balancing: ModelLoadBalancingConfig
|
||||
}, string> = (url) => {
|
||||
return get<{
|
||||
credentials?: Record<string, string | undefined | boolean>
|
||||
load_balancing: ModelLoadBalancingConfig
|
||||
}>(url)
|
||||
}
|
||||
|
||||
export const fetchModelProviderModelList: Fetcher<{ data: ModelItem[] }, string> = (url) => {
|
||||
return get<{ data: ModelItem[] }>(url)
|
||||
}
|
||||
|
||||
export const fetchModelList: Fetcher<{ data: Model[] }, string> = (url) => {
|
||||
return get<{ data: Model[] }>(url)
|
||||
}
|
||||
|
||||
export const validateModelProvider: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<ValidateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const validateModelLoadBalancingCredentials: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<ValidateOpenAIKeyResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const setModelProvider: Fetcher<CommonResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const deleteModelProvider: Fetcher<CommonResponse, { url: string; body?: any }> = ({ url, body }) => {
|
||||
return del<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const changeModelProviderPriority: Fetcher<CommonResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const setModelProviderModel: Fetcher<CommonResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const deleteModelProviderModel: Fetcher<CommonResponse, { url: string }> = ({ url }) => {
|
||||
return del<CommonResponse>(url)
|
||||
}
|
||||
|
||||
export const getPayUrl: Fetcher<{ url: string }, string> = (url) => {
|
||||
return get<{ url: string }>(url)
|
||||
}
|
||||
|
||||
export const fetchDefaultModal: Fetcher<{ data: DefaultModelResponse }, string> = (url) => {
|
||||
return get<{ data: DefaultModelResponse }>(url)
|
||||
}
|
||||
|
||||
export const updateDefaultModel: Fetcher<CommonResponse, { url: string; body: any }> = ({ url, body }) => {
|
||||
return post<CommonResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchModelParameterRules: Fetcher<{ data: ModelParameterRule[] }, string> = (url) => {
|
||||
return get<{ data: ModelParameterRule[] }>(url)
|
||||
}
|
||||
|
||||
export const fetchFileUploadConfig: Fetcher<FileUploadConfigResponse, { url: string }> = ({ url }) => {
|
||||
return get<FileUploadConfigResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchNotionConnection: Fetcher<{ data: string }, string> = (url) => {
|
||||
return get(url) as Promise<{ data: string }>
|
||||
}
|
||||
|
||||
export const fetchDataSourceNotionBinding: Fetcher<{ result: string }, string> = (url) => {
|
||||
return get(url) as Promise<{ result: string }>
|
||||
}
|
||||
|
||||
export const fetchApiBasedExtensionList: Fetcher<ApiBasedExtension[], string> = (url) => {
|
||||
return get(url) as Promise<ApiBasedExtension[]>
|
||||
}
|
||||
|
||||
export const fetchApiBasedExtensionDetail: Fetcher<ApiBasedExtension, string> = (url) => {
|
||||
return get(url) as Promise<ApiBasedExtension>
|
||||
}
|
||||
|
||||
export const addApiBasedExtension: Fetcher<ApiBasedExtension, { url: string; body: ApiBasedExtension }> = ({ url, body }) => {
|
||||
return post(url, { body }) as Promise<ApiBasedExtension>
|
||||
}
|
||||
|
||||
export const updateApiBasedExtension: Fetcher<ApiBasedExtension, { url: string; body: ApiBasedExtension }> = ({ url, body }) => {
|
||||
return post(url, { body }) as Promise<ApiBasedExtension>
|
||||
}
|
||||
|
||||
export const deleteApiBasedExtension: Fetcher<{ result: string }, string> = (url) => {
|
||||
return del(url) as Promise<{ result: string }>
|
||||
}
|
||||
|
||||
export const fetchCodeBasedExtensionList: Fetcher<CodeBasedExtension, string> = (url) => {
|
||||
return get(url) as Promise<CodeBasedExtension>
|
||||
}
|
||||
|
||||
export const moderate = (url: string, body: { app_id: string; text: string }) => {
|
||||
return post(url, { body }) as Promise<ModerateResponse>
|
||||
}
|
||||
|
||||
type RetrievalMethodsRes = {
|
||||
retrieval_method: RETRIEVE_METHOD[]
|
||||
}
|
||||
export const fetchSupportRetrievalMethods: Fetcher<RetrievalMethodsRes, string> = (url) => {
|
||||
return get<RetrievalMethodsRes>(url)
|
||||
}
|
||||
|
||||
export const getSystemFeatures = () => {
|
||||
return get<SystemFeatures>('/system-features')
|
||||
}
|
||||
|
||||
export const enableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }) =>
|
||||
patch<CommonResponse>(url, { body })
|
||||
|
||||
export const disableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }) =>
|
||||
patch<CommonResponse>(url, { body })
|
||||
|
||||
export const sendForgotPasswordEmail: Fetcher<CommonResponse & { data: string }, { url: string; body: { email: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse & { data: string }>(url, { body })
|
||||
|
||||
export const verifyForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boolean; email: string }, { url: string; body: { token: string } }> = ({ url, body }) => {
|
||||
return post(url, { body }) as Promise<CommonResponse & { is_valid: boolean; email: string }>
|
||||
}
|
||||
|
||||
export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse>(url, { body })
|
||||
|
||||
export const sendWebAppForgotPasswordEmail: Fetcher<CommonResponse & { data: string }, { url: string; body: { email: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse & { data: string }>(url, { body }, { isPublicAPI: true })
|
||||
|
||||
export const verifyWebAppForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boolean; email: string }, { url: string; body: { token: string } }> = ({ url, body }) => {
|
||||
return post(url, { body }, { isPublicAPI: true }) as Promise<CommonResponse & { is_valid: boolean; email: string }>
|
||||
}
|
||||
|
||||
export const changeWebAppPasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse>(url, { body }, { isPublicAPI: true })
|
||||
|
||||
export const uploadRemoteFileInfo = (url: string, isPublic?: boolean, silent?: boolean) => {
|
||||
return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic, silent })
|
||||
}
|
||||
|
||||
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
||||
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
|
||||
|
||||
export const emailLoginWithCode = (data: { email: string; code: string; token: string; language: string }) =>
|
||||
post<LoginResponse>('/email-code-login/validity', { body: data })
|
||||
|
||||
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
||||
post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } })
|
||||
|
||||
export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }) =>
|
||||
post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body })
|
||||
|
||||
export const sendWebAppEMailLoginCode = (email: string, language = 'en-US') =>
|
||||
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } }, { isPublicAPI: true })
|
||||
|
||||
export const webAppEmailLoginWithCode = (data: { email: string; code: string; token: string }) =>
|
||||
post<LoginResponse>('/email-code-login/validity', { body: data }, { isPublicAPI: true })
|
||||
|
||||
export const sendWebAppResetPasswordCode = (email: string, language = 'en-US') =>
|
||||
post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } }, { isPublicAPI: true })
|
||||
|
||||
export const verifyWebAppResetPasswordCode = (body: { email: string; code: string; token: string }) =>
|
||||
post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body }, { isPublicAPI: true })
|
||||
|
||||
export const sendDeleteAccountCode = () =>
|
||||
get<CommonResponse & { data: string }>('/account/delete/verify')
|
||||
|
||||
export const verifyDeleteAccountCode = (body: { code: string; token: string }) =>
|
||||
post<CommonResponse & { is_valid: boolean }>('/account/delete', { body })
|
||||
|
||||
export const submitDeleteAccountFeedback = (body: { feedback: string; email: string }) =>
|
||||
post<CommonResponse>('/account/delete/feedback', { body })
|
||||
|
||||
export const getDocDownloadUrl = (doc_name: string) =>
|
||||
get<{ url: string }>('/compliance/download', { params: { doc_name } }, { silent: true })
|
||||
|
||||
export const sendVerifyCode = (body: { email: string; phase: string; token?: string }) =>
|
||||
post<CommonResponse & { data: string }>('/account/change-email', { body })
|
||||
|
||||
export const verifyEmail = (body: { email: string; code: string; token: string }) =>
|
||||
post<CommonResponse & { is_valid: boolean; email: string; token: string }>('/account/change-email/validity', { body })
|
||||
|
||||
export const resetEmail = (body: { new_email: string; token: string }) =>
|
||||
post<CommonResponse>('/account/change-email/reset', { body })
|
||||
|
||||
export const checkEmailExisted = (body: { email: string }) =>
|
||||
post<CommonResponse>('/account/change-email/check-email-unique', { body }, { silent: true })
|
||||
289
dify/web/service/datasets.ts
Normal file
289
dify/web/service/datasets.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import qs from 'qs'
|
||||
import { del, get, patch, post, put } from './base'
|
||||
import type {
|
||||
CreateDocumentReq,
|
||||
DataSet,
|
||||
DataSetListResponse,
|
||||
ErrorDocsResponse,
|
||||
ExternalAPIDeleteResponse,
|
||||
ExternalAPIItem,
|
||||
ExternalAPIListResponse,
|
||||
ExternalAPIUsage,
|
||||
ExternalKnowledgeBaseHitTestingResponse,
|
||||
ExternalKnowledgeItem,
|
||||
FetchDatasetsParams,
|
||||
FileIndexingEstimateResponse,
|
||||
HitTestingRecordsResponse,
|
||||
HitTestingResponse,
|
||||
IndexingEstimateParams,
|
||||
IndexingEstimateResponse,
|
||||
IndexingStatusBatchResponse,
|
||||
IndexingStatusResponse,
|
||||
ProcessRuleResponse,
|
||||
RelatedAppResponse,
|
||||
createDocumentResponse,
|
||||
} from '@/models/datasets'
|
||||
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
|
||||
import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations'
|
||||
import type { CommonResponse, DataSourceNotionWorkspace } from '@/models/common'
|
||||
import { DataSourceProvider } from '@/models/common'
|
||||
import type {
|
||||
ApiKeysListResponse,
|
||||
CreateApiKeyResponse,
|
||||
} from '@/models/app'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
|
||||
// apis for documents in a dataset
|
||||
|
||||
type CommonDocReq = {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
}
|
||||
|
||||
type BatchReq = {
|
||||
datasetId: string
|
||||
batchId: string
|
||||
}
|
||||
|
||||
export type SortType = 'created_at' | 'hit_count' | '-created_at' | '-hit_count'
|
||||
|
||||
export type MetadataType = 'all' | 'only' | 'without'
|
||||
|
||||
export const fetchDatasetDetail: Fetcher<DataSet, string> = (datasetId: string) => {
|
||||
return get<DataSet>(`/datasets/${datasetId}`)
|
||||
}
|
||||
|
||||
export const updateDatasetSetting: Fetcher<DataSet, {
|
||||
datasetId: string
|
||||
body: Partial<Pick<DataSet,
|
||||
'name' | 'description' | 'permission' | 'partial_member_list' | 'indexing_technique' | 'retrieval_model' | 'embedding_model' | 'embedding_model_provider' | 'icon_info' | 'doc_form'
|
||||
>>
|
||||
}> = ({ datasetId, body }) => {
|
||||
return patch<DataSet>(`/datasets/${datasetId}`, { body })
|
||||
}
|
||||
|
||||
export const fetchDatasetRelatedApps: Fetcher<RelatedAppResponse, string> = (datasetId: string) => {
|
||||
return get<RelatedAppResponse>(`/datasets/${datasetId}/related-apps`)
|
||||
}
|
||||
|
||||
export const fetchDatasets: Fetcher<DataSetListResponse, FetchDatasetsParams> = ({ url, params }) => {
|
||||
const urlParams = qs.stringify(params, { indices: false })
|
||||
return get<DataSetListResponse>(`${url}?${urlParams}`)
|
||||
}
|
||||
|
||||
export const createEmptyDataset: Fetcher<DataSet, { name: string }> = ({ name }) => {
|
||||
return post<DataSet>('/datasets', { body: { name } })
|
||||
}
|
||||
|
||||
export const checkIsUsedInApp: Fetcher<{ is_using: boolean }, string> = (id) => {
|
||||
return get<{ is_using: boolean }>(`/datasets/${id}/use-check`, {}, {
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteDataset: Fetcher<DataSet, string> = (datasetID) => {
|
||||
return del<DataSet>(`/datasets/${datasetID}`)
|
||||
}
|
||||
|
||||
export const fetchExternalAPIList: Fetcher<ExternalAPIListResponse, { url: string }> = ({ url }) => {
|
||||
return get<ExternalAPIListResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchExternalAPI: Fetcher<ExternalAPIItem, { apiTemplateId: string }> = ({ apiTemplateId }) => {
|
||||
return get<ExternalAPIItem>(`/datasets/external-knowledge-api/${apiTemplateId}`)
|
||||
}
|
||||
|
||||
export const updateExternalAPI: Fetcher<ExternalAPIItem, { apiTemplateId: string; body: ExternalAPIItem }> = ({ apiTemplateId, body }) => {
|
||||
return patch<ExternalAPIItem>(`/datasets/external-knowledge-api/${apiTemplateId}`, { body })
|
||||
}
|
||||
|
||||
export const deleteExternalAPI: Fetcher<ExternalAPIDeleteResponse, { apiTemplateId: string }> = ({ apiTemplateId }) => {
|
||||
return del<ExternalAPIDeleteResponse>(`/datasets/external-knowledge-api/${apiTemplateId}`)
|
||||
}
|
||||
|
||||
export const checkUsageExternalAPI: Fetcher<ExternalAPIUsage, { apiTemplateId: string }> = ({ apiTemplateId }) => {
|
||||
return get<ExternalAPIUsage>(`/datasets/external-knowledge-api/${apiTemplateId}/use-check`)
|
||||
}
|
||||
|
||||
export const createExternalAPI: Fetcher<ExternalAPIItem, { body: CreateExternalAPIReq }> = ({ body }) => {
|
||||
return post<ExternalAPIItem>('/datasets/external-knowledge-api', { body })
|
||||
}
|
||||
|
||||
export const createExternalKnowledgeBase: Fetcher<ExternalKnowledgeItem, { body: CreateKnowledgeBaseReq }> = ({ body }) => {
|
||||
return post<ExternalKnowledgeItem>('/datasets/external', { body })
|
||||
}
|
||||
|
||||
export const fetchDefaultProcessRule: Fetcher<ProcessRuleResponse, { url: string }> = ({ url }) => {
|
||||
return get<ProcessRuleResponse>(url)
|
||||
}
|
||||
export const fetchProcessRule: Fetcher<ProcessRuleResponse, { params: { documentId: string } }> = ({ params: { documentId } }) => {
|
||||
return get<ProcessRuleResponse>('/datasets/process-rule', { params: { document_id: documentId } })
|
||||
}
|
||||
|
||||
export const createFirstDocument: Fetcher<createDocumentResponse, { body: CreateDocumentReq }> = ({ body }) => {
|
||||
return post<createDocumentResponse>('/datasets/init', { body })
|
||||
}
|
||||
|
||||
export const createDocument: Fetcher<createDocumentResponse, { datasetId: string; body: CreateDocumentReq }> = ({ datasetId, body }) => {
|
||||
return post<createDocumentResponse>(`/datasets/${datasetId}/documents`, { body })
|
||||
}
|
||||
|
||||
export const fetchIndexingEstimate: Fetcher<IndexingEstimateResponse, CommonDocReq> = ({ datasetId, documentId }) => {
|
||||
return get<IndexingEstimateResponse>(`/datasets/${datasetId}/documents/${documentId}/indexing-estimate`, {})
|
||||
}
|
||||
export const fetchIndexingEstimateBatch: Fetcher<IndexingEstimateResponse, BatchReq> = ({ datasetId, batchId }) => {
|
||||
return get<IndexingEstimateResponse>(`/datasets/${datasetId}/batch/${batchId}/indexing-estimate`, {})
|
||||
}
|
||||
|
||||
export const fetchIndexingStatus: Fetcher<IndexingStatusResponse, CommonDocReq> = ({ datasetId, documentId }) => {
|
||||
return get<IndexingStatusResponse>(`/datasets/${datasetId}/documents/${documentId}/indexing-status`, {})
|
||||
}
|
||||
|
||||
export const fetchIndexingStatusBatch: Fetcher<IndexingStatusBatchResponse, BatchReq> = ({ datasetId, batchId }) => {
|
||||
return get<IndexingStatusBatchResponse>(`/datasets/${datasetId}/batch/${batchId}/indexing-status`, {})
|
||||
}
|
||||
|
||||
export const renameDocumentName: Fetcher<CommonResponse, CommonDocReq & { name: string }> = ({ datasetId, documentId, name }) => {
|
||||
return post<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/rename`, {
|
||||
body: { name },
|
||||
})
|
||||
}
|
||||
|
||||
export const pauseDocIndexing: Fetcher<CommonResponse, CommonDocReq> = ({ datasetId, documentId }) => {
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/processing/pause`)
|
||||
}
|
||||
|
||||
export const resumeDocIndexing: Fetcher<CommonResponse, CommonDocReq> = ({ datasetId, documentId }) => {
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/processing/resume`)
|
||||
}
|
||||
|
||||
export const preImportNotionPages: Fetcher<{ notion_info: DataSourceNotionWorkspace[] }, { url: string; datasetId?: string }> = ({ url, datasetId }) => {
|
||||
return get<{ notion_info: DataSourceNotionWorkspace[] }>(url, { params: { dataset_id: datasetId } })
|
||||
}
|
||||
|
||||
export const modifyDocMetadata: Fetcher<CommonResponse, CommonDocReq & { body: { doc_type: string; doc_metadata: Record<string, any> } }> = ({ datasetId, documentId, body }) => {
|
||||
return put<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/metadata`, { body })
|
||||
}
|
||||
|
||||
// hit testing
|
||||
export const hitTesting: Fetcher<HitTestingResponse, { datasetId: string; queryText: string; retrieval_model: RetrievalConfig }> = ({ datasetId, queryText, retrieval_model }) => {
|
||||
return post<HitTestingResponse>(`/datasets/${datasetId}/hit-testing`, { body: { query: queryText, retrieval_model } })
|
||||
}
|
||||
|
||||
export const externalKnowledgeBaseHitTesting: Fetcher<ExternalKnowledgeBaseHitTestingResponse, { datasetId: string; query: string; external_retrieval_model: { top_k: number; score_threshold: number; score_threshold_enabled: boolean } }> = ({ datasetId, query, external_retrieval_model }) => {
|
||||
return post<ExternalKnowledgeBaseHitTestingResponse>(`/datasets/${datasetId}/external-hit-testing`, { body: { query, external_retrieval_model } })
|
||||
}
|
||||
|
||||
export const fetchTestingRecords: Fetcher<HitTestingRecordsResponse, { datasetId: string; params: { page: number; limit: number } }> = ({ datasetId, params }) => {
|
||||
return get<HitTestingRecordsResponse>(`/datasets/${datasetId}/queries`, { params })
|
||||
}
|
||||
|
||||
export const fetchFileIndexingEstimate: Fetcher<FileIndexingEstimateResponse, IndexingEstimateParams> = (body: IndexingEstimateParams) => {
|
||||
return post<FileIndexingEstimateResponse>('/datasets/indexing-estimate', { body })
|
||||
}
|
||||
|
||||
export const fetchNotionPagePreview: Fetcher<{ content: string }, { workspaceID: string; pageID: string; pageType: string; credentialID: string; }> = ({ workspaceID, pageID, pageType, credentialID }) => {
|
||||
return get<{ content: string }>(`notion/workspaces/${workspaceID}/pages/${pageID}/${pageType}/preview`, {
|
||||
params: {
|
||||
credential_id: credentialID,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchApiKeysList: Fetcher<ApiKeysListResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<ApiKeysListResponse>(url, params)
|
||||
}
|
||||
|
||||
export const delApikey: Fetcher<CommonResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return del<CommonResponse>(url, params)
|
||||
}
|
||||
|
||||
export const createApikey: Fetcher<CreateApiKeyResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
||||
return post<CreateApiKeyResponse>(url, body)
|
||||
}
|
||||
|
||||
export const fetchDataSources = () => {
|
||||
return get<CommonResponse>('api-key-auth/data-source')
|
||||
}
|
||||
|
||||
export const createDataSourceApiKeyBinding: Fetcher<CommonResponse, Record<string, any>> = (body) => {
|
||||
return post<CommonResponse>('api-key-auth/data-source/binding', { body })
|
||||
}
|
||||
|
||||
export const removeDataSourceApiKeyBinding: Fetcher<CommonResponse, string> = (id: string) => {
|
||||
return del<CommonResponse>(`api-key-auth/data-source/${id}`)
|
||||
}
|
||||
|
||||
export const createFirecrawlTask: Fetcher<CommonResponse, Record<string, any>> = (body) => {
|
||||
return post<CommonResponse>('website/crawl', {
|
||||
body: {
|
||||
...body,
|
||||
provider: DataSourceProvider.fireCrawl,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const checkFirecrawlTaskStatus: Fetcher<CommonResponse, string> = (jobId: string) => {
|
||||
return get<CommonResponse>(`website/crawl/status/${jobId}`, {
|
||||
params: {
|
||||
provider: DataSourceProvider.fireCrawl,
|
||||
},
|
||||
}, {
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
|
||||
export const createJinaReaderTask: Fetcher<CommonResponse, Record<string, any>> = (body) => {
|
||||
return post<CommonResponse>('website/crawl', {
|
||||
body: {
|
||||
...body,
|
||||
provider: DataSourceProvider.jinaReader,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const checkJinaReaderTaskStatus: Fetcher<CommonResponse, string> = (jobId: string) => {
|
||||
return get<CommonResponse>(`website/crawl/status/${jobId}`, {
|
||||
params: {
|
||||
provider: 'jinareader',
|
||||
},
|
||||
}, {
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
|
||||
export const createWatercrawlTask: Fetcher<CommonResponse, Record<string, any>> = (body) => {
|
||||
return post<CommonResponse>('website/crawl', {
|
||||
body: {
|
||||
...body,
|
||||
provider: DataSourceProvider.waterCrawl,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const checkWatercrawlTaskStatus: Fetcher<CommonResponse, string> = (jobId: string) => {
|
||||
return get<CommonResponse>(`website/crawl/status/${jobId}`, {
|
||||
params: {
|
||||
provider: DataSourceProvider.waterCrawl,
|
||||
},
|
||||
}, {
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
|
||||
export type FileTypesRes = {
|
||||
allowed_extensions: string[]
|
||||
}
|
||||
|
||||
export const fetchSupportFileTypes: Fetcher<FileTypesRes, { url: string }> = ({ url }) => {
|
||||
return get<FileTypesRes>(url)
|
||||
}
|
||||
|
||||
export const getErrorDocs: Fetcher<ErrorDocsResponse, { datasetId: string }> = ({ datasetId }) => {
|
||||
return get<ErrorDocsResponse>(`/datasets/${datasetId}/error-docs`)
|
||||
}
|
||||
|
||||
export const retryErrorDocs: Fetcher<CommonResponse, { datasetId: string; document_ids: string[] }> = ({ datasetId, document_ids }) => {
|
||||
return post<CommonResponse>(`/datasets/${datasetId}/retry`, { body: { document_ids } })
|
||||
}
|
||||
125
dify/web/service/debug.ts
Normal file
125
dify/web/service/debug.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { get, post, ssePost } from './base'
|
||||
import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessageReplace, IOnThought } from './base'
|
||||
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
|
||||
import type { AppModeEnum, ModelModeType } from '@/types/app'
|
||||
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
export type BasicAppFirstRes = {
|
||||
prompt: string
|
||||
variables: string[]
|
||||
opening_statement: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type GenRes = {
|
||||
modified: string
|
||||
message?: string // tip for human
|
||||
variables?: string[] // only for basic app first time rule
|
||||
opening_statement?: string // only for basic app first time rule
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type CodeGenRes = {
|
||||
code: string
|
||||
language: string[]
|
||||
error?: string
|
||||
}
|
||||
|
||||
export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace }: {
|
||||
onData: IOnData
|
||||
onCompleted: IOnCompleted
|
||||
onFile: IOnFile
|
||||
onThought: IOnThought
|
||||
onMessageEnd: IOnMessageEnd
|
||||
onMessageReplace: IOnMessageReplace
|
||||
onError: IOnError
|
||||
getAbortController?: (abortController: AbortController) => void
|
||||
}) => {
|
||||
return ssePost(`apps/${appId}/chat-messages`, {
|
||||
body: {
|
||||
...body,
|
||||
response_mode: 'streaming',
|
||||
},
|
||||
}, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace })
|
||||
}
|
||||
|
||||
export const stopChatMessageResponding = async (appId: string, taskId: string) => {
|
||||
return post(`apps/${appId}/chat-messages/${taskId}/stop`)
|
||||
}
|
||||
|
||||
export const sendCompletionMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError, onMessageReplace }: {
|
||||
onData: IOnData
|
||||
onCompleted: IOnCompleted
|
||||
onError: IOnError
|
||||
onMessageReplace: IOnMessageReplace
|
||||
}) => {
|
||||
return ssePost(`apps/${appId}/completion-messages`, {
|
||||
body: {
|
||||
...body,
|
||||
response_mode: 'streaming',
|
||||
},
|
||||
}, { onData, onCompleted, onError, onMessageReplace })
|
||||
}
|
||||
|
||||
export const fetchSuggestedQuestions = (appId: string, messageId: string, getAbortController?: any) => {
|
||||
return get(
|
||||
`apps/${appId}/chat-messages/${messageId}/suggested-questions`,
|
||||
{},
|
||||
{
|
||||
getAbortController,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export const fetchConversationMessages = (appId: string, conversation_id: string, getAbortController?: any) => {
|
||||
return get(`apps/${appId}/chat-messages`, {
|
||||
params: {
|
||||
conversation_id,
|
||||
},
|
||||
}, {
|
||||
getAbortController,
|
||||
})
|
||||
}
|
||||
|
||||
export const generateBasicAppFirstTimeRule = (body: Record<string, any>) => {
|
||||
return post<BasicAppFirstRes>('/rule-generate', {
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
export const generateRule = (body: Record<string, any>) => {
|
||||
return post<GenRes>('/instruction-generate', {
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchModelParams = (providerName: string, modelId: string) => {
|
||||
return get(`workspaces/current/model-providers/${providerName}/models/parameter-rules`, {
|
||||
params: {
|
||||
model: modelId,
|
||||
},
|
||||
}) as Promise<{ data: ModelParameterRule[] }>
|
||||
}
|
||||
|
||||
export const fetchPromptTemplate = ({
|
||||
appMode,
|
||||
mode,
|
||||
modelName,
|
||||
hasSetDataSet,
|
||||
}: { appMode: AppModeEnum; mode: ModelModeType; modelName: string; hasSetDataSet: boolean }) => {
|
||||
return get<Promise<{ chat_prompt_config: ChatPromptConfig; completion_prompt_config: CompletionPromptConfig; stop: [] }>>('/app/prompt-templates', {
|
||||
params: {
|
||||
app_mode: appMode,
|
||||
model_mode: mode,
|
||||
model_name: modelName,
|
||||
has_context: hasSetDataSet,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchTextGenerationMessage = ({
|
||||
appId,
|
||||
messageId,
|
||||
}: { appId: string; messageId: string }) => {
|
||||
return get<Promise<any>>(`/apps/${appId}/messages/${messageId}`)
|
||||
}
|
||||
102
dify/web/service/demo/index.tsx
Normal file
102
dify/web/service/demo/index.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import useSWR, { useSWRConfig } from 'swr'
|
||||
import { createApp, fetchAppDetail, fetchAppList, getAppDailyConversations, getAppDailyEndUsers, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
const Service: FC = () => {
|
||||
const { data: appList, error: appListError } = useSWR({ url: '/apps', params: { page: 1 } }, fetchAppList)
|
||||
const { data: firstApp, error: appDetailError } = useSWR({ url: '/apps', id: '1' }, fetchAppDetail)
|
||||
const { data: updateAppSiteStatusRes, error: err1 } = useSWR({ url: '/apps', id: '1', body: { enable_site: false } }, updateAppSiteStatus)
|
||||
const { data: updateAppApiStatusRes, error: err2 } = useSWR({ url: '/apps', id: '1', body: { enable_api: true } }, updateAppApiStatus)
|
||||
const { data: updateAppRateLimitRes, error: err3 } = useSWR({ url: '/apps', id: '1', body: { api_rpm: 10, api_rph: 20 } }, updateAppRateLimit)
|
||||
const { data: updateAppSiteCodeRes, error: err4 } = useSWR({ url: '/apps', id: '1', body: {} }, updateAppSiteAccessToken)
|
||||
const { data: updateAppSiteConfigRes, error: err5 } = useSWR({ url: '/apps', id: '1', body: { title: 'title test', author: 'author test' } }, updateAppSiteConfig)
|
||||
const { data: getAppDailyConversationsRes, error: err6 } = useSWR({ url: '/apps', id: '1', body: { start: '1', end: '2' } }, getAppDailyConversations)
|
||||
const { data: getAppDailyEndUsersRes, error: err7 } = useSWR({ url: '/apps', id: '1', body: { start: '1', end: '2' } }, getAppDailyEndUsers)
|
||||
const { data: updateAppModelConfigRes, error: err8 } = useSWR({ url: '/apps', id: '1', body: { model_id: 'gpt-100' } }, updateAppModelConfig)
|
||||
|
||||
const { mutate } = useSWRConfig()
|
||||
|
||||
const handleCreateApp = async () => {
|
||||
await createApp({
|
||||
name: `new app${Math.round(Math.random() * 100)}`,
|
||||
mode: AppModeEnum.CHAT,
|
||||
})
|
||||
// reload app list
|
||||
mutate({ url: '/apps', params: { page: 1 } })
|
||||
}
|
||||
|
||||
if (appListError || appDetailError || err1 || err2 || err3 || err4 || err5 || err6 || err7 || err8)
|
||||
return <div>{JSON.stringify(appListError)}</div>
|
||||
|
||||
if (!appList || !firstApp || !updateAppSiteStatusRes || !updateAppApiStatusRes || !updateAppRateLimitRes || !updateAppSiteCodeRes || !updateAppSiteConfigRes || !getAppDailyConversationsRes || !getAppDailyEndUsersRes || !updateAppModelConfigRes)
|
||||
return <Loading />
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<div>
|
||||
<div>1.App list</div>
|
||||
<div>
|
||||
{appList.data.map(item => (
|
||||
<div key={item.id}>{item.id} {item.name}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>2.First app detail</div>
|
||||
<div>{JSON.stringify(firstApp)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="button" onClick={handleCreateApp}>Click me to Create App</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>4.updateAppSiteStatusRes</div>
|
||||
<div>{JSON.stringify(updateAppSiteStatusRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>5.updateAppApiStatusRes</div>
|
||||
<div>{JSON.stringify(updateAppApiStatusRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>6.updateAppRateLimitRes</div>
|
||||
<div>{JSON.stringify(updateAppRateLimitRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>7.updateAppSiteCodeRes</div>
|
||||
<div>{JSON.stringify(updateAppSiteCodeRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>8.updateAppSiteConfigRes</div>
|
||||
<div>{JSON.stringify(updateAppSiteConfigRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>9.getAppDailyConversationsRes</div>
|
||||
<div>{JSON.stringify(getAppDailyConversationsRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>10.getAppDailyEndUsersRes</div>
|
||||
<div>{JSON.stringify(getAppDailyEndUsersRes)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>11.updateAppModelConfigRes</div>
|
||||
<div>{JSON.stringify(updateAppModelConfigRes)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Service)
|
||||
46
dify/web/service/explore.ts
Normal file
46
dify/web/service/explore.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { del, get, patch, post } from './base'
|
||||
import type { App, AppCategory } from '@/models/explore'
|
||||
import type { AccessMode } from '@/models/access-control'
|
||||
|
||||
export const fetchAppList = () => {
|
||||
return get<{
|
||||
categories: AppCategory[]
|
||||
recommended_apps: App[]
|
||||
}>('/explore/apps')
|
||||
}
|
||||
|
||||
export const fetchAppDetail = (id: string): Promise<any> => {
|
||||
return get(`/explore/apps/${id}`)
|
||||
}
|
||||
|
||||
export const fetchInstalledAppList = (app_id?: string | null) => {
|
||||
return get(`/installed-apps${app_id ? `?app_id=${app_id}` : ''}`)
|
||||
}
|
||||
|
||||
export const installApp = (id: string) => {
|
||||
return post('/installed-apps', {
|
||||
body: {
|
||||
app_id: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const uninstallApp = (id: string) => {
|
||||
return del(`/installed-apps/${id}`)
|
||||
}
|
||||
|
||||
export const updatePinStatus = (id: string, isPinned: boolean) => {
|
||||
return patch(`/installed-apps/${id}`, {
|
||||
body: {
|
||||
is_pinned: isPinned,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const getToolProviders = () => {
|
||||
return get('/workspaces/current/tool-providers')
|
||||
}
|
||||
|
||||
export const getAppAccessModeByAppId = (appId: string) => {
|
||||
return get<{ accessMode: AccessMode }>(`/enterprise/webapp/app/access-mode?appId=${appId}`)
|
||||
}
|
||||
218
dify/web/service/fetch.ts
Normal file
218
dify/web/service/fetch.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import type { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, Hooks } from 'ky'
|
||||
import ky from 'ky'
|
||||
import type { IOtherOptions } from './base'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { API_PREFIX, APP_VERSION, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_MARKETPLACE, MARKETPLACE_API_PREFIX, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
|
||||
import Cookies from 'js-cookie'
|
||||
import { getWebAppAccessToken, getWebAppPassport } from './webapp-auth'
|
||||
|
||||
const TIME_OUT = 100000
|
||||
|
||||
export const ContentType = {
|
||||
json: 'application/json',
|
||||
stream: 'text/event-stream',
|
||||
audio: 'audio/mpeg',
|
||||
form: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
download: 'application/octet-stream', // for download
|
||||
downloadZip: 'application/zip', // for download
|
||||
upload: 'multipart/form-data', // for upload
|
||||
}
|
||||
|
||||
export type FetchOptionType = Omit<RequestInit, 'body'> & {
|
||||
params?: Record<string, any>
|
||||
body?: BodyInit | Record<string, any> | null
|
||||
}
|
||||
|
||||
const afterResponse204: AfterResponseHook = async (_request, _options, response) => {
|
||||
if (response.status === 204) return Response.json({ result: 'success' })
|
||||
}
|
||||
|
||||
export type ResponseError = {
|
||||
code: string
|
||||
message: string
|
||||
status: number
|
||||
}
|
||||
|
||||
const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook => {
|
||||
return async (_request, _options, response) => {
|
||||
const clonedResponse = response.clone()
|
||||
if (!/^([23])\d{2}$/.test(String(clonedResponse.status))) {
|
||||
const bodyJson = clonedResponse.json() as Promise<ResponseError>
|
||||
switch (clonedResponse.status) {
|
||||
case 403:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!otherOptions.silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
if (data.code === 'already_setup')
|
||||
globalThis.location.href = `${globalThis.location.origin}/signin`
|
||||
})
|
||||
break
|
||||
case 401:
|
||||
return Promise.reject(response)
|
||||
// fall through
|
||||
default:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!otherOptions.silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
})
|
||||
return Promise.reject(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const beforeErrorToast = (otherOptions: IOtherOptions): BeforeErrorHook => {
|
||||
return (error) => {
|
||||
if (!otherOptions.silent)
|
||||
Toast.notify({ type: 'error', message: error.message })
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
const SHARE_ROUTE_DENY_LIST = new Set(['webapp-signin', 'check-code', 'login'])
|
||||
|
||||
const resolveShareCode = () => {
|
||||
const pathnameSegments = globalThis.location.pathname.split('/').filter(Boolean)
|
||||
const lastSegment = pathnameSegments.at(-1) || ''
|
||||
if (lastSegment && !SHARE_ROUTE_DENY_LIST.has(lastSegment))
|
||||
return lastSegment
|
||||
|
||||
const redirectParam = new URLSearchParams(globalThis.location.search).get('redirect_url')
|
||||
if (!redirectParam)
|
||||
return ''
|
||||
try {
|
||||
const redirectUrl = new URL(decodeURIComponent(redirectParam), globalThis.location.origin)
|
||||
const redirectSegments = redirectUrl.pathname.split('/').filter(Boolean)
|
||||
const redirectSegment = redirectSegments.at(-1) || ''
|
||||
return SHARE_ROUTE_DENY_LIST.has(redirectSegment) ? '' : redirectSegment
|
||||
}
|
||||
catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const beforeRequestPublicWithCode = (request: Request) => {
|
||||
const accessToken = getWebAppAccessToken()
|
||||
if (accessToken)
|
||||
request.headers.set('Authorization', `Bearer ${accessToken}`)
|
||||
else
|
||||
request.headers.delete('Authorization')
|
||||
const shareCode = resolveShareCode()
|
||||
if (!shareCode)
|
||||
return
|
||||
request.headers.set(WEB_APP_SHARE_CODE_HEADER_NAME, shareCode)
|
||||
request.headers.set(PASSPORT_HEADER_NAME, getWebAppPassport(shareCode))
|
||||
}
|
||||
|
||||
const baseHooks: Hooks = {
|
||||
afterResponse: [
|
||||
afterResponse204,
|
||||
],
|
||||
}
|
||||
|
||||
const baseClient = ky.create({
|
||||
hooks: baseHooks,
|
||||
timeout: TIME_OUT,
|
||||
})
|
||||
|
||||
export const getBaseOptions = (): RequestInit => ({
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
credentials: 'include', // always send cookies、HTTP Basic authentication.
|
||||
headers: new Headers({
|
||||
'Content-Type': ContentType.json,
|
||||
}),
|
||||
redirect: 'follow',
|
||||
})
|
||||
|
||||
async function base<T>(url: string, options: FetchOptionType = {}, otherOptions: IOtherOptions = {}): Promise<T> {
|
||||
const baseOptions = getBaseOptions()
|
||||
const { params, body, headers, ...init } = Object.assign({}, baseOptions, options)
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
isMarketplaceAPI = false,
|
||||
bodyStringify = true,
|
||||
needAllResponseContent,
|
||||
deleteContentType,
|
||||
getAbortController,
|
||||
} = otherOptions
|
||||
|
||||
let base: string
|
||||
if (isMarketplaceAPI)
|
||||
base = MARKETPLACE_API_PREFIX
|
||||
else if (isPublicAPI)
|
||||
base = PUBLIC_API_PREFIX
|
||||
else
|
||||
base = API_PREFIX
|
||||
|
||||
if (getAbortController) {
|
||||
const abortController = new AbortController()
|
||||
getAbortController(abortController)
|
||||
options.signal = abortController.signal
|
||||
}
|
||||
|
||||
const fetchPathname = base + (url.startsWith('/') ? url : `/${url}`)
|
||||
if (!isMarketplaceAPI)
|
||||
(headers as any).set(CSRF_HEADER_NAME, Cookies.get(CSRF_COOKIE_NAME()) || '')
|
||||
|
||||
if (deleteContentType)
|
||||
(headers as any).delete('Content-Type')
|
||||
|
||||
// ! For Marketplace API, help to filter tags added in new version
|
||||
if (isMarketplaceAPI)
|
||||
(headers as any).set('X-Dify-Version', !IS_MARKETPLACE ? APP_VERSION : '999.0.0')
|
||||
|
||||
const client = baseClient.extend({
|
||||
hooks: {
|
||||
...baseHooks,
|
||||
beforeError: [
|
||||
...baseHooks.beforeError || [],
|
||||
beforeErrorToast(otherOptions),
|
||||
],
|
||||
beforeRequest: [
|
||||
...baseHooks.beforeRequest || [],
|
||||
isPublicAPI && beforeRequestPublicWithCode,
|
||||
].filter((h): h is BeforeRequestHook => Boolean(h)),
|
||||
afterResponse: [
|
||||
...baseHooks.afterResponse || [],
|
||||
afterResponseErrorCode(otherOptions),
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const res = await client(fetchPathname, {
|
||||
...init,
|
||||
headers,
|
||||
credentials: isMarketplaceAPI
|
||||
? 'omit'
|
||||
: (options.credentials || 'include'),
|
||||
retry: {
|
||||
methods: [],
|
||||
},
|
||||
...(bodyStringify ? { json: body } : { body: body as BodyInit }),
|
||||
searchParams: params,
|
||||
fetch(resource: RequestInfo | URL, options?: RequestInit) {
|
||||
if (resource instanceof Request && options) {
|
||||
const mergedHeaders = new Headers(options.headers || {})
|
||||
resource.headers.forEach((value, key) => {
|
||||
mergedHeaders.append(key, value)
|
||||
})
|
||||
options.headers = mergedHeaders
|
||||
}
|
||||
return globalThis.fetch(resource, options)
|
||||
},
|
||||
})
|
||||
|
||||
if (needAllResponseContent)
|
||||
return res as T
|
||||
const contentType = res.headers.get('content-type')
|
||||
if (
|
||||
contentType
|
||||
&& [ContentType.download, ContentType.audio, ContentType.downloadZip].includes(contentType)
|
||||
)
|
||||
return await res.blob() as T
|
||||
|
||||
return await res.json() as T
|
||||
}
|
||||
|
||||
export { base }
|
||||
269
dify/web/service/knowledge/use-create-dataset.ts
Normal file
269
dify/web/service/knowledge/use-create-dataset.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import groupBy from 'lodash-es/groupBy'
|
||||
import type { MutationOptions } from '@tanstack/react-query'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { createDocument, createFirstDocument, fetchDefaultProcessRule, fetchFileIndexingEstimate } from '../datasets'
|
||||
import type { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||
import type {
|
||||
ChunkingMode,
|
||||
CrawlOptions,
|
||||
CrawlResultItem,
|
||||
CreateDatasetReq,
|
||||
CreateDatasetResponse,
|
||||
CreateDocumentReq,
|
||||
CustomFile,
|
||||
DataSourceType,
|
||||
FileIndexingEstimateResponse,
|
||||
IndexingEstimateParams,
|
||||
NotionInfo,
|
||||
ProcessRule,
|
||||
ProcessRuleResponse,
|
||||
createDocumentResponse,
|
||||
} from '@/models/datasets'
|
||||
import type { DataSourceProvider, NotionPage } from '@/models/common'
|
||||
import { post } from '../base'
|
||||
|
||||
const NAME_SPACE = 'knowledge/create-dataset'
|
||||
|
||||
export const getNotionInfo = (
|
||||
notionPages: NotionPage[],
|
||||
credentialId: string,
|
||||
) => {
|
||||
const workspacesMap = groupBy(notionPages, 'workspace_id')
|
||||
const workspaces = Object.keys(workspacesMap).map((workspaceId) => {
|
||||
return {
|
||||
workspaceId,
|
||||
pages: workspacesMap[workspaceId],
|
||||
}
|
||||
})
|
||||
return workspaces.map((workspace) => {
|
||||
return {
|
||||
credential_id: credentialId,
|
||||
workspace_id: workspace.workspaceId,
|
||||
pages: workspace.pages.map((page) => {
|
||||
const { page_id, page_name, page_icon, type } = page
|
||||
return {
|
||||
page_id,
|
||||
page_name,
|
||||
page_icon,
|
||||
type,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}) as NotionInfo[]
|
||||
}
|
||||
|
||||
export const getWebsiteInfo = (
|
||||
opts: {
|
||||
websiteCrawlProvider: DataSourceProvider
|
||||
websiteCrawlJobId: string
|
||||
websitePages: CrawlResultItem[]
|
||||
crawlOptions?: CrawlOptions
|
||||
},
|
||||
) => {
|
||||
const { websiteCrawlProvider, websiteCrawlJobId, websitePages, crawlOptions } = opts
|
||||
return {
|
||||
provider: websiteCrawlProvider,
|
||||
job_id: websiteCrawlJobId,
|
||||
urls: websitePages.map(page => page.source_url),
|
||||
only_main_content: crawlOptions?.only_main_content,
|
||||
}
|
||||
}
|
||||
|
||||
type GetFileIndexingEstimateParamsOptionBase = {
|
||||
docForm: ChunkingMode
|
||||
docLanguage: string
|
||||
indexingTechnique: IndexingType
|
||||
processRule: ProcessRule
|
||||
dataset_id: string
|
||||
}
|
||||
|
||||
type GetFileIndexingEstimateParamsOptionFile = GetFileIndexingEstimateParamsOptionBase & {
|
||||
dataSourceType: DataSourceType.FILE
|
||||
files: CustomFile[]
|
||||
}
|
||||
|
||||
const getFileIndexingEstimateParamsForFile = ({
|
||||
docForm,
|
||||
docLanguage,
|
||||
dataSourceType,
|
||||
files,
|
||||
indexingTechnique,
|
||||
processRule,
|
||||
dataset_id,
|
||||
}: GetFileIndexingEstimateParamsOptionFile): IndexingEstimateParams => {
|
||||
return {
|
||||
info_list: {
|
||||
data_source_type: dataSourceType,
|
||||
file_info_list: {
|
||||
file_ids: files.map(file => file.id) as string[],
|
||||
},
|
||||
},
|
||||
indexing_technique: indexingTechnique,
|
||||
process_rule: processRule,
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
dataset_id,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFetchFileIndexingEstimateForFile = (
|
||||
options: GetFileIndexingEstimateParamsOptionFile,
|
||||
mutationOptions: MutationOptions<FileIndexingEstimateResponse> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
return fetchFileIndexingEstimate(getFileIndexingEstimateParamsForFile(options))
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
type GetFileIndexingEstimateParamsOptionNotion = GetFileIndexingEstimateParamsOptionBase & {
|
||||
dataSourceType: DataSourceType.NOTION
|
||||
notionPages: NotionPage[]
|
||||
credential_id: string
|
||||
}
|
||||
|
||||
const getFileIndexingEstimateParamsForNotion = ({
|
||||
docForm,
|
||||
docLanguage,
|
||||
dataSourceType,
|
||||
notionPages,
|
||||
indexingTechnique,
|
||||
processRule,
|
||||
dataset_id,
|
||||
credential_id,
|
||||
}: GetFileIndexingEstimateParamsOptionNotion): IndexingEstimateParams => {
|
||||
return {
|
||||
info_list: {
|
||||
data_source_type: dataSourceType,
|
||||
notion_info_list: getNotionInfo(notionPages, credential_id),
|
||||
},
|
||||
indexing_technique: indexingTechnique,
|
||||
process_rule: processRule,
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
dataset_id,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFetchFileIndexingEstimateForNotion = (
|
||||
options: GetFileIndexingEstimateParamsOptionNotion,
|
||||
mutationOptions: MutationOptions<FileIndexingEstimateResponse> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
return fetchFileIndexingEstimate(getFileIndexingEstimateParamsForNotion(options))
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
type GetFileIndexingEstimateParamsOptionWeb = GetFileIndexingEstimateParamsOptionBase & {
|
||||
dataSourceType: DataSourceType.WEB
|
||||
websitePages: CrawlResultItem[]
|
||||
crawlOptions?: CrawlOptions
|
||||
websiteCrawlProvider: DataSourceProvider
|
||||
websiteCrawlJobId: string
|
||||
}
|
||||
|
||||
const getFileIndexingEstimateParamsForWeb = ({
|
||||
docForm,
|
||||
docLanguage,
|
||||
dataSourceType,
|
||||
websitePages,
|
||||
crawlOptions,
|
||||
websiteCrawlProvider,
|
||||
websiteCrawlJobId,
|
||||
indexingTechnique,
|
||||
processRule,
|
||||
dataset_id,
|
||||
}: GetFileIndexingEstimateParamsOptionWeb): IndexingEstimateParams => {
|
||||
return {
|
||||
info_list: {
|
||||
data_source_type: dataSourceType,
|
||||
website_info_list: getWebsiteInfo({
|
||||
websiteCrawlProvider,
|
||||
websiteCrawlJobId,
|
||||
websitePages,
|
||||
crawlOptions,
|
||||
}),
|
||||
},
|
||||
indexing_technique: indexingTechnique,
|
||||
process_rule: processRule,
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
dataset_id,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFetchFileIndexingEstimateForWeb = (
|
||||
options: GetFileIndexingEstimateParamsOptionWeb,
|
||||
mutationOptions: MutationOptions<FileIndexingEstimateResponse> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
return fetchFileIndexingEstimate(getFileIndexingEstimateParamsForWeb(options))
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateFirstDocument = (
|
||||
mutationOptions: MutationOptions<createDocumentResponse, Error, CreateDocumentReq> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: async (createDocumentReq: CreateDocumentReq,
|
||||
) => {
|
||||
return createFirstDocument({ body: createDocumentReq })
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateDocument = (
|
||||
datasetId: string,
|
||||
mutationOptions: MutationOptions<createDocumentResponse, Error, CreateDocumentReq> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: async (req: CreateDocumentReq) => {
|
||||
return createDocument({ datasetId, body: req })
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useFetchDefaultProcessRule = (
|
||||
mutationOptions: MutationOptions<ProcessRuleResponse, Error, string> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: async (url: string) => {
|
||||
return fetchDefaultProcessRule({ url })
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreatePipelineDataset = (
|
||||
mutationOptions: MutationOptions<CreateDatasetResponse, Error> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create-pipeline-empty-dataset'],
|
||||
mutationFn: () => {
|
||||
return post<CreateDatasetResponse>('/rag/pipeline/empty-dataset')
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreatePipelineDatasetFromCustomized = (
|
||||
mutationOptions: MutationOptions<CreateDatasetResponse, Error, CreateDatasetReq> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create-pipeline-dataset'],
|
||||
mutationFn: (req: CreateDatasetReq) => {
|
||||
return post<CreateDatasetResponse>('/rag/pipeline/dataset', { body: req })
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
99
dify/web/service/knowledge/use-dataset.ts
Normal file
99
dify/web/service/knowledge/use-dataset.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { MutationOptions } from '@tanstack/react-query'
|
||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import type {
|
||||
DataSet,
|
||||
DataSetListResponse,
|
||||
DatasetListRequest,
|
||||
IndexingStatusBatchRequest,
|
||||
IndexingStatusBatchResponse,
|
||||
ProcessRuleResponse,
|
||||
RelatedAppResponse,
|
||||
} from '@/models/datasets'
|
||||
import { get, post } from '../base'
|
||||
import { useInvalid } from '../use-base'
|
||||
import qs from 'qs'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
|
||||
const NAME_SPACE = 'dataset'
|
||||
|
||||
const DatasetListKey = [NAME_SPACE, 'list']
|
||||
|
||||
export const useDatasetList = (params: DatasetListRequest) => {
|
||||
const { initialPage, tag_ids, limit, include_all, keyword } = params
|
||||
return useInfiniteQuery({
|
||||
queryKey: [...DatasetListKey, initialPage, tag_ids, limit, include_all, keyword],
|
||||
queryFn: ({ pageParam = 1 }) => {
|
||||
const urlParams = qs.stringify({
|
||||
tag_ids,
|
||||
limit,
|
||||
include_all,
|
||||
keyword,
|
||||
page: pageParam,
|
||||
}, { indices: false })
|
||||
return get<DataSetListResponse>(`/datasets?${urlParams}`)
|
||||
},
|
||||
getNextPageParam: lastPage => lastPage.has_more ? lastPage.page + 1 : null,
|
||||
initialPageParam: initialPage,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDatasetList = () => {
|
||||
return useInvalid([...DatasetListKey])
|
||||
}
|
||||
|
||||
export const datasetDetailQueryKeyPrefix = [NAME_SPACE, 'detail']
|
||||
|
||||
export const useDatasetDetail = (datasetId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [...datasetDetailQueryKeyPrefix, datasetId],
|
||||
queryFn: () => get<DataSet>(`/datasets/${datasetId}`),
|
||||
enabled: !!datasetId,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDatasetRelatedApps = (datasetId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'related-apps', datasetId],
|
||||
queryFn: () => get<RelatedAppResponse>(`/datasets/${datasetId}/related-apps`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useIndexingStatusBatch = (
|
||||
params: IndexingStatusBatchRequest,
|
||||
mutationOptions: MutationOptions<IndexingStatusBatchResponse, Error> = {},
|
||||
) => {
|
||||
const { datasetId, batchId } = params
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'indexing-status-batch', datasetId, batchId],
|
||||
mutationFn: () => get<IndexingStatusBatchResponse>(`/datasets/${datasetId}/batch/${batchId}/indexing-status`),
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useProcessRule = (documentId: string) => {
|
||||
return useQuery<ProcessRuleResponse>({
|
||||
queryKey: [NAME_SPACE, 'process-rule', documentId],
|
||||
queryFn: () => get<ProcessRuleResponse>('/datasets/process-rule', { params: { document_id: documentId } }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDatasetApiBaseUrl = () => {
|
||||
return useQuery<{ api_base_url: string }>({
|
||||
queryKey: [NAME_SPACE, 'api-base-info'],
|
||||
queryFn: () => get<{ api_base_url: string }>('/datasets/api-base-info'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useEnableDatasetServiceApi = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'enable-api'],
|
||||
mutationFn: (datasetId: string) => post<CommonResponse>(`/datasets/${datasetId}/api-keys/enable`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDisableDatasetServiceApi = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'disable-api'],
|
||||
mutationFn: (datasetId: string) => post<CommonResponse>(`/datasets/${datasetId}/api-keys/disable`),
|
||||
})
|
||||
}
|
||||
165
dify/web/service/knowledge/use-document.ts
Normal file
165
dify/web/service/knowledge/use-document.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { del, get, patch } from '../base'
|
||||
import { useInvalid } from '../use-base'
|
||||
import type { MetadataType, SortType } from '../datasets'
|
||||
import { pauseDocIndexing, resumeDocIndexing } from '../datasets'
|
||||
import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets'
|
||||
import { DocumentActionType } from '@/models/datasets'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import { normalizeStatusForQuery } from '@/app/components/datasets/documents/status-filter'
|
||||
|
||||
const NAME_SPACE = 'knowledge/document'
|
||||
|
||||
export const useDocumentListKey = [NAME_SPACE, 'documentList']
|
||||
export const useDocumentList = (payload: {
|
||||
datasetId: string
|
||||
query: {
|
||||
keyword: string
|
||||
page: number
|
||||
limit: number
|
||||
sort?: SortType
|
||||
status?: string
|
||||
},
|
||||
refetchInterval?: number | false
|
||||
}) => {
|
||||
const { query, datasetId, refetchInterval } = payload
|
||||
const { keyword, page, limit, sort, status } = query
|
||||
const normalizedStatus = normalizeStatusForQuery(status)
|
||||
const params: Record<string, number | string> = {
|
||||
keyword,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
if (sort)
|
||||
params.sort = sort
|
||||
if (normalizedStatus && normalizedStatus !== 'all')
|
||||
params.status = normalizedStatus
|
||||
return useQuery<DocumentListResponse>({
|
||||
queryKey: [...useDocumentListKey, datasetId, keyword, page, limit, sort, normalizedStatus],
|
||||
queryFn: () => get<DocumentListResponse>(`/datasets/${datasetId}/documents`, {
|
||||
params,
|
||||
}),
|
||||
refetchInterval,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDocumentList = (datasetId?: string) => {
|
||||
return useInvalid(datasetId ? [...useDocumentListKey, datasetId] : useDocumentListKey)
|
||||
}
|
||||
|
||||
const useAutoDisabledDocumentKey = [NAME_SPACE, 'autoDisabledDocument']
|
||||
export const useAutoDisabledDocuments = (datasetId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [...useAutoDisabledDocumentKey, datasetId],
|
||||
queryFn: () => get<{ document_ids: string[] }>(`/datasets/${datasetId}/auto-disable-logs`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDisabledDocument = () => {
|
||||
return useInvalid(useAutoDisabledDocumentKey)
|
||||
}
|
||||
|
||||
const toBatchDocumentsIdParams = (documentIds: string[] | string) => {
|
||||
const ids = Array.isArray(documentIds) ? documentIds : [documentIds]
|
||||
return ids.map(id => `document_id=${id}`).join('&')
|
||||
}
|
||||
|
||||
export const useDocumentBatchAction = (action: DocumentActionType) => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentIds, documentId }: UpdateDocumentBatchParams) => {
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/status/${action}/batch?${toBatchDocumentsIdParams(documentId || documentIds!)}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentEnable = () => {
|
||||
return useDocumentBatchAction(DocumentActionType.enable)
|
||||
}
|
||||
|
||||
export const useDocumentDisable = () => {
|
||||
return useDocumentBatchAction(DocumentActionType.disable)
|
||||
}
|
||||
|
||||
export const useDocumentArchive = () => {
|
||||
return useDocumentBatchAction(DocumentActionType.archive)
|
||||
}
|
||||
|
||||
export const useDocumentUnArchive = () => {
|
||||
return useDocumentBatchAction(DocumentActionType.unArchive)
|
||||
}
|
||||
|
||||
export const useDocumentDelete = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentIds, documentId }: UpdateDocumentBatchParams) => {
|
||||
return del<CommonResponse>(`/datasets/${datasetId}/documents?${toBatchDocumentsIdParams(documentId || documentIds!)}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useSyncDocument = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
|
||||
return get<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/notion/sync`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useSyncWebsite = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
|
||||
return get<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/website-sync`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const useDocumentDetailKey = [NAME_SPACE, 'documentDetail', 'withoutMetaData']
|
||||
export const useDocumentDetail = (payload: {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
params: { metadata: MetadataType }
|
||||
}) => {
|
||||
const { datasetId, documentId, params } = payload
|
||||
return useQuery<DocumentDetailResponse>({
|
||||
queryKey: [...useDocumentDetailKey, 'withoutMetaData', datasetId, documentId],
|
||||
queryFn: () => get<DocumentDetailResponse>(`/datasets/${datasetId}/documents/${documentId}`, { params }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentMetadata = (payload: {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
params: { metadata: MetadataType }
|
||||
}) => {
|
||||
const { datasetId, documentId, params } = payload
|
||||
return useQuery<DocumentDetailResponse>({
|
||||
queryKey: [...useDocumentDetailKey, 'onlyMetaData', datasetId, documentId],
|
||||
queryFn: () => get<DocumentDetailResponse>(`/datasets/${datasetId}/documents/${documentId}`, { params }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDocumentDetail = () => {
|
||||
return useInvalid(useDocumentDetailKey)
|
||||
}
|
||||
|
||||
export const useDocumentPause = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
|
||||
if (!datasetId || !documentId)
|
||||
throw new Error('datasetId and documentId are required')
|
||||
return pauseDocIndexing({ datasetId, documentId }) as Promise<CommonResponse>
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentResume = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => {
|
||||
if (!datasetId || !documentId)
|
||||
throw new Error('datasetId and documentId are required')
|
||||
return resumeDocIndexing({ datasetId, documentId }) as Promise<CommonResponse>
|
||||
},
|
||||
})
|
||||
}
|
||||
1
dify/web/service/knowledge/use-hit-testing.ts
Normal file
1
dify/web/service/knowledge/use-hit-testing.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {}
|
||||
42
dify/web/service/knowledge/use-import.ts
Normal file
42
dify/web/service/knowledge/use-import.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { get } from '../base'
|
||||
import type { DataSourceNotionWorkspace } from '@/models/common'
|
||||
|
||||
type PreImportNotionPagesParams = {
|
||||
datasetId: string
|
||||
credentialId: string
|
||||
}
|
||||
|
||||
const PRE_IMPORT_NOTION_PAGES_QUERY_KEY = 'notion-pre-import-pages'
|
||||
|
||||
export const usePreImportNotionPages = ({
|
||||
datasetId,
|
||||
credentialId,
|
||||
}: PreImportNotionPagesParams) => {
|
||||
return useQuery({
|
||||
queryKey: [PRE_IMPORT_NOTION_PAGES_QUERY_KEY, datasetId, credentialId],
|
||||
queryFn: async () => {
|
||||
return get<{ notion_info: DataSourceNotionWorkspace[] }>('/notion/pre-import/pages', {
|
||||
params: {
|
||||
dataset_id: datasetId,
|
||||
credential_id: credentialId,
|
||||
},
|
||||
})
|
||||
},
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidPreImportNotionPages = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return ({
|
||||
datasetId,
|
||||
credentialId,
|
||||
}: PreImportNotionPagesParams) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [PRE_IMPORT_NOTION_PAGES_QUERY_KEY, datasetId, credentialId],
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
84
dify/web/service/knowledge/use-metadata.spec.tsx
Normal file
84
dify/web/service/knowledge/use-metadata.spec.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { DataType } from '@/app/components/datasets/metadata/types'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useBatchUpdateDocMetadata } from '@/service/knowledge/use-metadata'
|
||||
import { useDocumentListKey } from './use-document'
|
||||
|
||||
// Mock the post function to avoid real network requests
|
||||
jest.mock('@/service/base', () => ({
|
||||
post: jest.fn().mockResolvedValue({ success: true }),
|
||||
}))
|
||||
|
||||
const NAME_SPACE = 'dataset-metadata'
|
||||
|
||||
describe('useBatchUpdateDocMetadata', () => {
|
||||
let queryClient: QueryClient
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a fresh QueryClient before each test
|
||||
queryClient = new QueryClient()
|
||||
})
|
||||
|
||||
// Wrapper for React Query context
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
|
||||
it('should correctly invalidate dataset and document caches', async () => {
|
||||
const { result } = renderHook(() => useBatchUpdateDocMetadata(), { wrapper })
|
||||
|
||||
// Spy on queryClient.invalidateQueries
|
||||
const invalidateSpy = jest.spyOn(queryClient, 'invalidateQueries')
|
||||
|
||||
// Correct payload type: each document has its own metadata_list array
|
||||
|
||||
const payload = {
|
||||
dataset_id: 'dataset-1',
|
||||
metadata_list: [
|
||||
{
|
||||
document_id: 'doc-1',
|
||||
metadata_list: [
|
||||
{ key: 'title-1', id: '01', name: 'name-1', type: DataType.string, value: 'new title 01' },
|
||||
],
|
||||
},
|
||||
{
|
||||
document_id: 'doc-2',
|
||||
metadata_list: [
|
||||
{ key: 'title-2', id: '02', name: 'name-1', type: DataType.string, value: 'new title 02' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Execute the mutation
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(payload)
|
||||
})
|
||||
|
||||
// Expect invalidateQueries to have been called exactly 5 times
|
||||
expect(invalidateSpy).toHaveBeenCalledTimes(5)
|
||||
|
||||
// Dataset cache invalidation
|
||||
expect(invalidateSpy).toHaveBeenNthCalledWith(1, {
|
||||
queryKey: [NAME_SPACE, 'dataset', 'dataset-1'],
|
||||
})
|
||||
|
||||
// Document list cache invalidation
|
||||
expect(invalidateSpy).toHaveBeenNthCalledWith(2, {
|
||||
queryKey: [NAME_SPACE, 'document', 'dataset-1'],
|
||||
})
|
||||
|
||||
// useDocumentListKey cache invalidation
|
||||
expect(invalidateSpy).toHaveBeenNthCalledWith(3, {
|
||||
queryKey: [...useDocumentListKey, 'dataset-1'],
|
||||
})
|
||||
|
||||
// Single document cache invalidation
|
||||
expect(invalidateSpy.mock.calls.slice(3)).toEqual(
|
||||
expect.arrayContaining([
|
||||
[{ queryKey: [NAME_SPACE, 'document', 'dataset-1', 'doc-1'] }],
|
||||
[{ queryKey: [NAME_SPACE, 'document', 'dataset-1', 'doc-2'] }],
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
146
dify/web/service/knowledge/use-metadata.ts
Normal file
146
dify/web/service/knowledge/use-metadata.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { BuiltInMetadataItem, MetadataBatchEditToServer, MetadataItemWithValueLength } from '@/app/components/datasets/metadata/types'
|
||||
import { del, get, patch, post } from '../base'
|
||||
import { useDocumentListKey, useInvalidDocumentList } from './use-document'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useInvalid } from '../use-base'
|
||||
import type { DocumentDetailResponse } from '@/models/datasets'
|
||||
|
||||
const NAME_SPACE = 'dataset-metadata'
|
||||
|
||||
export const useDatasetMetaData = (datasetId: string) => {
|
||||
return useQuery<{ doc_metadata: MetadataItemWithValueLength[], built_in_field_enabled: boolean }>({
|
||||
queryKey: [NAME_SPACE, 'dataset', datasetId],
|
||||
queryFn: () => {
|
||||
return get<{ doc_metadata: MetadataItemWithValueLength[], built_in_field_enabled: boolean }>(`/datasets/${datasetId}/metadata`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDatasetMetaData = (datasetId: string) => {
|
||||
return useInvalid([NAME_SPACE, 'dataset', datasetId])
|
||||
}
|
||||
|
||||
export const useCreateMetaData = (datasetId: string) => {
|
||||
const invalidDatasetMetaData = useInvalidDatasetMetaData(datasetId)
|
||||
return useMutation({
|
||||
mutationFn: async (payload: BuiltInMetadataItem) => {
|
||||
await post(`/datasets/${datasetId}/metadata`, {
|
||||
body: payload,
|
||||
})
|
||||
await invalidDatasetMetaData()
|
||||
return Promise.resolve(true)
|
||||
},
|
||||
})
|
||||
}
|
||||
export const useInvalidAllDocumentMetaData = (datasetId: string) => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'document', datasetId],
|
||||
exact: false, // invalidate all document metadata: [NAME_SPACE, 'document', datasetId, documentId]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const useInvalidAllMetaData = (datasetId: string) => {
|
||||
const invalidDatasetMetaData = useInvalidDatasetMetaData(datasetId)
|
||||
const invalidDocumentList = useInvalidDocumentList(datasetId)
|
||||
const invalidateAllDocumentMetaData = useInvalidAllDocumentMetaData(datasetId)
|
||||
|
||||
return async () => {
|
||||
// meta data in dataset
|
||||
await invalidDatasetMetaData()
|
||||
// meta data in document list
|
||||
invalidDocumentList()
|
||||
// meta data in single document
|
||||
await invalidateAllDocumentMetaData() // meta data in document
|
||||
}
|
||||
}
|
||||
|
||||
export const useRenameMeta = (datasetId: string) => {
|
||||
const invalidateAllMetaData = useInvalidAllMetaData(datasetId)
|
||||
return useMutation({
|
||||
mutationFn: async (payload: MetadataItemWithValueLength) => {
|
||||
await patch(`/datasets/${datasetId}/metadata/${payload.id}`, {
|
||||
body: {
|
||||
name: payload.name,
|
||||
},
|
||||
})
|
||||
await invalidateAllMetaData()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteMetaData = (datasetId: string) => {
|
||||
const invalidateAllMetaData = useInvalidAllMetaData(datasetId)
|
||||
return useMutation({
|
||||
mutationFn: async (metaDataId: string) => {
|
||||
// datasetMetaData = datasetMetaData.filter(item => item.id !== metaDataId)
|
||||
await del(`/datasets/${datasetId}/metadata/${metaDataId}`)
|
||||
await invalidateAllMetaData()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useBuiltInMetaDataFields = () => {
|
||||
return useQuery<{ fields: BuiltInMetadataItem[] }>({
|
||||
queryKey: [NAME_SPACE, 'built-in'],
|
||||
queryFn: () => {
|
||||
return get('/datasets/metadata/built-in')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDocumentMetaData = ({ datasetId, documentId }: { datasetId: string, documentId: string }) => {
|
||||
return useQuery<DocumentDetailResponse>({
|
||||
queryKey: [NAME_SPACE, 'document', datasetId, documentId],
|
||||
queryFn: () => {
|
||||
return get<DocumentDetailResponse>(`/datasets/${datasetId}/documents/${documentId}`, { params: { metadata: 'only' } })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useBatchUpdateDocMetadata = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async (payload: {
|
||||
dataset_id: string
|
||||
metadata_list: MetadataBatchEditToServer
|
||||
}) => {
|
||||
const documentIds = payload.metadata_list.map(item => item.document_id)
|
||||
await post(`/datasets/${payload.dataset_id}/documents/metadata`, {
|
||||
body: {
|
||||
operation_data: payload.metadata_list,
|
||||
},
|
||||
})
|
||||
// meta data in dataset
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'dataset', payload.dataset_id],
|
||||
})
|
||||
// meta data in document list
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'document', payload.dataset_id],
|
||||
})
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [...useDocumentListKey, payload.dataset_id],
|
||||
})
|
||||
|
||||
// meta data in single document
|
||||
await Promise.all(documentIds.map(documentId => queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'document', payload.dataset_id, documentId],
|
||||
},
|
||||
)))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateBuiltInStatus = (datasetId: string) => {
|
||||
const invalidDatasetMetaData = useInvalidDatasetMetaData(datasetId)
|
||||
return useMutation({
|
||||
mutationFn: async (enabled: boolean) => {
|
||||
await post(`/datasets/${datasetId}/metadata/built-in/${enabled ? 'enable' : 'disable'}`)
|
||||
invalidDatasetMetaData()
|
||||
},
|
||||
})
|
||||
}
|
||||
172
dify/web/service/knowledge/use-segment.ts
Normal file
172
dify/web/service/knowledge/use-segment.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { del, get, patch, post } from '../base'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type {
|
||||
BatchImportResponse,
|
||||
ChildChunkDetail,
|
||||
ChildSegmentsResponse,
|
||||
ChunkingMode,
|
||||
SegmentDetailModel,
|
||||
SegmentUpdater,
|
||||
SegmentsResponse,
|
||||
} from '@/models/datasets'
|
||||
|
||||
const NAME_SPACE = 'segment'
|
||||
|
||||
export const useSegmentListKey = [NAME_SPACE, 'chunkList']
|
||||
export const useChunkListEnabledKey = [NAME_SPACE, 'chunkList', { enabled: true }]
|
||||
export const useChunkListDisabledKey = [NAME_SPACE, 'chunkList', { enabled: false }]
|
||||
export const useChunkListAllKey = [NAME_SPACE, 'chunkList', { enabled: 'all' }]
|
||||
|
||||
export const useSegmentList = (
|
||||
payload: {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
params: {
|
||||
page: number
|
||||
limit: number
|
||||
keyword: string
|
||||
enabled: boolean | 'all' | ''
|
||||
}
|
||||
},
|
||||
disable?: boolean,
|
||||
) => {
|
||||
const { datasetId, documentId, params } = payload
|
||||
const { page, limit, keyword, enabled } = params
|
||||
return useQuery<SegmentsResponse>({
|
||||
queryKey: [...useSegmentListKey, { datasetId, documentId, page, limit, keyword, enabled }],
|
||||
queryFn: () => {
|
||||
return get<SegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments`, { params })
|
||||
},
|
||||
enabled: !disable,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: SegmentUpdater }) => {
|
||||
const { datasetId, documentId, segmentId, body } = payload
|
||||
return patch<{ data: SegmentDetailModel; doc_form: ChunkingMode }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'add'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; body: SegmentUpdater }) => {
|
||||
const { datasetId, documentId, body } = payload
|
||||
return post<{ data: SegmentDetailModel; doc_form: ChunkingMode }>(`/datasets/${datasetId}/documents/${documentId}/segment`, { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useEnableSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'enable'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, documentId, segmentIds } = payload
|
||||
const query = segmentIds.map(id => `segment_id=${id}`).join('&')
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/enable?${query}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDisableSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'disable'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, documentId, segmentIds } = payload
|
||||
const query = segmentIds.map(id => `segment_id=${id}`).join('&')
|
||||
return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/disable?${query}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => {
|
||||
const { datasetId, documentId, segmentIds } = payload
|
||||
const query = segmentIds.map(id => `segment_id=${id}`).join('&')
|
||||
return del<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments?${query}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useChildSegmentListKey = [NAME_SPACE, 'childChunkList']
|
||||
|
||||
export const useChildSegmentList = (
|
||||
payload: {
|
||||
datasetId: string
|
||||
documentId: string
|
||||
segmentId: string
|
||||
params: {
|
||||
page: number
|
||||
limit: number
|
||||
keyword: string
|
||||
}
|
||||
},
|
||||
disable?: boolean,
|
||||
) => {
|
||||
const { datasetId, documentId, segmentId, params } = payload
|
||||
const { page, limit, keyword } = params
|
||||
return useQuery({
|
||||
queryKey: [...useChildSegmentListKey, { datasetId, documentId, segmentId, page, limit, keyword }],
|
||||
queryFn: () => {
|
||||
return get<ChildSegmentsResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, { params })
|
||||
},
|
||||
enabled: !disable,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteChildSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'childChunk', 'delete'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; childChunkId: string }) => {
|
||||
const { datasetId, documentId, segmentId, childChunkId } = payload
|
||||
return del<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddChildSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'childChunk', 'add'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: { content: string } }) => {
|
||||
const { datasetId, documentId, segmentId, body } = payload
|
||||
return post<{ data: ChildChunkDetail }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateChildSegment = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'childChunk', 'update'],
|
||||
mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; childChunkId: string; body: { content: string } }) => {
|
||||
const { datasetId, documentId, segmentId, childChunkId, body } = payload
|
||||
return patch<{ data: ChildChunkDetail }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`, { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useSegmentBatchImport = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'batchImport'],
|
||||
mutationFn: (payload: { url: string; body: { upload_file_id: string } }) => {
|
||||
const { url, body } = payload
|
||||
return post<BatchImportResponse>(url, { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useCheckSegmentBatchImportProgress = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'batchImport', 'checkProgress'],
|
||||
mutationFn: (payload: { jobID: string }) => {
|
||||
const { jobID } = payload
|
||||
return get<BatchImportResponse>(`/datasets/batch_import_status/${jobID}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
80
dify/web/service/log.ts
Normal file
80
dify/web/service/log.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { get, post } from './base'
|
||||
import type {
|
||||
AgentLogDetailRequest,
|
||||
AgentLogDetailResponse,
|
||||
AnnotationsCountResponse,
|
||||
ChatConversationFullDetailResponse,
|
||||
ChatConversationsRequest,
|
||||
ChatConversationsResponse,
|
||||
ChatMessagesRequest,
|
||||
ChatMessagesResponse,
|
||||
CompletionConversationFullDetailResponse,
|
||||
CompletionConversationsRequest,
|
||||
CompletionConversationsResponse,
|
||||
ConversationListResponse,
|
||||
LogMessageAnnotationsRequest,
|
||||
LogMessageAnnotationsResponse,
|
||||
LogMessageFeedbacksRequest,
|
||||
LogMessageFeedbacksResponse,
|
||||
WorkflowLogsResponse,
|
||||
WorkflowRunDetailResponse,
|
||||
} from '@/models/log'
|
||||
import type { NodeTracingListResponse } from '@/types/workflow'
|
||||
|
||||
export const fetchConversationList: Fetcher<ConversationListResponse, { name: string; appId: string; params?: Record<string, any> }> = ({ appId, params }) => {
|
||||
return get<ConversationListResponse>(`/console/api/apps/${appId}/messages`, params)
|
||||
}
|
||||
|
||||
// (Text Generation Application) Session List
|
||||
export const fetchCompletionConversations: Fetcher<CompletionConversationsResponse, { url: string; params?: CompletionConversationsRequest }> = ({ url, params }) => {
|
||||
return get<CompletionConversationsResponse>(url, { params })
|
||||
}
|
||||
|
||||
// (Text Generation Application) Session Detail
|
||||
export const fetchCompletionConversationDetail: Fetcher<CompletionConversationFullDetailResponse, { url: string }> = ({ url }) => {
|
||||
return get<CompletionConversationFullDetailResponse>(url, {})
|
||||
}
|
||||
|
||||
// (Chat Application) Session List
|
||||
export const fetchChatConversations: Fetcher<ChatConversationsResponse, { url: string; params?: ChatConversationsRequest }> = ({ url, params }) => {
|
||||
return get<ChatConversationsResponse>(url, { params })
|
||||
}
|
||||
|
||||
// (Chat Application) Session Detail
|
||||
export const fetchChatConversationDetail: Fetcher<ChatConversationFullDetailResponse, { url: string }> = ({ url }) => {
|
||||
return get<ChatConversationFullDetailResponse>(url, {})
|
||||
}
|
||||
|
||||
// (Chat Application) Message list in one session
|
||||
export const fetchChatMessages: Fetcher<ChatMessagesResponse, { url: string; params: ChatMessagesRequest }> = ({ url, params }) => {
|
||||
return get<ChatMessagesResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const updateLogMessageFeedbacks: Fetcher<LogMessageFeedbacksResponse, { url: string; body: LogMessageFeedbacksRequest }> = ({ url, body }) => {
|
||||
return post<LogMessageFeedbacksResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const updateLogMessageAnnotations: Fetcher<LogMessageAnnotationsResponse, { url: string; body: LogMessageAnnotationsRequest }> = ({ url, body }) => {
|
||||
return post<LogMessageAnnotationsResponse>(url, { body })
|
||||
}
|
||||
|
||||
export const fetchAnnotationsCount: Fetcher<AnnotationsCountResponse, { url: string }> = ({ url }) => {
|
||||
return get<AnnotationsCountResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchWorkflowLogs: Fetcher<WorkflowLogsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
||||
return get<WorkflowLogsResponse>(url, { params })
|
||||
}
|
||||
|
||||
export const fetchRunDetail = (url: string) => {
|
||||
return get<WorkflowRunDetailResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchTracingList: Fetcher<NodeTracingListResponse, { url: string }> = ({ url }) => {
|
||||
return get<NodeTracingListResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchAgentLogDetail = ({ appID, params }: { appID: string; params: AgentLogDetailRequest }) => {
|
||||
return get<AgentLogDetailResponse>(`/apps/${appID}/agent/logs`, { params })
|
||||
}
|
||||
108
dify/web/service/plugins.ts
Normal file
108
dify/web/service/plugins.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { get, getMarketplace, post, upload } from './base'
|
||||
import type {
|
||||
Dependency,
|
||||
InstallPackageResponse,
|
||||
Permissions,
|
||||
PluginDeclaration,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginManifestInMarket,
|
||||
PluginTasksResponse,
|
||||
TaskStatusResponse,
|
||||
UninstallPluginResponse,
|
||||
updatePackageResponse,
|
||||
uploadGitHubResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import type {
|
||||
MarketplaceCollectionPluginsResponse,
|
||||
MarketplaceCollectionsResponse,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
|
||||
export const uploadFile = async (file: File, isBundle: boolean) => {
|
||||
const formData = new FormData()
|
||||
formData.append(isBundle ? 'bundle' : 'pkg', file)
|
||||
return upload({
|
||||
xhr: new XMLHttpRequest(),
|
||||
data: formData,
|
||||
}, false, `/workspaces/current/plugin/upload/${isBundle ? 'bundle' : 'pkg'}`)
|
||||
}
|
||||
|
||||
export const updateFromMarketPlace = async (body: Record<string, string>) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateFromGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string,
|
||||
originalPlugin: string, newPlugin: string) => {
|
||||
return post<updatePackageResponse>('/workspaces/current/plugin/upgrade/github', {
|
||||
body: {
|
||||
repo: repoUrl,
|
||||
version: selectedVersion,
|
||||
package: selectedPackage,
|
||||
original_plugin_unique_identifier: originalPlugin,
|
||||
new_plugin_unique_identifier: newPlugin,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const uploadGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string) => {
|
||||
return post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
|
||||
body: {
|
||||
repo: repoUrl,
|
||||
version: selectedVersion,
|
||||
package: selectedPackage,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchIcon = (tenantId: string, fileName: string) => {
|
||||
return get(`workspaces/current/plugin/icon?tenant_id=${tenantId}&filename=${fileName}`)
|
||||
}
|
||||
|
||||
export const fetchManifest = async (uniqueIdentifier: string) => {
|
||||
return get<PluginDeclaration>(`/workspaces/current/plugin/fetch-manifest?plugin_unique_identifier=${uniqueIdentifier}`)
|
||||
}
|
||||
|
||||
export const fetchManifestFromMarketPlace = async (uniqueIdentifier: string) => {
|
||||
return getMarketplace<{ data: { plugin: PluginManifestInMarket, version: { version: string } } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`)
|
||||
}
|
||||
|
||||
export const fetchBundleInfoFromMarketPlace = async ({
|
||||
org,
|
||||
name,
|
||||
version,
|
||||
}: Record<string, string>) => {
|
||||
return getMarketplace<{ data: { version: { dependencies: Dependency[] } } }>(`/bundles/${org}/${name}/${version}`)
|
||||
}
|
||||
|
||||
export const fetchPluginInfoFromMarketPlace = async ({
|
||||
org,
|
||||
name,
|
||||
}: Record<string, string>) => {
|
||||
return getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${org}/${name}`)
|
||||
}
|
||||
|
||||
export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => {
|
||||
return get<MarketplaceCollectionsResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchMarketplaceCollectionPlugins: Fetcher<MarketplaceCollectionPluginsResponse, { url: string }> = ({ url }) => {
|
||||
return get<MarketplaceCollectionPluginsResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchPluginTasks = async () => {
|
||||
return get<PluginTasksResponse>('/workspaces/current/plugin/tasks?page=1&page_size=255')
|
||||
}
|
||||
|
||||
export const checkTaskStatus = async (taskId: string) => {
|
||||
return get<TaskStatusResponse>(`/workspaces/current/plugin/tasks/${taskId}`)
|
||||
}
|
||||
|
||||
export const updatePermission = async (permissions: Permissions) => {
|
||||
return post('/workspaces/current/plugin/permission/change', { body: permissions })
|
||||
}
|
||||
|
||||
export const uninstallPlugin = async (pluginId: string) => {
|
||||
return post<UninstallPluginResponse>('/workspaces/current/plugin/uninstall', { body: { plugin_installation_id: pluginId } })
|
||||
}
|
||||
88
dify/web/service/refresh-token.ts
Normal file
88
dify/web/service/refresh-token.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { fetchWithRetry } from '@/utils'
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'is_other_tab_refreshing'
|
||||
|
||||
let isRefreshing = false
|
||||
function waitUntilTokenRefreshed() {
|
||||
return new Promise<void>((resolve) => {
|
||||
function _check() {
|
||||
const isRefreshingSign = globalThis.localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||
if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) {
|
||||
setTimeout(() => {
|
||||
_check()
|
||||
}, 1000)
|
||||
}
|
||||
else {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
_check()
|
||||
})
|
||||
}
|
||||
|
||||
const isRefreshingSignAvailable = function (delta: number) {
|
||||
const nowTime = new Date().getTime()
|
||||
const lastTime = globalThis.localStorage.getItem('last_refresh_time') || '0'
|
||||
return nowTime - Number.parseInt(lastTime) <= delta
|
||||
}
|
||||
|
||||
// only one request can send
|
||||
async function getNewAccessToken(timeout: number): Promise<void> {
|
||||
try {
|
||||
const isRefreshingSign = globalThis.localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||
if ((isRefreshingSign && isRefreshingSign === '1' && isRefreshingSignAvailable(timeout)) || isRefreshing) {
|
||||
await waitUntilTokenRefreshed()
|
||||
}
|
||||
else {
|
||||
isRefreshing = true
|
||||
globalThis.localStorage.setItem(LOCAL_STORAGE_KEY, '1')
|
||||
globalThis.localStorage.setItem('last_refresh_time', new Date().getTime().toString())
|
||||
globalThis.addEventListener('beforeunload', releaseRefreshLock)
|
||||
|
||||
// Do not use baseFetch to refresh tokens.
|
||||
// If a 401 response occurs and baseFetch itself attempts to refresh the token,
|
||||
// it can lead to an infinite loop if the refresh attempt also returns 401.
|
||||
// To avoid this, handle token refresh separately in a dedicated function
|
||||
// that does not call baseFetch and uses a single retry mechanism.
|
||||
const [error, ret] = await fetchWithRetry(globalThis.fetch(`${API_PREFIX}/refresh-token`, {
|
||||
method: 'POST',
|
||||
credentials: 'include', // Important: include cookies in the request
|
||||
headers: {
|
||||
'Content-Type': 'application/json;utf-8',
|
||||
},
|
||||
// No body needed - refresh token is in cookie
|
||||
}))
|
||||
if (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
else {
|
||||
if (ret.status === 401)
|
||||
return Promise.reject(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
finally {
|
||||
releaseRefreshLock()
|
||||
}
|
||||
}
|
||||
|
||||
function releaseRefreshLock() {
|
||||
if (isRefreshing) {
|
||||
isRefreshing = false
|
||||
globalThis.localStorage.removeItem(LOCAL_STORAGE_KEY)
|
||||
globalThis.localStorage.removeItem('last_refresh_time')
|
||||
globalThis.removeEventListener('beforeunload', releaseRefreshLock)
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshAccessTokenOrRelogin(timeout: number) {
|
||||
return Promise.race([new Promise<void>((resolve, reject) => setTimeout(() => {
|
||||
releaseRefreshLock()
|
||||
reject(new Error('request timeout'))
|
||||
}, timeout)), getNewAccessToken(timeout)])
|
||||
}
|
||||
320
dify/web/service/share.ts
Normal file
320
dify/web/service/share.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import type {
|
||||
IOnCompleted,
|
||||
IOnData,
|
||||
IOnError,
|
||||
IOnFile,
|
||||
IOnIterationFinished,
|
||||
IOnIterationNext,
|
||||
IOnIterationStarted,
|
||||
IOnLoopFinished,
|
||||
IOnLoopNext,
|
||||
IOnLoopStarted,
|
||||
IOnMessageEnd,
|
||||
IOnMessageReplace,
|
||||
IOnNodeFinished,
|
||||
IOnNodeStarted,
|
||||
IOnTTSChunk,
|
||||
IOnTTSEnd,
|
||||
IOnTextChunk,
|
||||
IOnTextReplace,
|
||||
IOnThought,
|
||||
IOnWorkflowFinished,
|
||||
IOnWorkflowStarted,
|
||||
} from './base'
|
||||
import {
|
||||
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
|
||||
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
|
||||
} from './base'
|
||||
import type { FeedbackType } from '@/app/components/base/chat/chat/type'
|
||||
import type {
|
||||
AppConversationData,
|
||||
AppData,
|
||||
AppMeta,
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import type { ChatConfig } from '@/app/components/base/chat/types'
|
||||
import type { AccessMode } from '@/models/access-control'
|
||||
import { WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
|
||||
import { getWebAppAccessToken } from './webapp-auth'
|
||||
|
||||
function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) {
|
||||
switch (action) {
|
||||
case 'get':
|
||||
return isInstalledApp ? consoleGet : get
|
||||
case 'post':
|
||||
return isInstalledApp ? consolePost : post
|
||||
case 'patch':
|
||||
return isInstalledApp ? consolePatch : patch
|
||||
case 'del':
|
||||
return isInstalledApp ? consoleDel : del
|
||||
}
|
||||
}
|
||||
|
||||
export function getUrl(url: string, isInstalledApp: boolean, installedAppId: string) {
|
||||
return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url
|
||||
}
|
||||
|
||||
export const sendChatMessage = async (body: Record<string, any>, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace, onTTSChunk, onTTSEnd }: {
|
||||
onData: IOnData
|
||||
onCompleted: IOnCompleted
|
||||
onFile: IOnFile
|
||||
onThought: IOnThought
|
||||
onError: IOnError
|
||||
onMessageEnd?: IOnMessageEnd
|
||||
onMessageReplace?: IOnMessageReplace
|
||||
getAbortController?: (abortController: AbortController) => void
|
||||
onTTSChunk?: IOnTTSChunk
|
||||
onTTSEnd?: IOnTTSEnd
|
||||
}, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), {
|
||||
body: {
|
||||
...body,
|
||||
response_mode: 'streaming',
|
||||
},
|
||||
}, { onData, onCompleted, onThought, onFile, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace, onTTSChunk, onTTSEnd })
|
||||
}
|
||||
|
||||
export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return getAction('post', isInstalledApp)(getUrl(`chat-messages/${taskId}/stop`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const sendCompletionMessage = async (body: Record<string, any>, { onData, onCompleted, onError, onMessageReplace, getAbortController }: {
|
||||
onData: IOnData
|
||||
onCompleted: IOnCompleted
|
||||
onError: IOnError
|
||||
onMessageReplace: IOnMessageReplace
|
||||
getAbortController?: (abortController: AbortController) => void
|
||||
}, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return ssePost(getUrl('completion-messages', isInstalledApp, installedAppId), {
|
||||
body: {
|
||||
...body,
|
||||
response_mode: 'streaming',
|
||||
},
|
||||
}, { onData, onCompleted, isPublicAPI: !isInstalledApp, onError, onMessageReplace, getAbortController })
|
||||
}
|
||||
|
||||
export const sendWorkflowMessage = async (
|
||||
body: Record<string, any>,
|
||||
{
|
||||
onWorkflowStarted,
|
||||
onNodeStarted,
|
||||
onNodeFinished,
|
||||
onWorkflowFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onTextChunk,
|
||||
onTextReplace,
|
||||
}: {
|
||||
onWorkflowStarted: IOnWorkflowStarted
|
||||
onNodeStarted: IOnNodeStarted
|
||||
onNodeFinished: IOnNodeFinished
|
||||
onWorkflowFinished: IOnWorkflowFinished
|
||||
onIterationStart: IOnIterationStarted
|
||||
onIterationNext: IOnIterationNext
|
||||
onIterationFinish: IOnIterationFinished
|
||||
onLoopStart: IOnLoopStarted
|
||||
onLoopNext: IOnLoopNext
|
||||
onLoopFinish: IOnLoopFinished
|
||||
onTextChunk: IOnTextChunk
|
||||
onTextReplace: IOnTextReplace
|
||||
},
|
||||
isInstalledApp: boolean,
|
||||
installedAppId = '',
|
||||
) => {
|
||||
return ssePost(getUrl('workflows/run', isInstalledApp, installedAppId), {
|
||||
body: {
|
||||
...body,
|
||||
response_mode: 'streaming',
|
||||
},
|
||||
}, {
|
||||
onNodeStarted,
|
||||
onWorkflowStarted,
|
||||
onWorkflowFinished,
|
||||
isPublicAPI: !isInstalledApp,
|
||||
onNodeFinished,
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onLoopStart,
|
||||
onLoopNext,
|
||||
onLoopFinish,
|
||||
onTextChunk,
|
||||
onTextReplace,
|
||||
})
|
||||
}
|
||||
|
||||
export const stopWorkflowMessage = async (_appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
if (!taskId)
|
||||
return
|
||||
return getAction('post', isInstalledApp)(getUrl(`workflows/tasks/${taskId}/stop`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const fetchAppInfo = async () => {
|
||||
return get('/site') as Promise<AppData>
|
||||
}
|
||||
|
||||
export const fetchConversations = async (isInstalledApp: boolean, installedAppId = '', last_id?: string, pinned?: boolean, limit?: number) => {
|
||||
return getAction('get', isInstalledApp)(getUrl('conversations', isInstalledApp, installedAppId), { params: { limit: limit || 20, ...(last_id ? { last_id } : {}), ...(pinned !== undefined ? { pinned } : {}) } }) as Promise<AppConversationData>
|
||||
}
|
||||
|
||||
export const pinConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
|
||||
return getAction('patch', isInstalledApp)(getUrl(`conversations/${id}/pin`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const unpinConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
|
||||
return getAction('patch', isInstalledApp)(getUrl(`conversations/${id}/unpin`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const delConversation = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
|
||||
return getAction('del', isInstalledApp)(getUrl(`conversations/${id}`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const renameConversation = async (isInstalledApp: boolean, installedAppId = '', id: string, name: string) => {
|
||||
return getAction('post', isInstalledApp)(getUrl(`conversations/${id}/name`, isInstalledApp, installedAppId), { body: { name } })
|
||||
}
|
||||
|
||||
export const generationConversationName = async (isInstalledApp: boolean, installedAppId = '', id: string) => {
|
||||
return getAction('post', isInstalledApp)(getUrl(`conversations/${id}/name`, isInstalledApp, installedAppId), { body: { auto_generate: true } }) as Promise<ConversationItem>
|
||||
}
|
||||
|
||||
export const fetchChatList = async (conversationId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return getAction('get', isInstalledApp)(getUrl('messages', isInstalledApp, installedAppId), { params: { conversation_id: conversationId, limit: 20, last_id: '' } }) as any
|
||||
}
|
||||
|
||||
// Abandoned API interface
|
||||
// export const fetchAppVariables = async () => {
|
||||
// return get(`variables`)
|
||||
// }
|
||||
|
||||
// init value. wait for server update
|
||||
export const fetchAppParams = async (isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('get', isInstalledApp))(getUrl('parameters', isInstalledApp, installedAppId)) as Promise<ChatConfig>
|
||||
}
|
||||
|
||||
export const fetchWebSAMLSSOUrl = async (appCode: string, redirectUrl: string) => {
|
||||
return (getAction('get', false))(getUrl('/enterprise/sso/saml/login', false, ''), {
|
||||
params: {
|
||||
app_code: appCode,
|
||||
redirect_url: redirectUrl,
|
||||
},
|
||||
}) as Promise<{ url: string }>
|
||||
}
|
||||
|
||||
export const fetchWebOIDCSSOUrl = async (appCode: string, redirectUrl: string) => {
|
||||
return (getAction('get', false))(getUrl('/enterprise/sso/oidc/login', false, ''), {
|
||||
params: {
|
||||
app_code: appCode,
|
||||
redirect_url: redirectUrl,
|
||||
},
|
||||
|
||||
}) as Promise<{ url: string }>
|
||||
}
|
||||
|
||||
export const fetchWebOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => {
|
||||
return (getAction('get', false))(getUrl('/enterprise/sso/oauth2/login', false, ''), {
|
||||
params: {
|
||||
app_code: appCode,
|
||||
redirect_url: redirectUrl,
|
||||
},
|
||||
}) as Promise<{ url: string }>
|
||||
}
|
||||
|
||||
export const fetchMembersSAMLSSOUrl = async (appCode: string, redirectUrl: string) => {
|
||||
return (getAction('get', false))(getUrl('/enterprise/sso/members/saml/login', false, ''), {
|
||||
params: {
|
||||
app_code: appCode,
|
||||
redirect_url: redirectUrl,
|
||||
},
|
||||
}) as Promise<{ url: string }>
|
||||
}
|
||||
|
||||
export const fetchMembersOIDCSSOUrl = async (appCode: string, redirectUrl: string) => {
|
||||
return (getAction('get', false))(getUrl('/enterprise/sso/members/oidc/login', false, ''), {
|
||||
params: {
|
||||
app_code: appCode,
|
||||
redirect_url: redirectUrl,
|
||||
},
|
||||
|
||||
}) as Promise<{ url: string }>
|
||||
}
|
||||
|
||||
export const fetchMembersOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => {
|
||||
return (getAction('get', false))(getUrl('/enterprise/sso/members/oauth2/login', false, ''), {
|
||||
params: {
|
||||
app_code: appCode,
|
||||
redirect_url: redirectUrl,
|
||||
},
|
||||
}) as Promise<{ url: string }>
|
||||
}
|
||||
|
||||
export const fetchAppMeta = async (isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('get', isInstalledApp))(getUrl('meta', isInstalledApp, installedAppId)) as Promise<AppMeta>
|
||||
}
|
||||
|
||||
export const updateFeedback = async ({ url, body }: { url: string; body: FeedbackType }, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('post', isInstalledApp))(getUrl(url, isInstalledApp, installedAppId), { body })
|
||||
}
|
||||
|
||||
export const fetchMoreLikeThis = async (messageId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('get', isInstalledApp))(getUrl(`/messages/${messageId}/more-like-this`, isInstalledApp, installedAppId), {
|
||||
params: {
|
||||
response_mode: 'blocking',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const saveMessage = (messageId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('post', isInstalledApp))(getUrl('/saved-messages', isInstalledApp, installedAppId), { body: { message_id: messageId } })
|
||||
}
|
||||
|
||||
export const fetchSavedMessage = async (isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('get', isInstalledApp))(getUrl('/saved-messages', isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const removeMessage = (messageId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('del', isInstalledApp))(getUrl(`/saved-messages/${messageId}`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const fetchSuggestedQuestions = (messageId: string, isInstalledApp: boolean, installedAppId = '') => {
|
||||
return (getAction('get', isInstalledApp))(getUrl(`/messages/${messageId}/suggested-questions`, isInstalledApp, installedAppId))
|
||||
}
|
||||
|
||||
export const audioToText = (url: string, isPublicAPI: boolean, body: FormData) => {
|
||||
return (getAction('post', !isPublicAPI))(url, { body }, { bodyStringify: false, deleteContentType: true }) as Promise<{ text: string }>
|
||||
}
|
||||
|
||||
export const textToAudio = (url: string, isPublicAPI: boolean, body: FormData) => {
|
||||
return (getAction('post', !isPublicAPI))(url, { body }, { bodyStringify: false, deleteContentType: true }) as Promise<{ data: string }>
|
||||
}
|
||||
|
||||
export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { content_type: string }, body: { streaming: boolean; voice?: string; message_id?: string; text?: string | null | undefined }) => {
|
||||
return (getAction('post', !isPublicAPI))(url, { body, header }, { needAllResponseContent: true })
|
||||
}
|
||||
|
||||
export const fetchAccessToken = async ({ userId, appCode }: { userId?: string, appCode: string }) => {
|
||||
const headers = new Headers()
|
||||
headers.append(WEB_APP_SHARE_CODE_HEADER_NAME, appCode)
|
||||
const accessToken = getWebAppAccessToken()
|
||||
if (accessToken)
|
||||
headers.append('Authorization', `Bearer ${accessToken}`)
|
||||
const params = new URLSearchParams()
|
||||
if (userId)
|
||||
params.append('user_id', userId)
|
||||
const url = `/passport?${params.toString()}`
|
||||
return get<{ access_token: string }>(url, { headers }) as Promise<{ access_token: string }>
|
||||
}
|
||||
|
||||
export const getUserCanAccess = (appId: string, isInstalledApp: boolean) => {
|
||||
if (isInstalledApp)
|
||||
return consoleGet<{ result: boolean }>(`/enterprise/webapp/permission?appId=${appId}`)
|
||||
|
||||
return get<{ result: boolean }>(`/webapp/permission?appId=${appId}`)
|
||||
}
|
||||
|
||||
export const getAppAccessModeByAppCode = (appCode: string) => {
|
||||
return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appCode=${appCode}`)
|
||||
}
|
||||
16
dify/web/service/sso.ts
Normal file
16
dify/web/service/sso.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { get } from './base'
|
||||
|
||||
export const getUserSAMLSSOUrl = (invite_token?: string) => {
|
||||
const url = invite_token ? `/enterprise/sso/saml/login?invite_token=${invite_token}` : '/enterprise/sso/saml/login'
|
||||
return get<{ url: string }>(url)
|
||||
}
|
||||
|
||||
export const getUserOIDCSSOUrl = (invite_token?: string) => {
|
||||
const url = invite_token ? `/enterprise/sso/oidc/login?invite_token=${invite_token}` : '/enterprise/sso/oidc/login'
|
||||
return get<{ url: string; state: string }>(url)
|
||||
}
|
||||
|
||||
export const getUserOAuth2SSOUrl = (invite_token?: string) => {
|
||||
const url = invite_token ? `/enterprise/sso/oauth2/login?invite_token=${invite_token}` : '/enterprise/sso/oauth2/login'
|
||||
return get<{ url: string; state: string }>(url)
|
||||
}
|
||||
10
dify/web/service/strategy.ts
Normal file
10
dify/web/service/strategy.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { StrategyPluginDetail } from '@/app/components/plugins/types'
|
||||
import { get } from './base'
|
||||
|
||||
export const fetchStrategyList = () => {
|
||||
return get<StrategyPluginDetail[]>('/workspaces/current/agent-providers')
|
||||
}
|
||||
|
||||
export const fetchStrategyDetail = (agentProvider: string) => {
|
||||
return get<StrategyPluginDetail>(`/workspaces/current/agent-provider/${agentProvider}`)
|
||||
}
|
||||
47
dify/web/service/tag.ts
Normal file
47
dify/web/service/tag.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { del, get, patch, post } from './base'
|
||||
import type { Tag } from '@/app/components/base/tag-management/constant'
|
||||
|
||||
export const fetchTagList = (type: string) => {
|
||||
return get<Tag[]>('/tags', { params: { type } })
|
||||
}
|
||||
|
||||
export const createTag = (name: string, type: string) => {
|
||||
return post<Tag>('/tags', {
|
||||
body: {
|
||||
name,
|
||||
type,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const updateTag = (tagID: string, name: string) => {
|
||||
return patch(`/tags/${tagID}`, {
|
||||
body: {
|
||||
name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteTag = (tagID: string) => {
|
||||
return del(`/tags/${tagID}`)
|
||||
}
|
||||
|
||||
export const bindTag = (tagIDList: string[], targetID: string, type: string) => {
|
||||
return post('/tag-bindings/create', {
|
||||
body: {
|
||||
tag_ids: tagIDList,
|
||||
target_id: targetID,
|
||||
type,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const unBindTag = (tagID: string, targetID: string, type: string) => {
|
||||
return post('/tag-bindings/remove', {
|
||||
body: {
|
||||
tag_id: tagID,
|
||||
target_id: targetID,
|
||||
type,
|
||||
},
|
||||
})
|
||||
}
|
||||
142
dify/web/service/tools.ts
Normal file
142
dify/web/service/tools.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { get, post } from './base'
|
||||
import type {
|
||||
Collection,
|
||||
CustomCollectionBackend,
|
||||
CustomParamSchema,
|
||||
Tool,
|
||||
ToolCredential,
|
||||
WorkflowToolProviderRequest,
|
||||
WorkflowToolProviderResponse,
|
||||
} from '@/app/components/tools/types'
|
||||
import { buildProviderQuery } from './_tools_util'
|
||||
|
||||
export const fetchCollectionList = () => {
|
||||
return get<Collection[]>('/workspaces/current/tool-providers')
|
||||
}
|
||||
|
||||
export const fetchCollectionDetail = (collectionName: string) => {
|
||||
return get<Collection>(`/workspaces/current/tool-provider/${collectionName}/info`)
|
||||
}
|
||||
|
||||
export const fetchBuiltInToolList = (collectionName: string) => {
|
||||
return get<Tool[]>(`/workspaces/current/tool-provider/builtin/${collectionName}/tools`)
|
||||
}
|
||||
|
||||
export const fetchCustomToolList = (collectionName: string) => {
|
||||
const query = buildProviderQuery(collectionName)
|
||||
return get<Tool[]>(`/workspaces/current/tool-provider/api/tools?${query}`)
|
||||
}
|
||||
|
||||
export const fetchModelToolList = (collectionName: string) => {
|
||||
const query = buildProviderQuery(collectionName)
|
||||
return get<Tool[]>(`/workspaces/current/tool-provider/model/tools?${query}`)
|
||||
}
|
||||
|
||||
export const fetchWorkflowToolList = (appID: string) => {
|
||||
return get<Tool[]>(`/workspaces/current/tool-provider/workflow/tools?workflow_tool_id=${appID}`)
|
||||
}
|
||||
|
||||
export const fetchBuiltInToolCredentialSchema = (collectionName: string) => {
|
||||
return get<ToolCredential[]>(`/workspaces/current/tool-provider/builtin/${collectionName}/credentials_schema`)
|
||||
}
|
||||
|
||||
export const fetchBuiltInToolCredential = (collectionName: string) => {
|
||||
return get<ToolCredential[]>(`/workspaces/current/tool-provider/builtin/${collectionName}/credentials`)
|
||||
}
|
||||
export const updateBuiltInToolCredential = (collectionName: string, credential: Record<string, any>) => {
|
||||
return post(`/workspaces/current/tool-provider/builtin/${collectionName}/update`, {
|
||||
body: {
|
||||
credentials: credential,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const removeBuiltInToolCredential = (collectionName: string) => {
|
||||
return post(`/workspaces/current/tool-provider/builtin/${collectionName}/delete`, {
|
||||
body: {},
|
||||
})
|
||||
}
|
||||
|
||||
export const parseParamsSchema = (schema: string) => {
|
||||
return post<{ parameters_schema: CustomParamSchema[]; schema_type: string }>('/workspaces/current/tool-provider/api/schema', {
|
||||
body: {
|
||||
schema,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchCustomCollection = (collectionName: string) => {
|
||||
const query = buildProviderQuery(collectionName)
|
||||
return get<CustomCollectionBackend>(`/workspaces/current/tool-provider/api/get?${query}`)
|
||||
}
|
||||
|
||||
export const createCustomCollection = (collection: CustomCollectionBackend) => {
|
||||
return post('/workspaces/current/tool-provider/api/add', {
|
||||
body: {
|
||||
...collection,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const updateCustomCollection = (collection: CustomCollectionBackend) => {
|
||||
return post('/workspaces/current/tool-provider/api/update', {
|
||||
body: {
|
||||
...collection,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const removeCustomCollection = (collectionName: string) => {
|
||||
return post('/workspaces/current/tool-provider/api/delete', {
|
||||
body: {
|
||||
provider: collectionName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const importSchemaFromURL = (url: string) => {
|
||||
return get('/workspaces/current/tool-provider/api/remote', {
|
||||
params: {
|
||||
url,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const testAPIAvailable = (payload: any) => {
|
||||
return post('/workspaces/current/tool-provider/api/test/pre', {
|
||||
body: {
|
||||
...payload,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const createWorkflowToolProvider = (payload: WorkflowToolProviderRequest & { workflow_app_id: string }) => {
|
||||
return post('/workspaces/current/tool-provider/workflow/create', {
|
||||
body: { ...payload },
|
||||
})
|
||||
}
|
||||
|
||||
export const saveWorkflowToolProvider = (payload: WorkflowToolProviderRequest & Partial<{
|
||||
workflow_app_id: string
|
||||
workflow_tool_id: string
|
||||
}>) => {
|
||||
return post('/workspaces/current/tool-provider/workflow/update', {
|
||||
body: { ...payload },
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchWorkflowToolDetailByAppID = (appID: string) => {
|
||||
return get<WorkflowToolProviderResponse>(`/workspaces/current/tool-provider/workflow/get?workflow_app_id=${appID}`)
|
||||
}
|
||||
|
||||
export const fetchWorkflowToolDetail = (toolID: string) => {
|
||||
return get<WorkflowToolProviderResponse>(`/workspaces/current/tool-provider/workflow/get?workflow_tool_id=${toolID}`)
|
||||
}
|
||||
|
||||
export const deleteWorkflowTool = (toolID: string) => {
|
||||
return post('/workspaces/current/tool-provider/workflow/delete', {
|
||||
body: {
|
||||
workflow_tool_id: toolID,
|
||||
},
|
||||
})
|
||||
}
|
||||
41
dify/web/service/use-apps.ts
Normal file
41
dify/web/service/use-apps.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { get, post } from './base'
|
||||
import type { App } from '@/types/app'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import { useInvalid } from './use-base'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
|
||||
|
||||
const NAME_SPACE = 'apps'
|
||||
|
||||
// TODO paging for list
|
||||
const useAppFullListKey = [NAME_SPACE, 'full-list']
|
||||
export const useAppFullList = () => {
|
||||
return useQuery<AppListResponse>({
|
||||
queryKey: useAppFullListKey,
|
||||
queryFn: () => get<AppListResponse>('/apps', { params: { page: 1, limit: 100 } }),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAppFullList = () => {
|
||||
return useInvalid(useAppFullListKey)
|
||||
}
|
||||
|
||||
export const useAppDetail = (appID: string) => {
|
||||
return useQuery<App>({
|
||||
queryKey: [NAME_SPACE, 'detail', appID],
|
||||
queryFn: () => get<App>(`/apps/${appID}`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'generate-rule-template', type],
|
||||
queryFn: () => post<{ data: string }>('instruction-generate/template', {
|
||||
body: {
|
||||
type,
|
||||
},
|
||||
}),
|
||||
enabled: !disabled,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
30
dify/web/service/use-base.ts
Normal file
30
dify/web/service/use-base.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
type QueryKey,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
export const useInvalid = (key?: QueryKey) => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
if (!key)
|
||||
return
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: key,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const useReset = (key?: QueryKey) => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
if (!key)
|
||||
return
|
||||
queryClient.resetQueries(
|
||||
{
|
||||
queryKey: key,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
19
dify/web/service/use-billing.ts
Normal file
19
dify/web/service/use-billing.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { put } from './base'
|
||||
|
||||
const NAME_SPACE = 'billing'
|
||||
|
||||
export const useBindPartnerStackInfo = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'bind-partner-stack'],
|
||||
mutationFn: (data: { partnerKey: string; clickId: string }) => {
|
||||
return put(`/billing/partners/${data.partnerKey}/tenants`, {
|
||||
body: {
|
||||
click_id: data.clickId,
|
||||
},
|
||||
}, {
|
||||
silent: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
140
dify/web/service/use-common.ts
Normal file
140
dify/web/service/use-common.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { get, post } from './base'
|
||||
import type {
|
||||
FileUploadConfigResponse,
|
||||
Member,
|
||||
StructuredOutputRulesRequestBody,
|
||||
StructuredOutputRulesResponse,
|
||||
} from '@/models/common'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import type { FileTypesRes } from './datasets'
|
||||
|
||||
const NAME_SPACE = 'common'
|
||||
|
||||
export const useFileUploadConfig = () => {
|
||||
return useQuery<FileUploadConfigResponse>({
|
||||
queryKey: [NAME_SPACE, 'file-upload-config'],
|
||||
queryFn: () => get<FileUploadConfigResponse>('/files/upload'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useGenerateStructuredOutputRules = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'generate-structured-output-rules'],
|
||||
mutationFn: (body: StructuredOutputRulesRequestBody) => {
|
||||
return post<StructuredOutputRulesResponse>(
|
||||
'/rule-structured-output-generate',
|
||||
{ body },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type MailSendResponse = { data: string, result: string }
|
||||
export const useSendMail = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'mail-send'],
|
||||
mutationFn: (body: { email: string, language: string }) => {
|
||||
return post<MailSendResponse>('/email-register/send-email', { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type MailValidityResponse = { is_valid: boolean, token: string }
|
||||
|
||||
export const useMailValidity = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'mail-validity'],
|
||||
mutationFn: (body: { email: string, code: string, token: string }) => {
|
||||
return post<MailValidityResponse>('/email-register/validity', { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type MailRegisterResponse = { result: string, data: {} }
|
||||
|
||||
export const useMailRegister = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'mail-register'],
|
||||
mutationFn: (body: { token: string, new_password: string, password_confirm: string }) => {
|
||||
return post<MailRegisterResponse>('/email-register', { body })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useFileSupportTypes = () => {
|
||||
return useQuery<FileTypesRes>({
|
||||
queryKey: [NAME_SPACE, 'file-types'],
|
||||
queryFn: () => get<FileTypesRes>('/files/support-type'),
|
||||
})
|
||||
}
|
||||
|
||||
type MemberResponse = {
|
||||
accounts: Member[] | null
|
||||
}
|
||||
|
||||
export const useMembers = () => {
|
||||
return useQuery<MemberResponse>({
|
||||
queryKey: [NAME_SPACE, 'members'],
|
||||
queryFn: (params: Record<string, any>) => get<MemberResponse>('/workspaces/current/members', {
|
||||
params,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
type FilePreviewResponse = {
|
||||
content: string
|
||||
}
|
||||
|
||||
export const useFilePreview = (fileID: string) => {
|
||||
return useQuery<FilePreviewResponse>({
|
||||
queryKey: [NAME_SPACE, 'file-preview', fileID],
|
||||
queryFn: () => get<FilePreviewResponse>(`/files/${fileID}/preview`),
|
||||
enabled: !!fileID,
|
||||
})
|
||||
}
|
||||
|
||||
export type SchemaTypeDefinition = {
|
||||
name: string
|
||||
schema: {
|
||||
properties: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
||||
export const useSchemaTypeDefinitions = () => {
|
||||
return useQuery<SchemaTypeDefinition[]>({
|
||||
queryKey: [NAME_SPACE, 'schema-type-definitions'],
|
||||
queryFn: () => get<SchemaTypeDefinition[]>('/spec/schema-definitions'),
|
||||
})
|
||||
}
|
||||
|
||||
type isLogin = {
|
||||
logged_in: boolean
|
||||
}
|
||||
|
||||
export const useIsLogin = () => {
|
||||
return useQuery<isLogin>({
|
||||
queryKey: [NAME_SPACE, 'is-login'],
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
queryFn: async (): Promise<isLogin> => {
|
||||
try {
|
||||
await get('/account/profile', {}, {
|
||||
silent: true,
|
||||
})
|
||||
}
|
||||
catch (e: any) {
|
||||
if(e.status === 401)
|
||||
return { logged_in: false }
|
||||
return { logged_in: true }
|
||||
}
|
||||
return { logged_in: true }
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useLogout = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'logout'],
|
||||
mutationFn: () => post('/logout'),
|
||||
})
|
||||
}
|
||||
79
dify/web/service/use-datasource.ts
Normal file
79
dify/web/service/use-datasource.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { get } from './base'
|
||||
import { useInvalid } from './use-base'
|
||||
import type {
|
||||
DataSourceAuth,
|
||||
DataSourceCredential,
|
||||
} from '@/app/components/header/account-setting/data-source-page-new/types'
|
||||
|
||||
const NAME_SPACE = 'data-source-auth'
|
||||
|
||||
export const useGetDataSourceListAuth = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'list'],
|
||||
queryFn: () => get<{ result: DataSourceAuth[] }>('/auth/plugin/datasource/list'),
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDataSourceListAuth = (
|
||||
) => {
|
||||
return useInvalid([NAME_SPACE, 'list'])
|
||||
}
|
||||
|
||||
// !This hook is used for fetching the default data source list, which will be legacy and deprecated in the near future.
|
||||
export const useGetDefaultDataSourceListAuth = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'default-list'],
|
||||
queryFn: () => get<{ result: DataSourceAuth[] }>('/auth/plugin/datasource/default-list'),
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDefaultDataSourceListAuth = (
|
||||
) => {
|
||||
return useInvalid([NAME_SPACE, 'default-list'])
|
||||
}
|
||||
|
||||
export const useGetDataSourceOAuthUrl = (
|
||||
provider: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'oauth-url', provider],
|
||||
mutationFn: (credentialId?: string) => {
|
||||
return get<
|
||||
{
|
||||
authorization_url: string
|
||||
state: string
|
||||
context_id: string
|
||||
}>(`/oauth/plugin/${provider}/datasource/get-authorization-url?credential_id=${credentialId}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetDataSourceAuth = ({
|
||||
pluginId,
|
||||
provider,
|
||||
}: {
|
||||
pluginId: string
|
||||
provider: string
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'specific-data-source', pluginId, provider],
|
||||
queryFn: () => get<{ result: DataSourceCredential[] }>(`/auth/plugin/datasource/${pluginId}/${provider}`),
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDataSourceAuth = ({
|
||||
pluginId,
|
||||
provider,
|
||||
}: {
|
||||
pluginId: string
|
||||
provider: string
|
||||
}) => {
|
||||
return useInvalid([NAME_SPACE, 'specific-data-source', pluginId, provider])
|
||||
}
|
||||
68
dify/web/service/use-education.ts
Normal file
68
dify/web/service/use-education.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { get, post } from './base'
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { useInvalid } from './use-base'
|
||||
import type { EducationAddParams } from '@/app/education-apply/types'
|
||||
|
||||
const NAME_SPACE = 'education'
|
||||
|
||||
export const useEducationVerify = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'education-verify'],
|
||||
mutationFn: () => {
|
||||
return get<{ token: string }>('/account/education/verify', {}, { silent: true })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useEducationAdd = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'education-add'],
|
||||
mutationFn: (params: EducationAddParams) => {
|
||||
return post<{ message: string }>('/account/education', {
|
||||
body: params,
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
type SearchParams = {
|
||||
keywords?: string
|
||||
page?: number
|
||||
limit?: number
|
||||
}
|
||||
export const useEducationAutocomplete = () => {
|
||||
return useMutation({
|
||||
mutationFn: (searchParams: SearchParams) => {
|
||||
const {
|
||||
keywords = '',
|
||||
page = 0,
|
||||
limit = 40,
|
||||
} = searchParams
|
||||
return get<{ data: string[]; has_next: boolean; curr_page: number }>(`/account/education/autocomplete?keywords=${keywords}&page=${page}&limit=${limit}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useEducationStatus = (disable?: boolean) => {
|
||||
return useQuery({
|
||||
enabled: !disable,
|
||||
queryKey: [NAME_SPACE, 'education-status'],
|
||||
queryFn: () => {
|
||||
return get<{ is_student: boolean, allow_refresh: boolean, expire_at: number | null }>('/account/education')
|
||||
},
|
||||
retry: false,
|
||||
gcTime: 0, // No cache. Prevent switch account caused stale data
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateEducationStatus = () => {
|
||||
return useInvalid([NAME_SPACE, 'education-status'])
|
||||
}
|
||||
149
dify/web/service/use-endpoints.ts
Normal file
149
dify/web/service/use-endpoints.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { get, post } from './base'
|
||||
import type {
|
||||
EndpointsResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'endpoints'
|
||||
|
||||
export const useEndpointList = (pluginID: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'list', pluginID],
|
||||
queryFn: () => get<EndpointsResponse>('/workspaces/current/endpoints/list/plugin', {
|
||||
params: {
|
||||
plugin_id: pluginID,
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateEndpointList = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (pluginID: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'list', pluginID],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useCreateEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create'],
|
||||
mutationFn: (payload: { pluginUniqueID: string, state: Record<string, any> }) => {
|
||||
const { pluginUniqueID, state } = payload
|
||||
const newName = state.name
|
||||
delete state.name
|
||||
return post('/workspaces/current/endpoints/create', {
|
||||
body: {
|
||||
plugin_unique_identifier: pluginUniqueID,
|
||||
settings: state,
|
||||
name: newName,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update'],
|
||||
mutationFn: (payload: { endpointID: string, state: Record<string, any> }) => {
|
||||
const { endpointID, state } = payload
|
||||
const newName = state.name
|
||||
delete state.name
|
||||
return post('/workspaces/current/endpoints/update', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
settings: state,
|
||||
name: newName,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete'],
|
||||
mutationFn: (endpointID: string) => {
|
||||
return post('/workspaces/current/endpoints/delete', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useEnableEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'enable'],
|
||||
mutationFn: (endpointID: string) => {
|
||||
return post('/workspaces/current/endpoints/enable', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDisableEndpoint = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'disable'],
|
||||
mutationFn: (endpointID: string) => {
|
||||
return post('/workspaces/current/endpoints/disable', {
|
||||
body: {
|
||||
endpoint_id: endpointID,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
onError,
|
||||
})
|
||||
}
|
||||
81
dify/web/service/use-explore.ts
Normal file
81
dify/web/service/use-explore.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore'
|
||||
import { fetchAppMeta, fetchAppParams } from './share'
|
||||
|
||||
const NAME_SPACE = 'explore'
|
||||
|
||||
export const useGetInstalledApps = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'installedApps'],
|
||||
queryFn: () => {
|
||||
return fetchInstalledAppList()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUninstallApp = () => {
|
||||
const client = useQueryClient()
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'uninstallApp'],
|
||||
mutationFn: (appId: string) => uninstallApp(appId),
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({ queryKey: [NAME_SPACE, 'installedApps'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateAppPinStatus = () => {
|
||||
const client = useQueryClient()
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'updateAppPinStatus'],
|
||||
mutationFn: ({ appId, isPinned }: { appId: string; isPinned: boolean }) => updatePinStatus(appId, isPinned),
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({ queryKey: [NAME_SPACE, 'installedApps'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetInstalledAppAccessModeByAppId = (appId: string | null) => {
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appAccessMode', appId],
|
||||
queryFn: () => {
|
||||
if (systemFeatures.webapp_auth.enabled === false) {
|
||||
return {
|
||||
accessMode: AccessMode.PUBLIC,
|
||||
}
|
||||
}
|
||||
if (!appId || appId.length === 0)
|
||||
return Promise.reject(new Error('App code is required to get access mode'))
|
||||
|
||||
return getAppAccessModeByAppId(appId)
|
||||
},
|
||||
enabled: !!appId,
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetInstalledAppParams = (appId: string | null) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appParams', appId],
|
||||
queryFn: () => {
|
||||
if (!appId || appId.length === 0)
|
||||
return Promise.reject(new Error('App ID is required to get app params'))
|
||||
return fetchAppParams(true, appId)
|
||||
},
|
||||
enabled: !!appId,
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetInstalledAppMeta = (appId: string | null) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appMeta', appId],
|
||||
queryFn: () => {
|
||||
if (!appId || appId.length === 0)
|
||||
return Promise.reject(new Error('App ID is required to get app meta'))
|
||||
return fetchAppMeta(true, appId)
|
||||
},
|
||||
enabled: !!appId,
|
||||
})
|
||||
}
|
||||
33
dify/web/service/use-flow.ts
Normal file
33
dify/web/service/use-flow.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { FlowType } from '@/types/common'
|
||||
import {
|
||||
useDeleteAllInspectorVars as useDeleteAllInspectorVarsInner,
|
||||
useDeleteInspectVar as useDeleteInspectVarInner,
|
||||
useDeleteNodeInspectorVars as useDeleteNodeInspectorVarsInner,
|
||||
useEditInspectorVar as useEditInspectorVarInner,
|
||||
useInvalidateConversationVarValues as useInvalidateConversationVarValuesInner,
|
||||
useInvalidateSysVarValues as useInvalidateSysVarValuesInner,
|
||||
useResetConversationVar as useResetConversationVarInner,
|
||||
useResetToLastRunValue as useResetToLastRunValueInner,
|
||||
} from './use-workflow'
|
||||
import { curry } from 'lodash-es'
|
||||
|
||||
type Params = {
|
||||
flowType: FlowType
|
||||
}
|
||||
|
||||
const useFLow = ({
|
||||
flowType,
|
||||
}: Params) => {
|
||||
return {
|
||||
useInvalidateConversationVarValues: curry(useInvalidateConversationVarValuesInner)(flowType),
|
||||
useInvalidateSysVarValues: curry(useInvalidateSysVarValuesInner)(flowType),
|
||||
useResetConversationVar: curry(useResetConversationVarInner)(flowType),
|
||||
useResetToLastRunValue: curry(useResetToLastRunValueInner)(flowType),
|
||||
useDeleteAllInspectorVars: curry(useDeleteAllInspectorVarsInner)(flowType),
|
||||
useDeleteNodeInspectorVars: curry(useDeleteNodeInspectorVarsInner)(flowType),
|
||||
useDeleteInspectVar: curry(useDeleteInspectVarInner)(flowType),
|
||||
useEditInspectorVar: curry(useEditInspectorVarInner)(flowType),
|
||||
}
|
||||
}
|
||||
|
||||
export default useFLow
|
||||
155
dify/web/service/use-models.ts
Normal file
155
dify/web/service/use-models.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
del,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
} from './base'
|
||||
import type {
|
||||
ModelCredential,
|
||||
ModelItem,
|
||||
ModelLoadBalancingConfig,
|
||||
ModelTypeEnum,
|
||||
ProviderCredential,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
// useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'models'
|
||||
|
||||
export const useModelProviderModelList = (provider: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'model-list', provider],
|
||||
queryFn: () => get<{ data: ModelItem[] }>(`/workspaces/current/model-providers/${provider}/models`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetProviderCredential = (enabled: boolean, provider: string, credentialId?: string) => {
|
||||
return useQuery({
|
||||
enabled,
|
||||
queryKey: [NAME_SPACE, 'model-list', provider, credentialId],
|
||||
queryFn: () => get<ProviderCredential>(`/workspaces/current/model-providers/${provider}/credentials${credentialId ? `?credential_id=${credentialId}` : ''}`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddProviderCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: ProviderCredential) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useEditProviderCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: ProviderCredential) => put<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteProviderCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
credential_id: string
|
||||
}) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useActiveProviderCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
credential_id: string
|
||||
model?: string
|
||||
model_type?: ModelTypeEnum
|
||||
}) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/credentials/switch`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetModelCredential = (
|
||||
enabled: boolean,
|
||||
provider: string,
|
||||
credentialId?: string,
|
||||
model?: string,
|
||||
modelType?: string,
|
||||
configFrom?: string,
|
||||
) => {
|
||||
return useQuery({
|
||||
enabled,
|
||||
queryKey: [NAME_SPACE, 'model-list', provider, model, modelType, credentialId],
|
||||
queryFn: () => get<ModelCredential>(`/workspaces/current/model-providers/${provider}/models/credentials?model=${model}&model_type=${modelType}&config_from=${configFrom}${credentialId ? `&credential_id=${credentialId}` : ''}`),
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddModelCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: ModelCredential) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useEditModelCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: ModelCredential) => put<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteModelCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
credential_id: string
|
||||
model?: string
|
||||
model_type?: ModelTypeEnum
|
||||
}) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteModel = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
model: string
|
||||
model_type: ModelTypeEnum
|
||||
}) => del<{ result: string }>(`/workspaces/current/model-providers/${provider}/models`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useActiveModelCredential = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
credential_id: string
|
||||
model?: string
|
||||
model_type?: ModelTypeEnum
|
||||
}) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models/credentials/switch`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateModelLoadBalancingConfig = (provider: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
config_from: string
|
||||
model: string
|
||||
model_type: ModelTypeEnum
|
||||
load_balancing: ModelLoadBalancingConfig
|
||||
credential_id?: string
|
||||
}) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models`, {
|
||||
body: data,
|
||||
}),
|
||||
})
|
||||
}
|
||||
29
dify/web/service/use-oauth.ts
Normal file
29
dify/web/service/use-oauth.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { post } from './base'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'oauth-provider'
|
||||
|
||||
export type OAuthAppInfo = {
|
||||
app_icon: string
|
||||
app_label: Record<string, string>
|
||||
scope: string
|
||||
}
|
||||
|
||||
export type OAuthAuthorizeResponse = {
|
||||
code: string
|
||||
}
|
||||
|
||||
export const useOAuthAppInfo = (client_id: string, redirect_uri: string) => {
|
||||
return useQuery<OAuthAppInfo>({
|
||||
queryKey: [NAME_SPACE, 'authAppInfo', client_id, redirect_uri],
|
||||
queryFn: () => post<OAuthAppInfo>('/oauth/provider', { body: { client_id, redirect_uri } }, { silent: true }),
|
||||
enabled: Boolean(client_id && redirect_uri),
|
||||
})
|
||||
}
|
||||
|
||||
export const useAuthorizeOAuthApp = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'authorize'],
|
||||
mutationFn: (payload: { client_id: string }) => post<OAuthAuthorizeResponse>('/oauth/provider/authorize', { body: payload }),
|
||||
})
|
||||
}
|
||||
393
dify/web/service/use-pipeline.ts
Normal file
393
dify/web/service/use-pipeline.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import type { MutationOptions } from '@tanstack/react-query'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { del, get, patch, post } from './base'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import type {
|
||||
ConversionResponse,
|
||||
DatasourceNodeSingleRunRequest,
|
||||
DatasourceNodeSingleRunResponse,
|
||||
DeleteTemplateResponse,
|
||||
ExportTemplateDSLResponse,
|
||||
ImportPipelineDSLConfirmResponse,
|
||||
ImportPipelineDSLRequest,
|
||||
ImportPipelineDSLResponse,
|
||||
OnlineDocumentPreviewRequest,
|
||||
OnlineDocumentPreviewResponse,
|
||||
PipelineCheckDependenciesResponse,
|
||||
PipelineExecutionLogRequest,
|
||||
PipelineExecutionLogResponse,
|
||||
PipelinePreProcessingParamsRequest,
|
||||
PipelinePreProcessingParamsResponse,
|
||||
PipelineProcessingParamsRequest,
|
||||
PipelineProcessingParamsResponse,
|
||||
PipelineTemplateByIdRequest,
|
||||
PipelineTemplateByIdResponse,
|
||||
PipelineTemplateListParams,
|
||||
PipelineTemplateListResponse,
|
||||
PublishedPipelineInfoResponse,
|
||||
PublishedPipelineRunPreviewResponse,
|
||||
PublishedPipelineRunRequest,
|
||||
PublishedPipelineRunResponse,
|
||||
UpdateTemplateInfoRequest,
|
||||
UpdateTemplateInfoResponse,
|
||||
} from '@/models/pipeline'
|
||||
import type { DataSourceItem } from '@/app/components/workflow/block-selector/types'
|
||||
import type { ToolCredential } from '@/app/components/tools/types'
|
||||
import type { IconInfo } from '@/models/datasets'
|
||||
import { useInvalid } from './use-base'
|
||||
|
||||
const NAME_SPACE = 'pipeline'
|
||||
|
||||
export const PipelineTemplateListQueryKeyPrefix = [NAME_SPACE, 'template-list']
|
||||
export const usePipelineTemplateList = (params: PipelineTemplateListParams, enabled = true) => {
|
||||
const { type, language } = params
|
||||
return useQuery<PipelineTemplateListResponse>({
|
||||
queryKey: [...PipelineTemplateListQueryKeyPrefix, type, language],
|
||||
queryFn: () => {
|
||||
return get<PipelineTemplateListResponse>('/rag/pipeline/templates', { params })
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidCustomizedTemplateList = () => {
|
||||
return useInvalid([...PipelineTemplateListQueryKeyPrefix, 'customized'])
|
||||
}
|
||||
|
||||
export const usePipelineTemplateById = (params: PipelineTemplateByIdRequest, enabled: boolean) => {
|
||||
const { template_id, type } = params
|
||||
return useQuery<PipelineTemplateByIdResponse>({
|
||||
queryKey: [NAME_SPACE, 'template', type, template_id],
|
||||
queryFn: () => {
|
||||
return get<PipelineTemplateByIdResponse>(`/rag/pipeline/templates/${template_id}`, {
|
||||
params: {
|
||||
type,
|
||||
},
|
||||
})
|
||||
},
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateTemplateInfo = (
|
||||
mutationOptions: MutationOptions<UpdateTemplateInfoResponse, Error, UpdateTemplateInfoRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'template-update'],
|
||||
mutationFn: (request: UpdateTemplateInfoRequest) => {
|
||||
const { template_id, ...rest } = request
|
||||
return patch<UpdateTemplateInfoResponse>(`/rag/pipeline/customized/templates/${template_id}`, {
|
||||
body: rest,
|
||||
})
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteTemplate = (
|
||||
mutationOptions: MutationOptions<DeleteTemplateResponse, Error, string> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'template-delete'],
|
||||
mutationFn: (templateId: string) => {
|
||||
return del<DeleteTemplateResponse>(`/rag/pipeline/customized/templates/${templateId}`)
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useExportTemplateDSL = (
|
||||
mutationOptions: MutationOptions<ExportTemplateDSLResponse, Error, string> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'template-dsl-export'],
|
||||
mutationFn: (templateId: string) => {
|
||||
return post<ExportTemplateDSLResponse>(`/rag/pipeline/customized/templates/${templateId}`)
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useImportPipelineDSL = (
|
||||
mutationOptions: MutationOptions<ImportPipelineDSLResponse, Error, ImportPipelineDSLRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'dsl-import'],
|
||||
mutationFn: (request: ImportPipelineDSLRequest) => {
|
||||
return post<ImportPipelineDSLResponse>('/rag/pipelines/imports', { body: request })
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useImportPipelineDSLConfirm = (
|
||||
mutationOptions: MutationOptions<ImportPipelineDSLConfirmResponse, Error, string> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'dsl-import-confirm'],
|
||||
mutationFn: (importId: string) => {
|
||||
return post<ImportPipelineDSLConfirmResponse>(`/rag/pipelines/imports/${importId}/confirm`)
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCheckPipelineDependencies = (
|
||||
mutationOptions: MutationOptions<PipelineCheckDependenciesResponse, Error, string> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'check-dependencies'],
|
||||
mutationFn: (pipelineId: string) => {
|
||||
return get<PipelineCheckDependenciesResponse>(`/rag/pipelines/imports/${pipelineId}/check-dependencies`)
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDraftPipelineProcessingParams = (params: PipelineProcessingParamsRequest, enabled = true) => {
|
||||
const { pipeline_id, node_id } = params
|
||||
return useQuery<PipelineProcessingParamsResponse>({
|
||||
queryKey: [NAME_SPACE, 'draft-pipeline-processing-params', pipeline_id, node_id],
|
||||
queryFn: () => {
|
||||
return get<PipelineProcessingParamsResponse>(`/rag/pipelines/${pipeline_id}/workflows/draft/processing/parameters`, {
|
||||
params: {
|
||||
node_id,
|
||||
},
|
||||
})
|
||||
},
|
||||
staleTime: 0,
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePublishedPipelineProcessingParams = (params: PipelineProcessingParamsRequest) => {
|
||||
const { pipeline_id, node_id } = params
|
||||
return useQuery<PipelineProcessingParamsResponse>({
|
||||
queryKey: [NAME_SPACE, 'published-pipeline-processing-params', pipeline_id, node_id],
|
||||
queryFn: () => {
|
||||
return get<PipelineProcessingParamsResponse>(`/rag/pipelines/${pipeline_id}/workflows/published/processing/parameters`, {
|
||||
params: {
|
||||
node_id,
|
||||
},
|
||||
})
|
||||
},
|
||||
staleTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDataSourceList = (enabled: boolean, onSuccess?: (v: DataSourceItem[]) => void) => {
|
||||
return useQuery<DataSourceItem[]>({
|
||||
enabled,
|
||||
queryKey: [NAME_SPACE, 'datasource'],
|
||||
staleTime: 0,
|
||||
queryFn: async () => {
|
||||
const data = await get<DataSourceItem[]>('/rag/pipelines/datasource-plugins')
|
||||
onSuccess?.(data)
|
||||
return data
|
||||
},
|
||||
retry: false,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidDataSourceList = () => {
|
||||
return useInvalid([NAME_SPACE, 'datasource'])
|
||||
}
|
||||
|
||||
export const publishedPipelineInfoQueryKeyPrefix = [NAME_SPACE, 'published-pipeline']
|
||||
|
||||
export const usePublishedPipelineInfo = (pipelineId: string) => {
|
||||
return useQuery<PublishedPipelineInfoResponse>({
|
||||
queryKey: [...publishedPipelineInfoQueryKeyPrefix, pipelineId],
|
||||
queryFn: () => {
|
||||
return get<PublishedPipelineInfoResponse>(`/rag/pipelines/${pipelineId}/workflows/publish`)
|
||||
},
|
||||
enabled: !!pipelineId,
|
||||
})
|
||||
}
|
||||
|
||||
export const useRunPublishedPipeline = (
|
||||
mutationOptions: MutationOptions<PublishedPipelineRunPreviewResponse | PublishedPipelineRunResponse, Error, PublishedPipelineRunRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'run-published-pipeline'],
|
||||
mutationFn: (request: PublishedPipelineRunRequest) => {
|
||||
const { pipeline_id: pipelineId, is_preview, ...rest } = request
|
||||
return post<PublishedPipelineRunPreviewResponse | PublishedPipelineRunResponse>(`/rag/pipelines/${pipelineId}/workflows/published/run`, {
|
||||
body: {
|
||||
...rest,
|
||||
is_preview,
|
||||
response_mode: 'blocking',
|
||||
},
|
||||
})
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDataSourceCredentials = (provider: string, pluginId: string, onSuccess: (value: ToolCredential[]) => void) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'datasource-credentials', provider, pluginId],
|
||||
queryFn: async () => {
|
||||
const result = await get<{ result: ToolCredential[] }>(`/auth/plugin/datasource?provider=${provider}&plugin_id=${pluginId}`)
|
||||
onSuccess(result.result)
|
||||
return result.result
|
||||
},
|
||||
enabled: !!provider && !!pluginId,
|
||||
retry: 2,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateDataSourceCredentials = (
|
||||
) => {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-datasource-credentials'],
|
||||
mutationFn: ({
|
||||
provider,
|
||||
pluginId,
|
||||
credentials,
|
||||
name,
|
||||
}: { provider: string; pluginId: string; credentials: Record<string, any>; name: string; }) => {
|
||||
return post('/auth/plugin/datasource', {
|
||||
body: {
|
||||
provider,
|
||||
plugin_id: pluginId,
|
||||
credentials,
|
||||
name,
|
||||
},
|
||||
}).then(() => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'datasource'],
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDraftPipelinePreProcessingParams = (params: PipelinePreProcessingParamsRequest, enabled = true) => {
|
||||
const { pipeline_id, node_id } = params
|
||||
return useQuery<PipelinePreProcessingParamsResponse>({
|
||||
queryKey: [NAME_SPACE, 'draft-pipeline-pre-processing-params', pipeline_id, node_id],
|
||||
queryFn: () => {
|
||||
return get<PipelinePreProcessingParamsResponse>(`/rag/pipelines/${pipeline_id}/workflows/draft/pre-processing/parameters`, {
|
||||
params: {
|
||||
node_id,
|
||||
},
|
||||
})
|
||||
},
|
||||
staleTime: 0,
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePublishedPipelinePreProcessingParams = (params: PipelinePreProcessingParamsRequest, enabled = true) => {
|
||||
const { pipeline_id, node_id } = params
|
||||
return useQuery<PipelinePreProcessingParamsResponse>({
|
||||
queryKey: [NAME_SPACE, 'published-pipeline-pre-processing-params', pipeline_id, node_id],
|
||||
queryFn: () => {
|
||||
return get<PipelinePreProcessingParamsResponse>(`/rag/pipelines/${pipeline_id}/workflows/published/pre-processing/parameters`, {
|
||||
params: {
|
||||
node_id,
|
||||
},
|
||||
})
|
||||
},
|
||||
staleTime: 0,
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const useExportPipelineDSL = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'export-pipeline-dsl'],
|
||||
mutationFn: ({
|
||||
pipelineId,
|
||||
include = false,
|
||||
}: { pipelineId: string; include?: boolean }) => {
|
||||
return get<ExportTemplateDSLResponse>(`/rag/pipelines/${pipelineId}/exports?include_secret=${include}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const usePublishAsCustomizedPipeline = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'publish-as-customized-pipeline'],
|
||||
mutationFn: ({
|
||||
pipelineId,
|
||||
name,
|
||||
icon_info,
|
||||
description,
|
||||
}: {
|
||||
pipelineId: string,
|
||||
name: string,
|
||||
icon_info: IconInfo,
|
||||
description?: string,
|
||||
}) => {
|
||||
return post(`/rag/pipelines/${pipelineId}/customized/publish`, {
|
||||
body: {
|
||||
name,
|
||||
icon_info,
|
||||
description,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const usePipelineExecutionLog = (params: PipelineExecutionLogRequest) => {
|
||||
const { dataset_id, document_id } = params
|
||||
return useQuery<PipelineExecutionLogResponse>({
|
||||
queryKey: [NAME_SPACE, 'pipeline-execution-log', dataset_id, document_id],
|
||||
queryFn: () => {
|
||||
return get<PipelineExecutionLogResponse>(`/datasets/${dataset_id}/documents/${document_id}/pipeline-execution-log`)
|
||||
},
|
||||
staleTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePreviewOnlineDocument = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'preview-online-document'],
|
||||
mutationFn: (params: OnlineDocumentPreviewRequest) => {
|
||||
const { pipelineId, datasourceNodeId, workspaceID, pageID, pageType, credentialId } = params
|
||||
return post<OnlineDocumentPreviewResponse>(
|
||||
`/rag/pipelines/${pipelineId}/workflows/published/datasource/nodes/${datasourceNodeId}/preview`,
|
||||
{
|
||||
body: {
|
||||
datasource_type: DatasourceType.onlineDocument,
|
||||
credential_id: credentialId,
|
||||
inputs: {
|
||||
workspace_id: workspaceID,
|
||||
page_id: pageID,
|
||||
type: pageType,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useConvertDatasetToPipeline = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'convert-dataset-to-pipeline'],
|
||||
mutationFn: (datasetId: string) => {
|
||||
return post<ConversionResponse>(`/rag/pipelines/transform/datasets/${datasetId}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDatasourceSingleRun = (
|
||||
mutationOptions: MutationOptions<DatasourceNodeSingleRunResponse, Error, DatasourceNodeSingleRunRequest> = {},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'datasource-node-single-run'],
|
||||
mutationFn: (params: DatasourceNodeSingleRunRequest) => {
|
||||
const { pipeline_id: pipelineId, ...rest } = params
|
||||
return post<DatasourceNodeSingleRunResponse>(`/rag/pipelines/${pipelineId}/workflows/draft/datasource/variables-inspect`, {
|
||||
body: rest,
|
||||
})
|
||||
},
|
||||
...mutationOptions,
|
||||
})
|
||||
}
|
||||
164
dify/web/service/use-plugins-auth.ts
Normal file
164
dify/web/service/use-plugins-auth.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { del, get, post } from './base'
|
||||
import { useInvalid } from './use-base'
|
||||
import type {
|
||||
Credential,
|
||||
CredentialTypeEnum,
|
||||
} from '@/app/components/plugins/plugin-auth/types'
|
||||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
|
||||
const NAME_SPACE = 'plugins-auth'
|
||||
|
||||
export const useGetPluginCredentialInfo = (
|
||||
url: string,
|
||||
) => {
|
||||
return useQuery({
|
||||
enabled: !!url,
|
||||
queryKey: [NAME_SPACE, 'credential-info', url],
|
||||
queryFn: () => get<{
|
||||
allow_custom_token?: boolean
|
||||
supported_credential_types: string[]
|
||||
credentials: Credential[]
|
||||
is_oauth_custom_client_enabled: boolean
|
||||
}>(url),
|
||||
staleTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidPluginCredentialInfo = (
|
||||
url: string,
|
||||
) => {
|
||||
return useInvalid([NAME_SPACE, 'credential-info', url])
|
||||
}
|
||||
|
||||
export const useSetPluginDefaultCredential = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => {
|
||||
return post(url, { body: { id } })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetPluginCredentialList = (
|
||||
url: string,
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'credential-list', url],
|
||||
queryFn: () => get(url),
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddPluginCredential = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (params: {
|
||||
credentials: Record<string, any>
|
||||
type: CredentialTypeEnum
|
||||
name?: string
|
||||
}) => {
|
||||
return post(url, { body: params })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdatePluginCredential = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (params: {
|
||||
credential_id: string
|
||||
credentials?: Record<string, any>
|
||||
name?: string
|
||||
}) => {
|
||||
return post(url, { body: params })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeletePluginCredential = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (params: { credential_id: string }) => {
|
||||
return post(url, { body: params })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetPluginCredentialSchema = (
|
||||
url: string,
|
||||
) => {
|
||||
return useQuery({
|
||||
enabled: !!url,
|
||||
queryKey: [NAME_SPACE, 'credential-schema', url],
|
||||
queryFn: () => get<FormSchema[]>(url),
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetPluginOAuthUrl = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'oauth-url', url],
|
||||
mutationFn: () => {
|
||||
return get<
|
||||
{
|
||||
authorization_url: string
|
||||
state: string
|
||||
context_id: string
|
||||
}>(url)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetPluginOAuthClientSchema = (
|
||||
url: string,
|
||||
) => {
|
||||
return useQuery({
|
||||
enabled: !!url,
|
||||
queryKey: [NAME_SPACE, 'oauth-client-schema', url],
|
||||
queryFn: () => get<{
|
||||
schema: FormSchema[]
|
||||
is_oauth_custom_client_enabled: boolean
|
||||
is_system_oauth_params_exists?: boolean
|
||||
client_params?: Record<string, any>
|
||||
redirect_uri?: string
|
||||
}>(url),
|
||||
staleTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidPluginOAuthClientSchema = (
|
||||
url: string,
|
||||
) => {
|
||||
return useInvalid([NAME_SPACE, 'oauth-client-schema', url])
|
||||
}
|
||||
|
||||
export const useSetPluginOAuthCustomClient = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (params: {
|
||||
client_params: Record<string, any>
|
||||
enable_oauth_custom_client: boolean
|
||||
}) => {
|
||||
return post<{ result: string }>(url, { body: params })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeletePluginOAuthCustomClient = (
|
||||
url: string,
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: () => {
|
||||
return del<{ result: string }>(url)
|
||||
},
|
||||
})
|
||||
}
|
||||
745
dify/web/service/use-plugins.ts
Normal file
745
dify/web/service/use-plugins.ts
Normal file
@@ -0,0 +1,745 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import type {
|
||||
FormOption,
|
||||
ModelProvider,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { fetchPluginInfoFromMarketPlace } from '@/service/plugins'
|
||||
import type {
|
||||
DebugInfo as DebugInfoTypes,
|
||||
Dependency,
|
||||
GitHubItemAndMarketPlaceDependency,
|
||||
InstallPackageResponse,
|
||||
InstallStatusResponse,
|
||||
InstalledLatestVersionResponse,
|
||||
InstalledPluginListWithTotalResponse,
|
||||
PackageDependency,
|
||||
Plugin,
|
||||
PluginDeclaration,
|
||||
PluginDetail,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginTask,
|
||||
PluginsFromMarketplaceByInfoResponse,
|
||||
PluginsFromMarketplaceResponse,
|
||||
ReferenceSetting,
|
||||
VersionInfo,
|
||||
VersionListResponse,
|
||||
uploadGitHubResponse,
|
||||
} from '@/app/components/plugins/types'
|
||||
import { TaskStatus } from '@/app/components/plugins/types'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import type {
|
||||
PluginsSearchParams,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
import { get, getMarketplace, post, postMarketplace } from './base'
|
||||
import type { MutateOptions, QueryOptions } from '@tanstack/react-query'
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import { useInvalidateAllBuiltInTools } from './use-tools'
|
||||
import useReferenceSetting from '@/app/components/plugins/plugin-page/use-reference-setting'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
|
||||
|
||||
const NAME_SPACE = 'plugins'
|
||||
|
||||
const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
|
||||
export const useCheckInstalled = ({
|
||||
pluginIds,
|
||||
enabled,
|
||||
}: {
|
||||
pluginIds: string[],
|
||||
enabled: boolean
|
||||
}) => {
|
||||
return useQuery<{ plugins: PluginDetail[] }>({
|
||||
queryKey: [NAME_SPACE, 'checkInstalled', pluginIds],
|
||||
queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', {
|
||||
body: {
|
||||
plugin_ids: pluginIds,
|
||||
},
|
||||
}),
|
||||
enabled,
|
||||
staleTime: 0, // always fresh
|
||||
})
|
||||
}
|
||||
|
||||
const useRecommendedMarketplacePluginsKey = [NAME_SPACE, 'recommendedMarketplacePlugins']
|
||||
export const useRecommendedMarketplacePlugins = ({
|
||||
collection = '__recommended-plugins-tools',
|
||||
enabled = true,
|
||||
limit = 15,
|
||||
}: {
|
||||
collection?: string
|
||||
enabled?: boolean
|
||||
limit?: number
|
||||
} = {}) => {
|
||||
return useQuery<Plugin[]>({
|
||||
queryKey: [...useRecommendedMarketplacePluginsKey, collection, limit],
|
||||
queryFn: async () => {
|
||||
const response = await postMarketplace<{ data: { plugins: Plugin[] } }>(
|
||||
`/collections/${collection}/plugins`,
|
||||
{
|
||||
body: {
|
||||
limit,
|
||||
},
|
||||
},
|
||||
)
|
||||
return response.data.plugins.map(plugin => getFormattedPlugin(plugin))
|
||||
},
|
||||
enabled,
|
||||
staleTime: 60 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export const useFeaturedToolsRecommendations = (enabled: boolean, limit = 15) => {
|
||||
const {
|
||||
data: plugins = [],
|
||||
isLoading,
|
||||
} = useRecommendedMarketplacePlugins({
|
||||
collection: '__recommended-plugins-tools',
|
||||
enabled,
|
||||
limit,
|
||||
})
|
||||
|
||||
return {
|
||||
plugins,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFeaturedTriggersRecommendations = (enabled: boolean, limit = 15) => {
|
||||
const {
|
||||
data: plugins = [],
|
||||
isLoading,
|
||||
} = useRecommendedMarketplacePlugins({
|
||||
collection: '__recommended-plugins-triggers',
|
||||
enabled,
|
||||
limit,
|
||||
})
|
||||
|
||||
return {
|
||||
plugins,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
|
||||
export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => {
|
||||
const fetchPlugins = async ({ pageParam = 1 }) => {
|
||||
const response = await get<InstalledPluginListWithTotalResponse>(
|
||||
`/workspaces/current/plugin/list?page=${pageParam}&page_size=${pageSize}`,
|
||||
)
|
||||
return response
|
||||
}
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
} = useInfiniteQuery({
|
||||
enabled: !disable,
|
||||
queryKey: useInstalledPluginListKey,
|
||||
queryFn: fetchPlugins,
|
||||
getNextPageParam: (lastPage, pages) => {
|
||||
const totalItems = lastPage.total
|
||||
const currentPage = pages.length
|
||||
const itemsLoaded = currentPage * pageSize
|
||||
|
||||
if (itemsLoaded >= totalItems)
|
||||
return
|
||||
|
||||
return currentPage + 1
|
||||
},
|
||||
initialPageParam: 1,
|
||||
})
|
||||
|
||||
const plugins = data?.pages.flatMap(page => page.plugins) ?? []
|
||||
const total = data?.pages[0].total ?? 0
|
||||
|
||||
return {
|
||||
data: disable ? undefined : {
|
||||
plugins,
|
||||
total,
|
||||
},
|
||||
isLastPage: !hasNextPage,
|
||||
loadNextPage: () => {
|
||||
fetchNextPage()
|
||||
},
|
||||
isLoading,
|
||||
isFetching: isFetchingNextPage,
|
||||
error,
|
||||
isSuccess,
|
||||
}
|
||||
}
|
||||
|
||||
export const useInstalledLatestVersion = (pluginIds: string[]) => {
|
||||
return useQuery<InstalledLatestVersionResponse>({
|
||||
queryKey: [NAME_SPACE, 'installedLatestVersion', pluginIds],
|
||||
queryFn: () => post<InstalledLatestVersionResponse>('/workspaces/current/plugin/list/latest-versions', {
|
||||
body: {
|
||||
plugin_ids: pluginIds,
|
||||
},
|
||||
}),
|
||||
enabled: !!pluginIds.length,
|
||||
initialData: pluginIds.length ? undefined : { versions: {} },
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateInstalledPluginList = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
|
||||
return () => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: useInstalledPluginListKey,
|
||||
})
|
||||
invalidateAllBuiltInTools()
|
||||
}
|
||||
}
|
||||
|
||||
export const useInstallPackageFromMarketPlace = (options?: MutateOptions<InstallPackageResponse, Error, string>) => {
|
||||
return useMutation({
|
||||
...options,
|
||||
mutationFn: (uniqueIdentifier: string) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', { body: { plugin_unique_identifiers: [uniqueIdentifier] } })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdatePackageFromMarketPlace = (options?: MutateOptions<InstallPackageResponse, Error, object>) => {
|
||||
return useMutation({
|
||||
...options,
|
||||
mutationFn: (body: object) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/upgrade/marketplace', {
|
||||
body,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginDeclarationFromMarketPlace = (pluginUniqueIdentifier: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'pluginDeclaration', pluginUniqueIdentifier],
|
||||
queryFn: () => get<{ manifest: PluginDeclaration }>('/workspaces/current/plugin/marketplace/pkg', { params: { plugin_unique_identifier: pluginUniqueIdentifier } }),
|
||||
enabled: !!pluginUniqueIdentifier,
|
||||
})
|
||||
}
|
||||
|
||||
export const useVersionListOfPlugin = (pluginID: string) => {
|
||||
return useQuery<{ data: VersionListResponse }>({
|
||||
enabled: !!pluginID,
|
||||
queryKey: [NAME_SPACE, 'versions', pluginID],
|
||||
queryFn: () => getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, { params: { page: 1, page_size: 100 } }),
|
||||
})
|
||||
}
|
||||
export const useInvalidateVersionListOfPlugin = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (pluginID: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: [NAME_SPACE, 'versions', pluginID] })
|
||||
}
|
||||
}
|
||||
|
||||
export const useInstallPackageFromLocal = () => {
|
||||
return useMutation({
|
||||
mutationFn: (uniqueIdentifier: string) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: { plugin_unique_identifiers: [uniqueIdentifier] },
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useInstallPackageFromGitHub = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ repoUrl, selectedVersion, selectedPackage, uniqueIdentifier }: {
|
||||
repoUrl: string
|
||||
selectedVersion: string
|
||||
selectedPackage: string
|
||||
uniqueIdentifier: string
|
||||
}) => {
|
||||
return post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
|
||||
body: {
|
||||
repo: repoUrl,
|
||||
version: selectedVersion,
|
||||
package: selectedPackage,
|
||||
plugin_unique_identifier: uniqueIdentifier,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUploadGitHub = (payload: {
|
||||
repo: string
|
||||
version: string
|
||||
package: string
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'uploadGitHub', payload],
|
||||
queryFn: () => post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
|
||||
body: payload,
|
||||
}),
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInstallOrUpdate = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: (res: InstallStatusResponse[]) => void
|
||||
}) => {
|
||||
const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: {
|
||||
payload: Dependency[],
|
||||
plugin: Plugin[],
|
||||
installedInfo: Record<string, VersionInfo>
|
||||
}) => {
|
||||
const { payload, plugin, installedInfo } = data
|
||||
|
||||
return Promise.all(payload.map(async (item, i) => {
|
||||
try {
|
||||
const orgAndName = `${plugin[i]?.org || plugin[i]?.author}/${plugin[i]?.name}`
|
||||
const installedPayload = installedInfo[orgAndName]
|
||||
const isInstalled = !!installedPayload
|
||||
let uniqueIdentifier = ''
|
||||
let taskId = ''
|
||||
let isFinishedInstallation = false
|
||||
|
||||
if (item.type === 'github') {
|
||||
const data = item as GitHubItemAndMarketPlaceDependency
|
||||
// From local bundle don't have data.value.github_plugin_unique_identifier
|
||||
uniqueIdentifier = data.value.github_plugin_unique_identifier!
|
||||
if (!uniqueIdentifier) {
|
||||
const { unique_identifier } = await post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
|
||||
body: {
|
||||
repo: data.value.repo!,
|
||||
version: data.value.release! || data.value.version!,
|
||||
package: data.value.packages! || data.value.package!,
|
||||
},
|
||||
})
|
||||
uniqueIdentifier = data.value.github_plugin_unique_identifier! || unique_identifier
|
||||
// has the same version, but not installed
|
||||
if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
|
||||
return {
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isInstalled) {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
|
||||
body: {
|
||||
repo: data.value.repo!,
|
||||
version: data.value.release! || data.value.version!,
|
||||
package: data.value.packages! || data.value.package!,
|
||||
plugin_unique_identifier: uniqueIdentifier,
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (item.type === 'marketplace') {
|
||||
const data = item as GitHubItemAndMarketPlaceDependency
|
||||
uniqueIdentifier = data.value.marketplace_plugin_unique_identifier! || plugin[i]?.plugin_id
|
||||
if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
|
||||
return {
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
if (!isInstalled) {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
|
||||
body: {
|
||||
plugin_unique_identifiers: [uniqueIdentifier],
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (item.type === 'package') {
|
||||
const data = item as PackageDependency
|
||||
uniqueIdentifier = data.value.unique_identifier
|
||||
if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
|
||||
return {
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
if (!isInstalled) {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: {
|
||||
plugin_unique_identifiers: [uniqueIdentifier],
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (isInstalled) {
|
||||
if (item.type === 'package') {
|
||||
await uninstallPlugin(installedPayload.installedId)
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: {
|
||||
plugin_unique_identifiers: [uniqueIdentifier],
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
else {
|
||||
const { task_id, all_installed } = await updatePackageFromMarketPlace({
|
||||
original_plugin_unique_identifier: installedPayload?.uniqueIdentifier,
|
||||
new_plugin_unique_identifier: uniqueIdentifier,
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (isFinishedInstallation) {
|
||||
return {
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
status: TaskStatus.running,
|
||||
taskId,
|
||||
uniqueIdentifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
catch (e) {
|
||||
return Promise.resolve({ status: TaskStatus.failed, taskId: '', uniqueIdentifier: '' })
|
||||
}
|
||||
}))
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDebugKey = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'debugKey'],
|
||||
queryFn: () => get<DebugInfoTypes>('/workspaces/current/plugin/debugging-key'),
|
||||
})
|
||||
}
|
||||
|
||||
const useReferenceSettingKey = [NAME_SPACE, 'referenceSettings']
|
||||
export const useReferenceSettings = () => {
|
||||
return useQuery({
|
||||
queryKey: useReferenceSettingKey,
|
||||
queryFn: () => get<ReferenceSetting>('/workspaces/current/plugin/preferences/fetch'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateReferenceSettings = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: useReferenceSettingKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useMutationReferenceSettings = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: ReferenceSetting) => {
|
||||
return post('/workspaces/current/plugin/preferences/change', { body: payload })
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
export const useRemoveAutoUpgrade = () => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: { plugin_id: string }) => {
|
||||
return post('/workspaces/current/plugin/preferences/autoupgrade/exclude', { body: payload })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useMutationPluginsFromMarketplace = () => {
|
||||
return useMutation({
|
||||
mutationFn: (pluginsSearchParams: PluginsSearchParams) => {
|
||||
const {
|
||||
query,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
category,
|
||||
tags,
|
||||
exclude,
|
||||
type,
|
||||
page = 1,
|
||||
pageSize = 40,
|
||||
} = pluginsSearchParams
|
||||
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
|
||||
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
|
||||
body: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
query,
|
||||
sort_by: sortBy,
|
||||
sort_order: sortOrder,
|
||||
category: category !== 'all' ? category : '',
|
||||
tags,
|
||||
exclude,
|
||||
type,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useFetchPluginsInMarketPlaceByIds = (unique_identifiers: string[], options?: QueryOptions<{ data: PluginsFromMarketplaceResponse }>) => {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers],
|
||||
queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', {
|
||||
body: {
|
||||
unique_identifiers,
|
||||
},
|
||||
}),
|
||||
enabled: unique_identifiers?.filter(i => !!i).length > 0,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useFetchPluginListOrBundleList = (pluginsSearchParams: PluginsSearchParams) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'fetchPluginListOrBundleList', pluginsSearchParams],
|
||||
queryFn: () => {
|
||||
const {
|
||||
query,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
category,
|
||||
tags,
|
||||
exclude,
|
||||
type,
|
||||
page = 1,
|
||||
pageSize = 40,
|
||||
} = pluginsSearchParams
|
||||
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
|
||||
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
|
||||
body: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
query,
|
||||
sort_by: sortBy,
|
||||
sort_order: sortOrder,
|
||||
category: category !== 'all' ? category : '',
|
||||
tags,
|
||||
exclude,
|
||||
type,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useFetchPluginsInMarketPlaceByInfo = (infos: Record<string, any>[]) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByInfo', infos],
|
||||
queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceByInfoResponse }>('/plugins/versions/batch', {
|
||||
body: {
|
||||
plugin_tuples: infos.map(info => ({
|
||||
org: info.organization,
|
||||
name: info.plugin,
|
||||
version: info.version,
|
||||
})),
|
||||
},
|
||||
}),
|
||||
enabled: infos?.filter(i => !!i).length > 0,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList']
|
||||
export const usePluginTaskList = (category?: PluginCategoryEnum | string) => {
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const {
|
||||
canManagement,
|
||||
} = useReferenceSetting()
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
const {
|
||||
data,
|
||||
isFetched,
|
||||
isRefetching,
|
||||
refetch,
|
||||
...rest
|
||||
} = useQuery({
|
||||
enabled: canManagement,
|
||||
queryKey: usePluginTaskListKey,
|
||||
queryFn: () => get<{ tasks: PluginTask[] }>('/workspaces/current/plugin/tasks?page=1&page_size=100'),
|
||||
refetchInterval: (lastQuery) => {
|
||||
const lastData = lastQuery.state.data
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
return taskDone ? false : 5000
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// After first fetch, refresh plugin list each time all tasks are done
|
||||
// Skip initialization period, because the query cache is not updated yet
|
||||
if (!initialized || isRefetching)
|
||||
return
|
||||
|
||||
const lastData = cloneDeep(data)
|
||||
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed)
|
||||
const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
|
||||
if (taskDone && lastData?.tasks.length && !taskAllFailed)
|
||||
refreshPluginList(category ? { category } as any : undefined, !category)
|
||||
}, [initialized, isRefetching, data, category, refreshPluginList])
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetched && !initialized)
|
||||
setInitialized(true)
|
||||
}, [isFetched, initialized])
|
||||
|
||||
const handleRefetch = useCallback(() => {
|
||||
refetch()
|
||||
}, [refetch])
|
||||
|
||||
return {
|
||||
data,
|
||||
pluginTasks: data?.tasks || [],
|
||||
isFetched,
|
||||
handleRefetch,
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
|
||||
export const useMutationClearTaskPlugin = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ taskId, pluginId }: { taskId: string; pluginId: string }) => {
|
||||
return post<{ success: boolean }>(`/workspaces/current/plugin/tasks/${taskId}/delete/${pluginId}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useMutationClearAllTaskPlugin = () => {
|
||||
return useMutation({
|
||||
mutationFn: () => {
|
||||
return post<{ success: boolean }>('/workspaces/current/plugin/tasks/delete_all')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginManifestInfo = (pluginUID: string) => {
|
||||
return useQuery({
|
||||
enabled: !!pluginUID,
|
||||
queryKey: [[NAME_SPACE, 'manifest', pluginUID]],
|
||||
queryFn: () => getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${pluginUID}`),
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'downloadPlugin', info],
|
||||
queryFn: () => getMarketplace<Blob>(`/plugins/${info.organization}/${info.pluginName}/${info.version}/download`),
|
||||
enabled: needDownload,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useMutationCheckDependencies = () => {
|
||||
return useMutation({
|
||||
mutationFn: (appId: string) => {
|
||||
return get<{ leaked_dependencies: Dependency[] }>(`/apps/imports/${appId}/check-dependencies`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useModelInList = (currentProvider?: ModelProvider, modelId?: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['modelInList', currentProvider?.provider, modelId],
|
||||
queryFn: async () => {
|
||||
if (!modelId || !currentProvider) return false
|
||||
try {
|
||||
const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${currentProvider?.provider}/models`)
|
||||
return !!modelId && !!modelsData.data.find(item => item.model === modelId)
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
enabled: !!modelId && !!currentProvider,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginInfo = (providerName?: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['pluginInfo', providerName],
|
||||
queryFn: async () => {
|
||||
if (!providerName) return null
|
||||
const parts = providerName.split('/')
|
||||
const org = parts[0]
|
||||
const name = parts[1]
|
||||
try {
|
||||
const response = await fetchPluginInfoFromMarketPlace({ org, name })
|
||||
return response.data.plugin.category === PluginCategoryEnum.model ? response.data.plugin : null
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
enabled: !!providerName,
|
||||
})
|
||||
}
|
||||
|
||||
export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type?: string, extra?: Record<string, any>) => {
|
||||
return useMutation({
|
||||
mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', {
|
||||
params: {
|
||||
plugin_id,
|
||||
provider,
|
||||
action,
|
||||
parameter,
|
||||
provider_type,
|
||||
...extra,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginReadme = ({ plugin_unique_identifier, language }: { plugin_unique_identifier: string, language?: string }) => {
|
||||
return useQuery({
|
||||
queryKey: ['pluginReadme', plugin_unique_identifier, language],
|
||||
queryFn: () => get<{ readme: string }>('/workspaces/current/plugin/readme', { params: { plugin_unique_identifier, language } }, { silent: true }),
|
||||
enabled: !!plugin_unique_identifier,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginReadmeAsset = ({ file_name, plugin_unique_identifier }: { file_name?: string, plugin_unique_identifier?: string }) => {
|
||||
const normalizedFileName = file_name?.replace(/(^\.\/_assets\/|^_assets\/)/, '')
|
||||
return useQuery({
|
||||
queryKey: ['pluginReadmeAsset', plugin_unique_identifier, file_name],
|
||||
queryFn: () => get<Blob>('/workspaces/current/plugin/asset', { params: { plugin_unique_identifier, file_name: normalizedFileName } }, { silent: true }),
|
||||
enabled: !!plugin_unique_identifier && !!file_name && /(^\.\/_assets|^_assets)/.test(file_name),
|
||||
})
|
||||
}
|
||||
41
dify/web/service/use-share.ts
Normal file
41
dify/web/service/use-share.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchAppInfo, fetchAppMeta, fetchAppParams, getAppAccessModeByAppCode } from './share'
|
||||
|
||||
const NAME_SPACE = 'webapp'
|
||||
|
||||
export const useGetWebAppAccessModeByCode = (code: string | null) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appAccessMode', code],
|
||||
queryFn: () => getAppAccessModeByAppCode(code!),
|
||||
enabled: !!code,
|
||||
staleTime: 0, // backend change the access mode may cause the logic error. Because /permission API is no cached.
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetWebAppInfo = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appInfo'],
|
||||
queryFn: () => {
|
||||
return fetchAppInfo()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetWebAppParams = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appParams'],
|
||||
queryFn: () => {
|
||||
return fetchAppParams(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetWebAppMeta = () => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appMeta'],
|
||||
queryFn: () => {
|
||||
return fetchAppMeta(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
36
dify/web/service/use-strategy.ts
Normal file
36
dify/web/service/use-strategy.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type {
|
||||
StrategyPluginDetail,
|
||||
} from '@/app/components/plugins/types'
|
||||
import { useInvalid } from './use-base'
|
||||
import type { QueryOptions } from '@tanstack/react-query'
|
||||
import {
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { fetchStrategyDetail, fetchStrategyList } from './strategy'
|
||||
|
||||
const NAME_SPACE = 'agent_strategy'
|
||||
|
||||
const useStrategyListKey = [NAME_SPACE, 'strategyList']
|
||||
export const useStrategyProviders = () => {
|
||||
return useQuery<StrategyPluginDetail[]>({
|
||||
queryKey: useStrategyListKey,
|
||||
queryFn: fetchStrategyList,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateStrategyProviders = () => {
|
||||
return useInvalid(useStrategyListKey)
|
||||
}
|
||||
|
||||
export const useStrategyProviderDetail = (agentProvider: string, options?: QueryOptions<StrategyPluginDetail>) => {
|
||||
return useQuery<StrategyPluginDetail>({
|
||||
...options,
|
||||
queryKey: [NAME_SPACE, 'detail', agentProvider],
|
||||
queryFn: () => fetchStrategyDetail(agentProvider),
|
||||
enabled: !!agentProvider,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateStrategyProviderDetail = (agentProvider: string) => {
|
||||
return useInvalid([NAME_SPACE, 'detail', agentProvider])
|
||||
}
|
||||
392
dify/web/service/use-tools.ts
Normal file
392
dify/web/service/use-tools.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
import { del, get, post, put } from './base'
|
||||
import type {
|
||||
Collection,
|
||||
MCPServerDetail,
|
||||
Tool,
|
||||
} from '@/app/components/tools/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import type { RAGRecommendedPlugins, ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import { useInvalid } from './use-base'
|
||||
import type { QueryKey } from '@tanstack/react-query'
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const NAME_SPACE = 'tools'
|
||||
|
||||
const useAllToolProvidersKey = [NAME_SPACE, 'allToolProviders']
|
||||
export const useAllToolProviders = (enabled = true) => {
|
||||
return useQuery<Collection[]>({
|
||||
queryKey: useAllToolProvidersKey,
|
||||
queryFn: () => get<Collection[]>('/workspaces/current/tool-providers'),
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllToolProviders = () => {
|
||||
return useInvalid(useAllToolProvidersKey)
|
||||
}
|
||||
|
||||
const useAllBuiltInToolsKey = [NAME_SPACE, 'builtIn']
|
||||
export const useAllBuiltInTools = () => {
|
||||
return useQuery<ToolWithProvider[]>({
|
||||
queryKey: useAllBuiltInToolsKey,
|
||||
queryFn: () => get<ToolWithProvider[]>('/workspaces/current/tools/builtin'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllBuiltInTools = () => {
|
||||
return useInvalid(useAllBuiltInToolsKey)
|
||||
}
|
||||
|
||||
const useAllCustomToolsKey = [NAME_SPACE, 'customTools']
|
||||
export const useAllCustomTools = () => {
|
||||
return useQuery<ToolWithProvider[]>({
|
||||
queryKey: useAllCustomToolsKey,
|
||||
queryFn: () => get<ToolWithProvider[]>('/workspaces/current/tools/api'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllCustomTools = () => {
|
||||
return useInvalid(useAllCustomToolsKey)
|
||||
}
|
||||
|
||||
const useAllWorkflowToolsKey = [NAME_SPACE, 'workflowTools']
|
||||
export const useAllWorkflowTools = () => {
|
||||
return useQuery<ToolWithProvider[]>({
|
||||
queryKey: useAllWorkflowToolsKey,
|
||||
queryFn: () => get<ToolWithProvider[]>('/workspaces/current/tools/workflow'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllWorkflowTools = () => {
|
||||
return useInvalid(useAllWorkflowToolsKey)
|
||||
}
|
||||
|
||||
const useAllMCPToolsKey = [NAME_SPACE, 'MCPTools']
|
||||
export const useAllMCPTools = () => {
|
||||
return useQuery<ToolWithProvider[]>({
|
||||
queryKey: useAllMCPToolsKey,
|
||||
queryFn: () => get<ToolWithProvider[]>('/workspaces/current/tools/mcp'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllMCPTools = () => {
|
||||
return useInvalid(useAllMCPToolsKey)
|
||||
}
|
||||
|
||||
const useInvalidToolsKeyMap: Record<string, QueryKey> = {
|
||||
[CollectionType.builtIn]: useAllBuiltInToolsKey,
|
||||
[CollectionType.custom]: useAllCustomToolsKey,
|
||||
[CollectionType.workflow]: useAllWorkflowToolsKey,
|
||||
[CollectionType.mcp]: useAllMCPToolsKey,
|
||||
}
|
||||
export const useInvalidToolsByType = (type?: CollectionType | string) => {
|
||||
const queryKey = type ? useInvalidToolsKeyMap[type] : undefined
|
||||
return useInvalid(queryKey)
|
||||
}
|
||||
|
||||
export const useCreateMCP = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create-mcp'],
|
||||
mutationFn: (payload: {
|
||||
name: string
|
||||
server_url: string
|
||||
icon_type: AppIconType
|
||||
icon: string
|
||||
icon_background?: string | null
|
||||
timeout?: number
|
||||
sse_read_timeout?: number
|
||||
headers?: Record<string, string>
|
||||
}) => {
|
||||
return post<ToolWithProvider>('workspaces/current/tool-provider/mcp', {
|
||||
body: {
|
||||
...payload,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateMCP = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-mcp'],
|
||||
mutationFn: (payload: {
|
||||
name: string
|
||||
server_url: string
|
||||
icon_type: AppIconType
|
||||
icon: string
|
||||
icon_background?: string | null
|
||||
provider_id: string
|
||||
timeout?: number
|
||||
sse_read_timeout?: number
|
||||
headers?: Record<string, string>
|
||||
}) => {
|
||||
return put('workspaces/current/tool-provider/mcp', {
|
||||
body: {
|
||||
...payload,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteMCP = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete-mcp'],
|
||||
mutationFn: (id: string) => {
|
||||
return del('/workspaces/current/tool-provider/mcp', {
|
||||
body: {
|
||||
provider_id: id,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
export const useAuthorizeMCP = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'authorize-mcp'],
|
||||
mutationFn: (payload: { provider_id: string; }) => {
|
||||
return post<{ result?: string; authorization_url?: string }>('/workspaces/current/tool-provider/mcp/auth', {
|
||||
body: payload,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateMCPAuthorizationToken = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'refresh-mcp-server-code'],
|
||||
mutationFn: (payload: { provider_id: string; authorization_code: string }) => {
|
||||
return get<MCPServerDetail>('/workspaces/current/tool-provider/mcp/token', {
|
||||
params: {
|
||||
...payload,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useMCPTools = (providerID: string) => {
|
||||
return useQuery({
|
||||
enabled: !!providerID,
|
||||
queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID],
|
||||
queryFn: () => get<{ tools: Tool[] }>(`/workspaces/current/tool-provider/mcp/tools/${providerID}`),
|
||||
})
|
||||
}
|
||||
export const useInvalidateMCPTools = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (providerID: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useUpdateMCPTools = () => {
|
||||
return useMutation({
|
||||
mutationFn: (providerID: string) => get<{ tools: Tool[] }>(`/workspaces/current/tool-provider/mcp/update/${providerID}`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useMCPServerDetail = (appID: string) => {
|
||||
return useQuery<MCPServerDetail>({
|
||||
queryKey: [NAME_SPACE, 'MCPServerDetail', appID],
|
||||
queryFn: () => get<MCPServerDetail>(`/apps/${appID}/server`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateMCPServerDetail = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (appID: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'MCPServerDetail', appID],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useCreateMCPServer = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create-mcp-server'],
|
||||
mutationFn: (payload: {
|
||||
appID: string
|
||||
description?: string
|
||||
parameters?: Record<string, string>
|
||||
}) => {
|
||||
const { appID, ...rest } = payload
|
||||
return post(`apps/${appID}/server`, {
|
||||
body: {
|
||||
...rest,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateMCPServer = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-mcp-server'],
|
||||
mutationFn: (payload: {
|
||||
appID: string
|
||||
id: string
|
||||
description?: string
|
||||
status?: string
|
||||
parameters?: Record<string, string>
|
||||
}) => {
|
||||
const { appID, ...rest } = payload
|
||||
return put(`apps/${appID}/server`, {
|
||||
body: {
|
||||
...rest,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useRefreshMCPServerCode = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'refresh-mcp-server-code'],
|
||||
mutationFn: (appID: string) => {
|
||||
return get<MCPServerDetail>(`apps/${appID}/server/refresh`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useBuiltinProviderInfo = (providerName: string) => {
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'builtin-provider-info', providerName],
|
||||
queryFn: () => get<Collection>(`/workspaces/current/tool-provider/builtin/${providerName}/info`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateBuiltinProviderInfo = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (providerName: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'builtin-provider-info', providerName],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useBuiltinTools = (providerName: string) => {
|
||||
return useQuery({
|
||||
enabled: !!providerName,
|
||||
queryKey: [NAME_SPACE, 'builtin-provider-tools', providerName],
|
||||
queryFn: () => get<Tool[]>(`/workspaces/current/tool-provider/builtin/${providerName}/tools`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateProviderCredentials = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-provider-credentials'],
|
||||
mutationFn: (payload: { providerName: string, credentials: Record<string, any> }) => {
|
||||
const { providerName, credentials } = payload
|
||||
return post(`/workspaces/current/tool-provider/builtin/${providerName}/update`, {
|
||||
body: {
|
||||
credentials,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
export const useRemoveProviderCredentials = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: () => void
|
||||
}) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'remove-provider-credentials'],
|
||||
mutationFn: (providerName: string) => {
|
||||
return post(`/workspaces/current/tool-provider/builtin/${providerName}/delete`, {
|
||||
body: {},
|
||||
})
|
||||
},
|
||||
onSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
const useRAGRecommendedPluginListKey = [NAME_SPACE, 'rag-recommended-plugins']
|
||||
|
||||
export const useRAGRecommendedPlugins = () => {
|
||||
return useQuery<RAGRecommendedPlugins>({
|
||||
queryKey: useRAGRecommendedPluginListKey,
|
||||
queryFn: () => get<RAGRecommendedPlugins>('/rag/pipelines/recommended-plugins'),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateRAGRecommendedPlugins = () => {
|
||||
return useInvalid(useRAGRecommendedPluginListKey)
|
||||
}
|
||||
|
||||
// App Triggers API hooks
|
||||
export type AppTrigger = {
|
||||
id: string
|
||||
trigger_type: 'trigger-webhook' | 'trigger-schedule' | 'trigger-plugin'
|
||||
title: string
|
||||
node_id: string
|
||||
provider_name: string
|
||||
icon: string
|
||||
status: 'enabled' | 'disabled' | 'unauthorized'
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export const useAppTriggers = (appId: string | undefined, options?: any) => {
|
||||
return useQuery<{ data: AppTrigger[] }>({
|
||||
queryKey: [NAME_SPACE, 'app-triggers', appId],
|
||||
queryFn: () => get<{ data: AppTrigger[] }>(`/apps/${appId}/triggers`),
|
||||
enabled: !!appId,
|
||||
...options, // Merge additional options while maintaining backward compatibility
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAppTriggers = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (appId: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'app-triggers', appId],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useUpdateTriggerStatus = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-trigger-status'],
|
||||
mutationFn: (payload: {
|
||||
appId: string
|
||||
triggerId: string
|
||||
enableTrigger: boolean
|
||||
}) => {
|
||||
const { appId, triggerId, enableTrigger } = payload
|
||||
return post<AppTrigger>(`/apps/${appId}/trigger-enable`, {
|
||||
body: {
|
||||
trigger_id: triggerId,
|
||||
enable_trigger: enableTrigger,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
320
dify/web/service/use-triggers.ts
Normal file
320
dify/web/service/use-triggers.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { del, get, post } from './base'
|
||||
import type {
|
||||
TriggerLogEntity,
|
||||
TriggerOAuthClientParams,
|
||||
TriggerOAuthConfig,
|
||||
TriggerProviderApiEntity,
|
||||
TriggerSubscription,
|
||||
TriggerSubscriptionBuilder,
|
||||
TriggerWithProvider,
|
||||
} from '@/app/components/workflow/block-selector/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { useInvalid } from './use-base'
|
||||
|
||||
const NAME_SPACE = 'triggers'
|
||||
|
||||
// Trigger Provider Service - Provider ID Format: plugin_id/provider_name
|
||||
|
||||
// Convert backend API response to frontend ToolWithProvider format
|
||||
const convertToTriggerWithProvider = (provider: TriggerProviderApiEntity): TriggerWithProvider => {
|
||||
return {
|
||||
// Collection fields
|
||||
id: provider.plugin_id || provider.name,
|
||||
name: provider.name,
|
||||
author: provider.author,
|
||||
description: provider.description,
|
||||
icon: provider.icon || '',
|
||||
label: provider.label,
|
||||
type: CollectionType.trigger,
|
||||
team_credentials: {},
|
||||
is_team_authorization: false,
|
||||
allow_delete: false,
|
||||
labels: provider.tags || [],
|
||||
plugin_id: provider.plugin_id,
|
||||
plugin_unique_identifier: provider.plugin_unique_identifier || '',
|
||||
events: provider.events.map(event => ({
|
||||
name: event.name,
|
||||
author: provider.author,
|
||||
label: event.identity.label,
|
||||
description: event.description,
|
||||
parameters: event.parameters.map(param => ({
|
||||
name: param.name,
|
||||
label: param.label,
|
||||
human_description: param.description || param.label,
|
||||
type: param.type,
|
||||
form: param.type,
|
||||
llm_description: JSON.stringify(param.description || {}),
|
||||
required: param.required || false,
|
||||
default: param.default || '',
|
||||
options: param.options?.map(option => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
})) || [],
|
||||
multiple: param.multiple || false,
|
||||
})),
|
||||
labels: provider.tags || [],
|
||||
output_schema: event.output_schema || {},
|
||||
})),
|
||||
|
||||
// Trigger-specific schema fields
|
||||
subscription_constructor: provider.subscription_constructor,
|
||||
subscription_schema: provider.subscription_schema,
|
||||
supported_creation_methods: provider.supported_creation_methods,
|
||||
|
||||
meta: {
|
||||
version: '1.0',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const useAllTriggerPlugins = (enabled = true) => {
|
||||
return useQuery<TriggerWithProvider[]>({
|
||||
queryKey: [NAME_SPACE, 'all'],
|
||||
queryFn: async () => {
|
||||
const response = await get<TriggerProviderApiEntity[]>('/workspaces/current/triggers')
|
||||
return response.map(convertToTriggerWithProvider)
|
||||
},
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useTriggerPluginsByType = (triggerType: string, enabled = true) => {
|
||||
return useQuery<TriggerWithProvider[]>({
|
||||
queryKey: [NAME_SPACE, 'byType', triggerType],
|
||||
queryFn: async () => {
|
||||
const response = await get<TriggerProviderApiEntity[]>(`/workspaces/current/triggers?type=${triggerType}`)
|
||||
return response.map(convertToTriggerWithProvider)
|
||||
},
|
||||
enabled: enabled && !!triggerType,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAllTriggerPlugins = () => {
|
||||
return useInvalid([NAME_SPACE, 'all'])
|
||||
}
|
||||
|
||||
// ===== Trigger Subscriptions Management =====
|
||||
|
||||
export const useTriggerProviderInfo = (provider: string, enabled = true) => {
|
||||
return useQuery<TriggerProviderApiEntity>({
|
||||
queryKey: [NAME_SPACE, 'provider-info', provider],
|
||||
queryFn: () => get<TriggerProviderApiEntity>(`/workspaces/current/trigger-provider/${provider}/info`),
|
||||
enabled: enabled && !!provider,
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useTriggerSubscriptions = (provider: string, enabled = true) => {
|
||||
return useQuery<TriggerSubscription[]>({
|
||||
queryKey: [NAME_SPACE, 'list-subscriptions', provider],
|
||||
queryFn: () => get<TriggerSubscription[]>(`/workspaces/current/trigger-provider/${provider}/subscriptions/list`),
|
||||
enabled: enabled && !!provider,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateTriggerSubscriptions = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (provider: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'subscriptions', provider],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useCreateTriggerSubscriptionBuilder = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'create-subscription-builder'],
|
||||
mutationFn: (payload: {
|
||||
provider: string
|
||||
credential_type?: string
|
||||
}) => {
|
||||
const { provider, ...body } = payload
|
||||
return post<{ subscription_builder: TriggerSubscriptionBuilder }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/create`,
|
||||
{ body },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateTriggerSubscriptionBuilder = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update-subscription-builder'],
|
||||
mutationFn: (payload: {
|
||||
provider: string
|
||||
subscriptionBuilderId: string
|
||||
name?: string
|
||||
properties?: Record<string, any>
|
||||
parameters?: Record<string, any>
|
||||
credentials?: Record<string, any>
|
||||
}) => {
|
||||
const { provider, subscriptionBuilderId, ...body } = payload
|
||||
return post<TriggerSubscriptionBuilder>(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/update/${subscriptionBuilderId}`,
|
||||
{ body },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useVerifyTriggerSubscriptionBuilder = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'verify-subscription-builder'],
|
||||
mutationFn: (payload: {
|
||||
provider: string
|
||||
subscriptionBuilderId: string
|
||||
credentials?: Record<string, any>
|
||||
}) => {
|
||||
const { provider, subscriptionBuilderId, ...body } = payload
|
||||
return post<{ verified: boolean }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify/${subscriptionBuilderId}`,
|
||||
{ body },
|
||||
{ silent: true },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export type BuildTriggerSubscriptionPayload = {
|
||||
provider: string
|
||||
subscriptionBuilderId: string
|
||||
name?: string
|
||||
parameters?: Record<string, any>
|
||||
}
|
||||
|
||||
export const useBuildTriggerSubscription = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'build-subscription'],
|
||||
mutationFn: (payload: BuildTriggerSubscriptionPayload) => {
|
||||
const { provider, subscriptionBuilderId, ...body } = payload
|
||||
return post(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/build/${subscriptionBuilderId}`,
|
||||
{ body },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteTriggerSubscription = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete-subscription'],
|
||||
mutationFn: (subscriptionId: string) => {
|
||||
return post<{ result: string }>(
|
||||
`/workspaces/current/trigger-provider/${subscriptionId}/subscriptions/delete`,
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useTriggerSubscriptionBuilderLogs = (
|
||||
provider: string,
|
||||
subscriptionBuilderId: string,
|
||||
options: {
|
||||
enabled?: boolean
|
||||
refetchInterval?: number | false
|
||||
} = {},
|
||||
) => {
|
||||
const { enabled = true, refetchInterval = false } = options
|
||||
|
||||
return useQuery<{ logs: TriggerLogEntity[] }>({
|
||||
queryKey: [NAME_SPACE, 'subscription-builder-logs', provider, subscriptionBuilderId],
|
||||
queryFn: () => get(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/builder/logs/${subscriptionBuilderId}`,
|
||||
),
|
||||
enabled: enabled && !!provider && !!subscriptionBuilderId,
|
||||
refetchInterval,
|
||||
})
|
||||
}
|
||||
|
||||
// ===== OAuth Management =====
|
||||
export const useTriggerOAuthConfig = (provider: string, enabled = true) => {
|
||||
return useQuery<TriggerOAuthConfig>({
|
||||
queryKey: [NAME_SPACE, 'oauth-config', provider],
|
||||
queryFn: () => get<TriggerOAuthConfig>(`/workspaces/current/trigger-provider/${provider}/oauth/client`),
|
||||
enabled: enabled && !!provider,
|
||||
})
|
||||
}
|
||||
|
||||
export type ConfigureTriggerOAuthPayload = {
|
||||
provider: string
|
||||
client_params?: TriggerOAuthClientParams
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export const useConfigureTriggerOAuth = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'configure-oauth'],
|
||||
mutationFn: (payload: ConfigureTriggerOAuthPayload) => {
|
||||
const { provider, ...body } = payload
|
||||
return post<{ result: string }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/oauth/client`,
|
||||
{ body },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteTriggerOAuth = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete-oauth'],
|
||||
mutationFn: (provider: string) => {
|
||||
return del<{ result: string }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/oauth/client`,
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useInitiateTriggerOAuth = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'initiate-oauth'],
|
||||
mutationFn: (provider: string) => {
|
||||
return get<{ authorization_url: string; subscription_builder: TriggerSubscriptionBuilder }>(
|
||||
`/workspaces/current/trigger-provider/${provider}/subscriptions/oauth/authorize`,
|
||||
{},
|
||||
{ silent: true },
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Dynamic Options Support =====
|
||||
export const useTriggerPluginDynamicOptions = (payload: {
|
||||
plugin_id: string
|
||||
provider: string
|
||||
action: string
|
||||
parameter: string
|
||||
credential_id: string
|
||||
extra?: Record<string, any>
|
||||
}, enabled = true) => {
|
||||
return useQuery<{ options: Array<{ value: string; label: any }> }>({
|
||||
queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.credential_id, payload.extra],
|
||||
queryFn: () => get<{ options: Array<{ value: string; label: any }> }>(
|
||||
'/workspaces/current/plugin/parameters/dynamic-options',
|
||||
{
|
||||
params: {
|
||||
...payload,
|
||||
provider_type: 'trigger', // Add required provider_type parameter
|
||||
},
|
||||
},
|
||||
{ silent: true },
|
||||
),
|
||||
enabled: enabled && !!payload.plugin_id && !!payload.provider && !!payload.action && !!payload.parameter && !!payload.credential_id,
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Cache Invalidation Helpers =====
|
||||
|
||||
export const useInvalidateTriggerOAuthConfig = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (provider: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [NAME_SPACE, 'oauth-config', provider],
|
||||
})
|
||||
}
|
||||
}
|
||||
219
dify/web/service/use-workflow.ts
Normal file
219
dify/web/service/use-workflow.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { del, get, patch, post, put } from './base'
|
||||
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import type {
|
||||
FetchWorkflowDraftPageParams,
|
||||
FetchWorkflowDraftPageResponse,
|
||||
FetchWorkflowDraftResponse,
|
||||
NodeTracing,
|
||||
PublishWorkflowParams,
|
||||
UpdateWorkflowParams,
|
||||
VarInInspect,
|
||||
WorkflowConfigResponse,
|
||||
} from '@/types/workflow'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import { useInvalid, useReset } from './use-base'
|
||||
import type { FlowType } from '@/types/common'
|
||||
import { getFlowPrefix } from './utils'
|
||||
|
||||
const NAME_SPACE = 'workflow'
|
||||
|
||||
export const useAppWorkflow = (appID: string) => {
|
||||
return useQuery<FetchWorkflowDraftResponse>({
|
||||
enabled: !!appID,
|
||||
queryKey: [NAME_SPACE, 'publish', appID],
|
||||
queryFn: () => get<FetchWorkflowDraftResponse>(`/apps/${appID}/workflows/publish`),
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateAppWorkflow = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return (appID: string) => {
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: [NAME_SPACE, 'publish', appID],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const useWorkflowConfig = <T = WorkflowConfigResponse>(url: string, onSuccess: (v: T) => void) => {
|
||||
return useQuery({
|
||||
enabled: !!url,
|
||||
queryKey: [NAME_SPACE, 'config', url],
|
||||
staleTime: 0,
|
||||
queryFn: async () => {
|
||||
const data = await get<T>(url)
|
||||
onSuccess(data)
|
||||
return data
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const WorkflowVersionHistoryKey = [NAME_SPACE, 'versionHistory']
|
||||
|
||||
export const useWorkflowVersionHistory = (params: FetchWorkflowDraftPageParams) => {
|
||||
const { url, initialPage, limit, userId, namedOnly } = params
|
||||
return useInfiniteQuery({
|
||||
enabled: !!url,
|
||||
queryKey: [...WorkflowVersionHistoryKey, url, initialPage, limit, userId, namedOnly],
|
||||
queryFn: ({ pageParam = 1 }) => get<FetchWorkflowDraftPageResponse>(url, {
|
||||
params: {
|
||||
page: pageParam,
|
||||
limit,
|
||||
user_id: userId || '',
|
||||
named_only: !!namedOnly,
|
||||
},
|
||||
}),
|
||||
getNextPageParam: lastPage => lastPage.has_more ? lastPage.page + 1 : null,
|
||||
initialPageParam: initialPage,
|
||||
})
|
||||
}
|
||||
|
||||
export const useResetWorkflowVersionHistory = () => {
|
||||
return useReset([...WorkflowVersionHistoryKey])
|
||||
}
|
||||
|
||||
export const useUpdateWorkflow = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'update'],
|
||||
mutationFn: (params: UpdateWorkflowParams) => patch(params.url, {
|
||||
body: {
|
||||
marked_name: params.title,
|
||||
marked_comment: params.releaseNotes,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteWorkflow = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'delete'],
|
||||
mutationFn: (url: string) => del(url),
|
||||
})
|
||||
}
|
||||
|
||||
export const usePublishWorkflow = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'publish'],
|
||||
mutationFn: (params: PublishWorkflowParams) => post<CommonResponse & { created_at: number }>(params.url, {
|
||||
body: {
|
||||
marked_name: params.title,
|
||||
marked_comment: params.releaseNotes,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const useLastRunKey = [NAME_SPACE, 'last-run']
|
||||
export const useLastRun = (flowType: FlowType, flowId: string, nodeId: string, enabled: boolean) => {
|
||||
return useQuery<NodeTracing>({
|
||||
enabled,
|
||||
queryKey: [...useLastRunKey, flowType, flowId, nodeId],
|
||||
queryFn: async () => {
|
||||
return get(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/last-run`, {}, {
|
||||
silent: true,
|
||||
})
|
||||
},
|
||||
retry: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidLastRun = (flowType: FlowType, flowId: string, nodeId: string) => {
|
||||
return useInvalid([...useLastRunKey, flowType, flowId, nodeId])
|
||||
}
|
||||
|
||||
// Rerun workflow or change the version of workflow
|
||||
export const useInvalidAllLastRun = (flowType?: FlowType, flowId?: string) => {
|
||||
return useInvalid([NAME_SPACE, flowType, 'last-run', flowId])
|
||||
}
|
||||
|
||||
export const useConversationVarValues = (flowType?: FlowType, flowId?: string) => {
|
||||
return useQuery({
|
||||
enabled: !!flowId,
|
||||
queryKey: [NAME_SPACE, flowType, 'conversation var values', flowId],
|
||||
queryFn: async () => {
|
||||
const { items } = (await get(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/conversation-variables`)) as { items: VarInInspect[] }
|
||||
return items
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateConversationVarValues = (flowType: FlowType, flowId: string) => {
|
||||
return useInvalid([NAME_SPACE, flowType, 'conversation var values', flowId])
|
||||
}
|
||||
|
||||
export const useResetConversationVar = (flowType: FlowType, flowId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, flowType, 'reset conversation var', flowId],
|
||||
mutationFn: async (varId: string) => {
|
||||
return put(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/variables/${varId}/reset`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useResetToLastRunValue = (flowType: FlowType, flowId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, flowType, 'reset to last run value', flowId],
|
||||
mutationFn: async (varId: string): Promise<{ value: any }> => {
|
||||
return put(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/variables/${varId}/reset`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useSysVarValuesKey = [NAME_SPACE, 'sys-variable']
|
||||
export const useSysVarValues = (flowType?: FlowType, flowId?: string) => {
|
||||
return useQuery({
|
||||
enabled: !!flowId,
|
||||
queryKey: [NAME_SPACE, flowType, 'sys var values', flowId],
|
||||
queryFn: async () => {
|
||||
const { items } = (await get(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/system-variables`)) as { items: VarInInspect[] }
|
||||
return items
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useInvalidateSysVarValues = (flowType: FlowType, flowId: string) => {
|
||||
return useInvalid([NAME_SPACE, flowType, 'sys var values', flowId])
|
||||
}
|
||||
|
||||
export const useDeleteAllInspectorVars = (flowType: FlowType, flowId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, flowType, 'delete all inspector vars', flowId],
|
||||
mutationFn: async () => {
|
||||
return del(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/variables`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteNodeInspectorVars = (flowType: FlowType, flowId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, flowType, 'delete node inspector vars', flowId],
|
||||
mutationFn: async (nodeId: string) => {
|
||||
return del(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/variables`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteInspectVar = (flowType: FlowType, flowId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, flowType, 'delete inspector var', flowId],
|
||||
mutationFn: async (varId: string) => {
|
||||
return del(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/variables/${varId}`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// edit the name or value of the inspector var
|
||||
export const useEditInspectorVar = (flowType: FlowType, flowId: string) => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, flowType, 'edit inspector var', flowId],
|
||||
mutationFn: async ({ varId, ...rest }: {
|
||||
varId: string
|
||||
name?: string
|
||||
value?: any
|
||||
}) => {
|
||||
return patch(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/variables/${varId}`, {
|
||||
body: rest,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
170
dify/web/service/utils.spec.ts
Normal file
170
dify/web/service/utils.spec.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Test suite for service utility functions
|
||||
*
|
||||
* This module provides utilities for working with different flow types in the application.
|
||||
* Flow types determine the API endpoint prefix used for various operations.
|
||||
*
|
||||
* Key concepts:
|
||||
* - FlowType.appFlow: Standard application workflows (prefix: 'apps')
|
||||
* - FlowType.ragPipeline: RAG (Retrieval-Augmented Generation) pipelines (prefix: 'rag/pipelines')
|
||||
*
|
||||
* The getFlowPrefix function maps flow types to their corresponding API path prefixes,
|
||||
* with a fallback to 'apps' for undefined or unknown flow types.
|
||||
*/
|
||||
import { flowPrefixMap, getFlowPrefix } from './utils'
|
||||
import { FlowType } from '@/types/common'
|
||||
|
||||
describe('Service Utils', () => {
|
||||
describe('flowPrefixMap', () => {
|
||||
/**
|
||||
* Test that the flowPrefixMap object contains the expected mappings
|
||||
* This ensures the mapping configuration is correct
|
||||
*/
|
||||
it('should have correct flow type to prefix mappings', () => {
|
||||
expect(flowPrefixMap[FlowType.appFlow]).toBe('apps')
|
||||
expect(flowPrefixMap[FlowType.ragPipeline]).toBe('rag/pipelines')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test that the map only contains the expected flow types
|
||||
* This helps catch unintended additions to the mapping
|
||||
*/
|
||||
it('should contain exactly two flow type mappings', () => {
|
||||
const keys = Object.keys(flowPrefixMap)
|
||||
expect(keys).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFlowPrefix', () => {
|
||||
/**
|
||||
* Test that appFlow type returns the correct prefix
|
||||
* This is the most common flow type for standard application workflows
|
||||
*/
|
||||
it('should return "apps" for appFlow type', () => {
|
||||
const result = getFlowPrefix(FlowType.appFlow)
|
||||
expect(result).toBe('apps')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test that ragPipeline type returns the correct prefix
|
||||
* RAG pipelines have a different API structure with nested paths
|
||||
*/
|
||||
it('should return "rag/pipelines" for ragPipeline type', () => {
|
||||
const result = getFlowPrefix(FlowType.ragPipeline)
|
||||
expect(result).toBe('rag/pipelines')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test fallback behavior when no flow type is provided
|
||||
* Should default to 'apps' prefix for backward compatibility
|
||||
*/
|
||||
it('should return "apps" when flow type is undefined', () => {
|
||||
const result = getFlowPrefix(undefined)
|
||||
expect(result).toBe('apps')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test fallback behavior for unknown flow types
|
||||
* Any unrecognized flow type should default to 'apps'
|
||||
*/
|
||||
it('should return "apps" for unknown flow type', () => {
|
||||
// Cast to FlowType to test the fallback behavior
|
||||
const unknownType = 'unknown' as FlowType
|
||||
const result = getFlowPrefix(unknownType)
|
||||
expect(result).toBe('apps')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test that the function handles null gracefully
|
||||
* Null should be treated the same as undefined
|
||||
*/
|
||||
it('should return "apps" when flow type is null', () => {
|
||||
const result = getFlowPrefix(null as any)
|
||||
expect(result).toBe('apps')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test consistency with flowPrefixMap
|
||||
* The function should return the same values as direct map access
|
||||
*/
|
||||
it('should return values consistent with flowPrefixMap', () => {
|
||||
expect(getFlowPrefix(FlowType.appFlow)).toBe(flowPrefixMap[FlowType.appFlow])
|
||||
expect(getFlowPrefix(FlowType.ragPipeline)).toBe(flowPrefixMap[FlowType.ragPipeline])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Integration scenarios', () => {
|
||||
/**
|
||||
* Test typical usage pattern in API path construction
|
||||
* This demonstrates how the function is used in real application code
|
||||
*/
|
||||
it('should construct correct API paths for different flow types', () => {
|
||||
const appId = '123'
|
||||
|
||||
// App flow path construction
|
||||
const appFlowPath = `/${getFlowPrefix(FlowType.appFlow)}/${appId}`
|
||||
expect(appFlowPath).toBe('/apps/123')
|
||||
|
||||
// RAG pipeline path construction
|
||||
const ragPipelinePath = `/${getFlowPrefix(FlowType.ragPipeline)}/${appId}`
|
||||
expect(ragPipelinePath).toBe('/rag/pipelines/123')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test that the function can be used in conditional logic
|
||||
* Common pattern for determining which API endpoint to use
|
||||
*/
|
||||
it('should support conditional API routing logic', () => {
|
||||
const determineEndpoint = (flowType?: FlowType, resourceId?: string) => {
|
||||
const prefix = getFlowPrefix(flowType)
|
||||
return `/${prefix}/${resourceId || 'default'}`
|
||||
}
|
||||
|
||||
expect(determineEndpoint(FlowType.appFlow, 'app-1')).toBe('/apps/app-1')
|
||||
expect(determineEndpoint(FlowType.ragPipeline, 'pipeline-1')).toBe('/rag/pipelines/pipeline-1')
|
||||
expect(determineEndpoint(undefined, 'fallback')).toBe('/apps/fallback')
|
||||
})
|
||||
|
||||
/**
|
||||
* Test behavior with empty string flow type
|
||||
* Empty strings should fall back to default
|
||||
*/
|
||||
it('should handle empty string as flow type', () => {
|
||||
const result = getFlowPrefix('' as any)
|
||||
expect(result).toBe('apps')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Type safety', () => {
|
||||
/**
|
||||
* Test that all FlowType enum values are handled
|
||||
* This ensures we don't miss any flow types in the mapping
|
||||
*/
|
||||
it('should handle all FlowType enum values', () => {
|
||||
// Get all enum values
|
||||
const flowTypes = Object.values(FlowType)
|
||||
|
||||
// Each flow type should return a valid prefix
|
||||
flowTypes.forEach((flowType) => {
|
||||
const prefix = getFlowPrefix(flowType)
|
||||
expect(prefix).toBeTruthy()
|
||||
expect(typeof prefix).toBe('string')
|
||||
expect(prefix.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Test that returned prefixes are valid path segments
|
||||
* Prefixes should not contain leading/trailing slashes or invalid characters
|
||||
*/
|
||||
it('should return valid path segments without leading/trailing slashes', () => {
|
||||
const appFlowPrefix = getFlowPrefix(FlowType.appFlow)
|
||||
const ragPipelinePrefix = getFlowPrefix(FlowType.ragPipeline)
|
||||
|
||||
expect(appFlowPrefix).not.toMatch(/^\//)
|
||||
expect(appFlowPrefix).not.toMatch(/\/$/)
|
||||
expect(ragPipelinePrefix).not.toMatch(/^\//)
|
||||
expect(ragPipelinePrefix).not.toMatch(/\/$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
10
dify/web/service/utils.ts
Normal file
10
dify/web/service/utils.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FlowType } from '@/types/common'
|
||||
|
||||
export const flowPrefixMap = {
|
||||
[FlowType.appFlow]: 'apps',
|
||||
[FlowType.ragPipeline]: 'rag/pipelines',
|
||||
}
|
||||
|
||||
export const getFlowPrefix = (type?: FlowType) => {
|
||||
return flowPrefixMap[type!] || flowPrefixMap[FlowType.appFlow]
|
||||
}
|
||||
50
dify/web/service/webapp-auth.ts
Normal file
50
dify/web/service/webapp-auth.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ACCESS_TOKEN_LOCAL_STORAGE_NAME, PASSPORT_LOCAL_STORAGE_NAME } from '@/config'
|
||||
import { getPublic, postPublic } from './base'
|
||||
|
||||
export function setWebAppAccessToken(token: string) {
|
||||
localStorage.setItem(ACCESS_TOKEN_LOCAL_STORAGE_NAME, token)
|
||||
}
|
||||
|
||||
export function setWebAppPassport(shareCode: string, token: string) {
|
||||
localStorage.setItem(PASSPORT_LOCAL_STORAGE_NAME(shareCode), token)
|
||||
}
|
||||
|
||||
export function getWebAppAccessToken() {
|
||||
return localStorage.getItem(ACCESS_TOKEN_LOCAL_STORAGE_NAME) || ''
|
||||
}
|
||||
|
||||
export function getWebAppPassport(shareCode: string) {
|
||||
return localStorage.getItem(PASSPORT_LOCAL_STORAGE_NAME(shareCode)) || ''
|
||||
}
|
||||
|
||||
export function clearWebAppAccessToken() {
|
||||
localStorage.removeItem(ACCESS_TOKEN_LOCAL_STORAGE_NAME)
|
||||
}
|
||||
|
||||
export function clearWebAppPassport(shareCode: string) {
|
||||
localStorage.removeItem(PASSPORT_LOCAL_STORAGE_NAME(shareCode))
|
||||
}
|
||||
|
||||
type isWebAppLogin = {
|
||||
logged_in: boolean
|
||||
app_logged_in: boolean
|
||||
}
|
||||
|
||||
export async function webAppLoginStatus(shareCode: string, userId?: string) {
|
||||
// always need to check login to prevent passport from being outdated
|
||||
// check remotely, the access token could be in cookie (enterprise SSO redirected with https)
|
||||
const params = new URLSearchParams({ app_code: shareCode })
|
||||
if (userId)
|
||||
params.append('user_id', userId)
|
||||
const { logged_in, app_logged_in } = await getPublic<isWebAppLogin>(`/login/status?${params.toString()}`)
|
||||
return {
|
||||
userLoggedIn: logged_in,
|
||||
appLoggedIn: app_logged_in,
|
||||
}
|
||||
}
|
||||
|
||||
export async function webAppLogout(shareCode: string) {
|
||||
clearWebAppAccessToken()
|
||||
clearWebAppPassport(shareCode)
|
||||
await postPublic('/logout')
|
||||
}
|
||||
152
dify/web/service/workflow-payload.ts
Normal file
152
dify/web/service/workflow-payload.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { produce } from 'immer'
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types'
|
||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
|
||||
export type TriggerPluginNodePayload = {
|
||||
title: string
|
||||
desc: string
|
||||
plugin_id: string
|
||||
provider_id: string
|
||||
event_name: string
|
||||
subscription_id: string
|
||||
plugin_unique_identifier: string
|
||||
event_parameters: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type WorkflowDraftSyncParams = Pick<
|
||||
FetchWorkflowDraftResponse,
|
||||
'graph' | 'features' | 'environment_variables' | 'conversation_variables'
|
||||
>
|
||||
|
||||
const removeTempProperties = (data: Record<string, unknown>): void => {
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (key.startsWith('_'))
|
||||
delete data[key]
|
||||
})
|
||||
}
|
||||
|
||||
type TriggerParameterSchema = Record<string, unknown>
|
||||
|
||||
type TriggerPluginHydratePayload = (PluginTriggerNodeType & {
|
||||
paramSchemas?: TriggerParameterSchema[]
|
||||
parameters_schema?: TriggerParameterSchema[]
|
||||
})
|
||||
|
||||
const sanitizeTriggerPluginNode = (node: Node<TriggerPluginNodePayload>): Node<TriggerPluginNodePayload> => {
|
||||
const data = node.data
|
||||
|
||||
if (!data || data.type !== BlockEnum.TriggerPlugin)
|
||||
return node
|
||||
|
||||
const sanitizedData: TriggerPluginNodePayload & { type: BlockEnum.TriggerPlugin } = {
|
||||
type: BlockEnum.TriggerPlugin,
|
||||
title: data.title ?? '',
|
||||
desc: data.desc ?? '',
|
||||
plugin_id: data.plugin_id ?? '',
|
||||
provider_id: data.provider_id ?? '',
|
||||
event_name: data.event_name ?? '',
|
||||
subscription_id: data.subscription_id ?? '',
|
||||
plugin_unique_identifier: data.plugin_unique_identifier ?? '',
|
||||
event_parameters: (typeof data.event_parameters === 'object' && data.event_parameters !== null)
|
||||
? data.event_parameters as Record<string, unknown>
|
||||
: {},
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: sanitizedData,
|
||||
}
|
||||
}
|
||||
|
||||
export const sanitizeWorkflowDraftPayload = (params: WorkflowDraftSyncParams): WorkflowDraftSyncParams => {
|
||||
const { graph } = params
|
||||
|
||||
if (!graph?.nodes?.length)
|
||||
return params
|
||||
|
||||
const sanitizedNodes = graph.nodes.map(node => sanitizeTriggerPluginNode(node as Node<TriggerPluginNodePayload>))
|
||||
|
||||
return {
|
||||
...params,
|
||||
graph: {
|
||||
...graph,
|
||||
nodes: sanitizedNodes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const isTriggerPluginNode = (node: Node): node is Node<TriggerPluginHydratePayload> => {
|
||||
const data = node.data as unknown
|
||||
|
||||
if (!data || typeof data !== 'object')
|
||||
return false
|
||||
|
||||
const payload = data as Partial<TriggerPluginHydratePayload> & { type?: BlockEnum }
|
||||
|
||||
if (payload.type !== BlockEnum.TriggerPlugin)
|
||||
return false
|
||||
|
||||
return 'event_parameters' in payload
|
||||
}
|
||||
|
||||
const hydrateTriggerPluginNode = (node: Node): Node => {
|
||||
if (!isTriggerPluginNode(node))
|
||||
return node
|
||||
|
||||
const typedNode = node as Node<TriggerPluginHydratePayload>
|
||||
const data = typedNode.data
|
||||
const eventParameters = data.event_parameters ?? {}
|
||||
const parametersSchema = data.parameters_schema ?? data.paramSchemas ?? []
|
||||
const config = data.config ?? eventParameters ?? {}
|
||||
|
||||
const nextData: typeof data = {
|
||||
...data,
|
||||
config,
|
||||
paramSchemas: data.paramSchemas ?? parametersSchema,
|
||||
parameters_schema: parametersSchema,
|
||||
}
|
||||
|
||||
return {
|
||||
...typedNode,
|
||||
data: nextData,
|
||||
}
|
||||
}
|
||||
|
||||
export const hydrateWorkflowDraftResponse = (draft: FetchWorkflowDraftResponse): FetchWorkflowDraftResponse => {
|
||||
return produce(draft, (mutableDraft) => {
|
||||
if (!mutableDraft?.graph)
|
||||
return
|
||||
|
||||
if (mutableDraft.graph.nodes) {
|
||||
mutableDraft.graph.nodes = mutableDraft.graph.nodes
|
||||
.filter((node: Node) => !node.data?._isTempNode)
|
||||
.map((node: Node) => {
|
||||
if (node.data)
|
||||
removeTempProperties(node.data as Record<string, unknown>)
|
||||
|
||||
return hydrateTriggerPluginNode(node)
|
||||
})
|
||||
}
|
||||
|
||||
if (mutableDraft.graph.edges) {
|
||||
mutableDraft.graph.edges = mutableDraft.graph.edges
|
||||
.filter((edge: Edge) => !edge.data?._isTemp)
|
||||
.map((edge: Edge) => {
|
||||
if (edge.data)
|
||||
removeTempProperties(edge.data as Record<string, unknown>)
|
||||
|
||||
return edge
|
||||
})
|
||||
}
|
||||
|
||||
if (mutableDraft.environment_variables) {
|
||||
mutableDraft.environment_variables = mutableDraft.environment_variables.map(env =>
|
||||
env.value_type === 'secret'
|
||||
? { ...env, value: '[__HIDDEN__]' }
|
||||
: env,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
109
dify/web/service/workflow.ts
Normal file
109
dify/web/service/workflow.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
import { get, post } from './base'
|
||||
import type { CommonResponse } from '@/models/common'
|
||||
import type {
|
||||
ChatRunHistoryResponse,
|
||||
ConversationVariableResponse,
|
||||
FetchWorkflowDraftResponse,
|
||||
NodesDefaultConfigsResponse,
|
||||
WorkflowRunHistoryResponse,
|
||||
} from '@/types/workflow'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { VarInInspect } from '@/types/workflow'
|
||||
import type { FlowType } from '@/types/common'
|
||||
import { getFlowPrefix } from './utils'
|
||||
|
||||
export const fetchWorkflowDraft = (url: string) => {
|
||||
return get(url, {}, { silent: true }) as Promise<FetchWorkflowDraftResponse>
|
||||
}
|
||||
|
||||
export const syncWorkflowDraft = ({ url, params }: {
|
||||
url: string
|
||||
params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features' | 'environment_variables' | 'conversation_variables'>
|
||||
}) => {
|
||||
return post<CommonResponse & { updated_at: number; hash: string }>(url, { body: params }, { silent: true })
|
||||
}
|
||||
|
||||
export const fetchNodesDefaultConfigs: Fetcher<NodesDefaultConfigsResponse, string> = (url) => {
|
||||
return get<NodesDefaultConfigsResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchWorkflowRunHistory: Fetcher<WorkflowRunHistoryResponse, string> = (url) => {
|
||||
return get<WorkflowRunHistoryResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchChatRunHistory: Fetcher<ChatRunHistoryResponse, string> = (url) => {
|
||||
return get<ChatRunHistoryResponse>(url)
|
||||
}
|
||||
|
||||
export const singleNodeRun = (flowType: FlowType, flowId: string, nodeId: string, params: object) => {
|
||||
return post(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/run`, { body: params })
|
||||
}
|
||||
|
||||
export const getIterationSingleNodeRunUrl = (flowType: FlowType, isChatFlow: boolean, flowId: string, nodeId: string) => {
|
||||
return `${getFlowPrefix(flowType)}/${flowId}/${isChatFlow ? 'advanced-chat/' : ''}workflows/draft/iteration/nodes/${nodeId}/run`
|
||||
}
|
||||
|
||||
export const getLoopSingleNodeRunUrl = (flowType: FlowType, isChatFlow: boolean, flowId: string, nodeId: string) => {
|
||||
return `${getFlowPrefix(flowType)}/${flowId}/${isChatFlow ? 'advanced-chat/' : ''}workflows/draft/loop/nodes/${nodeId}/run`
|
||||
}
|
||||
|
||||
export const fetchPublishedWorkflow: Fetcher<FetchWorkflowDraftResponse, string> = (url) => {
|
||||
return get<FetchWorkflowDraftResponse>(url)
|
||||
}
|
||||
|
||||
export const stopWorkflowRun = (url: string) => {
|
||||
return post<CommonResponse>(url)
|
||||
}
|
||||
|
||||
export const fetchNodeDefault = (appId: string, blockType: BlockEnum, query = {}) => {
|
||||
return get(`apps/${appId}/workflows/default-workflow-block-configs/${blockType}`, {
|
||||
params: { q: JSON.stringify(query) },
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchPipelineNodeDefault = (pipelineId: string, blockType: BlockEnum, query = {}) => {
|
||||
return get(`rag/pipelines/${pipelineId}/workflows/default-workflow-block-configs/${blockType}`, {
|
||||
params: { q: JSON.stringify(query) },
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: archived
|
||||
export const updateWorkflowDraftFromDSL = (appId: string, data: string) => {
|
||||
return post<FetchWorkflowDraftResponse>(`apps/${appId}/workflows/draft/import`, { body: { data } })
|
||||
}
|
||||
|
||||
export const fetchCurrentValueOfConversationVariable: Fetcher<ConversationVariableResponse, {
|
||||
url: string
|
||||
params: { conversation_id: string }
|
||||
}> = ({ url, params }) => {
|
||||
return get<ConversationVariableResponse>(url, { params })
|
||||
}
|
||||
|
||||
const fetchAllInspectVarsOnePage = async (flowType: FlowType, flowId: string, page: number): Promise<{ total: number, items: VarInInspect[] }> => {
|
||||
return get(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/variables`, {
|
||||
params: { page, limit: 100 },
|
||||
})
|
||||
}
|
||||
export const fetchAllInspectVars = async (flowType: FlowType, flowId: string): Promise<VarInInspect[]> => {
|
||||
const res = await fetchAllInspectVarsOnePage(flowType, flowId, 1)
|
||||
const { items, total } = res
|
||||
if (total <= 100)
|
||||
return items
|
||||
|
||||
const pageCount = Math.ceil(total / 100)
|
||||
const promises = []
|
||||
for (let i = 2; i <= pageCount; i++)
|
||||
promises.push(fetchAllInspectVarsOnePage(flowType, flowId, i))
|
||||
|
||||
const restData = await Promise.all(promises)
|
||||
restData.forEach(({ items: item }) => {
|
||||
items.push(...item)
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
export const fetchNodeInspectVars = async (flowType: FlowType, flowId: string, nodeId: string): Promise<VarInInspect[]> => {
|
||||
const { items } = (await get(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/variables`)) as { items: VarInInspect[] }
|
||||
return items
|
||||
}
|
||||
Reference in New Issue
Block a user