feat: add Creators Platform helper for DSL upload and OAuth redirect (Vibe Kanban) (#32232)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Junyan Chin 2026-02-11 21:10:48 +08:00 committed by GitHub
parent 98d2eb6579
commit 2bf767d5f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 258 additions and 1 deletions

View File

@ -627,6 +627,11 @@ INNER_API_KEY_FOR_PLUGIN=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y
MARKETPLACE_ENABLED=true
MARKETPLACE_API_URL=https://marketplace.dify.ai
# Creators Platform configuration
CREATORS_PLATFORM_FEATURES_ENABLED=true
CREATORS_PLATFORM_API_URL=https://creators.dify.ai
CREATORS_PLATFORM_OAUTH_CLIENT_ID=
# Endpoint configuration
ENDPOINT_URL_TEMPLATE=http://localhost:5002/e/{hook_id}

View File

@ -293,6 +293,27 @@ class MarketplaceConfig(BaseSettings):
)
class CreatorsPlatformConfig(BaseSettings):
"""
Configuration for creators platform
"""
CREATORS_PLATFORM_FEATURES_ENABLED: bool = Field(
description="Enable or disable creators platform features",
default=True,
)
CREATORS_PLATFORM_API_URL: HttpUrl = Field(
description="Creators Platform API URL",
default=HttpUrl("https://creators.dify.ai"),
)
CREATORS_PLATFORM_OAUTH_CLIENT_ID: str = Field(
description="OAuth client_id for the Creators Platform app registered in Dify",
default="",
)
class EndpointConfig(BaseSettings):
"""
Configuration for various application endpoints and URLs
@ -1396,6 +1417,7 @@ class FeatureConfig(
PluginConfig,
CliApiConfig,
MarketplaceConfig,
CreatorsPlatformConfig,
DataSetConfig,
EndpointConfig,
FileAccessConfig,

View File

@ -736,6 +736,46 @@ class AppExportBundleApi(Resource):
return result.model_dump(mode="json")
@console_ns.route("/apps/<uuid:app_id>/publish-to-creators-platform")
class AppPublishToCreatorsPlatformApi(Resource):
@get_app_model
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
def post(self, app_model):
"""Bundle the app DSL and upload to Creators Platform, returning a redirect URL."""
import httpx
from configs import dify_config
from core.helper.creators import get_redirect_url, upload_dsl
from services.app_bundle_service import AppBundleService
if not dify_config.CREATORS_PLATFORM_FEATURES_ENABLED:
return {"message": "Creators Platform is not enabled"}, 403
current_user, _ = current_account_with_tenant()
# 1. Export the app bundle (DSL + assets) to a temporary zip
bundle_result = AppBundleService.export_bundle(
app_model=app_model,
account_id=str(current_user.id),
include_secret=False,
)
# 2. Download the zip from the temporary URL
download_response = httpx.get(bundle_result.download_url, timeout=60, follow_redirects=True)
download_response.raise_for_status()
# 3. Upload to Creators Platform
claim_code = upload_dsl(download_response.content, filename=bundle_result.filename)
# 4. Generate redirect URL (with optional OAuth code)
redirect_url = get_redirect_url(str(current_user.id), claim_code)
return {"redirect_url": redirect_url}
@console_ns.route("/apps/<uuid:app_id>/name")
class AppNameApi(Resource):
@console_ns.doc("check_app_name")

View File

@ -0,0 +1,75 @@
"""
Helper module for Creators Platform integration.
Provides functionality to upload DSL files to the Creators Platform
and generate redirect URLs with OAuth authorization codes.
"""
import logging
from urllib.parse import urlencode
import httpx
from yarl import URL
from configs import dify_config
logger = logging.getLogger(__name__)
creators_platform_api_url = URL(str(dify_config.CREATORS_PLATFORM_API_URL))
def upload_dsl(dsl_file_bytes: bytes, filename: str = "template.yaml") -> str:
"""Upload a DSL file to the Creators Platform anonymous upload endpoint.
Args:
dsl_file_bytes: Raw bytes of the DSL file (YAML or ZIP).
filename: Original filename for the upload.
Returns:
The claim_code string used to retrieve the DSL later.
Raises:
httpx.HTTPStatusError: If the upload request fails.
ValueError: If the response does not contain a valid claim_code.
"""
url = str(creators_platform_api_url / "api/v1/templates/anonymous-upload")
response = httpx.post(url, files={"file": (filename, dsl_file_bytes)}, timeout=30)
response.raise_for_status()
data = response.json()
claim_code = data.get("data", {}).get("claim_code")
if not claim_code:
raise ValueError("Creators Platform did not return a valid claim_code")
return claim_code
def get_redirect_url(user_account_id: str, claim_code: str) -> str:
"""Generate the redirect URL to the Creators Platform frontend.
Redirects to the Creators Platform root page with the dsl_claim_code.
If CREATORS_PLATFORM_OAUTH_CLIENT_ID is configured (Dify Cloud),
also signs an OAuth authorization code so the frontend can
automatically authenticate the user via the OAuth callback.
For self-hosted Dify without OAuth client_id configured, only the
dsl_claim_code is passed and the user must log in manually.
Args:
user_account_id: The Dify user account ID.
claim_code: The claim_code obtained from upload_dsl().
Returns:
The full redirect URL string.
"""
base_url = str(dify_config.CREATORS_PLATFORM_API_URL).rstrip("/")
params: dict[str, str] = {"dsl_claim_code": claim_code}
client_id = str(dify_config.CREATORS_PLATFORM_OAUTH_CLIENT_ID or "")
if client_id:
from services.oauth_server import OAuthServerService
oauth_code = OAuthServerService.sign_oauth_authorization_code(client_id, user_account_id)
params["oauth_code"] = oauth_code
return f"{base_url}?{urlencode(params)}"

View File

@ -177,6 +177,7 @@ class SystemFeatureModel(BaseModel):
trial_models: list[str] = []
enable_trial_app: bool = False
enable_explore_banner: bool = False
enable_creators_platform: bool = False
class FeatureService:
@ -238,6 +239,9 @@ class FeatureService:
if dify_config.MARKETPLACE_ENABLED:
system_features.enable_marketplace = True
if dify_config.CREATORS_PLATFORM_FEATURES_ENABLED:
system_features.enable_creators_platform = True
return system_features
@classmethod

View File

@ -9,10 +9,12 @@ import {
RiArrowRightSLine,
RiBuildingLine,
RiGlobalLine,
RiLoader2Line,
RiLockLine,
RiPlanetLine,
RiPlayCircleLine,
RiPlayList2Line,
RiStore2Line,
RiTerminalBoxLine,
RiVerifiedBadgeLine,
} from '@remixicon/react'
@ -47,7 +49,7 @@ import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { AccessMode } from '@/models/access-control'
import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control'
import { fetchAppDetailDirect } from '@/service/apps'
import { fetchAppDetailDirect, publishToCreatorsPlatform } from '@/service/apps'
import { fetchInstalledAppList } from '@/service/explore'
import { useInvalidateAppWorkflow } from '@/service/use-workflow'
import { fetchPublishedWorkflow } from '@/service/workflow'
@ -162,6 +164,7 @@ const AppPublisher = ({
const [showAppAccessControl, setShowAppAccessControl] = useState(false)
const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false)
const [publishingToMarketplace, setPublishingToMarketplace] = useState(false)
const workflowStore = useContext(WorkflowContext)
const appDetail = useAppStore(state => state.appDetail)
@ -288,6 +291,22 @@ const AppPublisher = ({
}
}, [appDetail, setAppDetail])
const handlePublishToMarketplace = useCallback(async () => {
if (!appDetail?.id || publishingToMarketplace)
return
setPublishingToMarketplace(true)
try {
const result = await publishToCreatorsPlatform({ appID: appDetail.id })
window.open(result.redirect_url, '_blank')
}
catch (error: any) {
Toast.notify({ type: 'error', message: error.message || t('common.publishToMarketplaceFailed', { ns: 'workflow' }) })
}
finally {
setPublishingToMarketplace(false)
}
}, [appDetail?.id, publishingToMarketplace, t])
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
e.preventDefault()
if (publishDisabled || published || publishLoading)
@ -547,6 +566,22 @@ const AppPublisher = ({
</div>
)
}
{systemFeatures.enable_creators_platform && (
<div className="flex flex-col gap-y-1 border-t-[0.5px] border-t-divider-regular p-4 pt-3">
<SuggestedAction
className="flex-1"
onClick={handlePublishToMarketplace}
disabled={publishingToMarketplace}
icon={publishingToMarketplace
? <RiLoader2Line className="h-4 w-4 animate-spin" />
: <RiStore2Line className="h-4 w-4" />}
>
{publishingToMarketplace
? t('common.publishingToMarketplace', { ns: 'workflow' })
: t('common.publishToMarketplace', { ns: 'workflow' })}
</SuggestedAction>
</div>
)}
</>
)}
</div>

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "نشر التحديث",
"common.published": "منشور",
"common.publishedAt": "تم النشر في",
"common.publishToMarketplace": "نشر في السوق",
"common.publishToMarketplaceFailed": "فشل النشر في السوق",
"common.publishingToMarketplace": "جارٍ النشر...",
"common.redo": "إعادة",
"common.restart": "إعادة تشغيل",
"common.restore": "استعادة",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Update veröffentlichen",
"common.published": "Veröffentlicht",
"common.publishedAt": "Veröffentlicht am",
"common.publishToMarketplace": "Im Marketplace veröffentlichen",
"common.publishToMarketplaceFailed": "Veröffentlichung im Marketplace fehlgeschlagen",
"common.publishingToMarketplace": "Veröffentlichen...",
"common.redo": "Wiederholen",
"common.restart": "Neustarten",
"common.restore": "Wiederherstellen",

View File

@ -231,6 +231,9 @@
"common.published": "Published",
"common.publishedAt": "Published",
"common.publishing": "Publishing...",
"common.publishToMarketplace": "Publish to Marketplace",
"common.publishToMarketplaceFailed": "Failed to publish to Marketplace",
"common.publishingToMarketplace": "Publishing...",
"common.redo": "Redo",
"common.restart": "Restart",
"common.restore": "Restore",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Publicar actualización",
"common.published": "Publicado",
"common.publishedAt": "Publicado el",
"common.publishToMarketplace": "Publicar en Marketplace",
"common.publishToMarketplaceFailed": "Error al publicar en Marketplace",
"common.publishingToMarketplace": "Publicando...",
"common.redo": "Rehacer",
"common.restart": "Reiniciar",
"common.restore": "Restaurar",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "به‌روزرسانی منتشر کنید",
"common.published": "منتشر شده",
"common.publishedAt": "منتشر شده",
"common.publishToMarketplace": "انتشار در بازار",
"common.publishToMarketplaceFailed": "انتشار در بازار ناموفق بود",
"common.publishingToMarketplace": "در حال انتشار...",
"common.redo": "پیشرفت",
"common.restart": "راه‌اندازی مجدد",
"common.restore": "بازیابی",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Publier une mise à jour",
"common.published": "Publié",
"common.publishedAt": "Publié le",
"common.publishToMarketplace": "Publier sur le Marketplace",
"common.publishToMarketplaceFailed": "Échec de la publication sur le Marketplace",
"common.publishingToMarketplace": "Publication...",
"common.redo": "Réexécuter",
"common.restart": "Redémarrer",
"common.restore": "Restaurer",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "अपडेट प्रकाशित करें",
"common.published": "प्रकाशित",
"common.publishedAt": "प्रकाशित",
"common.publishToMarketplace": "Marketplace पर प्रकाशित करें",
"common.publishToMarketplaceFailed": "Marketplace पर प्रकाशित करने में विफल",
"common.publishingToMarketplace": "प्रकाशित हो रहा है...",
"common.redo": "फिर से करें",
"common.restart": "पुनः आरंभ करें",
"common.restore": "पुनर्स्थापित करें",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Publikasikan Pembaruan",
"common.published": "Diterbitkan",
"common.publishedAt": "Diterbitkan",
"common.publishToMarketplace": "Publikasikan ke Marketplace",
"common.publishToMarketplaceFailed": "Gagal mempublikasikan ke Marketplace",
"common.publishingToMarketplace": "Mempublikasikan...",
"common.redo": "Ulangi",
"common.restart": "Restart",
"common.restore": "Mengembalikan",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Pubblica aggiornamento",
"common.published": "Pubblicato",
"common.publishedAt": "Pubblicato",
"common.publishToMarketplace": "Pubblica nel Marketplace",
"common.publishToMarketplaceFailed": "Pubblicazione nel Marketplace non riuscita",
"common.publishingToMarketplace": "Pubblicazione...",
"common.redo": "Ripeti",
"common.restart": "Riavvia",
"common.restore": "Ripristina",

View File

@ -223,6 +223,9 @@
"common.publishUpdate": "更新を公開",
"common.published": "公開済み",
"common.publishedAt": "公開日時",
"common.publishToMarketplace": "マーケットプレイスに公開",
"common.publishToMarketplaceFailed": "マーケットプレイスへの公開に失敗しました",
"common.publishingToMarketplace": "公開中...",
"common.redo": "やり直し",
"common.restart": "再起動",
"common.restore": "復元",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "업데이트 게시",
"common.published": "게시됨",
"common.publishedAt": "발행일",
"common.publishToMarketplace": "마켓플레이스에 게시",
"common.publishToMarketplaceFailed": "마켓플레이스에 게시하지 못했습니다",
"common.publishingToMarketplace": "게시 중...",
"common.redo": "다시 실행",
"common.restart": "재시작",
"common.restore": "복원",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Opublikuj aktualizację",
"common.published": "Opublikowane",
"common.publishedAt": "Opublikowane",
"common.publishToMarketplace": "Opublikuj na Marketplace",
"common.publishToMarketplaceFailed": "Nie udało się opublikować na Marketplace",
"common.publishingToMarketplace": "Publikowanie...",
"common.redo": "Ponów",
"common.restart": "Uruchom ponownie",
"common.restore": "Przywróć",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Publicar Atualização",
"common.published": "Publicado",
"common.publishedAt": "Publicado em",
"common.publishToMarketplace": "Publicar no Marketplace",
"common.publishToMarketplaceFailed": "Falha ao publicar no Marketplace",
"common.publishingToMarketplace": "Publicando...",
"common.redo": "Refazer",
"common.restart": "Reiniciar",
"common.restore": "Restaurar",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Publicați actualizarea",
"common.published": "Publicat",
"common.publishedAt": "Publicat la",
"common.publishToMarketplace": "Publicare pe Marketplace",
"common.publishToMarketplaceFailed": "Publicarea pe Marketplace a eșuat",
"common.publishingToMarketplace": "Se publică...",
"common.redo": "Refă",
"common.restart": "Repornește",
"common.restore": "Restaurează",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Опубликовать обновление",
"common.published": "Опубликовано",
"common.publishedAt": "Опубликовано",
"common.publishToMarketplace": "Опубликовать на Marketplace",
"common.publishToMarketplaceFailed": "Не удалось опубликовать на Marketplace",
"common.publishingToMarketplace": "Публикация...",
"common.redo": "Повторить",
"common.restart": "Перезапустить",
"common.restore": "Восстановить",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Objavi posodobitev",
"common.published": "Objavljeno",
"common.publishedAt": "Objavljeno",
"common.publishToMarketplace": "Objavi na Marketplace",
"common.publishToMarketplaceFailed": "Objava na Marketplace ni uspela",
"common.publishingToMarketplace": "Objavljanje...",
"common.redo": "Ponovno naredi",
"common.restart": "Znova zaženi",
"common.restore": "Obnovi",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "เผยแพร่การอัปเดต",
"common.published": "เผย แพร่",
"common.publishedAt": "เผย แพร่",
"common.publishToMarketplace": "เผยแพร่ไปยัง Marketplace",
"common.publishToMarketplaceFailed": "เผยแพร่ไปยัง Marketplace ล้มเหลว",
"common.publishingToMarketplace": "กำลังเผยแพร่...",
"common.redo": "พร้อม",
"common.restart": "เริ่มใหม่",
"common.restore": "ซ่อมแซม",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Güncellemeyi Yayınla",
"common.published": "Yayınlandı",
"common.publishedAt": "Yayınlandı",
"common.publishToMarketplace": "Marketplace'e Yayınla",
"common.publishToMarketplaceFailed": "Marketplace'e yayınlama başarısız oldu",
"common.publishingToMarketplace": "Yayınlanıyor...",
"common.redo": "Yinele",
"common.restart": "Yeniden Başlat",
"common.restore": "Geri Yükle",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Опублікувати оновлення",
"common.published": "Опубліковано",
"common.publishedAt": "Опубліковано о",
"common.publishToMarketplace": "Опублікувати на Marketplace",
"common.publishToMarketplaceFailed": "Не вдалося опублікувати на Marketplace",
"common.publishingToMarketplace": "Публікація...",
"common.redo": "Повторити",
"common.restart": "Перезапустити",
"common.restore": "Відновити",

View File

@ -222,6 +222,9 @@
"common.publishUpdate": "Cập nhật xuất bản",
"common.published": "Đã xuất bản",
"common.publishedAt": "Đã xuất bản lúc",
"common.publishToMarketplace": "Xuất bản lên Marketplace",
"common.publishToMarketplaceFailed": "Xuất bản lên Marketplace thất bại",
"common.publishingToMarketplace": "Đang xuất bản...",
"common.redo": "Làm lại",
"common.restart": "Khởi động lại",
"common.restore": "Khôi phục",

View File

@ -227,6 +227,9 @@
"common.published": "已发布",
"common.publishedAt": "发布于",
"common.publishing": "发布中...",
"common.publishToMarketplace": "发布到市场",
"common.publishToMarketplaceFailed": "发布到市场失败",
"common.publishingToMarketplace": "发布中...",
"common.redo": "重做",
"common.restart": "重新开始",
"common.restore": "恢复",

View File

@ -223,6 +223,9 @@
"common.publishUpdate": "發布更新",
"common.published": "已發佈",
"common.publishedAt": "發佈於",
"common.publishToMarketplace": "發布到市場",
"common.publishToMarketplaceFailed": "發布到市場失敗",
"common.publishingToMarketplace": "發布中...",
"common.redo": "重做",
"common.restart": "重新開始",
"common.restore": "恢復",

View File

@ -157,6 +157,14 @@ export const importDSLConfirm = ({ import_id }: { import_id: string }): Promise<
return post<DSLImportResponse>(`apps/imports/${import_id}/confirm`, { body: {} })
}
export type PublishToCreatorsPlatformResponse = {
redirect_url: string
}
export const publishToCreatorsPlatform = ({ appID }: { appID: string }): Promise<PublishToCreatorsPlatformResponse> => {
return post<PublishToCreatorsPlatformResponse>(`apps/${appID}/publish-to-creators-platform`, { body: {} })
}
export type ImportBundlePrepareResponse = {
import_id: string
upload_url: string

View File

@ -65,6 +65,7 @@ export type SystemFeatures = {
}
enable_trial_app: boolean
enable_explore_banner: boolean
enable_creators_platform: boolean
}
export const defaultSystemFeatures: SystemFeatures = {
@ -108,4 +109,5 @@ export const defaultSystemFeatures: SystemFeatures = {
},
enable_trial_app: false,
enable_explore_banner: false,
enable_creators_platform: false,
}