Merge branch 'main' into AUT-1372

This commit is contained in:
Jakub P.
2025-01-25 20:44:30 +01:00
240 changed files with 7212 additions and 1234 deletions

View File

@@ -0,0 +1,3 @@
import sendMessage from './send-message/index.js';
export default [sendMessage];

View File

@@ -0,0 +1,124 @@
import defineAction from '../../../../helpers/define-action.js';
const castFloatOrUndefined = (value) => {
return value === '' ? undefined : parseFloat(value);
};
export default defineAction({
name: 'Send message',
key: 'send Message',
description:
'Sends a structured list of input messages with text content, and the model will generate the next message in the conversation.',
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown',
required: true,
variables: true,
description: 'The model that will complete your prompt.',
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listModels',
},
],
},
},
{
label: 'Messages',
key: 'messages',
type: 'dynamic',
required: true,
description: 'Add or remove messages as needed',
value: [{ role: 'assistant', body: '' }],
fields: [
{
label: 'Role',
key: 'role',
type: 'dropdown',
required: true,
options: [
{
label: 'Assistant',
value: 'assistant',
},
{
label: 'User',
value: 'user',
},
],
},
{
label: 'Content',
key: 'content',
type: 'string',
required: true,
variables: true,
},
],
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string',
required: true,
variables: true,
description: 'The maximum number of tokens to generate before stopping.',
},
{
label: 'Temperature',
key: 'temperature',
type: 'string',
required: false,
variables: true,
value: '1.0',
description:
'Amount of randomness injected into the response. Defaults to 1.0. Ranges from 0.0 to 1.0. Use temperature closer to 0.0 for analytical / multiple choice, and closer to 1.0 for creative and generative tasks.',
},
{
label: 'Stop sequences',
key: 'stopSequences',
type: 'dynamic',
required: false,
variables: true,
description:
'Custom text sequences that will cause the model to stop generating.',
fields: [
{
label: 'Stop sequence',
key: 'stopSequence',
type: 'string',
required: false,
variables: true,
},
],
},
],
async run($) {
const nonEmptyStopSequences = $.step.parameters.stopSequences
.filter(({ stopSequence }) => stopSequence)
.map(({ stopSequence }) => stopSequence);
const payload = {
model: $.step.parameters.model,
temperature: castFloatOrUndefined($.step.parameters.temperature),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens),
stop_sequences: nonEmptyStopSequences,
messages: $.step.parameters.messages.map((message) => ({
role: message.role,
content: message.content,
})),
};
const { data } = await $.http.post('/v1/messages', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="176px" viewBox="0 0 256 176" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<title>Anthropic</title>
<g fill="#181818">
<path d="M147.486878,0 C147.486878,0 217.568251,175.780074 217.568251,175.780074 C217.568251,175.780074 256,175.780074 256,175.780074 C256,175.780074 185.918621,0 185.918621,0 C185.918621,0 147.486878,0 147.486878,0 C147.486878,0 147.486878,0 147.486878,0 Z"></path>
<path d="M66.1828124,106.221191 C66.1828124,106.221191 90.1624677,44.4471185 90.1624677,44.4471185 C90.1624677,44.4471185 114.142128,106.221191 114.142128,106.221191 C114.142128,106.221191 66.1828124,106.221191 66.1828124,106.221191 C66.1828124,106.221191 66.1828124,106.221191 66.1828124,106.221191 Z M70.0705318,0 C70.0705318,0 0,175.780074 0,175.780074 C0,175.780074 39.179211,175.780074 39.179211,175.780074 C39.179211,175.780074 53.5097704,138.86606 53.5097704,138.86606 C53.5097704,138.86606 126.817544,138.86606 126.817544,138.86606 C126.817544,138.86606 141.145724,175.780074 141.145724,175.780074 C141.145724,175.780074 180.324935,175.780074 180.324935,175.780074 C180.324935,175.780074 110.254409,0 110.254409,0 C110.254409,0 70.0705318,0 70.0705318,0 C70.0705318,0 70.0705318,0 70.0705318,0 Z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,34 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Anthropic AI API key of your account.',
docUrl: 'https://automatisch.io/docs/anthropic#api-key',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,7 @@
const isStillVerified = async ($) => {
await $.http.get('/v1/models');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async ($) => {
await $.http.get('/v1/models');
};
export default verifyCredentials;

View File

@@ -0,0 +1,7 @@
const addAuthHeader = ($, requestConfig) => {
requestConfig.headers['anthropic-version'] = '2023-06-01';
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers['x-api-key'] = $.auth.data.apiKey;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,3 @@
import listModels from './list-models/index.js';
export default [listModels];

View File

@@ -0,0 +1,31 @@
export default {
name: 'List models',
key: 'listModels',
async run($) {
const models = {
data: [],
};
const params = {
limit: 999,
};
let hasMore = false;
do {
const { data } = await $.http.get('/v1/models', { params });
params.after_id = data.last_id;
hasMore = data.has_more;
for (const base of data.data) {
models.data.push({
value: base.id,
name: base.display_name,
});
}
} while (hasMore);
return models;
},
};

View File

@@ -0,0 +1,21 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import addAnthropicVersionHeader from './common/add-anthropic-version-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'Anthropic',
key: 'anthropic',
baseUrl: 'https://anthropic.com',
apiBaseUrl: 'https://api.anthropic.com',
iconUrl: '{BASE_URL}/apps/anthropic/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/anthropic/connection',
primaryColor: '#181818',
supportsConnections: true,
beforeRequest: [addAuthHeader, addAnthropicVersionHeader],
auth,
actions,
dynamicData,
});

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
fill="#0078D7" stroke="none">
<path d="M1120 2544 c-220 -44 -286 -64 -425 -136 -331 -170 -574 -485 -656
-850 -27 -120 -38 -419 -18 -512 28 -134 72 -219 156 -302 72 -70 153 -108
265 -124 160 -22 303 23 414 129 39 36 49 42 63 33 41 -30 160 -85 210 -99 78
-21 220 -20 306 1 214 54 387 228 441 441 35 143 16 318 -50 441 -112 208
-311 327 -546 327 -235 0 -434 -119 -546 -327 -49 -92 -65 -171 -71 -346 -3
-85 -10 -163 -15 -173 -5 -10 -27 -31 -50 -47 -73 -53 -163 -32 -205 49 -60
118 -29 435 61 621 198 406 644 609 1075 489 418 -117 708 -541 660 -967 -64
-573 -607 -944 -1159 -791 -41 12 -93 30 -114 40 -46 22 -46 22 -46 10 0 -4
-27 -49 -60 -99 -33 -51 -60 -94 -60 -98 0 -3 -15 -26 -32 -51 -31 -43 -32
-45 -14 -59 36 -26 201 -85 301 -107 143 -31 406 -31 547 0 486 110 861 485
971 971 31 140 31 404 0 544 -109 484 -485 862 -965 969 -91 20 -375 35 -438
23z m252 -1025 c29 -11 69 -36 90 -56 41 -40 78 -126 78 -183 0 -57 -37 -143
-78 -183 -43 -42 -127 -77 -182 -77 -55 0 -139 35 -182 77 -41 40 -78 126 -78
181 0 62 31 133 80 182 77 77 173 98 272 59z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,44 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'instanceUrl',
label: 'FreeScout instance URL',
type: 'string',
required: true,
readOnly: false,
description: 'Your FreeScout instance URL.',
docUrl: 'https://automatisch.io/docs/freescout#instance-url',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'FreeScout API key of your account.',
docUrl: 'https://automatisch.io/docs/freescout#api-key',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,6 @@
const isStillVerified = async ($) => {
await $.http.get('/api/mailboxes');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async ($) => {
await $.http.get('/api/mailboxes');
};
export default verifyCredentials;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers['X-FreeScout-API-Key'] = $.auth.data.apiKey;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,6 @@
const setBaseUrl = ($, requestConfig) => {
requestConfig.baseURL = $.auth.data.instanceUrl;
return requestConfig;
};
export default setBaseUrl;

View File

@@ -0,0 +1,52 @@
const webhookFilters = [
{
value: 'convo.assigned',
label: 'Conversation assigned',
},
{
value: 'convo.created',
label: 'Conversation created',
},
{
value: 'convo.deleted',
label: 'Conversation deleted',
},
{
value: 'convo.deleted_forever',
label: 'Conversation deleted forever',
},
{
value: 'convo.restored',
label: 'Conversation restored from Deleted folder',
},
{
value: 'convo.moved',
label: 'Conversation moved',
},
{
value: 'convo.status',
label: 'Conversation status updated',
},
{
value: 'convo.customer.reply.created',
label: 'Customer replied',
},
{
value: 'convo.agent.reply.created',
label: 'Agent replied',
},
{
value: 'convo.note.created',
label: 'Note added',
},
{
value: 'customer.created',
label: 'Customer create',
},
{
value: 'customer.updated',
label: 'Customer update',
},
];
export default webhookFilters;

View File

@@ -0,0 +1,18 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import triggers from './triggers/index.js';
import setBaseUrl from './common/set-base-url.js';
export default defineApp({
name: 'FreeScout',
key: 'freescout',
iconUrl: '{BASE_URL}/apps/freescout/assets/favicon.svg',
supportsConnections: true,
baseUrl: 'https://freescout.net',
primaryColor: '#F5D05E',
authDocUrl: '{DOCS_URL}/apps/freescout/connection',
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,
});

View File

@@ -0,0 +1,3 @@
import newEvent from './new-event/index.js';
export default [newEvent];

View File

@@ -0,0 +1,61 @@
import Crypto from 'crypto';
import isEmpty from 'lodash/isEmpty.js';
import defineTrigger from '../../../../helpers/define-trigger.js';
import webhookFilters from '../../common/webhook-filters.js';
export default defineTrigger({
name: 'New event',
key: 'newEvent',
type: 'webhook',
description: 'Triggers when a new event occurs.',
arguments: [
{
label: 'Event type',
key: 'eventType',
type: 'dropdown',
required: true,
description: 'Pick an event type to receive events for.',
variables: false,
options: webhookFilters,
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const lastExecutionStep = await $.getLastExecutionStep();
if (!isEmpty(lastExecutionStep?.dataOut)) {
$.pushTriggerItem({
raw: lastExecutionStep.dataOut,
meta: {
internalId: '',
},
});
}
},
async registerHook($) {
const payload = {
url: $.webhookUrl,
events: [$.step.parameters.eventType],
};
const response = await $.http.post('/api/webhooks', payload);
await $.flow.setRemoteWebhookId(response.data?.id?.toString());
},
async unregisterHook($) {
await $.http.delete(`/api/webhooks/${$.flow.remoteWebhookId}`);
},
});

View File

@@ -0,0 +1,157 @@
import defineAction from '../../../../helpers/define-action.js';
const castFloatOrUndefined = (value) => {
return value === '' ? undefined : parseFloat(value);
};
export default defineAction({
name: 'Create chat completion',
key: 'createChatCompletion',
description: 'Creates a chat completion.',
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listModels',
},
],
},
},
{
label: 'Messages',
key: 'messages',
type: 'dynamic',
required: true,
description:
'The prompt(s) to generate completions for, encoded as a list of dict with role and content.',
value: [{ role: 'system', body: '' }],
fields: [
{
label: 'Role',
key: 'role',
type: 'dropdown',
required: true,
options: [
{
label: 'System',
value: 'system',
},
{
label: 'Assistant',
value: 'assistant',
},
{
label: 'User',
value: 'user',
},
],
},
{
label: 'Content',
key: 'content',
type: 'string',
required: true,
variables: true,
},
],
},
{
label: 'Temperature',
key: 'temperature',
type: 'string',
required: false,
variables: true,
description:
'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.',
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string',
required: false,
variables: true,
description: `The maximum number of tokens to generate in the completion. The token count of your prompt plus max_tokens cannot exceed the model's context length.`,
},
{
label: 'Stop sequences',
key: 'stopSequences',
type: 'dynamic',
required: false,
variables: true,
description: 'Stop generation if one of these tokens is detected',
fields: [
{
label: 'Stop sequence',
key: 'stopSequence',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'Top P',
key: 'topP',
type: 'string',
required: false,
variables: true,
description:
'Nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both.',
},
{
label: 'Frequency Penalty',
key: 'frequencyPenalty',
type: 'string',
required: false,
variables: true,
description: `Frequency_penalty penalizes the repetition of words based on their frequency in the generated text. A higher frequency penalty discourages the model from repeating words that have already appeared frequently in the output, promoting diversity and reducing repetition.`,
},
{
label: 'Presence Penalty',
key: 'presencePenalty',
type: 'string',
required: false,
variables: true,
description: `Presence penalty determines how much the model penalizes the repetition of words or phrases. A higher presence penalty encourages the model to use a wider variety of words and phrases, making the output more diverse and creative.`,
},
],
async run($) {
const nonEmptyStopSequences = $.step.parameters.stopSequences
.filter(({ stopSequence }) => stopSequence)
.map(({ stopSequence }) => stopSequence);
const messages = $.step.parameters.messages.map((message) => ({
role: message.role,
content: message.content,
}));
const payload = {
model: $.step.parameters.model,
messages,
stop: nonEmptyStopSequences,
temperature: castFloatOrUndefined($.step.parameters.temperature),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens),
top_p: castFloatOrUndefined($.step.parameters.topP),
frequency_penalty: castFloatOrUndefined(
$.step.parameters.frequencyPenalty
),
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty),
};
const { data } = await $.http.post('/v1/chat/completions', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,3 @@
import createChatCompletion from './create-chat-completion/index.js';
export default [createChatCompletion];

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="233px" viewBox="0 0 256 233" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<title>Mistral AI</title>
<g>
<rect fill="#000000" x="186.181818" y="0" width="46.5454545" height="46.5454545"></rect>
<rect fill="#F7D046" x="209.454545" y="0" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="0" y="0" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="0" y="46.5454545" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="0" y="93.0909091" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="0" y="139.636364" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="0" y="186.181818" width="46.5454545" height="46.5454545"></rect>
<rect fill="#F7D046" x="23.2727273" y="0" width="46.5454545" height="46.5454545"></rect>
<rect fill="#F2A73B" x="209.454545" y="46.5454545" width="46.5454545" height="46.5454545"></rect>
<rect fill="#F2A73B" x="23.2727273" y="46.5454545" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="139.636364" y="46.5454545" width="46.5454545" height="46.5454545"></rect>
<rect fill="#F2A73B" x="162.909091" y="46.5454545" width="46.5454545" height="46.5454545"></rect>
<rect fill="#F2A73B" x="69.8181818" y="46.5454545" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EE792F" x="116.363636" y="93.0909091" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EE792F" x="162.909091" y="93.0909091" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EE792F" x="69.8181818" y="93.0909091" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="93.0909091" y="139.636364" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EB5829" x="116.363636" y="139.636364" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EE792F" x="209.454545" y="93.0909091" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EE792F" x="23.2727273" y="93.0909091" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="186.181818" y="139.636364" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EB5829" x="209.454545" y="139.636364" width="46.5454545" height="46.5454545"></rect>
<rect fill="#000000" x="186.181818" y="186.181818" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EB5829" x="23.2727273" y="139.636364" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EA3326" x="209.454545" y="186.181818" width="46.5454545" height="46.5454545"></rect>
<rect fill="#EA3326" x="23.2727273" y="186.181818" width="46.5454545" height="46.5454545"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,34 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Mistral AI API key of your account.',
docUrl: 'https://automatisch.io/docs/mistral-ai#api-key',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,6 @@
const isStillVerified = async ($) => {
await $.http.get('/v1/models');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async ($) => {
await $.http.get('/v1/models');
};
export default verifyCredentials;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,3 @@
import listModels from './list-models/index.js';
export default [listModels];

View File

@@ -0,0 +1,17 @@
export default {
name: 'List models',
key: 'listModels',
async run($) {
const response = await $.http.get('/v1/models');
const models = response.data.data.map((model) => {
return {
value: model.id,
name: model.id,
};
});
return { data: models };
},
};

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'Mistral AI',
key: 'mistral-ai',
baseUrl: 'https://mistral.ai',
apiBaseUrl: 'https://api.mistral.ai',
iconUrl: '{BASE_URL}/apps/mistral-ai/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/mistral-ai/connection',
primaryColor: '#000000',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
});

View File

@@ -0,0 +1,157 @@
import defineAction from '../../../../helpers/define-action.js';
const castFloatOrUndefined = (value) => {
return value === '' ? undefined : parseFloat(value);
};
export default defineAction({
name: 'Create chat completion',
key: 'createChatCompletion',
description: 'Creates a chat completion.',
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listModels',
},
],
},
},
{
label: 'Messages',
key: 'messages',
type: 'dynamic',
required: true,
description:
'The prompt(s) to generate completions for, encoded as a list of dict with role and content.',
value: [{ role: 'system', body: '' }],
fields: [
{
label: 'Role',
key: 'role',
type: 'dropdown',
required: true,
options: [
{
label: 'System',
value: 'system',
},
{
label: 'Assistant',
value: 'assistant',
},
{
label: 'User',
value: 'user',
},
],
},
{
label: 'Content',
key: 'content',
type: 'string',
required: true,
variables: true,
},
],
},
{
label: 'Temperature',
key: 'temperature',
type: 'string',
required: false,
variables: true,
description:
'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.',
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string',
required: false,
variables: true,
description: `The maximum number of tokens to generate in the completion. The token count of your prompt plus max_tokens cannot exceed the model's context length.`,
},
{
label: 'Stop sequences',
key: 'stopSequences',
type: 'dynamic',
required: false,
variables: true,
description: 'Stop generation if one of these tokens is detected',
fields: [
{
label: 'Stop sequence',
key: 'stopSequence',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'Top P',
key: 'topP',
type: 'string',
required: false,
variables: true,
description:
'Nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both.',
},
{
label: 'Frequency Penalty',
key: 'frequencyPenalty',
type: 'string',
required: false,
variables: true,
description: `Frequency_penalty penalizes the repetition of words based on their frequency in the generated text. A higher frequency penalty discourages the model from repeating words that have already appeared frequently in the output, promoting diversity and reducing repetition.`,
},
{
label: 'Presence Penalty',
key: 'presencePenalty',
type: 'string',
required: false,
variables: true,
description: `Presence penalty determines how much the model penalizes the repetition of words or phrases. A higher presence penalty encourages the model to use a wider variety of words and phrases, making the output more diverse and creative.`,
},
],
async run($) {
const nonEmptyStopSequences = $.step.parameters.stopSequences
.filter(({ stopSequence }) => stopSequence)
.map(({ stopSequence }) => stopSequence);
const messages = $.step.parameters.messages.map((message) => ({
role: message.role,
content: message.content,
}));
const payload = {
model: $.step.parameters.model,
messages,
stop: nonEmptyStopSequences,
temperature: castFloatOrUndefined($.step.parameters.temperature),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens),
top_p: castFloatOrUndefined($.step.parameters.topP),
frequency_penalty: castFloatOrUndefined(
$.step.parameters.frequencyPenalty
),
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty),
};
const { data } = await $.http.post('/v1/chat/completions', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,3 @@
import createChatCompletion from './create-chat-completion/index.js';
export default [createChatCompletion];

View File

@@ -0,0 +1 @@
<svg width="100%" height="100%" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="w-4 rounded-full" fill="#71717a" stroke="#71717a" aria-label="Logo"><g clip-path="url(#clip0_205_3)"><path d="M3 248.945C18 248.945 76 236 106 219C136 202 136 202 198 158C276.497 102.293 332 120.945 423 120.945" stroke-width="90"></path><path d="M511 121.5L357.25 210.268L357.25 32.7324L511 121.5Z"></path><path d="M0 249C15 249 73 261.945 103 278.945C133 295.945 133 295.945 195 339.945C273.497 395.652 329 377 420 377" stroke-width="90"></path><path d="M508 376.445L354.25 287.678L354.25 465.213L508 376.445Z"></path></g><title style="display:none">OpenRouter</title><defs><clipPath id="clip0_205_3"><rect width="512" height="512" fill="white"></rect></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@@ -0,0 +1,34 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'OpenRouter API key of your account.',
docUrl: 'https://automatisch.io/docs/openrouter#api-key',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,6 @@
const isStillVerified = async ($) => {
await $.http.get('/v1/models');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async ($) => {
await $.http.get('/v1/models');
};
export default verifyCredentials;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,3 @@
import listModels from './list-models/index.js';
export default [listModels];

View File

@@ -0,0 +1,17 @@
export default {
name: 'List models',
key: 'listModels',
async run($) {
const response = await $.http.get('/v1/models');
const models = response.data.data.map((model) => {
return {
value: model.id,
name: model.id,
};
});
return { data: models };
},
};

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'OpenRouter',
key: 'openrouter',
baseUrl: 'https://openrouter.ai',
apiBaseUrl: 'https://openrouter.ai/api',
iconUrl: '{BASE_URL}/apps/openrouter/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/openrouter/connection',
primaryColor: '#71717a',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
});

View File

@@ -0,0 +1,3 @@
import sendChatPrompt from './send-chat-prompt/index.js';
export default [sendChatPrompt];

View File

@@ -0,0 +1,185 @@
import defineAction from '../../../../helpers/define-action.js';
const castFloatOrUndefined = (value) => {
return value === '' ? undefined : parseFloat(value);
};
export default defineAction({
name: 'Send chat prompt',
key: 'sendChatPrompt',
description: `Generates a model's response for the given chat conversation.`,
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown',
required: true,
variables: true,
options: [
{
label: 'Sonar Pro',
value: 'sonar-pro',
},
{
label: 'Sonar',
value: 'sonar',
},
],
},
{
label: 'Messages',
key: 'messages',
type: 'dynamic',
required: true,
description: 'Add or remove messages as needed',
value: [{ role: 'system', body: '' }],
fields: [
{
label: 'Role',
key: 'role',
type: 'dropdown',
required: true,
description:
'The role of the speaker in this turn of conversation. After the (optional) system message, user and assistant roles should alternate with user then assistant, ending in user.',
options: [
{
label: 'System',
value: 'system',
},
{
label: 'Assistant',
value: 'assistant',
},
{
label: 'User',
value: 'user',
},
],
},
{
label: 'Content',
key: 'content',
type: 'string',
required: true,
variables: true,
description:
'The contents of the message in this turn of conversation.',
},
],
},
{
label: 'Temperature',
key: 'temperature',
type: 'string',
required: false,
variables: true,
description:
'The amount of randomness in the response, valued between 0 inclusive and 2 exclusive. Higher values are more random, and lower values are more deterministic.',
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string',
required: false,
variables: true,
description:
'The maximum number of tokens to generate in the completion.',
},
{
label: 'Top P',
key: 'topP',
type: 'string',
required: false,
variables: true,
description:
'The nucleus sampling threshold, valued between 0 and 1 inclusive. For each subsequent token, the model considers the results of the tokens with top_p probability mass. We recommend either altering top_k or top_p, but not both.',
},
{
label: 'Top K',
key: 'topK',
type: 'string',
required: false,
variables: true,
description:
'The number of tokens to keep for highest top-k filtering, specified as an integer between 0 and 2048 inclusive. If set to 0, top-k filtering is disabled. We recommend either altering top_k or top_p, but not both.',
},
{
label: 'Frequency Penalty',
key: 'frequencyPenalty',
type: 'string',
required: false,
variables: true,
description: `A multiplicative penalty greater than 0. Values greater than 1.0 penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. A value of 1.0 means no penalty. Incompatible with presence_penalty.`,
},
{
label: 'Presence Penalty',
key: 'presencePenalty',
type: 'string',
required: false,
variables: true,
description: `A value between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. Incompatible with frequency_penalty.`,
},
{
label: 'Return images',
key: 'returnImages',
type: 'dropdown',
required: false,
variables: true,
value: false,
options: [
{
label: 'Yes',
value: true,
},
{
label: 'No',
value: false,
},
],
},
{
label: 'Return related questions',
key: 'returnRelatedQuestions',
type: 'dropdown',
required: false,
variables: true,
value: false,
options: [
{
label: 'Yes',
value: true,
},
{
label: 'No',
value: false,
},
],
},
],
async run($) {
const payload = {
model: $.step.parameters.model,
temperature: castFloatOrUndefined($.step.parameters.temperature),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens),
top_p: castFloatOrUndefined($.step.parameters.topP),
top_k: castFloatOrUndefined($.step.parameters.topK),
frequency_penalty: castFloatOrUndefined(
$.step.parameters.frequencyPenalty
),
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty),
messages: $.step.parameters.messages.map((message) => ({
role: message.role,
content: message.content,
})),
return_images: $.step.parameters.returnImages,
return_related_questions: $.step.parameters.returnRelatedQuestons,
};
const { data } = await $.http.post('/chat/completions', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0.13 -0.15 77.31 92.56"><path d="M38.6936 29.9832L12.8633 5.88983V29.9832H38.6936Z" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path><path d="M39.5005 29.9832L65.3308 5.88983V29.9832H39.5005Z" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path><path d="M38.7227 2L38.7227 90.2534" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path><path d="M64.5246 53.7584L38.6943 30.0068V62.9404L64.5246 85.9724V53.7584Z" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path><path d="M12.8924 53.7584L38.7227 30.0068V62.9404L12.8924 85.9724V53.7584Z" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path><path d="M2.28711 29.9832V64.4236H12.8863V53.7348L38.7226 29.9832H2.28711Z" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path><path d="M38.6943 30.3L64.5246 54.0515V64.7403H75.2872V30.3L38.6943 30.3Z" stroke-width="4.30504" stroke-miterlimit="10" stroke="#20b8cd"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,34 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Perplexity API key of your account.',
docUrl: 'https://automatisch.io/docs/perplexity#api-key',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,5 @@
const isStillVerified = async () => {
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async () => {
return true;
};
export default verifyCredentials;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,18 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
export default defineApp({
name: 'Perplexity',
key: 'perplexity',
baseUrl: 'https://perplexity.ai',
apiBaseUrl: 'https://api.perplexity.ai',
iconUrl: '{BASE_URL}/apps/perplexity/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/perplexity/connection',
primaryColor: '#091717',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
});

View File

@@ -1,4 +1,6 @@
const cronTimes = {
everyNMinutes: (n) => `*/${n} * * * *`,
everyNMinutesExcludingWeekends: (n) => `*/${n} * * * 1-5`,
everyHour: '0 * * * *',
everyHourExcludingWeekends: '0 * * * 1-5',
everyDayAt: (hour) => `0 ${hour} * * *`,

View File

@@ -0,0 +1,131 @@
import { DateTime } from 'luxon';
import defineTrigger from '../../../../helpers/define-trigger.js';
import cronTimes from '../../common/cron-times.js';
import getNextCronDateTime from '../../common/get-next-cron-date-time.js';
import getDateTimeObjectRepresentation from '../../common/get-date-time-object.js';
export default defineTrigger({
name: 'Every N minutes',
key: 'everyNMinutes',
description: 'Triggers every N minutes.',
arguments: [
{
label: 'Trigger on weekends?',
key: 'triggersOnWeekend',
type: 'dropdown',
description: 'Should this flow trigger on Saturday and Sunday?',
required: true,
value: true,
variables: false,
options: [
{
label: 'Yes',
value: true,
},
{
label: 'No',
value: false,
},
],
},
{
label: 'Interval',
key: 'interval',
type: 'dropdown',
required: true,
value: null,
variables: false,
options: [
{ label: 'Every 1 minute', value: 1 },
{ label: 'Every 2 minutes', value: 2 },
{ label: 'Every 3 minutes', value: 3 },
{ label: 'Every 4 minutes', value: 4 },
{ label: 'Every 5 minutes', value: 5 },
{ label: 'Every 6 minutes', value: 6 },
{ label: 'Every 7 minutes', value: 7 },
{ label: 'Every 8 minutes', value: 8 },
{ label: 'Every 9 minutes', value: 9 },
{ label: 'Every 10 minutes', value: 10 },
{ label: 'Every 11 minutes', value: 11 },
{ label: 'Every 12 minutes', value: 12 },
{ label: 'Every 13 minutes', value: 13 },
{ label: 'Every 14 minutes', value: 14 },
{ label: 'Every 15 minutes', value: 15 },
{ label: 'Every 16 minutes', value: 16 },
{ label: 'Every 17 minutes', value: 17 },
{ label: 'Every 18 minutes', value: 18 },
{ label: 'Every 19 minutes', value: 19 },
{ label: 'Every 20 minutes', value: 20 },
{ label: 'Every 21 minutes', value: 21 },
{ label: 'Every 22 minutes', value: 22 },
{ label: 'Every 23 minutes', value: 23 },
{ label: 'Every 24 minutes', value: 24 },
{ label: 'Every 25 minutes', value: 25 },
{ label: 'Every 26 minutes', value: 26 },
{ label: 'Every 27 minutes', value: 27 },
{ label: 'Every 28 minutes', value: 28 },
{ label: 'Every 29 minutes', value: 29 },
{ label: 'Every 30 minutes', value: 30 },
{ label: 'Every 31 minutes', value: 31 },
{ label: 'Every 32 minutes', value: 32 },
{ label: 'Every 33 minutes', value: 33 },
{ label: 'Every 34 minutes', value: 34 },
{ label: 'Every 35 minutes', value: 35 },
{ label: 'Every 36 minutes', value: 36 },
{ label: 'Every 37 minutes', value: 37 },
{ label: 'Every 38 minutes', value: 38 },
{ label: 'Every 39 minutes', value: 39 },
{ label: 'Every 40 minutes', value: 40 },
{ label: 'Every 41 minutes', value: 41 },
{ label: 'Every 42 minutes', value: 42 },
{ label: 'Every 43 minutes', value: 43 },
{ label: 'Every 44 minutes', value: 44 },
{ label: 'Every 45 minutes', value: 45 },
{ label: 'Every 46 minutes', value: 46 },
{ label: 'Every 47 minutes', value: 47 },
{ label: 'Every 48 minutes', value: 48 },
{ label: 'Every 49 minutes', value: 49 },
{ label: 'Every 50 minutes', value: 50 },
{ label: 'Every 51 minutes', value: 51 },
{ label: 'Every 52 minutes', value: 52 },
{ label: 'Every 53 minutes', value: 53 },
{ label: 'Every 54 minutes', value: 54 },
{ label: 'Every 55 minutes', value: 55 },
{ label: 'Every 56 minutes', value: 56 },
{ label: 'Every 57 minutes', value: 57 },
{ label: 'Every 58 minutes', value: 58 },
{ label: 'Every 59 minutes', value: 59 },
],
},
],
getInterval(parameters) {
if (parameters.triggersOnWeekend) {
return cronTimes.everyNMinutes(parameters.interval);
}
return cronTimes.everyNMinutesExcludingWeekends(parameters.interval);
},
async run($) {
const nextCronDateTime = getNextCronDateTime(
this.getInterval($.step.parameters)
);
const dateTime = DateTime.now();
const dateTimeObjectRepresentation = getDateTimeObjectRepresentation(
$.execution.testRun ? nextCronDateTime : dateTime
);
const dataItem = {
raw: dateTimeObjectRepresentation,
meta: {
internalId: dateTime.toMillis().toString(),
},
};
$.pushTriggerItem(dataItem);
},
});

View File

@@ -1,6 +1,7 @@
import everyNMinutes from './every-n-minutes/index.js';
import everyHour from './every-hour/index.js';
import everyDay from './every-day/index.js';
import everyWeek from './every-week/index.js';
import everyMonth from './every-month/index.js';
export default [everyHour, everyDay, everyWeek, everyMonth];
export default [everyNMinutes, everyHour, everyDay, everyWeek, everyMonth];

View File

@@ -0,0 +1,169 @@
import { XMLBuilder } from 'fast-xml-parser';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Add voice XML node',
key: 'addVoiceXmlNode',
description: 'Add a voice XML node in the XML document',
supportsConnections: false,
arguments: [
{
label: 'Node name',
key: 'nodeName',
type: 'dropdown',
required: true,
description: 'The name of the node to be added.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceXmlNodes',
},
],
},
},
{
label: 'Node value',
key: 'nodeValue',
type: 'string',
required: false,
description: 'The value of the node to be added.',
variables: true,
},
{
label: 'Attributes',
key: 'attributes',
type: 'dynamic',
required: false,
description: 'Add or remove attributes for the node as needed',
value: [
{
key: '',
value: '',
},
],
fields: [
{
label: 'Attribute name',
key: 'key',
type: 'dropdown',
required: false,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceXmlNodeAttributes',
},
{
name: 'parameters.nodeName',
value: '{parameters.nodeName}',
},
],
},
},
{
label: 'Attribute value',
key: 'value',
type: 'dropdown',
required: false,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceXmlNodeAttributeValues',
},
{
name: 'parameters.nodeName',
value: '{parameters.nodeName}',
},
{
name: 'parameters.attributeKey',
value: '{fieldsScope.key}',
},
],
},
},
],
},
{
label: 'Add children node',
key: 'hasChildrenNodes',
type: 'dropdown',
required: true,
description: 'Add a nested node to the main node',
value: false,
options: [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listNodeFields',
},
{
name: 'parameters.hasChildrenNodes',
value: '{parameters.hasChildrenNodes}',
},
],
},
},
],
async run($) {
const nodeName = $.step.parameters.nodeName;
const nodeValue = $.step.parameters.nodeValue;
const attributes = $.step.parameters.attributes;
const childrenNodes = $.step.parameters.childrenNodes;
const hasChildrenNodes = $.step.parameters.hasChildrenNodes;
const builder = new XMLBuilder({
ignoreAttributes: false,
suppressEmptyNode: true,
preserveOrder: true,
});
const computeAttributes = (attributes) =>
attributes
.filter((attribute) => attribute.key || attribute.value)
.reduce(
(result, attribute) => ({
...result,
[`@_${attribute.key}`]: attribute.value,
}),
{}
);
const computeTextNode = (nodeValue) => ({
'#text': nodeValue,
});
const computedChildrenNodes = hasChildrenNodes
? childrenNodes.map((childNode) => ({
[childNode.nodeName]: [computeTextNode(childNode.nodeValue)],
':@': computeAttributes(childNode.attributes),
}))
: [];
const xmlObject = {
[nodeName]: [computeTextNode(nodeValue), ...computedChildrenNodes],
':@': computeAttributes(attributes),
};
const xmlString = builder.build([xmlObject]);
$.setActionItem({ raw: { stringNode: xmlString } });
},
});

View File

@@ -1,3 +1,5 @@
import sendSms from './send-sms/index.js';
import addVoiceXmlNode from './add-voice-xml-node/index.js';
import respondWithVoiceXml from './respond-with-voice-xml/index.js';
export default [sendSms];
export default [addVoiceXmlNode, respondWithVoiceXml, sendSms];

View File

@@ -0,0 +1,66 @@
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Respond with voice XML',
key: 'respondWithVoiceXml',
description: 'Respond with defined voice XML document',
supportsConnections: false,
arguments: [
{
label: 'Nodes',
key: 'nodes',
type: 'dynamic',
required: false,
description: 'Add or remove nodes for the XML document as needed',
value: [
{
nodeString: '',
},
],
fields: [
{
label: 'Node',
key: 'nodeString',
type: 'string',
required: true,
variables: true,
},
],
},
],
async run($) {
const builder = new XMLBuilder({
ignoreAttributes: false,
suppressEmptyNode: true,
preserveOrder: true,
});
const parser = new XMLParser({
ignoreAttributes: false,
preserveOrder: true,
parseTagValue: false,
});
const nodes = $.step.parameters.nodes;
const computedNodes = nodes.map((node) => node.nodeString);
const parsedNodes = computedNodes.flatMap((computedNode) =>
parser.parse(computedNode)
);
const xmlString = builder.build([
{
Response: parsedNodes,
},
]);
$.setActionItem({
raw: {
body: xmlString,
statusCode: 200,
headers: { 'content-type': 'text/xml' },
},
});
},
});

View File

@@ -19,7 +19,7 @@ export default defineAction({
arguments: [
{
name: 'key',
value: 'listIncomingPhoneNumbers',
value: 'listIncomingSmsPhoneNumbers',
},
],
},

View File

@@ -1,3 +1,15 @@
import listIncomingPhoneNumbers from './list-incoming-phone-numbers/index.js';
import listIncomingCallPhoneNumbers from './list-incoming-call-phone-numbers/index.js';
import listIncomingSmsPhoneNumbers from './list-incoming-sms-phone-numbers/index.js';
import listVoiceXmlNodeAttributes from './list-voice-xml-node-attributes/index.js';
import listVoiceXmlNodeAttributeValues from './list-voice-xml-node-attribute-values/index.js';
import listVoiceXmlChildrenNodes from './list-voice-xml-children-nodes/index.js';
import listVoiceXmlNodes from './list-voice-xml-nodes/index.js';
export default [listIncomingPhoneNumbers];
export default [
listIncomingCallPhoneNumbers,
listIncomingSmsPhoneNumbers,
listVoiceXmlNodeAttributes,
listVoiceXmlNodeAttributeValues,
listVoiceXmlNodes,
listVoiceXmlChildrenNodes,
];

View File

@@ -0,0 +1,37 @@
export default {
name: 'List incoming call phone numbers',
key: 'listIncomingCallPhoneNumbers',
async run($) {
let requestPath = `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/IncomingPhoneNumbers`;
const aggregatedResponse = {
data: [],
};
do {
const { data } = await $.http.get(requestPath);
const voiceCapableIncomingPhoneNumbers = data.incoming_phone_numbers
.filter((incomingPhoneNumber) => {
return incomingPhoneNumber.capabilities.voice;
})
.map((incomingPhoneNumber) => {
const friendlyName = incomingPhoneNumber.friendly_name;
const phoneNumber = incomingPhoneNumber.phone_number;
const name = [friendlyName, phoneNumber].filter(Boolean).join(' - ');
return {
value: incomingPhoneNumber.sid,
name,
};
});
aggregatedResponse.data.push(...voiceCapableIncomingPhoneNumbers);
requestPath = data.next_page_uri;
} while (requestPath);
return aggregatedResponse;
},
};

View File

@@ -1,6 +1,6 @@
export default {
name: 'List incoming phone numbers',
key: 'listIncomingPhoneNumbers',
name: 'List incoming SMS phone numbers',
key: 'listIncomingSmsPhoneNumbers',
async run($) {
let requestPath = `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/IncomingPhoneNumbers`;

View File

@@ -0,0 +1,37 @@
export default {
name: 'List voice XML children nodes',
key: 'listVoiceXmlChildrenNodes',
async run($) {
const parentNodeName = $.step.parameters.parentNodeName;
const parentChildrenNodeMap = {
Dial: [
{ name: 'Number', value: 'Number' },
{ name: 'Conference', value: 'Conference' },
{ name: 'Queue', value: 'Queue' },
{ name: 'Sip', value: 'Sip' },
{ name: 'Verto', value: 'Verto' },
],
Gather: [
{ name: 'Say', value: 'Say' },
{ name: 'Play', value: 'Play' },
{ name: 'Pause', value: 'Pause' },
],
Refer: [{ name: 'Sip', value: 'Sip' }],
Connect: [
{ name: 'Room', value: 'Room' },
{ name: 'Stream', value: 'Stream' },
{ name: 'VirtualAgent', value: 'VirtualAgent' },
],
};
const childrenNodes = parentChildrenNodeMap[parentNodeName] || [];
const nodes = {
data: childrenNodes,
};
return nodes;
},
};

View File

@@ -0,0 +1,516 @@
export default {
name: 'List voice XML node attribute values',
key: 'listVoiceXmlNodeAttributeValues',
async run($) {
const nodeName = $.step.parameters.nodeName;
const attributeKey = $.step.parameters.attributeKey;
// Node: Conference
const conferenceMutedAttributeValues = [
{
name: 'Yes',
value: true,
},
{
name: 'No',
value: false,
},
];
const conferenceBeepAttributeValues = [
{
name: 'Yes',
value: true,
},
{
name: 'No',
value: false,
},
{
name: 'On Enter Only',
value: 'onEnter',
},
{
name: 'On Exit Only',
value: 'onExit',
},
];
const conferenceStartConferenceOnEnterAttributeValues = [
{
name: 'Yes',
value: true,
},
{
name: 'No',
value: false,
},
];
const conferenceEndConferenceOnExitAttributeValues = [
{
name: 'Yes',
value: true,
},
{
name: 'No',
value: false,
},
];
const conferenceWaitMethodAttributeValues = [
{
name: 'POST',
value: 'POST',
},
{
name: 'GET',
value: 'GET',
},
];
const conferenceRecordAttributeValues = [
{
name: 'Record From Start',
value: 'record-from-start',
},
{
name: 'Do Not Record',
value: 'do-not-record',
},
];
const conferenceTrimAttributeValues = [
{
name: 'Trim Silence',
value: 'trim-silence',
},
{
name: 'Do Not Trim',
value: 'do-not-trim',
},
];
const conferenceJitterBufferAttributeValues = [
{
name: 'Off',
value: 'off',
},
{
name: 'Fixed',
value: 'fixed',
},
{
name: 'Adaptive',
value: 'adaptive',
},
];
const conference = {
muted: conferenceMutedAttributeValues,
beep: conferenceBeepAttributeValues,
startConferenceOnEnter: conferenceStartConferenceOnEnterAttributeValues,
endConferenceOnExit: conferenceEndConferenceOnExitAttributeValues,
waitMethod: conferenceWaitMethodAttributeValues,
record: conferenceRecordAttributeValues,
trim: conferenceTrimAttributeValues,
jitterBuffer: conferenceJitterBufferAttributeValues,
};
// NODE: Say
const sayVoiceAttributeValues = [
{ name: 'Man', value: 'man' },
{ name: 'Woman', value: 'woman' },
{ name: 'Polly Man', value: 'Polly.man' },
{ name: 'Polly Woman', value: 'Polly.woman' },
{ name: 'Polly Man Neural', value: 'Polly.man-Neural' },
{ name: 'Polly Woman Neural', value: 'Polly.woman-Neural' },
{ name: 'Google Cloud Man', value: 'gcloud.man' },
{ name: 'Google Cloud Woman', value: 'gcloud.woman' },
];
const sayLoopAttributeValues = [
{ name: 'Infinite', value: 0 },
{ name: 'One Time', value: 1 },
{ name: 'Two Times', value: 2 },
{ name: 'Three Times', value: 3 },
{ name: 'Four Times', value: 4 },
{ name: 'Five Times', value: 5 },
];
const sayLanguageAttributeValues = [
{ name: 'English (US)', value: 'en-US' },
{ name: 'English (UK)', value: 'en-GB' },
{ name: 'Spanish (Spain)', value: 'es-ES' },
{ name: 'French (France)', value: 'fr-FR' },
{ name: 'German (Germany)', value: 'de-DE' },
];
const say = {
voice: sayVoiceAttributeValues,
loop: sayLoopAttributeValues,
language: sayLanguageAttributeValues,
};
// Node: Sip
const sipCodecsAttributeValues = [
{ name: 'PCMU', value: 'PCMU' },
{ name: 'PCMA', value: 'PCMA' },
{ name: 'G722', value: 'G722' },
{ name: 'G729', value: 'G729' },
{ name: 'OPUS', value: 'OPUS' },
];
const sipMethodAttributeValues = [
{ name: 'GET', value: 'GET' },
{ name: 'POST', value: 'POST' },
];
const sipStatusCallbackMethodAttributeValues = [
{ name: 'GET', value: 'GET' },
{ name: 'POST', value: 'POST' },
];
const sipStatusCallbackEventValues = [
{ name: 'Initiated', value: 'initiated' },
{ name: 'Ringing', value: 'ringing' },
{ name: 'Answered', value: 'answered' },
{ name: 'Completed', value: 'completed' },
];
const sip = {
codecs: sipCodecsAttributeValues,
method: sipMethodAttributeValues,
statusCallbackMethod: sipStatusCallbackMethodAttributeValues,
statusCallbackEvent: sipStatusCallbackEventValues,
};
// Node: Stream
const streamTrackAttributeValues = [
{
name: 'Inbound Track',
value: 'inbound_track',
},
{
name: 'Outbound Track',
value: 'outbound_track',
},
{
name: 'Both Tracks',
value: 'both_tracks',
},
];
const streamStatusCallbackMethodAttributeValues = [
{
name: 'GET',
value: 'GET',
},
{
name: 'POST',
value: 'POST',
},
];
const stream = {
track: streamTrackAttributeValues,
statusCallbackMethod: streamStatusCallbackMethodAttributeValues,
};
// Node: Dial
const dialAnswerOnBridgeAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const dialHangupOnStarAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const dialMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const dialRecordAttributeValues = [
{ name: 'Do Not Record', value: 'do-not-record' },
{ name: 'Record from Answer', value: 'record-from-answer' },
{ name: 'Record from Ringing', value: 'record-from-ringing' },
{ name: 'Dual Channel from Answer', value: 'record-from-answer-dual' },
{ name: 'Dual Channel from Ringing', value: 'record-from-ringing-dual' },
];
const dialRecordingStatusCallbackEventAttributeValues = [
{ name: 'Completed', value: 'completed' },
{ name: 'In Progress', value: 'in-progress' },
{ name: 'Absent', value: 'absent' },
];
const dialRecordingStatusCallbackMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const dialRecordingTrackAttributeValues = [
{ name: 'Inbound', value: 'inbound' },
{ name: 'Outbound', value: 'outbound' },
{ name: 'Both', value: 'both' },
];
const dialRingToneAttributeValues = [
{ name: 'Austria', value: 'at' },
{ name: 'Australia', value: 'au' },
{ name: 'Belgium', value: 'be' },
{ name: 'Brazil', value: 'br' },
{ name: 'Canada', value: 'ca' },
{ name: 'China', value: 'cn' },
{ name: 'Denmark', value: 'dk' },
{ name: 'France', value: 'fr' },
{ name: 'Germany', value: 'de' },
{ name: 'United States', value: 'us' },
{ name: 'United Kingdom', value: 'uk' },
{ name: 'Japan', value: 'jp' },
// Add more ISO 3166-1 alpha-2 codes as needed
];
const dialTrimAttributeValues = [
{ name: 'Trim Silence', value: 'trim-silence' },
{ name: 'Do Not Trim', value: 'do-not-trim' },
];
const dial = {
answerOnBridge: dialAnswerOnBridgeAttributeValues,
hangupOnStar: dialHangupOnStarAttributeValues,
method: dialMethodAttributeValues,
record: dialRecordAttributeValues,
recordingStatusCallbackEvent:
dialRecordingStatusCallbackEventAttributeValues,
recordingStatusCallbackMethod:
dialRecordingStatusCallbackMethodAttributeValues,
recordingTrack: dialRecordingTrackAttributeValues,
ringTone: dialRingToneAttributeValues,
trim: dialTrimAttributeValues,
};
// Node: Enqueue
const enqueueMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const enqueueWaitUrlMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const enqueue = {
method: enqueueMethodAttributeValues,
waitUrlMethod: enqueueWaitUrlMethodAttributeValues,
};
// Node: Gather
const gatherActionOnEmptyResultAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const gatherEnhancedAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const gatherInputAttributeValues = [
{ name: 'DTMF', value: 'dtmf' },
{ name: 'Speech', value: 'speech' },
{ name: 'DTMF and Speech', value: 'dtmf speech' },
];
const gatherLanguageAttributeValues = [
{ name: 'English (US)', value: 'en-US' },
{ name: 'English (UK)', value: 'en-GB' },
{ name: 'Spanish (Spain)', value: 'es-ES' },
{ name: 'French (France)', value: 'fr-FR' },
{ name: 'German (Germany)', value: 'de-DE' },
];
const gatherMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const gatherProfanityFilterAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const gatherSpeechModelAttributeValues = [
{ name: 'Phone Call', value: 'phone_call' },
{ name: 'Video', value: 'video' },
{ name: 'Default', value: 'default' },
];
const gatherSpeechTimeoutAttributeValues = [
{ name: 'Auto', value: 'auto' },
];
const gather = {
actionOnEmptyResult: gatherActionOnEmptyResultAttributeValues,
enhanced: gatherEnhancedAttributeValues,
input: gatherInputAttributeValues,
language: gatherLanguageAttributeValues,
method: gatherMethodAttributeValues,
profanityFilter: gatherProfanityFilterAttributeValues,
speechModel: gatherSpeechModelAttributeValues,
speechTimeout: gatherSpeechTimeoutAttributeValues,
};
// Node: Number
const numberMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const numberStatusCallbackEventAttributeValues = [
{ name: 'Initiated', value: 'initiated' },
{ name: 'Ringing', value: 'ringing' },
{ name: 'Answered', value: 'answered' },
{ name: 'Completed', value: 'completed' },
];
const number = {
method: numberMethodAttributeValues,
statusCallbackEvent: numberStatusCallbackEventAttributeValues,
};
// Node: Queue
const queueMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const queue = {
method: queueMethodAttributeValues,
};
// Node: Record
const recordMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const recordPlayBeepAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const recordTrimAttributeValues = [
{ name: 'Trim Silence', value: 'trim-silence' },
{ name: 'Do Not Trim', value: 'do-not-trim' },
];
const recordRecordingStatusCallbackEventAttributeValues = [
{ name: 'Completed', value: 'completed' },
{ name: 'In Progress', value: 'in-progress' },
{ name: 'Absent', value: 'absent' },
];
const recordRecordingStatusCallbackMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const recordStorageUrlMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'PUT', value: 'PUT' },
];
const recordTranscribeAttributeValues = [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
];
const record = {
method: recordMethodAttributeValues,
playBeep: recordPlayBeepAttributeValues,
trim: recordTrimAttributeValues,
recordingStatusCallbackEvent:
recordRecordingStatusCallbackEventAttributeValues,
recordingStatusCallbackMethod:
recordRecordingStatusCallbackMethodAttributeValues,
storageUrlMethod: recordStorageUrlMethodAttributeValues,
transcribe: recordTranscribeAttributeValues,
};
// Node: Redirect
const redirectMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const redirect = {
method: redirectMethodAttributeValues,
};
// Node: Refer
const referMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const refer = {
method: referMethodAttributeValues,
};
// Node: Reject
const rejectReasonAttributeValues = [
{ name: 'Busy', value: 'busy' },
{ name: 'Rejected', value: 'rejected' },
];
const reject = {
reason: rejectReasonAttributeValues,
};
// Node: Sms
const smsMethodAttributeValues = [
{ name: 'POST', value: 'POST' },
{ name: 'GET', value: 'GET' },
];
const sms = {
method: smsMethodAttributeValues,
};
const allNodeAttributeValues = {
Conference: conference,
Dial: dial,
Enqueue: enqueue,
Gather: gather,
Number: number,
Queue: queue,
Record: record,
Redirect: redirect,
Refer: refer,
Reject: reject,
Say: say,
Sip: sip,
Sms: sms,
Stream: stream,
};
if (!nodeName) return { data: [] };
const selectedNodeAttributes = allNodeAttributeValues[nodeName];
if (!selectedNodeAttributes) return { data: [] };
const selectedNodeAttributeValues = selectedNodeAttributes[attributeKey];
if (!selectedNodeAttributeValues) return { data: [] };
return { data: selectedNodeAttributeValues };
},
};

View File

@@ -0,0 +1,205 @@
export default {
name: 'List voice XML node attributes',
key: 'listVoiceXmlNodeAttributes',
async run($) {
const nodeName = $.step.parameters.nodeName;
const conferenceAttributes = [
{ name: 'Beep', value: 'beep' },
{ name: 'Coach', value: 'coach' },
{ name: 'End Conference On Exit', value: 'endConferenceOnExit' },
{ name: 'Event Callback URL', value: 'eventCallbackUrl' },
{ name: 'Max Participants', value: 'maxParticipants' },
{ name: 'Muted', value: 'muted' },
{ name: 'Record', value: 'record' },
{
name: 'Recording Status Callback Event',
value: 'recordingStatusCallbackEvent',
},
{
name: 'Recording Status Callback Method',
value: 'recordingStatusCallbackMethod',
},
{ name: 'Recording Status Callback', value: 'recordingStatusCallback' },
{ name: 'Start Conference On Enter', value: 'startConferenceOnEnter' },
{ name: 'Status Callback Event', value: 'statusCallbackEvent' },
{ name: 'Status Callback Method', value: 'statusCallbackMethod' },
{ name: 'Status Callback', value: 'statusCallback' },
{ name: 'Trim', value: 'trim' },
{ name: 'Wait Method', value: 'waitMethod' },
{ name: 'Wait URL', value: 'waitUrl' },
];
const dialAttributes = [
{ name: 'Action', value: 'action' },
{ name: 'Answer On Bridge', value: 'answerOnBridge' },
{ name: 'Caller ID', value: 'callerId' },
{ name: 'Caller Name', value: 'callerName' },
{ name: 'Hangup On Star', value: 'hangupOnStar' },
{ name: 'Method', value: 'method' },
{ name: 'Record', value: 'record' },
{
name: 'Recording Status Callback Event',
value: 'recordingStatusCallbackEvent',
},
{
name: 'Recording Status Callback Method',
value: 'recordingStatusCallbackMethod',
},
{ name: 'Recording Status Callback', value: 'recordingStatusCallback' },
{
name: 'Recording Storage URL Method',
value: 'recordingStorageUrlMethod',
},
{ name: 'Recording Storage URL', value: 'recordingStorageUrl' },
{ name: 'Recording Track', value: 'recordingTrack' },
{ name: 'Ring Tone', value: 'ringTone' },
{ name: 'Time Limit', value: 'timeLimit' },
{ name: 'Timeout', value: 'timeout' },
{ name: 'Trim', value: 'trim' },
];
const echoAttributes = [{ name: 'Timeout', value: 'timeout' }];
const enqueueAttributes = [
{ name: 'Action', value: 'action' },
{ name: 'Method', value: 'method' },
{ name: 'Wait URL', value: 'waitUrl' },
{ name: 'Wait URL Method', value: 'waitUrlMethod' },
];
const gatherAttributes = [
{ name: 'Action On Empty Result', value: 'actionOnEmptyResult' },
{ name: 'Action', value: 'action' },
{ name: 'Enhanced', value: 'enhanced' },
{ name: 'Finish On Key', value: 'finishOnKey' },
{ name: 'Hints', value: 'hints' },
{ name: 'Input', value: 'input' },
{ name: 'Language', value: 'language' },
{ name: 'Method', value: 'method' },
{ name: 'Num Digits', value: 'numDigits' },
{
name: 'Partial Result Callback Method',
value: 'partialResultCallbackMethod',
},
{ name: 'Partial Result Callback', value: 'partialResultCallback' },
{ name: 'Profanity Filter', value: 'profanityFilter' },
{ name: 'Speech Model', value: 'speechModel' },
{ name: 'Speech Timeout', value: 'speechTimeout' },
{ name: 'Timeout', value: 'timeout' },
];
const numberAttributes = [
{ name: 'Method', value: 'method' },
{ name: 'Send Digits', value: 'sendDigits' },
{ name: 'Status Callback Event', value: 'statusCallbackEvent' },
{ name: 'Status Callback Method', value: 'statusCallbackMethod' },
{ name: 'Status Callback', value: 'statusCallback' },
{ name: 'URL', value: 'url' },
];
const pauseAttributes = [{ name: 'Length', value: 'length' }];
const playAttributes = [
{ name: 'Digits', value: 'digits' },
{ name: 'Loop', value: 'loop' },
];
const queueAttributes = [
{ name: 'Method', value: 'method' },
{ name: 'URL', value: 'url' },
];
const recordAttributes = [
{ name: 'Action', value: 'action' },
{ name: 'Finish On Key', value: 'finishOnKey' },
{ name: 'Max Length', value: 'maxLength' },
{ name: 'Method', value: 'method' },
{ name: 'Play Beep', value: 'playBeep' },
{
name: 'Recording Status Callback Event',
value: 'recordingStatusCallbackEvent',
},
{
name: 'Recording Status Callback Method',
value: 'recordingStatusCallbackMethod',
},
{ name: 'Recording Status Callback', value: 'recordingStatusCallback' },
{ name: 'Storage URL Method', value: 'storageUrlMethod' },
{ name: 'Storage URL', value: 'storageUrl' },
{ name: 'Timeout', value: 'timeout' },
{ name: 'Transcribe Callback', value: 'transcribeCallback' },
{ name: 'Transcribe', value: 'transcribe' },
{ name: 'Trim', value: 'trim' },
];
const redirectAttributes = [{ name: 'Method', value: 'method' }];
const referAttributes = [
{ name: 'Action', value: 'action' },
{ name: 'Method', value: 'method' },
];
const rejectAttributes = [{ name: 'Reason', value: 'reason' }];
const sayAttributes = [
{ name: 'Language', value: 'language' },
{ name: 'Loop', value: 'loop' },
{ name: 'Voice', value: 'voice' },
];
const sipAttributes = [
{ name: 'Codecs', value: 'codecs' },
{ name: 'Method', value: 'method' },
{ name: 'Password', value: 'password' },
{ name: 'Session Timeout', value: 'sessionTimeout' },
{ name: 'Status Callback Event', value: 'statusCallbackEvent' },
{ name: 'Status Callback Method', value: 'statusCallbackMethod' },
{ name: 'Status Callback', value: 'statusCallback' },
{ name: 'URL', value: 'url' },
{ name: 'Username', value: 'username' },
];
const smsAttributes = [
{ name: 'Action', value: 'action' },
{ name: 'From', value: 'from' },
{ name: 'Method', value: 'method' },
{ name: 'Status Callback', value: 'statusCallback' },
{ name: 'To', value: 'to' },
];
const virtualAgentAttributes = [
{ name: 'Connector Name', value: 'connectorName' },
];
const streamAttributes = [
{ name: 'URL', value: 'url' },
{ name: 'Name', value: 'name' },
{ name: 'Track', value: 'track' },
{ name: 'Status Callback', value: 'statusCallback' },
{ name: 'Status Callback Method', value: 'statusCallbackMethod' },
];
if (nodeName === 'Conference') return { data: conferenceAttributes };
if (nodeName === 'Dial') return { data: dialAttributes };
if (nodeName === 'Echo') return { data: echoAttributes };
if (nodeName === 'Enqueue') return { data: enqueueAttributes };
if (nodeName === 'Gather') return { data: gatherAttributes };
if (nodeName === 'Number') return { data: numberAttributes };
if (nodeName === 'Pause') return { data: pauseAttributes };
if (nodeName === 'Play') return { data: playAttributes };
if (nodeName === 'Queue') return { data: queueAttributes };
if (nodeName === 'Record') return { data: recordAttributes };
if (nodeName === 'Redirect') return { data: redirectAttributes };
if (nodeName === 'Refer') return { data: referAttributes };
if (nodeName === 'Reject') return { data: rejectAttributes };
if (nodeName === 'Say') return { data: sayAttributes };
if (nodeName === 'Sip') return { data: sipAttributes };
if (nodeName === 'Sms') return { data: smsAttributes };
if (nodeName === 'Stream') return { data: streamAttributes };
if (nodeName === 'VirtualAgent') return { data: virtualAgentAttributes };
return { data: [] };
},
};

View File

@@ -0,0 +1,37 @@
export default {
name: 'List voice XML nodes',
key: 'listVoiceXmlNodes',
async run() {
const nodes = {
data: [
{ name: 'Conference', value: 'Conference' },
{ name: 'Connect', value: 'Connect' },
{ name: 'Denoise', value: 'Denoise' },
{ name: 'Dial', value: 'Dial' },
{ name: 'Echo', value: 'Echo' },
{ name: 'Enqueue', value: 'Enqueue' },
{ name: 'Gather', value: 'Gather' },
{ name: 'Hangup', value: 'Hangup' },
{ name: 'Leave', value: 'Leave' },
{ name: 'Number', value: 'Number' },
{ name: 'Pause', value: 'Pause' },
{ name: 'Play', value: 'Play' },
{ name: 'Queue', value: 'Queue' },
{ name: 'Record', value: 'Record' },
{ name: 'Redirect', value: 'Redirect' },
{ name: 'Refer', value: 'Refer' },
{ name: 'Reject', value: 'Reject' },
{ name: 'Room', value: 'Room' },
{ name: 'Say', value: 'Say' },
{ name: 'Sip', value: 'Sip' },
{ name: 'Sms', value: 'Sms' },
{ name: 'Stream', value: 'Stream' },
{ name: 'Verto', value: 'Verto' },
{ name: 'VirtualAgent', value: 'VirtualAgent' },
],
};
return nodes;
},
};

View File

@@ -0,0 +1,3 @@
import listNodeFields from './list-node-fields/index.js';
export default [listNodeFields];

View File

@@ -0,0 +1,121 @@
export default {
name: 'List node fields',
key: 'listNodeFields',
async run($) {
const hasChildrenNodes = $.step.parameters.hasChildrenNodes;
if (!hasChildrenNodes) {
return [];
}
return [
{
label: 'Children nodes',
key: 'childrenNodes',
type: 'dynamic',
required: false,
description: 'Add or remove nested node as needed',
value: [
{
key: 'Content-Type',
value: 'application/json',
},
],
fields: [
{
label: 'Node name',
key: 'nodeName',
type: 'dropdown',
required: false,
description: 'The name of the node to be added.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceXmlChildrenNodes',
},
{
name: 'parameters.parentNodeName',
value: '{parameters.nodeName}',
},
],
},
},
{
label: 'Node value',
key: 'nodeValue',
type: 'string',
required: false,
description: 'The value of the node to be added.',
variables: true,
},
{
label: 'Attributes',
key: 'attributes',
type: 'dynamic',
required: false,
description: 'Add or remove attributes for the node as needed',
value: [
{
key: '',
value: '',
},
],
fields: [
{
label: 'Attribute name',
key: 'key',
type: 'dropdown',
required: false,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceXmlNodeAttributes',
},
{
name: 'parameters.nodeName',
value: '{outerScope.nodeName}',
},
],
},
},
{
label: 'Attribute value',
key: 'value',
type: 'dropdown',
required: false,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceXmlNodeAttributeValues',
},
{
name: 'parameters.nodeName',
value: '{outerScope.nodeName}',
},
{
name: 'parameters.attributeKey',
value: '{fieldsScope.key}',
},
],
},
},
],
},
],
},
];
},
};

View File

@@ -4,6 +4,7 @@ import auth from './auth/index.js';
import triggers from './triggers/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
import dynamicFields from './dynamic-fields/index.js';
export default defineApp({
name: 'SignalWire',
@@ -19,4 +20,5 @@ export default defineApp({
triggers,
actions,
dynamicData,
dynamicFields,
});

View File

@@ -1,3 +1,4 @@
import receiveCall from './receive-call/index.js';
import receiveSms from './receive-sms/index.js';
export default [receiveSms];
export default [receiveCall, receiveSms];

View File

@@ -0,0 +1,83 @@
import { URLSearchParams } from 'node:url';
import Crypto from 'node:crypto';
import isEmpty from 'lodash/isEmpty.js';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'Receive Call',
key: 'receiveCall',
workSynchronously: true,
type: 'webhook',
description: 'Triggers when a new call is received.',
arguments: [
{
label: 'To Number',
key: 'phoneNumberSid',
type: 'dropdown',
required: true,
description:
'The number to receive the call on. It should be a SignalWire number in your project.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listIncomingCallPhoneNumbers',
},
],
},
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const lastExecutionStep = await $.getLastExecutionStep();
if (!isEmpty(lastExecutionStep?.dataOut)) {
$.pushTriggerItem({
raw: lastExecutionStep.dataOut,
meta: {
internalId: '',
},
});
}
},
async registerHook($) {
const phoneNumberSid = $.step.parameters.phoneNumberSid;
const payload = new URLSearchParams({
VoiceUrl: $.webhookUrl,
}).toString();
await $.http.post(
`/2010-04-01/Accounts/${$.auth.data.accountSid}/IncomingPhoneNumbers/${phoneNumberSid}.json`,
payload
);
},
async unregisterHook($) {
const phoneNumberSid = $.step.parameters.phoneNumberSid;
const payload = new URLSearchParams({
VoiceUrl: '',
}).toString();
await $.http.post(
`/2010-04-01/Accounts/${$.auth.data.accountSid}/IncomingPhoneNumbers/${phoneNumberSid}.json`,
payload
);
},
});

View File

@@ -20,7 +20,7 @@ export default defineTrigger({
arguments: [
{
name: 'key',
value: 'listIncomingPhoneNumbers',
value: 'listIncomingSmsPhoneNumbers',
},
],
},

View File

@@ -0,0 +1,169 @@
import defineAction from '../../../../helpers/define-action.js';
const castFloatOrUndefined = (value) => {
return value === '' ? undefined : parseFloat(value);
};
export default defineAction({
name: 'Create chat completion',
key: 'createChatCompletion',
description: 'Queries a chat model.',
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listModels',
},
],
},
},
{
label: 'Messages',
key: 'messages',
type: 'dynamic',
required: true,
description: 'A list of messages comprising the conversation so far.',
value: [{ role: 'system', body: '' }],
fields: [
{
label: 'Role',
key: 'role',
type: 'dropdown',
required: true,
description:
'The role of the messages author. Choice between: system, user, or assistant.',
options: [
{
label: 'System',
value: 'system',
},
{
label: 'Assistant',
value: 'assistant',
},
{
label: 'User',
value: 'user',
},
],
},
{
label: 'Content',
key: 'content',
type: 'string',
required: true,
variables: true,
description:
'The content of the message, which can either be a simple string or a structured format.',
},
],
},
{
label: 'Temperature',
key: 'temperature',
type: 'string',
required: false,
variables: true,
description:
'A decimal number from 0-1 that determines the degree of randomness in the response. A temperature less than 1 favors more correctness and is appropriate for question answering or summarization. A value closer to 1 introduces more randomness in the output.',
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string',
required: false,
variables: true,
description: 'The maximum number of tokens to generate.',
},
{
label: 'Stop sequences',
key: 'stopSequences',
type: 'dynamic',
required: false,
variables: true,
description:
'A list of string sequences that will truncate (stop) inference text output. For example, "" will stop generation as soon as the model generates the given token.',
fields: [
{
label: 'Stop sequence',
key: 'stopSequence',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'Top P',
key: 'topP',
type: 'string',
required: false,
variables: true,
description: `A percentage (also called the nucleus parameter) that's used to dynamically adjust the number of choices for each predicted token based on the cumulative probabilities. It specifies a probability threshold below which all less likely tokens are filtered out. This technique helps maintain diversity and generate more fluent and natural-sounding text.`,
},
{
label: 'Top K',
key: 'topK',
type: 'string',
required: false,
variables: true,
description: `An integer that's used to limit the number of choices for the next predicted word or token. It specifies the maximum number of tokens to consider at each step, based on their probability of occurrence. This technique helps to speed up the generation process and can improve the quality of the generated text by focusing on the most likely options.`,
},
{
label: 'Frequency Penalty',
key: 'frequencyPenalty',
type: 'string',
required: false,
variables: true,
description: `A number between -2.0 and 2.0 where a positive value decreases the likelihood of repeating tokens that have already been mentioned.`,
},
{
label: 'Presence Penalty',
key: 'presencePenalty',
type: 'string',
required: false,
variables: true,
description: `A number between -2.0 and 2.0 where a positive value increases the likelihood of a model talking about new topics.`,
},
],
async run($) {
const nonEmptyStopSequences = $.step.parameters.stopSequences
.filter(({ stopSequence }) => stopSequence)
.map(({ stopSequence }) => stopSequence);
const messages = $.step.parameters.messages.map((message) => ({
role: message.role,
content: message.content,
}));
const payload = {
model: $.step.parameters.model,
messages,
temperature: castFloatOrUndefined($.step.parameters.temperature),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens),
stop: nonEmptyStopSequences,
top_p: castFloatOrUndefined($.step.parameters.topP),
top_k: castFloatOrUndefined($.step.parameters.topK),
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty),
frequency_penalty: castFloatOrUndefined(
$.step.parameters.frequencyPenalty
),
};
const { data } = await $.http.post('/v1/chat/completions', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,131 @@
import defineAction from '../../../../helpers/define-action.js';
const castFloatOrUndefined = (value) => {
return value === '' ? undefined : parseFloat(value);
};
export default defineAction({
name: 'Create completion',
key: 'createCompletion',
description: 'Queries a language, code, or image model.',
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listModels',
},
],
},
},
{
label: 'Prompt',
key: 'prompt',
type: 'string',
required: true,
variables: true,
description: 'A string providing context for the model to complete.',
},
{
label: 'Temperature',
key: 'temperature',
type: 'string',
required: false,
variables: true,
description:
'A decimal number from 0-1 that determines the degree of randomness in the response. A temperature less than 1 favors more correctness and is appropriate for question answering or summarization. A value closer to 1 introduces more randomness in the output.',
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string',
required: false,
variables: true,
description: 'The maximum number of tokens to generate.',
},
{
label: 'Stop sequences',
key: 'stopSequences',
type: 'dynamic',
required: false,
variables: true,
description:
'A list of string sequences that will truncate (stop) inference text output. For example, "" will stop generation as soon as the model generates the given token.',
fields: [
{
label: 'Stop sequence',
key: 'stopSequence',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'Top P',
key: 'topP',
type: 'string',
required: false,
variables: true,
description: `A percentage (also called the nucleus parameter) that's used to dynamically adjust the number of choices for each predicted token based on the cumulative probabilities. It specifies a probability threshold below which all less likely tokens are filtered out. This technique helps maintain diversity and generate more fluent and natural-sounding text.`,
},
{
label: 'Top K',
key: 'topK',
type: 'string',
required: false,
variables: true,
description: `An integer that's used to limit the number of choices for the next predicted word or token. It specifies the maximum number of tokens to consider at each step, based on their probability of occurrence. This technique helps to speed up the generation process and can improve the quality of the generated text by focusing on the most likely options.`,
},
{
label: 'Frequency Penalty',
key: 'frequencyPenalty',
type: 'string',
required: false,
variables: true,
description: `A number between -2.0 and 2.0 where a positive value decreases the likelihood of repeating tokens that have already been mentioned.`,
},
{
label: 'Presence Penalty',
key: 'presencePenalty',
type: 'string',
required: false,
variables: true,
description: `A number between -2.0 and 2.0 where a positive value increases the likelihood of a model talking about new topics.`,
},
],
async run($) {
const nonEmptyStopSequences = $.step.parameters.stopSequences
.filter(({ stopSequence }) => stopSequence)
.map(({ stopSequence }) => stopSequence);
const payload = {
model: $.step.parameters.model,
prompt: $.step.parameters.prompt,
temperature: castFloatOrUndefined($.step.parameters.temperature),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens),
stop: nonEmptyStopSequences,
top_p: castFloatOrUndefined($.step.parameters.topP),
top_k: castFloatOrUndefined($.step.parameters.topK),
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty),
frequency_penalty: castFloatOrUndefined(
$.step.parameters.frequencyPenalty
),
};
const { data } = await $.http.post('/v1/completions', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,4 @@
import createCompletion from './create-completion/index.js';
import createChatCompletion from './create-chat-completion/index.js';
export default [createChatCompletion, createCompletion];

View File

@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g clip-path="url(#clip0_542_18748)"><rect width="32" height="32" rx="5.64706" fill="#F1EFED"></rect><circle cx="22.8233" cy="9.64706" r="5.64706" fill="#D3D1D1"></circle><circle cx="22.8233" cy="22.8238" r="5.64706" fill="#D3D1D1"></circle><circle cx="9.64706" cy="22.8238" r="5.64706" fill="#D3D1D1"></circle><circle cx="9.64706" cy="9.64706" r="5.64706" fill="#0F6FFF"></circle></g><defs><clipPath id="clip0_542_18748"><rect width="32" height="32" fill="white"></rect></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 567 B

View File

@@ -0,0 +1,34 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Together AI API key of your account.',
docUrl: 'https://automatisch.io/docs/together-ai#api-key',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,6 @@
const isStillVerified = async ($) => {
await $.http.get('/v1/models');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async ($) => {
await $.http.get('/v1/models');
};
export default verifyCredentials;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,3 @@
import listModels from './list-models/index.js';
export default [listModels];

View File

@@ -0,0 +1,17 @@
export default {
name: 'List models',
key: 'listModels',
async run($) {
const { data } = await $.http.get('/v1/models');
const models = data.map((model) => {
return {
value: model.id,
name: model.display_name,
};
});
return { data: models };
},
};

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'Together AI',
key: 'together-ai',
baseUrl: 'https://together.ai',
apiBaseUrl: 'https://api.together.xyz',
iconUrl: '{BASE_URL}/apps/together-ai/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/together-ai/connection',
primaryColor: '#000000',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
});

View File

@@ -35,9 +35,6 @@ export default defineTrigger({
},
],
useSingletonWebhook: true,
singletonWebhookRefValueParameter: 'phoneNumberSid',
async run($) {
const dataItem = {
raw: $.request.body,

View File

@@ -0,0 +1,134 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create waiter',
key: 'createWaiter',
description: 'Enqueues a waiter to the line with the selected line.',
arguments: [
{
label: 'Line',
key: 'lineId',
type: 'dropdown',
required: true,
variables: true,
description: 'The line to join',
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLines',
},
],
},
},
{
label: 'Phone',
key: 'phone',
type: 'string',
required: true,
variables: true,
description:
"The caller's phone number including country code (for example +4017111112233)",
},
{
label: 'Channel',
key: 'channel',
type: 'dropdown',
description:
'Option describing if the waiter expects a callback or will receive a text message',
required: true,
variables: true,
options: [
{ label: 'Call back', value: 'CallBack' },
{ label: 'Call in', value: 'CallIn' },
],
},
{
label: 'Source',
key: 'source',
type: 'dropdown',
description: 'Option describing the source where the caller came from',
required: true,
variables: true,
options: [
{ label: 'Widget', value: 'Widget' },
{ label: 'Phone', value: 'Phone' },
{ label: 'Mobile', value: 'Mobile' },
{ label: 'App', value: 'App' },
{ label: 'Other', value: 'Other' },
],
},
{
label: 'Appointment',
key: 'appointment',
type: 'dropdown',
required: true,
variables: true,
value: false,
description:
'If set to true, then this marks this as an appointment. If appointment_time is set, this is automatically set to true.',
options: [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listAppointmentFields',
},
{
name: 'parameters.appointment',
value: '{parameters.appointment}',
},
],
},
},
{
label: 'Service phone to call',
key: 'servicePhoneToCall',
type: 'string',
description:
"If set, callback uses this number instead of the line's service phone number",
required: false,
variables: true,
},
],
async run($) {
const {
lineId,
phone,
channel,
source,
appointment,
appointmentTime,
servicePhoneToCall,
} = $.step.parameters;
const body = {
data: {
type: 'waiters',
attributes: {
line_id: lineId,
phone,
channel,
source,
appointment,
servicePhoneToCall,
},
},
};
if (appointment) {
body.data.attributes.appointmentTime = appointmentTime;
}
const { data } = await $.http.post('/v2/waiters', body);
$.setActionItem({ raw: data });
},
});

View File

@@ -0,0 +1,34 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Delete waiter',
key: 'deleteWaiter',
description:
'Cancels waiting. The provided waiter will be removed from the queue.',
arguments: [
{
label: 'Waiter',
key: 'waiterId',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWaiters',
},
],
},
},
],
async run($) {
const waiterId = $.step.parameters.waiterId;
const { data } = await $.http.delete(`/v2/waiters/${waiterId}`);
$.setActionItem({ raw: { output: data } });
},
});

View File

@@ -0,0 +1,6 @@
import createWaiter from './create-waiter/index.js';
import deleteWaiter from './delete-waiter/index.js';
import showWaiter from './show-waiter/index.js';
import updateWaiter from './update-waiter/index.js';
export default [createWaiter, deleteWaiter, showWaiter, updateWaiter];

View File

@@ -0,0 +1,33 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Show waiter',
key: 'showWaiter',
description: 'Returns the complete waiter information.',
arguments: [
{
label: 'Waiter',
key: 'waiterId',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWaiters',
},
],
},
},
],
async run($) {
const waiterId = $.step.parameters.waiterId;
const { data } = await $.http.get(`/v2/waiters/${waiterId}`);
$.setActionItem({ raw: data });
},
});

View File

@@ -0,0 +1,157 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Update waiter',
key: 'updateWaiter',
description: 'Updates a waiter to the line with the selected line.',
arguments: [
{
label: 'Waiter',
key: 'waiterId',
type: 'dropdown',
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWaiters',
},
],
},
},
{
label: 'Line',
key: 'lineId',
type: 'dropdown',
required: false,
variables: true,
description: 'Used to find caller if 0 is used for waiter field',
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLines',
},
],
},
},
{
label: 'Phone',
key: 'phone',
type: 'string',
required: false,
variables: true,
description: 'Used to find caller if 0 is used for waiter field',
},
{
label: 'EWT',
key: 'serviceWaiterEwt',
type: 'string',
description: 'EWT as calculated by the service',
required: false,
variables: true,
},
{
label: 'State',
key: 'serviceWaiterState',
type: 'dropdown',
description: 'State of caller in the call center',
required: false,
variables: true,
options: [
{ label: 'Waiting', value: 'Waiting' },
{ label: 'Connected', value: 'Connected' },
{ label: 'Transferred', value: 'Transferred' },
{ label: 'Timeout', value: 'Timeout' },
{ label: 'Canceled', value: 'Canceled' },
],
},
{
label: 'Wait time',
key: 'waitTimeWhenUp',
type: 'string',
description: 'Wait time in seconds before being transferred to agent',
required: false,
variables: true,
},
{
label: 'Talk time',
key: 'talkTime',
type: 'string',
description: 'Time in seconds spent talking with Agent',
required: false,
variables: true,
},
{
label: 'Agent',
key: 'agentId',
type: 'string',
description: 'Agent where call was transferred to',
required: false,
variables: true,
},
{
label: 'Service phone to call',
key: 'servicePhoneToCall',
type: 'string',
description:
"If set, callback uses this number instead of the line's service phone number",
required: false,
variables: true,
},
],
async run($) {
const {
waiterId,
lineId,
phone,
serviceWaiterEwt,
serviceWaiterState,
waitTimeWhenUp,
talkTime,
agentId,
servicePhoneToCall,
} = $.step.parameters;
const body = {
data: {
type: 'waiters',
attributes: {
line_id: lineId,
phone,
service_phone_to_call: servicePhoneToCall,
},
},
};
if (serviceWaiterEwt) {
body.data.attributes.service_waiter_ewt = serviceWaiterEwt;
}
if (serviceWaiterState) {
body.data.attributes.service_waiter_state = serviceWaiterState;
}
if (talkTime) {
body.data.attributes.talk_time = talkTime;
}
if (agentId) {
body.data.attributes.agent_id = agentId;
}
if (waitTimeWhenUp) {
body.data.attributes.wait_time_when_up = waitTimeWhenUp;
}
const { data } = await $.http.put(`/v2/waiters/${waiterId}`, body);
$.setActionItem({ raw: data });
},
});

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 229.2 57.3">
<defs>
<style>
.cls-1 { fill: #2E3D59; } .cls-2 { fill: #f5d05e; }
</style>
</defs>
<g>
<g id="Ebene_1">
<a>
<path id="fullLogo" class="cls-1" d="M16.3,40.4c-.2.5-.6.8-1.1.8h-4.3c-.5,0-1-.2-1.2-.8L0,17.9c-.2-.4.1-.8.5-.8h4.3c.5,0,1,.2,1.1.8l6.9,16.6,6.9-16.6c.2-.5.6-.8,1.1-.8h4.3c.4,0,.7.4.5.8l-9.6,22.5h0Z"/>
</a>
<path id="fullLogo-2" class="cls-1" d="M219.7,19.2c-5.3,0-9.6-4.3-9.6-9.6S214.4,0,219.7,0s9.6,4.3,9.6,9.6-4.3,9.6-9.6,9.6ZM219.7,1c-4.7,0-8.6,3.8-8.6,8.6s3.8,8.6,8.6,8.6,8.6-3.8,8.6-8.6-3.8-8.6-8.6-8.6Z"/>
<path id="fullLogo-3" class="cls-2" d="M185.7,35.9c-7.8,0-14.2-6.4-14.2-14.2s6.4-14.2,14.2-14.2,14.2,6.4,14.2,14.2-.8,5.3-2.2,7.5l-2.2-2.1c-1.4-1.3-3.7-1.3-5,.1-1.3,1.4-1.3,3.7.1,5l2,1.8c-2,1.1-4.4,1.8-6.9,1.8M202.9,34c2.5-3.5,4-7.7,4-12.3,0-11.7-9.5-21.2-21.2-21.2s-21.2,9.5-21.2,21.2,9.5,21.2,21.2,21.2,8.7-1.4,12.1-3.8l3.1,2.9c.7.6,1.6,1,2.4,1s1.9-.4,2.6-1.1c1.3-1.4,1.3-3.7-.1-5l-2.9-2.7h0Z"/>
<path id="fullLogo-4" class="cls-1" d="M37.8,40.4c0,.5-.3.8-.8.8h-3.9c-.5,0-.8-.3-.8-.8v-22.4c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v22.4ZM35,4.2c1.9,0,3.4,1.5,3.4,3.4s-1.5,3.4-3.4,3.4-3.4-1.5-3.4-3.4,1.5-3.4,3.4-3.4"/>
<a>
<path id="fullLogo-5" class="cls-1" d="M106.8,29.9v-11.9c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v11.9c0,8-5.1,12-11.8,12s-11.8-4-11.8-12v-11.9c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v11.9c0,5.5,3.6,6.7,6.3,6.7s6.3-1.2,6.3-6.7"/>
</a>
<path id="fullLogo-6" class="cls-1" d="M131.7,21.7c-3.1,0-7.2,1.8-7.2,7.5s3.4,7.5,6.4,7.6c3,.2,7.7-1.5,7.7-7.5s-3.9-7.6-7-7.6M144.3,40.4c0,.5-.3.8-.8.8h-3.9c-.5,0-.8-.3-.8-.8v-2.4c-2,3.2-5.8,3.9-8.2,3.9-7.5,0-11.6-6-11.6-12.5s5.7-12.9,12.8-12.9,12.5,4.3,12.5,13v11h0Z"/>
<a>
<path id="fullLogo-7" class="cls-1" d="M156.8,4.2h-3.9c-.5,0-.8.3-.8.8v35.3c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8V5c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-8" class="cls-1" d="M83.1,17.1h-6.4V5c0-.5-.3-.8-.8-.8h-3.9c-.5,0-.8.3-.8.8v12.1h-6.4c-.5,0-.8.3-.8.8v3.6c0,.5.3.8.8.8h6.4v17.9c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8v-17.9h6.4c.5,0,.8-.3.8-.8v-3.6c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-9" class="cls-1" d="M58.4,17.1h-6.4c-4.5,0-7.9,3.4-7.9,10v13.2c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8v-.9s0,0,0,0v-12.3c0-3,1.8-4.7,3.9-4.7h4.9c.5,0,.8-.3.8-.8v-3.6c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-39" class="cls-1" d="M218.8,6.9c0-.1-.1-.2-.3-.2h-1c-.1,0-.2,0-.3.2l-2.3,5.3c0,0,0,.2.1.2h1c.1,0,.2,0,.3-.2l1.6-3.9,1.6,3.9c0,.1.1.2.3.2h1c0,0,.2,0,.1-.2l-2.3-5.3h0Z"/>
</a>
<path id="fullLogo-40" class="cls-1" d="M223.8,12.2c0,.1,0,.2-.2.2h-.9c-.1,0-.2,0-.2-.2v-5.3c0-.1,0-.2.2-.2h.9c.1,0,.2,0,.2.2v5.3Z"/>
<path id="fullLogo-41" class="cls-1" d="M219.7,10.2c0,0,.1,0,.1.2v.9c0,.1,0,.2-.1.2h-3.4c0,0-.1,0-.1-.2v-.9c0-.1,0-.2.1-.2h3.4Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,21 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'API key of the VirtualQ API service.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,8 @@
import verifyCredentials from './verify-credentials.js';
const isStillVerified = async ($) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,14 @@
const verifyCredentials = async ($) => {
const response = await $.http.get('/v2/call_centers');
const callCenterNames = response.data.data
.map((callCenter) => callCenter.attributes.name)
.join(' - ');
await $.auth.set({
screenName: callCenterNames,
apiKey: $.auth.data.apiKey,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,15 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers['X-API-Key'] = $.auth.data.apiKey;
}
if (requestConfig.method === 'post' || requestConfig.method === 'put') {
requestConfig.headers['Content-Type'] = 'application/vnd.api+json';
}
requestConfig.headers['X-Keys-Format'] = 'underscore';
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,4 @@
import listLines from './list-lines/index.js';
import listWaiters from './list-waiters/index.js';
export default [listLines, listWaiters];

View File

@@ -0,0 +1,15 @@
export default {
name: 'List lines',
key: 'listLines',
async run($) {
const response = await $.http.get('/v2/lines');
const lines = response.data.data.map((line) => ({
value: line.id,
name: line.attributes.name,
}));
return { data: lines };
},
};

View File

@@ -0,0 +1,15 @@
export default {
name: 'List waiters',
key: 'listWaiters',
async run($) {
const response = await $.http.get('/v2/waiters');
const waiters = response.data.data.map((waiter) => ({
value: waiter.id,
name: `${waiter.attributes.phone} @ ${waiter.attributes.line.name}`,
}));
return { data: waiters };
},
};

View File

@@ -0,0 +1,3 @@
import listAppointmentFields from './list-appointment-fields/index.js';
export default [listAppointmentFields];

View File

@@ -0,0 +1,20 @@
export default {
name: 'List appointment fields',
key: 'listAppointmentFields',
async run($) {
if ($.step.parameters.appointment) {
return [
{
label: 'Appointment Time',
key: 'appointmentTime',
type: 'string',
required: true,
variables: true,
description:
'Overrides the estimated up time with this time. Specify number of seconds since 1970 UTC.',
},
];
}
},
};

View File

@@ -0,0 +1,22 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
import dynamicFields from './dynamic-fields/index.js';
export default defineApp({
name: 'VirtualQ',
key: 'virtualq',
iconUrl: '{BASE_URL}/apps/virtualq/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/virtualq/connection',
supportsConnections: true,
baseUrl: 'https://www.virtualq.io',
apiBaseUrl: 'https://api.virtualq.io/api/',
primaryColor: '#2E3D59',
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
dynamicFields,
});

View File

@@ -6,9 +6,9 @@ export default async (request, response) => {
.findOne({ key: request.params.appKey })
.throwIfNotFound();
const oauthClient = await appConfig
.$relatedQuery('oauthClients')
.insert(oauthClientParams(request));
const oauthClient = await appConfig.createOAuthClient(
oauthClientParams(request)
);
renderObject(response, oauthClient, { status: 201 });
};

View File

@@ -48,6 +48,34 @@ describe('POST /api/v1/admin/apps/:appKey/oauth-clients', () => {
expect(response.body).toMatchObject(expectedPayload);
});
it('should throw validation error for app that does not support oauth connections', async () => {
await createAppConfig({
key: 'deepl',
});
const oauthClient = {
active: true,
appKey: 'deepl',
name: 'First auth client',
formattedAuthDefaults: {
clientid: 'sample client ID',
clientSecret: 'sample client secret',
instanceUrl: 'https://deepl.com',
oAuthRedirectUrl: 'http://localhost:3001/app/deepl/connection/add',
},
};
const response = await request(app)
.post('/api/v1/admin/apps/deepl/oauth-clients')
.set('Authorization', token)
.send(oauthClient)
.expect(422);
expect(response.body.errors).toMatchObject({
app: ['This app does not support OAuth clients!'],
});
});
it('should return not found response for not existing app config', async () => {
const oauthClient = {
active: true,

View File

@@ -7,6 +7,7 @@ export default async (request, response) => {
const flowsQuery = request.currentUser.authorizedFlows
.clone()
.distinct('flows.*')
.joinRelated({
steps: true,
})

Some files were not shown because too many files have changed in this diff Show More