diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..d1f0d08514 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" diff --git a/.prettierignore b/.prettierignore index 5f0516bbca..275dce31e8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ docs/docs/cli/reference.md build/ +bazel-* diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 4e5a49f6ce..0000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "bracketSpacing": false, - "singleQuote": true, - "jsxBracketSameLine": true, - "semi": true, - "trailingComma": "es5", - "printWidth": 120, - "overrides": [ - { - "files": "*.{ts,tsx}", - "options": { - "parser": "typescript" - } - }, - { - "files": "*.md", - "options": { - "singleQuote": false, - "quoteProps": "preserve", - "trailingComma": "none" - } - } - ] -} diff --git a/BUILD b/BUILD index b4c444fd97..c4b6f3cdba 100644 --- a/BUILD +++ b/BUILD @@ -1,4 +1,5 @@ load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") +load("@com_github_airyhq_bazel_tools//lint:prettier.bzl", "fix_prettier") load("@rules_java//java:defs.bzl", "java_library", "java_plugin") load("@io_bazel_rules_docker//container:container.bzl", "container_image") load("@com_github_atlassian_bazel_tools//multirun:def.bzl", "multirun") @@ -12,10 +13,15 @@ alias( actual = "//:bazel.tsconfig.json", ) +fix_prettier( + name = "fix_prettier", + ignore = "//:.prettierignore", +) + multirun( name = "fix", commands = [ - "@com_github_airyhq_bazel_tools//lint:fix_prettier", + ":fix_prettier", "@com_github_airyhq_bazel_tools//lint:fix_buildifier", ], visibility = ["//visibility:public"], diff --git a/README.md b/README.md index 8d88711076..0e91801169 100644 --- a/README.md +++ b/README.md @@ -13,26 +13,25 @@ [![License](https://img.shields.io/github/license/airyhq/airy)](https://github.com/airyhq/airy/blob/develop/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/airyhq/airy/projects) - Airy Core is an open source, fully-featured, production ready messaging platform. With Airy you can process conversational data from a variety of sources: - - **Facebook** - - **WhatsApp** - - **Google's Business Messages** - - **SMS** - - **Website Chat Plugins** - - **Twilio** - - **Your own conversational channels** +- **Facebook** +- **WhatsApp** +- **Google's Business Messages** +- **SMS** +- **Website Chat Plugins** +- **Twilio** +- **Your own conversational channels** You can then use Airy to: - - **Unify your messaging channels** - - **Stream your conversational data wherever you want** - - **Integrate with different NLP frameworks** - - **Mediate open requests with Agents via our messaging UI** - - **Analyze your conversations** +- **Unify your messaging channels** +- **Stream your conversational data wherever you want** +- **Integrate with different NLP frameworks** +- **Mediate open requests with Agents via our messaging UI** +- **Analyze your conversations** Since Airy's infrastructure is built around Apache Kafka, it can process a large amount of conversations and messages simultaneously and stream the relevant diff --git a/VERSION b/VERSION index d9df1bbc0c..ac454c6a1f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.0 +0.12.0 diff --git a/WORKSPACE b/WORKSPACE index 94a8823417..1017cc10fc 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -9,9 +9,9 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") # Airy Bazel tools git_repository( name = "com_github_airyhq_bazel_tools", - commit = "da1aa5af0f03767d08ed8af478248d3709342252", + commit = "9cf55cc0a42d68f0198883b04493ce7c7b3113ed", remote = "https://github.com/airyhq/bazel-tools.git", - shallow_since = "1614184856 +0100", + shallow_since = "1614958086 +0100", ) load("@com_github_airyhq_bazel_tools//:repositories.bzl", "airy_bazel_tools_dependencies", "airy_jvm_deps") @@ -130,6 +130,10 @@ load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_depe go_rules_dependencies() +load("@io_bazel_rules_go//extras:embed_data_deps.bzl", "go_embed_data_dependencies") + +go_embed_data_dependencies() + go_register_toolchains(nogo = "@//:airy_nogo") # my_nogo is in the top-level BUILD file of this workspace load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java index 0ff6ceb3f3..dbb2e3eb86 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/TemplatesController.java @@ -27,11 +27,10 @@ @RestController public class TemplatesController { private final Stores stores; - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper = new ObjectMapper(); - public TemplatesController(Stores stores, ObjectMapper objectMapper) { + public TemplatesController(Stores stores) { this.stores = stores; - this.objectMapper = objectMapper; } @PostMapping("/templates.create") @@ -39,7 +38,7 @@ ResponseEntity createTemplate(@RequestBody @Valid CreateTemplateRequestPayloa final Template template = Template.newBuilder() .setId(UUID.randomUUID().toString()) .setName(payload.getName()) - .setContent(payload.getContent()) + .setContent(objectMapper.writeValueAsString(payload.getContent())) .setVariables(objectMapper.writeValueAsString(payload.getVariables())) .build(); @@ -89,14 +88,23 @@ ResponseEntity listTemplates(@RequestBody @Valid ListTemplatesRequestPayload @PostMapping("/templates.update") ResponseEntity updateTemplate(@RequestBody @Valid UpdateTemplateRequestPayload payload) throws JsonProcessingException { - final Template template = stores.getTemplate(payload.getName()); + final Template template = stores.getTemplate(payload.getId().toString()); if (template == null) { return ResponseEntity.notFound().build(); } - template.setContent(payload.getName()); - template.setContent(objectMapper.writeValueAsString(payload.getContent())); + if (payload.getName() != null) { + template.setName(payload.getName()); + } + + if (payload.getContent() != null) { + template.setContent(objectMapper.writeValueAsString(payload.getContent())); + } + + if (payload.getVariables() != null) { + template.setVariables(objectMapper.writeValueAsString(payload.getVariables())); + } try { stores.storeTemplate(template); diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java index 2baf16be26..2c462fb27f 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/CreateTemplateRequestPayload.java @@ -15,7 +15,7 @@ public class CreateTemplateRequestPayload { @NotNull private String name; @NotNull - private String content; + private JsonNode content; @Valid @NotNull private JsonNode variables; diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java index 509753ec8a..a5e52e4dbc 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/payload/UpdateTemplateRequestPayload.java @@ -15,9 +15,7 @@ public class UpdateTemplateRequestPayload { @NotNull private UUID id; - @NotNull private String name; - @Valid - @NotNull private JsonNode content; + private JsonNode variables; } diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java b/backend/api/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java index 54f2eefaa4..30763d2eca 100644 --- a/backend/api/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java +++ b/backend/api/admin/src/test/java/co/airy/core/api/admin/TemplatesControllerTest.java @@ -78,8 +78,8 @@ void beforeEach() throws Exception { @Test void canManageTemplates() throws Exception { final String name = "awesome-template"; - final String content = "{\\\"blueprint\\\":\\\"text\\\",\\\"payload\\\":\\\"[[salutation]]!\\\"}"; - final String payload = "{\"name\":\"" + name + "\",\"content\": \"" + content + "\", \"variables\": { \"en\": {\"salutation\": \"Hello\"}}}"; + final String content = "{\"blueprint\":\"text\",\"payload\":\"[[salutation]]!\"}"; + final String payload = "{\"name\":\"" + name + "\",\"content\":" + content + ",\"variables\": { \"en\": {\"salutation\": \"Hello\"}}}"; final String createTagResponse = webTestHelper.post("/templates.create", payload, "user-id") .andExpect(status().isCreated()) @@ -98,7 +98,7 @@ void canManageTemplates() throws Exception { webTestHelper.post("/templates.info", "{\"id\":\"" + templateId + "\"}", "user-id") .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(is(templateId))) - .andExpect(jsonPath("$.content").value(is("{\"blueprint\":\"text\",\"payload\":\"[[salutation]]!\"}"))) + .andExpect(jsonPath("$.content").value(is(content))) .andExpect(jsonPath("$.variables.en.salutation").value(is("Hello"))) .andExpect(jsonPath("$.name").value(is(name))); @@ -108,6 +108,17 @@ void canManageTemplates() throws Exception { .andExpect(jsonPath("$.data[0].id").value(is(templateId))) .andExpect(jsonPath("$.data[0].name").value(is(name))); + webTestHelper.post("/templates.update", "{\"id\":\"" + templateId + "\", \"name\": \"new-template-name\", \"content\": " + content + "}", "user-id") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(is(templateId))) + .andExpect(jsonPath("$.content").value(is(content))) + .andExpect(jsonPath("$.variables.en.salutation").value(is("Hello"))) + .andExpect(jsonPath("$.name").value(is("new-template-name"))); + + webTestHelper.post("/templates.info", "{\"id\":\"" + templateId + "\"}", "user-id") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(is("new-template-name"))); + webTestHelper.post("/templates.list", "{}", "user-id") .andExpect(status().isOk()) .andExpect(jsonPath("$.data.length()", is(1))); diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index a8a534a616..d4e4f04548 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -3,16 +3,6 @@ load("@com_github_airyhq_bazel_tools//lint:prettier.bzl", "prettier") prettier( name = "prettier", - srcs = glob([ - "**/*.md", - "**/*.js", - "**/*.jsx", - "**/*.ts", - "**/*.tsx", - "**/*.scss", - "**/*.css", - ]), - config = "//:.prettierrc.json", ignore = "//:.prettierignore", ) diff --git a/docs/cli-doc/BUILD b/docs/cli-doc/BUILD index 271bb996b1..db92bc52a8 100644 --- a/docs/cli-doc/BUILD +++ b/docs/cli-doc/BUILD @@ -15,7 +15,7 @@ go_library( "ProjectDir": "{PROJECT_DIR}", }, deps = [ - "//infrastructure/cli/cmd", + "//infrastructure/cli/pkg/cmd", "@com_github_spf13_cobra//:cobra", ], ) diff --git a/docs/cli-doc/generate_cli_docs.go b/docs/cli-doc/generate_cli_docs.go index 4026bdb587..fb063d80e6 100644 --- a/docs/cli-doc/generate_cli_docs.go +++ b/docs/cli-doc/generate_cli_docs.go @@ -2,7 +2,7 @@ package main import ( "bufio" - "cli/cmd" + "cli/pkg/cmd" "os" "path/filepath" "sort" diff --git a/docs/docs/api/endpoints/templates.md b/docs/docs/api/endpoints/templates.md index 83205b6962..01a72ffc90 100644 --- a/docs/docs/api/endpoints/templates.md +++ b/docs/docs/api/endpoints/templates.md @@ -12,13 +12,18 @@ for more information. **Sample request** -```json +```json5 { - "name": "my template name", - "content": "{\"blueprint\":\"text\",\"payload\":\"[[salutation]]!\"}", - "variables": { - "en": { - "salutation": "%s" + name: "my template name", + content: { + message: { + text: "[[salutation]]!" + } + }, + // source specific message content + variables: { + en: { + salutation: "%s" } } } @@ -28,7 +33,7 @@ for more information. ```json5 { - "id": "TEMPLATE-UUID" + id: "TEMPLATE-UUID" } ``` @@ -46,7 +51,7 @@ Find templates whose name contains "NASA": ```json5 { - "name": "NASA" //optional + name: "NASA" // optional } ``` @@ -54,14 +59,19 @@ Find templates whose name contains "NASA": ```json5 { - "data": [ + data: [ { - "id": "TEMPLATE-UUID", - "name": "Template for NASA", - "content": '{"blueprint":"text","payload":"[[salutation]]!"}', - "variables": { - "en": { - "salutation": "%s" + id: "TEMPLATE-UUID", + name: "Template for NASA", + content: { + message: { + text: "[[salutation]]!" + } + }, + // source specific message content + variables: { + en: { + salutation: "%s" } } } @@ -75,7 +85,7 @@ Find templates whose name contains "NASA": ```json5 { - "id": "TEMPLATE-UUID" + id: "TEMPLATE-UUID" } ``` @@ -83,12 +93,17 @@ Find templates whose name contains "NASA": ```json5 { - "id": "TEMPLATE-UUID", - "name": "{String}", - "content": "{String}", - "variables": { - "en": { - "salutation": "%s" + id: "TEMPLATE-UUID", + name: "{String}", + content: { + message: { + text: "[[salutation]]!" + } + }, + // source specific message content + variables: { + en: { + salutation: "%s" } } } @@ -100,12 +115,17 @@ Find templates whose name contains "NASA": ```json5 { - "id": "template id", - "name": "my template name", - "content": '{"blueprint":"text","payload":"[[salutation]]!"}', - "variables": { - "en": { - "salutation": "%s" + id: "template id", + name: "my template name", + content: { + message: { + text: "[[salutation]]!" + } + }, + // source specific message content + variables: { + en: { + salutation: "%s" } } } @@ -121,7 +141,7 @@ This endpoint returns _200_ if successful. ```json5 { - "id": "TEMPLATE-UUID" + id: "TEMPLATE-UUID" } ``` diff --git a/docs/docs/api/introduction.md b/docs/docs/api/introduction.md index 251336cc51..8919878034 100644 --- a/docs/docs/api/introduction.md +++ b/docs/docs/api/introduction.md @@ -6,9 +6,9 @@ sidebar_label: Introduction import TLDR from "@site/src/components/TLDR"; import ButtonBoxList from "@site/src/components/ButtonBoxList"; import ButtonBox from "@site/src/components/ButtonBox"; -import BoltSVG from "@site/static/icons/bolt.svg"; -import ServerStackSVG from "@site/static/icons/server-stack.svg"; -import WebhooksSVG from "@site/static/icons/webhooks.svg"; +import HighVoltageSVG from "@site/static/icons/high-voltage.svg"; +import ElectricPlugSVG from "@site/static/icons/electric-plug.svg"; +import FishingPoleSVG from "@site/static/icons/fishing-pole.svg"; @@ -27,19 +27,19 @@ interacting with data: } + icon={() => } title='HTTP API' description='Access your conversational data with blazing fast HTTP endpoints' link='api/endpoints/introduction' /> } + icon={() => } title='WebSocket Server' description='Power real-time applications with STOMP style WebSocket' link='api/websocket' /> } + icon={() => } title='Webhook' description='Participate programmatically in conversations by listening to events' link='api/webhook' diff --git a/docs/docs/apps/ui/introduction.md b/docs/docs/apps/ui/introduction.md index f92e0ee9f2..0a951e0246 100644 --- a/docs/docs/apps/ui/introduction.md +++ b/docs/docs/apps/ui/introduction.md @@ -6,9 +6,9 @@ sidebar_label: Introduction import useBaseUrl from '@docusaurus/useBaseUrl'; import ButtonBoxList from "@site/src/components/ButtonBoxList"; import ButtonBox from "@site/src/components/ButtonBox"; -import CogSVG from "@site/static/icons/cog.svg"; -import InboxSVG from "@site/static/icons/prototype.svg"; -import TagsSVG from "@site/static/icons/price-tag.svg"; +import GearSVG from "@site/static/icons/gear.svg"; +import DesktopComputerSVG from "@site/static/icons/desktop-computer.svg"; +import LabelSVG from "@site/static/icons/label.svg"; import UsersSVG from "@site/static/icons/users.svg"; import ComponentsSVG from "@site/static/icons/information-architecture.svg"; @@ -21,19 +21,19 @@ Additional features like [Filters, Search](inbox) and [Tags](tags) help you. } + icon={() => } title='UI Quickstart' description='Step by Step Guide on getting up and running with the UI' link='apps/ui/quickstart' /> } + icon={() => } title='Inbox' description='One inbox to see all your conversations & respond to them' link='apps/ui/inbox' /> } + icon={() => } title='Tags' description='Tag your conversations for easy filtering, searching & segmenting' link='apps/ui/tags' diff --git a/docs/docs/cli/reference.md b/docs/docs/cli/reference.md index 40b3700832..0a44465172 100644 --- a/docs/docs/cli/reference.md +++ b/docs/docs/cli/reference.md @@ -94,8 +94,9 @@ airy create [flags] #### Options ``` - -h, --help help for create - --provider string One of the supported providers (aws|local). Default is aws + -h, --help help for create + --namespace string (optional) Kubernetes namespace that Airy should be installed to. (default "default") + --provider string One of the supported providers (aws|local|minikube). (default "local") ``` #### Options inherited from parent commands diff --git a/docs/docs/getting-started/installation/introduction.md b/docs/docs/getting-started/installation/introduction.md index 840d9946eb..b71681a6f2 100644 --- a/docs/docs/getting-started/installation/introduction.md +++ b/docs/docs/getting-started/installation/introduction.md @@ -8,8 +8,8 @@ import TLDR from "@site/src/components/TLDR"; import ButtonBoxList from "@site/src/components/ButtonBoxList"; import ButtonBox from "@site/src/components/ButtonBox"; import VagrantSVG from "@site/static/icons/vagrant.svg"; -import ProductionSVG from "@site/static/icons/cloud.svg"; -import DeploySVG from "@site/static/icons/deploy.svg"; +import KafkaSVG from "@site/static/icons/kafka.svg"; +import RocketSVG from "@site/static/icons/rocket.svg"; @@ -26,12 +26,11 @@ easy to install and works on macOS, Windows, and Linux. } +icon={() => } title='CLI' description='Install the Airy Core CLI application' link='/cli/installation' /> - } title='Local test environment with Vagrant' @@ -39,7 +38,7 @@ description='Step by step guide to run Airy Core on your local machine' link='getting-started/installation/vagrant' /> } +icon={() => } title='Production ready environment with Kafka' description='Manual step by step guide for running Airy Core in production' link='getting-started/installation/production' diff --git a/docs/docs/getting-started/installation/production.md b/docs/docs/getting-started/installation/production.md index 5d4618ad1d..af7b5fc5be 100644 --- a/docs/docs/getting-started/installation/production.md +++ b/docs/docs/getting-started/installation/production.md @@ -3,6 +3,12 @@ title: Production sidebar_label: Production --- +import TLDR from "@site/src/components/TLDR"; + + +Run Airy Core in a production environment with Kafka + + This document provides our recommendations on how to run the Airy Core in production environments. If you are not familiar with the architecture of the system, we suggest you read the [Architecture](/concepts/architecture.md) document before diff --git a/docs/docs/getting-started/introduction.md b/docs/docs/getting-started/introduction.md index afa7edc9a4..c3ff5d2e8a 100644 --- a/docs/docs/getting-started/introduction.md +++ b/docs/docs/getting-started/introduction.md @@ -7,8 +7,13 @@ slug: / import TLDR from "@site/src/components/TLDR"; import ButtonBoxList from "@site/src/components/ButtonBoxList"; import ButtonBox from "@site/src/components/ButtonBox"; -import DeploySVG from "@site/static/icons/deploy.svg"; +import RocketSVG from "@site/static/icons/rocket.svg"; import DiamondSVG from "@site/static/icons/diamond.svg"; +import SpeechBalloonSVG from "@site/static/icons/speech-balloon.svg"; +import HighVoltageSVG from "@site/static/icons/high-voltage.svg"; +import ElectricPlugSVG from "@site/static/icons/electric-plug.svg"; +import FishingPoleSVG from "@site/static/icons/fishing-pole.svg"; +import GearSVG from "@site/static/icons/gear.svg"; ## What is Airy Core? @@ -44,7 +49,7 @@ conversational data to wherever you need it. } + icon={() => } title='Start Installation' description='Install Airy with our CLI, locally or in the cloud' link='cli/installation' @@ -73,16 +78,41 @@ We'll guide you through the following journey: The platform contains the following core components: -- An ingestion platform that heavily relies on Apache Kafka to process incoming - webhook data from different sources. We make sense of the data and reshape it - into source independent contacts, conversations, and messages (see our - glossary for formal definitions). - -- An HTTP API that allows you to manage the data sets the platform handles. - -- A webhook integration server that allows you to programmatically participate - in conversations by sending messages. The webhook integration exposes events - you can "listen" to and react programmatically. - -- A WebSocket server that allows you to receive near real-time updates about the - data flowing through the system. + + } + title='Connectors for all conversational sources' + description="Connect anything from our free open-source live chat plugin, Facebook Messenger, Google's Business Messages to your Airy Core. This is all possible through an ingestion platform that heavily relies on Apache Kafka to process incoming webhook data from different sources. We make sense of the data and reshape it into source independent contacts, conversations, and messages." + link='/sources/introduction' +/> + } + title='APIs to access your data' + description="An API to access conversational data with blazing fast HTTP endpoints." + link='/api/endpoints/introduction' +/> + } + title='WebSockets to power real-time applications' + description="A WebSocket server that allows clients to receive near real-time updates about data flowing through the system." + link='/api/websocket' +/> + } + title='Webhook integration to connect custom apps' + description="A webhook integration server that allows its users to programmatically participate in conversations by sending messages (the webhook integration exposes events users can listen to and react programmatically.)" + link='/api/webhook' +/> + } + title='UI: From an inbox to dashboards' + description="Not every message can be handled by code, this is why Airy comes with different UIs ready for you and your teams to use." + link='/apps/ui/introduction' +/> + } + title='Integrations' + description="Pre-made integrations into popular conversational tools, for example NLP tools like Rasa" + link='/integrations/rasa' +/> + diff --git a/docs/docs/getting-started/quickstart.md b/docs/docs/getting-started/quickstart.md index df5f6fb305..760375a472 100644 --- a/docs/docs/getting-started/quickstart.md +++ b/docs/docs/getting-started/quickstart.md @@ -5,6 +5,8 @@ sidebar_label: Quickstart import useBaseUrl from '@docusaurus/useBaseUrl'; import TLDR from "@site/src/components/TLDR"; +import ButtonBox from "@site/src/components/ButtonBox"; +import RocketSVG from "@site/static/icons/rocket.svg"; @@ -22,13 +24,12 @@ directly in Apache Kafka. - [Step 3: Use the HTTP API to list conversations](#step-3-use-the-http-api-to-list-conversations) - [Step 4: Consume directly from Apache Kafka](#step-4-consume-directly-from-apache-kafka) -:::info - -To get going with the Quickstart, [you must install Airy -first](/getting-started/installation/introduction.md). Once you installed Airy and have the -CLI up and running you are good to go. - -::: + } +title='Did you already install the Airy CLI?' +description='To get going with the Quickstart, you must install Airy first. Once the CLI is up and running you are good to go.' +link='/getting-started/installation/introduction' +/> ## Step 1: How to setup your first source diff --git a/docs/docs/sources/chat-plugin.md b/docs/docs/sources/chat-plugin.md index 22c3dbd6c5..565a212576 100644 --- a/docs/docs/sources/chat-plugin.md +++ b/docs/docs/sources/chat-plugin.md @@ -30,7 +30,7 @@ the `` section: ```html