diff --git a/package.json b/package.json index 8cc56dbbd57..d12a443dc2b 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@modelcontextprotocol/sdk": "1.25.2", "@nx/devkit": "22.0.1", "@octokit/rest": "21.1.1", - "@openrouter/ai-sdk-provider": "1.4.1", + "@openrouter/ai-sdk-provider": "2.1.1", "@openrouter/sdk": "0.2.9", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.206.0", diff --git a/packages/ee/ui/embed-sdk/src/index.ts b/packages/ee/ui/embed-sdk/src/index.ts index 1dc0701f675..4151e28ee30 100644 --- a/packages/ee/ui/embed-sdk/src/index.ts +++ b/packages/ee/ui/embed-sdk/src/index.ts @@ -11,6 +11,8 @@ export enum ActivepiecesClientEventName { CLIENT_CONFIGURATION_FINISHED = 'CLIENT_CONFIGURATION_FINISHED', CLIENT_CONNECTION_PIECE_NOT_FOUND = 'CLIENT_CONNECTION_PIECE_NOT_FOUND', CLIENT_BUILDER_HOME_BUTTON_CLICKED = 'CLIENT_BUILDER_HOME_BUTTON_CLICKED', + CLIENT_STEP_SETTINGS_DIALOG_CLOSED = 'CLIENT_STEP_SETTINGS_DIALOG_CLOSED', + CLIENT_SHOW_STEP_SETTINGS_IFRAME = 'CLIENT_SHOW_STEP_SETTINGS_IFRAME', } export interface ActivepiecesClientInit { type: ActivepiecesClientEventName.CLIENT_INIT; @@ -63,6 +65,14 @@ export interface ActivepiecesBuilderHomeButtonClicked { route: string; }; } +export interface ActivepiecesClientShowStepSettingsIframe { + type: ActivepiecesClientEventName.CLIENT_SHOW_STEP_SETTINGS_IFRAME; + data: Record; +} +export interface ActivepiecesStepSettingsDialogClosed { + type: ActivepiecesClientEventName.CLIENT_STEP_SETTINGS_DIALOG_CLOSED; + data: Record; +} type IframeWithWindow = HTMLIFrameElement & { contentWindow: Window }; @@ -72,6 +82,12 @@ export const NEW_CONNECTION_QUERY_PARAMS = { randomId: 'randomId' }; +export const STEP_SETTINGS_QUERY_PARAMS = { + stepName: 'stepName', + flowVersionId: 'flowVersionId', + flowId: 'flowId', +}; + export type ActivepiecesClientEvent = | ActivepiecesClientInit | ActivepiecesClientRouteChanged; @@ -165,6 +181,8 @@ class ActivepiecesEmbedded { _resolveNewConnectionDialogClosed?: (result: ActivepiecesNewConnectionDialogClosed['data']) => void; _dashboardAndBuilderIframeWindow?: Window; _rejectNewConnectionDialogClosed?: (error: unknown) => void; + _resolveStepSettingsDialogClosed?: () => void; + _rejectStepSettingsDialogClosed?: (error: unknown) => void; _handleVendorNavigation?: (data: { route: string }) => void; _handleClientNavigation?: (data: { route: string }) => void; _parentOrigin = window.location.origin; @@ -362,12 +380,7 @@ class ActivepiecesEmbedded { async connect({ pieceName, connectionName, newWindow }: { pieceName: string, connectionName?: string, - newWindow?:{ - height?: number, - width?: number, - top?: number, - left?: number, - } + newWindow?:newWindowFeatures }) { this._cleanConnectionIframe(); return this._addGracePeriodBeforeMethod({ @@ -398,6 +411,59 @@ class ActivepiecesEmbedded { } + private _addStepSettingsIframe({stepName, flowVersionId, flowId}:{stepName:string, flowVersionId:string, flowId:string}) { + const stepSettingsIframe = this.connectToEmbed({ + iframeContainer: document.body, + initialRoute: `/embed/step-settings?${STEP_SETTINGS_QUERY_PARAMS.stepName}=${stepName}&${STEP_SETTINGS_QUERY_PARAMS.flowVersionId}=${flowVersionId}&${STEP_SETTINGS_QUERY_PARAMS.flowId}=${flowId}` + }); + stepSettingsIframe.style.cssText = ['display:none', 'position:fixed', 'top:0', 'left:0', 'width:100%', 'height:100%', 'border:none'].join(';'); + return stepSettingsIframe; + } + + private _openNewWindowForStepSettings({stepName, flowVersionId, flowId, newWindow}:{stepName:string, flowVersionId:string, flowId:string, newWindow:newWindowFeatures}) { + const popup = window.open(`${this._instanceUrl}/embed`, '_blank', this._getNewWindowFeatures(newWindow)); + if (!popup) { + this._errorCreator('Failed to open popup window'); + } + this._setupInitialMessageHandler(popup, `/embed/step-settings?${STEP_SETTINGS_QUERY_PARAMS.stepName}=${stepName}&${STEP_SETTINGS_QUERY_PARAMS.flowVersionId}=${flowVersionId}&${STEP_SETTINGS_QUERY_PARAMS.flowId}=${flowId}`); + return popup; + } + + async openStepSettings({ stepName, flowVersionId, flowId, newWindow }: { + stepName: string, + flowVersionId: string, + flowId: string, + newWindow?:newWindowFeatures + }) { + this._cleanStepSettingsIframe(); + return this._addGracePeriodBeforeMethod({ + condition: () => { + return !!document.body; + }, + method: async () => { + const target = newWindow? this._openNewWindowForStepSettings({stepName, flowVersionId, flowId, newWindow}) : this._addStepSettingsIframe({stepName, flowVersionId, flowId}); + //don't check for window because (instanceof Window) is false for popups + if(!(target instanceof HTMLIFrameElement)) { + const checkClosed = setInterval(() => { + if (target.closed) { + clearInterval(checkClosed); + if(this._resolveStepSettingsDialogClosed) { + this._resolveStepSettingsDialogClosed() + } + } + }, 500); + } + return new Promise((resolve, reject) => { + this._resolveStepSettingsDialogClosed = resolve; + this._rejectStepSettingsDialogClosed = reject; + this._setStepSettingsIframeEventsListener(target); + }); + }, + errorMessage: 'unable to add step settings embedding' + }); + } + + navigate({ route }: { route: string }) { if (!this._dashboardAndBuilderIframeWindow) { this._logger().error('dashboard iframe not found'); @@ -458,6 +524,8 @@ class ActivepiecesEmbedded { } // eslint-disable-next-line @typescript-eslint/no-empty-function private _cleanConnectionIframe = () => { }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + private _cleanStepSettingsIframe = () => { }; private _setConnectionIframeEventsListener(target: Window | HTMLIFrameElement ) { const connectionRelatedMessageHandler = (event: MessageEvent) => { if (event.data.type) { @@ -503,6 +571,39 @@ class ActivepiecesEmbedded { } } + private _setStepSettingsIframeEventsListener(target: Window | HTMLIFrameElement ) { + const stepSettingsRelatedMessageHandler = (event: MessageEvent) => { + if (event.data.type) { + switch (event.data.type) { + case ActivepiecesClientEventName.CLIENT_STEP_SETTINGS_DIALOG_CLOSED: { + if (this._resolveStepSettingsDialogClosed) { + this._resolveStepSettingsDialogClosed(); + } + this._removeEmbedding(target); + window.removeEventListener('message', stepSettingsRelatedMessageHandler); + break; + } + case ActivepiecesClientEventName.CLIENT_SHOW_STEP_SETTINGS_IFRAME: { + if (target instanceof HTMLIFrameElement) { + target.style.display = 'block'; + } + break; + } + } + } + } + window.addEventListener( + 'message', + stepSettingsRelatedMessageHandler + ); + this._cleanStepSettingsIframe = () => { + window.removeEventListener('message', stepSettingsRelatedMessageHandler); + this._resolveStepSettingsDialogClosed = undefined; + this._rejectStepSettingsDialogClosed = undefined; + this._removeEmbedding(target); + } + } + private _removeTrailingSlashes(str: string) { return str.endsWith('/') ? str.slice(0, -1) : str; } diff --git a/packages/engine/src/lib/tools/index.ts b/packages/engine/src/lib/tools/index.ts index 898af41e82c..2f523efaf5f 100644 --- a/packages/engine/src/lib/tools/index.ts +++ b/packages/engine/src/lib/tools/index.ts @@ -1,7 +1,7 @@ import { Action, DropdownOption, ExecutePropsResult, PieceProperty, PropertyType } from '@activepieces/pieces-framework' import { AgentPieceTool, ExecuteToolOperation, ExecuteToolResponse, ExecutionToolStatus, FieldControlMode, FlowActionType, isNil, PieceAction, PropertyExecutionType, StepOutputStatus } from '@activepieces/shared' -import { generateText, LanguageModel, Output, Tool } from 'ai' -import { z } from 'zod/v3' +import { generateText, JSONParseError, LanguageModel, NoObjectGeneratedError, Output, Tool, zodSchema } from 'ai' +import { z } from 'zod' import { EngineConstants } from '../handler/context/engine-constants' import { FlowExecutorContext } from '../handler/context/flow-execution-context' import { flowExecutor } from '../handler/flow-executor' @@ -107,7 +107,7 @@ async function resolveProperties( if (Object.keys(propertyToFill).length === 0) continue - const schemaObject = z.object(propertyToFill) as z.ZodTypeAny + const schemaObject = zodSchema(z.object(propertyToFill).strict()) const extractionPrompt = constructExtractionPrompt( instruction, propertyToFill, @@ -120,7 +120,16 @@ async function resolveProperties( prompt: extractionPrompt, output: Output.object({ schema: schemaObject, + }), + + }).catch(error => { + if (NoObjectGeneratedError.isInstance(error) && JSONParseError.isInstance(error.cause) && error.text?.startsWith('```json') && error.text?.endsWith('```')) { + return { + output: JSON.parse(error.text.replace('```json', '').replace('```', '')), + } + } + throw error }) result = { @@ -238,12 +247,12 @@ async function propertyToSchema(propertyName: string, property: PieceProperty, o break case PropertyType.DROPDOWN: case PropertyType.STATIC_DROPDOWN: { - schema = z.union([z.string(), z.number(), z.record(z.string(), z.unknown())]) + schema = z.union([z.string(), z.number(), z.object({}).loose()]) break } case PropertyType.MULTI_SELECT_DROPDOWN: case PropertyType.STATIC_MULTI_SELECT_DROPDOWN: { - schema = z.union([z.array(z.string()), z.array(z.record(z.string(), z.unknown()))]) + schema = z.union([z.array(z.string()), z.array(z.object({}).loose())]) break } case PropertyType.NUMBER: @@ -252,10 +261,10 @@ async function propertyToSchema(propertyName: string, property: PieceProperty, o case PropertyType.ARRAY: return z.array(z.string()) case PropertyType.OBJECT: - schema = z.record(z.string(), z.unknown()) + schema = z.object({}).loose() break case PropertyType.JSON: - schema = z.record(z.string(), z.unknown()) + schema = z.object({}).loose() break case PropertyType.DYNAMIC: { schema = await buildDynamicSchema(propertyName, operation, resolvedInput) @@ -273,9 +282,6 @@ async function propertyToSchema(propertyName: string, property: PieceProperty, o case PropertyType.SECRET_TEXT: throw new Error(`Unsupported property type: ${property.type}`) } - if (property.defaultValue) { - schema = schema.default(property.defaultValue) - } if (property.description) { schema = schema.describe(property.description) } @@ -304,6 +310,7 @@ type PropertyDetail = { type: PropertyType description?: string options?: DropdownOption[] + defaultValue?: unknown } async function buildPropertyDetail(propertyName: string, property: PieceProperty, operation: ExecuteToolOperation, input: Record): Promise { @@ -311,6 +318,7 @@ async function buildPropertyDetail(propertyName: string, property: PieceProperty name: propertyName, type: property.type, description: property.description, + defaultValue: property.defaultValue, } if ( diff --git a/packages/pieces/community/ai/package.json b/packages/pieces/community/ai/package.json index 8230552b89f..b463120c934 100644 --- a/packages/pieces/community/ai/package.json +++ b/packages/pieces/community/ai/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-ai", - "version": "0.1.2", + "version": "0.1.3", "type": "commonjs", "main": "./src/index.js", "types": "./src/index.d.ts", diff --git a/packages/pieces/community/ai/src/lib/actions/agents/tools.ts b/packages/pieces/community/ai/src/lib/actions/agents/tools.ts index 9e58c9659ea..2d21961164a 100644 --- a/packages/pieces/community/ai/src/lib/actions/agents/tools.ts +++ b/packages/pieces/community/ai/src/lib/actions/agents/tools.ts @@ -23,9 +23,7 @@ function createTransportConfig( } } case McpProtocol.STREAMABLE_HTTP: { - const sessionId = crypto.randomUUID() return new StreamableHTTPClientTransport(url, { - sessionId, requestInit: { headers, }, diff --git a/packages/react-ui/src/app/builder/builder-hooks.ts b/packages/react-ui/src/app/builder/builder-hooks.ts index c66f2ee0b78..0bb3f675c20 100644 --- a/packages/react-ui/src/app/builder/builder-hooks.ts +++ b/packages/react-ui/src/app/builder/builder-hooks.ts @@ -53,7 +53,7 @@ export const createBuilderStore = (initialState: BuilderInitialState) => const pieceSelectorState = createPieceSelectorState(get, set); const runState = createRunState(initialState, get, set); const chatState = createChatState(set); - const canvasState = createCanvasState(initialState, set); + const canvasState = createCanvasState(initialState, set, get); const stepFormState = createStepFormState(set); const notesState = createNotesState(get, set); return { diff --git a/packages/react-ui/src/app/builder/step-settings/index.tsx b/packages/react-ui/src/app/builder/step-settings/index.tsx index 11c8785dcc2..2d532f3d1ed 100644 --- a/packages/react-ui/src/app/builder/step-settings/index.tsx +++ b/packages/react-ui/src/app/builder/step-settings/index.tsx @@ -2,6 +2,7 @@ import { typeboxResolver } from '@hookform/resolvers/typebox'; import deepEqual from 'deep-equal'; import { useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; +import { useLocation } from 'react-router-dom'; import { useBuilderStateContext } from '@/app/builder/builder-hooks'; import { Form } from '@/components/ui/form'; @@ -14,6 +15,7 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { pieceSelectorUtils } from '@/features/pieces/lib/piece-selector-utils'; import { stepsHooks } from '@/features/pieces/lib/steps-hooks'; import { projectCollectionUtils } from '@/hooks/project-collection'; +import { parentWindow } from '@/lib/utils'; import { FlowAction, FlowActionType, @@ -22,6 +24,7 @@ import { FlowTriggerType, isNil, } from '@activepieces/shared'; +import { ActivepiecesClientEventName } from 'ee-embed-sdk'; import { formUtils } from '../../../features/pieces/lib/form-utils'; import { ActionErrorHandlingForm } from '../piece-properties/action-error-handling'; @@ -40,7 +43,9 @@ import { RouterSettings } from './router-settings'; import { useStepSettingsContext } from './step-settings-context'; const StepSettingsContainer = () => { - const { selectedStep, pieceModel, formSchema } = useStepSettingsContext(); + const location = useLocation(); + const { selectedStep, pieceModel, formSchema, hideTestStep } = + useStepSettingsContext(); const { project } = projectCollectionUtils.useCurrentProject(); const [ readonly, @@ -126,8 +131,9 @@ const StepSettingsContainer = () => { pieceName: modifiedStep.settings.pieceName, triggerName: modifiedStep.settings.triggerName ?? '', }); - const showGenerateSampleData = !readonly && !isManualTrigger; - const showStepInputOutFromRun = !isNil(run) && !isManualTrigger; + const showGenerateSampleData = !readonly && !isManualTrigger && !hideTestStep; + const showStepInputOutFromRun = + !isNil(run) && !isManualTrigger && !hideTestStep; const [isEditingStepOrBranchName, setIsEditingStepOrBranchName] = useState(false); @@ -147,6 +153,19 @@ const StepSettingsContainer = () => { const { height, setHeight } = useResizableVerticalPanelsContext(); + const handleClose = () => { + if (location.pathname.includes('/embed/step-settings')) { + parentWindow.postMessage( + { + type: ActivepiecesClientEventName.CLIENT_STEP_SETTINGS_DIALOG_CLOSED, + data: {}, + }, + '*', + ); + } + exitStepSettings(); + }; + return (
{ className="w-full h-full" >
- exitStepSettings()}> + { diff --git a/packages/react-ui/src/app/builder/step-settings/step-settings-context.tsx b/packages/react-ui/src/app/builder/step-settings/step-settings-context.tsx index dc9ae4e6f84..6e589b85730 100644 --- a/packages/react-ui/src/app/builder/step-settings/step-settings-context.tsx +++ b/packages/react-ui/src/app/builder/step-settings/step-settings-context.tsx @@ -49,12 +49,14 @@ export type StepSettingsContextState = { propertyName: string, form: UseFormReturn, ) => void; + hideTestStep?: boolean; }; export type StepSettingsProviderProps = { selectedStep: FlowAction | FlowTrigger; pieceModel: PieceMetadataModel | undefined; children: ReactNode; + hideTestStep?: boolean; }; const StepSettingsContext = createContext( @@ -65,6 +67,7 @@ export const StepSettingsProvider = ({ selectedStep, pieceModel, children, + hideTestStep = false, }: StepSettingsProviderProps) => { const [formSchema, setFormSchema] = useState>( Type.Object(Type.Unknown()), @@ -122,6 +125,7 @@ export const StepSettingsProvider = ({ formSchema, updateFormSchema, updatePropertySettingsSchema, + hideTestStep, }} > {children} diff --git a/packages/react-ui/src/app/components/project-settings/members/columns.tsx b/packages/react-ui/src/app/components/project-settings/members/columns.tsx index 40cbd5f6c0a..676cffcc7ea 100644 --- a/packages/react-ui/src/app/components/project-settings/members/columns.tsx +++ b/packages/react-ui/src/app/components/project-settings/members/columns.tsx @@ -1,7 +1,7 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { ColumnDef } from '@tanstack/react-table'; import { t } from 'i18next'; -import { ChevronDown, Info, Trash2, User, Shield } from 'lucide-react'; +import { Info, Trash2, User, Shield, ChevronDown } from 'lucide-react'; import { toast } from 'sonner'; import { PermissionNeededTooltip } from '@/components/custom/permission-needed-tooltip'; @@ -9,12 +9,6 @@ import { ConfirmationDeleteDialog } from '@/components/delete-dialog'; import { Button } from '@/components/ui/button'; import { RowDataWithActions } from '@/components/ui/data-table'; import { DataTableColumnHeader } from '@/components/ui/data-table/data-table-column-header'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; import { internalErrorToast } from '@/components/ui/sonner'; import { Tooltip, @@ -23,6 +17,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip'; import { UserAvatar } from '@/components/ui/user-avatar'; +import { RoleDropdown } from '@/features/members/component/role-selector'; import { projectMembersApi } from '@/features/members/lib/project-members-api'; import { userInvitationApi } from '@/features/members/lib/user-invitation'; import { projectRoleApi } from '@/features/platform-admin/lib/project-role-api'; @@ -143,31 +138,12 @@ const RoleCell = ({ return ( - - - - - - {roles.map((role) => ( - { - e.stopPropagation(); - handleValueChange(role.name); - }} - > - {role.name} - - ))} - - + ); }; diff --git a/packages/react-ui/src/app/guards/index.tsx b/packages/react-ui/src/app/guards/index.tsx index 0c5929dd929..02187c01b2b 100644 --- a/packages/react-ui/src/app/guards/index.tsx +++ b/packages/react-ui/src/app/guards/index.tsx @@ -34,6 +34,7 @@ import AuthenticatePage from '../routes/authenticate'; import { ChangePasswordPage } from '../routes/change-password'; import { AppConnectionsPage } from '../routes/connections'; import { EmbeddedConnectionDialog } from '../routes/embed/embedded-connection-dialog'; +import { EmbeddedStepSettingsDialog } from '../routes/embed/embedded-step-settings-dialog'; import { FlowsPage } from '../routes/flows'; import { FlowBuilderPage } from '../routes/flows/id'; import { ResetPasswordPage } from '../routes/forget-password'; @@ -91,6 +92,10 @@ const routes = [ path: '/embed/connections', element: , }, + { + path: '/embed/step-settings', + element: , + }, { path: '/authenticate', element: , diff --git a/packages/react-ui/src/app/routes/embed/embedded-step-settings-dialog.tsx b/packages/react-ui/src/app/routes/embed/embedded-step-settings-dialog.tsx new file mode 100644 index 00000000000..c24d9118dc9 --- /dev/null +++ b/packages/react-ui/src/app/routes/embed/embedded-step-settings-dialog.tsx @@ -0,0 +1,184 @@ +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; + +import { memoryRouter } from '@/app/guards'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; +import { LoadingSpinner } from '@/components/ui/spinner'; +import { flowsApi } from '@/features/flows/lib/flows-api'; +import { cn, parentWindow } from '@/lib/utils'; +import { + FlowAction, + FlowTrigger, + flowStructureUtil, +} from '@activepieces/shared'; +import { + ActivepiecesClientEventName, + ActivepiecesClientShowStepSettingsIframe, + ActivepiecesStepSettingsDialogClosed, + STEP_SETTINGS_QUERY_PARAMS, +} from 'ee-embed-sdk'; + +import { piecesHooks } from '../../../features/pieces/lib/pieces-hooks'; +import { BuilderStateProvider } from '../../builder/state/builder-state-provider'; +import { StepSettingsContainer } from '../../builder/step-settings'; +import { StepSettingsProvider } from '../../builder/step-settings/step-settings-context'; + +const extractIdFromQueryParams = () => { + const stepName = new URLSearchParams(memoryRouter.state.location.search).get( + STEP_SETTINGS_QUERY_PARAMS.stepName, + ); + return stepName; +}; + +export const EmbeddedStepSettingsDialog = () => { + const stepName = extractIdFromQueryParams(); + const queryParams = new URLSearchParams(memoryRouter.state.location.search); + const flowVersionId = queryParams.get( + STEP_SETTINGS_QUERY_PARAMS.flowVersionId, + ); + const flowId = queryParams.get(STEP_SETTINGS_QUERY_PARAMS.flowId); + const dialogKey = `${stepName}-${flowVersionId}-${flowId}-${Date.now()}`; + + return ( + + ); +}; + +type EmbeddedStepSettingsDialogContentProps = { + stepName: string | null; + flowVersionId: string | null; + flowId: string | null; +}; + +const EmbeddedStepSettingsDialogContent = ({ + stepName, + flowVersionId, + flowId, +}: EmbeddedStepSettingsDialogContentProps) => { + const [isDialogOpen, setIsDialogOpen] = useState(true); + + const hideStepSettingsIframe = () => { + setIsDialogOpen(false); + postMessageToParent({ + type: ActivepiecesClientEventName.CLIENT_STEP_SETTINGS_DIALOG_CLOSED, + data: {}, + }); + }; + + const postMessageToParent = (event: ActivepiecesStepSettingsDialogClosed) => { + parentWindow.postMessage(event, '*'); + }; + + const { + data: flow, + isLoading: isLoadingFlow, + isError: isFlowError, + } = useQuery({ + queryKey: ['flow', flowId, flowVersionId], + queryFn: () => flowsApi.get(flowId!, { versionId: flowVersionId! }), + enabled: !!flowId && !!flowVersionId, + }); + + const flowVersion = flow?.version; + const selectedStep = + flowVersion && stepName + ? flowStructureUtil.getStep(stepName, flowVersion.trigger) + : undefined; + + const { pieceModel, isLoading: isLoadingPiece } = + piecesHooks.usePieceModelForStepSettings({ + name: selectedStep?.settings?.pieceName, + version: selectedStep?.settings?.pieceVersion, + enabled: !!selectedStep && 'pieceName' in (selectedStep.settings || {}), + getExactVersion: false, + }); + + useEffect(() => { + const showStepSettingsIframeEvent: ActivepiecesClientShowStepSettingsIframe = + { + type: ActivepiecesClientEventName.CLIENT_SHOW_STEP_SETTINGS_IFRAME, + data: {}, + }; + parentWindow.postMessage(showStepSettingsIframeEvent, '*'); + document.body.style.background = 'transparent'; + }, []); + + if (isFlowError) { + hideStepSettingsIframe(); + return null; + } + + const isLoading = isLoadingFlow || isLoadingPiece; + + return ( + { + setIsDialogOpen(open); + if (!open) { + hideStepSettingsIframe(); + } + }} + > + + {isLoading && ( +
+ +
+ )} + + {!isLoading && selectedStep && flow && ( + + )} +
+
+ ); +}; + +type EmbeddedStepSettingsFormProps = { + selectedStep: FlowAction | FlowTrigger; + pieceModel: any; + flow: any; +}; + +const EmbeddedStepSettingsForm = ({ + selectedStep, + pieceModel, + flow, +}: EmbeddedStepSettingsFormProps) => { + return ( + + + + + + ); +}; diff --git a/packages/react-ui/src/app/routes/platform/users/actions/update-user-dialog.tsx b/packages/react-ui/src/app/routes/platform/users/actions/update-user-dialog.tsx index d265730b910..f17f6a0e8c7 100644 --- a/packages/react-ui/src/app/routes/platform/users/actions/update-user-dialog.tsx +++ b/packages/react-ui/src/app/routes/platform/users/actions/update-user-dialog.tsx @@ -16,14 +16,7 @@ import { import { Form, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; +import { RoleSelector } from '@/features/members/component/role-selector'; import { platformUserApi } from '@/lib/platform-user-api'; import { PlatformRole, @@ -83,29 +76,11 @@ export const UpdateUserDialog = ({ render={({ field }) => ( - + /> )} diff --git a/packages/react-ui/src/features/members/component/invite-user/invite-user-dialog.tsx b/packages/react-ui/src/features/members/component/invite-user/invite-user-dialog.tsx index e5b585975ca..e7bfd928615 100644 --- a/packages/react-ui/src/features/members/component/invite-user/invite-user-dialog.tsx +++ b/packages/react-ui/src/features/members/component/invite-user/invite-user-dialog.tsx @@ -22,15 +22,6 @@ import { import { FormField, FormItem, Form, FormMessage } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { TagInput } from '@/components/ui/tag-input'; import { Tooltip, @@ -38,6 +29,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip'; import { PlatformRoleSelect } from '@/features/members/component/platform-role-select'; +import { RoleSelector } from '@/features/members/component/role-selector'; import { userInvitationApi } from '@/features/members/lib/user-invitation'; import { projectRoleApi } from '@/features/platform-admin/lib/project-role-api'; import { useAuthorization } from '@/hooks/authorization-hooks'; @@ -254,7 +246,7 @@ export const InviteUserDialog = ({ setTagInputKey(0); }} > - + {invitationLink @@ -332,29 +324,13 @@ export const InviteUserDialog = ({ render={({ field }) => ( - + onValueChange={field.onChange} + roles={roles} + placeholder={t('Select Role')} + /> )} diff --git a/packages/react-ui/src/features/members/component/platform-role-select.tsx b/packages/react-ui/src/features/members/component/platform-role-select.tsx index 21f766d5816..97da0a557fc 100644 --- a/packages/react-ui/src/features/members/component/platform-role-select.tsx +++ b/packages/react-ui/src/features/members/component/platform-role-select.tsx @@ -3,16 +3,7 @@ import { UseFormReturn } from 'react-hook-form'; import { FormField, FormItem, FormMessage } from '@/components/ui/form'; import { Label } from '@/components/ui/label'; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { PlatformRole } from '@activepieces/shared'; +import { RoleSelector } from '@/features/members/component/role-selector'; type PlatformRoleSelectProps = { form: UseFormReturn; @@ -25,23 +16,12 @@ export const PlatformRoleSelect = ({ form }: PlatformRoleSelectProps) => { render={({ field }) => ( - + )} diff --git a/packages/react-ui/src/features/members/component/role-selector.tsx b/packages/react-ui/src/features/members/component/role-selector.tsx new file mode 100644 index 00000000000..a62dd1b91e3 --- /dev/null +++ b/packages/react-ui/src/features/members/component/role-selector.tsx @@ -0,0 +1,161 @@ +import { t } from 'i18next'; + +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { PlatformRole } from '@activepieces/shared'; + +type RoleConfig = { + value: T; + label: string; + description: string; +}; + +const PLATFORM_ROLES: RoleConfig[] = [ + { + value: PlatformRole.ADMIN, + label: t('Admin'), + description: t('Full access to all projects and platform settings'), + }, + { + value: PlatformRole.OPERATOR, + label: t('Operator'), + description: t( + 'Access and edit flows in all projects, no platform settings', + ), + }, + { + value: PlatformRole.MEMBER, + label: t('Member'), + description: t( + "Access to personal project and any team projects they're invited to", + ), + }, +]; + +const PROJECT_ROLE_DESCRIPTIONS: Record = { + Admin: t('Manage project settings, members, connections, and git sync'), + Editor: t('Build, publish, and manage flows'), + Viewer: t('View flows and monitor run history'), +}; + +export const getProjectRoleDescription = (roleName: string): string => { + return PROJECT_ROLE_DESCRIPTIONS[roleName] || ''; +}; + +interface RoleSelectorProps { + type: 'platform' | 'project'; + value: string; + onValueChange: (value: string) => void; + disabled?: boolean; + placeholder?: string; + roles?: Array<{ name: string }>; +} + +export const RoleSelector = ({ + type, + value, + onValueChange, + disabled = false, + placeholder, + roles = [], +}: RoleSelectorProps) => { + const isPlatform = type === 'platform'; + + const label = isPlatform ? t('Platform Roles') : t('Project Roles'); + + const options = isPlatform + ? PLATFORM_ROLES.map((role) => ({ + value: role.value, + label: role.label, + description: role.description, + })) + : roles.map((role) => ({ + value: role.name, + label: role.name, + description: getProjectRoleDescription(role.name), + })); + + const selectedRole = options.find((r) => r.value === value); + + return ( + + ); +}; + +interface RoleDropdownProps { + value: string; + onValueChange: (value: string) => void; + disabled?: boolean; + roles: Array<{ name: string }>; + className?: string; +} + +export const RoleDropdown = ({ + value, + onValueChange, + disabled = false, + roles, + className = '', +}: RoleDropdownProps) => { + return ( + + ); +}; diff --git a/packages/server/api/src/app/mcp/mcp-server-controller.ts b/packages/server/api/src/app/mcp/mcp-server-controller.ts index d06285f0eea..7b9b9a52442 100644 --- a/packages/server/api/src/app/mcp/mcp-server-controller.ts +++ b/packages/server/api/src/app/mcp/mcp-server-controller.ts @@ -100,9 +100,7 @@ function createTransportConfig( } } case McpProtocol.STREAMABLE_HTTP: { - const sessionId = crypto.randomUUID() return new StreamableHTTPClientTransport(url, { - sessionId, requestInit: { headers, },