diff --git a/packages/backend/src/apps/perplexity/actions/index.js b/packages/backend/src/apps/perplexity/actions/index.js new file mode 100644 index 00000000..c95d62f1 --- /dev/null +++ b/packages/backend/src/apps/perplexity/actions/index.js @@ -0,0 +1,3 @@ +import sendChatPrompt from './send-chat-prompt/index.js'; + +export default [sendChatPrompt]; diff --git a/packages/backend/src/apps/perplexity/actions/send-chat-prompt/index.js b/packages/backend/src/apps/perplexity/actions/send-chat-prompt/index.js new file mode 100644 index 00000000..bde28cf1 --- /dev/null +++ b/packages/backend/src/apps/perplexity/actions/send-chat-prompt/index.js @@ -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, + }); + }, +}); diff --git a/packages/backend/src/apps/perplexity/assets/favicon.svg b/packages/backend/src/apps/perplexity/assets/favicon.svg new file mode 100644 index 00000000..b27ffc98 --- /dev/null +++ b/packages/backend/src/apps/perplexity/assets/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/backend/src/apps/perplexity/auth/index.js b/packages/backend/src/apps/perplexity/auth/index.js new file mode 100644 index 00000000..67b870bf --- /dev/null +++ b/packages/backend/src/apps/perplexity/auth/index.js @@ -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, +}; diff --git a/packages/backend/src/apps/perplexity/auth/is-still-verified.js b/packages/backend/src/apps/perplexity/auth/is-still-verified.js new file mode 100644 index 00000000..3f853952 --- /dev/null +++ b/packages/backend/src/apps/perplexity/auth/is-still-verified.js @@ -0,0 +1,5 @@ +const isStillVerified = async () => { + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/perplexity/auth/verify-credentials.js b/packages/backend/src/apps/perplexity/auth/verify-credentials.js new file mode 100644 index 00000000..07e4f027 --- /dev/null +++ b/packages/backend/src/apps/perplexity/auth/verify-credentials.js @@ -0,0 +1,5 @@ +const verifyCredentials = async () => { + return true; +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/perplexity/common/add-auth-header.js b/packages/backend/src/apps/perplexity/common/add-auth-header.js new file mode 100644 index 00000000..f9f5acba --- /dev/null +++ b/packages/backend/src/apps/perplexity/common/add-auth-header.js @@ -0,0 +1,9 @@ +const addAuthHeader = ($, requestConfig) => { + if ($.auth.data?.apiKey) { + requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/perplexity/index.js b/packages/backend/src/apps/perplexity/index.js new file mode 100644 index 00000000..ed401025 --- /dev/null +++ b/packages/backend/src/apps/perplexity/index.js @@ -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, +}); diff --git a/packages/backend/src/models/__snapshots__/app.test.js.snap b/packages/backend/src/models/__snapshots__/app.test.js.snap index 7fb9a297..4a9b7efb 100644 --- a/packages/backend/src/models/__snapshots__/app.test.js.snap +++ b/packages/backend/src/models/__snapshots__/app.test.js.snap @@ -42,6 +42,7 @@ exports[`App model > list should have list of applications keys 1`] = ` "ntfy", "odoo", "openai", + "perplexity", "pipedrive", "placetel", "postgresql", diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 1508b069..11fc33ec 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -359,6 +359,15 @@ export default defineConfig({ { text: 'Connection', link: '/apps/openai/connection' }, ], }, + { + text: 'Perplexity', + collapsible: true, + collapsed: true, + items: [ + { text: 'Actions', link: '/apps/perplexity/actions' }, + { text: 'Connection', link: '/apps/perplexity/connection' }, + ], + }, { text: 'Pipedrive', collapsible: true, diff --git a/packages/docs/pages/apps/perplexity/actions.md b/packages/docs/pages/apps/perplexity/actions.md new file mode 100644 index 00000000..114be044 --- /dev/null +++ b/packages/docs/pages/apps/perplexity/actions.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/perplexity.svg +items: + - name: Send chat prompt + desc: Generates a model's response for the given chat conversation. +--- + + + + diff --git a/packages/docs/pages/apps/perplexity/connection.md b/packages/docs/pages/apps/perplexity/connection.md new file mode 100644 index 00000000..5962f51f --- /dev/null +++ b/packages/docs/pages/apps/perplexity/connection.md @@ -0,0 +1,8 @@ +# Perplexity + +1. Go to [API page](https://www.perplexity.ai/settings/api) on Perplexity. +2. Generate a new API key. +3. Paste the key into the `API Key` field in Automatisch. +4. Write any screen name to be displayed in Automatisch. +5. Click `Save`. +6. Start using Perplexity integration with Automatisch! diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index 6f010074..33372e90 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -36,6 +36,7 @@ The following integrations are currently supported by Automatisch. - [Ntfy](/apps/ntfy/actions) - [Odoo](/apps/odoo/actions) - [OpenAI](/apps/openai/actions) +- [Perplexity](/apps/perplexity/actions) - [Pipedrive](/apps/pipedrive/triggers) - [Placetel](/apps/placetel/triggers) - [PostgreSQL](/apps/postgresql/actions) diff --git a/packages/docs/pages/public/favicons/perplexity.svg b/packages/docs/pages/public/favicons/perplexity.svg new file mode 100644 index 00000000..b27ffc98 --- /dev/null +++ b/packages/docs/pages/public/favicons/perplexity.svg @@ -0,0 +1 @@ + \ No newline at end of file