diff --git a/docs/install/options/gcp.mdx b/docs/install/options/gcp.mdx new file mode 100644 index 0000000000..960be249fc --- /dev/null +++ b/docs/install/options/gcp.mdx @@ -0,0 +1,29 @@ +--- +title: "GCP" +description: "" +--- + +This documentation is to deploy activepieces on VM Instance or VM Instance Group, we should first create VM template + +## Create VM Template + +First choose machine type (e.g e2-medium) + +After configuring the VM Template, you can proceed to click on "Deploy Container" and specify the following container-specific settings: + +- Image: activepieces/activepieces +- Run as a privileged container: true +- Environment Variables: + - `AP_QUEUE_MODE`: MEMORY + - `AP_DB_TYPE`: SQLITE3 + - `AP_FRONTEND_URL`: http://localhost:80 + - `AP_EXECUTION_MODE`: SANDBOXED +- Firewall: Allow HTTP traffic (for testing purposes only) + +Once these details are entered, click on the "Deploy" button and patiently wait for the container deployment process to complete.\ + +After a successful deployment, you can access the ActivePieces application by visiting the external IP address of the VM on GCP. + +## Production Deployment + +Please visit [ActivePieces](../configuration) for more details on how to customize the application. \ No newline at end of file diff --git a/docs/install/options/overview.mdx b/docs/install/options/overview.mdx index c2f63fda4b..8d5a4c0752 100755 --- a/docs/install/options/overview.mdx +++ b/docs/install/options/overview.mdx @@ -11,7 +11,7 @@ Community Editions is focused on **individuals**, It is **free** and **open sour You can read the difference between the editions [here](.././editions). -## Self Hosting Options (Community Edition) +## Recommended Options @@ -22,6 +22,12 @@ Deploy Activepieces as a single Docker container using the sqllite database. Deploy Activepieces with **redis** and **postgres** setup + + +## Other Options + + + @@ -45,6 +51,11 @@ Deploy Activepieces as a single Docker container using the sqllite database. Install on AWS with Pulumi + + + Install on GCP as VM template + + ## Cloud Edition diff --git a/docs/mint.json b/docs/mint.json index 5275959045..40b768fdd4 100755 --- a/docs/mint.json +++ b/docs/mint.json @@ -81,7 +81,8 @@ "install/options/docker", "install/options/docker-compose", "install/options/easypanel", - "install/options/aws" + "install/options/aws", + "install/options/gcp" ] }, { diff --git a/packages/backend/src/app/chatbot/bots/chatbots.ts b/packages/backend/src/app/chatbot/bots/chatbots.ts index 43378bf6f5..306bf69b4a 100644 --- a/packages/backend/src/app/chatbot/bots/chatbots.ts +++ b/packages/backend/src/app/chatbot/bots/chatbots.ts @@ -1,8 +1,9 @@ -import { ActivepiecesError, ErrorCode, SecretTextConnectionValue } from '@activepieces/shared' +import { APChatMessage as APChatMessage, ActivepiecesError, ErrorCode, SecretTextConnectionValue } from '@activepieces/shared' import { customBot } from './custom-bot' import { embeddings } from '../embedings' import { llm } from '../framework/llm' + const chatbots = [customBot] export const runBot = async ({ @@ -11,12 +12,14 @@ export const runBot = async ({ input, auth, prompt, + history, }: { botId: string type: string auth: SecretTextConnectionValue input: string prompt: string + history: APChatMessage[] }): Promise => { const bot = chatbots.find((b) => b.name === type) if (!bot) { @@ -31,6 +34,7 @@ export const runBot = async ({ settings: { prompt, }, + history, }) } catch (error) { diff --git a/packages/backend/src/app/chatbot/bots/custom-bot.ts b/packages/backend/src/app/chatbot/bots/custom-bot.ts index 5b4f758339..3c74b1ec83 100644 --- a/packages/backend/src/app/chatbot/bots/custom-bot.ts +++ b/packages/backend/src/app/chatbot/bots/custom-bot.ts @@ -1,5 +1,7 @@ + import { createChatbot } from '../framework/chatbot' + export const customBot = createChatbot({ name: 'custom-bot', run: async (ctx) => { @@ -8,13 +10,12 @@ export const customBot = createChatbot({ ${ctx.settings.prompt} [Information]: ${information.join('\n')} - [Question]: - ${ctx.input} ` - return ctx.llm.chat({ - input: finalPrompt, - history: [], + input: ctx.input, + history: ctx.history, + settingsPrompt: finalPrompt, }) }, }) + diff --git a/packages/backend/src/app/chatbot/chatbot.module.ts b/packages/backend/src/app/chatbot/chatbot.module.ts index 9e9f814050..343c5ad81a 100644 --- a/packages/backend/src/app/chatbot/chatbot.module.ts +++ b/packages/backend/src/app/chatbot/chatbot.module.ts @@ -2,6 +2,7 @@ import { UpdateChatbotRequest, ListChatbotsRequest, CreateChatBotRequest, + APChatMessage, } from '@activepieces/shared' import { FastifyPluginCallbackTypebox, @@ -108,6 +109,7 @@ export const chatbotController: FastifyPluginCallbackTypebox = ( params: ChatBotIdParams, body: Type.Object({ input: Type.String(), + history: Type.Optional(Type.Array(APChatMessage)), }), }, }, @@ -116,6 +118,7 @@ export const chatbotController: FastifyPluginCallbackTypebox = ( projectId: request.principal.projectId, chatbotId: request.params.id, input: request.body.input, + history: request.body.history ?? [], }) }, ), diff --git a/packages/backend/src/app/chatbot/chatbot.service.ts b/packages/backend/src/app/chatbot/chatbot.service.ts index e24cf4e011..3a0c2fb41b 100644 --- a/packages/backend/src/app/chatbot/chatbot.service.ts +++ b/packages/backend/src/app/chatbot/chatbot.service.ts @@ -15,6 +15,7 @@ import { UpdateChatbotRequest, ChatbotResponse, ChatbotMetadata, + APChatMessage, } from '@activepieces/shared' import { databaseConnection } from '../database/database-connection' import { ChatbotEntity } from './chatbot.entity' @@ -92,10 +93,12 @@ export const chatbotService = { projectId, chatbotId, input, + history, }: { projectId: ProjectId chatbotId: string input: string + history: APChatMessage[] }): Promise { const chatbot = await chatbotRepo.findOneBy({ id: chatbotId, @@ -126,6 +129,7 @@ export const chatbotService = { type: chatbot.type, auth: connection.value as SecretTextConnectionValue, prompt: chatbot.prompt, + history, }) return { output, diff --git a/packages/backend/src/app/chatbot/framework/chatbot.ts b/packages/backend/src/app/chatbot/framework/chatbot.ts index ab61bfc718..27fd0630be 100644 --- a/packages/backend/src/app/chatbot/framework/chatbot.ts +++ b/packages/backend/src/app/chatbot/framework/chatbot.ts @@ -1,30 +1,33 @@ +import { APChatMessage } from '@activepieces/shared' import { ApEmbeddings } from '../embedings' import { APLLM } from './llm' -type ChatbotAnswerContext = { +type ChatbotAskContext = { settings: { prompt: string } input: string llm: APLLM embeddings: ApEmbeddings + history: APChatMessage[] } class IChatbot { constructor( public readonly name: string, - public readonly run: (ctx: ChatbotAnswerContext) => Promise, + public readonly run: (ctx: ChatbotAskContext) => Promise, ) { } } export const createChatbot = (request: { name: string - run: (ctx: ChatbotAnswerContext) => Promise + run: (ctx: ChatbotAskContext) => Promise }) => { return new IChatbot( request.name, request.run, ) } + diff --git a/packages/backend/src/app/chatbot/framework/llm.ts b/packages/backend/src/app/chatbot/framework/llm.ts index a1122d58e0..4475996df7 100644 --- a/packages/backend/src/app/chatbot/framework/llm.ts +++ b/packages/backend/src/app/chatbot/framework/llm.ts @@ -1,30 +1,96 @@ +import { ChatMessageHistory, ConversationSummaryMemory } from 'langchain/memory' +import { BaseChatMessageHistory, SystemMessage } from 'langchain/schema' +import { BaseLanguageModel } from 'langchain/dist/base_language' import { OpenAI } from 'langchain/llms/openai' - +import { BufferWindowMemory } from 'langchain/memory' +import { PromptTemplate } from 'langchain/prompts' +import { ConversationChain } from 'langchain/chains' +import { APChatMessage } from '@activepieces/shared' export type APLLM = { chat: ({ input, temperature, maxTokens }: AskChat) => Promise } - export const llm = (openAIApiKey: string, modelName: string) => { return { - async chat({ input, temperature, maxTokens }: AskChat) { + async chat({ input, temperature, maxTokens, history, settingsPrompt }: AskChat) { const model = new OpenAI({ modelName, openAIApiKey, temperature: temperature || 0.7, maxTokens, }) - const response = await model.call(input) + + const template = ` +${settingsPrompt} +The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. +Current conversation: +System: {chat_summary} +{recent_chat_history} +Human: {human_input} +AI:` + const prompt = new PromptTemplate({ + inputVariables: ['chat_summary', 'human_input', 'recent_chat_history'], + template, + }) + const k = 10 + const summary = history.length > k ? await summarizeMessages(model, history) : '' + const historyThread = await createChatMessageHistory(history) + const memory = new BufferWindowMemory({ + chatHistory: historyThread, + memoryKey: 'recent_chat_history', + inputKey: 'human_input', + k, + returnMessages: false, + }) + const chain = new ConversationChain({ + memory, + verbose: true, + llm: model, + prompt, + }) + const response = await chain.predict({ + chat_summary: summary, + human_input: input, + }) return response }, } } -type AskChat = { +export type AskChat = { input: string - history: { - text: string - role: 'bot' | 'user' - }[] + history: APChatMessage[] + settingsPrompt: string temperature?: number maxTokens?: number } + +export const summarizeMessages = async (model: BaseLanguageModel, messages: APChatMessage[]): Promise => { + const summary_memory = new ConversationSummaryMemory({ + llm: model, + chatHistory: await createChatMessageHistory(messages), + }) + + const summary = await summary_memory.predictNewSummary(await summary_memory.chatHistory.getMessages(), '') + return summary +} + +export const createChatMessageHistory = async (messages: APChatMessage[]): Promise => { + const history = new ChatMessageHistory() + for (const message of messages) { + switch (message.role) { + case 'user': { + await history.addUserMessage(message.text) + break + } + case 'bot': { + await history.addAIChatMessage(message.text) + break + } + default: { + await history.addMessage(new SystemMessage(message.text)) + break + } + } + } + return history +} \ No newline at end of file diff --git a/packages/backend/src/app/flags/flag.service.ts b/packages/backend/src/app/flags/flag.service.ts index 1cc185d147..8b74ddb4e9 100755 --- a/packages/backend/src/app/flags/flag.service.ts +++ b/packages/backend/src/app/flags/flag.service.ts @@ -6,6 +6,7 @@ import { FlagEntity } from './flag.entity' import axios from 'axios' import { webhookService } from '../webhooks/webhook-service' import { getEdition } from '../helper/secret-helper' +import { theme } from './theme' const flagRepo = databaseConnection.getRepository(FlagEntity) @@ -56,15 +57,21 @@ export const flagService = { created, updated, }, + { + id: ApFlagId.THEME, + value: theme, + created, + updated, + }, { id: ApFlagId.SHOW_DOCS, - value: getEdition() !== ApEdition.ENTERPRISE, + value: false, created, updated, }, { id: ApFlagId.SHOW_COMMUNITY, - value: getEdition() !== ApEdition.ENTERPRISE, + value: false, created, updated, }, diff --git a/packages/backend/src/app/flags/theme.ts b/packages/backend/src/app/flags/theme.ts new file mode 100644 index 0000000000..9031a1033e --- /dev/null +++ b/packages/backend/src/app/flags/theme.ts @@ -0,0 +1,98 @@ +export const theme = { + colors: { + avatar: '#515151', + 'blue-link': '#1890ff', + danger: '#dc3545', + 'primary-color': { + default: '#6e41e2', + dark: '#6838e0', + light: '#eee9fc', + medium: '#c5b3f3', + }, + 'warn-color': { + default: '#f78a3b', + light: '#fff6e4', + dark: '#cc8805', + medium: '', + }, + 'success-color': { + default: '#14ae5c', + light: '#3cad71', + }, + 'selection-color': '8965e6', + }, + logos: { + fullLogoUrl: + 'https://cdn.activepieces.com/brand/full-logo.svg', + smallFullLogoUrl: + 'https://cdn.activepieces.com/brand/full-logo-small.svg', + favIconUrl: + 'https://cdn.activepieces.com/brand/favicon.ico', + logoIconUrl: + 'https://cdn.activepieces.com/brand/logo.svg', + }, + materialPrimaryPalette: { + '50': '#eee8fc', + '100': '#d4c6f6', + '200': '#b7a0f1', + '300': '#9a7aeb', + '400': '#845ee6', + '500': '#6e41e2', + '600': '#663bdf', + '700': '#5b32da', + '800': '#512ad6', + '900': '#3f1ccf', + A100: '#ffffff', + A200: '#d9d1ff', + A400: '#ae9eff', + A700: '#9985ff', + contrast: { + '50': '#000000', + '100': '#000000', + '200': '#000000', + '300': '#000000', + '400': '#ffffff', + '500': '#ffffff', + '600': '#ffffff', + '700': '#ffffff', + '800': '#ffffff', + '900': '#ffffff', + A100: '#000000', + A200: '#000000', + A400: '#000000', + A700: '#000000', + }, + }, + materialWarnPalette: { + '50': '#fbe7e9', + '100': '#f5c2c7', + '200': '#ee9aa2', + '300': '#e7727d', + '400': '#e15361', + '500': '#dc3545', + '600': '#d8303e', + '700': '#d32836', + '800': '#ce222e', + '900': '#c5161f', + A100: '#fff6f7', + A200: '#ffc3c6', + A400: '#ff9095', + A700: '#ff777c', + contrast: { + '50': '#000000', + '100': '#000000', + '200': '#000000', + '300': '#000000', + '400': '#ffffff', + '500': '#ffffff', + '600': '#ffffff', + '700': '#ffffff', + '800': '#ffffff', + '900': '#ffffff', + A100: '#000000', + A200: '#000000', + A400: '#000000', + A700: '#000000', + }, + }, +} diff --git a/packages/backend/src/app/flows/flow-version/flow-version.service.ts b/packages/backend/src/app/flows/flow-version/flow-version.service.ts index 02d0f669b1..385cc953a1 100755 --- a/packages/backend/src/app/flows/flow-version/flow-version.service.ts +++ b/packages/backend/src/app/flows/flow-version/flow-version.service.ts @@ -33,7 +33,7 @@ import { DEFAULT_SAMPLE_DATA_SETTINGS } from '@activepieces/shared' import { isNil } from '@activepieces/shared' import { pieceMetadataService } from '../../pieces/piece-metadata-service' import dayjs from 'dayjs' -import { captureException } from '../../helper/logger' +import { captureException, logger } from '../../helper/logger' import { stepFileService } from '../step-file/step-file.service' const branchSettingsValidator = TypeCompiler.Compile(BranchActionSettingsWithValidation) @@ -75,9 +75,24 @@ export const flowVersionService = { default: operations = [userOperation] break + case FlowOperationType.DUPLICATE_ACTION: + mutatedFlowVersion = await this.getFlowVersion({ + flowId: flowVersion.flowId, + includeArtifactAsBase64: true, + projectId, + removeSecrets: false, + versionId: flowVersion.id, + }) + operations = [userOperation] + break } for (const operation of operations) { mutatedFlowVersion = await applySingleOperation(projectId, mutatedFlowVersion, operation) + if (operation.type === FlowOperationType.DUPLICATE_ACTION) { + flowHelper.getAllSteps(mutatedFlowVersion.trigger).filter(c => c.type === ActionType.CODE).forEach(async (cs) => { + await uploadArtifact(projectId, cs.settings) + }) + } } mutatedFlowVersion.updated = dayjs().toISOString() mutatedFlowVersion.updatedBy = userId @@ -147,6 +162,7 @@ export const flowVersionService = { } async function applySingleOperation(projectId: ProjectId, flowVersion: FlowVersion, operation: FlowOperationRequest): Promise { + logger.info(`applying ${operation.type} to ${flowVersion.displayName}`) await flowVersionSideEffects.preApplyOperation({ projectId, flowVersion, @@ -158,7 +174,6 @@ async function applySingleOperation(projectId: ProjectId, flowVersion: FlowVersi async function removeSecretsFromFlow(flowVersion: FlowVersion): Promise { const flowVersionWithArtifacts: FlowVersion = JSON.parse(JSON.stringify(flowVersion)) - const steps = flowHelper.getAllSteps(flowVersionWithArtifacts.trigger) for (const step of steps) { /* @@ -211,7 +226,7 @@ function handleImportFlowOperation(flowVersion: FlowVersion, operation: ImportFl return operations } -async function addArtifactsAsBase64(projectId: ProjectId, flowVersion: FlowVersion) { +async function addArtifactsAsBase64(projectId: ProjectId, flowVersion: FlowVersion): Promise { const flowVersionWithArtifacts: FlowVersion = JSON.parse(JSON.stringify(flowVersion)) const steps = flowHelper.getAllSteps(flowVersionWithArtifacts.trigger) @@ -236,7 +251,7 @@ async function addArtifactsAsBase64(projectId: ProjectId, flowVersion: FlowVersi } -async function prepareRequest(projectId: ProjectId, flowVersion: FlowVersion, request: FlowOperationRequest) { +async function prepareRequest(projectId: ProjectId, flowVersion: FlowVersion, request: FlowOperationRequest): Promise { const clonedRequest: FlowOperationRequest = JSON.parse(JSON.stringify(request)) switch (clonedRequest.type) { case FlowOperationType.ADD_ACTION: @@ -316,7 +331,6 @@ async function prepareRequest(projectId: ProjectId, flowVersion: FlowVersion, re } break } - case FlowOperationType.UPDATE_TRIGGER: switch (clonedRequest.request.type) { case TriggerType.EMPTY: @@ -333,6 +347,7 @@ async function prepareRequest(projectId: ProjectId, flowVersion: FlowVersion, re break } break + default: break } @@ -340,7 +355,7 @@ async function prepareRequest(projectId: ProjectId, flowVersion: FlowVersion, re } -async function validateAction({ projectId, settings }: { projectId: ProjectId, settings: PieceActionSettings }) { +async function validateAction({ projectId, settings }: { projectId: ProjectId, settings: PieceActionSettings }): Promise { if ( isNil(settings.pieceName) || @@ -367,7 +382,7 @@ async function validateAction({ projectId, settings }: { projectId: ProjectId, s return validateProps(action.props, settings.input) } -async function validateTrigger({ settings, projectId }: { settings: PieceTriggerSettings, projectId: ProjectId }) { +async function validateTrigger({ settings, projectId }: { settings: PieceTriggerSettings, projectId: ProjectId }): Promise { if ( isNil(settings.pieceName) || isNil(settings.pieceVersion) || @@ -393,7 +408,7 @@ async function validateTrigger({ settings, projectId }: { settings: PieceTrigger return validateProps(trigger.props, settings.input) } -function validateProps(props: PiecePropertyMap, input: Record) { +function validateProps(props: PiecePropertyMap, input: Record): boolean { const propsSchema = buildSchema(props) const propsValidator = TypeCompiler.Compile(propsSchema) return propsValidator.Check(input) @@ -475,7 +490,9 @@ async function deleteArtifact(projectId: ProjectId, codeSettings: CodeActionSett } async function uploadArtifact(projectId: ProjectId, codeSettings: CodeActionSettings): Promise { + if (codeSettings.artifact !== undefined) { + const bufferFromBase64 = Buffer.from(codeSettings.artifact, 'base64') const savedFile = await fileService.save({ @@ -483,6 +500,7 @@ async function uploadArtifact(projectId: ProjectId, codeSettings: CodeActionSett data: bufferFromBase64, type: FileType.CODE_SOURCE, compression: FileCompression.NONE, + }) codeSettings.artifact = undefined diff --git a/packages/pieces/http/package.json b/packages/pieces/http/package.json index 3252a0f613..6211f6593f 100644 --- a/packages/pieces/http/package.json +++ b/packages/pieces/http/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-http", - "version": "0.3.8" + "version": "0.3.9" } diff --git a/packages/pieces/http/src/lib/actions/send-http-request-action.ts b/packages/pieces/http/src/lib/actions/send-http-request-action.ts index b05d361abf..ed36e92b9e 100644 --- a/packages/pieces/http/src/lib/actions/send-http-request-action.ts +++ b/packages/pieces/http/src/lib/actions/send-http-request-action.ts @@ -53,10 +53,11 @@ export const httpSendRequestAction = createAction({ url, headers: headers as HttpHeaders, queryParams: queryParams as QueryParams, - body, timeout: timeout ? timeout * 1000 : 0, }; - + if (body) { + request.body = body; + } try { return await httpClient.sendRequest(request); } catch (error) { diff --git a/packages/pieces/invoiceninja/package.json b/packages/pieces/invoiceninja/package.json index f7f7ee201a..eca008df68 100644 --- a/packages/pieces/invoiceninja/package.json +++ b/packages/pieces/invoiceninja/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-invoiceninja", - "version": "0.1.8" + "version": "0.1.7" } diff --git a/packages/pieces/microsoft-onedrive/.eslintrc.json b/packages/pieces/microsoft-onedrive/.eslintrc.json new file mode 100644 index 0000000000..3456be9b90 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/microsoft-onedrive/README.md b/packages/pieces/microsoft-onedrive/README.md new file mode 100644 index 0000000000..0655b96678 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/README.md @@ -0,0 +1,7 @@ +# pieces-microsoft-onedrive + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-microsoft-onedrive` to build the library. diff --git a/packages/pieces/microsoft-onedrive/package.json b/packages/pieces/microsoft-onedrive/package.json new file mode 100644 index 0000000000..a92f3d9da6 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-microsoft-onedrive", + "version": "0.0.2" +} \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/project.json b/packages/pieces/microsoft-onedrive/project.json new file mode 100644 index 0000000000..05db2997d9 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-microsoft-onedrive", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/microsoft-onedrive/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/microsoft-onedrive", + "tsConfig": "packages/pieces/microsoft-onedrive/tsconfig.lib.json", + "packageJson": "packages/pieces/microsoft-onedrive/package.json", + "main": "packages/pieces/microsoft-onedrive/src/index.ts", + "assets": [ + "packages/pieces/microsoft-onedrive/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-microsoft-onedrive {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/microsoft-onedrive/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/src/index.ts b/packages/pieces/microsoft-onedrive/src/index.ts new file mode 100644 index 0000000000..9c1892e171 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/index.ts @@ -0,0 +1,25 @@ +import { createPiece, PieceAuth } from "@activepieces/pieces-framework"; +import { uploadFile } from "./lib/actions/upload-file"; +import { listFiles } from "./lib/actions/list-files"; +import { listFolders } from "./lib/actions/list-folders"; +import { downloadFile } from "./lib/actions/download-file"; +import { newFile } from "./lib/triggers/new-file"; + +export const oneDriveAuth = PieceAuth.OAuth2({ + description: "Authentication for Microsoft OneDrive", + authUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token", + required: true, + scope: ['Files.ReadWrite', 'offline_access'], +}); + + +export const microsoftOneDrive = createPiece({ + displayName: "Microsoft OneDrive", + auth: oneDriveAuth, + minimumSupportedRelease: '0.8.0', + logoUrl: "https://cdn.activepieces.com/pieces/oneDrive.png", + authors: ["BastienMe"], + actions: [uploadFile, downloadFile, listFiles, listFolders], + triggers: [newFile], +}); diff --git a/packages/pieces/microsoft-onedrive/src/lib/actions/download-file.ts b/packages/pieces/microsoft-onedrive/src/lib/actions/download-file.ts new file mode 100644 index 0000000000..2fce8cb86b --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/lib/actions/download-file.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, AuthenticationType } from "@activepieces/pieces-common"; +import { oneDriveAuth } from "../../"; +import { oneDriveCommon } from "../common/common"; + +export const downloadFile = createAction({ + auth: oneDriveAuth, + name: 'download_file', + description: 'Download a file from your Microsoft OneDrive', + displayName: 'Download file', + props: { + fileId: Property.ShortText({ + displayName: 'File ID', + description: 'The ID of the file to download', + required: true, + }), + }, + async run(context) { + const fileId = context.propsValue.fileId; + + const result = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${oneDriveCommon.baseUrl}/items/${fileId}/content`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + } + }); + + const desiredHeaders = ["content-length", "content-type", "content-location", "expires"]; + const filteredHeaders: any = {}; + + if (result.headers) { + for (const key of desiredHeaders) { + filteredHeaders[key] = result.headers[key]; + } + } + + return filteredHeaders; + } +}); \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/src/lib/actions/list-files.ts b/packages/pieces/microsoft-onedrive/src/lib/actions/list-files.ts new file mode 100644 index 0000000000..20d18002d9 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/lib/actions/list-files.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, AuthenticationType } from "@activepieces/pieces-common"; +import { oneDriveAuth } from "../../"; +import { oneDriveCommon } from "../common/common"; + +export const listFiles = createAction({ + auth: oneDriveAuth, + name: 'list_files', + description: 'List files in a OneDrive folder', + displayName: 'List Files', + props: { + parentFolder: oneDriveCommon.parentFolder, + }, + async run(context) { + const parentId = context.propsValue.parentFolder ?? 'root'; + + const result = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${oneDriveCommon.baseUrl}/items/${parentId}/children?$filter=folder eq null`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + } + }); + + return result.body["value"]; + } +}); \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/src/lib/actions/list-folders.ts b/packages/pieces/microsoft-onedrive/src/lib/actions/list-folders.ts new file mode 100644 index 0000000000..fb4209c6a1 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/lib/actions/list-folders.ts @@ -0,0 +1,28 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, AuthenticationType } from "@activepieces/pieces-common"; +import { oneDriveAuth } from "../../"; +import { oneDriveCommon } from "../common/common"; + +export const listFolders = createAction({ + auth: oneDriveAuth, + name: 'list_folders', + description: 'List folders in a OneDrive folder', + displayName: 'List Folders', + props: { + parentFolder: oneDriveCommon.parentFolder, + }, + async run(context) { + const parentId = context.propsValue.parentFolder ?? 'root'; + + const result = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${oneDriveCommon.baseUrl}/items/${parentId}/children?$filter=folder ne null`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + } + }); + + return result.body["value"]; + } +}); \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/src/lib/actions/upload-file.ts b/packages/pieces/microsoft-onedrive/src/lib/actions/upload-file.ts new file mode 100644 index 0000000000..7c211ca044 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/lib/actions/upload-file.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from "@activepieces/pieces-framework"; +import { httpClient, HttpMethod, AuthenticationType } from "@activepieces/pieces-common"; +import { oneDriveAuth } from "../../"; +import mime from 'mime-types'; +import { oneDriveCommon } from "../common/common"; + +export const uploadFile = createAction({ + auth: oneDriveAuth, + name: 'upload_onedrive_file', + description: 'Upload a file to your Microsoft OneDrive', + displayName: 'Upload file', + props: { + fileName: Property.ShortText({ + displayName: 'File name', + description: 'The name the file should be saved as (e.g. file.txt)', + required: true, + }), + file: Property.File({ + displayName: "File", + description: "The file URL or base64 to upload", + required: true, + }), + parentId: oneDriveCommon.parentFolder + }, + async run(context) { + const fileData = context.propsValue.file; + const mimeTypeLookup = mime.lookup(fileData.extension ? fileData.extension : ''); + const mimeType = mimeTypeLookup ? mimeTypeLookup : 'application/octet-stream'; // Fallback to a default MIME type + const encodedFilename = encodeURIComponent(context.propsValue.fileName); + const parentId = context.propsValue.parentId ?? 'root'; + + const fileBuffer = Buffer.from(fileData.base64, 'base64'); + + const result = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `${oneDriveCommon.baseUrl}/items/${parentId}:/${encodedFilename}:/content`, + body: fileBuffer, + headers: { + 'Content-Type': mimeType, + 'Content-length': fileBuffer.length.toString(), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + } + }); + + return result.body; + } +}); \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/src/lib/common/common.ts b/packages/pieces/microsoft-onedrive/src/lib/common/common.ts new file mode 100644 index 0000000000..a9882b16bf --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/lib/common/common.ts @@ -0,0 +1,86 @@ +import { httpClient, HttpMethod, AuthenticationType } from "@activepieces/pieces-common"; +import { OAuth2PropertyValue, Property } from "@activepieces/pieces-framework"; +import dayjs from "dayjs"; + +export const oneDriveCommon = { + baseUrl: "https://graph.microsoft.com/v1.0/me/drive", + + parentFolder: Property.Dropdown({ + displayName: "Parent Folder", + required: false, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first' + } + } + + const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue; + let folders: { id: string, name: string }[] = []; + + try { + const result = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${oneDriveCommon.baseUrl}/items/root/children?$filter=folder ne null`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authProp.access_token, + } + }); + folders = result.body["value"]; + } catch (e) { + throw new Error(`Failed to get folders\nError:${e}`); + } + + return { + disabled: false, + options: folders.map((folder: { id: string, name: string }) => { + return { + label: folder.name, + value: folder.id + } + }) + }; + } + }), + + async getFiles(auth: OAuth2PropertyValue, search?: { + parentFolder?: string, + createdTime?: string | number | Date, + createdTimeOp?: string + }) { + let url = `${this.baseUrl}/items/root/children?$filter=folder eq null`; + if (search?.parentFolder) { + url = `${this.baseUrl}/items/${search.parentFolder}/children?$filter=folder eq null`; + } + + const response = await httpClient.sendRequest<{ value: { id: string, name: string, createdDateTime: string }[] }>({ + method: HttpMethod.GET, + url: url, + queryParams: { + $select: 'id,name,createdDateTime', + $orderby: 'createdDateTime asc' + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + } + }); + + const files = response.body.value; + + if (search?.createdTime) { + const compareDate = dayjs(search.createdTime); + return files.filter(file => { + const fileDate = dayjs(file.createdDateTime); + const comparison = search.createdTimeOp === '<' ? fileDate.isBefore(compareDate) : fileDate.isAfter(compareDate); + return comparison; + }); + } + + return files; + }, +} diff --git a/packages/pieces/microsoft-onedrive/src/lib/triggers/new-file.ts b/packages/pieces/microsoft-onedrive/src/lib/triggers/new-file.ts new file mode 100644 index 0000000000..9efb2f3628 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/src/lib/triggers/new-file.ts @@ -0,0 +1,67 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from "@activepieces/pieces-framework"; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import dayjs from 'dayjs'; +import { oneDriveAuth } from '../..'; +import { oneDriveCommon } from '../common/common'; + +const polling: Polling, { parentFolder?: any }> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const currentValues = await oneDriveCommon.getFiles(auth, { + parentFolder: propsValue.parentFolder, + createdTime: lastFetchEpochMS + }) ?? []; + const items = currentValues.map((item: any) => ({ + epochMilliSeconds: dayjs(item.createdDateTime).valueOf(), + data: item + })); + return items; + } +}; + +export const newFile = createTrigger({ + auth: oneDriveAuth, + name: 'new_file', + displayName: 'New File', + description: 'Trigger when a new file is uploaded.', + props: { + parentFolder: oneDriveCommon.parentFolder + }, + type: TriggerStrategy.POLLING, + onEnable: async (context) => { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }) + }, + onDisable: async (context) => { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }) + }, + run: async (context) => { + return await pollingHelper.poll(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + test: async (context) => { + return await pollingHelper.test(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + + sampleData: { + "id": "123456", + "name": "example.jpg", + "createdDateTime": "2023-09-25T12:34:17Z" + } +}); \ No newline at end of file diff --git a/packages/pieces/microsoft-onedrive/tsconfig.json b/packages/pieces/microsoft-onedrive/tsconfig.json new file mode 100644 index 0000000000..f2400abede --- /dev/null +++ b/packages/pieces/microsoft-onedrive/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/microsoft-onedrive/tsconfig.lib.json b/packages/pieces/microsoft-onedrive/tsconfig.lib.json new file mode 100644 index 0000000000..d91986f837 --- /dev/null +++ b/packages/pieces/microsoft-onedrive/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": [ + "node" + ] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ], + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/pieces/notion/package.json b/packages/pieces/notion/package.json index 31a7886677..6812fd01c9 100644 --- a/packages/pieces/notion/package.json +++ b/packages/pieces/notion/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-notion", - "version": "0.2.4" + "version": "0.2.5" } diff --git a/packages/pieces/notion/src/lib/common/models.ts b/packages/pieces/notion/src/lib/common/models.ts index bae61028a5..3c9cb6aa50 100644 --- a/packages/pieces/notion/src/lib/common/models.ts +++ b/packages/pieces/notion/src/lib/common/models.ts @@ -412,7 +412,7 @@ export const NotionFieldMapping: Record = { required: false, }), buildNotionType: (property: DynamicPropsValue) => ({ - number: property, + number: Number(property), }), }, phone_number: { diff --git a/packages/shared/package.json b/packages/shared/package.json index cb9ee774a8..b21698c3fc 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,5 +1,5 @@ { "name": "@activepieces/shared", - "version": "0.8.8", + "version": "0.9.0", "type": "commonjs" -} +} \ No newline at end of file diff --git a/packages/shared/src/lib/chatbot/chatbot.ts b/packages/shared/src/lib/chatbot/chatbot.ts index 46553f67d3..f7c639e64b 100644 --- a/packages/shared/src/lib/chatbot/chatbot.ts +++ b/packages/shared/src/lib/chatbot/chatbot.ts @@ -49,3 +49,16 @@ export const ChatbotResponse = Type.Object({ }); export type ChatbotResponse = Static; + +export const APChatMessage = Type.Object({ + role:Type.Union([Type.Literal('user'),Type.Literal('bot')]), + text:Type.String() +}) +export type APChatMessage = Static; + +export const AskChatBotRequest = Type.Object({ + chatbotId:Type.String(), + input: Type.String(), + history: Type.Array(APChatMessage) +}) +export type AskChatBotRequest = Static; \ No newline at end of file diff --git a/packages/shared/src/lib/common/utils/object-utils.ts b/packages/shared/src/lib/common/utils/object-utils.ts index e879798dcf..e0def75bff 100644 --- a/packages/shared/src/lib/common/utils/object-utils.ts +++ b/packages/shared/src/lib/common/utils/object-utils.ts @@ -10,23 +10,42 @@ export const spreadIfDefined = (key: string, value: T | undefined | null) => } } +export function applyFunctionToValuesSync(obj: any, apply: (str: any) => any) { + if (isNil(obj)) { + return obj; + } else if (isString(obj)) { + return apply(obj); + } else if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; ++i) { + obj[i] = applyFunctionToValuesSync(obj[i], apply); + } + } else if (typeof obj === 'object') { + const entries = Object.entries(obj); + for (let i = 0; i < entries.length; ++i) { + const [key, value] = entries[i]; + obj[key] = applyFunctionToValuesSync(value, apply); + } + } + return apply(obj); +} + -export async function applyFunctionToValues(obj: any, trim: (str: any) => Promise) { +export async function applyFunctionToValues(obj: any, apply: (str: any) => Promise) { if (isNil(obj)) { return obj; } else if (isString(obj)) { - return await trim(obj); + return await apply(obj); } else if (Array.isArray(obj)) { for (let i = 0; i < obj.length; ++i) { - obj[i] = await applyFunctionToValues(obj[i], trim); + obj[i] = await applyFunctionToValues(obj[i], apply); } } else if (typeof obj === 'object') { const entries = Object.entries(obj); for (let i = 0; i < entries.length; ++i) { const [key, value] = entries[i]; - obj[key] = await applyFunctionToValues(value, trim); + obj[key] = await applyFunctionToValues(value, apply); } } - return await trim(obj); + return await apply(obj); } diff --git a/packages/shared/src/lib/flag/flag.ts b/packages/shared/src/lib/flag/flag.ts index d8c434a6a7..d7af730b46 100755 --- a/packages/shared/src/lib/flag/flag.ts +++ b/packages/shared/src/lib/flag/flag.ts @@ -21,6 +21,7 @@ export enum ApEdition { export enum ApFlagId { SANDBOX_RUN_TIME_SECONDS = 'SANDBOX_RUN_TIME_SECONDS', FRONTEND_URL = "FRONTEND_URL", + THEME = "THEME", EDITION = "EDITION", ENVIRONMENT = "ENVIRONMENT", CHATBOT_ENABLED = 'CHATBOT_ENABLED', diff --git a/packages/shared/src/lib/flows/actions/action.ts b/packages/shared/src/lib/flows/actions/action.ts index 5f52669779..aa5f9ffb44 100755 --- a/packages/shared/src/lib/flows/actions/action.ts +++ b/packages/shared/src/lib/flows/actions/action.ts @@ -202,10 +202,12 @@ export const SingleActionSchema = Type.Union([ ]) export type Action = Static; + export type BranchAction = Static & { nextAction?: Action, onFailureAction?: Action, onSuccessAction?: Action }; export type LoopOnItemsAction = Static & { nextAction?: Action, firstLoopAction?: Action }; + export type PieceAction = Static & { nextAction?: Action }; export type CodeAction = Static & { nextAction?: Action }; diff --git a/packages/shared/src/lib/flows/flow-helper.ts b/packages/shared/src/lib/flows/flow-helper.ts index b8fe0864f4..84f46be380 100755 --- a/packages/shared/src/lib/flows/flow-helper.ts +++ b/packages/shared/src/lib/flows/flow-helper.ts @@ -6,20 +6,21 @@ import { UpdateActionRequest, UpdateTriggerRequest, StepLocationRelativeToParent, - MoveActionRequest + MoveActionRequest, } from './flow-operations'; import { Action, ActionType, BranchAction, LoopOnItemsAction, - SingleActionSchema + SingleActionSchema, } from './actions/action'; import { Trigger, TriggerType } from './triggers/trigger'; import { TypeCompiler } from '@sinclair/typebox/compiler'; import { FlowVersion, FlowVersionState } from './flow-version'; import { ActivepiecesError, ErrorCode } from '../common/activepieces-error'; import semver from 'semver'; +import { applyFunctionToValuesSync, isString } from '../common'; type Step = Action | Trigger; @@ -172,7 +173,6 @@ function transferStep( transferFunction: (step: T) => T ): Step { const updatedStep = transferFunction(step as T); - if (updatedStep.type === ActionType.BRANCH) { const { onSuccessAction, onFailureAction } = updatedStep; if (onSuccessAction) { @@ -384,11 +384,13 @@ function moveAction( return flowVersion; } + function addAction( flowVersion: FlowVersion, request: AddActionRequest ): FlowVersion { return transferFlow(flowVersion, (parentStep: Step) => { + if (parentStep.name !== request.parentStep) { return parentStep; } @@ -407,8 +409,10 @@ function addAction( request.stepLocationRelativeToParent === StepLocationRelativeToParent.AFTER ) { + parentStep.nextAction = createAction(request.action, { - nextAction: parentStep.nextAction + nextAction: parentStep.nextAction, + }); } else { throw new ActivepiecesError( @@ -691,9 +695,9 @@ function upgradePiece(step: Step, stepName: string): Step { const clonedStep: Step = JSON.parse(JSON.stringify(step)); switch (step.type) { case ActionType.PIECE: - case TriggerType.PIECE:{ + case TriggerType.PIECE: { const { pieceVersion, pieceName } = step.settings; - if (isLegacyApp({pieceName, pieceVersion})) { + if (isLegacyApp({ pieceName, pieceVersion })) { return step; } if (pieceVersion.startsWith('^') || pieceVersion.startsWith('~')) { @@ -711,9 +715,9 @@ function upgradePiece(step: Step, stepName: string): Step { } // TODO Remove this in 2024, these pieces didn't follow the standarad versioning where the minor version has to be increased when there is breaking change. -function isLegacyApp({pieceName, pieceVersion}: {pieceName: string, pieceVersion: string}){ +function isLegacyApp({ pieceName, pieceVersion }: { pieceName: string, pieceVersion: string }) { let newVersion = pieceVersion; - if(newVersion.startsWith("^") || newVersion.startsWith("~")){ + if (newVersion.startsWith("^") || newVersion.startsWith("~")) { newVersion = newVersion.substring(1) } if ( @@ -731,6 +735,85 @@ function isLegacyApp({pieceName, pieceVersion}: {pieceName: string, pieceVersion return false; } +function duplicateStep(stepName: string, flowVersionWithArtifacts: FlowVersion): FlowVersion { + const clonedStep = JSON.parse(JSON.stringify(flowHelper.getStep(flowVersionWithArtifacts, stepName))); + clonedStep.nextAction = undefined; + if (!clonedStep) { + throw new Error(`step with name '${stepName}' not found`); + } + const existingNames = getAllSteps(flowVersionWithArtifacts.trigger).map((step) => step.name); + const oldStepsNameToReplace = getAllSteps(clonedStep).map((step) => step.name); + const oldNameToNewName: { [key: string]: string } = {}; + + oldStepsNameToReplace.forEach((name) => { + const newName = findUnusedName(existingNames, 'step'); + oldNameToNewName[name] = newName; + existingNames.push(newName); + }); + + const duplicatedStep = transferStep(clonedStep, (step: Step) => { + step.displayName = `${step.displayName} Copy`; + step.name = oldNameToNewName[step.name]; + if (step.settings.inputUiInfo) { + step.settings.inputUiInfo.currentSelectedData = undefined; + step.settings.inputUiInfo.lastTestDate = undefined; + } + if (step.type === ActionType.CODE) { + step.settings.artifactSourceId = undefined; + } + oldStepsNameToReplace.forEach((oldName) => { + step.settings.input = applyFunctionToValuesSync(step.settings.input, (value: unknown) => { + if (isString(value)) { + return replaceOldStepNameWithNewOne({ input: value, oldStepName: oldName, newStepName: oldNameToNewName[oldName] }); + } + return value; + }); + }); + return step; + }) + let finalFlow = addAction(flowVersionWithArtifacts, { + action: duplicatedStep as Action, + parentStep: stepName, + stepLocationRelativeToParent: StepLocationRelativeToParent.AFTER + }); + const operations = getImportOperations(duplicatedStep); + operations.forEach((operation) => { + finalFlow = flowHelper.apply(finalFlow, operation); + }); + return finalFlow; +} + +function replaceOldStepNameWithNewOne({ input, oldStepName, newStepName }: { input: string, oldStepName: string, newStepName: string }) { + const regex = /{{(.*?)}}/g; // Regular expression to match strings inside {{ }} + return input.replace(regex, (match, content) => { + // Replace the content inside {{ }} using the provided function + const replacedContent = content.replaceAll(new RegExp(`\\b${oldStepName}\\b`, 'g'), `${newStepName}`) + + // Reconstruct the {{ }} with the replaced content + return `{{${replacedContent}}}`; + }); +} + + +function findUnusedName(names: string[], stepPrefix: string): string { + let availableNumber = 1; + let availableName = `${stepPrefix}_${availableNumber}`; + + while (names.includes(availableName)) { + availableNumber++; + availableName = `${stepPrefix}_${availableNumber}`; + } + + return availableName; +} + +function findAvailableStepName(flowVersion: FlowVersion, stepPrefix: string): string { + const steps = flowHelper + .getAllSteps(flowVersion.trigger) + .map((f) => f.name); + return findUnusedName(steps, stepPrefix); +} + export const flowHelper = { isValid: isValid, apply( @@ -774,10 +857,15 @@ export const flowHelper = { upgradePiece(step, operation.request.name) ); break; + case FlowOperationType.DUPLICATE_ACTION: + { + clonedVersion = duplicateStep(operation.request.stepName, clonedVersion); + } } clonedVersion.valid = isValid(clonedVersion); return clonedVersion; }, + getStep, isAction, isTrigger, @@ -789,5 +877,7 @@ export const flowHelper = { isChildOf, transferFlowAsync, getAllChildSteps, - getAllStepsAtFirstLevel + getAllStepsAtFirstLevel, + duplicateStep, + findAvailableStepName }; diff --git a/packages/shared/src/lib/flows/flow-operations.ts b/packages/shared/src/lib/flows/flow-operations.ts index 4e428f35c0..a02f4a9330 100755 --- a/packages/shared/src/lib/flows/flow-operations.ts +++ b/packages/shared/src/lib/flows/flow-operations.ts @@ -1,5 +1,5 @@ import { - CodeActionSchema, BranchActionSchema, LoopOnItemsActionSchema, PieceActionSchema, MissingActionSchema, Action, + CodeActionSchema, BranchActionSchema, LoopOnItemsActionSchema, PieceActionSchema, MissingActionSchema, Action } from "./actions/action"; import { EmptyTrigger, PieceTrigger, WebhookTrigger } from "./triggers/trigger"; import { Static, Type } from "@sinclair/typebox"; @@ -14,7 +14,8 @@ export enum FlowOperationType { UPDATE_TRIGGER = "UPDATE_TRIGGER", ADD_ACTION = "ADD_ACTION", UPDATE_ACTION = "UPDATE_ACTION", - DELETE_ACTION = "DELETE_ACTION" + DELETE_ACTION = "DELETE_ACTION", + DUPLICATE_ACTION = "DUPLICATE_ACTION" } export enum StepLocationRelativeToParent { @@ -43,9 +44,10 @@ export const ChangeFolderRequest = Type.Object({ folderId: Type.Union([Type.String(),Type.Null()]) }); -export type ChangeFolderRequest = Static; +export type ChangeFolderRequest = Static; + export const ChangeNameRequest = Type.Object({ displayName: Type.String({}), }); @@ -61,6 +63,12 @@ export type DeleteActionRequest = Static; export const UpdateActionRequest = Type.Union([CodeActionSchema, LoopOnItemsActionSchema, PieceActionSchema, BranchActionSchema, MissingActionSchema]); export type UpdateActionRequest = Static; +export const DuplicateStepRequest = Type.Object({ + stepName:Type.String() +}); + +export type DuplicateStepRequest = Static; + export const MoveActionRequest = Type.Object({ name: Type.String(), newParentStep: Type.String(), @@ -115,6 +123,10 @@ export const FlowOperationRequest = Type.Union([ Type.Object({ type: Type.Literal(FlowOperationType.CHANGE_FOLDER), request: ChangeFolderRequest + }), + Type.Object({ + type: Type.Literal(FlowOperationType.DUPLICATE_ACTION), + request: DuplicateStepRequest }) ]); diff --git a/packages/ui/common/src/lib/service/flag.service.ts b/packages/ui/common/src/lib/service/flag.service.ts index 4e449a1cd4..837c683973 100644 --- a/packages/ui/common/src/lib/service/flag.service.ts +++ b/packages/ui/common/src/lib/service/flag.service.ts @@ -1,9 +1,8 @@ import { ApEdition, ApFlagId } from '@activepieces/shared'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { map, Observable, of, shareReplay } from 'rxjs'; +import { map, Observable, shareReplay } from 'rxjs'; import { environment } from '../environments/environment'; -import { theme } from './themeing'; type FlagsMap = Record; @@ -123,25 +122,36 @@ export class FlagService { }) ); } + getTheme() { + return this.getAllFlags().pipe( + map((flags) => { + return flags[ApFlagId.THEME] as Record; + }) + ); + } + getLogos(): Observable<{ fullLogoUrl: string; smallFullLogoUrl: string; favIconUrl: string; logoIconUrl: string; }> { - return of(theme.logos); + return this.getTheme().pipe(map((theme) => theme['logos'])); } + getColors(): Observable>> { - return of(theme.colors); + return this.getTheme().pipe(map((theme) => theme['colors'])); } getWarnPalette(): Observable< Record> > { - return of(theme.materialWarnPalette); + return this.getTheme().pipe(map((theme) => theme['materialWarnPalette'])); } getPrimaryPalette(): Observable< Record> > { - return of(theme.materialPrimaryPalette); + return this.getTheme().pipe( + map((theme) => theme['materialPrimaryPalette']) + ); } } diff --git a/packages/ui/common/src/lib/service/flow.service.ts b/packages/ui/common/src/lib/service/flow.service.ts index 430e8ebd59..b52bce0032 100755 --- a/packages/ui/common/src/lib/service/flow.service.ts +++ b/packages/ui/common/src/lib/service/flow.service.ts @@ -50,12 +50,16 @@ export class FlowService { get( flowId: FlowId, - flowVersionId: undefined | FlowVersionId + flowVersionId?: FlowVersionId, + viewMode?: FlowViewMode ): Observable { const params: Record = {}; if (flowVersionId) { params['versionId'] = flowVersionId; } + if (viewMode) { + params['viewMode'] = viewMode; + } return this.http.get(environment.apiUrl + '/flows/' + flowId, { params: params, }); diff --git a/packages/ui/common/src/lib/service/themeing.ts b/packages/ui/common/src/lib/service/themeing.ts deleted file mode 100644 index fa8d199869..0000000000 --- a/packages/ui/common/src/lib/service/themeing.ts +++ /dev/null @@ -1,98 +0,0 @@ -export const theme = { - colors: { - avatar: '#515151', - 'blue-link': '#1890ff', - danger: '#dc3545', - 'primary-color': { - default: '#6e41e2', - dark: '#6838e0', - light: '#eee9fc', - medium: '#c5b3f3', - }, - 'warn-color': { - default: '#f78a3b', - light: '#fff6e4', - dark: '#cc8805', - medium: '', - }, - 'success-color': { - default: '#14ae5c', - light: '#3cad71', - }, - 'selection-color': '8965e6', - }, - logos: { - fullLogoUrl: - 'https://cdn.activepieces.com/brand/full-logo.svg', - smallFullLogoUrl: - 'https://cdn.activepieces.com/brand/full-logo-small.svg', - favIconUrl: - 'https://cdn.activepieces.com/brand/favicon.ico', - logoIconUrl: - 'https://cdn.activepieces.com/brand/logo.svg', - }, - materialPrimaryPalette: { - '50': '#eee8fc', - '100': '#d4c6f6', - '200': '#b7a0f1', - '300': '#9a7aeb', - '400': '#845ee6', - '500': '#6e41e2', - '600': '#663bdf', - '700': '#5b32da', - '800': '#512ad6', - '900': '#3f1ccf', - A100: '#ffffff', - A200: '#d9d1ff', - A400: '#ae9eff', - A700: '#9985ff', - contrast: { - '50': '#000000', - '100': '#000000', - '200': '#000000', - '300': '#000000', - '400': '#ffffff', - '500': '#ffffff', - '600': '#ffffff', - '700': '#ffffff', - '800': '#ffffff', - '900': '#ffffff', - A100: '#000000', - A200: '#000000', - A400: '#000000', - A700: '#000000', - }, - }, - materialWarnPalette: { - '50': '#fbe7e9', - '100': '#f5c2c7', - '200': '#ee9aa2', - '300': '#e7727d', - '400': '#e15361', - '500': '#dc3545', - '600': '#d8303e', - '700': '#d32836', - '800': '#ce222e', - '900': '#c5161f', - A100: '#fff6f7', - A200: '#ffc3c6', - A400: '#ff9095', - A700: '#ff777c', - contrast: { - '50': '#000000', - '100': '#000000', - '200': '#000000', - '300': '#000000', - '400': '#ffffff', - '500': '#ffffff', - '600': '#ffffff', - '700': '#ffffff', - '800': '#ffffff', - '900': '#ffffff', - A100: '#000000', - A200: '#000000', - A400: '#000000', - A700: '#000000', - }, - }, -}; diff --git a/packages/ui/core/src/assets/img/custom/duplicate-step.svg b/packages/ui/core/src/assets/img/custom/duplicate-step.svg new file mode 100644 index 0000000000..d9e09ea997 --- /dev/null +++ b/packages/ui/core/src/assets/img/custom/duplicate-step.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html index af8498a4b9..16c3273449 100644 --- a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html @@ -1,11 +1,47 @@ -
- - -
\ No newline at end of file + + +
+ +
+
+ +
+ + + +
+ + +
+ + + +
+
+ + +
\ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html index d7d77977e3..7d839e797e 100644 --- a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html @@ -1,2 +1,2 @@ - \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html new file mode 100644 index 0000000000..a9eaa8b6b4 --- /dev/null +++ b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts new file mode 100644 index 0000000000..df7bc2cae5 --- /dev/null +++ b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts @@ -0,0 +1,63 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { + BuilderSelectors, + FlowItem, + FlowsActions, +} from '@activepieces/ui/feature-builder-store'; +import { Observable, forkJoin, map, switchMap, take, tap } from 'rxjs'; +import { FlowVersion, FlowViewMode, flowHelper } from '@activepieces/shared'; +import { Store } from '@ngrx/store'; +import { FlowService } from '@activepieces/ui/common'; +@Component({ + selector: 'app-duplicate-step-action', + templateUrl: './duplicate-step-action.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DuplicateStepActionComponent { + @Input({ required: true }) + flowItem: FlowItem; + duplicate$: Observable | undefined; + constructor(private store: Store, private flowService: FlowService) {} + duplicateStep() { + const flowVersionWithArtifacts$ = this.getFlowVersionWithArtifacts(); + this.duplicate$ = flowVersionWithArtifacts$.pipe( + tap((flowVersionWithArtifacts) => { + if (this.flowItem && flowHelper.isAction(this.flowItem.type)) { + this.store.dispatch( + FlowsActions.duplicateStep({ + operation: { + flowVersionWithArtifacts: flowVersionWithArtifacts, + originalStepName: this.flowItem.name, + }, + }) + ); + } + }), + map(() => void 0) + ); + } + private getFlowVersionWithArtifacts(): Observable { + const currentFlow = this.store + .select(BuilderSelectors.selectCurrentFlow) + .pipe(take(1)); + const currentFlowVersionId = this.store + .select(BuilderSelectors.selectCurrentFlowVersionId) + .pipe(take(1)); + const flowVersion$: Observable = forkJoin({ + currentFlowVersionId, + currentFlow, + }).pipe( + switchMap((res) => { + return this.flowService.get( + res.currentFlow.id, + res.currentFlowVersionId, + FlowViewMode.WITH_ARTIFACTS + ); + }), + map((flow) => { + return flow.version; + }) + ); + return flowVersion$; + } +} diff --git a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/flow-item.component.html b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/flow-item.component.html index e9cba81542..c25157f38e 100755 --- a/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/flow-item.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/flow-item-tree/flow-item/flow-item.component.html @@ -26,13 +26,14 @@ [selected]="selected$ | async | defaultFalse"> -
+
+ class="ap-absolute ap-select-none ap-typography-body-2 ap-h-[92px] ap-text-description ap-flex ap-flex-col ap-justify-center ap-transition ap-fade-in ap-duration-500 ap-opacity-0 ap-top-0" + [class.ap-opacity-100]="itemContent.isHovered || name.isHovered" #nameDiv + [style.right]="-(15 + nameDiv.clientWidth) + 'px'">
{{_flowItemData.name}}
diff --git a/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts b/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts index 8480be3d58..044baa71bc 100644 --- a/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts +++ b/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts @@ -22,6 +22,7 @@ import { ViewOnlyModeComponent } from './view-only-mode/view-only-mode.component import { DeleteFlowItemActionComponent } from './flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component'; import { ReplaceTriggerActionComponent } from './flow-item-tree/flow-item/actions/replace-trigger-action/replace-trigger-action.component'; import { ActionsContainerComponent } from './flow-item-tree/flow-item/actions/actions-container/actions-container.component'; +import { DuplicateStepActionComponent } from './flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component'; @NgModule({ imports: [CommonModule, UiCommonModule, DragAndDropModule, CodemirrorModule], declarations: [ @@ -44,6 +45,7 @@ import { ActionsContainerComponent } from './flow-item-tree/flow-item/actions/ac DeleteFlowItemActionComponent, ReplaceTriggerActionComponent, ActionsContainerComponent, + DuplicateStepActionComponent, ], exports: [FlowItemTreeComponent, CanvasUtilsComponent, CanvasPannerDirective], }) diff --git a/packages/ui/feature-builder-header/src/lib/feedback/feedback.component.html b/packages/ui/feature-builder-header/src/lib/feedback/feedback.component.html index a41fc14099..7349ac5cab 100644 --- a/packages/ui/feature-builder-header/src/lib/feedback/feedback.component.html +++ b/packages/ui/feature-builder-header/src/lib/feedback/feedback.component.html @@ -1,4 +1,4 @@ -
; + + constructor(private flagService: FlagService) { + this.showSupport$ = this.flagService.isFlagEnabled(ApFlagId.SHOW_COMMUNITY); + } + openSupport() { window.open(supportUrl, '_blank', 'noopener'); } diff --git a/packages/ui/feature-builder-right-sidebar/src/lib/step-type-sidebar/step-type-sidebar.component.ts b/packages/ui/feature-builder-right-sidebar/src/lib/step-type-sidebar/step-type-sidebar.component.ts index 2ea1688136..17a7a3e35e 100755 --- a/packages/ui/feature-builder-right-sidebar/src/lib/step-type-sidebar/step-type-sidebar.component.ts +++ b/packages/ui/feature-builder-right-sidebar/src/lib/step-type-sidebar/step-type-sidebar.component.ts @@ -265,7 +265,7 @@ export class StepTypeSidebarComponent implements OnInit, AfterViewInit { stepLocationRelativeToParent: StepLocationRelativeToParent ): AddActionRequest { const baseProps = { - name: this.findAvailableName(flowVersion, 'step'), + name: flowHelper.findAvailableStepName(flowVersion, 'step'), displayName: getDefaultDisplayNameForPiece( flowItemDetails.type as ActionType, flowItemDetails.name @@ -353,21 +353,6 @@ export class StepTypeSidebarComponent implements OnInit, AfterViewInit { } } - findAvailableName(flowVersion: FlowVersion, stepPrefix: string): string { - const steps = flowHelper - .getAllSteps(flowVersion.trigger) - .map((f) => f.name); - let availableNumber = 1; - let availableName = `${stepPrefix}_${availableNumber}`; - - while (steps.includes(availableName)) { - availableNumber++; - availableName = `${stepPrefix}_${availableNumber}`; - } - - return availableName; - } - applySearchToObservable( obs$: Observable ): Observable { diff --git a/packages/ui/feature-builder-store/src/lib/service/code.service.ts b/packages/ui/feature-builder-store/src/lib/service/code.service.ts index 4e62aabcc2..858dbeae5b 100755 --- a/packages/ui/feature-builder-store/src/lib/service/code.service.ts +++ b/packages/ui/feature-builder-store/src/lib/service/code.service.ts @@ -24,7 +24,10 @@ type ArtifactsCache = Map; export class CodeService { artifactsCacheForFlowConfigs: ArtifactsCache = new Map(); artifactsCacheForSteps: ArtifactsCache = new Map(); - cachedFile: Map = new Map>(); + cachedFile: Map> = new Map< + string, + Observable + >(); constructor(private http: HttpClient) {} @@ -57,7 +60,7 @@ export class CodeService { }) ); } - return this.cachedFile.get(url); + return this.cachedFile.get(url)!; } public helloWorldBase64(): string { diff --git a/packages/ui/feature-builder-store/src/lib/store/builder/canvas/canvas.reducer.ts b/packages/ui/feature-builder-store/src/lib/store/builder/canvas/canvas.reducer.ts index 1916366587..ba00dc39a3 100755 --- a/packages/ui/feature-builder-store/src/lib/store/builder/canvas/canvas.reducer.ts +++ b/packages/ui/feature-builder-store/src/lib/store/builder/canvas/canvas.reducer.ts @@ -9,6 +9,7 @@ import { } from '../../../model'; import { FlowOperationType, + FlowVersion, FlowVersionState, TriggerType, flowHelper, @@ -163,6 +164,22 @@ const __CanvasReducer = createReducer( ); return clonedState; }), + on(FlowsActions.duplicateStep, (state, { operation }): CanvasState => { + const clonedState: CanvasState = JSON.parse(JSON.stringify(state)); + const clonedFlowVersionWithArtifacts: FlowVersion = JSON.parse( + JSON.stringify(operation.flowVersionWithArtifacts) + ); + clonedState.displayedFlowVersion = flowHelper.apply( + clonedFlowVersionWithArtifacts, + { + type: FlowOperationType.DUPLICATE_ACTION, + request: { + stepName: operation.originalStepName, + }, + } + ); + return clonedState; + }), on(FlowsActions.updateTrigger, (state, { operation }): CanvasState => { const clonedState: CanvasState = JSON.parse(JSON.stringify(state)); clonedState.displayedFlowVersion = flowHelper.apply( diff --git a/packages/ui/feature-builder-store/src/lib/store/flow/flow.effects.ts b/packages/ui/feature-builder-store/src/lib/store/flow/flow.effects.ts index 39dccf9e31..7233510377 100755 --- a/packages/ui/feature-builder-store/src/lib/store/flow/flow.effects.ts +++ b/packages/ui/feature-builder-store/src/lib/store/flow/flow.effects.ts @@ -261,11 +261,22 @@ export class FlowsEffects { }, }; break; - case FlowsActionType.MOVE_ACTION: + case FlowsActionType.MOVE_ACTION: { flowOperation = { type: FlowOperationType.MOVE_ACTION, request: action.operation, }; + break; + } + case FlowsActionType.DUPLICATE_ACTION: { + flowOperation = { + request: { + stepName: action.operation.originalStepName, + }, + type: FlowOperationType.DUPLICATE_ACTION, + }; + break; + } } if (flow) { return of( diff --git a/packages/ui/feature-builder-store/src/lib/store/flow/flows.action.ts b/packages/ui/feature-builder-store/src/lib/store/flow/flows.action.ts index bcef704e85..ee4213e35e 100755 --- a/packages/ui/feature-builder-store/src/lib/store/flow/flows.action.ts +++ b/packages/ui/feature-builder-store/src/lib/store/flow/flows.action.ts @@ -10,6 +10,7 @@ import { FlowOperationRequest, Folder, MoveActionRequest, + FlowVersion, } from '@activepieces/shared'; export enum FlowsActionType { @@ -28,6 +29,7 @@ export enum FlowsActionType { MOVE_ACTION = '[FLOWS] MOVE_ACTION', IMPORT_FLOW = '[FLOWS] IMPORT_FLOW', TOGGLE_WAITING_TO_SAVE = '[FLOWS] TOGGLE_WAITING_TO_SAVE', + DUPLICATE_ACTION = `[FLOWS] DUPLICATE_ACTION`, } const updateTrigger = createAction( @@ -86,6 +88,15 @@ const importFlow = createAction( flow: Flow; }>() ); +const duplicateStep = createAction( + FlowsActionType.DUPLICATE_ACTION, + props<{ + operation: { + flowVersionWithArtifacts: FlowVersion; + originalStepName: string; + }; + }>() +); const applyUpdateOperation = createAction( FlowsActionType.APPLY_UPDATE_OPERATION, props<{ flow: Flow; operation: FlowOperationRequest; saveRequestId: UUID }>() @@ -110,6 +121,7 @@ export const FlowsActions = { moveAction, importFlow, toggleWaitingToSave, + duplicateStep, }; export const SingleFlowModifyingState = [ @@ -119,4 +131,5 @@ export const SingleFlowModifyingState = [ updateTrigger, deleteAction, moveAction, + duplicateStep, ]; diff --git a/packages/ui/feature-builder-store/src/lib/store/flow/flows.reducer.ts b/packages/ui/feature-builder-store/src/lib/store/flow/flows.reducer.ts index a7ee13c56b..dcc6c3d13f 100755 --- a/packages/ui/feature-builder-store/src/lib/store/flow/flows.reducer.ts +++ b/packages/ui/feature-builder-store/src/lib/store/flow/flows.reducer.ts @@ -70,6 +70,22 @@ const _flowsReducer = createReducer( }); return clonedState; }), + on(FlowsActions.duplicateStep, (state, { operation }): FlowState => { + const clonedState: FlowState = JSON.parse(JSON.stringify(state)); + const clonedFlowVersionWithArtifacts = JSON.parse( + JSON.stringify(operation.flowVersionWithArtifacts) + ); + clonedState.flow.version = flowHelper.apply( + clonedFlowVersionWithArtifacts, + { + type: FlowOperationType.DUPLICATE_ACTION, + request: { + stepName: operation.originalStepName, + }, + } + ); + return clonedState; + }), on(FlowsActions.updateAction, (state, { operation }): FlowState => { const clonedState: FlowState = JSON.parse(JSON.stringify(state)); clonedState.flow.version = flowHelper.apply(clonedState.flow.version, { diff --git a/packages/ui/feature-chatbot/src/lib/chat/chat.component.html b/packages/ui/feature-chatbot/src/lib/chat/chat.component.html index 661bcb947d..da4504c2d5 100644 --- a/packages/ui/feature-chatbot/src/lib/chat/chat.component.html +++ b/packages/ui/feature-chatbot/src/lib/chat/chat.component.html @@ -44,10 +44,10 @@
- + - diff --git a/packages/ui/feature-chatbot/src/lib/chat/chat.component.ts b/packages/ui/feature-chatbot/src/lib/chat/chat.component.ts index df58c55da5..c3ca2ac1b7 100644 --- a/packages/ui/feature-chatbot/src/lib/chat/chat.component.ts +++ b/packages/ui/feature-chatbot/src/lib/chat/chat.component.ts @@ -16,14 +16,11 @@ import { } from 'rxjs'; import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { Chatbot } from '@activepieces/shared'; +import { APChatMessage, Chatbot } from '@activepieces/shared'; import { AuthenticationService, FlagService } from '@activepieces/ui/common'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { MatSnackBar } from '@angular/material/snack-bar'; -type Message = { - text: string; - sender: 'user' | 'bot'; -}; + @Component({ selector: 'app-chat', @@ -35,11 +32,11 @@ export class ChatComponent { @ViewChild('chatThread') chatThreadHTML: | ElementRef | undefined; - messages: Message[] = []; + messages: APChatMessage[] = []; messageControl: FormControl; sendMessage$: Observable | undefined; sendingMessage$: BehaviorSubject = new BehaviorSubject(false); - chatbotId: string | undefined; + chatbotId: string; chatbotDisplayName = ''; dots$: Observable; data$: Observable; @@ -75,28 +72,30 @@ export class ChatComponent { } send() { - if (this.sendingMessage$.value || !this.messageControl.value?.trim()) { + const input = this.messageControl.value?.trim(); + if (this.sendingMessage$.value || !input) { return; } - const input = this.messageControl.value!; + this.messages.push({ text: input, - sender: 'user', + role: 'user', }); this.messageControl.reset(); this.scrollThreadDown(); this.sendingMessage$.next(true); this.sendMessage$ = this.chatbotService .ask({ - chatbotId: this.chatbotId!, + chatbotId: this.chatbotId, input, + history:this.messages }) .pipe( tap((res) => { this.sendingMessage$.next(false); this.messages.push({ text: res.output, - sender: 'bot', + role: 'bot', }); }), tap(() => { @@ -109,17 +108,17 @@ export class ChatComponent { ) { this.messages.push({ text: 'Oops! make sure your OpenAI api key is valid, it seems it is not.', - sender: 'bot', + role: 'bot', }); } else if (err.status === HttpStatusCode.PaymentRequired) { this.messages.push({ text: 'Oops! Your OpenAI quota is exceeded, please check your OpenAI plan and billing details.', - sender: 'bot', + role: 'bot', }); } else { this.messages.push({ text: 'Oops! an unexpected error occured, please contact support.', - sender: 'bot', + role: 'bot', }); } this.sendingMessage$.next(false); diff --git a/packages/ui/feature-chatbot/src/lib/chatbot.service.ts b/packages/ui/feature-chatbot/src/lib/chatbot.service.ts index 3317133d13..de611d87eb 100644 --- a/packages/ui/feature-chatbot/src/lib/chatbot.service.ts +++ b/packages/ui/feature-chatbot/src/lib/chatbot.service.ts @@ -15,7 +15,7 @@ import { import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map } from 'rxjs'; - +import {AskChatBotRequest} from '@activepieces/shared' @Injectable({ providedIn: 'root', }) @@ -43,11 +43,12 @@ export class ChatBotService { ); } - ask(req: { chatbotId: string; input: string }) { + ask(req: AskChatBotRequest) { return this.http.post( environment.apiUrl + '/chatbots/' + req.chatbotId + '/ask', { input: req.input, + history:req.history } ).pipe(map(res=>{ const withHtmlNewLines= res.output; diff --git a/packages/ui/feature-connections/src/lib/add-edit-connection-button/add-edit-connection-button.component.ts b/packages/ui/feature-connections/src/lib/add-edit-connection-button/add-edit-connection-button.component.ts index fcb1af3547..14ff466051 100644 --- a/packages/ui/feature-connections/src/lib/add-edit-connection-button/add-edit-connection-button.component.ts +++ b/packages/ui/feature-connections/src/lib/add-edit-connection-button/add-edit-connection-button.component.ts @@ -447,7 +447,6 @@ export class AddEditConnectionButtonComponent { ) { this.updateOrAddConnectionDialogClosed$ = currentConnection$.pipe( switchMap((connection) => { - console.log(connection); if (connection.type === AppConnectionType.OAUTH2) { return this.dialogService .open(OAuth2ConnectionDialogComponent, { diff --git a/tsconfig.base.json b/tsconfig.base.json index 4577704884..c264bf4d30 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -133,6 +133,9 @@ "packages/pieces/mattermost/src/index.ts" ], "@activepieces/piece-mautic": ["packages/pieces/mautic/src/index.ts"], + "@activepieces/piece-microsoft-onedrive": [ + "packages/pieces/microsoft-onedrive/src/index.ts" + ], "@activepieces/piece-mindee": ["packages/pieces/mindee/src/index.ts"], "@activepieces/piece-monday": ["packages/pieces/monday/src/index.ts"], "@activepieces/piece-mysql": ["packages/pieces/mysql/src/index.ts"],