From 281268aae8105d66514d1f1eaedab2b7adfc977f Mon Sep 17 00:00:00 2001 From: hugh-codes <166336705+hugh-codes@users.noreply.github.com> Date: Tue, 5 May 2026 22:18:09 +1200 Subject: [PATCH 1/4] feat(descript): add Descript integration with actions and triggers (#13030) Co-authored-by: Copilot Co-authored-by: David Anyatonwu <51977119+onyedikachi-david@users.noreply.github.com> Co-authored-by: David Anyatonwu --- bun.lock | 12 ++ .../pieces/community/descript/.eslintrc.json | 9 + .../pieces/community/descript/package.json | 16 ++ .../pieces/community/descript/src/index.ts | 40 ++++ .../descript/src/lib/actions/agent-edit.ts | 145 +++++++++++++++ .../src/lib/actions/get-job-status.ts | 54 ++++++ .../descript/src/lib/actions/get-project.ts | 47 +++++ .../descript/src/lib/actions/import-media.ts | 175 ++++++++++++++++++ .../descript/src/lib/actions/list-projects.ts | 142 ++++++++++++++ .../src/lib/actions/publish-project.ts | 96 ++++++++++ .../pieces/community/descript/src/lib/auth.ts | 41 ++++ .../descript/src/lib/common/index.ts | 152 +++++++++++++++ .../src/lib/triggers/job-completed.ts | 154 +++++++++++++++ .../pieces/community/descript/tsconfig.json | 15 ++ .../community/descript/tsconfig.lib.json | 14 ++ tsconfig.base.json | 3 + 16 files changed, 1115 insertions(+) create mode 100644 packages/pieces/community/descript/.eslintrc.json create mode 100644 packages/pieces/community/descript/package.json create mode 100644 packages/pieces/community/descript/src/index.ts create mode 100644 packages/pieces/community/descript/src/lib/actions/agent-edit.ts create mode 100644 packages/pieces/community/descript/src/lib/actions/get-job-status.ts create mode 100644 packages/pieces/community/descript/src/lib/actions/get-project.ts create mode 100644 packages/pieces/community/descript/src/lib/actions/import-media.ts create mode 100644 packages/pieces/community/descript/src/lib/actions/list-projects.ts create mode 100644 packages/pieces/community/descript/src/lib/actions/publish-project.ts create mode 100644 packages/pieces/community/descript/src/lib/auth.ts create mode 100644 packages/pieces/community/descript/src/lib/common/index.ts create mode 100644 packages/pieces/community/descript/src/lib/triggers/job-completed.ts create mode 100644 packages/pieces/community/descript/tsconfig.json create mode 100644 packages/pieces/community/descript/tsconfig.lib.json diff --git a/bun.lock b/bun.lock index 16947ef7d6c..64f375c6752 100644 --- a/bun.lock +++ b/bun.lock @@ -2114,6 +2114,16 @@ "tslib": "^2.3.0", }, }, + "packages/pieces/community/descript": { + "name": "@activepieces/piece-descript", + "version": "0.0.1", + "dependencies": { + "@activepieces/pieces-common": "workspace:*", + "@activepieces/pieces-framework": "workspace:*", + "@activepieces/shared": "workspace:*", + "tslib": "2.6.2", + }, + }, "packages/pieces/community/detecting-ai": { "name": "@activepieces/piece-detecting-ai", "version": "0.0.5", @@ -8744,6 +8754,8 @@ "@activepieces/piece-denser-ai": ["@activepieces/piece-denser-ai@workspace:packages/pieces/community/denser-ai"], + "@activepieces/piece-descript": ["@activepieces/piece-descript@workspace:packages/pieces/community/descript"], + "@activepieces/piece-detecting-ai": ["@activepieces/piece-detecting-ai@workspace:packages/pieces/community/detecting-ai"], "@activepieces/piece-devin": ["@activepieces/piece-devin@workspace:packages/pieces/community/devin"], diff --git a/packages/pieces/community/descript/.eslintrc.json b/packages/pieces/community/descript/.eslintrc.json new file mode 100644 index 00000000000..c7ebd5ecdcc --- /dev/null +++ b/packages/pieces/community/descript/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "extends": ["../../../../.eslintrc.base.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": {} }, + { "files": ["*.ts", "*.tsx"], "rules": {} }, + { "files": ["*.js", "*.jsx"], "rules": {} } + ] +} diff --git a/packages/pieces/community/descript/package.json b/packages/pieces/community/descript/package.json new file mode 100644 index 00000000000..15e679c9d02 --- /dev/null +++ b/packages/pieces/community/descript/package.json @@ -0,0 +1,16 @@ +{ + "name": "@activepieces/piece-descript", + "version": "0.0.1", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.lib.json && cp package.json dist/", + "lint": "eslint 'src/**/*.ts'" + }, + "dependencies": { + "@activepieces/pieces-common": "workspace:*", + "@activepieces/pieces-framework": "workspace:*", + "@activepieces/shared": "workspace:*", + "tslib": "2.6.2" + } +} diff --git a/packages/pieces/community/descript/src/index.ts b/packages/pieces/community/descript/src/index.ts new file mode 100644 index 00000000000..a410fa85a20 --- /dev/null +++ b/packages/pieces/community/descript/src/index.ts @@ -0,0 +1,40 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { descriptAgentEditAction } from './lib/actions/agent-edit'; +import { descriptGetJobStatusAction } from './lib/actions/get-job-status'; +import { descriptGetProjectAction } from './lib/actions/get-project'; +import { descriptImportMediaAction } from './lib/actions/import-media'; +import { descriptListProjectsAction } from './lib/actions/list-projects'; +import { descriptPublishProjectAction } from './lib/actions/publish-project'; +import { descriptAuth, getAuthToken } from './lib/auth'; +import { descriptJobCompletedTrigger } from './lib/triggers/job-completed'; + +export { descriptAuth }; + +export const descript = createPiece({ + displayName: 'Descript', + description: + 'AI-powered video and podcast editor. Import media, run AI edits with Underlord, and publish.', + minimumSupportedRelease: '0.82.1', + logoUrl: 'https://cdn.activepieces.com/pieces/descript.png', + categories: [PieceCategory.CONTENT_AND_FILES], + auth: descriptAuth, + authors: ['hugh-codes', 'onyedikachi-david'], + actions: [ + descriptImportMediaAction, + descriptAgentEditAction, + descriptPublishProjectAction, + descriptGetJobStatusAction, + descriptListProjectsAction, + descriptGetProjectAction, + createCustomApiCallAction({ + baseUrl: () => 'https://descriptapi.com/v1', + auth: descriptAuth, + authMapping: async (auth) => ({ + Authorization: `Bearer ${getAuthToken(auth)}`, + }), + }), + ], + triggers: [descriptJobCompletedTrigger], +}); diff --git a/packages/pieces/community/descript/src/lib/actions/agent-edit.ts b/packages/pieces/community/descript/src/lib/actions/agent-edit.ts new file mode 100644 index 00000000000..0d5e2c975c4 --- /dev/null +++ b/packages/pieces/community/descript/src/lib/actions/agent-edit.ts @@ -0,0 +1,145 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +export const descriptAgentEditAction = createAction({ + auth: descriptAuth, + name: 'agent_edit', + displayName: 'Agent Edit (Underlord)', + description: + 'Use Descript\'s AI agent "Underlord" to edit an existing project or create a new one with a natural language prompt.', + props: { + mode: Property.StaticDropdown({ + displayName: 'Mode', + description: + 'Choose whether to edit an existing project or create a brand new one.', + required: true, + defaultValue: 'existing', + options: { + options: [ + { label: 'Edit existing project', value: 'existing' }, + { label: 'Create new project from prompt', value: 'new' }, + ], + }, + }), + project_id: Property.Dropdown({ + auth: descriptAuth, + displayName: 'Project', + description: 'Select the project for the agent to edit.', + refreshers: ['mode'], + required: false, + options: async ({ auth, mode }) => { + if (mode !== 'existing') + return { + disabled: true, + options: [], + placeholder: 'Not needed for new projects', + }; + if (!auth) + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + try { + const projects = await descriptCommon.fetchAllProjects( + descriptCommon.getAuthToken(auth) + ); + return { + disabled: false, + options: projects.map((p) => ({ label: p.name, value: p.id })), + }; + } catch { + return { + disabled: true, + options: [], + placeholder: 'Failed to load projects. Check your connection.', + }; + } + }, + }), + project_name: Property.ShortText({ + displayName: 'New Project Name', + description: + 'Name for the new project (only used when creating a new project).', + required: false, + }), + team_access: Property.StaticDropdown({ + displayName: 'Team Access', + description: + 'Access level for team members on the new project. Only applies when creating a new project.', + required: false, + defaultValue: 'none', + options: { + options: [ + { label: 'None (private to owner)', value: 'none' }, + { label: 'View', value: 'view' }, + { label: 'Comment', value: 'comment' }, + { label: 'Edit', value: 'edit' }, + ], + }, + }), + composition_id: descriptCommon.compositionIdProp(false), + prompt: Property.LongText({ + displayName: 'Prompt', + description: + 'Natural language instruction for Underlord. Examples: "Add studio sound to every clip", "Remove all filler words", "Create a 30-second highlight reel with the best moments". Be specific — frame edits as a single one-shot instruction.', + required: true, + }), + model: Property.ShortText({ + displayName: 'AI Model', + description: + 'AI model to use for editing. Leave blank to use the Descript default.', + required: false, + }), + callback_url: Property.ShortText({ + displayName: 'Callback URL', + description: + 'Optional webhook URL. When the job finishes or fails, Descript will POST the job status to this URL (same payload as Get Job Status).', + required: false, + }), + }, + async run(context) { + const { + mode, + project_id, + project_name, + team_access, + composition_id, + prompt, + model, + callback_url, + } = context.propsValue; + + const body: Record = { prompt }; + + if (mode === 'existing') { + if (!project_id) throw new Error('Please select a project to edit.'); + body['project_id'] = project_id; + if (composition_id) body['composition_id'] = composition_id; + } else { + if (!project_name) + throw new Error('Please enter a project name for the new project.'); + body['project_name'] = project_name; + if (team_access) body['team_access'] = team_access; + } + + if (model) body['model'] = model; + if (callback_url) body['callback_url'] = callback_url; + + const response = await descriptCommon.descriptApiCall<{ + job_id: string; + drive_id: string; + project_id: string; + project_url: string; + }>({ + apiKey: descriptCommon.getAuthToken(context.auth), + method: HttpMethod.POST, + path: '/jobs/agent', + body, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/descript/src/lib/actions/get-job-status.ts b/packages/pieces/community/descript/src/lib/actions/get-job-status.ts new file mode 100644 index 00000000000..4274534b77f --- /dev/null +++ b/packages/pieces/community/descript/src/lib/actions/get-job-status.ts @@ -0,0 +1,54 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +type JobResult = { + status?: string; + agent_response?: string; + project_changed?: boolean; + media_seconds_used?: number; + ai_credits_used?: number; + share_url?: string; + download_url?: string; + download_url_expires_at?: string; + error_message?: string; +}; + +type JobStatusResponse = { + job_id: string; + job_type: string; + job_state: 'queued' | 'running' | 'stopped' | 'cancelled'; + created_at: string; + stopped_at?: string; + drive_id: string; + project_id: string; + project_url: string; + result?: JobResult; + progress?: { label: string; percent?: number; last_update_at?: string }; +}; + +export const descriptGetJobStatusAction = createAction({ + auth: descriptAuth, + name: 'get_job_status', + displayName: 'Get Job Status', + description: + 'Retrieves the current status of a Descript background job (import, agent edit, or publish). Use the job_id returned by any job-creation action.', + props: { + job_id: Property.ShortText({ + displayName: 'Job ID', + description: + 'The job ID returned when you created the job (e.g. from "Import Media", "Agent Edit", or "Publish Project" actions).', + required: true, + }), + }, + async run(context) { + const response = await descriptCommon.descriptApiCall({ + apiKey: descriptCommon.getAuthToken(context.auth), + method: HttpMethod.GET, + path: `/jobs/${context.propsValue.job_id}`, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/descript/src/lib/actions/get-project.ts b/packages/pieces/community/descript/src/lib/actions/get-project.ts new file mode 100644 index 00000000000..171b0551cf3 --- /dev/null +++ b/packages/pieces/community/descript/src/lib/actions/get-project.ts @@ -0,0 +1,47 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +type Composition = { + id: string; + name: string; + duration?: number; + media_type?: string; +}; + +type MediaFile = { + type: string; + duration?: number; +}; + +type ProjectDetailResponse = { + id: string; + name: string; + drive_id: string; + created_at: string; + updated_at: string; + media_files: Record; + compositions: Composition[]; +}; + +export const descriptGetProjectAction = createAction({ + auth: descriptAuth, + name: 'get_project', + displayName: 'Get Project', + description: + 'Retrieves details for a Descript project including its compositions and media files.', + props: { + project_id: descriptCommon.projectIdProp, + }, + async run(context) { + const response = + await descriptCommon.descriptApiCall({ + apiKey: descriptCommon.getAuthToken(context.auth), + method: HttpMethod.GET, + path: `/projects/${context.propsValue.project_id}`, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/descript/src/lib/actions/import-media.ts b/packages/pieces/community/descript/src/lib/actions/import-media.ts new file mode 100644 index 00000000000..53a1d3d1b39 --- /dev/null +++ b/packages/pieces/community/descript/src/lib/actions/import-media.ts @@ -0,0 +1,175 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +export const descriptImportMediaAction = createAction({ + auth: descriptAuth, + name: 'import_media', + displayName: 'Import Media', + description: + 'Imports a media file from a URL into a new or existing Descript project and optionally creates a composition.', + props: { + mode: Property.StaticDropdown({ + displayName: 'Project', + description: 'Create a new project or import into an existing one.', + required: true, + defaultValue: 'new', + options: { + options: [ + { label: 'Create new project', value: 'new' }, + { label: 'Import into existing project', value: 'existing' }, + ], + }, + }), + project_id: Property.Dropdown({ + auth: descriptAuth, + displayName: 'Existing Project', + description: 'Select the project to import media into.', + refreshers: ['mode'], + required: false, + options: async ({ auth, mode }) => { + if (mode !== 'existing') return { disabled: true, options: [], placeholder: 'Not needed for new projects' }; + if (!auth) return { disabled: true, options: [], placeholder: 'Please connect your account first' }; + try { + const projects = await descriptCommon.fetchAllProjects( + descriptCommon.getAuthToken(auth), + ); + return { + disabled: false, + options: projects.map((p) => ({ label: p.name, value: p.id })), + }; + } catch { + return { disabled: true, options: [], placeholder: 'Failed to load projects. Check your connection.' }; + } + }, + }), + project_name: Property.ShortText({ + displayName: 'New Project Name', + description: 'Name for the new project. Only used when creating a new project.', + required: false, + }), + folder_name: Property.ShortText({ + displayName: 'Project Folder', + description: + 'Folder path for the new project (e.g. "Clients/Acme"). Supports nested paths using "/". Missing folders are created automatically. Only applies when creating a new project.', + required: false, + }), + team_access: Property.StaticDropdown({ + displayName: 'Team Access', + description: 'Access level for Drive members on the new project. Only applies when creating a new project.', + required: false, + defaultValue: 'none', + options: { + options: [ + { label: 'None (private to owner)', value: 'none' }, + { label: 'View', value: 'view' }, + { label: 'Comment', value: 'comment' }, + { label: 'Edit', value: 'edit' }, + ], + }, + }), + media_name: Property.ShortText({ + displayName: 'Media File Name', + description: + 'Display name for the media file (e.g. "intro.mp4" or "Recordings/interview.mp4"). Use "/" to organize into sub-folders. Must not conflict with existing file names when importing into an existing project.', + required: true, + }), + media_url: Property.ShortText({ + displayName: 'Media URL', + description: + 'Public URL of the media file. Must be accessible by Descript servers and support HTTP Range requests. Sign URLs for 12–48 hours if they expire.', + required: true, + }), + language: Property.ShortText({ + displayName: 'Transcription Language', + description: + 'ISO 639-1 language code for transcription (e.g. "en", "es", "fr"). Leave blank to auto-detect from the audio.', + required: false, + }), + composition_name: Property.ShortText({ + displayName: 'Composition Name', + description: + 'Name for the composition (timeline) to create from the imported media. Leave blank to skip composition creation.', + required: false, + }), + composition_width: Property.Number({ + displayName: 'Composition Width (px)', + description: 'Width of the composition in pixels. Defaults to 1920. Only used when a Composition Name is provided.', + required: false, + defaultValue: 1920, + }), + composition_height: Property.Number({ + displayName: 'Composition Height (px)', + description: 'Height of the composition in pixels. Defaults to 1080. Only used when a Composition Name is provided.', + required: false, + defaultValue: 1080, + }), + callback_url: Property.ShortText({ + displayName: 'Callback URL', + description: + 'Optional webhook URL. When the import finishes or fails, Descript will POST the job status to this URL (same payload as Get Job Status).', + required: false, + }), + }, + async run(context) { + const { + mode, + project_id, + project_name, + folder_name, + team_access, + media_name, + media_url, + language, + composition_name, + composition_width, + composition_height, + callback_url, + } = context.propsValue; + + const mediaItem: Record = { url: media_url }; + if (language) mediaItem['language'] = language; + + const body: Record = { + add_media: { [media_name]: mediaItem }, + }; + + if (mode === 'existing') { + if (!project_id) throw new Error('Please select a project to import into.'); + body['project_id'] = project_id; + } else { + if (!project_name) throw new Error('Please enter a name for the new project.'); + body['project_name'] = project_name; + if (folder_name) body['folder_name'] = folder_name; + if (team_access) body['team_access'] = team_access; + } + + if (composition_name) { + body['add_compositions'] = [ + { + name: composition_name, + width: composition_width ?? 1920, + height: composition_height ?? 1080, + clips: [{ media: media_name }], + }, + ]; + } + + if (callback_url) body['callback_url'] = callback_url; + + const response = await descriptCommon.descriptApiCall<{ + job_id: string; + drive_id: string; + project_id: string; + project_url: string; + }>({ + apiKey: descriptCommon.getAuthToken(context.auth), + method: HttpMethod.POST, + path: '/jobs/import/project_media', + body, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/descript/src/lib/actions/list-projects.ts b/packages/pieces/community/descript/src/lib/actions/list-projects.ts new file mode 100644 index 00000000000..ed16a4bc251 --- /dev/null +++ b/packages/pieces/community/descript/src/lib/actions/list-projects.ts @@ -0,0 +1,142 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +type Project = { + id: string; + name: string; + created_at: string; + updated_at: string; +}; + +export const descriptListProjectsAction = createAction({ + auth: descriptAuth, + name: 'list_projects', + displayName: 'List Projects', + description: + 'Returns projects in your Descript Drive. Supports filtering by name, date, and creator.', + props: { + name: Property.ShortText({ + displayName: 'Name Filter', + description: + 'Return only projects whose name contains this string (case-insensitive).', + required: false, + }), + created_by: Property.StaticDropdown({ + displayName: 'Created By', + description: 'Filter projects by creator.', + required: false, + options: { + options: [ + { label: 'Anyone', value: '' }, + { label: 'Me', value: 'me' }, + ], + }, + }), + sort: Property.StaticDropdown({ + displayName: 'Sort By', + description: 'Field to sort results by.', + required: false, + defaultValue: 'created_at', + options: { + options: [ + { label: 'Date created', value: 'created_at' }, + { label: 'Date updated', value: 'updated_at' }, + { label: 'Name', value: 'name' }, + { label: 'Last viewed', value: 'last_viewed_at' }, + ], + }, + }), + direction: Property.StaticDropdown({ + displayName: 'Sort Direction', + description: 'Sort order for results.', + required: false, + defaultValue: 'desc', + options: { + options: [ + { label: 'Newest first (desc)', value: 'desc' }, + { label: 'Oldest first (asc)', value: 'asc' }, + ], + }, + }), + created_after: Property.ShortText({ + displayName: 'Created After', + description: + 'Return only projects created after this date (ISO 8601, e.g. 2025-01-01T00:00:00Z).', + required: false, + }), + created_before: Property.ShortText({ + displayName: 'Created Before', + description: + 'Return only projects created before this date (ISO 8601, e.g. 2025-12-31T23:59:59Z).', + required: false, + }), + updated_after: Property.ShortText({ + displayName: 'Updated After', + description: + 'Return only projects updated after this date (ISO 8601, e.g. 2025-01-01T00:00:00Z).', + required: false, + }), + updated_before: Property.ShortText({ + displayName: 'Updated Before', + description: + 'Return only projects updated before this date (ISO 8601, e.g. 2025-12-31T23:59:59Z).', + required: false, + }), + }, + async run(context) { + const { + name, + created_by, + sort, + direction, + created_after, + created_before, + updated_after, + updated_before, + } = context.propsValue; + + const apiKey = descriptCommon.getAuthToken(context.auth); + + const baseParams: Record = { limit: '100' }; + if (sort) baseParams['sort'] = sort; + if (direction) baseParams['direction'] = direction; + if (name) baseParams['name'] = name; + if (created_by) baseParams['created_by'] = created_by; + if (created_after) baseParams['created_after'] = created_after; + if (created_before) baseParams['created_before'] = created_before; + if (updated_after) baseParams['updated_after'] = updated_after; + if (updated_before) baseParams['updated_before'] = updated_before; + + const projects: Project[] = []; + let cursor: string | undefined = undefined; + + do { + const queryParams: Record = { + ...baseParams, + ...(cursor ? { cursor } : {}), + }; + + const response = await descriptCommon.descriptApiCall<{ + data: Project[]; + pagination: { next_cursor?: string }; + }>({ + apiKey, + method: HttpMethod.GET, + path: '/projects', + queryParams, + }); + + projects.push(...response.body.data); + cursor = response.body.pagination.next_cursor; + } while (cursor); + + return projects.map((p) => ({ + project_id: p.id, + project_name: p.name, + created_at: p.created_at, + updated_at: p.updated_at, + })); + }, +}); diff --git a/packages/pieces/community/descript/src/lib/actions/publish-project.ts b/packages/pieces/community/descript/src/lib/actions/publish-project.ts new file mode 100644 index 00000000000..adac82087e3 --- /dev/null +++ b/packages/pieces/community/descript/src/lib/actions/publish-project.ts @@ -0,0 +1,96 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +export const descriptPublishProjectAction = createAction({ + auth: descriptAuth, + name: 'publish_project', + displayName: 'Publish Project', + description: + 'Publishes a composition from a project to create a shareable link and a downloadable export file.', + props: { + project_id: descriptCommon.projectIdProp, + composition_id: descriptCommon.compositionIdProp(false), + media_type: Property.StaticDropdown({ + displayName: 'Output Type', + description: + 'Choose whether to export the project as a video or audio file.', + required: false, + defaultValue: 'Video', + options: { + options: [ + { label: 'Video', value: 'Video' }, + { label: 'Audio only', value: 'Audio' }, + ], + }, + }), + resolution: Property.StaticDropdown({ + displayName: 'Video Resolution', + description: + 'Resolution for the exported video. Only applies when Output Type is Video.', + required: false, + defaultValue: '1080p', + options: { + options: [ + { label: '480p', value: '480p' }, + { label: '720p', value: '720p' }, + { label: '1080p (Full HD)', value: '1080p' }, + { label: '1440p (2K)', value: '1440p' }, + { label: '4K', value: '4K' }, + ], + }, + }), + access_level: Property.StaticDropdown({ + displayName: 'Share Access Level', + description: + "Who can view the published share page. Leave blank to use your Drive's default. Note: requesting a level your Drive disallows (e.g. Public when search-engine indexing is off) will return an error.", + required: false, + options: { + options: [ + { label: 'Public (anyone)', value: 'public' }, + { label: 'Unlisted (link only)', value: 'unlisted' }, + { label: 'Drive members only', value: 'drive' }, + { label: 'Private (owner only)', value: 'private' }, + ], + }, + }), + callback_url: Property.ShortText({ + displayName: 'Callback URL', + description: + 'Optional webhook URL. When the publish job finishes or fails, Descript will POST the job status to this URL (same payload as Get Job Status). The result includes share_url and download_url once complete.', + required: false, + }), + }, + async run(context) { + const { + project_id, + composition_id, + media_type, + resolution, + access_level, + callback_url, + } = context.propsValue; + + const body: Record = { project_id }; + if (composition_id) body['composition_id'] = composition_id; + if (media_type) body['media_type'] = media_type; + if (media_type === 'Video' && resolution) body['resolution'] = resolution; + if (access_level) body['access_level'] = access_level; + if (callback_url) body['callback_url'] = callback_url; + + const response = await descriptCommon.descriptApiCall<{ + job_id: string; + drive_id: string; + project_id: string; + project_url: string; + }>({ + apiKey: descriptCommon.getAuthToken(context.auth), + method: HttpMethod.POST, + path: '/jobs/publish', + body, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/descript/src/lib/auth.ts b/packages/pieces/community/descript/src/lib/auth.ts new file mode 100644 index 00000000000..cfdc2af472b --- /dev/null +++ b/packages/pieces/community/descript/src/lib/auth.ts @@ -0,0 +1,41 @@ +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { + AppConnectionValueForAuthProperty, + PieceAuth, +} from '@activepieces/pieces-framework'; + +export const descriptAuth = PieceAuth.SecretText({ + displayName: 'API Token', + description: [ + 'To create an API token:', + '1. Open **Settings** in Descript.', + '2. Select **API tokens** from the sidebar.', + '3. Click **Create token**, give it a name, select a Drive, and click **Create token**.', + '4. Copy the token and paste it here — you can only view it once.', + ].join('\n'), + required: true, + validate: async ({ auth }) => { + try { + await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://descriptapi.com/v1/status', + headers: { + Authorization: `Bearer ${getAuthToken(auth)}`, + }, + }); + return { valid: true }; + } catch { + return { + valid: false, + error: 'Invalid API token. Please check and try again.', + }; + } + }, +}); + +export function getAuthToken( + auth: string | AppConnectionValueForAuthProperty +): string { + const raw = typeof auth === 'string' ? auth : auth.secret_text; + return raw.trim(); +} diff --git a/packages/pieces/community/descript/src/lib/common/index.ts b/packages/pieces/community/descript/src/lib/common/index.ts new file mode 100644 index 00000000000..1def6b81dd9 --- /dev/null +++ b/packages/pieces/community/descript/src/lib/common/index.ts @@ -0,0 +1,152 @@ +import { + httpClient, + HttpMethod, + HttpMessageBody, + HttpResponse, +} from '@activepieces/pieces-common'; +import { Property } from '@activepieces/pieces-framework'; +import { descriptAuth, getAuthToken } from '../auth'; + +const BASE_URL = 'https://descriptapi.com/v1'; + +type ProjectSummary = { + id: string; + name: string; + created_at: string; + updated_at: string; +}; + +async function descriptApiCall({ + apiKey, + method, + path, + body, + queryParams, +}: { + apiKey: string; + method: HttpMethod; + path: string; + body?: unknown; + queryParams?: Record; +}): Promise> { + return httpClient.sendRequest({ + method, + url: `${BASE_URL}${path}`, + headers: { + Authorization: `Bearer ${apiKey}`, + }, + body, + queryParams, + }); +} + +async function fetchAllProjects(apiKey: string): Promise { + const projects: ProjectSummary[] = []; + let cursor: string | undefined = undefined; + + do { + const params: Record = { + limit: '100', + sort: 'name', + direction: 'asc', + }; + if (cursor) params['cursor'] = cursor; + + const response = await descriptApiCall<{ + data: ProjectSummary[]; + pagination: { next_cursor?: string }; + }>({ + apiKey, + method: HttpMethod.GET, + path: '/projects', + queryParams: params, + }); + + projects.push(...response.body.data); + cursor = response.body.pagination.next_cursor; + } while (cursor); + + return projects; +} + +const projectIdProp = Property.Dropdown({ + auth: descriptAuth, + displayName: 'Project', + description: 'Select the Descript project to use.', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first', + }; + } + try { + const projects = await fetchAllProjects(getAuthToken(auth)); + return { + disabled: false, + options: projects.map((p) => ({ label: p.name, value: p.id })), + }; + } catch { + return { + disabled: true, + options: [], + placeholder: 'Failed to load projects. Check your connection.', + }; + } + }, +}); + +const compositionIdProp = (required: boolean) => + Property.Dropdown({ + auth: descriptAuth, + displayName: 'Composition', + description: + 'Select the composition (timeline) within the project. If omitted, the agent chooses automatically.', + refreshers: ['project_id'], + required, + options: async ({ auth, project_id }) => { + if (!auth || !project_id || typeof project_id !== 'string') { + return { + disabled: true, + options: [], + placeholder: 'Please select a project first', + }; + } + try { + const response = await descriptApiCall<{ + id: string; + name: string; + compositions: { id: string; name: string }[]; + }>({ + apiKey: getAuthToken(auth), + method: HttpMethod.GET, + path: `/projects/${project_id}`, + }); + return { + disabled: false, + options: response.body.compositions.map((c) => ({ + label: c.name, + value: c.id, + })), + }; + } catch { + return { + disabled: true, + options: [], + placeholder: 'Failed to load compositions. Check your connection.', + }; + } + }, + }); + +export const descriptCommon = { + BASE_URL, + descriptApiCall, + fetchAllProjects, + getAuthToken, + projectIdProp, + compositionIdProp, +}; diff --git a/packages/pieces/community/descript/src/lib/triggers/job-completed.ts b/packages/pieces/community/descript/src/lib/triggers/job-completed.ts new file mode 100644 index 00000000000..b2bd9bc875b --- /dev/null +++ b/packages/pieces/community/descript/src/lib/triggers/job-completed.ts @@ -0,0 +1,154 @@ +import { + createTrigger, + TriggerStrategy, + Property, + AppConnectionValueForAuthProperty, +} from '@activepieces/pieces-framework'; +import { + DedupeStrategy, + Polling, + pollingHelper, + HttpMethod, +} from '@activepieces/pieces-common'; +import { descriptAuth } from '../auth'; +import { descriptCommon } from '../common'; + +type JobResult = { + status?: string; + agent_response?: string; + project_changed?: boolean; + media_seconds_used?: number; + ai_credits_used?: number; + share_url?: string; + download_url?: string; + download_url_expires_at?: string; + error_message?: string; +}; + +type JobItem = { + job_id: string; + job_type: string; + job_state: 'queued' | 'running' | 'stopped' | 'cancelled'; + created_at: string; + stopped_at?: string; + drive_id: string; + project_id: string; + project_url: string; + result?: JobResult; + progress?: { label: string; percent?: number; last_update_at?: string }; +}; + +type TriggerProps = { + job_type_filter: string | undefined; +}; + +// "stopped" covers all terminal outcomes (success/partial/error); "cancelled" is user-initiated. +const TERMINAL_STATES = new Set(['stopped', 'cancelled']); + +function isCompletedJob(job: JobItem): boolean { + return TERMINAL_STATES.has(job.job_state); +} + +const SAMPLE_DATA: JobItem = { + job_id: '6dc3f30a-58c2-4174-96a6-dc18cf3c7776', + job_type: 'import/project_media', + job_state: 'stopped', + created_at: '2025-11-18T10:30:00Z', + stopped_at: '2025-11-18T10:35:00Z', + drive_id: 'c9c5c47e-158a-49f7-846b-4f6ee2a229a2', + project_id: '9f36ee32-5a2c-47e7-b1a3-94991d3e3ddb', + project_url: 'https://web.descript.com/9f36ee32-5a2c-47e7-b1a3-94991d3e3ddb', + result: { + status: 'success', + media_seconds_used: 136, + }, +}; + +const polling: Polling< + AppConnectionValueForAuthProperty, + TriggerProps +> = { + strategy: DedupeStrategy.TIMEBASED, + items: async ({ auth, propsValue, lastFetchEpochMS }) => { + const { job_type_filter } = propsValue; + const apiKey = descriptCommon.getAuthToken(auth); + const params: Record = { limit: '100' }; + if (job_type_filter && job_type_filter !== 'all') { + params['type'] = job_type_filter; + } + + // Use created_after to avoid re-fetching full history on every poll. + if (lastFetchEpochMS > 0) { + params['created_after'] = new Date(lastFetchEpochMS).toISOString(); + } + + const allJobs: JobItem[] = []; + let cursor: string | undefined = undefined; + let pageCount = 0; + const maxPages = 10; + + do { + pageCount++; + const queryParams: Record = { + ...params, + ...(cursor ? { cursor } : {}), + }; + + const response = await descriptCommon.descriptApiCall<{ + data: JobItem[]; + pagination: { next_cursor?: string }; + }>({ + apiKey, + method: HttpMethod.GET, + path: '/jobs', + queryParams, + }); + + allJobs.push(...response.body.data); + cursor = response.body.pagination.next_cursor; + } while (cursor && pageCount < maxPages); + + return allJobs.filter(isCompletedJob).map((job) => ({ + epochMilliSeconds: new Date(job.stopped_at ?? job.created_at).getTime(), + data: { ...job }, + })); + }, +}; + +export const descriptJobCompletedTrigger = createTrigger({ + auth: descriptAuth, + name: 'job_completed', + displayName: 'Job Completed', + description: + 'Triggers when a Descript background job (import, agent edit, or publish) finishes.', + props: { + job_type_filter: Property.StaticDropdown({ + displayName: 'Job Type Filter', + description: + 'Only trigger for jobs of this type. Select "All job types" to trigger for any completed job.', + required: false, + defaultValue: 'all', + options: { + options: [ + { label: 'All job types', value: 'all' }, + { label: 'Import media', value: 'import/project_media' }, + { label: 'Agent edit (Underlord)', value: 'agent' }, + ], + }, + }), + }, + sampleData: SAMPLE_DATA, + type: TriggerStrategy.POLLING, + async test() { + return [SAMPLE_DATA]; + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, context); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, +}); diff --git a/packages/pieces/community/descript/tsconfig.json b/packages/pieces/community/descript/tsconfig.json new file mode 100644 index 00000000000..71bc5814f5d --- /dev/null +++ b/packages/pieces/community/descript/tsconfig.json @@ -0,0 +1,15 @@ +{ + "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/community/descript/tsconfig.lib.json b/packages/pieces/community/descript/tsconfig.lib.json new file mode 100644 index 00000000000..c7a66a28cfd --- /dev/null +++ b/packages/pieces/community/descript/tsconfig.lib.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "baseUrl": ".", + "paths": {}, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 8904faa9ec3..d94a8277b64 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -365,6 +365,9 @@ "packages/pieces/community/deftform/src/index.ts" ], "@activepieces/piece-delay": ["packages/pieces/core/delay/src/index.ts"], + "@activepieces/piece-descript": [ + "packages/pieces/community/descript/src/index.ts" + ], "@activepieces/piece-devin": [ "packages/pieces/community/devin/src/index.ts" ], From add680808f4dc329312a047622b8431791c85508 Mon Sep 17 00:00:00 2001 From: sanket-a11y Date: Tue, 5 May 2026 16:08:56 +0530 Subject: [PATCH 2/4] feat(parallel): add Parallel piece with web research actions (#13098) --- bun.lock | 24 +++- .../pieces/community/parallel/.eslintrc.json | 33 +++++ .../pieces/community/parallel/package.json | 17 +++ .../pieces/community/parallel/src/index.ts | 42 ++++++ .../src/lib/actions/chat-completion.ts | 90 ++++++++++++ .../src/lib/actions/create-findall-run.ts | 123 ++++++++++++++++ .../src/lib/actions/create-task-run.ts | 136 ++++++++++++++++++ .../parallel/src/lib/actions/extract.ts | 80 +++++++++++ .../src/lib/actions/get-findall-result.ts | 26 ++++ .../src/lib/actions/get-task-run-result.ts | 40 ++++++ .../parallel/src/lib/actions/get-task-run.ts | 25 ++++ .../parallel/src/lib/actions/search.ts | 100 +++++++++++++ .../pieces/community/parallel/src/lib/auth.ts | 33 +++++ .../parallel/src/lib/common/client.ts | 54 +++++++ .../pieces/community/parallel/tsconfig.json | 19 +++ .../community/parallel/tsconfig.lib.json | 22 +++ tsconfig.base.json | 3 + 17 files changed, 861 insertions(+), 6 deletions(-) create mode 100644 packages/pieces/community/parallel/.eslintrc.json create mode 100644 packages/pieces/community/parallel/package.json create mode 100644 packages/pieces/community/parallel/src/index.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/chat-completion.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/create-findall-run.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/create-task-run.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/extract.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/get-findall-result.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/get-task-run-result.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/get-task-run.ts create mode 100644 packages/pieces/community/parallel/src/lib/actions/search.ts create mode 100644 packages/pieces/community/parallel/src/lib/auth.ts create mode 100644 packages/pieces/community/parallel/src/lib/common/client.ts create mode 100644 packages/pieces/community/parallel/tsconfig.json create mode 100644 packages/pieces/community/parallel/tsconfig.lib.json diff --git a/bun.lock b/bun.lock index 64f375c6752..33e18c5771c 100644 --- a/bun.lock +++ b/bun.lock @@ -5206,6 +5206,16 @@ "tslib": "^2.3.0", }, }, + "packages/pieces/community/parallel": { + "name": "@activepieces/piece-parallel", + "version": "0.0.1", + "dependencies": { + "@activepieces/pieces-common": "workspace:*", + "@activepieces/pieces-framework": "workspace:*", + "@activepieces/shared": "workspace:*", + "tslib": "2.6.2", + }, + }, "packages/pieces/community/parser-expert": { "name": "@activepieces/piece-parser-expert", "version": "0.1.4", @@ -6390,7 +6400,7 @@ }, "packages/pieces/community/snowflake": { "name": "@activepieces/piece-snowflake", - "version": "0.3.1", + "version": "0.3.2", "dependencies": { "@activepieces/pieces-common": "workspace:*", "@activepieces/pieces-framework": "workspace:*", @@ -8260,7 +8270,7 @@ }, "packages/shared": { "name": "@activepieces/shared", - "version": "0.68.6", + "version": "0.71.1", "dependencies": { "dayjs": "1.11.9", "deepmerge-ts": "7.1.0", @@ -9330,6 +9340,8 @@ "@activepieces/piece-paperform": ["@activepieces/piece-paperform@workspace:packages/pieces/community/paperform"], + "@activepieces/piece-parallel": ["@activepieces/piece-parallel@workspace:packages/pieces/community/parallel"], + "@activepieces/piece-parser-expert": ["@activepieces/piece-parser-expert@workspace:packages/pieces/community/parser-expert"], "@activepieces/piece-parseur": ["@activepieces/piece-parseur@workspace:packages/pieces/community/parseur"], @@ -14446,7 +14458,7 @@ "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], - "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="], "quick-temp": ["quick-temp@0.1.9", "", { "dependencies": { "mktemp": "^2.0.1", "rimraf": "^5.0.10", "underscore.string": "~3.3.6" } }, "sha512-yI0h7tIhKVObn03kD+Ln9JFi4OljD28lfaOsTdfpTR0xzrhGOod+q66CjGafUqYX2juUfT9oHIGrTBBo22mkRA=="], @@ -16068,7 +16080,7 @@ "@dnd-kit/accessibility/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@dust-tt/client/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@github:dust-tt/typescript-sdk#bca26e4", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "dust-tt-typescript-sdk-bca26e4", "sha512-oxLE3SEGCVIhVNbn7xB9YgL7I6zrKujcH9s6e/1KcAj1VOht4lnM/oetqhwh9XhBh02cDHwkqHTzzIEBnBViiw=="], + "@dust-tt/client/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@github:dust-tt/typescript-sdk#bca26e4", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "dust-tt-typescript-sdk-bca26e4"], "@dust-tt/client/eventsource-parser": ["eventsource-parser@1.1.2", "", {}, "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA=="], @@ -17084,8 +17096,6 @@ "camelcase-keys/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - "camelcase-keys/quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="], - "checkly/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.1", "@typescript-eslint/tsconfig-utils": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g=="], "checkly/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], @@ -17374,6 +17384,8 @@ "http-call/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], + "http2-wrapper/quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "hume/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "hume/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], diff --git a/packages/pieces/community/parallel/.eslintrc.json b/packages/pieces/community/parallel/.eslintrc.json new file mode 100644 index 00000000000..359ff63d51d --- /dev/null +++ b/packages/pieces/community/parallel/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/parallel/package.json b/packages/pieces/community/parallel/package.json new file mode 100644 index 00000000000..08bcbd9da24 --- /dev/null +++ b/packages/pieces/community/parallel/package.json @@ -0,0 +1,17 @@ +{ + "name": "@activepieces/piece-parallel", + "version": "0.0.1", + "type": "commonjs", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "dependencies": { + "@activepieces/pieces-common": "workspace:*", + "@activepieces/pieces-framework": "workspace:*", + "@activepieces/shared": "workspace:*", + "tslib": "2.6.2" + }, + "scripts": { + "build": "tsc -p tsconfig.lib.json && cp package.json dist/", + "lint": "eslint 'src/**/*.ts'" + } +} \ No newline at end of file diff --git a/packages/pieces/community/parallel/src/index.ts b/packages/pieces/community/parallel/src/index.ts new file mode 100644 index 00000000000..669d0ce2787 --- /dev/null +++ b/packages/pieces/community/parallel/src/index.ts @@ -0,0 +1,42 @@ +import { createCustomApiCallAction } from '@activepieces/pieces-common'; +import { createPiece } from '@activepieces/pieces-framework'; +import { PieceCategory } from '@activepieces/shared'; +import { parallelAuth } from './lib/auth'; +import { chatCompletionAction } from './lib/actions/chat-completion'; +import { createFindAllRunAction } from './lib/actions/create-findall-run'; +import { createTaskRunAction } from './lib/actions/create-task-run'; +import { extractAction } from './lib/actions/extract'; +import { getFindAllResultAction } from './lib/actions/get-findall-result'; +import { getTaskRunAction } from './lib/actions/get-task-run'; +import { getTaskRunResultAction } from './lib/actions/get-task-run-result'; +import { searchAction } from './lib/actions/search'; +import { PARALLEL_BASE_URL } from './lib/common/client'; + +export const parallel = createPiece({ + displayName: 'Parallel', + description: + 'Web research APIs for AI: search, extract, multi-hop research tasks, entity discovery (FindAll), and web monitoring.', + auth: parallelAuth, + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/parallel.png', + categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE], + authors: ['sanket-a11y'], + actions: [ + searchAction, + extractAction, + createTaskRunAction, + getTaskRunAction, + getTaskRunResultAction, + createFindAllRunAction, + getFindAllResultAction, + chatCompletionAction, + createCustomApiCallAction({ + baseUrl: () => PARALLEL_BASE_URL, + auth: parallelAuth, + authMapping: async (auth) => ({ + 'x-api-key': auth.secret_text, + }), + }), + ], + triggers: [], +}); diff --git a/packages/pieces/community/parallel/src/lib/actions/chat-completion.ts b/packages/pieces/community/parallel/src/lib/actions/chat-completion.ts new file mode 100644 index 00000000000..36b35ab7e2c --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/chat-completion.ts @@ -0,0 +1,90 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient } from '../common/client'; + +export const chatCompletionAction = createAction({ + auth: parallelAuth, + name: 'chat_completion', + displayName: 'Chat Completion', + description: + "Get a chat completion using a Parallel processor. OpenAI-compatible interface; processor names like 'speed', 'base', 'core', 'pro', 'ultra' map to Parallel tiers.", + props: { + model: Property.ShortText({ + displayName: 'Model / Processor', + description: + 'Model name to use (e.g. `speed`, `base`, `core`, `pro`, `ultra`).', + required: true, + defaultValue: 'speed', + }), + system: Property.LongText({ + displayName: 'System Message', + description: 'Optional system message to set the assistant behaviour.', + required: false, + }), + user_message: Property.LongText({ + displayName: 'User Message', + description: 'The user prompt to send.', + required: true, + }), + response_format: Property.StaticDropdown({ + displayName: 'Response Format', + description: 'Whether the model should return free text or a JSON object.', + required: false, + defaultValue: 'text', + options: { + options: [ + { label: 'Text', value: 'text' }, + { label: 'JSON Object', value: 'json_object' }, + { label: 'JSON Schema', value: 'json_schema' }, + ], + }, + }), + json_schema: Property.Json({ + displayName: 'JSON Schema', + description: + 'Used when Response Format is "JSON Schema". Provide a JSON Schema object.', + required: false, + }), + json_schema_name: Property.ShortText({ + displayName: 'JSON Schema Name', + description: 'Name for the JSON schema (required when Response Format is JSON Schema).', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + const messages: Array> = []; + if (props.system) { + messages.push({ role: 'system', content: props.system }); + } + messages.push({ role: 'user', content: props.user_message }); + + const body: Record = { + model: props.model, + messages, + }; + + if (props.response_format === 'json_object') { + body['response_format'] = { type: 'json_object' }; + } else if (props.response_format === 'json_schema') { + if (!props.json_schema || typeof props.json_schema !== 'object') { + throw new Error('JSON Schema is required when Response Format is "JSON Schema".'); + } + body['response_format'] = { + type: 'json_schema', + json_schema: { + name: props.json_schema_name ?? 'response', + schema: props.json_schema, + }, + }; + } + + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.POST, + path: '/v1beta/chat/completions', + body, + }); + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/actions/create-findall-run.ts b/packages/pieces/community/parallel/src/lib/actions/create-findall-run.ts new file mode 100644 index 00000000000..3ecbee2a0fe --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/create-findall-run.ts @@ -0,0 +1,123 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { FINDALL_GENERATORS, parallelClient } from '../common/client'; + +export const createFindAllRunAction = createAction({ + auth: parallelAuth, + name: 'create_findall_run', + displayName: 'Create FindAll Run', + description: + 'Discover and verify entities matching plain-language criteria. Match conditions can be supplied directly or auto-generated from the objective.', + props: { + objective: Property.LongText({ + displayName: 'Objective', + description: + 'Natural-language description of the target entities. e.g. "AI companies that raised Series A funding in 2024".', + required: true, + }), + entity_type: Property.ShortText({ + displayName: 'Entity Type', + description: 'Type of entities to find (e.g. "company", "people").', + required: true, + }), + match_conditions: Property.Json({ + displayName: 'Match Conditions', + description: + 'Array of `{ "name": "...", "description": "..." }` objects describing the criteria each match must satisfy. Leave empty to auto-generate from the objective.', + required: false, + defaultValue: [], + }), + generator: Property.StaticDropdown({ + displayName: 'Generator', + description: 'Generator tier — higher tiers use deeper search at higher cost.', + required: true, + defaultValue: 'core', + options: { options: FINDALL_GENERATORS }, + }), + match_limit: Property.Number({ + displayName: 'Match Limit', + description: 'Maximum number of matches to find (5–1000). Defaults to 100.', + required: true, + defaultValue: 100, + }), + metadata: Property.Object({ + displayName: 'Metadata', + description: 'Optional key/value metadata stored with the run.', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + const apiKey = context.auth.secret_text; + + const providedConditions = normalizeMatchConditions(props.match_conditions); + + let entityType = props.entity_type; + let generator = props.generator; + let matchConditions = providedConditions; + + if (matchConditions.length === 0) { + const ingested = await parallelClient.request<{ + entity_type?: string; + match_conditions?: Array<{ name: string; description: string }>; + generator?: string; + }>({ + apiKey, + method: HttpMethod.POST, + path: '/v1beta/findall/ingest', + body: { objective: props.objective }, + }); + + if (!ingested.match_conditions || ingested.match_conditions.length === 0) { + throw new Error( + 'Could not auto-generate match conditions from the objective. Please provide them explicitly.', + ); + } + matchConditions = ingested.match_conditions; + if (!entityType && ingested.entity_type) entityType = ingested.entity_type; + if (ingested.generator) generator = ingested.generator; + } + + const body: Record = { + objective: props.objective, + entity_type: entityType, + match_conditions: matchConditions, + generator, + match_limit: props.match_limit, + }; + if (props.metadata && Object.keys(props.metadata).length > 0) { + body['metadata'] = props.metadata; + } + + return await parallelClient.request({ + apiKey, + method: HttpMethod.POST, + path: '/v1beta/findall/runs', + body, + }); + }, +}); + +function normalizeMatchConditions( + raw: unknown, +): Array<{ name: string; description: string }> { + if (!Array.isArray(raw)) return []; + const out: Array<{ name: string; description: string }> = []; + for (const item of raw) { + if ( + item && + typeof item === 'object' && + 'name' in item && + 'description' in item && + typeof (item as Record)['name'] === 'string' && + typeof (item as Record)['description'] === 'string' + ) { + out.push({ + name: (item as Record)['name'], + description: (item as Record)['description'], + }); + } + } + return out; +} diff --git a/packages/pieces/community/parallel/src/lib/actions/create-task-run.ts b/packages/pieces/community/parallel/src/lib/actions/create-task-run.ts new file mode 100644 index 00000000000..18cd3c8d757 --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/create-task-run.ts @@ -0,0 +1,136 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient, TASK_PROCESSORS } from '../common/client'; + +export const createTaskRunAction = createAction({ + auth: parallelAuth, + name: 'create_task_run', + displayName: 'Create Task Run', + description: + 'Initiate a Parallel research task. Returns immediately with a queued run; use Get Task Run Result to wait for completion.', + props: { + input: Property.LongText({ + displayName: 'Input', + description: + 'Input to the task (free text or a JSON string). Example: "What was the GDP of France in 2023?"', + required: true, + }), + processor: Property.StaticDropdown({ + displayName: 'Processor', + description: 'Speed/depth tier for the task.', + required: true, + defaultValue: 'base', + options: { options: TASK_PROCESSORS }, + }), + output_schema_type: Property.StaticDropdown({ + displayName: 'Output Schema Type', + description: 'How the task should structure its output.', + required: false, + defaultValue: 'auto', + options: { + options: [ + { label: 'Auto', value: 'auto' }, + { label: 'Text', value: 'text' }, + { label: 'JSON Schema', value: 'json' }, + ], + }, + }), + output_text_description: Property.LongText({ + displayName: 'Output Description (Text)', + description: + 'Used when Output Schema Type is "Text". Plain-language description of the desired output.', + required: false, + }), + output_json_schema: Property.Json({ + displayName: 'Output JSON Schema', + description: + 'Used when Output Schema Type is "JSON Schema". A JSON Schema object describing the desired structured output.', + required: false, + }), + include_domains: Property.Array({ + displayName: 'Include Domains', + description: 'Optional source policy: only return results from these domains.', + required: false, + }), + exclude_domains: Property.Array({ + displayName: 'Exclude Domains', + description: 'Optional source policy: exclude results from these domains.', + required: false, + }), + metadata: Property.Object({ + displayName: 'Metadata', + description: + 'Optional key/value metadata stored with the run (keys ≤16 chars, values ≤512 chars).', + required: false, + }), + }, + async run(context) { + const props = context.propsValue; + + const includeDomains = ((props.include_domains ?? []) as unknown[]).filter( + (d): d is string => typeof d === 'string' && d.trim().length > 0, + ); + const excludeDomains = ((props.exclude_domains ?? []) as unknown[]).filter( + (d): d is string => typeof d === 'string' && d.trim().length > 0, + ); + + const sourcePolicy = + includeDomains.length || excludeDomains.length + ? { + ...(includeDomains.length ? { include_domains: includeDomains } : {}), + ...(excludeDomains.length ? { exclude_domains: excludeDomains } : {}), + } + : undefined; + + const taskSpec = buildTaskSpec({ + type: props.output_schema_type, + textDescription: props.output_text_description, + jsonSchema: props.output_json_schema, + }); + + const body: Record = { + input: props.input, + processor: props.processor, + }; + if (taskSpec) body['task_spec'] = taskSpec; + if (sourcePolicy) body['source_policy'] = sourcePolicy; + if (props.metadata && Object.keys(props.metadata).length > 0) { + body['metadata'] = props.metadata; + } + + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.POST, + path: '/v1/tasks/runs', + body, + }); + }, +}); + +function buildTaskSpec({ + type, + textDescription, + jsonSchema, +}: { + type: string | undefined; + textDescription: string | undefined; + jsonSchema: unknown; +}): Record | undefined { + if (!type || type === 'auto') { + return undefined; + } + if (type === 'text') { + if (!textDescription) return undefined; + return { + output_schema: { type: 'text', description: textDescription }, + }; + } + if (type === 'json') { + if (!jsonSchema || typeof jsonSchema !== 'object') return undefined; + return { + output_schema: { type: 'json', json_schema: jsonSchema }, + }; + } + return undefined; +} diff --git a/packages/pieces/community/parallel/src/lib/actions/extract.ts b/packages/pieces/community/parallel/src/lib/actions/extract.ts new file mode 100644 index 00000000000..174d62252c1 --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/extract.ts @@ -0,0 +1,80 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient } from '../common/client'; + +export const extractAction = createAction({ + auth: parallelAuth, + name: 'extract', + displayName: 'Extract Web Content', + description: + 'Extract clean, citation-aware content from specific web URLs. Up to 20 URLs per request.', + props: { + urls: Property.Array({ + displayName: 'URLs', + description: 'List of URLs to extract content from (max 20).', + required: true, + }), + objective: Property.LongText({ + displayName: 'Objective', + description: + 'Natural-language description of the goal driving the extraction. Used to focus excerpts on the most relevant content.', + required: false, + }), + search_queries: Property.Array({ + displayName: 'Search Queries', + description: + 'Optional keyword search queries used together with the objective to focus excerpts.', + required: false, + }), + full_content: Property.Checkbox({ + displayName: 'Include Full Content', + description: + 'When enabled, requests full markdown of each page in addition to excerpts. May increase latency.', + required: false, + defaultValue: false, + }), + max_chars_total: Property.Number({ + displayName: 'Max Total Characters', + description: 'Upper bound on total characters across excerpts from all results.', + required: false, + }), + session_id: Property.ShortText({ + displayName: 'Session ID', + description: 'Optional session identifier to share context across search/extract calls.', + required: false, + }), + }, + async run(context) { + const urls = ((context.propsValue.urls ?? []) as unknown[]).filter( + (u): u is string => typeof u === 'string' && u.trim().length > 0, + ); + if (urls.length === 0) { + throw new Error('At least one URL is required.'); + } + + const queries = ((context.propsValue.search_queries ?? []) as unknown[]).filter( + (q): q is string => typeof q === 'string' && q.trim().length > 0, + ); + + const body: Record = { urls }; + if (context.propsValue.objective) body['objective'] = context.propsValue.objective; + if (queries.length) body['search_queries'] = queries; + if (context.propsValue.max_chars_total !== undefined && context.propsValue.max_chars_total !== null) { + body['max_chars_total'] = context.propsValue.max_chars_total; + } + if (context.propsValue.session_id) body['session_id'] = context.propsValue.session_id; + if (context.propsValue.full_content) { + body['advanced_settings'] = { + full_content: { enabled: true }, + }; + } + + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.POST, + path: '/v1/extract', + body, + }); + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/actions/get-findall-result.ts b/packages/pieces/community/parallel/src/lib/actions/get-findall-result.ts new file mode 100644 index 00000000000..c7bdb02fb62 --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/get-findall-result.ts @@ -0,0 +1,26 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient } from '../common/client'; + +export const getFindAllResultAction = createAction({ + auth: parallelAuth, + name: 'get_findall_result', + displayName: 'Get FindAll Result', + description: + 'Return the current snapshot of matched candidates for a FindAll run, including any enrichment fields.', + props: { + findall_id: Property.ShortText({ + displayName: 'FindAll ID', + description: 'The FindAll run ID returned from Create FindAll Run.', + required: true, + }), + }, + async run(context) { + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.GET, + path: `/v1beta/findall/runs/${encodeURIComponent(context.propsValue.findall_id)}/result`, + }); + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/actions/get-task-run-result.ts b/packages/pieces/community/parallel/src/lib/actions/get-task-run-result.ts new file mode 100644 index 00000000000..8d5991ca6dd --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/get-task-run-result.ts @@ -0,0 +1,40 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient } from '../common/client'; + +export const getTaskRunResultAction = createAction({ + auth: parallelAuth, + name: 'get_task_run_result', + displayName: 'Get Task Run Result', + description: + 'Retrieve a task run result by run ID. Blocks until the run completes (or until the timeout is reached).', + props: { + run_id: Property.ShortText({ + displayName: 'Run ID', + description: 'The task run ID returned from Create Task Run.', + required: true, + }), + timeout: Property.Number({ + displayName: 'Timeout (seconds)', + description: 'How long to wait for the run to complete. Defaults to 600 seconds.', + required: false, + defaultValue: 600, + }), + }, + async run(context) { + const queryParams: Record = {}; + if ( + context.propsValue.timeout !== undefined && + context.propsValue.timeout !== null + ) { + queryParams['timeout'] = String(context.propsValue.timeout); + } + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.GET, + path: `/v1/tasks/runs/${encodeURIComponent(context.propsValue.run_id)}/result`, + queryParams, + }); + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/actions/get-task-run.ts b/packages/pieces/community/parallel/src/lib/actions/get-task-run.ts new file mode 100644 index 00000000000..c9647ea33bc --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/get-task-run.ts @@ -0,0 +1,25 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient } from '../common/client'; + +export const getTaskRunAction = createAction({ + auth: parallelAuth, + name: 'get_task_run', + displayName: 'Get Task Run Status', + description: 'Retrieve the current status of a task run by its ID.', + props: { + run_id: Property.ShortText({ + displayName: 'Run ID', + description: 'The task run ID, e.g. `trun_e0083b6aac0544eb8686e8d2a76533d2`.', + required: true, + }), + }, + async run(context) { + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.GET, + path: `/v1/tasks/runs/${encodeURIComponent(context.propsValue.run_id)}`, + }); + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/actions/search.ts b/packages/pieces/community/parallel/src/lib/actions/search.ts new file mode 100644 index 00000000000..0231912ed17 --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/actions/search.ts @@ -0,0 +1,100 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { parallelAuth } from '../auth'; +import { parallelClient } from '../common/client'; + +export const searchAction = createAction({ + auth: parallelAuth, + name: 'search', + displayName: 'Search the Web', + description: + 'Search the web with natural-language objectives. Returns LLM-optimized excerpts with citations.', + props: { + objective: Property.LongText({ + displayName: 'Objective', + description: + 'Natural-language description of the underlying question or goal driving the search. Should be self-contained with enough context to understand the intent.', + required: false, + }), + search_queries: Property.Array({ + displayName: 'Search Queries', + description: + 'Concise keyword search queries (3-6 words each). At least one query is required; 2-3 is best.', + required: true, + }), + mode: Property.StaticDropdown({ + displayName: 'Mode', + description: + 'Basic: low latency with 2-3 high-quality queries. Advanced: higher quality with deeper retrieval.', + required: false, + defaultValue: 'advanced', + options: { + options: [ + { label: 'Basic', value: 'basic' }, + { label: 'Advanced', value: 'advanced' }, + ], + }, + }), + max_chars_total: Property.Number({ + displayName: 'Max Total Characters', + description: 'Upper bound on total characters across excerpts from all results.', + required: false, + }), + include_domains: Property.Array({ + displayName: 'Include Domains', + description: 'Optional list of domains to restrict results to (e.g. wikipedia.org, .gov).', + required: false, + }), + exclude_domains: Property.Array({ + displayName: 'Exclude Domains', + description: 'Optional list of domains to exclude from results.', + required: false, + }), + session_id: Property.ShortText({ + displayName: 'Session ID', + description: 'Optional session identifier to share context across search/extract calls.', + required: false, + }), + }, + async run(context) { + const queries = (context.propsValue.search_queries ?? []).filter( + (q): q is string => typeof q === 'string' && q.trim().length > 0, + ); + if (queries.length === 0) { + throw new Error('At least one search query is required.'); + } + + const includeDomains = ((context.propsValue.include_domains ?? []) as unknown[]).filter( + (d): d is string => typeof d === 'string' && d.trim().length > 0, + ); + const excludeDomains = ((context.propsValue.exclude_domains ?? []) as unknown[]).filter( + (d): d is string => typeof d === 'string' && d.trim().length > 0, + ); + + const sourcePolicy = + includeDomains.length || excludeDomains.length + ? { + ...(includeDomains.length ? { include_domains: includeDomains } : {}), + ...(excludeDomains.length ? { exclude_domains: excludeDomains } : {}), + } + : undefined; + + const body: Record = { + search_queries: queries, + }; + if (context.propsValue.objective) body['objective'] = context.propsValue.objective; + if (context.propsValue.mode) body['mode'] = context.propsValue.mode; + if (context.propsValue.max_chars_total !== undefined && context.propsValue.max_chars_total !== null) { + body['max_chars_total'] = context.propsValue.max_chars_total; + } + if (context.propsValue.session_id) body['session_id'] = context.propsValue.session_id; + if (sourcePolicy) body['source_policy'] = sourcePolicy; + + return await parallelClient.request({ + apiKey: context.auth.secret_text, + method: HttpMethod.POST, + path: '/v1/search', + body, + }); + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/auth.ts b/packages/pieces/community/parallel/src/lib/auth.ts new file mode 100644 index 00000000000..c0db96d1303 --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/auth.ts @@ -0,0 +1,33 @@ +import { HttpMethod } from '@activepieces/pieces-common'; +import { PieceAuth } from '@activepieces/pieces-framework'; +import { parallelClient } from './common/client'; + +export const parallelAuth = PieceAuth.SecretText({ + displayName: 'API Key', + required: true, + description: ` +**Get your Parallel API key:** + +1. Sign in at https://platform.parallel.ai +2. Navigate to **API Keys** +3. Create a new key and paste it here. + `, + validate: async ({ auth }) => { + try { + await parallelClient.request({ + apiKey: auth, + method: HttpMethod.POST, + path: '/v1/search', + body: { + objective: 'API key validation', + search_queries: ['parallel ai api'], + mode: 'basic', + max_chars_total: 100, + }, + }); + return { valid: true }; + } catch (e) { + return { valid: false, error: 'Invalid API key' }; + } + }, +}); diff --git a/packages/pieces/community/parallel/src/lib/common/client.ts b/packages/pieces/community/parallel/src/lib/common/client.ts new file mode 100644 index 00000000000..d028090c1f8 --- /dev/null +++ b/packages/pieces/community/parallel/src/lib/common/client.ts @@ -0,0 +1,54 @@ +import { + HttpMessageBody, + HttpMethod, + HttpRequest, + httpClient, +} from '@activepieces/pieces-common'; + +async function callApi({ + apiKey, + method, + path, + body, + queryParams, +}: { + apiKey: string; + method: HttpMethod; + path: string; + body?: unknown; + queryParams?: Record; +}): Promise { + const request: HttpRequest = { + method, + url: `${PARALLEL_BASE_URL}${path}`, + headers: { + 'x-api-key': apiKey, + 'Content-Type': 'application/json', + }, + body, + queryParams, + }; + const response = await httpClient.sendRequest(request); + return response.body; +} + +export const parallelClient = { + request: callApi, +}; + +export const PARALLEL_BASE_URL = 'https://api.parallel.ai'; + +export const TASK_PROCESSORS = [ + { label: 'Lite', value: 'lite' }, + { label: 'Base', value: 'base' }, + { label: 'Core', value: 'core' }, + { label: 'Pro', value: 'pro' }, + { label: 'Ultra', value: 'ultra' }, +]; + +export const FINDALL_GENERATORS = [ + { label: 'Base', value: 'base' }, + { label: 'Core', value: 'core' }, + { label: 'Pro', value: 'pro' }, + { label: 'Preview', value: 'preview' }, +]; diff --git a/packages/pieces/community/parallel/tsconfig.json b/packages/pieces/community/parallel/tsconfig.json new file mode 100644 index 00000000000..b512ca3708c --- /dev/null +++ b/packages/pieces/community/parallel/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" + } + ] +} \ No newline at end of file diff --git a/packages/pieces/community/parallel/tsconfig.lib.json b/packages/pieces/community/parallel/tsconfig.lib.json new file mode 100644 index 00000000000..8b4345e5114 --- /dev/null +++ b/packages/pieces/community/parallel/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "baseUrl": ".", + "paths": {}, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] +} \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index d94a8277b64..e4edffe5766 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -881,6 +881,9 @@ "@activepieces/piece-pagerduty": [ "packages/pieces/community/pagerduty/src/index.ts" ], + "@activepieces/piece-parallel": [ + "packages/pieces/community/parallel/src/index.ts" + ], "@activepieces/piece-parseur": [ "packages/pieces/community/parseur/src/index.ts" ], From f529835e0a6dea2796123f2428c6b373aebc312c Mon Sep 17 00:00:00 2001 From: Fionn Hughes Date: Tue, 5 May 2026 11:55:04 +0100 Subject: [PATCH 3/4] feat(piece): add Chess.com community piece (#13018) Co-authored-by: Ahmad Tash --- bun.lock | 12 ++++++ .../pieces/community/chess-com/.eslintrc.json | 33 ++++++++++++++++ packages/pieces/community/chess-com/README.md | 5 +++ .../pieces/community/chess-com/package.json | 16 ++++++++ .../chess-com/src/i18n/translation.json | 11 ++++++ .../pieces/community/chess-com/src/index.ts | 16 ++++++++ .../src/lib/actions/get-daily-puzzle.ts | 16 ++++++++ .../src/lib/actions/get-player-profile.ts | 38 +++++++++++++++++++ .../src/lib/actions/get-player-stats.ts | 38 +++++++++++++++++++ .../pieces/community/chess-com/tsconfig.json | 19 ++++++++++ .../community/chess-com/tsconfig.lib.json | 15 ++++++++ tsconfig.base.json | 3 ++ 12 files changed, 222 insertions(+) create mode 100644 packages/pieces/community/chess-com/.eslintrc.json create mode 100644 packages/pieces/community/chess-com/README.md create mode 100644 packages/pieces/community/chess-com/package.json create mode 100644 packages/pieces/community/chess-com/src/i18n/translation.json create mode 100644 packages/pieces/community/chess-com/src/index.ts create mode 100644 packages/pieces/community/chess-com/src/lib/actions/get-daily-puzzle.ts create mode 100644 packages/pieces/community/chess-com/src/lib/actions/get-player-profile.ts create mode 100644 packages/pieces/community/chess-com/src/lib/actions/get-player-stats.ts create mode 100644 packages/pieces/community/chess-com/tsconfig.json create mode 100644 packages/pieces/community/chess-com/tsconfig.lib.json diff --git a/bun.lock b/bun.lock index 33e18c5771c..ce26e738090 100644 --- a/bun.lock +++ b/bun.lock @@ -1595,6 +1595,16 @@ "tslib": "^2.3.0", }, }, + "packages/pieces/community/chess-com": { + "name": "@activepieces/piece-chess-com", + "version": "0.0.1", + "dependencies": { + "@activepieces/pieces-common": "workspace:*", + "@activepieces/pieces-framework": "workspace:*", + "@activepieces/shared": "workspace:*", + "tslib": "2.6.2", + }, + }, "packages/pieces/community/circle": { "name": "@activepieces/piece-circle", "version": "0.1.4", @@ -8656,6 +8666,8 @@ "@activepieces/piece-checkout": ["@activepieces/piece-checkout@workspace:packages/pieces/community/checkout"], + "@activepieces/piece-chess-com": ["@activepieces/piece-chess-com@workspace:packages/pieces/community/chess-com"], + "@activepieces/piece-circle": ["@activepieces/piece-circle@workspace:packages/pieces/community/circle"], "@activepieces/piece-clarifai": ["@activepieces/piece-clarifai@workspace:packages/pieces/community/clarifai"], diff --git a/packages/pieces/community/chess-com/.eslintrc.json b/packages/pieces/community/chess-com/.eslintrc.json new file mode 100644 index 00000000000..610e15b05bf --- /dev/null +++ b/packages/pieces/community/chess-com/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "../../../../.eslintrc.base.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {} + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/chess-com/README.md b/packages/pieces/community/chess-com/README.md new file mode 100644 index 00000000000..c4ea12dcf32 --- /dev/null +++ b/packages/pieces/community/chess-com/README.md @@ -0,0 +1,5 @@ +# pieces-chess-com + +## Building + +Run `turbo run build --filter=@activepieces/piece-chess-com` to build the library. diff --git a/packages/pieces/community/chess-com/package.json b/packages/pieces/community/chess-com/package.json new file mode 100644 index 00000000000..174568cd91c --- /dev/null +++ b/packages/pieces/community/chess-com/package.json @@ -0,0 +1,16 @@ +{ + "name": "@activepieces/piece-chess-com", + "version": "0.0.1", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.lib.json && cp package.json dist/", + "lint": "eslint 'src/**/*.ts'" + }, + "dependencies": { + "@activepieces/pieces-common": "workspace:*", + "@activepieces/pieces-framework": "workspace:*", + "@activepieces/shared": "workspace:*", + "tslib": "2.6.2" + } +} diff --git a/packages/pieces/community/chess-com/src/i18n/translation.json b/packages/pieces/community/chess-com/src/i18n/translation.json new file mode 100644 index 00000000000..2210b43bc94 --- /dev/null +++ b/packages/pieces/community/chess-com/src/i18n/translation.json @@ -0,0 +1,11 @@ +{ + "Access Chess.com player data": "Access Chess.com player data", + "Get Player Profile": "Get Player Profile", + "Get Player Stats": "Get Player Stats", + "Get Daily Puzzle": "Get Daily Puzzle", + "Retrieve a Chess.com player's public profile by username (avatar, country, join date, followers).": "Retrieve a Chess.com player's public profile by username (avatar, country, join date, followers).", + "Retrieve a Chess.com player's ratings and W/D/L for rapid, blitz, bullet, and chess960.": "Retrieve a Chess.com player's ratings and W/D/L for rapid, blitz, bullet, and chess960.", + "Retrieve today's Chess.com daily puzzle.": "Retrieve today's Chess.com daily puzzle.", + "Username": "Username", + "The Chess.com username to look up.": "The Chess.com username to look up." +} diff --git a/packages/pieces/community/chess-com/src/index.ts b/packages/pieces/community/chess-com/src/index.ts new file mode 100644 index 00000000000..c440494058d --- /dev/null +++ b/packages/pieces/community/chess-com/src/index.ts @@ -0,0 +1,16 @@ +import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { getPlayerProfile } from './lib/actions/get-player-profile'; +import { getPlayerStats } from './lib/actions/get-player-stats'; +import { getDailyPuzzle } from './lib/actions/get-daily-puzzle'; + +export const chesscom = createPiece({ + displayName: 'Chess.com', + description: 'Access Chess.com player data', + auth: PieceAuth.None(), + minimumSupportedRelease: '0.36.1', + logoUrl: 'https://cdn.activepieces.com/pieces/chess-com.png', + categories: [], + authors: ['FionnHughes'], + actions: [getPlayerProfile, getPlayerStats, getDailyPuzzle], + triggers: [], +}); diff --git a/packages/pieces/community/chess-com/src/lib/actions/get-daily-puzzle.ts b/packages/pieces/community/chess-com/src/lib/actions/get-daily-puzzle.ts new file mode 100644 index 00000000000..79e47dec272 --- /dev/null +++ b/packages/pieces/community/chess-com/src/lib/actions/get-daily-puzzle.ts @@ -0,0 +1,16 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const getDailyPuzzle = createAction({ + name: 'get_daily_puzzle', + displayName: 'Get Daily Puzzle', + description: "Retrieve today's Chess.com daily puzzle.", + props: {}, + async run() { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.chess.com/pub/puzzle', + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/chess-com/src/lib/actions/get-player-profile.ts b/packages/pieces/community/chess-com/src/lib/actions/get-player-profile.ts new file mode 100644 index 00000000000..11d16e38156 --- /dev/null +++ b/packages/pieces/community/chess-com/src/lib/actions/get-player-profile.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + propsValidation, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getPlayerProfile = createAction({ + name: 'get_player_profile', + displayName: 'Get Player Profile', + description: + "Retrieve a Chess.com player's public profile by username (avatar, country, join date, followers).", + props: { + username: Property.ShortText({ + displayName: 'Username', + description: 'The Chess.com username to look up.', + required: true, + }), + }, + async run({ propsValue }) { + await propsValidation.validateZod(propsValue, { + username: z + .string() + .trim() + .min(3) + .max(25) + .regex(/^[A-Za-z0-9_-]+$/), + }); + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.chess.com/pub/player/${encodeURIComponent( + propsValue.username.trim().toLowerCase(), + )}`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/chess-com/src/lib/actions/get-player-stats.ts b/packages/pieces/community/chess-com/src/lib/actions/get-player-stats.ts new file mode 100644 index 00000000000..74866f55b2c --- /dev/null +++ b/packages/pieces/community/chess-com/src/lib/actions/get-player-stats.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + httpClient, + HttpMethod, + propsValidation, +} from '@activepieces/pieces-common'; +import { z } from 'zod'; + +export const getPlayerStats = createAction({ + name: 'get_player_stats', + displayName: 'Get Player Stats', + description: + 'Retrieve a Chess.com player\'s ratings and W/D/L for rapid, blitz, bullet, and chess960.', + props: { + username: Property.ShortText({ + displayName: 'Username', + description: 'The Chess.com username to look up.', + required: true, + }), + }, + async run({ propsValue }) { + await propsValidation.validateZod(propsValue, { + username: z + .string() + .trim() + .min(3) + .max(25) + .regex(/^[A-Za-z0-9_-]+$/), + }); + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.chess.com/pub/player/${encodeURIComponent( + propsValue.username.trim().toLowerCase(), + )}/stats`, + }); + return response.body; + }, +}); diff --git a/packages/pieces/community/chess-com/tsconfig.json b/packages/pieces/community/chess-com/tsconfig.json new file mode 100644 index 00000000000..29c9dd1bfc1 --- /dev/null +++ b/packages/pieces/community/chess-com/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/chess-com/tsconfig.lib.json b/packages/pieces/community/chess-com/tsconfig.lib.json new file mode 100644 index 00000000000..0ba4caeb858 --- /dev/null +++ b/packages/pieces/community/chess-com/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "rootDir": ".", + "baseUrl": ".", + "paths": {}, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index e4edffe5766..36f68711ac0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -243,6 +243,9 @@ "@activepieces/piece-checkout": [ "packages/pieces/community/checkout/src/index.ts" ], + "@activepieces/piece-chess-com": [ + "packages/pieces/community/chess-com/src/index.ts" + ], "@activepieces/piece-circle": [ "packages/pieces/community/circle/src/index.ts" ], From bb4eb29eb1006f0820da997ab03afae36b2ae20f Mon Sep 17 00:00:00 2001 From: Hazem Adel Date: Tue, 5 May 2026 14:52:01 +0300 Subject: [PATCH 4/4] fix(chat): replace hardcoded Anthropic with generic AI provider in setup UI (#13101) --- packages/react-ui/public/locales/de/translation.json | 4 ++-- packages/react-ui/public/locales/en/translation.json | 4 ++-- packages/react-ui/public/locales/es/translation.json | 4 ++-- packages/react-ui/public/locales/fr/translation.json | 4 ++-- packages/react-ui/public/locales/ja/translation.json | 4 ++-- packages/react-ui/public/locales/nl/translation.json | 4 ++-- packages/react-ui/public/locales/pt/translation.json | 4 ++-- packages/react-ui/public/locales/zh-TW/translation.json | 4 ++-- packages/react-ui/public/locales/zh/translation.json | 4 ++-- packages/web/public/locales/en/translation.json | 3 ++- .../app/routes/chat-with-ai/components/chat-empty-state.tsx | 4 ++-- 11 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/react-ui/public/locales/de/translation.json b/packages/react-ui/public/locales/de/translation.json index 13aa901b147..508019580b7 100644 --- a/packages/react-ui/public/locales/de/translation.json +++ b/packages/react-ui/public/locales/de/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/en/translation.json b/packages/react-ui/public/locales/en/translation.json index bbb64eaf016..ac327e46fdb 100644 --- a/packages/react-ui/public/locales/en/translation.json +++ b/packages/react-ui/public/locales/en/translation.json @@ -486,8 +486,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/es/translation.json b/packages/react-ui/public/locales/es/translation.json index dafaf894c3e..8332f429b98 100644 --- a/packages/react-ui/public/locales/es/translation.json +++ b/packages/react-ui/public/locales/es/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/fr/translation.json b/packages/react-ui/public/locales/fr/translation.json index dafaf894c3e..8332f429b98 100644 --- a/packages/react-ui/public/locales/fr/translation.json +++ b/packages/react-ui/public/locales/fr/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/ja/translation.json b/packages/react-ui/public/locales/ja/translation.json index c66eb982b4a..944802abcc4 100644 --- a/packages/react-ui/public/locales/ja/translation.json +++ b/packages/react-ui/public/locales/ja/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/nl/translation.json b/packages/react-ui/public/locales/nl/translation.json index 13aa901b147..508019580b7 100644 --- a/packages/react-ui/public/locales/nl/translation.json +++ b/packages/react-ui/public/locales/nl/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/pt/translation.json b/packages/react-ui/public/locales/pt/translation.json index dafaf894c3e..8332f429b98 100644 --- a/packages/react-ui/public/locales/pt/translation.json +++ b/packages/react-ui/public/locales/pt/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/zh-TW/translation.json b/packages/react-ui/public/locales/zh-TW/translation.json index c66eb982b4a..944802abcc4 100644 --- a/packages/react-ui/public/locales/zh-TW/translation.json +++ b/packages/react-ui/public/locales/zh-TW/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/react-ui/public/locales/zh/translation.json b/packages/react-ui/public/locales/zh/translation.json index c66eb982b4a..944802abcc4 100644 --- a/packages/react-ui/public/locales/zh/translation.json +++ b/packages/react-ui/public/locales/zh/translation.json @@ -488,8 +488,8 @@ "Handle approvals": "", "Check my data": "", "Brainstorm ideas": "", - "Set up Anthropic to get started": "", - "AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.": "", + "Set up an AI provider to get started": "", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "", "Go to AI Settings": "", "Sandbox not configured": "", "AI Chat requires an E2B sandbox to run. Ask your admin to set the AP_E2B_API_KEY environment variable.": "", diff --git a/packages/web/public/locales/en/translation.json b/packages/web/public/locales/en/translation.json index bdcbf14e682..e0afc5fbdd9 100644 --- a/packages/web/public/locales/en/translation.json +++ b/packages/web/public/locales/en/translation.json @@ -1584,7 +1584,8 @@ "Regenerate": "Regenerate", "Response stopped": "Response stopped", "Sandbox not configured": "Sandbox not configured", - "Set up Anthropic to get started": "Set up Anthropic to get started", + "Set up an AI provider to get started": "Set up an AI provider to get started", + "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.": "AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.", "Show me what I have running": "Show me what I have running", "Stop": "Stop", "This automation needs a {name} connection to work": "This automation needs a {name} connection to work", diff --git a/packages/web/src/app/routes/chat-with-ai/components/chat-empty-state.tsx b/packages/web/src/app/routes/chat-with-ai/components/chat-empty-state.tsx index d3f6ffa7b92..dc4fbfd3c59 100644 --- a/packages/web/src/app/routes/chat-with-ai/components/chat-empty-state.tsx +++ b/packages/web/src/app/routes/chat-with-ai/components/chat-empty-state.tsx @@ -78,11 +78,11 @@ export function SetupRequiredState() {

- {t('Set up Anthropic to get started')} + {t('Set up an AI provider to get started')}

{t( - 'AI Chat requires an Anthropic API key. Add your Anthropic provider in the AI settings to start chatting.', + 'AI Chat requires an AI provider. Add your provider in the AI settings to start chatting.', )}