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,253 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { presentonAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
export const generatePresentations = createAction({
auth: presentonAuth,
name: 'generate_presentations',
displayName: 'Generate Presentations (async)',
description:
'Create an asynchronous presentation generation task at Presenton and return the created task.',
props: {
content: Property.LongText({
displayName: 'Content',
description: 'The content for generating the presentation',
required: false,
}),
instructions: Property.LongText({
displayName: 'Instructions',
description: 'Instruction for generating the presentation (optional).',
required: false,
}),
tone: Property.StaticDropdown({
displayName: 'Tone',
description: 'Tone to use for the text',
required: false,
defaultValue: 'default',
options: {
options: [
{ value: 'default', label: 'Default' },
{ value: 'casual', label: 'Casual' },
{ value: 'professional', label: 'Professional' },
{ value: 'funny', label: 'Funny' },
{ value: 'educational', label: 'Educational' },
{ value: 'sales_pitch', label: 'Sales pitch' },
],
},
}),
verbosity: Property.StaticDropdown({
displayName: 'Verbosity',
description: 'How verbose the text should be',
required: false,
defaultValue: 'standard',
options: {
options: [
{ value: 'concise', label: 'Concise' },
{ value: 'standard', label: 'Standard' },
{ value: 'text-heavy', label: 'Text-heavy' },
],
},
}),
slides_markdown: Property.Array({
displayName: 'Slides markdown',
description: 'An array of markdown strings for each slide (optional).',
required: false,
defaultValue: [],
}),
markdown_emphasis: Property.Checkbox({
displayName: 'Markdown emphasis',
description: 'Whether to emphasize the markdown',
required: false,
defaultValue: true,
}),
web_search: Property.Checkbox({
displayName: 'Enable web search',
description: 'Whether to enable web search',
required: false,
defaultValue: false,
}),
image_type: Property.StaticDropdown({
displayName: 'Image type',
description: 'Type of image to generate',
required: false,
defaultValue: 'stock',
options: {
options: [
{ value: 'stock', label: 'Stock' },
{ value: 'ai-generated', label: 'AI generated' },
],
},
}),
theme: Property.ShortText({
displayName: 'Theme',
description:
'Theme to use for the presentation (e.g. edge-yellow, light-rose)',
required: false,
}),
n_slides: Property.Number({
displayName: 'Number of slides',
description: 'Number of slides to generate',
required: false,
defaultValue: 8,
}),
language: Property.ShortText({
displayName: 'Language',
description: 'Language for the presentation',
required: false,
defaultValue: 'English',
}),
template: Property.StaticDropdown({
displayName: 'Template',
description: 'Template to use for the presentation',
required: false,
defaultValue: 'general',
options: {
options: [
{ value: 'general', label: 'General' },
{ value: 'modern', label: 'Modern' },
{ value: 'standard', label: 'Standard' },
{ value: 'swift', label: 'Swift' },
],
},
}),
include_table_of_contents: Property.Checkbox({
displayName: 'Include table of contents',
description: 'Whether to include a table of contents',
required: false,
defaultValue: false,
}),
include_title_slide: Property.Checkbox({
displayName: 'Include title slide',
description: 'Whether to include a title slide',
required: false,
defaultValue: true,
}),
allow_access_to_user_info: Property.Checkbox({
displayName: "Allow access to user's info",
description: "Whether to allow access to user's info",
required: false,
defaultValue: true,
}),
files: Property.Array({
displayName: 'Files',
description:
'Array of file identifiers uploaded via Presenton files API (optional).',
required: false,
defaultValue: [],
}),
export_as: Property.StaticDropdown({
displayName: 'Export as',
description: 'Export format',
required: false,
defaultValue: 'pptx',
options: {
options: [
{ value: 'pptx', label: 'PPTX' },
{ value: 'pdf', label: 'PDF' },
],
},
}),
trigger_webhook: Property.Checkbox({
displayName: 'Trigger webhook',
description: 'Whether to trigger subscribed webhooks',
required: false,
defaultValue: false,
}),
},
async run({ auth, propsValue }) {
const apiKey = auth.secret_text;
const {
content,
slides_markdown,
instructions,
tone,
verbosity,
markdown_emphasis,
web_search,
image_type,
theme,
n_slides,
language,
template,
include_table_of_contents,
include_title_slide,
allow_access_to_user_info,
files,
export_as,
trigger_webhook,
} = propsValue;
const body: any = {};
if (content) body['content'] = content;
if (slides_markdown)
body['slides_markdown'] = slides_markdown as unknown as string[];
if (instructions) body['instructions'] = instructions;
if (tone) body['tone'] = tone;
if (verbosity) body['verbosity'] = verbosity;
body['markdown_emphasis'] = markdown_emphasis ?? true;
body['web_search'] = web_search ?? false;
if (image_type) body['image_type'] = image_type;
if (theme) body['theme'] = theme;
body['n_slides'] = Number(n_slides ?? 8);
body['language'] = language ?? 'English';
body['template'] = template ?? 'general';
body['include_table_of_contents'] = include_table_of_contents ?? false;
body['include_title_slide'] = include_title_slide ?? true;
body['allow_access_to_user_info'] = allow_access_to_user_info ?? true;
if (files) body['files'] = files as unknown as string[];
body['export_as'] = export_as ?? 'pptx';
body['trigger_webhook'] = trigger_webhook ?? false;
try {
const response = await makeRequest(
apiKey,
HttpMethod.POST,
'/ppt/presentation/generate/async',
body
);
const pollIntervalSeconds = 5;
const taskId = response.id;
if (!taskId) {
throw new Error(
`Presenton did not return a task id: ${JSON.stringify(response)}`
);
}
const start = Date.now();
const timeoutMs = 120 * 1000;
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
while (Date.now() - start < timeoutMs) {
const statusResp = await makeRequest(
apiKey,
HttpMethod.GET,
`/ppt/presentation/status/${taskId}`
);
if (statusResp.status === 'completed') {
return statusResp;
}
if (statusResp.status === 'failed') {
throw new Error(
`Presentation generation failed: ${JSON.stringify(statusResp)}`
);
}
await sleep(pollIntervalSeconds * 1000);
}
throw new Error(
`Timed out waiting for presentation generation after 120 seconds`
);
} catch (err) {
throw new Error(`Presenton API error: ${err}`);
}
},
});

View File

@@ -0,0 +1,34 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { makeRequest } from './client';
import { HttpMethod } from '@activepieces/pieces-common';
export const presentonAuth = PieceAuth.SecretText({
displayName: 'Presenton API Key',
description: `
`,
required: true,
validate: async ({ auth }) => {
if (auth) {
try {
await makeRequest(
auth as string,
HttpMethod.GET,
'/ppt/presentation/all',
{}
);
return {
valid: true,
};
} catch (error) {
return {
valid: false,
error: 'Invalid Api Key',
};
}
}
return {
valid: false,
error: 'Invalid Api Key',
};
},
});

View File

@@ -0,0 +1,25 @@
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
export const BASE_URL = `https://api.presenton.ai/api/v1`;
export async function makeRequest(
api_key: string,
method: HttpMethod,
path: string,
body?: unknown
) {
try {
const response = await httpClient.sendRequest({
method,
url: `${BASE_URL}${path}`,
headers: {
Authorization: `Bearer ${api_key}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
throw new Error(`Unexpected error: ${error.message || String(error)}`);
}
}

View File

@@ -0,0 +1,100 @@
import {
createTrigger,
TriggerStrategy,
PiecePropValueSchema,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import dayjs from 'dayjs';
import { presentonAuth } from '../common/auth';
import { makeRequest } from '../common/client';
import { HttpMethod } from '@activepieces/pieces-common';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof presentonAuth>,
Record<string, never>
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const apiKey = auth.secret_text;
const response: any = await makeRequest(
apiKey,
HttpMethod.GET,
'/ppt/presentation/all'
);
const itemsArray = response.results;
return itemsArray.map((item: any) => ({
epochMilliSeconds: dayjs(item.created_at).valueOf(),
data: item,
}));
},
};
export const newPresentation = createTrigger({
auth: presentonAuth,
name: 'newPresentation',
displayName: 'New Presentation',
description: 'Triggers when a new presentation is created in Presenton.',
props: {},
sampleData: {
id: '93d4092b-2a20-4637-bbe7-2addb6273761',
user: 'fa86a74f-53b9-46fa-9ac9-e3a526d125ca',
content: 'Indian society ',
n_slides: 1,
language: 'English',
title: ' AI **Presented by:** Jon deo -',
created_at: '2025-11-11T11:33:55.269027Z',
updated_at: '2025-11-11T11:34:12.978847Z',
tone: 'default',
verbosity: 'standard',
theme: null,
slides: [
{
presentation: '93d4092b-2a20-4637-bbe7-2addb6273761',
layout: 'general:general-intro-slide',
index: 0,
layout_group: 'general',
speaker_note: '',
id: '38ca1626-83ef-451f-8529-8b10725bcb3f',
content: {
title: 'AI Presentation',
description: '',
presenterName: 'Jon Deo',
presentationDate: '2025-11-11',
image: {
__image_prompt__: 'r',
__image_url__:
'https://images.pexels.com/photos/17898879/pexels-photo-17898879.jpeg?auto=compress&cs=tinysrgb&h=650&w=940',
},
__speaker_note__: '',
},
html_content: null,
properties: null,
},
],
},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});