diff --git a/apps/console/src/routeTree.gen.ts b/apps/console/src/routeTree.gen.ts index 50660620478..f66a53ff5c0 100644 --- a/apps/console/src/routeTree.gen.ts +++ b/apps/console/src/routeTree.gen.ts @@ -118,6 +118,7 @@ import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnviron import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm/route' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database/route' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/route' +import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/route' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/route' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables/route' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsRouteRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/route' @@ -127,6 +128,7 @@ import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnviron import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm/index' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database/index' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/index' +import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/index' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/index' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables/index' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsIndexRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/index' @@ -155,6 +157,8 @@ import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnviron import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobResourcesRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobGeneralRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure' +import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' +import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugVariablesRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugSummaryRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary' import { Route as AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugResourcesRouteImport } from './routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources' @@ -1047,6 +1051,14 @@ const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironm getParentRoute: () => AuthenticatedOrganizationOrganizationIdRouteRoute, } as any, ) +const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute = + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteImport.update( + { + id: '/project/$projectId/environment/$environmentId/service/create/agentic-workflow', + path: '/project/$projectId/environment/$environmentId/service/create/agentic-workflow', + getParentRoute: () => AuthenticatedOrganizationOrganizationIdRouteRoute, + } as any, + ) const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRoute = AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteImport.update( { @@ -1124,6 +1136,15 @@ const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironm AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRoute, } as any, ) +const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute = + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRouteImport.update( + { + id: '/', + path: '/', + getParentRoute: () => + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute, + } as any, + ) const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugIndexRoute = AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugIndexRouteImport.update( { @@ -1375,6 +1396,24 @@ const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironm AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRoute, } as any, ) +const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute = + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRouteImport.update( + { + id: '/summary', + path: '/summary', + getParentRoute: () => + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute, + } as any, + ) +const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute = + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRouteImport.update( + { + id: '/configuration', + path: '/configuration', + getParentRoute: () => + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute, + } as any, + ) const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugVariablesRoute = AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugVariablesRouteImport.update( { @@ -1824,6 +1863,7 @@ export interface FileRoutesByFullPath { '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsRouteRouteWithChildren '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesRouteRouteWithChildren '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteWithChildren + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteWithChildren '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteWithChildren '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseRouteRouteWithChildren '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmRouteRouteWithChildren @@ -1865,6 +1905,8 @@ export interface FileRoutesByFullPath { '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugResourcesRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugSummaryRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugVariablesRoute + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobGeneralRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobResourcesRoute @@ -1893,6 +1935,7 @@ export interface FileRoutesByFullPath { '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugIndexRoute + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmIndexRoute @@ -2031,6 +2074,8 @@ export interface FileRoutesByTo { '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugResourcesRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugSummaryRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugVariablesRoute + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobGeneralRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobResourcesRoute @@ -2059,6 +2104,7 @@ export interface FileRoutesByTo { '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugIndexRoute + '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseIndexRoute '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmIndexRoute @@ -2177,6 +2223,7 @@ export interface FileRoutesById { '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsRouteRouteWithChildren '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesRouteRouteWithChildren '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteWithChildren + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteWithChildren '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteWithChildren '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseRouteRouteWithChildren '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmRouteRouteWithChildren @@ -2218,6 +2265,8 @@ export interface FileRoutesById { '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugResourcesRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugSummaryRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugVariablesRoute + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobGeneralRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobResourcesRoute @@ -2246,6 +2295,7 @@ export interface FileRoutesById { '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsIndexRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesIndexRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugIndexRoute + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobIndexRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseIndexRoute '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm/': typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmIndexRoute @@ -2365,6 +2415,7 @@ export interface FileRouteTypes { | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm' @@ -2406,6 +2457,8 @@ export interface FileRouteTypes { | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources' @@ -2434,6 +2487,7 @@ export interface FileRouteTypes { | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables/' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database/' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm/' @@ -2572,6 +2626,8 @@ export interface FileRouteTypes { | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources' @@ -2600,6 +2656,7 @@ export interface FileRouteTypes { | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug' + | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database' | '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm' @@ -2717,6 +2774,7 @@ export interface FileRouteTypes { | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug' + | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm' @@ -2758,6 +2816,8 @@ export interface FileRouteTypes { | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/resources' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/summary' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables' + | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' + | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources' @@ -2786,6 +2846,7 @@ export interface FileRouteTypes { | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/variables/' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/' + | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database/' | '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm/' @@ -3576,6 +3637,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteImport parentRoute: typeof AuthenticatedOrganizationOrganizationIdRouteRoute } + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow': { + id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow' + path: '/project/$projectId/environment/$environmentId/service/create/agentic-workflow' + fullPath: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow' + preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteImport + parentRoute: typeof AuthenticatedOrganizationOrganizationIdRouteRoute + } '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug': { id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug' path: '/project/$projectId/environment/$environmentId/service/create/$slug' @@ -3639,6 +3707,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobIndexRouteImport parentRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRoute } + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/': { + id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/' + path: '/' + fullPath: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/' + preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRouteImport + parentRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute + } '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/': { id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/' path: '/' @@ -3835,6 +3910,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRouteImport parentRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRoute } + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary': { + id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' + path: '/summary' + fullPath: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' + preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRouteImport + parentRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute + } + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration': { + id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' + path: '/configuration' + fullPath: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' + preLoaderRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRouteImport + parentRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute + } '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables': { id: '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug/variables' path: '/variables' @@ -4531,6 +4620,27 @@ const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironm AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteChildren, ) +interface AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteChildren { + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute +} + +const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteChildren: AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteChildren = + { + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute: + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowConfigurationRoute, + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute: + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowSummaryRoute, + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute: + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowIndexRoute, + } + +const AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteWithChildren = + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute._addFileChildren( + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteChildren, + ) + interface AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteChildren { AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobConfigureRoute AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobGeneralRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobGeneralRoute @@ -4752,6 +4862,7 @@ interface AuthenticatedOrganizationOrganizationIdRouteRouteChildren { AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdSettingsRouteRouteWithChildren AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesRouteRouteWithChildren AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteWithChildren + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteWithChildren AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteWithChildren AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseRouteRouteWithChildren AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmRouteRoute: typeof AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateHelmRouteRouteWithChildren @@ -4835,6 +4946,8 @@ const AuthenticatedOrganizationOrganizationIdRouteRouteChildren: AuthenticatedOr AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceServiceIdVariablesRouteRouteWithChildren, AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRoute: AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateSlugRouteRouteWithChildren, + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRoute: + AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateAgenticWorkflowRouteRouteWithChildren, AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRoute: AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateCronJobRouteRouteWithChildren, AuthenticatedOrganizationOrganizationIdProjectProjectIdEnvironmentEnvironmentIdServiceCreateDatabaseRouteRoute: diff --git a/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration.tsx b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration.tsx new file mode 100644 index 00000000000..836922c909b --- /dev/null +++ b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' +import { AgenticWorkflowConfiguration } from '@qovery/domains/services/feature' +import { useDocumentTitle } from '@qovery/shared/util-hooks' + +export const Route = createFileRoute( + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/configuration' +)({ + component: RouteComponent, +}) + +function RouteComponent() { + useDocumentTitle('Agentic workflow configuration') + + return +} diff --git a/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/index.tsx b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/index.tsx new file mode 100644 index 00000000000..ab9b892b66c --- /dev/null +++ b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/index.tsx @@ -0,0 +1,19 @@ +import { Navigate, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute( + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/' +)({ + component: RouteComponent, +}) + +function RouteComponent() { + const { organizationId, projectId, environmentId } = Route.useParams() + + return ( + + ) +} diff --git a/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/route.tsx b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/route.tsx new file mode 100644 index 00000000000..177b183eda4 --- /dev/null +++ b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/route.tsx @@ -0,0 +1,44 @@ +import { Outlet, createFileRoute, useNavigate } from '@tanstack/react-router' +import { useFeatureFlagEnabled } from 'posthog-js/react' +import { useEffect } from 'react' +import { AgenticWorkflowCreationFlow } from '@qovery/domains/services/feature' +import { serviceCreateParamsSchema } from '@qovery/shared/router' + +export const Route = createFileRoute( + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow' +)({ + component: RouteComponent, + validateSearch: serviceCreateParamsSchema, +}) + +function RouteComponent() { + const { organizationId, projectId, environmentId } = Route.useParams() + const navigate = useNavigate() + const isAgenticWorkflowEnabled = Boolean(useFeatureFlagEnabled('argentic-workflow')) + const creationFlowUrl = `/organization/${organizationId}/project/${projectId}/environment/${environmentId}/service/create/agentic-workflow` + + useEffect(() => { + if (!isAgenticWorkflowEnabled) { + navigate({ + to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/new', + params: { organizationId, projectId, environmentId }, + }) + } + }, [environmentId, isAgenticWorkflowEnabled, navigate, organizationId, projectId]) + + if (!isAgenticWorkflowEnabled) return null + + return ( + + navigate({ + to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/new', + params: { organizationId, projectId, environmentId }, + }) + } + > + + + ) +} diff --git a/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary.tsx b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary.tsx new file mode 100644 index 00000000000..6bb8d520991 --- /dev/null +++ b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' +import { AgenticWorkflowSummary } from '@qovery/domains/services/feature' +import { useDocumentTitle } from '@qovery/shared/util-hooks' + +export const Route = createFileRoute( + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow/summary' +)({ + component: RouteComponent, +}) + +function RouteComponent() { + useDocumentTitle('Summary - Create agentic workflow') + + return +} diff --git a/apps/console/src/routes/_authenticated/organization/$organizationId/route.tsx b/apps/console/src/routes/_authenticated/organization/$organizationId/route.tsx index 79cc12afcf5..a0b8eff01e9 100644 --- a/apps/console/src/routes/_authenticated/organization/$organizationId/route.tsx +++ b/apps/console/src/routes/_authenticated/organization/$organizationId/route.tsx @@ -51,6 +51,7 @@ const hiddenProgressCardRouteIds: FileRouteTypes['id'][] = [ '/_authenticated/organization/$organizationId/cluster/create/$slug', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/blueprint/$provider/$serviceFamily', + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm', diff --git a/apps/console/src/routes/_authenticated/organization/route.tsx b/apps/console/src/routes/_authenticated/organization/route.tsx index dbb2c1271a2..28108223689 100644 --- a/apps/console/src/routes/_authenticated/organization/route.tsx +++ b/apps/console/src/routes/_authenticated/organization/route.tsx @@ -489,6 +489,7 @@ const bypassLayoutRouteIds: FileRouteTypes['id'][] = [ '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/$slug', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/database', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/blueprint/$provider/$serviceFamily', + '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/agentic-workflow', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/helm', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/terraform', '/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job', diff --git a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts index f35a54b415a..1be8f1db97c 100644 --- a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts +++ b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts @@ -1,6 +1,6 @@ import { createQueryKeys, type inferQueryKeys } from '@lukemorales/query-key-factory' import { - type AgenticWorkflowResponse, + type AgenticWorkflowRequest, AgenticWorkflowsApi, ApplicationActionsApi, type ApplicationAdvancedSettings, @@ -77,6 +77,7 @@ import { TerraformMainCallsApi, type TerraformRequest, TerraformsApi, + type AgenticWorkflowResponse as _AgenticWorkflow, type Application as _Application, type ArgocdAppResponse as _ArgoCd, type CloneServiceRequest as _CloneServiceRequest, @@ -163,6 +164,7 @@ export type JobType = Extract export type HelmType = Extract export type TerraformType = Extract export type ArgoCdType = Extract +export type AgenticWorkflowType = Extract // XXX: Need to remove `serviceType` and use only `service_type` since the the API now supports it. // Waiting to have this implementation available in the edition interfaces. @@ -194,16 +196,21 @@ export type ArgoCd = _ArgoCd & { // @deprecated Prefer use `service_type` from API instead of `serviceType` serviceType: ArgoCdType } -export type AnyService = Application | Database | Container | Job | Helm | Terraform | ArgoCd +export type AgenticWorkflow = _AgenticWorkflow & { + // @deprecated Prefer use `service_type` from API instead of `serviceType` + serviceType: AgenticWorkflowType + icon_uri: string +} +export type AnyService = Application | Database | Container | Job | Helm | Terraform | ArgoCd | AgenticWorkflow export type BlueprintService = AnyService & { blueprint_id: string tag?: string } export type ReadOnlyService = ArgoCd -export type EditableService = Exclude +export type EditableService = Exclude export type EditableServiceType = Exclude -export type DeployableServiceType = Exclude -export type AdvancedSettingsServiceType = Exclude +export type DeployableServiceType = Exclude +export type AdvancedSettingsServiceType = Exclude export type AdvancedSettings = | ApplicationAdvancedSettings @@ -240,8 +247,12 @@ export function isArgoCd(service?: AnyService): service is ArgoCd { return service?.service_type === 'ARGOCD_APP' } +export function isAgenticWorkflow(service?: AnyService): service is AgenticWorkflow { + return service?.service_type === 'AGENTIC_WORKFLOW' +} + export function isEditableService(service: AnyService): service is EditableService { - return !isArgoCd(service) && (service.service_type as string) !== 'AGENTIC_WORKFLOW' + return !isArgoCd(service) && !isAgenticWorkflow(service) } export function isEditableServiceType(serviceType?: ServiceType): serviceType is EditableServiceType { @@ -370,9 +381,15 @@ export const services = createQueryKeys('services', { }), listAgenticWorkflows: (environmentId: string) => ({ queryKey: [environmentId], - async queryFn(): Promise { + async queryFn(): Promise { const response = await agenticWorkflowsApi.listAgenticWorkflows(environmentId) - return response.data.results ?? [] + return ( + response.data.results?.map((service) => ({ + ...service, + serviceType: 'AGENTIC_WORKFLOW', + icon_uri: 'app://qovery-console/agentic-workflow', + })) ?? [] + ) }, }), argocdManifest: (serviceId: string) => ({ @@ -387,11 +404,7 @@ export const services = createQueryKeys('services', { async queryFn() { const response = await environmentApi.listServicesByEnvironmentId(environmentId) return (response.data.results || []).map( - (service) => - ({ - ...service, - serviceType: service.service_type, - }) as AnyService + (service) => ({ ...service, serviceType: service.service_type }) as AnyService ) }, }), @@ -405,6 +418,13 @@ export const services = createQueryKeys('services', { .with('JOB', 'CRON_JOB', 'LIFECYCLE_JOB', () => jobMainCallsApi.getJobStatus.bind(jobMainCallsApi)) .with('HELM', () => helmMainCallsApi.getHelmStatus.bind(helmMainCallsApi)) .with('TERRAFORM', () => terraformMainCallsApi.getTerraform.bind(terraformMainCallsApi)) // TODO [QOV-821] should it be replaced with `getTerraformStatus` ? + .with( + 'AGENTIC_WORKFLOW', + () => () => + new Promise(() => { + // TODO: Add the Agentic Workflow status request when supported. + }) + ) .exhaustive() const response = await fn(serviceId) return response.data @@ -429,6 +449,13 @@ export const services = createQueryKeys('services', { terraformDeploymentRestrictionApi.getTerraformDeploymentRestrictions.bind(terraformDeploymentRestrictionApi) ) .with('CONTAINER', 'DATABASE', () => null) + .with( + 'AGENTIC_WORKFLOW', + () => () => + new Promise(() => { + // TODO: Add the Agentic Workflow deployment restrictions request when supported. + }) + ) .exhaustive() if (!fn) { throw new Error(`deploymentRestrictions unsupported for serviceType: ${serviceType}`) @@ -466,12 +493,15 @@ export const services = createQueryKeys('services', { serviceType: 'TERRAFORM' as const, })) .with('ARGOCD_APP', async () => { - const service = (await argoCdApi.getArgoCdApp(serviceId)).data return { - ...service, + ...(await argoCdApi.getArgoCdApp(serviceId)).data, serviceType: 'ARGOCD_APP' as const, } }) + .with('AGENTIC_WORKFLOW', async () => ({ + ...(await agenticWorkflowsApi.getAgenticWorkflow(serviceId)).data, + serviceType: 'AGENTIC_WORKFLOW' as const, + })) .exhaustive() return service }, @@ -559,6 +589,13 @@ export const services = createQueryKeys('services', { 'TERRAFORM', async () => (await terraformDeploymentsApi.listTerraformDeploymentHistoryV2(serviceId, pageSize)).data.results ) + .with( + 'AGENTIC_WORKFLOW', + () => + new Promise(() => { + // TODO: Add the Agentic Workflow deployment history request when supported. + }) + ) .exhaustive() }, }), @@ -780,6 +817,9 @@ type CreateServiceRequest = { | ({ serviceType: TerraformType } & TerraformRequest) + | ({ + serviceType: AgenticWorkflowType + } & AgenticWorkflowRequest) } type CreateBlueprintRequest = { @@ -895,6 +935,13 @@ export const mutations = { mutation: terraformsApi.cloneTerraform.bind(terraformsApi), serviceType, })) + .with('AGENTIC_WORKFLOW', (serviceType) => ({ + mutation: () => + new Promise(() => { + // TODO: Add the Agentic Workflow clone request when supported. + }), + serviceType, + })) .exhaustive() const response = await mutation(serviceId, payload) return response.data @@ -997,7 +1044,7 @@ export const mutations = { environment: Environment payload: EnvironmentServiceIdsAllRequest }) { - const response = await environmentActionApi.deleteSelectedServices(environment.id, payload) + const response = await environmentActionApi.deleteSelectedServices(environment.id, undefined, payload) return response.data }, async deleteService({ @@ -1032,10 +1079,15 @@ export const mutations = { terraformMainCallsApi.deleteTerraform.bind(terraformMainCallsApi)( serviceId, undefined, + undefined, skipDestroy ? 'SKIP_DESTROY' : undefined ), serviceType, })) + .with('AGENTIC_WORKFLOW', (serviceType) => ({ + mutation: agenticWorkflowsApi.deleteAgenticWorkflow.bind(agenticWorkflowsApi), + serviceType, + })) .exhaustive() const response = await mutation(serviceId) return response.data @@ -1066,7 +1118,10 @@ export const mutations = { mutation: terraformsApi.createTerraform.bind(terraformsApi, environmentId, payload), serviceType: 'TERRAFORM' as const, })) - + .with({ serviceType: 'AGENTIC_WORKFLOW' }, ({ serviceType, ...payload }) => ({ + mutation: agenticWorkflowsApi.createAgenticWorkflow.bind(agenticWorkflowsApi, environmentId, payload), + serviceType, + })) .exhaustive() const response = await mutation() return response.data @@ -1147,6 +1202,13 @@ export const mutations = { mutation: terraformActionsApi.redeployTerraform.bind(terraformActionsApi), serviceType, })) + .with('AGENTIC_WORKFLOW', (serviceType) => ({ + mutation: () => + new Promise(() => { + // TODO: Add the Agentic Workflow restart request when supported. + }), + serviceType, + })) .exhaustive() const response = await mutation(serviceId) return response.data @@ -1227,7 +1289,14 @@ export const mutations = { .with('TERRAFORM', (serviceType) => ({ mutation: () => ({ data: { deployment_request_id: 'id', id: 'id' } }), serviceType, - })) // TODO [QOV-821] to implement + })) + .with('AGENTIC_WORKFLOW', (serviceType) => ({ + mutation: () => + new Promise(() => { + // TODO: Add the Agentic Workflow stop request when supported. + }), + serviceType, + })) .exhaustive() const response = await mutation(serviceId) return response.data @@ -1267,6 +1336,13 @@ export const mutations = { ), serviceType, })) + .with('AGENTIC_WORKFLOW', (serviceType) => ({ + mutation: () => + new Promise(() => { + // TODO: Add the Agentic Workflow uninstall request when supported. + }), + serviceType, + })) .exhaustive() const response = await mutation(serviceId) return response.data diff --git a/libs/domains/services/feature/src/index.ts b/libs/domains/services/feature/src/index.ts index 744892f77ba..980dfa1af3f 100644 --- a/libs/domains/services/feature/src/index.ts +++ b/libs/domains/services/feature/src/index.ts @@ -108,6 +108,10 @@ export * from './lib/service-creation-flow/database/database-summary-view/databa export * from './lib/service-creation-flow/database/step-summary/step-summary' export * from './lib/service-creation-flow/blueprint/blueprint-creation-flow' export * from './lib/service-blueprint-update-flow/blueprint-update-flow' +export * from './lib/service-creation-flow/agentic-workflow/agentic-workflow-context' +export * from './lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration' +export * from './lib/service-creation-flow/agentic-workflow/agentic-workflow-summary' +export * from './lib/service-creation-flow/agentic-workflow/agentic-workflow-request' export * from './lib/application-container-healthchecks/application-container-healthchecks-form/application-container-healthchecks-form' export * from './lib/application-container-healthchecks/healthchecks-utils' export * from './lib/application-container-healthchecks/step-healthchecks/step-healthchecks' diff --git a/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx b/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx index 5277d0b36fa..c8b739fb459 100644 --- a/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx +++ b/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx @@ -54,7 +54,7 @@ export function AgenticWorkflowServiceList({ environmentId }: AgenticWorkflowSer
- + {service.name} diff --git a/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx b/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx index cfd0814245b..284ddabc4f5 100644 --- a/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx +++ b/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx @@ -37,6 +37,7 @@ export const ServiceAvatar = forwardRef, ServiceAvatar .with({ serviceType: 'APPLICATION' }, { serviceType: 'CONTAINER' }, () => 'APPLICATION') .with({ serviceType: 'DATABASE' }, () => 'DATABASE') .with({ serviceType: 'TERRAFORM' }, () => 'TERRAFORM') + .with({ serviceType: 'AGENTIC_WORKFLOW' }, () => 'AGENTIC_WORKFLOW') .exhaustive() const serviceAvatar = ServiceIcons[service.icon_uri as IconURI] diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.spec.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.spec.tsx new file mode 100644 index 00000000000..9c17beb8f0a --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.spec.tsx @@ -0,0 +1,33 @@ +import { getJsonError, isGitRepositoryComplete, isOutputComplete } from './agentic-workflow-configuration' + +describe('AgenticWorkflowConfiguration validation', () => { + it('should require valid JSON only when a required JSON field is empty or invalid', () => { + expect(getJsonError('', false)).toBeUndefined() + expect(getJsonError('', true)).toBe('Please enter a valid JSON configuration.') + expect(getJsonError('{"mcpServers":{}}', true)).toBeUndefined() + expect(getJsonError('{invalid', true)).toBe('Invalid JSON format.') + }) + + it('should require token, repository, and branch for configured repositories', () => { + expect( + isGitRepositoryComplete({ + provider: 'GITHUB', + repository: 'https://github.com/qovery/console', + branch: 'main', + }) + ).toBe(true) + + expect( + isGitRepositoryComplete({ + provider: 'GITHUB', + repository: 'https://github.com/qovery/console', + branch: '', + }) + ).toBe(false) + }) + + it('should require a webhook URL for configured output webhooks', () => { + expect(isOutputComplete({ url: 'https://hooks.example.com/workflow', headersJson: '{}', prompt: '' })).toBe(true) + expect(isOutputComplete({ url: '', headersJson: '{}', prompt: 'Notify the team.' })).toBe(false) + }) +}) diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx new file mode 100644 index 00000000000..2494f20cf56 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx @@ -0,0 +1,624 @@ +import { type IconName } from '@fortawesome/fontawesome-common-types' +import { useNavigate } from '@tanstack/react-router' +import { type ReactNode, useEffect, useRef } from 'react' +import { + Button, + CodeEditor, + FunnelFlowBody, + Heading, + Icon, + InputText, + InputTextArea, + InputToggle, + Section, +} from '@qovery/shared/ui' +import { + type AgenticWorkflowConfigurationSection, + type AgenticWorkflowGitRepository, + type AgenticWorkflowOutput, + useAgenticWorkflowCreateContext, +} from '../agentic-workflow-context' +import { AIModelCards } from './ai-model-cards' +import { GitRepositoryCard } from './git-repository-card' + +const sectionOrder: AgenticWorkflowConfigurationSection[] = [ + 'service-information', + 'ai-model', + 'connectors', + 'git-repositories', + 'governance', + 'docker-fragment', + 'outputs', + 'agent-prompt', +] + +export function getJsonError(value: string, required = false) { + if (!value.trim()) return required ? 'Please enter a valid JSON configuration.' : undefined + + try { + JSON.parse(value) + return undefined + } catch { + return 'Invalid JSON format.' + } +} + +function isSectionCompleted( + section: AgenticWorkflowConfigurationSection, + activeSection: AgenticWorkflowConfigurationSection +) { + return sectionOrder.indexOf(section) < sectionOrder.indexOf(activeSection) +} + +function getSectionTitle(section: AgenticWorkflowConfigurationSection) { + const titles: Record = { + 'service-information': 'Service information', + 'ai-model': 'AI model', + connectors: 'MCPs', + 'git-repositories': 'Git repositories', + governance: 'Governance', + 'docker-fragment': 'Docker fragment', + outputs: 'Output webhooks', + 'agent-prompt': 'Agent prompt', + } + + return titles[section] +} + +export function isGitRepositoryComplete(repository: AgenticWorkflowGitRepository) { + return Boolean( + (repository.gitTokenId || repository.provider || repository.isPublicRepository) && + repository.repository.trim() && + repository.branch.trim() + ) +} + +export function isOutputComplete(output: AgenticWorkflowOutput) { + return Boolean(output.url.trim()) +} + +function AgenticWorkflowSection({ + children, + headerAction, + iconName, + invalid, + section, +}: { + children: ReactNode + headerAction?: ReactNode + iconName: IconName + invalid?: boolean + section: AgenticWorkflowConfigurationSection +}) { + const { activeSection, setActiveSection } = useAgenticWorkflowCreateContext() + const active = activeSection === section + const completed = isSectionCompleted(section, activeSection) + const showStatus = completed && !active + const title = getSectionTitle(section) + const headerContent = ( + <> +
+ + {title} +
+
+ {showStatus && + (invalid ? ( + + ) : ( + + ))} +
+ + ) + + return ( +
+ {active ? ( +
+ {headerContent} + {headerAction &&
{headerAction}
} +
+ ) : ( + + )} + {active &&
{children}
} +
+ ) +} + +function CodeEditorField({ + error, + height = '180px', + hint, + label, + language, + name, + onChange, + placeholder, + value, +}: { + error?: string + height?: string + hint?: ReactNode + label: string + language: string + name: string + onChange: (value: string) => void + placeholder?: string + value: string +}) { + return ( +
+ +
+ {placeholder && !value.trim() && ( +
+ {placeholder} +
+ )} + onChange(nextValue ?? '')} + options={{ + scrollbar: { alwaysConsumeMouseWheel: false }, + scrollBeyondLastLine: false, + wordWrap: 'on', + }} + /> +
+ {hint && !error &&
{hint}
} + {error &&

{error}

} +
+ ) +} + +export function AgenticWorkflowConfiguration() { + const navigate = useNavigate() + const { activeSection, creationFlowUrl, form, setActiveSection, setCurrentStep } = useAgenticWorkflowCreateContext() + const modelApiKeyInputRef = useRef(null) + const whitelistHostsTextareaRef = useRef(null) + const agentPromptTextareaRef = useRef(null) + const values = form.watch() + const { dirtyFields } = form.formState + const mcpJsonError = getJsonError(values.mcpJson) + const outputHeadersErrors = values.outputs.map((output) => getJsonError(output.headersJson)) + const modelSettingsJsonError = getJsonError(values.modelSettingsJson, true) + const gitRepositoriesValid = values.gitRepositories.every(isGitRepositoryComplete) + const outputsValid = values.outputs.every(isOutputComplete) + const showNameError = Boolean(dirtyFields.name) && !values.name.trim() + const showModelApiKeyError = Boolean(dirtyFields.modelApiKey) && !values.modelApiKey.trim() + const sectionInvalid: Record = { + 'service-information': !values.name.trim(), + 'ai-model': !values.modelApiKey.trim() || Boolean(modelSettingsJsonError), + connectors: Boolean(mcpJsonError), + 'git-repositories': !gitRepositoriesValid, + governance: false, + 'docker-fragment': false, + outputs: !outputsValid || outputHeadersErrors.some(Boolean), + 'agent-prompt': !values.agentPrompt.trim(), + } + const isValid = + Boolean(values.name.trim()) && + Boolean(values.modelApiKey.trim()) && + Boolean(values.agentPrompt.trim()) && + gitRepositoriesValid && + outputsValid && + !mcpJsonError && + outputHeadersErrors.every((error) => !error) && + !modelSettingsJsonError + + useEffect(() => { + setCurrentStep(1) + }, [setCurrentStep]) + + useEffect(() => { + const inputBySection: Partial> = { + 'ai-model': modelApiKeyInputRef.current, + governance: whitelistHostsTextareaRef.current, + 'agent-prompt': agentPromptTextareaRef.current, + } + + window.requestAnimationFrame(() => inputBySection[activeSection]?.focus()) + }, [activeSection]) + + const goToNextSection = () => { + const nextSection = sectionOrder[sectionOrder.indexOf(activeSection) + 1] + + if (nextSection) { + setActiveSection(nextSection) + return + } + + navigate({ to: `${creationFlowUrl}/summary` }) + } + + const addRepository = () => + form.setValue( + 'gitRepositories', + [ + ...values.gitRepositories, + { + provider: undefined, + gitTokenId: undefined, + gitTokenName: undefined, + isPublicRepository: false, + repository: '', + gitRepository: undefined, + branch: '', + }, + ], + { shouldDirty: true } + ) + + const addOutput = () => + form.setValue( + 'outputs', + [ + ...values.outputs, + { + url: '', + headersJson: `{ + "Authorization": "Bearer {{TOKEN}}" +}`, + prompt: '', + }, + ], + { + shouldDirty: true, + } + ) + + return ( + +
+
+ Create agentic workflow +

+ Configure the inputs, tools, model, governance, and outputs used by your workflow. +

+
+ +
+ + form.setValue('name', event.currentTarget.value, { shouldDirty: true })} + /> + form.setValue('description', event.currentTarget.value, { shouldDirty: true })} + /> +
+ form.setValue('cpu', event.currentTarget.value, { shouldDirty: true })} + /> + form.setValue('memory', event.currentTarget.value, { shouldDirty: true })} + /> + form.setValue('storage', event.currentTarget.value, { shouldDirty: true })} + /> +
+ form.setValue('workflowEnabled', value, { shouldDirty: true })} + /> + +
+ + + + form.setValue('modelApiKey', event.currentTarget.value, { shouldDirty: true })} + /> + + Need help configuring Claude Code settings? Read the{' '} + + Claude Code settings documentation + + . + + } + onChange={(value) => form.setValue('modelSettingsJson', value, { shouldDirty: true })} + /> + + + + + + See Claude Code docs for{' '} + + remote HTTP + {' '} + and{' '} + + local stdio + {' '} + servers. + + } + onChange={(value) => form.setValue('mcpJson', value, { shouldDirty: true })} + /> + + + + + + Add repository + + } + > + {values.gitRepositories.map((repository, index) => ( + { + const gitRepositories = [...values.gitRepositories] + gitRepositories[index] = nextRepository + form.setValue('gitRepositories', gitRepositories, { shouldDirty: true }) + }} + onRemove={() => + form.setValue( + 'gitRepositories', + values.gitRepositories.filter((_, repositoryIndex) => repositoryIndex !== index), + { shouldDirty: true } + ) + } + /> + ))} + {!gitRepositoriesValid && ( +

+ Select a Git account, repository, and branch for each repository. +

+ )} + +
+ + + form.setValue('whitelistHosts', event.currentTarget.value, { shouldDirty: true })} + /> + + + + + form.setValue('dockerFragment', value, { shouldDirty: true })} + /> + + + + + + Add webhook + + } + > + {values.outputs.map((output, index) => ( +
+
+ Webhook {index + 1} + +
+
+ { + const outputs = [...values.outputs] + outputs[index] = { ...output, url: event.currentTarget.value } + form.setValue('outputs', outputs, { shouldDirty: true }) + }} + /> + { + const outputs = [...values.outputs] + outputs[index] = { ...output, headersJson: value } + form.setValue('outputs', outputs, { shouldDirty: true }) + }} + /> + { + const outputs = [...values.outputs] + outputs[index] = { ...output, prompt: event.currentTarget.value } + form.setValue('outputs', outputs, { shouldDirty: true }) + }} + /> +
+
+ ))} + {!outputsValid && ( +

+ Enter a webhook URL for each configured output webhook. +

+ )} + +
+ + + form.setValue('agentPrompt', event.currentTarget.value, { shouldDirty: true })} + /> + + +
+ +
+
+ +
+
+
+
+ ) +} + +function ContinueButton({ disabled, onClick }: { disabled?: boolean; onClick: () => void }) { + return ( + + ) +} diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/ai-model-cards.spec.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/ai-model-cards.spec.tsx new file mode 100644 index 00000000000..1a34a2889b0 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/ai-model-cards.spec.tsx @@ -0,0 +1,37 @@ +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { AgenticWorkflowCreationFlow, useAgenticWorkflowCreateContext } from '../agentic-workflow-context' +import { AIModelCards } from './ai-model-cards' + +function SelectedModel() { + const { form } = useAgenticWorkflowCreateContext() + + return {form.watch('aiModel')} +} + +describe('AIModelCards', () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it('should keep Claude selectable and Bedrock disabled', async () => { + const { userEvent } = renderWithProviders( + + <> + + + + + ) + + expect(screen.getByTestId('selected-model')).toHaveTextContent('CLAUDE') + expect(screen.getByRole('button', { name: /bedrock/i })).toBeDisabled() + + await userEvent.click(screen.getByRole('button', { name: /claude/i })) + + expect(screen.getByTestId('selected-model')).toHaveTextContent('CLAUDE') + }) +}) diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/ai-model-cards.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/ai-model-cards.tsx new file mode 100644 index 00000000000..acb8eedccb9 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/ai-model-cards.tsx @@ -0,0 +1,40 @@ +import { AgenticWorkflowModelType } from 'qovery-typescript-axios' +import { IconEnum } from '@qovery/shared/enums' +import { Icon } from '@qovery/shared/ui' +import { useAgenticWorkflowCreateContext } from '../agentic-workflow-context' + +export function AIModelCards() { + const { form } = useAgenticWorkflowCreateContext() + const selectedModel = form.watch('aiModel') + + return ( +
+ + +
+ ) +} diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/git-repository-card.spec.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/git-repository-card.spec.tsx new file mode 100644 index 00000000000..3ea02939d86 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/git-repository-card.spec.tsx @@ -0,0 +1,59 @@ +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { GitRepositoryCard } from './git-repository-card' + +jest.mock('@tanstack/react-router', () => ({ + ...jest.requireActual('@tanstack/react-router'), + useParams: () => ({ organizationId: 'org-1' }), +})) + +jest.mock('@qovery/domains/organizations/feature', () => ({ + GitBranchSettings: ({ organizationId, gitTokenId }: { organizationId: string; gitTokenId?: string }) => ( +
+ {organizationId}:{gitTokenId} +
+ ), + GitProviderSetting: ({ organizationId }: { organizationId: string }) => ( +
{organizationId}
+ ), + GitRepositorySetting: ({ organizationId, gitTokenId }: { organizationId: string; gitTokenId?: string }) => ( +
+ {organizationId}:{gitTokenId} +
+ ), +})) + +describe('GitRepositoryCard', () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it('should render repository settings and remove the repository', async () => { + const onRemove = jest.fn() + const { userEvent } = renderWithProviders( + + ) + + expect(screen.getByText('Repository 1')).toBeInTheDocument() + expect(screen.getByTestId('git-provider-setting')).toHaveTextContent('org-1') + expect(screen.getByTestId('git-repository-setting')).toHaveTextContent('org-1:token-1') + expect(screen.getByTestId('git-branch-settings')).toHaveTextContent('org-1:token-1') + + await userEvent.click(screen.getByRole('button', { name: 'Remove' })) + + expect(onRemove).toHaveBeenCalledTimes(1) + }) +}) diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/git-repository-card.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/git-repository-card.tsx new file mode 100644 index 00000000000..00e6d11709a --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/git-repository-card.tsx @@ -0,0 +1,92 @@ +import { useParams } from '@tanstack/react-router' +import { type GitProviderEnum, type GitRepository } from 'qovery-typescript-axios' +import { useEffect } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import { GitBranchSettings, GitProviderSetting, GitRepositorySetting } from '@qovery/domains/organizations/feature' +import { Button } from '@qovery/shared/ui' +import { type AgenticWorkflowGitRepository } from '../agentic-workflow-context' + +export function GitRepositoryCard({ + index, + onChange, + onRemove, + repository, +}: { + index: number + onChange: (repository: AgenticWorkflowGitRepository) => void + onRemove: () => void + repository: AgenticWorkflowGitRepository +}) { + const { organizationId = '' } = useParams({ strict: false }) + const methods = useForm<{ + provider?: keyof typeof GitProviderEnum | string | null + is_public_repository?: boolean + repository: string + branch: string + git_token_name?: string | null + git_token_id?: string | null + git_repository?: GitRepository + }>({ + defaultValues: { + provider: repository.provider, + is_public_repository: repository.isPublicRepository, + repository: repository.repository, + branch: repository.branch, + git_token_name: repository.gitTokenName, + git_token_id: repository.gitTokenId, + git_repository: repository.gitRepository, + }, + mode: 'onChange', + }) + const provider = methods.watch('provider') as keyof typeof GitProviderEnum | undefined + const watchedRepository = methods.watch('repository') + const gitTokenId = methods.watch('git_token_id') ?? undefined + const isPublicRepository = methods.watch('is_public_repository') + + useEffect(() => { + const subscription = methods.watch((values) => { + onChange({ + provider: values.provider, + gitTokenId: values.git_token_id, + gitTokenName: values.git_token_name, + isPublicRepository: values.is_public_repository, + repository: values.repository ?? '', + gitRepository: values.git_repository as GitRepository | undefined, + branch: values.branch ?? '', + }) + }) + + return () => subscription.unsubscribe() + }, [methods, onChange]) + + return ( +
+
+ Repository {index + 1} + +
+ +
+ + {isPublicRepository ? null : ( + <> + {provider && ( + + )} + {provider && watchedRepository && ( + + )} + + )} +
+
+
+ ) +} diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-context.spec.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-context.spec.tsx new file mode 100644 index 00000000000..9bb1252a8f3 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-context.spec.tsx @@ -0,0 +1,33 @@ +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { AgenticWorkflowCreationFlow, useAgenticWorkflowCreateContext } from './agentic-workflow-context' + +function FormDefaults() { + const { form } = useAgenticWorkflowCreateContext() + const values = form.getValues() + + return ( + <> + {values.cpu} + {values.memory} + {values.storage} + {values.whitelistHosts} + {String(values.workflowEnabled)} + + ) +} + +describe('AgenticWorkflowCreationFlow', () => { + it('should initialize the form with MVP defaults', () => { + renderWithProviders( + + + + ) + + expect(screen.getByTestId('cpu')).toHaveTextContent('2000') + expect(screen.getByTestId('memory')).toHaveTextContent('2048') + expect(screen.getByTestId('storage')).toHaveTextContent('10') + expect(screen.getByTestId('whitelist-hosts')).toHaveTextContent('*') + expect(screen.getByTestId('workflow-enabled')).toHaveTextContent('true') + }) +}) diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-context.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-context.tsx new file mode 100644 index 00000000000..43959558c55 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-context.tsx @@ -0,0 +1,172 @@ +import { AgenticWorkflowModelType, type GitProviderEnum, type GitRepository } from 'qovery-typescript-axios' +import { type PropsWithChildren, createContext, useContext, useState } from 'react' +import { FormProvider, type UseFormReturn, useForm } from 'react-hook-form' +import { FunnelFlow } from '@qovery/shared/ui' + +const DEFAULT_MODEL_SETTINGS = `{ + "provider": "anthropic", + "models": [ + { + "name": "claude-sonnet-4" + }, + { + "name": "claude-haiku-4" + } + ], + "extendedThinking": false, + "tools": [ + "Agent", + "Bash", + "Edit", + "Glob", + "Grep", + "LSP", + "NotebookEdit", + "Read", + "Skill", + "TaskCreate", + "TaskGet", + "TaskList", + "TaskUpdate", + "TodoWrite", + "WebFetch", + "WebSearch", + "Write" + ] +}` + +export const MCP_CONNECTOR_JSON_EXAMPLE = `{ + "mcpServers": { + "investigator": { + "type": "stdio", + "command": "target/debug/mcp_server_example", + "args": [], + "env": { + "INVESTIGATOR_TOKEN": "{{INVESTIGATOR_TOKEN}}" + } + }, + "costory": { + "type": "http", + "url": "https://app-api.costory.io/mcp", + "headers": { + "Authorization": "Bearer {{COSTORY_TOKEN}}" + } + } + } +}` + +export const agenticWorkflowCreationSteps: { title: string }[] = [{ title: 'Configuration' }, { title: 'Summary' }] + +export type AgenticWorkflowConfigurationSection = + | 'service-information' + | 'ai-model' + | 'connectors' + | 'git-repositories' + | 'governance' + | 'docker-fragment' + | 'outputs' + | 'agent-prompt' + +export interface AgenticWorkflowOutput { + url: string + headersJson: string + prompt: string +} + +export interface AgenticWorkflowGitRepository { + provider?: keyof typeof GitProviderEnum | string | null + gitTokenId?: string | null + gitTokenName?: string | null + isPublicRepository?: boolean + repository: string + gitRepository?: GitRepository + branch: string +} + +export interface AgenticWorkflowFormData { + name: string + description: string + cpu: string + memory: string + storage: string + workflowEnabled: boolean + aiModel: AgenticWorkflowModelType + webhookEnabled: boolean + mcpJson: string + gitRepositories: AgenticWorkflowGitRepository[] + modelApiKey: string + modelSettingsJson: string + whitelistHosts: string + dockerFragment: string + outputs: AgenticWorkflowOutput[] + agentPrompt: string +} + +export interface AgenticWorkflowCreateContextInterface { + activeSection: AgenticWorkflowConfigurationSection + creationFlowUrl: string + currentStep: number + form: UseFormReturn + setActiveSection: (section: AgenticWorkflowConfigurationSection) => void + setCurrentStep: (step: number) => void +} + +const AgenticWorkflowCreateContext = createContext(undefined) + +export function useAgenticWorkflowCreateContext() { + const context = useContext(AgenticWorkflowCreateContext) + + if (!context) { + throw new Error('useAgenticWorkflowCreateContext must be used within AgenticWorkflowCreationFlow') + } + + return context +} + +export interface AgenticWorkflowCreationFlowProps extends PropsWithChildren { + creationFlowUrl: string + onExit: () => void +} + +export function AgenticWorkflowCreationFlow({ children, creationFlowUrl, onExit }: AgenticWorkflowCreationFlowProps) { + const [currentStep, setCurrentStep] = useState(1) + const [activeSection, setActiveSection] = useState('service-information') + const form = useForm({ + defaultValues: { + name: '', + description: '', + cpu: '2000', + memory: '2048', + storage: '10', + workflowEnabled: true, + aiModel: AgenticWorkflowModelType.CLAUDE, + webhookEnabled: true, + mcpJson: MCP_CONNECTOR_JSON_EXAMPLE, + gitRepositories: [], + modelApiKey: '', + modelSettingsJson: DEFAULT_MODEL_SETTINGS, + whitelistHosts: '*', + dockerFragment: '', + outputs: [], + agentPrompt: '', + }, + mode: 'onChange', + }) + + return ( + + + + {children} + + + + ) +} diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-request.ts b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-request.ts new file mode 100644 index 00000000000..2f72912f395 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-request.ts @@ -0,0 +1,55 @@ +import { type AgenticWorkflowHeader, type AgenticWorkflowRequest } from 'qovery-typescript-axios' +import { type AgenticWorkflowFormData } from './agentic-workflow-context' + +function formatWhitelistHosts(value: string) { + return value + .split(',') + .map((host) => host.trim()) + .filter(Boolean) +} + +function parseHeaders(headersJson: string): AgenticWorkflowHeader[] { + if (!headersJson.trim()) { + return [] + } + + const parsedValue = JSON.parse(headersJson) + + if (!parsedValue || typeof parsedValue !== 'object' || Array.isArray(parsedValue)) { + return [] + } + + return Object.entries(parsedValue) + .filter((entry): entry is [string, string] => typeof entry[1] === 'string') + .map(([name, value]) => ({ name, value })) +} + +export function formatAgenticWorkflowRequest(values: AgenticWorkflowFormData): AgenticWorkflowRequest { + return { + name: values.name, + description: values.description, + docker_fragment: values.dockerFragment, + enabled: values.workflowEnabled, + mcp: values.mcpJson.trim() || undefined, + outputs: values.outputs.map((output, index) => ({ + name: `Output ${index + 1}`, + url: output.url, + headers: parseHeaders(output.headersJson), + instructions: output.prompt, + })), + model: { + type: values.aiModel, + api_key: values.modelApiKey, + settings: values.modelSettingsJson, + }, + project_repositories: values.gitRepositories.map((repository) => ({ + url: repository.repository, + branch: repository.branch, + git_token_id: repository.gitTokenId ?? '', + })), + agent_prompt: values.agentPrompt, + governance: { + host_allowlist: formatWhitelistHosts(values.whitelistHosts), + }, + } +} diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-summary.spec.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-summary.spec.tsx new file mode 100644 index 00000000000..465f0273168 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-summary.spec.tsx @@ -0,0 +1,138 @@ +import { useLayoutEffect, useState } from 'react' +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { + AgenticWorkflowCreationFlow, + type AgenticWorkflowFormData, + useAgenticWorkflowCreateContext, +} from './agentic-workflow-context' +import { AgenticWorkflowSummary } from './agentic-workflow-summary' + +const mockNavigate = jest.fn() +const mockCreateService = jest.fn() + +jest.mock('@tanstack/react-router', () => ({ + ...jest.requireActual('@tanstack/react-router'), + useNavigate: () => mockNavigate, + useParams: () => ({ + organizationId: 'org-1', + projectId: 'project-1', + environmentId: 'environment-1', + }), +})) + +jest.mock('posthog-js', () => ({ + capture: jest.fn(), +})) + +jest.mock('../../hooks/use-create-service/use-create-service', () => ({ + ...jest.requireActual('../../hooks/use-create-service/use-create-service'), + useCreateService: () => ({ + isLoading: false, + mutateAsync: mockCreateService, + }), +})) + +const validValues: Partial = { + name: 'review-agent', + description: 'Reviews incoming changes', + modelApiKey: 'sk-ant-test', + agentPrompt: 'Review payloads and open pull requests.', + whitelistHosts: '*', + mcpJson: '{"mcpServers":{"costory":{"type":"http","url":"https://app-api.costory.io/mcp"}}}', + outputs: [{ url: 'https://hooks.example.com/workflow', headersJson: '{}', prompt: 'Notify the team.' }], +} + +function WithFormValues({ + children, + values = validValues, +}: { + children: JSX.Element + values?: Partial +}) { + const { form } = useAgenticWorkflowCreateContext() + const [ready, setReady] = useState(false) + + useLayoutEffect(() => { + form.reset({ + ...form.getValues(), + ...values, + }) + setReady(true) + }, [form, values]) + + if (!ready) { + return null + } + + return children +} + +function renderSummary(values?: Partial) { + return renderWithProviders( + + + + + + ) +} + +describe('AgenticWorkflowSummary', () => { + beforeEach(() => { + jest.useFakeTimers() + jest.clearAllMocks() + mockCreateService.mockResolvedValue({ id: 'workflow-1' }) + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it('should render configured values from the configuration form', () => { + renderSummary({ + ...validValues, + gitRepositories: [ + { + provider: 'GITHUB', + repository: 'Qovery/console', + branch: 'staging', + }, + ], + }) + + expect(screen.getByText('review-agent')).toBeInTheDocument() + expect(screen.getByText('Reviews incoming changes')).toBeInTheDocument() + expect(screen.getByText('********')).toBeInTheDocument() + expect(screen.getByText('*')).toBeInTheDocument() + expect(screen.getByText(validValues.mcpJson ?? '')).toBeInTheDocument() + expect(screen.getByText('1 webhook')).toBeInTheDocument() + }) + + it('should navigate back to the edited section from summary', async () => { + const { userEvent } = renderSummary() + + await userEvent.click(screen.getByRole('button', { name: 'Edit AI model' })) + + expect(mockNavigate).toHaveBeenCalledWith({ to: '/create/agentic-workflow/configuration' }) + }) + + it('should create the agentic workflow and navigate back to the environment overview', async () => { + const { userEvent } = renderSummary() + + await userEvent.click(screen.getByRole('button', { name: 'Create' })) + + expect(mockCreateService).toHaveBeenCalledWith({ + environmentId: 'environment-1', + payload: expect.objectContaining({ + serviceType: 'AGENTIC_WORKFLOW', + name: 'review-agent', + mcp: validValues.mcpJson, + outputs: [expect.objectContaining({ url: 'https://hooks.example.com/workflow' })], + }), + }) + expect(mockNavigate).toHaveBeenCalledWith({ + to: '/organization/$organizationId/project/$projectId/environment/$environmentId/overview', + params: { organizationId: 'org-1', projectId: 'project-1', environmentId: 'environment-1' }, + }) + }) +}) diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-summary.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-summary.tsx new file mode 100644 index 00000000000..b8304dc60a5 --- /dev/null +++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-summary.tsx @@ -0,0 +1,184 @@ +import { useNavigate, useParams } from '@tanstack/react-router' +import posthog from 'posthog-js' +import { type ReactNode, useEffect } from 'react' +import { Button, FunnelFlowBody, Heading, Icon, Section, SummaryValue, truncateText } from '@qovery/shared/ui' +import { pluralize } from '@qovery/shared/util-js' +import { useCreateService } from '../../hooks/use-create-service/use-create-service' +import { isGitRepositoryComplete } from './agentic-workflow-configuration/agentic-workflow-configuration' +import { + type AgenticWorkflowConfigurationSection, + type AgenticWorkflowFormData, + useAgenticWorkflowCreateContext, +} from './agentic-workflow-context' +import { formatAgenticWorkflowRequest } from './agentic-workflow-request' + +function truncateSummary(value: string) { + if (!value.trim()) return '-' + + return value.length > 160 ? `${truncateText(value, 157)}…` : value +} + +function formatCount(count: number, singular: string) { + if (count === 0) return 'None' + + return `${count} ${pluralize(count, singular)}` +} + +function maskValue(value: string) { + return value.trim() ? '********' : '-' +} + +function hasIncompleteGitRepository(values: AgenticWorkflowFormData) { + return values.gitRepositories.some((repository) => !isGitRepositoryComplete(repository)) +} + +function hasIncompleteOutput(values: AgenticWorkflowFormData) { + return values.outputs.some((output) => { + if (!output.url.trim()) return true + + if (!output.headersJson.trim()) return false + + try { + JSON.parse(output.headersJson) + return false + } catch { + return true + } + }) +} + +function SummarySection({ children, onEdit, title }: { children: ReactNode; onEdit: () => void; title: string }) { + return ( +
+
+ {title} + +
+
    {children}
+
+ ) +} + +export function AgenticWorkflowSummary() { + const navigate = useNavigate() + const { environmentId = '', organizationId = '', projectId = '' } = useParams({ strict: false }) + const { creationFlowUrl, form, setActiveSection, setCurrentStep } = useAgenticWorkflowCreateContext() + const { isLoading: isCreating, mutateAsync: createService } = useCreateService({ organizationId }) + const values = form.watch() + + useEffect(() => { + setCurrentStep(2) + }, [setCurrentStep]) + + useEffect(() => { + if ( + !values.name.trim() || + !values.modelApiKey.trim() || + !values.agentPrompt.trim() || + hasIncompleteGitRepository(values) || + hasIncompleteOutput(values) + ) { + navigate({ to: `${creationFlowUrl}/configuration` }) + } + }, [creationFlowUrl, navigate, values]) + + const handleEditSection = (section: AgenticWorkflowConfigurationSection) => { + setActiveSection(section) + navigate({ to: `${creationFlowUrl}/configuration` }) + } + + const handleCreate = async () => { + await createService({ + environmentId, + payload: { + serviceType: 'AGENTIC_WORKFLOW', + ...formatAgenticWorkflowRequest(form.getValues()), + }, + }) + posthog.capture('create-service', { + selectedServiceType: 'agentic-workflow', + }) + navigate({ + to: '/organization/$organizationId/project/$projectId/environment/$environmentId/overview', + params: { organizationId, projectId, environmentId }, + }) + } + + return ( + +
+
+ Ready to create your agentic workflow +

+ Review the workflow configuration before creating it. Webhook details will be generated after creation. +

+
+ +
+ handleEditSection('service-information')}> + + + + + + + + + handleEditSection('ai-model')}> + + + + + + handleEditSection('connectors')}> + + + + handleEditSection('git-repositories')}> + + + + handleEditSection('governance')}> + + + + handleEditSection('docker-fragment')}> + + + + handleEditSection('outputs')}> + + + + handleEditSection('agent-prompt')}> + + +
+ +
+ + +
+
+
+ ) +} diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx index f1b830bee7f..279835fe860 100644 --- a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx +++ b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx @@ -301,7 +301,7 @@ export function ServiceVersionCell({ service }: ServiceVersionCellProps) { .with({ service: { serviceType: 'TERRAFORM' } }, ({ service }) => { return gitInfo(service, service?.terraform_files_source?.git?.git_repository) }) - .with({ service: { serviceType: 'ARGOCD_APP' } }, () => null) + .with({ service: { serviceType: P.union('ARGOCD_APP', 'AGENTIC_WORKFLOW') } }, () => null) .exhaustive() return cell } diff --git a/libs/domains/services/feature/src/lib/service-new/service-new.spec.tsx b/libs/domains/services/feature/src/lib/service-new/service-new.spec.tsx index da887bce673..ace0ba99c94 100644 --- a/libs/domains/services/feature/src/lib/service-new/service-new.spec.tsx +++ b/libs/domains/services/feature/src/lib/service-new/service-new.spec.tsx @@ -131,6 +131,21 @@ describe('ServiceNew', () => { expect(screen.getByText('Cron Job')).toBeInTheDocument() expect(screen.getByText('Helm')).toBeInTheDocument() expect(screen.getAllByText('Terraform').length).toBeGreaterThanOrEqual(1) + expect(screen.queryByText('Agentic workflow')).not.toBeInTheDocument() + }) + + it('should render agentic workflow entry when feature flag is enabled', () => { + mockUseFeatureFlagEnabled.mockImplementation((flag: string) => flag === 'argentic-workflow') + + renderWithProviders( + + ) + + expect(screen.getByText('Agentic workflow')).toBeInTheDocument() + expect(screen.getByRole('link', { name: /Agentic workflow/i })).toHaveAttribute( + 'href', + '/organization/org-1/project/project-1/environment/env-1/service/create/agentic-workflow' + ) }) it('should show base service descriptions in info tooltips', async () => { diff --git a/libs/domains/services/feature/src/lib/service-new/service-new.tsx b/libs/domains/services/feature/src/lib/service-new/service-new.tsx index 2a790b6be0b..9461d2c3eb4 100644 --- a/libs/domains/services/feature/src/lib/service-new/service-new.tsx +++ b/libs/domains/services/feature/src/lib/service-new/service-new.tsx @@ -157,10 +157,23 @@ export function ServiceNew({ }: ServiceNewProps) { const isTerraformFeatureFlag = Boolean(useFeatureFlagEnabled('terraform')) const isServiceCatalogEnabled = Boolean(useFeatureFlagEnabled('service-catalog')) + const isAgenticWorkflowEnabled = Boolean(useFeatureFlagEnabled('argentic-workflow')) const { showPylonForm } = useSupportChat() const serviceEmpty: ServiceBlock[] = useMemo( () => [ + ...(isAgenticWorkflowEnabled + ? [ + { + title: 'Agentic workflow', + description: 'Run an AI workflow with webhooks, MCP connectors, governance, and configured outputs.', + icon: , + link: getServicesPath(organizationId, projectId, environmentId, '/service/create/agentic-workflow'), + cloud_provider: cloudProvider, + badge: 'BETA', + }, + ] + : []), { title: 'Application', description: 'Deploy a long running service running from Git or a Container Registry.', @@ -228,7 +241,15 @@ export function ServiceNew({ }, ]), ], - [cloudProvider, organizationId, projectId, environmentId, isTerraformFeatureFlag, showPylonForm] + [ + cloudProvider, + environmentId, + isAgenticWorkflowEnabled, + isTerraformFeatureFlag, + organizationId, + projectId, + showPylonForm, + ] ) const [blueprintSearchInput, setBlueprintSearchInput] = useState('') diff --git a/libs/domains/services/feature/src/lib/services-deployment-pipeline/services-deployment-pipeline/services-deployment-pipeline.tsx b/libs/domains/services/feature/src/lib/services-deployment-pipeline/services-deployment-pipeline/services-deployment-pipeline.tsx index 88cb181187a..88964c581e7 100644 --- a/libs/domains/services/feature/src/lib/services-deployment-pipeline/services-deployment-pipeline/services-deployment-pipeline.tsx +++ b/libs/domains/services/feature/src/lib/services-deployment-pipeline/services-deployment-pipeline/services-deployment-pipeline.tsx @@ -92,6 +92,7 @@ function getServiceIconName(service: AnyService) { .with({ serviceType: 'JOB' }, () => IconEnum.CRON_JOB) .with({ serviceType: 'TERRAFORM' }, () => IconEnum.TERRAFORM) .with({ serviceType: 'ARGOCD_APP' }, () => IconEnum.ARGOCD) + .with({ serviceType: 'AGENTIC_WORKFLOW' }, () => IconEnum.AGENTIC_WORKFLOW) .exhaustive() } diff --git a/libs/shared/enums/src/lib/icon.enum.ts b/libs/shared/enums/src/lib/icon.enum.ts index 9d1c55c5773..487de20127d 100644 --- a/libs/shared/enums/src/lib/icon.enum.ts +++ b/libs/shared/enums/src/lib/icon.enum.ts @@ -7,6 +7,7 @@ export enum IconEnum { ENVIRONMENT = 'ENVIRONMENT', AZURE = 'AZURE', APPLICATION = 'APPLICATION', + AGENTIC_WORKFLOW = 'AGENTIC_WORKFLOW', ARGOCD = 'ARGOCD', SERVICES = 'SERVICES', DATABASE = 'DATABASE', diff --git a/libs/shared/enums/src/lib/service-type.enum.ts b/libs/shared/enums/src/lib/service-type.enum.ts index 616c1a4a5fc..a62fe89aefb 100644 --- a/libs/shared/enums/src/lib/service-type.enum.ts +++ b/libs/shared/enums/src/lib/service-type.enum.ts @@ -2,6 +2,7 @@ export enum ServiceTypeEnum { ARGOCD_APP = 'ARGOCD_APP', DATABASE = 'DATABASE', APPLICATION = 'APPLICATION', + AGENTIC_WORKFLOW = 'AGENTIC_WORKFLOW', CONTAINER = 'CONTAINER', JOB = 'JOB', CRON_JOB = 'CRON_JOB', diff --git a/libs/shared/spotlight/feature/src/lib/hooks/use-quick-actions/use-quick-actions.ts b/libs/shared/spotlight/feature/src/lib/hooks/use-quick-actions/use-quick-actions.ts index 5015e2ae8f9..09c3e411a53 100644 --- a/libs/shared/spotlight/feature/src/lib/hooks/use-quick-actions/use-quick-actions.ts +++ b/libs/shared/spotlight/feature/src/lib/hooks/use-quick-actions/use-quick-actions.ts @@ -56,7 +56,8 @@ export function useQuickActions(): QuickAction[] { iconName: 'clock-rotate-left', link: AUDIT_LOGS_PARAMS_URL(organizationId, { targetId: serviceId, - targetType: isEditableServiceType(serviceType) ? serviceType : undefined, + targetType: + serviceType !== 'AGENTIC_WORKFLOW' && isEditableServiceType(serviceType) ? serviceType : undefined, projectId, environmentId, }), diff --git a/libs/shared/ui/src/lib/components/icon/icon.tsx b/libs/shared/ui/src/lib/components/icon/icon.tsx index 6b721a91f94..cb0d5c5c46d 100644 --- a/libs/shared/ui/src/lib/components/icon/icon.tsx +++ b/libs/shared/ui/src/lib/components/icon/icon.tsx @@ -6,6 +6,7 @@ import { type IconAwesomeEnum } from './icon-awesome.enum' import BitbucketIcon from './icons-git/bitbucket' import { GithubIcon, GithubWhiteIcon } from './icons-git/github' import GitlabIcon from './icons-git/gitlab' +import AgenticWorkflowIcon from './icons/agentic-workflow' import ApplicationIcon from './icons/application' import ArgoCdIcon from './icons/argocd' import AWSIcon from './icons/aws' @@ -117,6 +118,8 @@ export const Icon = forwardRef(fu return case IconEnum.APPLICATION: return + case IconEnum.AGENTIC_WORKFLOW: + return case IconEnum.ARGOCD: return case IconEnum.AZURE: diff --git a/libs/shared/ui/src/lib/components/icon/icons/agentic-workflow.tsx b/libs/shared/ui/src/lib/components/icon/icons/agentic-workflow.tsx new file mode 100644 index 00000000000..c469ad03488 --- /dev/null +++ b/libs/shared/ui/src/lib/components/icon/icons/agentic-workflow.tsx @@ -0,0 +1,20 @@ +import { type IconProps } from '../icon' + +export function AgenticWorkflowIcon(props: IconProps) { + return ( + + + + + + + + + + + + + ) +} + +export default AgenticWorkflowIcon diff --git a/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.spec.ts b/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.spec.ts index 1a49e0985e9..e2206340677 100644 --- a/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.spec.ts +++ b/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.spec.ts @@ -37,6 +37,15 @@ describe('computeAvailableEnvironmentVariableScope', () => { const scopes = computeAvailableScope(undefined, false, 'PROJECT') expect(scopes).toEqual([APIVariableScopeEnum.PROJECT]) }) + + it('should use the application scope for an agentic workflow', () => { + const scopes = computeAvailableScope(undefined, false, 'AGENTIC_WORKFLOW') + expect(scopes).toEqual([ + APIVariableScopeEnum.PROJECT, + APIVariableScopeEnum.ENVIRONMENT, + APIVariableScopeEnum.APPLICATION, + ]) + }) }) describe('when the scope is set', () => { diff --git a/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.ts b/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.ts index 250e44ecc77..47657755b95 100644 --- a/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.ts +++ b/libs/shared/util-js/src/lib/compute-available-environment-variable-scope.ts @@ -35,12 +35,10 @@ function scopeHierarchy(targetScope: APIVariableScopeEnum) { function targetToScope(target: keyof typeof APIVariableScopeEnum | keyof typeof ServiceTypeEnum): APIVariableScopeEnum { return match(target) - .with('APPLICATION', () => APIVariableScopeEnum.APPLICATION) - .with('DATABASE', () => APIVariableScopeEnum.APPLICATION) + .with('APPLICATION', 'DATABASE', 'ARGOCD_APP', 'AGENTIC_WORKFLOW', () => APIVariableScopeEnum.APPLICATION) .with('CONTAINER', () => APIVariableScopeEnum.CONTAINER) .with('JOB', 'CRON_JOB', 'LIFECYCLE_JOB', () => APIVariableScopeEnum.JOB) .with('HELM', () => APIVariableScopeEnum.HELM) - .with('ARGOCD_APP', () => APIVariableScopeEnum.APPLICATION) .with('BUILT_IN', () => APIVariableScopeEnum.BUILT_IN) .with('PROJECT', () => APIVariableScopeEnum.PROJECT) .with('ENVIRONMENT', () => APIVariableScopeEnum.ENVIRONMENT) diff --git a/package.json b/package.json index 81e51a29890..600aadd193e 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "mermaid": "11.6.0", "monaco-editor": "0.53.0", "posthog-js": "1.345.1", - "qovery-typescript-axios": "1.1.934", + "qovery-typescript-axios": "1.1.940", "react": "18.3.1", "react-country-flag": "3.0.2", "react-datepicker": "4.12.0", diff --git a/yarn.lock b/yarn.lock index f5e9b3bada0..9a22ead7614 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6343,7 +6343,7 @@ __metadata: prettier: 3.2.5 prettier-plugin-tailwindcss: 0.5.14 pretty-quick: 4.0.0 - qovery-typescript-axios: 1.1.934 + qovery-typescript-axios: 1.1.940 qovery-ws-typescript-axios: 0.1.621 react: 18.3.1 react-country-flag: 3.0.2 @@ -25863,12 +25863,12 @@ __metadata: languageName: node linkType: hard -"qovery-typescript-axios@npm:1.1.934": - version: 1.1.934 - resolution: "qovery-typescript-axios@npm:1.1.934" +"qovery-typescript-axios@npm:1.1.940": + version: 1.1.940 + resolution: "qovery-typescript-axios@npm:1.1.940" dependencies: axios: 1.18.1 - checksum: 916504676e45b051ada1c1a910d655e5ac3f7fada105e1a48e7e91e0b626ebd00a195741c09be2811612a7820f8cf005b185fd2cec7554cab54f85a1871532a7 + checksum: 03bc438a48938bfa3b7a8391c046c75d0d2563fa41e9c030714fe8510ebf1c5f28e5b2b981165fe1e53f59453699bf61316e18783d447d60931cf3373f4fa251 languageName: node linkType: hard