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 @@
{
"extends": [
"../../../../.eslintrc.base.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# pieces-chatling
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build pieces-chatling` to build the library.

View File

@@ -0,0 +1,10 @@
{
"name": "@activepieces/piece-chatling",
"version": "0.0.1",
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -0,0 +1,65 @@
{
"name": "pieces-chatling",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/chatling/src",
"projectType": "library",
"release": {
"version": {
"manifestRootsToUpdate": [
"dist/{projectRoot}"
],
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
},
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/chatling",
"tsConfig": "packages/pieces/community/chatling/tsconfig.lib.json",
"packageJson": "packages/pieces/community/chatling/package.json",
"main": "packages/pieces/community/chatling/src/index.ts",
"assets": [
"packages/pieces/community/chatling/*.md",
{
"input": "packages/pieces/community/chatling/src/i18n",
"output": "./src/i18n",
"glob": "**/!(i18n.json)"
}
],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"prebuild",
"^build"
]
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
},
"prebuild": {
"dependsOn": [
"^build"
],
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/chatling",
"command": "bun install --no-save --silent"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
}
}
}

View File

@@ -0,0 +1,60 @@
import {
createCustomApiCallAction,
HttpMethod,
} from '@activepieces/pieces-common';
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { makeRequest } from './lib/common';
import { sendMessage } from './lib/actions/send-message';
import { createChatbot } from './lib/actions/create-chatbot';
import { newConversation } from './lib/triggers/new-conversation';
import { newContact } from './lib/triggers/new-contact';
const markdownDescription = `
To obtain your API key:
1. Go to your [Chatling account](https://app.chatling.ai)
2. Open **Project Settings**
3. Click the **API Keys** tab
4. Press **New API key** and generate a new key
5. Copy the key (it's only shown once)
`;
export const chatlingAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: markdownDescription,
required: true,
validate: async ({ auth }) => {
try {
await makeRequest(auth, HttpMethod.GET, '/project/settings');
return { valid: true };
} catch (e) {
return {
valid: false,
error: 'Invalid API key',
};
}
},
});
export const chatling = createPiece({
displayName: 'Chatling',
description: 'Build AI chatbots trained on your data.',
auth: chatlingAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/chatling.png',
categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE],
authors: ['onyedikachi-david'],
actions: [
sendMessage,
createChatbot,
createCustomApiCallAction({
auth: chatlingAuth,
baseUrl: () => 'https://api.chatling.ai/v2',
authMapping: async (auth) => ({
Authorization: `Bearer ${(auth as { secret_text: string }).secret_text}`,
}),
}),
],
triggers: [newConversation, newContact],
});

View File

@@ -0,0 +1,38 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { chatlingAuth } from '../../index';
import { makeRequest } from '../common';
import { templateIdDropdown } from '../common/props';
export const createChatbot = createAction({
auth: chatlingAuth,
name: 'create_chatbot',
displayName: 'Create Chatbot',
description: 'Create a new chatbot using a template or from scratch.',
props: {
name: Property.ShortText({
displayName: 'Name',
description: 'The name of the chatbot',
required: true,
}),
template_id: templateIdDropdown,
},
async run(context) {
const { name, template_id } = context.propsValue;
const apiKey = context.auth.secret_text;
const body: Record<string, unknown> = { name };
if (template_id) body['template_id'] = template_id;
const response = await makeRequest(
apiKey,
HttpMethod.POST,
'/chatbots',
body
);
return response;
},
});

View File

@@ -0,0 +1,80 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { chatlingAuth } from '../../index';
import { makeRequest } from '../common';
import {
chatbotIdDropdown,
aiModelIdDropdown,
languageIdDropdown,
contactIdDropdown,
conversationIdDropdown,
} from '../common/props';
export const sendMessage = createAction({
auth: chatlingAuth,
name: 'send_message',
displayName: 'Send Message',
description: 'Send a message to the chatbot and receive an AI response.',
props: {
chatbotId: chatbotIdDropdown,
message: Property.LongText({
displayName: 'Message',
description: 'The message to send to the AI',
required: true,
}),
ai_model_id: aiModelIdDropdown,
conversation_id: conversationIdDropdown,
contact_id: contactIdDropdown,
language_id: languageIdDropdown,
temperature: Property.Number({
displayName: 'Temperature',
description:
'Controls randomness (0 = focused, 1 = creative). Default is 0.',
required: false,
defaultValue: 0,
}),
instructions: Property.Array({
displayName: 'Instructions',
description: 'Additional instructions to tailor the AI response',
required: false,
}),
},
async run(context) {
const {
chatbotId,
message,
ai_model_id,
conversation_id,
contact_id,
language_id,
temperature,
instructions,
} = context.propsValue;
const apiKey = context.auth.secret_text;
const body: Record<string, unknown> = {
message,
ai_model_id,
};
if (conversation_id) body['conversation_id'] = conversation_id;
if (contact_id) body['contact_id'] = contact_id;
if (language_id) body['language_id'] = language_id;
if (temperature !== undefined && temperature !== null)
body['temperature'] = temperature;
if (instructions && instructions.length > 0)
body['instructions'] = instructions;
const response = await makeRequest(
apiKey,
HttpMethod.POST,
`/chatbots/${chatbotId}/ai/kb/chat`,
body
);
return response;
},
});

View File

@@ -0,0 +1,25 @@
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
export const chatlingCommon = {
baseUrl: 'https://api.chatling.ai/v2',
};
export async function makeRequest<T = unknown>(
apiKey: string,
method: HttpMethod,
path: string,
body?: unknown
): Promise<T> {
const response = await httpClient.sendRequest<T>({
method,
url: `${chatlingCommon.baseUrl}${path}`,
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
}

View File

@@ -0,0 +1,274 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { DropdownOption, Property } from '@activepieces/pieces-framework';
import { makeRequest } from './index';
import { chatlingAuth } from '../../index';
export const chatbotIdDropdown = Property.Dropdown({
auth: chatlingAuth,
displayName: 'Chatbot',
description: 'Select a chatbot',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const response = await makeRequest<{
data: {
chatbots: { id: string; name: string }[];
};
}>(auth.secret_text, HttpMethod.GET, '/chatbots');
const options: DropdownOption<string>[] = response.data.chatbots.map(
(chatbot) => ({
label: chatbot.name,
value: chatbot.id,
})
);
return {
disabled: false,
options,
};
},
});
export const aiModelIdDropdown = Property.Dropdown({
auth: chatlingAuth,
displayName: 'AI Model',
description: 'Select the AI model to use',
required: true,
refreshers: ['chatbotId'],
options: async ({ auth, chatbotId }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
if (!chatbotId) {
return {
disabled: true,
placeholder: 'Please select a chatbot first.',
options: [],
};
}
const response = await makeRequest<{
data: {
models: { id: number; name: string }[];
};
}>(
auth.secret_text,
HttpMethod.GET,
`/chatbots/${chatbotId}/ai/kb/models`
);
const options: DropdownOption<number>[] = response.data.models.map(
(model) => ({
label: model.name,
value: model.id,
})
);
return {
disabled: false,
options,
};
},
});
export const languageIdDropdown = Property.Dropdown({
auth: chatlingAuth,
displayName: 'Language',
description: 'Select the language for the AI response',
required: false,
refreshers: ['chatbotId'],
options: async ({ auth, chatbotId }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
if (!chatbotId) {
return {
disabled: true,
placeholder: 'Please select a chatbot first.',
options: [],
};
}
const response = await makeRequest<{
data: {
languages: { id: number; name: string }[];
};
}>(
auth.secret_text,
HttpMethod.GET,
`/chatbots/${chatbotId}/ai/kb/languages`
);
const options: DropdownOption<number>[] = response.data.languages.map(
(language) => ({
label: language.name,
value: language.id,
})
);
return {
disabled: false,
options,
};
},
});
export const contactIdDropdown = Property.Dropdown({
auth: chatlingAuth,
displayName: 'Contact',
description: 'Associate the conversation with a contact',
required: false,
refreshers: ['chatbotId'],
options: async ({ auth, chatbotId }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
if (!chatbotId) {
return {
disabled: true,
placeholder: 'Please select a chatbot first.',
options: [],
};
}
const response = await makeRequest<{
data: {
contacts: { id: string; name: string; email: string }[];
};
}>(
auth.secret_text,
HttpMethod.GET,
`/chatbots/${chatbotId}/contacts`
);
const options: DropdownOption<string>[] = response.data.contacts.map(
(contact) => ({
label: contact.name || contact.email || contact.id,
value: contact.id,
})
);
return {
disabled: false,
options,
};
},
});
export const templateIdDropdown = Property.Dropdown({
auth: chatlingAuth,
displayName: 'Template',
description: 'Use a template to create the chatbot',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const response = await makeRequest<{
data: {
templates: { id: number; name: string; description: string }[];
};
}>(auth.secret_text, HttpMethod.GET, '/chatbot-templates');
const options: DropdownOption<number>[] = response.data.templates.map(
(template) => ({
label: template.name,
value: template.id,
})
);
return {
disabled: false,
options,
};
},
});
export const conversationIdDropdown = Property.Dropdown({
auth: chatlingAuth,
displayName: 'Conversation',
description: 'Continue an existing conversation',
required: false,
refreshers: ['chatbotId'],
options: async ({ auth, chatbotId }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
if (!chatbotId) {
return {
disabled: true,
placeholder: 'Please select a chatbot first.',
options: [],
};
}
const response = await makeRequest<{
data: {
conversations: {
id: string;
created_at: string;
messages: { text: string; role: string }[];
}[];
};
}>(
auth.secret_text,
HttpMethod.GET,
`/chatbots/${chatbotId}/conversations`
);
const options: DropdownOption<string>[] = response.data.conversations.map(
(conv) => {
const firstUserMsg = conv.messages?.find((m) => m.role === 'user');
const preview = firstUserMsg?.text
? firstUserMsg.text.substring(0, 50) +
(firstUserMsg.text.length > 50 ? '...' : '')
: conv.created_at;
return {
label: preview,
value: conv.id,
};
}
);
return {
disabled: false,
options,
};
},
});

View File

@@ -0,0 +1,99 @@
import {
createTrigger,
TriggerStrategy,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import {
DedupeStrategy,
HttpMethod,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { chatlingAuth } from '../../index';
import { chatbotIdDropdown } from '../common/props';
import { makeRequest } from '../common';
import dayjs from 'dayjs';
type ContactItem = {
id: string;
name: string;
email: string;
phone: string;
job_title: string;
website: string;
company: string;
created_at: string;
};
const polling: Polling<
AppConnectionValueForAuthProperty<typeof chatlingAuth>,
{ chatbotId: string }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const response = await makeRequest<{
data: {
contacts: ContactItem[];
};
}>(
auth.secret_text,
HttpMethod.GET,
`/chatbots/${propsValue.chatbotId}/contacts?sort=date_desc`
);
const contacts = response.data.contacts;
const items = contacts
.filter((contact) => {
if (lastFetchEpochMS === 0) return true;
return dayjs(contact.created_at).valueOf() > lastFetchEpochMS;
})
.map((contact) => ({
epochMilliSeconds: dayjs(contact.created_at).valueOf(),
data: contact,
}));
return items;
},
};
export const newContact = createTrigger({
auth: chatlingAuth,
name: 'new_contact',
displayName: 'New Contact/Lead',
description: 'Triggers when a new contact or lead is collected by the chatbot.',
props: {
chatbotId: chatbotIdDropdown,
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
sampleData: {
id: 'contact_abc123',
name: 'John Doe',
email: 'john@example.com',
phone: '+1234567890',
job_title: 'Product Manager',
website: 'https://example.com',
company: 'Acme Inc',
created_at: '2024-01-15T10:30:00.000Z',
},
});

View File

@@ -0,0 +1,102 @@
import {
createTrigger,
TriggerStrategy,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import {
DedupeStrategy,
HttpMethod,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { chatlingAuth } from '../../index';
import { chatbotIdDropdown } from '../common/props';
import { makeRequest } from '../common';
import dayjs from 'dayjs';
type ConversationItem = {
id: string;
contact_id: string;
archived: boolean;
important: boolean;
created_at: string;
messages: { id: string; text: string; role: string }[];
};
const polling: Polling<
AppConnectionValueForAuthProperty<typeof chatlingAuth>,
{ chatbotId: string }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const response = await makeRequest<{
data: {
conversations: ConversationItem[];
};
}>(
auth.secret_text,
HttpMethod.GET,
`/chatbots/${propsValue.chatbotId}/conversations?sort=date_desc`
);
const conversations = response.data.conversations;
// Filter by lastFetchEpochMS if not first run
const items = conversations
.filter((conv) => {
if (lastFetchEpochMS === 0) return true;
return dayjs(conv.created_at).valueOf() > lastFetchEpochMS;
})
.map((conv) => ({
epochMilliSeconds: dayjs(conv.created_at).valueOf(),
data: conv,
}));
return items;
},
};
export const newConversation = createTrigger({
auth: chatlingAuth,
name: 'new_conversation',
displayName: 'New Conversation',
description: 'Triggers when a new conversation is started by a customer.',
props: {
chatbotId: chatbotIdDropdown,
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
sampleData: {
id: 'conv_abc123',
contact_id: 'contact_xyz',
archived: false,
important: false,
created_at: '2024-01-15T10:30:00.000Z',
messages: [
{
id: 'msg_1',
text: 'Hello, I need help with your product.',
role: 'user',
},
],
},
});

View File

@@ -0,0 +1,20 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}