diff --git a/apps/default/main.py b/apps/default/main.py
index ce674c987..a20e88414 100644
--- a/apps/default/main.py
+++ b/apps/default/main.py
@@ -23,14 +23,14 @@ def increment(state):
print("The counter has been incremented.")
_update_message(state)
-# Initialise the state
+# Initialize the state
-# "_my_private_element" won't be serialised or sent to the frontend,
+# "_my_private_element" won't be serialized or sent to the frontend,
# because it starts with an underscore
initial_state = wf.init_state({
"my_app": {
- "title": "MY APP"
+ "title": "My app"
},
"_my_private_element": 1337,
"message": None,
diff --git a/docs/framework/chat-assistant.mdx b/docs/framework/chat-assistant.mdx
index 14159b269..ae8110599 100644
--- a/docs/framework/chat-assistant.mdx
+++ b/docs/framework/chat-assistant.mdx
@@ -2,153 +2,394 @@
title: "Chat assistant"
---
-In this introductory tutorial, you'll use the Writer Framework to build an AI chat assistant.
+In this tutorial, you'll use the Writer Framework to create a simple yet powerful chat assistant that can engage in conversations on various topics, provide answers to your questions, and maybe even help you when you're experiencing writer's block!
+
+The process will take only minutes using a drag-and-drop visual editor to build the user interface and Python for the back-end code.
+
+Here's what the finished project will look like:
![Finished chat assistant project](/framework/images/tutorial/chat/chat_assistant_1.png)
+
+## Prerequisites
+
+Before starting, ensure you have:
+
+- **A Writer account:** You don't need an account to use Writer Framework, but you'll need one to use the AI module. [Fortunately, you can sign up for an account for free!](https://app.writer.com/aistudio/signup)
+- **Python 3.9.2 or later**: Use the installer from [python.org](https://www.python.org/downloads/).
+- **pip:** This command-line application comes with Python and is used for installing Python packages, including those from Writer.
+- **A basic understanding of Python:** You should be familiar with the basics of the language.
+- **Your favorite code editor (optional):** There's a code editor built into Writer for editing back-end code, but you can also use Visual Studio Code, Notepad++, Vim, Emacs, or any text editor made for programming if you prefer.
+
+
## Setting up your project
-### Creating a Writer app and getting your API key
+### Create a Writer app and get its API key
-From the Home screen, click on **Build an app**.
+First, you'll need to create a new app within Writer.
-![Writer home screen](/framework/images/tutorial/chat/chat_assistant_2.png)
+
+
+ Log into Writer. From the Home screen, click on the **Build an app** button.
-Select Framework as the app type you’d like to create, enabling you to generate keys and build your app with the Writer Framework.
+ ![Writer home screen](/framework/images/tutorial/chat/chat_assistant_2.png)
-![App type selection](/framework/images/tutorial/chat/chat_assistant_3.png)
+ The **Start building** menu will appear, presenting options for the types of apps you can create.
-On the next screen, you can edit your Writer application name in the upper left. Underneath “Authenticate with an API key,” click on **Reveal** to see and copy your API key.
+ Select **Framework**, located under **Developer tools**. This will create a brand new app based on Writer Framework.
+ !["Start building" menu](/framework/images/tutorial/chat/chat_assistant_3.png)
+
-### Creating the application
+
+ On the next screen, titled **How to deploy an application**, you can get the API key for the app by clicking on the **Reveal key** button, located under the text **Authenticate with an API key**. Your complete API key will be displayed, and a "copy" button will appear. Click this button to copy the key; you'll use it in the next step.
-Next, open your terminal and navigate to the directory where you want to create your application directory.
+ !["How to deploy an application" page](/framework/images/tutorial/chat/chat_assistant_2a.png)
+
+
-
-
- To pass your API key to the Writer Framework, you need to set an environment variable called `WRITER_API_KEY`. One simple way to do this is by exporting the variable for your terminal session.
+### Set up your computer and create the app's project
-
- ```sh On macOS/Linux
- export WRITER_API_KEY=[key]
- ```
+The next step is to set up the Writer Framework environment on your computer. You'll do this by creating a directory for the project, installing dependencies, and creating the project for the application using a template.
- ```sh On Windows
- set WRITER_API_KEY=[key]
- ```
-
+
+
+ Open your terminal application. On macOS and Linux, this application goes by the name _Terminal_; on Windows, you can use either _Windows PowerShell_ (which is preferred) or _Command Prompt_.
-
- Run the following command to create your application, replacing `chat-assistant` with your desired project name and `ai-starter` with the template you want to use:
- ```bash
- writer create chat-assistant --template=ai-starter
- ```
+
+ If you already have the `writer` and `python-dotenv` packages installed on your computer, you can skip this step.
+
+ Install the `writer` and `python-dotenv` packages by entering the following commands in your terminal application:
+
+ ```
+ pip install writer python-dotenv
+ ```
- This command sets up a new project called `chat-assistant` in the specified directory.
+ This command tells `pip`, the Python package installer, to install two packages:
+
+ - `writer`, which provides some command-line commands and enables Python code to interact with Writer and the Writer Framework.
+ - `python-dotenv`, which makes it easy to manage environment variables by loading them from a `.env` file. This one is optional for this exercise, but you might find it useful when working on larger projects.
-
- To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:4005`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup.
+
+
+ To pass your API key to the Writer Framework, you need to set an environment variable called `WRITER_API_KEY`.
+
+ Select your operating system and terminal application below, then copy and paste the command into your terminal application, replacing `[your_api_key]` with the API key you copied earlier:
- ```bash Standard port
- writer edit chat-assistant
+ ```sh macOS/Linux (Terminal)
+ export WRITER_API_KEY=[your_api_key]
```
- ```bash Custom port
- writer edit chat-assistant –port=3007
+ ```sh On Windows (Windows PowerShell)
+ $env:WRITER_API_KEY=[your_api_key]
+ ```
+
+ ```sh On Windows (Command Prompt)
+ set WRITER_API_KEY=[your_api_key]
```
+ The `WRITER_API_KEY` environment variable will remain defined as long your terminal session is open (that is, until you close your terminal application’s window).
-
+
+ Create the project by entering this command into your terminal application:
+ ```
+ writer create chat-assistant --template=ai-starter
+ ```
+
+ This command sets up a new project called `chat-assistant` using a starter template called `ai-starter` so that you're not starting "from scratch."
+
+
-## Creating the UI
-The Writer Framework lets you set up any layout according to your preferences with a fast drag-and-drop UI.
+## Build the UI
-To rename this application and update the Header component, open the code editor and update `my_app.title` in `wf.init_state`:
+Now that you've created the project, it's time to define the UI. The Writer Framework's drag-and-drop capabilities make it easy — even if you haven't done much UI work before!
-```python
-wf.init_state({
- "my_app": {
- "title": "CHAT ASSISTANT"
- }
-})
+The project editor is a web application that runs on your computer and enables you to define and edit your app's user interface. Launch it by typing the following into your terminal application:
+
+```
+writer edit chat-assistant
```
-Click the provided Section component to open its Component settings and clear out the default title. If you’d like to provide any other instructions or context to the user, you can also drag a Text component into the section.
+You'll see a URL. Control-click it (command-click on macOS) to open it, or copy the URL and paste it into the address bar of a browser window.
-Finally, drag a Chatbot component into the Section beneath the Text box.
+The browser window will contain the project editor, which will look like this:
-![Initial UI with text and chatbot](/framework/images/tutorial/chat/chat_assistant_4.png)
+![Project editor](/framework/images/tutorial/chat/chat_assistant_2b.png)
+You'll see the following:
-## Updating the code
+- The **canvas** is in the center. It displays the app's user interface.
+- The column on the left contains:
+ - The **Core toolkit**, which contains all the UI components. You define the user interface by dragging components from the Toolkit and placing them on the canvas.
+ - The **Component tree**, which shows the arrangement of the UI components on the canvas. It's also useful for selecting items on the canvas, especially when it has a lot of UI components.
-With the UI built, you can now update your code to add chat functionality.
+It's time to build the UI!
-
- First, clear any default-generated state and add a `conversation` property set to `writer.ai.Conversation()`. Update your `initial_state` as follows:
-
- ```python
- wf.init_state({
- // Other state elements above
- "conversation": writer.ai.Conversation()
- })
- ```
+
+ Select the **Header** component by clicking it — it's the component at the top, containing the title **AI STARTER** and a gray area labeled **Empty Header**.
+
+ When you click it, you'll see the **properties** panel appear on the right side of the page. This lets you view and edit the properties of the selected component.
+
+ ![The selected header and its properties panel](/framework/images/tutorial/chat/chat_assistant_2c.png)
+
+ The first property you'll see in the panel is the **Text** property, which defines the text that appears as the header's title. It should contain the value `@{my_app.title}`. The `@{` and `}` indicate that `my_app.title` is a variable and that its contents should be the text displayed instead of the literal text "my_app.title". You'll set the value of this variable soon.
+
+
+
+ Select the **Section** component by clicking it — it's just below the **Header** component and contains the title **Section Title** and a gray area labeled **Empty Section**.
+
+ In the **properties** panel, clear out the value of the **Title** property. This will remove the **Section**'s default title.
+
+ ![The selected section and its properties panel](/framework/images/tutorial/chat/chat_assistant_2d.png)
+
+
+
+ Drag a **Text** component from the **Core toolkit** panel on the left (it's under **Content**, and you may need to scroll down a little to find it) and into the *Section*. Sections can act as containers for other components.
- The `Conversation` method can optionally accept a dictionary or a content prompt (e.g., “You are a social media expert in the financial services industry”), but it can also be left empty to use the defaults.
+ You can search for a specific component by using the search bar at the top of the **Core toolkit** panel.
+
+ Select the **Text** component. In the **properties** panel, set the **Text** property to provide instructions or context for your chat assistant. Here's an example: `Welcome to the Chat Assistant. Ask me anything!`
+
+ ![The text component and its properties panel](/framework/images/tutorial/chat/chat_assistant_2e.png)
-
- Next, create a handler for incoming messages by adding the `handle_simple_message` handler. This method will manage the chat interactions:
- ```python
- def handle_simple_message(state, payload):
- state["conversation"] += payload
-
- for chunk in state["conversation"].stream_complete():
+
+ The heart of this app is the **Chatbot** component, a pre-built component that displays the conversation between the LLM and the user and provides a text field where the user can enter prompts.
+
+ Drag a **Chatbot** component from the **Core toolkit** panel (it's under **Content**) into the *Section*, just below the Text box.
+
+ ![The chatbot component](/framework/images/tutorial/chat/chat_assistant_2f.png)
+
+
+
+
+## Add the back-end code
+
+With the UI laid out, it's time to work on the logic behind it.
+
+The logic behind the user interface is defined in a file named `main.py`, which is in your project's directory. This file was automatically generated; you'll update the code in it to define the behavior of your app.
+
+The simplest way to edit `main.py` is within the project editor. Click on the "toggle code" button (beside the word **Code**) near the lower left corner of the project editor page.
+
+![Project editor with arrow pointing to toggle code button](/framework/images/tutorial/chat/chat_assistant_2g.png)
+
+A pane with the name **Code** will appear at the bottom half of the screen, displaying an editor for the the contents of `main.py`.
+
+![Project editor with the code editor displayed](/framework/images/tutorial/chat/chat_assistant_2h.png)
+
+If you'd rather use a code editor instead of coding in the browser, use it to open the `main.py` file in your project's directory.
+
+Now follow these steps:
+
+
+
+ You should see the following at the start of the file:
+
+ ```python
+ import writer as wf
+ import writer.ai
+ ```
+
+ Replace that code with the following:
+
+ ```python
+ import os
+ import writer as wf
+ import writer.ai
+
+ # Set the API key
+ wf.api_key = os.getenv("WRITER_API_KEY")
+ ```
+
+ This code imports the libraries that the application will need and then reads your Writer Framework API key in the `WRITER_API_KEY` environment variable.
+
+
+
+ The application needs a function to handle incoming chat messages. Find these comments in the code...
+
+ ```python
+ # Welcome to Writer Framework!
+ # This template is a starting point for your AI apps.
+ # More documentation is available at https://dev.writer.com/framework
+ ```
+
+ ...and replace them with the following function:
+
+ ```python
+ def generate_completion(state, payload):
+ print(f"Here's what the user entered: {payload['content']}")
+ state["conversation"] += payload
+ print(f"Conversation: {state['conversation'].messages}")
+ try:
+ for index, chunk in enumerate(state["conversation"].stream_complete()):
+ print(f"Chunk {index}: {chunk}")
+ if not chunk.get("content"):
+ chunk["content"] = ""
state["conversation"] += chunk
+
+ print(f"state['conversation']:\n{state['conversation'].messages}")
+ except Exception as e:
+ print(f"Error during stream_complete: {e}")
```
- This code uses the streaming function of the `Conversation` method, which is a wrapper for the `chat` API endpoint. Each chunk returned from the stream is added to the `conversation` variable in the application state.
+ The `generate_completion()` function will be called when the user enters a prompt, which is contained in the `payload` object. The `payload` object is added to the `conversation` object contained in the application's `state`, which adds the user's prompt to the record of the conversation between the user and the LLM.
+
+ After adding the user's prompt to the conversational record, `generate_completion()` calls the `conversation` object's `stream_complete()` method, which generates an LLM completion based on the conversation so far. As its name implies, `stream_complete()` returns the completion as a stream of text chunks, which are captured and added to the `conversation` object.
+
+ The `conversation` object in the code above is an instance of Writer’s `Conversation` class. You can find out more about this class on our [_Writer AI module_](https://dev.writer.com/framework/ai-module) page.
+
+ Note that `generate_completion()` completion uses a lot of `print()` functions for debugging purposes, and you can use them to get a better idea of what's happening in the function. You'll see their output in both your terminal application and in the project editor's 'log' pane (which will be covered shortly) as you use the chat assistant. This output will include:
+
+ - The prompt the user entered
+ - The chunks of data that make up the LLM's response as they are generated
+ - The record of the conversation between the user and the LLM.
+
+ The `print()` functions don't affect the operation of the chat assistant in any way, and you can remove them if you wish.
+
+
+
+ The final step is to set the application's initial state. Find this code, which should be just after the `generate_completion()` function...
+
+ ```python
+ # Initialise the state
+ wf.init_state({
+ "my_app": {
+ "title": "AI STARTER"
+ },
+ })
+ ```
+
+ ...and replace it with this:
+
+ ```python
+ # Initialize the state
+ wf.init_state({
+ "conversation": writer.ai.Conversation(),
+ "my_app": {
+ "title": "CHAT ASSISTANT"
+ },
+ })
+ ```
+
+ The Writer Framework's `init_state()` method sets the initial value of `state`, a dictionary containing values that define the state of the application. The key-value pairs in `state` are how you store values used by your app and how you pass data between the back-end code and the UI.
+
+ The code above sets the initial value of `state` so that it has two key-value pairs:
+
+ - `conversation`: An object that keeps a record of the conversation that the user is having with the LLM. You'll bind its value to the **Chatbot** component soon.
+ - `my_app`: A dictionary containing values that define the application's appearance. This dictionary has a single key-value pair, `title`, which defines the text that appears as the application's title in the **Header**.
+
+ For more details about the `state` variable, see our [_Application state_](https://dev.writer.com/framework/application-state#application-state) page.
+
+
+
+ That’s all the code. If you edited the code in the browser, save it by clicking the “save” button near the top right corner of the code editor.
+
+ ![Project editor and code editor, with arrow pointing to save button](/framework/images/tutorial/chat/chat_assistant_2i.png)
+
+ Click the "toggle code" button to hide the code editor.
+## Bind the UI to the back-end code
-## Binding to the UI
+You've built the UI and written the code behind it. Let's connect the two! Go back to the browser window with the project editor and do the following:
-Click on the **chatbot component** to open up the Component settings panel. Bind this chatbot to a conversation variable by adding `@{conversation}` in the Conversation Object property under General. This variable will reference the Writer AI SDK. You can also update properties such as the assistant's initials, the user's initials, and whether the chat uses markdown.
+
+
+ Earlier, you saw that the **Header** component's **Text** property was set to `@{my_app.title}`, a value in the app's `state` variable. You changed this value when you update the call to the Writer Framework's `init_state()` method.
+
-![Finished chat assistant project](/framework/images/tutorial/chat/chat_assistant_5.png)
+
+ Recall that the `conversation` object contained within the `state` variable contains the record of the conversation that the user is having with the LLM. Binding the **Chatbot** component to this object allows it to display the conversation to the user.
-Finally, attach the handler to the chatbot. In the User Interface, click on the **chatbot component** to bring up the Component settings panel. Scroll to the Events section towards the bottom of the pane and choose the `handle_simple_message` handler for the `wf-chatbot-message` event.
+ Select the **Chatbot** component. In the **properties** panel, find the **Conversation** property and set its value to `@{conversation}`.
-![Finished chat assistant project](/framework/images/tutorial/chat/chat_assistant_6.png)
+ ![Updating the Chatbot's conversation property](/framework/images/tutorial/chat/chat_assistant_2j.png)
-After saving and running your code, click the preview button and type something into your chat assistant. You should see the response appear on the screen as it comes back from the assistant. Congratulations!
+ The value `@{conversation}` specifies that the **Chatbot** component should get its information from the value corresponding to the `conversation` key in the application's `state` variable.
+
-![Finished chat assistant project](/framework/images/tutorial/chat/chat_assistant_7.png)
+
+ You need to specify that the **Chatbot** component should call the `generate_completion()` function when the user enters a prompt.
+
+ Do this by scrolling down the **properties** panel to the **Events** section until you see a property called **`wf_chatbot_message`**. Select **`generate_completion`** from its menu.
+
+ ![Updating the Chatbot's wf_chatbot_message property](/framework/images/tutorial/chat/chat_assistant_2k.png)
+
+
-## Deploying the application
+## Test the application
-To deploy the application to the Writer cloud, either terminate your current Writer Framework process or open a new terminal session and run the following command:
+You've completed all the steps to make a working chat assistant, and you can try using it right now, even while editing the user interface!
+Try entering some prompts into the text entry at the bottom of the **Chatbot** component. The LLM should respond accordingly:
+
+![The chat assistant, with the project editor in "UI" mode](/framework/images/tutorial/chat/chat_assistant_2l.png)
+
+To get a better sense of what the experience will be like for the user, switch to the preview by changing the edit mode (located near the upper left corner of the page) from _UI_ mode to _Preview_ mode by selecting the **Preview** option:
+
+![The project editor with an arrow pointing to the Preview button](/framework/images/tutorial/chat/chat_assistant_2m.png)
+
+Here’s what the app looks like in _Preview_ mode:
+
+![The chat assistant, with the project editor in "Preview" mode](/framework/images/tutorial/chat/chat_assistant_2n.png)
+
+You can see the output of any `print()` functions and error messages by clicking on the **Log** button located near the upper right corner of the page:
+
+![The chat assistant with an arrow pointing to the Log button](/framework/images/tutorial/chat/chat_assistant_2o.png)
+
+Here’s what the app looks like when displaying the log:
+
+![The working chat assistant, with the log pane displayed](/framework/images/tutorial/chat/chat_assistant_2p.png)
+
+It's very helpful to be able to test the application while editing it. As you continue to work with Writer Framework, you'll find yourself alternating between making changes to your application and testing those changes without having to leave the project editor.
+
+
+## Run the application locally
+
+Once you've tested the application, it's time to run it locally.
+
+Switch back to your terminal application. Stop the project editor with ctrl-c, then run the application by entering the following command:
+
+```
+writer run chat-assistant
+```
+
+Note that the command starts with `writer run` as opposed to `writer edit`. This launches the application as your users will see it, without any of the editing tools. Even though you can preview your applications in the project editor, it's still a good idea to test it by running it on your computer, outside the project editor, before deploying it.
+
+You'll be able to access the application with your browser at the URL that appears on the command line. It should look like this:
+
+![Finished chat assistant project](/framework/images/tutorial/chat/chat_assistant_1.png)
+
+The Writer editor, which you launched with `writer edit chat-assistant`, and your application, which you launched with `writer run chat-assistant`, run on the same URL, but on different *ports* (specified by the number after the `:` character at the end of the URL).
+
+
+## Deploy the app to the Writer Cloud (optional)
+
+Right now, the app will only run on your computer. To make it available online, you'll need to deploy it to the Writer Cloud.
+
+In your terminal application, stop your app with ctrl-c, then deploy your application by entering the following command:
```
writer deploy chat-assistant
```
+You'll be asked to enter your app's API key. Once you do that, the Writer command-line application will start deploying your application to the Writer Cloud. The process should take a couple of minutes.
-Once the application is deployed, the CLI will return with the URL of your live application.
+Once the app has been deployed to the Writer Cloud, you'll be shown the URL for your application, which you can use to access it online.
## Conclusion
-That's all it takes to set up a basic application with the Writer Framework. This setup not only demonstrates the platform's capabilities, but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation.
+That's it — you've built a functional chat assistant using the Writer Framework!
+
+Feel free to modify this project! The Writer platform is flexible enough for you to customize, extend, and evolve your application into something completely different! To find out what else you can do, check out the documentation for [Writer Framework](https://dev.writer.com/framework/introduction) and the [Writer API](https://dev.writer.com/api-guides/introduction).
\ No newline at end of file
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_1.png b/docs/framework/images/tutorial/chat/chat_assistant_1.png
index 27225ac4a..c125bbdbb 100644
Binary files a/docs/framework/images/tutorial/chat/chat_assistant_1.png and b/docs/framework/images/tutorial/chat/chat_assistant_1.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2a.png b/docs/framework/images/tutorial/chat/chat_assistant_2a.png
new file mode 100644
index 000000000..5b87c9e35
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2a.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2b.png b/docs/framework/images/tutorial/chat/chat_assistant_2b.png
new file mode 100644
index 000000000..7a361bebb
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2b.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2c.png b/docs/framework/images/tutorial/chat/chat_assistant_2c.png
new file mode 100644
index 000000000..42bcb16e6
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2c.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2d.png b/docs/framework/images/tutorial/chat/chat_assistant_2d.png
new file mode 100644
index 000000000..0a0f13eb4
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2d.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2e.png b/docs/framework/images/tutorial/chat/chat_assistant_2e.png
new file mode 100644
index 000000000..5e2fbe958
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2e.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2f.png b/docs/framework/images/tutorial/chat/chat_assistant_2f.png
new file mode 100644
index 000000000..a8668a2c8
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2f.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2g.png b/docs/framework/images/tutorial/chat/chat_assistant_2g.png
new file mode 100644
index 000000000..c105fdd1d
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2g.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2h.png b/docs/framework/images/tutorial/chat/chat_assistant_2h.png
new file mode 100644
index 000000000..106b9758b
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2h.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2i.png b/docs/framework/images/tutorial/chat/chat_assistant_2i.png
new file mode 100644
index 000000000..57cfd9680
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2i.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2j.png b/docs/framework/images/tutorial/chat/chat_assistant_2j.png
new file mode 100644
index 000000000..9e01c9f92
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2j.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2k.png b/docs/framework/images/tutorial/chat/chat_assistant_2k.png
new file mode 100644
index 000000000..dda596e1f
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2k.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2l.png b/docs/framework/images/tutorial/chat/chat_assistant_2l.png
new file mode 100644
index 000000000..505a3a68a
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2l.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2m.png b/docs/framework/images/tutorial/chat/chat_assistant_2m.png
new file mode 100644
index 000000000..2b28698dd
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2m.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2n.png b/docs/framework/images/tutorial/chat/chat_assistant_2n.png
new file mode 100644
index 000000000..6b920f916
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2n.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2o.png b/docs/framework/images/tutorial/chat/chat_assistant_2o.png
new file mode 100644
index 000000000..0f3b4b773
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2o.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2p.png b/docs/framework/images/tutorial/chat/chat_assistant_2p.png
new file mode 100644
index 000000000..df9bf0a0b
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2p.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2a.png b/docs/framework/images/tutorial/social_post/sp_gen_2a.png
new file mode 100644
index 000000000..5b87c9e35
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2a.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2aa.png b/docs/framework/images/tutorial/social_post/sp_gen_2aa.png
new file mode 100644
index 000000000..960eb3c35
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2aa.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2ab.png b/docs/framework/images/tutorial/social_post/sp_gen_2ab.png
new file mode 100644
index 000000000..642d3314b
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2ab.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2b.png b/docs/framework/images/tutorial/social_post/sp_gen_2b.png
new file mode 100644
index 000000000..7a361bebb
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2b.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2c.png b/docs/framework/images/tutorial/social_post/sp_gen_2c.png
new file mode 100644
index 000000000..42bcb16e6
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2c.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2d.png b/docs/framework/images/tutorial/social_post/sp_gen_2d.png
new file mode 100644
index 000000000..0a0f13eb4
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2d.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2e.png b/docs/framework/images/tutorial/social_post/sp_gen_2e.png
new file mode 100644
index 000000000..184bcef34
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2e.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2f.png b/docs/framework/images/tutorial/social_post/sp_gen_2f.png
new file mode 100644
index 000000000..6dc6647ae
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2f.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2g.png b/docs/framework/images/tutorial/social_post/sp_gen_2g.png
new file mode 100644
index 000000000..6e909f5ae
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2g.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2h.png b/docs/framework/images/tutorial/social_post/sp_gen_2h.png
new file mode 100644
index 000000000..759582d66
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2h.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2i.png b/docs/framework/images/tutorial/social_post/sp_gen_2i.png
new file mode 100644
index 000000000..9c1981de7
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2i.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2j.png b/docs/framework/images/tutorial/social_post/sp_gen_2j.png
new file mode 100644
index 000000000..981fda0fc
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2j.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2k.png b/docs/framework/images/tutorial/social_post/sp_gen_2k.png
new file mode 100644
index 000000000..7599fdd2f
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2k.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2l.png b/docs/framework/images/tutorial/social_post/sp_gen_2l.png
new file mode 100644
index 000000000..0bd08906b
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2l.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2m.png b/docs/framework/images/tutorial/social_post/sp_gen_2m.png
new file mode 100644
index 000000000..18032cc0e
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2m.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2n.png b/docs/framework/images/tutorial/social_post/sp_gen_2n.png
new file mode 100644
index 000000000..62a119982
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2n.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2o.png b/docs/framework/images/tutorial/social_post/sp_gen_2o.png
new file mode 100644
index 000000000..1439332b1
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2o.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2p.png b/docs/framework/images/tutorial/social_post/sp_gen_2p.png
new file mode 100644
index 000000000..18148e1cc
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2p.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2q.png b/docs/framework/images/tutorial/social_post/sp_gen_2q.png
new file mode 100644
index 000000000..f6f6f372f
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2q.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2r.png b/docs/framework/images/tutorial/social_post/sp_gen_2r.png
new file mode 100644
index 000000000..0cdd01d5c
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2r.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2s.png b/docs/framework/images/tutorial/social_post/sp_gen_2s.png
new file mode 100644
index 000000000..fd61db7a6
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2s.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2t.png b/docs/framework/images/tutorial/social_post/sp_gen_2t.png
new file mode 100644
index 000000000..01292852b
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2t.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2u.png b/docs/framework/images/tutorial/social_post/sp_gen_2u.png
new file mode 100644
index 000000000..7419df4f4
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2u.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2v.png b/docs/framework/images/tutorial/social_post/sp_gen_2v.png
new file mode 100644
index 000000000..6380b6865
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2v.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2w.png b/docs/framework/images/tutorial/social_post/sp_gen_2w.png
new file mode 100644
index 000000000..d9ec42f1c
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2w.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2x.png b/docs/framework/images/tutorial/social_post/sp_gen_2x.png
new file mode 100644
index 000000000..d32d6c7a0
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2x.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2y.png b/docs/framework/images/tutorial/social_post/sp_gen_2y.png
new file mode 100644
index 000000000..92d2e9b60
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2y.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2z.png b/docs/framework/images/tutorial/social_post/sp_gen_2z.png
new file mode 100644
index 000000000..bff8746fa
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2z.png differ
diff --git a/docs/framework/quickstart.mdx b/docs/framework/quickstart.mdx
index cb3bdffff..d4efeebf5 100644
--- a/docs/framework/quickstart.mdx
+++ b/docs/framework/quickstart.mdx
@@ -56,7 +56,7 @@ writer edit hello-world
After running this command, you'll see a local URL displayed in your command line. Open this URL in a web browser to access the editor.
-
+
@@ -85,7 +85,7 @@ If you need to disable this protection, use the flag `--enable-remote-edit`.
Experiment with arranging these components on the canvas to create a simple layout.
-
+
@@ -109,7 +109,7 @@ If you need to disable this protection, use the flag `--enable-remote-edit`.
Change `"AI STARTER"` to something unique, like `"My First Writer App"`. Save the file, and you’ll see the updated name reflected immediately in the editor.
-
+
diff --git a/docs/framework/social-post-generator.mdx b/docs/framework/social-post-generator.mdx
index 7babc8516..54b6e9ae1 100644
--- a/docs/framework/social-post-generator.mdx
+++ b/docs/framework/social-post-generator.mdx
@@ -2,218 +2,499 @@
title: "Social post generator"
---
-In this tutorial, you’ll use the Writer Framework to build an application that generates social media posts and tags based on user input.
+In this tutorial, you'll use the Writer Framework to build an AI-powered tool for generating social media posts and tags based on the input you provide!
-![Finished social post generator application](/framework/images/tutorial/social_post/sp_gen_1.png)
+The process will take only minutes using a drag-and-drop visual editor to build the user interface and Python for the back-end code.
+
+Here's what the finished project will look like:
+
+![Finished social post generator project](/framework/images/tutorial/social_post/sp_gen_2ab.png)
+
+
+## Prerequisites
+
+Before starting, ensure you have:
+
+- **A Writer account:** You don't need an account to use Writer Framework, but you'll need one to use the AI module. [Fortunately, you can sign up for an account for free!](https://app.writer.com/aistudio/signup)
+- **Python 3.9.2 or later**: Use the installer from [python.org](https://www.python.org/downloads/).
+- **pip:** This command-line application comes with Python and is used for installing Python packages, including those from Writer.
+- **A basic understanding of Python:** You should be familiar with the basics of the language.
+- **Your favorite code editor (optional):** There's a code editor built into Writer for writing back-end code, but you can also use Visual Studio Code, Notepad++, Vim, Emacs, or any text editor made for programming if you prefer.
## Setting up your project
+### Create a Writer app and get its API key
-### Creating a Writer app and getting your API key
+First, you'll need to create a new app within Writer.
-From the Home screen, click on **Build an app**.
+
+
+ Log into Writer. From the Home screen, click on the **Build an app** button.
-![Writer home screen](/framework/images/tutorial/social_post/sp_gen_2.png)
+ ![Writer home screen](/framework/images/tutorial/social_post/sp_gen_2.png)
-Select Framework as the type of application you want to create so that you can generate keys and build your application using the Writer Framework
+ The **Start building** menu will appear, presenting options for the types of apps you can create.
-![App type selection](/framework/images/tutorial/social_post/sp_gen_3.png)
+ Select **Framework**, located under **Developer tools**. This will create a brand new app based on Writer Framework.
-On the next screen, you can edit your Writer application name at the top left. Under "Authenticate with an API key", click **Reveal** to view and copy your API key.
+ !["Start building" menu](/framework/images/tutorial/social_post/sp_gen_3.png)
+
-### Creating the application
+
+ On the next screen, titled **How to deploy an application**, you can get the API key for the app by clicking on the **Reveal key** button, located under the text **Authenticate with an API key**. Your complete API key will be displayed, and a "copy" button will appear. Click this button to copy the key; you'll use it in the next step.
-Next, open your terminal and navigate to the directory where you want to create your application directory.
+ !["How to deploy an application" page](/framework/images/tutorial/social_post/sp_gen_2a.png)
+
+
+
+### Set up your computer and create the app's project
+
+The next step is to set up the Writer Framework environment on your computer. You'll do this by creating a directory for the project, installing dependencies, and creating the project for the application using a template.
-
- To pass your API key to the Writer Framework, you need to set an environment variable called `WRITER_API_KEY`. An easy way to do this is to export the variable for your terminal session.
+
+ Open your terminal application. On macOS and Linux, this application goes by the name _Terminal_; on Windows, you can use either _Windows PowerShell_ (which is preferred) or _Command Prompt_.
+
+
+
+ If you already have the `writer` and `python-dotenv` packages installed on your computer, you can skip this step.
+
+ Install the `writer` and `python-dotenv` packages by entering the following commands in your terminal application:
+
+ ```
+ pip install writer python-dotenv
+ ```
+
+ This command tells `pip`, the Python package installer, to install two packages:
+
+ - `writer`, which provides some command-line commands and enables Python code to interact with Writer and the Writer Framework.
+ - `python-dotenv`, which makes it easy to manage environment variables by loading them from a `.env` file. This one is optional for this exercise, but you might find it useful when working on larger projects.
+
+
+
+ To pass your API key to the Writer Framework, you need to set an environment variable called `WRITER_API_KEY`.
+
+ Select your operating system and terminal application below, then copy and paste the command into your terminal application, replacing `[your_api_key]` with the API key you copied earlier:
- ```sh On macOS/Linux
- export WRITER_API_KEY=[key]
+ ```sh macOS/Linux (Terminal)
+ export WRITER_API_KEY=[your_api_key]
```
- ```sh On Windows
- set WRITER_API_KEY=[key]
+ ```sh On Windows (Windows PowerShell)
+ $env:WRITER_API_KEY=[your_api_key]
+ ```
+
+ ```sh On Windows (Command Prompt)
+ set WRITER_API_KEY=[your_api_key]
```
+
+ The `WRITER_API_KEY` environment variable will remain defined as long your terminal session is open (that is, until you close your terminal application’s window).
+
+
+
+ Create the project by entering this command into your terminal application:
+
+ ```
+ writer create social-post-generator --template=ai-starter
+ ```
+ This command sets up a new project called `social-post-generator` using a starter template called `ai-starter` so that you're not starting "from scratch."
-
- To create the application, run the following command:
+
- ```bash
- writer create social-generator --template=ai-starter
- ```
- This command will set up a new project called `social-generator` in the specified directory using an `ai-starter` template.
+## Build the UI
+
+Now that you've created the project, it's time to define the UI. The Writer Framework's drag-and-drop capabilities make it easy — even if you haven't done much UI work before!
+
+The project editor is a web application that runs on your computer and enables you to define and edit your app's user interface. Launch it by typing the following into your terminal application:
+
+```
+writer edit social-post-generator
+```
+
+You'll see a URL. Control-click it (command-click on macOS) to open it, or copy the URL and paste it into the address bar of a browser window.
+
+The browser window will contain the project editor, which will look like this:
+
+![Project editor](/framework/images/tutorial/social_post/sp_gen_2b.png)
+
+You'll see the following:
+
+- The **canvas** is in the center. It displays the app's user interface.
+- The column on the left contains:
+ - The **Core toolkit**, which contains all the UI components. You define the user interface by dragging components from the Toolkit and placing them on the canvas.
+ - The **Component tree**, which shows the arrangement of the UI components on the canvas. It's also useful for selecting items on the canvas, especially when it has a lot of UI components.
+
+It's time to build the UI!
+
+
+
+ Select the **Header** component by clicking it — it's the component at the top, containing the title **AI STARTER** and a gray area labeled **Empty Header**.
+
+ When you click it, you'll see the **properties** panel appear on the right side of the page. This lets you view and edit the properties of the selected component.
+
+ ![The selected header and its properties panel](/framework/images/tutorial/social_post/sp_gen_2c.png)
+
+ The first property you'll see in the panel is the **Text** property, which defines the text that appears as the header's title. It should contain the value `@{my_app.title}`. The `@{` and `}` indicate that `my_app.title` is a variable and that its contents should be the text displayed instead of the literal text "my_app.title". You'll set the value of this variable soon.
-
- To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:4005`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup.
-
- ```bash Standard port
- writer edit social-generator
- ```
+
+ Select the **Section** component by clicking it — it's just below the **Header** component and contains the title **Section Title** and a gray area labeled **Empty Section**.
- ```bash Custom port
- writer edit social-generator --port=3007
- ```
-
+ In the **properties** panel, clear out the value of the **Title** property. This will remove the _Section_'s default title.
+
+ ![The selected section and its properties panel](/framework/images/tutorial/social_post/sp_gen_2d.png)
-
-## Building the UI
+
+ The user will need a place to enter words or phrases that the app will use as the basis for generating posts and tags.
+
+ Drag a **Text Input** component — and note, it's **Text _Input_**, not **Text** — from the **Core toolkit** panel on the left (it's under **Input**, and you may need to scroll down a little to find it) and into the **Section**. Sections can act as containers for other components.
-The Writer Framework lets you set up any layout according to your preferences with a fast drag-and-drop UI.
+ You can search for a specific component by using the search bar at the top of the **Core toolkit** panel.
+ Select the **Text Input** component. In the **properties** panel:
-### Input layout
+ - Find the **Label** property and set its value to `Topic for social posts and tags`.
+ - (Optional) Feel free to add some placeholder to the *Text Input* component by setting the value of the **Placeholder** property with something like `Enter words or phrases describing your topic`.
-To rename this application and update the Header component, open the code editor and update `my_app.title` in `wf.init_state`:
+ ![The text input component and its properties panel](/framework/images/tutorial/social_post/sp_gen_2e.png)
+
-```python
-wf.init_state({
- "my_app": {
- "title": "SOCIAL POST GENERATOR"
- }
-})
-```
+
+ Drag a **Button** component from the **Core toolkit** panel (it's under **Other**, and you may need to scroll down a little to find it) into the **Section**, directly below the **Text Input**. The user will click this button to submit their prompt.
-Click the provided Section component to open its Component settings and clear out the default title.
+ Select the **Button**. In the **properties** panel:
-If you’d like, you can also change the Section’s colors. Choose “Pick” under Accent color to bring up a color picker and input `RGB(114,28,201)`. You can also input HSL or hex values by clicking on the switch currently labeled “RGB.” For the Container background, click “Pick” and input `RGB(237,226,255)`.
+ - Set the **Text** property's value to `Generate posts`.
+ - Find the **Icon** property, and set its value to `arrow_forward`.
-Next, drag a Text Input component into the Section and label it with something like, “Topic for social posts.” Then, drag a Button component below the Text Input. You can replace the button text in the Component settings panel with “Generate posts.” In the Icon field, add “arrow_forward.”
+ ![The button component and its properties panel](/framework/images/tutorial/social_post/sp_gen_2f.png)
+
-To display loading messages, drag a Messages component below the Button. Click the component to bring up the component settings and update the Loading color to `RGB(212,255,242)`.
+
+ The process of creating social media posts and tags takes a few moments. In order to reassure the user that the app is working and hasn't crashed, it will use a **Message** component to display something reassuring while it's generating.
+ Drag a **Message** component from the **Core toolkit** panel into the **Section** positioning it immediately below the **Button**.
-### Generated text layout
+ Select the **Message** component. In the **properties** panel:
-Next, build the layout for where the generated text will appear. Drag a new Section component inside of the current Section component below the Messages component. Delete the Title text and update the Container background color to `RGB(246,239,253)`.
+ - Scroll down to the **Style** section and look for the **Loading** property, which sets the color of the **Message** component when it's loading.
+ - Click its **CSS** button, which will cause a text field to appear below it.
+ - Enter this color value into the text field: `#D4FFF2`.
-Next, drag a Tags component from the Content section of the Toolkit into the new Section.
+ ![The message component and its properties panel](/framework/images/tutorial/social_post/sp_gen_2g.png)
+
-Then, drag a Separator component below the Tags component.
+
+ The **Section** that you were working on is for user input. Let's add a new **Section** to hold the output — the social media posts and tags the app will generate.
-Finally, drag a Text component below the button. This is where the text generated by the LLM will appear.
+ Drag a **Section** component from the **Toolbox** panel and place it _inside_ the **Section** that's already there, just below the **Message** component.
-![Initial UI layout](/framework/images/tutorial/social_post/sp_gen_4.png)
+ That's right — **Sections** can contain other **Sections**!
+ ![The new section inside the existing section](/framework/images/tutorial/social_post/sp_gen_2h.png)
+
+ Select the **Section** you just added. In the **properties** panel:
+
+ - Find the **Title** property and clear it its value to remove the **Section**'s title.
+ - Scroll down to the **Style** section and look for the **Container background** property, which sets the **Section**'s background color.
+ - Click its **CSS** button, which will cause a text field to appear below it.
+ - Enter this color value into the text field: `#F6EFFD`.
+
+ ![The new section and its properties](/framework/images/tutorial/social_post/sp_gen_2i.png)
+
-## Updating the code
+
+ Writer Framework has a number of useful components to make your apps more functional and beautiful. One of these is the **Tags** component, which can take a list of hashtags (or words, or short phrases) and display them inside colorful "bubbles" to make them stand out. This app will display the social media tags it generates in a **Tags** component.
-With the UI set up, you’ll next add some code to manage user input, communicate with an LLM when the user interacts with the UI, and update the application state based on the results.
+ Drag a **Tags** component from the **Toolbox** panel and place it inside the new **Section**.
+
+ ![The tags component](/framework/images/tutorial/social_post/sp_gen_2j.png)
+
+
+
+ Drag a **Separator** component from the **Toolbox** panel and place it inside the new **Section**, just below the **Tags** component. This will separate the tags from the posts.
+
+ ![The separator](/framework/images/tutorial/social_post/sp_gen_2k.png)
+
+
+
+ Finally, drag a **Text** component from the **Toolbox** panel and position it below the **Separator**. This will hold the generated social media posts.
+
+ ![The text component](/framework/images/tutorial/social_post/sp_gen_2l.png)
+
+
+
+
+## Add the back-end code
+
+With the UI laid out, it's time to work on the logic behind it.
+
+The logic behind the user interface is defined in a file named `main.py`, which is in your project's directory. This file was automatically generated; you'll update the code in it to define the behavior of your app.
+
+The simplest way to edit `main.py` is within the project editor. Click on the "toggle code" button (beside the word **Code**) near the lower left corner of the project editor page.
+
+![Project editor with arrow pointing to toggle code button](/framework/images/tutorial/social_post/sp_gen_2m.png)
+
+A pane with the name **Code** will appear at the bottom half of the screen, displaying an editor for the the contents of `main.py`.
+
+![Project editor with the code editor displayed](/framework/images/tutorial/social_post/sp_gen_2n.png)
+
+If you'd rather use a code editor instead of coding in the browser, use it to open the `main.py` file in your project's directory.
+
+Now follow these steps:
-
- Open your code editor. The `ai-starter` template already includes necessary imports from the Writer Framework and Writer AI packages:
+
+ You should see the following at the start of the file:
- ```python
- import writer as wf
- import writer.ai
- ```
+ ```python
+ import writer as wf
+ import writer.ai
+ ```
- You’ll also need a regular expression library to create hashtags. Import that library at the top of your file:
+ Replace that code with the following:
- ```
- import re
- ```
+ ```python
+ import os
+ import re
+ import writer as wf
+ import writer.ai
+
+ # Set the API key
+ wf.api_key = os.getenv("WRITER_API_KEY")
+ ```
+
+ This code imports the libraries that the application will need and then reads your Writer Framework API key in the `WRITER_API_KEY` environment variable.
-
- Initialize your application state by defining key state elements. Here's how to set up your initial state:
-
- ```python
- wf.init_state({
- "my_app": {
- "title": "SOCIAL POST GENERATOR"
- },
- "posts": "",
- "topic": "writing",
- "message": ""
- })
- ```
- You can replace "writing" with any default topic you prefer.
+
+ When the user presses the app's **Button**, the app needs to call a function to generate and display the social media posts and tags. Find these comments in the code...
+
+ ```python
+ # Welcome to Writer Framework!
+ # This template is a starting point for your AI apps.
+ # More documentation is available at https://dev.writer.com/framework
+ ```
+
+ ...and replace them with the following function:
+
+ ```python
+ def generate_and_display_posts_and_tags(state):
+ print(f"Here's what the user entered: {state['topic']}")
+
+ # Display message
+ state["message"] = "% Generating social media posts and tags for you..."
+
+ # Generate and display social posts
+ prompt = f"You are a social media expert. Generate 5 engaging social media posts about {state['topic']}. Include emojis, and put a blank line between each post."
+ state["posts"] = writer.ai.complete(prompt)
+ print(f"Posts: {state['posts']}")
+
+ # Generate and display hashtags
+ prompt = f"You are a social media expert. Generate around 5 hashtags about {state['topic']}, delimited by spaces. For example, #dogs #cats #ducks #elephants #badgers"
+ pattern = r"#\w+"
+ hashtags = re.findall(pattern, writer.ai.complete(prompt))
+ state["tags"] = {item: item for item in hashtags}
+ print(f"Tags: {state['tags']}")
+
+ # Hide message
+ state["message"] = ""
+ ```
+
+ The `%` at the start of the string being assigned to `state["message"]` will be replaced by a “spinning circle” progress indicator graphic in the _Message_ component.
+
+ The `pattern` variable in the `# Generate and display hashtags` section defines a regular expression pattern to search for words that begin with the `#` character. The `r` in front of the opening quote specifies that the string is a _raw string_, which means that the `\` character should be treated as a literal backslash and not as the start of an escape character sequence.
+
+ Note that `generate_and_display_posts_and_tags()` uses `print()` functions for debugging purposes, and you can use them to get a better idea of what's happening in the function. You'll see their output in both your terminal application and in the project editor's 'log' pane (which will be covered shortly) as you use the social post generator. This output will include:
+
+ - The topic the user entered
+ - The posts generated by the LLM
+ - The hashtags generated by the LLM
+
+ The `print()` functions don't affect the operation of the social post generator in any way, and you can remove them if you wish.
-
- Add a method called `handle_button_click` to handle interactions. This method will pass user input to the Writer AI module `complete` method and update the state with the results. Here’s the method implementation:
- ```python
- def handle_button_click(state):
- state["message"] = "% Loading up expert social media posts..."
-
- prompt = f"You are a social media expert. Generate 5 engaging social media posts about {state['topic']}. Include emojis."
- state["posts"] = writer.ai.complete(prompt)
+
+ The final step is to set the application's initial state. Find this code, which should be just after the `generate_and_display_posts_and_tags()` function...
+
+ ```python
+ # Initialise the state
+ wf.init_state({
+ "my_app": {
+ "title": "AI STARTER"
+ },
+ })
+ ```
+
+ ...and replace it with this:
+
+ ```python
+ # Initialize the state
+ wf.init_state({
+ "topic": "writing",
+ "message": "",
+ "tags": {},
+ "posts": "",
+ "my_app": {
+ "title": "SOCIAL POST GENERATOR"
+ }
+ })
+ ```
+
+ The Writer Framework's `init_state()` method sets the initial value of `state`, a dictionary containing values that define the state of the application. The key-value pairs in `state` are how you store values used by your app and how you pass data between the back-end code and the UI.
+
+ The code above sets the initial value of `state` so that it has these key-value pairs:
+
+ - `topic`: A string containing the topic that the application should generate social media posts and tags for. You'll bind its value to the _Text Input_ component where the user will enter the topic.
+ - `message`: A string containing text of the message that will be displayed to the user while the application is generating posts and tags. You'll bind its value to the **Message** component.
+ - `tags`: A list containing the hashtags generated by the LLM. You'll bind its value to the **Tags** component.
+ - `posts`: A string containing the social media posts generated by the LLM. You'll bind its value to the **Text** component.
+ - `my_app`: A dictionary containing values that define the application's appearance. This dictionary has a single key-value pair, `title`, which defines the text that appears as the application's title.
+
+ For more details about the `state` variable, see our [_Application state_](https://dev.writer.com/framework/application-state#application-state) page.
+
- prompt = f"You are a social media expert. Generate 5 hashtags about {state['topic']}, delimited by spaces. For example, #dogs #cats #ducks #elephants #badgers"
- pattern = r'#\w+'
- hashtags = re.findall(pattern, writer.ai.complete(prompt))
- state["tags"] = {item: item for item in hashtags}
-
- state["message"] = ""
- ```
+
+ That’s all the code. If you edited the code in the browser, save it by clicking the “save” button near the top right corner of the code editor.
- This function sets a loading message, generates social posts and hashtags using AI, and clears the message once processing is complete.
+ ![Project editor and code editor, with arrow pointing to save button](/framework/images/tutorial/social_post/sp_gen_2o.png)
+
+ Click the "toggle code" button to hide the code editor.
-## Binding to the UI components
+## Bind the UI to the back-end code
-The last task is to bind all of the state elements and the click handler to the UI.
+You've built the UI and written the code behind it. Let's connect the two! Go back to the browser window with the project editor and do the following:
-
- Click on the **Text Input component** to bring up the Component settings panel. In the Bindings section, type `topic`.
+
+ Earlier, you saw that the **Header** component's **Text** property was set to `@{my_app.title}`, a value in the app's `state` variable. You changed this value when you update the call to the Writer Framework's `init_state()` method.
+
- ![Text input settings](/framework/images/tutorial/social_post/sp_gen_5.png)
+
+ Select the **Text Input** component. In the **properties** panel, scroll down to the **Binding** section and find the **State element** property. This is where you specify the `state` variable key whose value will be connected to the **Text Input** component. Set its value to `topic`.
+
+ ![Updating the text input component's state element property](/framework/images/tutorial/social_post/sp_gen_2p.png)
-
- Click on the **Button component** to bring up the Component settings panel. In the Events section, use the dropdown for `wf-click` to choose the `handle_button_click` handler.
- ![Text input settings](/framework/images/tutorial/social_post/sp_gen_6.png)
+
+ Select the **Button** component. In the **properties** panel, scroll down to the **Events** section and find the **`wf-click`** property. This is where you specify the function to call when the user clicks the button — set its value to `generate_and_display_posts_and_tags`.
+
+ ![Updating the button's wf-click property](/framework/images/tutorial/social_post/sp_gen_2q.png)
-
- Click on the **Message component** and set the Text to `@{message}`. Under Visibility, choose “Custom” and set it to `message`.
+
+
+ Select the **Message** component. In the **properties** panel, find the **Message** property, which specifies the content of the **Message** component. Set its value to `@{message}`.
+
+ ![Updating the message's message property](/framework/images/tutorial/social_post/sp_gen_2r.png)
-
- Click on the **Tags component**. Under General, click “JSON” for Tags and set the object to `@{tags}`.
- ![Text input settings](/framework/images/tutorial/social_post/sp_gen_7.png)
+
+ Select the **Tags** component. In the **properties** panel:
+
+ - Find the **Tags** property, which specifies the source of the tags that the component will display.
+ - Click its **JSON** button.
+ - In the text field below the **JSON** button, set the **Tags** property's value to `@{tags}`.
+
+ ![Updating the tags component's tags property](/framework/images/tutorial/social_post/sp_gen_2s.png)
-
- Click on the **Text component** to bring up the Component settings panel. In General Properties, bind this text box to a variable called `posts` by entering `@{posts}` in the text property field. You can also set this text box to accept Markdown input if needed by toggling the “Use Markdown” property.
- ![Text input settings](/framework/images/tutorial/social_post/sp_gen_8.png)
+
+ Select the **Text** component. In the **properties** panel:
+
+ - Find the **Text** property, which specifies the content of the **Text** component. Set its value to `@{posts}`.
+ - Set the **Use Markdown** property to **yes**.
+
+ ![Updating the text component's properties](/framework/images/tutorial/social_post/sp_gen_2t.png)
-
- Finally, click on the **Section** that contains the Tags and Text components. Under Visibility, choose **Custom** and set it to `posts`.
+
+
+ Select the **Section** component containing the **Tags** and **Text** components. In the **properties** panel:
+
+ - Scroll to the **Visibility** property at the bottom.
+ - Click on the **Custom** button.
+ - In the **Visibility value** field, set the value to `posts`. This will cause the **Section** to be visible only when the `state` variable's `posts` key has a non-empty value.
+
+ ![Updating the inner section's visibility property](/framework/images/tutorial/social_post/sp_gen_2u.png)
-When you save and run this code, your social media posts will generate in the text section you created earlier.
-![Finished social post generator application](/framework/images/tutorial/social_post/sp_gen_9.png)
+## Test the application
+
+You've completed all the steps to make a working social post generator, and you can try using it right now, even while editing the user interface!
+
+Enter a topic into the **Topic for social posts and tags** text field, then click the **Generate Posts** button* _twice_ — the first time will cause the **properties** panel to appear, and the second click will register as a click. You'll know that you've clicked the button when you see the **Message** component display the text “Generating social media posts and tags for you...”
+
+![Waiting for the generator to finish while the message component displays its message](/framework/images/tutorial/social_post/sp_gen_2v.png)
+
+...and soon after that, you should see some results:
+
+![The results](/framework/images/tutorial/social_post/sp_gen_2w.png)
+
+To get a better sense of what the experience will be like for the user, switch to the preview by changing the edit mode (located near the upper left corner of the page) from _UI_ mode to _Preview_ mode by selecting the **Preview** option:
+
+![The project editor with an arrow pointing to the Preview button](/framework/images/tutorial/social_post/sp_gen_2x.png)
+
+Here’s what the app looks like in _Preview_ mode:
+
+![The working social post generator, with the project editor in "Preview" mode](/framework/images/tutorial/social_post/sp_gen_2y.png)
+
+You can see the output of any `print()` functions and error messages by clicking on the **Log** button located near the upper right corner of the page:
+![The social post generator with an arrow pointing to the Log button](/framework/images/tutorial/social_post/sp_gen_2z.png)
+Here’s what the app looks like when displaying the log:
-## Deploying the application
+![The social post generator, with the log pane displayed](/framework/images/tutorial/social_post/sp_gen_2aa.png)
-To deploy the application to the Writer cloud from your terminal, either terminate your current Writer Framework process or open a new terminal session and run the following command:
+It's very helpful to be able to test the application while editing it. As you continue to work with Writer Framework, you'll find yourself alternating between making changes to your application and testing those changes without having to leave the project editor.
-```sh
-writer deploy social-generator
+## Run the application locally
+
+Once you've tested the application, it's time to run it locally.
+
+Switch back to your terminal application. Stop the editor with ctrl-c, then run the application by entering the following command:
+
+```
+writer run social-post-generator
+```
+
+Note that the command starts with `writer run` as opposed to `writer edit`. This launches the application as your users will see it, without any of the editing tools. Even though you can preview your applications in the project editor, it's still a good idea to test it by running it on your computer, outside the project editor, before deploying it.
+
+You'll be able to access the application with your browser at the URL that appears on the command line. It should look like this:
+
+![Finished social post generator project](/framework/images/tutorial/social_post/sp_gen_2ab.png)
+
+The Writer editor, which you launched with `writer edit social-post-generator`, and your application, which you launched with `writer run social-post-generator`, run on the same URL, but on different *ports* (specified by the number after the `:` character at the end of the URL).
+
+
+## Deploy the app to the Writer Cloud (optional)
+
+Right now, the app will only run on your computer. To make it available online, you'll need to deploy it to the Writer Cloud.
+
+In your terminal application, stop your app with ctrl-c, then deploy your application by entering the following command:
+
+```
+writer deploy social-post-generator
```
+You'll be asked to enter your app's API key. Once you do that, the Writer command-line application will start deploying your application to the Writer Cloud. The process should take a couple of minutes.
-Once the application is deployed, the CLI will return with the URL of your live application.
+Once the app has been deployed to the Writer Cloud, you'll be shown the URL for your application, which you can use to access it online.
## Conclusion
-That's all it takes to set up a basic application with the Writer Framework. This setup not only demonstrates the platform's capabilities but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation.
+That's it — you've built a functional social post generator using the Writer Framework!
+
+Feel free to modify this project! The Writer platform is flexible enough for you to customize, extend, and evolve your application into something completely different! To find out what else you can do, check out the documentation for [Writer Framework](https://dev.writer.com/framework/introduction) and the [Writer API](https://dev.writer.com/api-guides/introduction).
diff --git a/pyproject.toml b/pyproject.toml
index dff683345..7ec18f681 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "writer"
-version = "0.8.2"
+version = "0.8.3rc1"
description = "An open-source, Python framework for building feature-rich apps that are fully integrated with the Writer platform."
authors = ["Writer, Inc."]
readme = "README.md"
diff --git a/src/ui/src/builder/BuilderApp.vue b/src/ui/src/builder/BuilderApp.vue
index 5668e1fd4..421a08bac 100644
--- a/src/ui/src/builder/BuilderApp.vue
+++ b/src/ui/src/builder/BuilderApp.vue
@@ -29,9 +29,7 @@
:key="selectedId ?? 'noneSelected'"
>
-
+
@@ -375,7 +373,7 @@ onMounted(() => {
--notificationsDisplacement: calc(var(--builderSettingsWidth) + 24px);
}
-.builderPanelSwitcher {
+.panelSwitcher {
grid-column: 2 / 3;
grid-row: 3;
}
diff --git a/src/ui/src/builder/settings/BuilderFieldsAlign.vue b/src/ui/src/builder/settings/BuilderFieldsAlign.vue
index b45d97ef2..261f663aa 100644
--- a/src/ui/src/builder/settings/BuilderFieldsAlign.vue
+++ b/src/ui/src/builder/settings/BuilderFieldsAlign.vue
@@ -42,9 +42,9 @@
diff --git a/src/ui/src/builder/sidebar/BuilderSidebarToolkit.vue b/src/ui/src/builder/sidebar/BuilderSidebarToolkit.vue
index c00ef7bb1..d9bc28bfc 100644
--- a/src/ui/src/builder/sidebar/BuilderSidebarToolkit.vue
+++ b/src/ui/src/builder/sidebar/BuilderSidebarToolkit.vue
@@ -26,6 +26,7 @@
+
+
diff --git a/src/ui/src/components/workflows/WorkflowsWorkflow.vue b/src/ui/src/components/workflows/WorkflowsWorkflow.vue
index acf754999..6d2cb2166 100644
--- a/src/ui/src/components/workflows/WorkflowsWorkflow.vue
+++ b/src/ui/src/components/workflows/WorkflowsWorkflow.vue
@@ -142,7 +142,6 @@ import { useDragDropComponent } from "@/builder/useDragDropComponent";
import injectionKeys from "@/injectionKeys";
const renderProxiedComponent = inject(injectionKeys.renderProxiedComponent);
-const instancePath = inject(injectionKeys.instancePath);
const workflowComponentId = inject(injectionKeys.componentId);
const rootEl: Ref
= ref(null);
@@ -224,9 +223,10 @@ async function handleRun() {
callback: () => {
isRunning.value = false;
},
+ handler: `$runWorkflowById_${workflowComponentId}`,
},
}),
- instancePath,
+ null,
false,
);
}
diff --git a/src/ui/src/components/workflows/abstract/WorkflowsNode.vue b/src/ui/src/components/workflows/abstract/WorkflowsNode.vue
index 6520b1d56..d8258abf8 100644
--- a/src/ui/src/components/workflows/abstract/WorkflowsNode.vue
+++ b/src/ui/src/components/workflows/abstract/WorkflowsNode.vue
@@ -1,13 +1,7 @@
-
- !isImageFallback ? handleImageError(ev) : undefined
- "
- />
+