dify/web/features/deployments/components/runtime-credential-bindings-utils.ts
Stephen Zhou 48452aefbc
feat: app deploy (#35670)
Co-authored-by: zhangx1n <zhangxin@dify.ai>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-17 09:28:43 +00:00

116 lines
3.4 KiB
TypeScript

import type {
CredentialCandidate,
CredentialSelectionInput,
CredentialSlot,
} from '@dify/contracts/enterprise/types.gen'
export type RuntimeCredentialBindingSelections = Record<string, string>
export type RuntimeCredentialSelectOption = {
value: string
label: string
}
const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
azure_openai: 'Azure OpenAI',
bedrock: 'Amazon Bedrock',
gemini: 'Gemini',
google: 'Google',
openai: 'OpenAI',
vertex_ai: 'Vertex AI',
volcengine_maas: 'Volcengine',
}
function providerSlug(providerId: string) {
const parts = providerId.split('/').filter(Boolean)
return parts[parts.length - 1]
}
function titleCaseProviderName(value: string) {
return value
.split(/[-_]/)
.filter(Boolean)
.map(part => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
.join(' ')
}
export function runtimeCredentialSlotKey(slot: CredentialSlot) {
return [slot.providerId, slot.category].join(':')
}
export function runtimeCredentialProviderName(providerId: string) {
const slug = providerSlug(providerId)
if (!slug)
return undefined
return PROVIDER_DISPLAY_NAMES[slug.toLowerCase()] ?? titleCaseProviderName(slug)
}
function runtimeCredentialCandidateLabel(candidate: CredentialCandidate) {
const fallback = candidate.credentialId
const rawLabel = candidate.displayName.trim() || fallback
const providerId = candidate.providerId.trim()
const providerSuffixes = [
` · ${providerId}`,
` - ${providerId}`,
` (${providerId})`,
]
const label = providerSuffixes.reduce((nextLabel, suffix) => {
return nextLabel.endsWith(suffix)
? nextLabel.slice(0, -suffix.length).trim()
: nextLabel
}, rawLabel)
return label || fallback
}
export function runtimeCredentialCandidateOptions(slot: CredentialSlot): RuntimeCredentialSelectOption[] {
return slot.candidates.map(candidate => ({
value: candidate.credentialId,
label: runtimeCredentialCandidateLabel(candidate),
}))
}
export function hasMissingRequiredRuntimeCredentialBinding(_slot: CredentialSlot, selectedValue?: string) {
return !selectedValue
}
export function selectedRuntimeCredentialSelections(
slots: CredentialSlot[],
manualBindings: RuntimeCredentialBindingSelections,
): RuntimeCredentialBindingSelections {
const next: RuntimeCredentialBindingSelections = {}
for (const slot of slots) {
const slotKey = runtimeCredentialSlotKey(slot)
const candidates = runtimeCredentialCandidateOptions(slot)
const existing = manualBindings[slotKey]
if (existing && candidates.some(candidate => candidate.value === existing))
next[slotKey] = existing
else if (slot.lastCredentialId && candidates.some(candidate => candidate.value === slot.lastCredentialId))
next[slotKey] = slot.lastCredentialId
else if (candidates.length === 1 && candidates[0])
next[slotKey] = candidates[0].value
}
return next
}
export function selectedDeploymentRuntimeCredentials(
slots: CredentialSlot[],
selections: RuntimeCredentialBindingSelections,
): CredentialSelectionInput[] {
return slots
.flatMap((slot): CredentialSelectionInput[] => {
const slotKey = runtimeCredentialSlotKey(slot)
const selectedValue = selections[slotKey]
if (!selectedValue)
return []
return [{
providerId: slot.providerId,
category: slot.category,
credentialId: selectedValue,
}]
})
}