diff --git a/packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js b/packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js
new file mode 100644
index 00000000..8e983103
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/actions/create-chat-completion/index.js
@@ -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,
+ });
+ },
+});
diff --git a/packages/backend/src/apps/mistral-ai/actions/index.js b/packages/backend/src/apps/mistral-ai/actions/index.js
new file mode 100644
index 00000000..cc0e0532
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/actions/index.js
@@ -0,0 +1,3 @@
+import createChatCompletion from './create-chat-completion/index.js';
+
+export default [createChatCompletion];
diff --git a/packages/backend/src/apps/mistral-ai/assets/favicon.svg b/packages/backend/src/apps/mistral-ai/assets/favicon.svg
new file mode 100644
index 00000000..3f583306
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/assets/favicon.svg
@@ -0,0 +1,32 @@
+
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/mistral-ai/auth/index.js b/packages/backend/src/apps/mistral-ai/auth/index.js
new file mode 100644
index 00000000..429699eb
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/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: 'Mistral AI API key of your account.',
+ docUrl: 'https://automatisch.io/docs/mistral-ai#api-key',
+ clickToCopy: false,
+ },
+ ],
+
+ verifyCredentials,
+ isStillVerified,
+};
diff --git a/packages/backend/src/apps/mistral-ai/auth/is-still-verified.js b/packages/backend/src/apps/mistral-ai/auth/is-still-verified.js
new file mode 100644
index 00000000..3e6c9095
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/auth/is-still-verified.js
@@ -0,0 +1,6 @@
+const isStillVerified = async ($) => {
+ await $.http.get('/v1/models');
+ return true;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/mistral-ai/auth/verify-credentials.js b/packages/backend/src/apps/mistral-ai/auth/verify-credentials.js
new file mode 100644
index 00000000..7f43f884
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/auth/verify-credentials.js
@@ -0,0 +1,5 @@
+const verifyCredentials = async ($) => {
+ await $.http.get('/v1/models');
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/mistral-ai/common/add-auth-header.js b/packages/backend/src/apps/mistral-ai/common/add-auth-header.js
new file mode 100644
index 00000000..f9f5acba
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/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/mistral-ai/dynamic-data/index.js b/packages/backend/src/apps/mistral-ai/dynamic-data/index.js
new file mode 100644
index 00000000..6db48046
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/dynamic-data/index.js
@@ -0,0 +1,3 @@
+import listModels from './list-models/index.js';
+
+export default [listModels];
diff --git a/packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js b/packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js
new file mode 100644
index 00000000..a8e81538
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/dynamic-data/list-models/index.js
@@ -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 };
+ },
+};
diff --git a/packages/backend/src/apps/mistral-ai/index.js b/packages/backend/src/apps/mistral-ai/index.js
new file mode 100644
index 00000000..08f9e4b5
--- /dev/null
+++ b/packages/backend/src/apps/mistral-ai/index.js
@@ -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,
+});
diff --git a/packages/backend/src/models/__snapshots__/app.test.js.snap b/packages/backend/src/models/__snapshots__/app.test.js.snap
index 38324be3..346d917e 100644
--- a/packages/backend/src/models/__snapshots__/app.test.js.snap
+++ b/packages/backend/src/models/__snapshots__/app.test.js.snap
@@ -37,6 +37,7 @@ exports[`App model > list should have list of applications keys 1`] = `
"mailerlite",
"mattermost",
"miro",
+ "mistral-ai",
"notion",
"ntfy",
"odoo",
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 0fd2511b..dc16c3cd 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -313,6 +313,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/miro/connection' },
],
},
+ {
+ text: 'Mistral AI',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ { text: 'Actions', link: '/apps/mistral-ai/actions' },
+ { text: 'Connection', link: '/apps/mistral-ai/connection' },
+ ],
+ },
{
text: 'Notion',
collapsible: true,
diff --git a/packages/docs/pages/apps/mistral-ai/actions.md b/packages/docs/pages/apps/mistral-ai/actions.md
new file mode 100644
index 00000000..82f3207e
--- /dev/null
+++ b/packages/docs/pages/apps/mistral-ai/actions.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/mistral-ai.svg
+items:
+ - name: Create chat completion
+ desc: Creates a chat completion.
+---
+
+
+
+
diff --git a/packages/docs/pages/apps/mistral-ai/connection.md b/packages/docs/pages/apps/mistral-ai/connection.md
new file mode 100644
index 00000000..41201303
--- /dev/null
+++ b/packages/docs/pages/apps/mistral-ai/connection.md
@@ -0,0 +1,8 @@
+# Mistral AI
+
+1. Go to [Your API keys page](https://console.mistral.ai/api-keys/) on Mistral AI.
+2. Create 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 Mistral AI integration with Automatisch!
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 4be6447b..1e1d517b 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -31,6 +31,7 @@ The following integrations are currently supported by Automatisch.
- [MailerLite](/apps/mailerlite/triggers)
- [Mattermost](/apps/mattermost/actions)
- [Miro](/apps/miro/actions)
+- [Mistral AI](/apps/mistral-ai/actions)
- [Notion](/apps/notion/triggers)
- [Ntfy](/apps/ntfy/actions)
- [Odoo](/apps/odoo/actions)
diff --git a/packages/docs/pages/public/favicons/mistral-ai.svg b/packages/docs/pages/public/favicons/mistral-ai.svg
new file mode 100644
index 00000000..3f583306
--- /dev/null
+++ b/packages/docs/pages/public/favicons/mistral-ai.svg
@@ -0,0 +1,32 @@
+
+
\ No newline at end of file