Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,33 @@
{
"You can obtain API key from [Account Settings](https://app.chaindesk.ai/account).": "You can obtain API key from [Account Settings](https://app.chaindesk.ai/account).",
"Query Agent": "Query Agent",
"Query Datastore": "Query Datastore",
"Upload File": "Upload File",
"Custom API Call": "Custom API Call",
"Asks question to your Agent.": "Asks question to your Agent.",
"Asks question to your Datastore.": "Asks question to your Datastore.",
"Uploads a new file to provided Datastore.": "Uploads a new file to provided Datastore.",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"Agent ID": "Agent ID",
"Query": "Query",
"Conversation ID": "Conversation ID",
"Datastore ID": "Datastore ID",
"File": "File",
"File Name": "File Name",
"Method": "Method",
"Headers": "Headers",
"Query Parameters": "Query Parameters",
"Body": "Body",
"Response is Binary ?": "Response is Binary ?",
"No Error on Failure": "No Error on Failure",
"Timeout (in seconds)": "Timeout (in seconds)",
"ID of the conversation (If not provided a new conversation is created).": "ID of the conversation (If not provided a new conversation is created).",
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD"
}

View File

@@ -0,0 +1,32 @@
import { createPiece } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { queryAgentAction } from './lib/actions/query-agent';
import { queryDatastoretAction } from './lib/actions/query-datastore';
import { uploadFileAction } from './lib/actions/upload-file';
import { createCustomApiCallAction } from '@activepieces/pieces-common';
import { chaindeskAuth } from './lib/common/auth';
import { BASE_URL } from './lib/common/constants';
export const chaindesk = createPiece({
displayName: 'Chaindesk',
auth: chaindeskAuth,
minimumSupportedRelease: '0.36.1',
categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE],
logoUrl: 'https://cdn.activepieces.com/pieces/chaindesk.png',
authors: ['kishanprmr'],
actions: [
queryAgentAction,
queryDatastoretAction,
uploadFileAction,
createCustomApiCallAction({
auth: chaindeskAuth,
baseUrl: () => BASE_URL,
authMapping: async (auth) => {
return {
Authorization: `Bearer ${auth.secret_text}`,
};
},
}),
],
triggers: [],
});

View File

@@ -0,0 +1,47 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { chaindeskAuth } from '../common/auth';
import { agentIdDropdown } from '../common/props';
import {
AuthenticationType,
httpClient,
HttpMethod,
} from '@activepieces/pieces-common';
import { BASE_URL } from '../common/constants';
export const queryAgentAction = createAction({
displayName: 'Query Agent',
name: 'query-agent',
auth: chaindeskAuth,
description: 'Asks question to your Agent.',
props: {
agentId: agentIdDropdown,
query: Property.LongText({
displayName: 'Query',
required: true,
}),
conversationId: Property.LongText({
displayName: 'Conversation ID',
required: false,
description:
'ID of the conversation (If not provided a new conversation is created).',
}),
},
async run(context) {
const { agentId, query, conversationId } = context.propsValue;
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: BASE_URL + `/agents/${agentId}/query`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: context.auth.secret_text,
},
body: {
query,
conversationId,
},
});
return response.body;
},
});

View File

@@ -0,0 +1,40 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { chaindeskAuth } from '../common/auth';
import { datastoreIdDropdown } from '../common/props';
import {
AuthenticationType,
httpClient,
HttpMethod,
} from '@activepieces/pieces-common';
import { BASE_URL } from '../common/constants';
export const queryDatastoretAction = createAction({
displayName: 'Query Datastore',
name: 'query-datastore',
auth: chaindeskAuth,
description: 'Asks question to your Datastore.',
props: {
datastoreId: datastoreIdDropdown,
query: Property.LongText({
displayName: 'Query',
required: true,
}),
},
async run(context) {
const { query, datastoreId } = context.propsValue;
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: BASE_URL + `/datastores/${datastoreId}/query`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: context.auth.secret_text,
},
body: {
query,
},
});
return response.body;
},
});

View File

@@ -0,0 +1,51 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { chaindeskAuth } from '../common/auth';
import { datastoreIdDropdown } from '../common/props';
import FormData from 'form-data';
import {
AuthenticationType,
httpClient,
HttpMethod,
} from '@activepieces/pieces-common';
import { BASE_URL } from '../common/constants';
export const uploadFileAction = createAction({
auth: chaindeskAuth,
name: 'upload-file',
displayName: 'Upload File',
description: 'Uploads a new file to provided Datastore.',
props: {
datastoreId: datastoreIdDropdown,
file: Property.File({
displayName: 'File',
required: true,
}),
filename: Property.ShortText({
displayName: 'File Name',
required: false,
}),
},
async run(context) {
const { file, filename, datastoreId } = context.propsValue;
const formData = new FormData();
formData.append('file', file.data, { filename: filename || file.filename });
formData.append('type', 'file');
formData.append('datastoreId', datastoreId);
formData.append('fileName',filename || file.filename )
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: BASE_URL + `/datasources`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: context.auth.secret_text,
},
headers: {
...formData.getHeaders(),
},
body: formData,
});
return response.body;
},
});

View File

@@ -0,0 +1,34 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import {
AuthenticationType,
httpClient,
HttpMethod,
} from '@activepieces/pieces-common';
import { BASE_URL } from './constants';
export const chaindeskAuth = PieceAuth.SecretText({
displayName: 'API Key',
required: true,
description: `You can obtain API key from [Account Settings](https://app.chaindesk.ai/account).`,
validate: async ({ auth }) => {
try {
await httpClient.sendRequest({
method: HttpMethod.GET,
url: BASE_URL + '/conversations',
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth,
},
});
return {
valid: true,
};
} catch {
return {
valid: false,
error: 'Invalid API key',
};
}
},
});

View File

@@ -0,0 +1 @@
export const BASE_URL = 'https://app.chaindesk.ai/api'

View File

@@ -0,0 +1,75 @@
import {
AuthenticationType,
httpClient,
HttpMethod,
} from '@activepieces/pieces-common';
import { Property } from '@activepieces/pieces-framework';
import { BASE_URL } from './constants';
import { ListAgentsResponse } from './types';
import { chaindeskAuth } from './auth';
export const agentIdDropdown = Property.Dropdown({
displayName: 'Agent ID',
auth: chaindeskAuth,
refreshers: [],
required: true,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
const response = await httpClient.sendRequest<Array<ListAgentsResponse>>({
method: HttpMethod.GET,
url: BASE_URL + '/agents',
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.secret_text,
},
});
return {
disabled: false,
options: response.body.map((agent) => ({
label: agent.name,
value: agent.id,
})),
};
},
});
export const datastoreIdDropdown = Property.Dropdown({
displayName: 'Datastore ID',
refreshers: [],
required: true,
auth: chaindeskAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
const response = await httpClient.sendRequest<Array<ListAgentsResponse>>({
method: HttpMethod.GET,
url: BASE_URL + '/datastores',
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.secret_text,
},
});
return {
disabled: false,
options: response.body.map((agent) => ({
label: agent.name,
value: agent.id,
})),
};
},
});

View File

@@ -0,0 +1,4 @@
export type ListAgentsResponse = {
id:string,
name:string
}