@@ -90,59 +90,55 @@ const fields = inject(injectionKeys.evaluatedFields);
```
-The code above will make _Bubble Message_ available in Builder.
+The code above will make Bubble Message available in the Builder.
-![Custom Components - Bubble Message](./images/custom-components.bubble-message.png)
+![Custom Components - Bubble Message](/framework/images/custom-components.bubble-message.png)
## Developing templates
### Run a local server
-To get started, clone the [Framework repository](https://github.com/streamsync-cloud/streamsync) from GitHub.
+
+
+ To get started, clone the [Framework repository](https://github.com/streamsync-cloud/streamsync) from GitHub.
+
+
+ To develop custom templates in a developer-friendly way, ensure you have a front-end development server with instant reload capabilities. The front-end code for Framework is located in the `ui` folder. With Node and npm installed on your system, run `npm install` to install dependencies. Then, start the server with support for custom component templates using `npm run custom.dev`.
+ ```sh
+ cd ui
+ npm install
+ # "custom.dev" links templates in "custom_components/"
+ # "dev" runs the server without them
+ npm run custom.dev
+ ```
+
+
+ The command `npm run custom.dev` starts a front-end server, which requires a back-end to function fully. Start Framework via command line, specifying the option `--port 5000`, to provide a back-end on that port. It's recommended to create a new app for testing the template you're developing.
+ ```sh
+ writer create customtester
+ writer edit customtester --port 5000
+ ```
+
+
+ You should now be able to access Framework via the URL provided by Vite, e.g. `http://localhost:5174`. In the Builder's _Toolkit_, you should see the sample component, _Balloon Message_. Add it to your tester application.
+
+
-To develop custom templates, at least in a developer-friendly way, you'll need a frontend development server with instant reloads.
-
-The frontend code for Framework can be found in the folder `ui`. With Node and npm in your system, run `npm install` to install dependencies, and start the server with support for custom component templates using `npm run custom.dev`.
-
-```sh
-cd ui
-npm install
-
-# "custom.dev" links templates in "custom_components/"
-# "dev" runs the server without them
-
-npm run custom.dev
-```
-
-The command above will start a frontend server, but won't be of much use by itself —a backend is needed. The frontend development server proxies backend requests to port 5000.
-
-Start Framework via command line, specifying the option `--port 5000`, to provide a backend in that port. It's recommended to create a new app for testing the template you're developing.
-
-```sh
-writer create customtester
-writer edit customtester --port 5000
-```
-
-You should now be able to access Framework via the URL provided by Vite, e.g. `http://localhost:5174`. In Builder's _Toolkit_, you should see the sample component, _Balloon Message_. Add it to your tester application.
### Create a new component
-Go to `ui/src/custom_components/` and open the Vue single-file components, i.e. the `.vue` files. These files contain comments that will help you get started. Try editing the provided templates, you should see changes reflected.
-
-
+
You can also have a look at the built-in component templates, since their syntax is equivalent. They can be found in the `ui/src/core_components/` folder.
-
+
+Go to `ui/src/custom_components/` and open the Vue single-file components, i.e. the `.vue` files. These files contain comments that will help you get started. Try editing the provided templates, you should see changes reflected.
You can get started by duplicating one of these examples. Make sure you add the new template to the entrypoint, as discussed below.
### Define entrypoint
-For custom component templates to be taken into account, they need to be accessible from the entrypoint.
-
-Edit `ui/src/custom_components/index.ts` to define which templates you wish to export and under which identifiers.
+For custom component templates to be taken into account, they need to be accessible from the entrypoint. Edit `ui/src/custom_components/index.ts` to define which templates you wish to export and under which identifiers.
```ts
-
// Import the templates
import BubbleMessage from './BubbleMessage.vue';
@@ -160,19 +156,13 @@ A single or multiple templates can be specified. Take into account that they wil
## Bundling templates
-### Pack and collect
-
Execute `npm run custom.build`, this will generate the output `.js` and `.css` files into `ui/custom_components_dist`.
```sh
-# "build" builds the entire frontend
+# "build" builds the entire front-end
# "custom.build" only builds the custom templates
npm run custom.build
```
-Collect the files from `ui/custom_components_dist` and pack them in a folder such as `my_custom_bubbles`.
-
-### Try them
-
-The folder containing the generated files, e.g. `my_custom_bubbles`, can now be placed in the `extensions/` folder of any Framework project. It'll be automatically detected during server startup.
\ No newline at end of file
+Collect the files from `ui/custom_components_dist` and pack them in a folder such as `my_custom_bubbles`. The folder containing the generated files, e.g. `my_custom_bubbles`, can now be placed in the `extensions/` folder of any Framework project. It'll be automatically detected during server startup.
\ No newline at end of file
diff --git a/docs/framework/custom-server.mdx b/docs/framework/custom-server.mdx
index 742ea73db..5b2c241ad 100644
--- a/docs/framework/custom-server.mdx
+++ b/docs/framework/custom-server.mdx
@@ -28,11 +28,9 @@ asgi_app: FastAPI = writer.serve.app
def hello():
return "1"
```
-
-::: warning Use `server_setup.py` in `edit` mode
-If you want to use in `edit` mode,
-you can launch `writer edit --enable-server-setup
`.
-:::
+
+Use `server_setup.py` in `edit` mode. If you want to use in `edit` mode, you can launch `writer edit --enable-server-setup `.
+
## Implement custom server
@@ -60,11 +58,10 @@ Note the inclusion of the imported `ws_max_size` setting. This is important for
Fine-tuning Uvicorn allows you to set up SSL, configure proxy headers, etc, which can prove vital in complex deployments.
-::: tip Use server setup hook
+Use server setup hook
```python
asgi_app = writer.serve.get_asgi_app(app_path, mode, enable_server_setup=True)
```
-:::
## Multiple apps at once
diff --git a/docs/framework/deploy-with-docker.mdx b/docs/framework/deploy-with-docker.mdx
index 0a1be5492..79e8595ee 100644
--- a/docs/framework/deploy-with-docker.mdx
+++ b/docs/framework/deploy-with-docker.mdx
@@ -2,21 +2,27 @@
title: "Deploy with Docker"
---
+
+To deploy on the Writer cloud see instructions [here](/framework/quickstart#deploying-on-writer-cloud).
+
+
You can use Docker to deploy Framework anywhere. If you're an experienced Docker user, you may want to go straight to the provided Dockerfile.
## Creating a Docker image
-### Setting up
-
-- Make sure you have Docker installed.
-- Open a terminal and navigate to your app's folder.
-- Create a `pyproject.toml` using `poetry init` and install `writer` using `poetry add writer`
-
-### Creating a Dockerfile
-
-A Dockerfile is a file with instructions that tell Docker how to build your image. It must be named `Dockerfile`.
+
+
+ Make sure you have Docker installed on your system.
+
+
+ Open a terminal and navigate to your app’s folder.
+
+
+ Create a `pyproject.toml` file using `poetry init` and install `writer` using `poetry add writer`.
+
+
-You can use the following as-is, or as a starting point. It should be saved in your app's folder, together with `main.py` and `ui.json`.
+A Dockerfile is a file with instructions that tell Docker how to build your image. It must be named `Dockerfile`. You can use the following as-is, or as a starting point. It should be saved in your app's folder, together with `main.py` and `ui.json`.
```docker
FROM python:3.10-bullseye
@@ -38,17 +44,13 @@ It uses an official Python slim base image with a multistage build to reduce the
If you're a Docker expert, feel free to work on your own `Dockerfile`. Framework is, after all, a standard Python package.
-### Building the Docker image
-
-To build the image, write `docker build . -t ` followed by an image tag, which you're free to choose and will locally identify your image.
+To build the image, use `docker build . -t ` followed by an image tag, which you're free to choose and will locally identify your image.
```sh
docker build . -t my_streamsync_app
```
-Platform considerations
-
By default, Docker builds images in the architecture it's being run on. If you're working with an ARM computer, such as a Mac M2 or a Raspberry Pi, Docker will build an ARM image. Most cloud services will only accept x86 images. You can use another computer (or virtual machine) to build the image, or you can use [Docker buildx](https://docs.docker.com/build/building/multi-platform/).
@@ -83,11 +85,20 @@ Go on your browser to [http://localhost:8080](http://localhost:8080) to check ev
As mentioned earlier, once the image is a registry, it can be spun up by others. After trying a few options, we recommend using Google Cloud Run. Its free tier is generous and SSL works out of the box.
-![Run and Share - Google Cloud Run](./images/deploy-with-docker.google-cloud-run.png)
+![Run and Share - Google Cloud Run](/framework/images/deploy-with-docker.google-cloud-run.png)
Cloud Run can be configured in just one page. It takes the image from a registry and makes it available via a URL, with SSL enabled by default. We recommend the following settings:
-- Minimum 0 instances, maximum 4 instances. Unless your app needs to serve several thousands of users.
-- Request timeout to the maximum allowed and _Session Affinity_ enabled. This ensures that WebSockets connections are not unnecessarily dropped.
-- 2GB of memory and 2 vCPUs. This will likely be enough to comfortably run a simple app. You can probably get away with much less (512MB of memory and 1vCPU), if your app isn't too demanding and you don't expect much traffic.
+
+
+ Minimum 0 instances, maximum 4 instances. This range is suitable unless your app needs to serve several thousands of users.
+
+
+ Set the request timeout to the maximum allowed and enable Session Affinity. This ensures that WebSocket connections are not unnecessarily dropped.
+
+
+ Allocate 2GB of memory and 2 vCPUs. This configuration will likely be enough to comfortably run a simple app. However, you can probably manage with much less—512MB of memory and 1 vCPU—if your app isn’t too demanding and you don’t expect much traffic.
+
+
+
diff --git a/docs/framework/event-handlers.mdx b/docs/framework/event-handlers.mdx
index 3d7c7523d..8b902aabb 100644
--- a/docs/framework/event-handlers.mdx
+++ b/docs/framework/event-handlers.mdx
@@ -2,7 +2,7 @@
title: "Event handlers"
---
-Events originate in the frontend, for example, when a user clicks a _Button_ component. Using Builder, these events can be linked to event handlers.
+Events originate in the front-end, for example, when a user clicks a _Button_ component. Using the Builder, these events can be linked to event handlers.
## Plain Python functions
@@ -14,10 +14,10 @@ def handle_click()
print("Hello")
```
-To specify that a function isn't an event handler and should remain hidden to the frontend, prefix it with a `_` (underscore).
+To specify that a function isn't an event handler and should remain hidden to the front-end, prefix it with a `_` (underscore).
```py
-# This function won't be visible in the frontend
+# This function won't be visible in the front-end
# because its name starts with an underscore
def _reticulate(splines):
r_splines = np.random.normal(size=(splines,100))
@@ -43,7 +43,7 @@ import my_handlers_module
wf.init_handlers(my_handlers_module)
# Register all functions from the module as handlers;
-# this makes `increment` handler accessible on frontend
+# this makes `increment` handler accessible on front-end
```
```py init_handlers many modules
@@ -58,7 +58,7 @@ wf.init_handlers([handler_module_one, handler_module_two])
-Each function inside a module is attempted to be registered as a handler. Make sure to use `_` prefix as described [before](#plain-python-functions) to prevent exposing unwanted functions to frontend.
+Each function inside a module is attempted to be registered as a handler. Make sure to use `_` prefix as described [before](#plain-python-functions) to prevent exposing unwanted functions to front-end.
You can also call `init_handlers` within other modules, which allows for a sequence of registrations:
@@ -77,7 +77,7 @@ import writer as wf
import another_handlers_module
wf.init_handlers(another_handlers_module)
-# Makes `decrement` handler accessible on frontend
+# Makes `decrement` handler accessible on front-end
...
```
@@ -108,11 +108,17 @@ The handler above receives the application state for the relevant session and mu
## Mutation detection
-When communicating with the frontend, Framework only sends state elements that have mutated.
+
+Mutations are detected via assignment.
+Make sure you perform an assignment on the state element you're mutating, for the mutation to be detected.
+
-To detect which elements have mutated, it relies on assignment (via operators such as `=`, `+=`, etc). This is because Python doesn't offer a performant, reliable mechanism to detect mutations. See the example below.
+When communicating with the front-end, Framework only sends state elements that have mutated.
-```py
+To detect which elements have mutated, it relies on assignment (via operators such as `=`, `+=`, etc). This is because Python doesn't offer a performant, reliable mechanism to detect mutations. See the two examples below.
+
+
+```python hande_click
def handle_click(state):
state["my_df"].sample(frac=1, random_state=random.seed())
@@ -121,20 +127,14 @@ def handle_click(state):
state["my_df"] = state["my_df"]
```
-
-The following, arguably cleaner, code, also works as it naturally relies on assignment.
-
-```py
-def handle_click(state):
+```python hande_click_cleaner
+# The following cleaner code also works as it relies on assignment
+def hande_click_cleaner(state):
my_df = state["my_df"]
my_df.sample(frac=1, random_state=random.seed())
state["my_df"] = my_df # State assignmnet
```
-
-
-Mutations are detected via assignment.
-Make sure you perform an assignment on the state element you're mutating, for the mutation to be detected.
-
+
## Receiving a payload
@@ -156,7 +156,7 @@ def handle_webcam_capture(payload):
file_handle.write(image_file)
```
-Handling different payloads across events can be challenging, especially since the shape of the payload may vary. To simplify this process, Builder provides stub code that can help you get started with writing an event handler. You can access it by clicking the icon located next to the event when configuring the component's settings. This feature can help you quickly understand the structure of the payload and start writing the appropriate code to handle it.
+Handling different payloads across events can be challenging, especially since the shape of the payload may vary. To simplify this process, the Builder provides stub code that can help you get started with writing an event handler. You can access it by clicking the icon located next to the event when configuring the component's settings. This feature can help you quickly understand the structure of the payload and start writing the appropriate code to handle it.
## Globals
@@ -189,32 +189,31 @@ def payload_inspector(state, payload):
Event handlers run in a thread pool and are non-blocking. Each event is processed independently from each other.
-State mutations are sent to the frontend after the function has finished executing. The code below will accumulate all mutations and send to the frontend after the function returns.
+State mutations are sent to the front-end after the function has finished executing. The code in `handle_fast` will accumulate all mutations and send to the front-end after the function returns. For long-running tasks, Framework will periodically check state and provide partial updates to the user.
-```py
+
+```py handle_fast
def handle_fast(state):
state["text"] = "Hello"
state["x"] += 3
state["y"] += 2
```
-However, for long-running tasks, Framework will periodically check state and provide partial updates to the user.
-
-```py
+```py handle slowly
+# The code below will set `message` to "Loading...", then to "Completed".
def handle_slowly(state):
state["message"] = "Loading..."
import time
time.sleep(5)
state["message"] = "Completed"
```
+
-The code above will set `message` to "Loading...", then to "Completed".
-
-## Asynchronous Event Handlers
+## Asynchronous event handlers
Framework supports asynchronous event handlers, allowing for non-blocking I/O operations directly within event handlers. This is particularly useful for tasks such as fetching data from a database, making HTTP requests, or performing any other I/O bound operation that can benefit from asynchronous execution.
-### Defining an Asynchronous Handler
+### Defining an asynchronous handler
An asynchronous event handler is defined with the standard `async` keyword syntax.
@@ -227,7 +226,7 @@ async def handle_async_click(state):
In the example above, `fetch_data()` is an asynchronous function that retrieves data, potentially from a remote source. The `await` keyword is used to wait for the operation to complete without blocking the main thread, allowing other tasks to run concurrently.
-### Awaitable Objects
+### Awaitable objects
You can use any awaitable object within an async event handler. This includes the output of any function defined with `async def`, or objects with an `__await__` method. This makes it easy to integrate with asynchronous libraries and frameworks.
diff --git a/docs/framework/frontend-scripts.mdx b/docs/framework/frontend-scripts.mdx
index 386271dba..bf1031908 100644
--- a/docs/framework/frontend-scripts.mdx
+++ b/docs/framework/frontend-scripts.mdx
@@ -2,13 +2,13 @@
title: "Frontend scripts"
---
-Framework can import custom JavaScript/ES6 modules from the frontend. Module functions can be triggered from the backend.
+Framework can import custom JavaScript/ES6 modules from the front-end. Module functions can be triggered from the back-end.
## Importing an ES6 module
-Similarly to [stylesheets](/stylesheets), frontend scripts are imported via Framework's `mail` capability. This allows you to trigger an import for all or specific sessions at any time during runtime. When the `import_frontend_module` method is called, this triggers a dynamic [import()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) call in the frontend.
+Similarly to [stylesheets](/stylesheets), front-end scripts are imported via Framework's `mail` capability. This allows you to trigger an import for all or specific sessions at any time during runtime. When the `import_frontend_module` method is called, this triggers a dynamic [import()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) call in the front-end.
-The `import_frontend_module` method takes the `module_key` and `specifier` arguments. The `module_key` is an identifier used to store the reference to the module, which will be used later to call the module's functions. The `specifier` is the path to the module, such as `/static/mymodule.js`. It needs to be available to the frontend, so storing in the `/static/` folder is recommended.
+The `import_frontend_module` method takes the `module_key` and `specifier` arguments. The `module_key` is an identifier used to store the reference to the module, which will be used later to call the module's functions. The `specifier` is the path to the module, such as `/static/mymodule.js`. It needs to be available to the front-end, so storing in the `/static/` folder is recommended.
The following code imports a module during event handling.
@@ -28,16 +28,12 @@ initial_state.import_frontend_module("my_script", "/static/mymodule.js")
```
-Use versions to avoid caching
-
-Similarly to stylesheets, your browser may cache modules, preventing updates from being reflected. Append a querystring to invalidate the cache, e.g. use `/static/script.js?3`.
+Use versions to avoid caching. Similarly to stylesheets, your browser may cache modules, preventing updates from being reflected. Append a querystring to invalidate the cache, e.g. use `/static/script.js?3`.
## Writing a module
-The module should be a standard ES6 module and export at least one function, enabling it to be triggered from the backend. As per JavaScript development best practices, modules should have no side effects.
-
-An example of a module is shown below.
+The module should be a standard ES6 module and export at least one function, enabling it to be triggered from the back-end. As per JavaScript development best practices, modules should have no side effects. An example of a module is shown below.
```js
let i = 0;
@@ -50,18 +46,18 @@ export function sendAlert(personName) {
## Calling a function
-Once the module is imported, functions can be called from the backend using the `call_frontend_function` method of state. This function takes three arguments. The first, `module_key` is the identifier used to import the module. The second, `function_name` is the name of the exported frontend function. The third, `args` is a `List` containing the arguments for the call.
+Once the module is imported, functions can be called from the back-end using the `call_frontend_function` method of state. This function takes three arguments. The first, `module_key` is the identifier used to import the module. The second, `function_name` is the name of the exported front-end function. The third, `args` is a `List` containing the arguments for the call.
-The following event handler triggers the frontend function defined in the section above.
+The following event handler triggers the front-end function defined in the section above.
```py
def handle_click(state):
state.call_frontend_function("mymodule", "sendAlert", ["Bob"])
```
-## Import a JavaScript script
+## Import a JS script
-Framework can also import and run JavaScript scripts directly, for their side effects. These are imported via the report's `import_script` method. This method takes two arguments. The first, `script_key` is the identifier used to import the script. The second, `path` is the path to the file. The specified path must be available to the frontend, so storing it in your application's `./static` folder is recommended.
+Framework can also import and run JavaScript scripts directly, for their side effects. These are imported via the report's `import_script` method. This method takes two arguments. The first, `script_key` is the identifier used to import the script. The second, `path` is the path to the file. The specified path must be available to the front-end, so storing it in your application's `./static` folder is recommended.
```py
initial_state = wf.init_state({
@@ -72,12 +68,10 @@ initial_state.import_script("my_script", "/static/script.js")
```
-Prefer ES6 modules
-
-Importing scripts is useful to import libraries that don't support ES6 modules. When possible, use ES6 modules. The `import_script` syntax is only used for side effects; you'll only be able to call functions from the backend using modules that have been previously imported via `import_frontend_module`.
+Prefer ES6 modules: importing scripts is useful to import libraries that don't support ES6 modules. When possible, use ES6 modules. The `import_script` syntax is only used for side effects; you'll only be able to call functions from the back-end using modules that have been previously imported via `import_frontend_module`.
-## Importing a script or stylesheet from a URL
+## Importing a script from a URL
Framework can also import scripts and stylesheets from URLs. This is useful for importing libraries from CDNs. The `import_script` and `import_stylesheet` methods take a `url` argument, which is the URL to the script or stylesheet.
@@ -93,7 +87,11 @@ initial_state.import_script("lodash", "https://cdnjs.cloudflare.com/ajax/libs/lo
## Frontend core
-You can access Framework's frontend core via `globalThis.core`, unlocking all sorts of functionality. Notably, you can use `getUserState()` to get values from state.
+
+Effectively using Framework's core can be challenging and will likely entail reading its [source code](https://github.com/streamsync-cloud/streamsync/blob/master/ui/src/core/index.ts). Furthermore, it's considered an internal capability rather than a public API, so it may unexpectedly change between releases.
+
+
+You can access Framework's front-end core via `globalThis.core`, unlocking all sorts of functionality. Notably, you can use `getUserState()` to get values from state.
```js
export function alertHueRotationValue() {
@@ -102,8 +100,3 @@ export function alertHueRotationValue() {
}
```
-
-Here be dragons
-
-Effectively using Framework's core can be challenging and will likely entail reading its [source code](https://github.com/streamsync-cloud/streamsync/blob/master/ui/src/core/index.ts). Furthermore, it's considered an internal capability rather than a public API, so it may unexpectedly change between releases.
-
\ No newline at end of file
diff --git a/docs/framework/handling-inputs.mdx b/docs/framework/handling-inputs.mdx
index ec3abdde3..5d85797b3 100644
--- a/docs/framework/handling-inputs.mdx
+++ b/docs/framework/handling-inputs.mdx
@@ -8,7 +8,7 @@ There are two, complementary, ways to handle inputs in Framework: via event hand
Input components have _change_ events that are dispatched when the value changes. The new value is provided as a payload in the event handler. Change events have slightly different names across components, reflecting the payloads they provide. For example, _Number Input_ and _Slider Input_ use the event `wf-number-change` while _Text Input_ and _Text Area Input_ use the generic `wf-change`.
-As discussed in the [Event handlers](event-handlers.html) section, the payload can be accessed via the `payload` argument in the event handler.
+As discussed in the [Event handlers](/framework/event-handlers) section, the payload can be accessed via the `payload` argument in the event handler.
```py
# This event handler takes the payload and assigns it
@@ -21,13 +21,13 @@ def handle_input_change(state, payload):
Writing event handlers for every input component can be tedious. In most cases, you'll only need to update a single element of state when the value changes, akin to the example above. You can achieve this by binding a component to a state element.
-Bindings automatically handle the _change_ event for the component and set the value of the state element to the payload. Furthermore, bindings are two-way. If the state element is updated from the backend, the frontend component is updated to reflect the new value.
+Bindings automatically handle the _change_ event for the component and set the value of the state element to the payload. Furthermore, bindings are two-way. If the state element is updated from the back-end, the front-end component is updated to reflect the new value.
-As mentioned in the [Builder basics](builder-basics.html) section of the guide, bindings can be configured in the component settings.
+As mentioned in the [Builder basics](/framework/builder-basics) section of the guide, bindings can be configured in the component settings.
![Repeater example](/framework/images/handling-inputs.binding.png)
-The binding above establishes a two-way link between the component and the state element `name`. If `name` changes in the backend, the component changes. If the component changes, the value of `name` changes.
+The binding above establishes a two-way link between the component and the state element `name`. If `name` changes in the back-end, the component changes. If the component changes, the value of `name` changes.
## Using events and bindings simultaneously
diff --git a/docs/framework/images/auth_unauthorized_default.png b/docs/framework/images/auth_unauthorized_default.png
index 280ebeb6c..cf7038772 100644
Binary files a/docs/framework/images/auth_unauthorized_default.png and b/docs/framework/images/auth_unauthorized_default.png differ
diff --git a/docs/framework/images/backend-initiated-actions.notifications.png b/docs/framework/images/backend-initiated-actions.notifications.png
index 4098cc5a3..670cd5eb2 100644
Binary files a/docs/framework/images/backend-initiated-actions.notifications.png and b/docs/framework/images/backend-initiated-actions.notifications.png differ
diff --git a/docs/framework/images/custom-components.architecture.png b/docs/framework/images/custom-components.architecture.png
index d3571879f..e1463c317 100644
Binary files a/docs/framework/images/custom-components.architecture.png and b/docs/framework/images/custom-components.architecture.png differ
diff --git a/docs/framework/images/custom-components.bubble-message.png b/docs/framework/images/custom-components.bubble-message.png
index 7a378f804..d5f2aeb34 100644
Binary files a/docs/framework/images/custom-components.bubble-message.png and b/docs/framework/images/custom-components.bubble-message.png differ
diff --git a/docs/framework/images/custom-components.external.png b/docs/framework/images/custom-components.external.png
index 586a23d18..0f41ece92 100644
Binary files a/docs/framework/images/custom-components.external.png and b/docs/framework/images/custom-components.external.png differ
diff --git a/docs/framework/images/deploy-with-docker.google-cloud-run.png b/docs/framework/images/deploy-with-docker.google-cloud-run.png
index b6909cf10..c7452417c 100644
Binary files a/docs/framework/images/deploy-with-docker.google-cloud-run.png and b/docs/framework/images/deploy-with-docker.google-cloud-run.png differ
diff --git a/docs/framework/images/repeater.example.png b/docs/framework/images/repeater.example.png
index 58973b099..5fe5c312f 100644
Binary files a/docs/framework/images/repeater.example.png and b/docs/framework/images/repeater.example.png differ
diff --git a/docs/framework/images/tutorial/app-selection.png b/docs/framework/images/tutorial/app-selection.png
deleted file mode 100644
index ab9958cbe..000000000
Binary files a/docs/framework/images/tutorial/app-selection.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/chat-conversation-setting.png b/docs/framework/images/tutorial/chat-conversation-setting.png
deleted file mode 100644
index 9ea129ed1..000000000
Binary files a/docs/framework/images/tutorial/chat-conversation-setting.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/chat-event-handler.png b/docs/framework/images/tutorial/chat-event-handler.png
deleted file mode 100644
index b929509c6..000000000
Binary files a/docs/framework/images/tutorial/chat-event-handler.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/chat-finished-app.png b/docs/framework/images/tutorial/chat-finished-app.png
deleted file mode 100644
index c543351b9..000000000
Binary files a/docs/framework/images/tutorial/chat-finished-app.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/chat-initial-ui.png b/docs/framework/images/tutorial/chat-initial-ui.png
deleted file mode 100644
index 143e20bce..000000000
Binary files a/docs/framework/images/tutorial/chat-initial-ui.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_1.png b/docs/framework/images/tutorial/chat/chat_assistant_1.png
new file mode 100644
index 000000000..27225ac4a
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_1.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_2.png b/docs/framework/images/tutorial/chat/chat_assistant_2.png
new file mode 100644
index 000000000..37d4c1a55
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_2.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_3.png b/docs/framework/images/tutorial/chat/chat_assistant_3.png
new file mode 100644
index 000000000..405d53d3f
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_3.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_4.png b/docs/framework/images/tutorial/chat/chat_assistant_4.png
new file mode 100644
index 000000000..b3708a3a7
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_4.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_5.png b/docs/framework/images/tutorial/chat/chat_assistant_5.png
new file mode 100644
index 000000000..a08bfb9e1
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_5.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_6.png b/docs/framework/images/tutorial/chat/chat_assistant_6.png
new file mode 100644
index 000000000..3eec817ae
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_6.png differ
diff --git a/docs/framework/images/tutorial/chat/chat_assistant_7.png b/docs/framework/images/tutorial/chat/chat_assistant_7.png
new file mode 100644
index 000000000..27225ac4a
Binary files /dev/null and b/docs/framework/images/tutorial/chat/chat_assistant_7.png differ
diff --git a/docs/framework/images/tutorial/home.png b/docs/framework/images/tutorial/home.png
deleted file mode 100644
index d69a0b090..000000000
Binary files a/docs/framework/images/tutorial/home.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-add-outlet-form.png b/docs/framework/images/tutorial/pdg-add-outlet-form.png
deleted file mode 100644
index 8d3c4aa7b..000000000
Binary files a/docs/framework/images/tutorial/pdg-add-outlet-form.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-button-settings.png b/docs/framework/images/tutorial/pdg-button-settings.png
deleted file mode 100644
index 95f23e7c8..000000000
Binary files a/docs/framework/images/tutorial/pdg-button-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-finished-app.png b/docs/framework/images/tutorial/pdg-finished-app.png
deleted file mode 100644
index e72aa6253..000000000
Binary files a/docs/framework/images/tutorial/pdg-finished-app.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-finished-descriptions.png b/docs/framework/images/tutorial/pdg-finished-descriptions.png
deleted file mode 100644
index e8c714222..000000000
Binary files a/docs/framework/images/tutorial/pdg-finished-descriptions.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-repeater-settings.png b/docs/framework/images/tutorial/pdg-repeater-settings.png
deleted file mode 100644
index 51578f8f4..000000000
Binary files a/docs/framework/images/tutorial/pdg-repeater-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-seo-chart.png b/docs/framework/images/tutorial/pdg-seo-chart.png
deleted file mode 100644
index c325d2d74..000000000
Binary files a/docs/framework/images/tutorial/pdg-seo-chart.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-seo-tab.png b/docs/framework/images/tutorial/pdg-seo-tab.png
deleted file mode 100644
index fc6cf1d55..000000000
Binary files a/docs/framework/images/tutorial/pdg-seo-tab.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-tab-settings.png b/docs/framework/images/tutorial/pdg-tab-settings.png
deleted file mode 100644
index 2c1870f8d..000000000
Binary files a/docs/framework/images/tutorial/pdg-tab-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-tab-visibility.png b/docs/framework/images/tutorial/pdg-tab-visibility.png
deleted file mode 100644
index 20954c1f0..000000000
Binary files a/docs/framework/images/tutorial/pdg-tab-visibility.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/pdg-text-settings.png b/docs/framework/images/tutorial/pdg-text-settings.png
deleted file mode 100644
index 07cf336a6..000000000
Binary files a/docs/framework/images/tutorial/pdg-text-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_1.png b/docs/framework/images/tutorial/product_desciption/pd_gen_1.png
new file mode 100644
index 000000000..9e705cd43
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_1.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_10.png b/docs/framework/images/tutorial/product_desciption/pd_gen_10.png
new file mode 100644
index 000000000..77f2397b2
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_10.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_11.png b/docs/framework/images/tutorial/product_desciption/pd_gen_11.png
new file mode 100644
index 000000000..d88489eeb
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_11.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_12.png b/docs/framework/images/tutorial/product_desciption/pd_gen_12.png
new file mode 100644
index 000000000..7c50cee28
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_12.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_13.png b/docs/framework/images/tutorial/product_desciption/pd_gen_13.png
new file mode 100644
index 000000000..eb7bcce59
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_13.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_2.png b/docs/framework/images/tutorial/product_desciption/pd_gen_2.png
new file mode 100644
index 000000000..37d4c1a55
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_2.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_3.png b/docs/framework/images/tutorial/product_desciption/pd_gen_3.png
new file mode 100644
index 000000000..405d53d3f
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_3.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_4.png b/docs/framework/images/tutorial/product_desciption/pd_gen_4.png
new file mode 100644
index 000000000..3e3f06810
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_4.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_5.png b/docs/framework/images/tutorial/product_desciption/pd_gen_5.png
new file mode 100644
index 000000000..b0ebbee03
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_5.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_6.png b/docs/framework/images/tutorial/product_desciption/pd_gen_6.png
new file mode 100644
index 000000000..b5db00ea1
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_6.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_7.png b/docs/framework/images/tutorial/product_desciption/pd_gen_7.png
new file mode 100644
index 000000000..d1f95438b
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_7.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_8.png b/docs/framework/images/tutorial/product_desciption/pd_gen_8.png
new file mode 100644
index 000000000..0d10edb0b
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_8.png differ
diff --git a/docs/framework/images/tutorial/product_desciption/pd_gen_9.png b/docs/framework/images/tutorial/product_desciption/pd_gen_9.png
new file mode 100644
index 000000000..fbcfa2a19
Binary files /dev/null and b/docs/framework/images/tutorial/product_desciption/pd_gen_9.png differ
diff --git a/docs/framework/images/tutorial/social-button-event-settings.png b/docs/framework/images/tutorial/social-button-event-settings.png
deleted file mode 100644
index 1822335f4..000000000
Binary files a/docs/framework/images/tutorial/social-button-event-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/social-finished-app.png b/docs/framework/images/tutorial/social-finished-app.png
deleted file mode 100644
index 4a829aa44..000000000
Binary files a/docs/framework/images/tutorial/social-finished-app.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/social-initial-ui-layout.png b/docs/framework/images/tutorial/social-initial-ui-layout.png
deleted file mode 100644
index c702bc545..000000000
Binary files a/docs/framework/images/tutorial/social-initial-ui-layout.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/social-tags-settings.png b/docs/framework/images/tutorial/social-tags-settings.png
deleted file mode 100644
index a5392cf16..000000000
Binary files a/docs/framework/images/tutorial/social-tags-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/social-text-input-settings.png b/docs/framework/images/tutorial/social-text-input-settings.png
deleted file mode 100644
index e2555039a..000000000
Binary files a/docs/framework/images/tutorial/social-text-input-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/social-text-settings.png b/docs/framework/images/tutorial/social-text-settings.png
deleted file mode 100644
index 163dd88c0..000000000
Binary files a/docs/framework/images/tutorial/social-text-settings.png and /dev/null differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_1.png b/docs/framework/images/tutorial/social_post/sp_gen_1.png
new file mode 100644
index 000000000..9223bde2e
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_1.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_2.png b/docs/framework/images/tutorial/social_post/sp_gen_2.png
new file mode 100644
index 000000000..37d4c1a55
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_2.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_3.png b/docs/framework/images/tutorial/social_post/sp_gen_3.png
new file mode 100644
index 000000000..405d53d3f
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_3.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_4.png b/docs/framework/images/tutorial/social_post/sp_gen_4.png
new file mode 100644
index 000000000..f155a7768
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_4.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_5.png b/docs/framework/images/tutorial/social_post/sp_gen_5.png
new file mode 100644
index 000000000..414370110
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_5.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_6.png b/docs/framework/images/tutorial/social_post/sp_gen_6.png
new file mode 100644
index 000000000..3a7e75dc8
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_6.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_7.png b/docs/framework/images/tutorial/social_post/sp_gen_7.png
new file mode 100644
index 000000000..9d8108438
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_7.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_8.png b/docs/framework/images/tutorial/social_post/sp_gen_8.png
new file mode 100644
index 000000000..0c80f5819
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_8.png differ
diff --git a/docs/framework/images/tutorial/social_post/sp_gen_9.png b/docs/framework/images/tutorial/social_post/sp_gen_9.png
new file mode 100644
index 000000000..9223bde2e
Binary files /dev/null and b/docs/framework/images/tutorial/social_post/sp_gen_9.png differ
diff --git a/docs/framework/introduction.mdx b/docs/framework/introduction.mdx
index 9ac4635bc..70fc2a4c6 100644
--- a/docs/framework/introduction.mdx
+++ b/docs/framework/introduction.mdx
@@ -2,50 +2,81 @@
title: "Introduction"
---
-## What is Framework?
+The Writer Framework lets you build feature-rich apps by using a drag-and-drop visual editor called **the Builder** and writing the back-end code in Python. It's fast and flexible, with clean, easy-to-test syntax. It provides separation of concerns between UI and business logic, enabling more complex apps.
+
+![Framework Builder screenshot](/framework/public/builder.png#framework)
+
+Build AI apps with the Writer Framework when:
+
+1. You need to incorporate external data sources, such as external APIs
+2. You have complex user input that requires custom logic, such as conditions that trigger the use of different prompts
+3. You want to quickly analyze and visualize data using an LLM
+
+The Writer Framework offers:
+
+
+
+ Define event handlers as plain Python functions.
+ ```python
+ def handle_text_update(state):
+ state["text"] = "Updated text"
+
+ writer.init_state({
+ "text": "Initial text"
+ })
+ ```
+
+
+
+ Link the event handler and state to the UI seamlessly.
+
+
+
+ Install with a simple pip command.
+
+
+
+ Save user interfaces as JSON to be version controlled with the rest of the app.
+
+
+
+ Use your local code editor with instant refreshes or the provided web-based editor. Edit the UI while your app is running without needing to click “Preview.”
+
+
+
+ Event handling adds only 1-2ms to your Python code.
+
+
+
+ Use WebSockets to synchronize front-end and back-end states.
+
+
+
+ Non-blocking by default, with asynchronous event handling in a dedicated thread pool.
+
+
+
+ No CSS required for customization like shadows, button icons, and background colors.
+
+
+
+ Include HTML elements with custom CSS using the HTML Element component, which can serve as containers for built-in components.
+
+
+
+To get started, head to [Quickstart](/framework/quickstart) or our tutorials:
+
+
+
+ Generate multiple social media posts in a click of button using our social media generator.
+
+
+ Using Knowledge Graph, our graph-based RAG solution, you can build chat assistants to quickly ask questions using your data sources.
+
+
+ Build real-time digital shelves for hundreds of products that are automatically customized for different e-retailers.
+
+
-Framework is an open-source framework for creating data apps. Build user interfaces using a visual editor; write the backend code in Python.
-
-![Framework Builder screenshot](/framework/public/builder.png)
-
-It's an alternative to Plotly Dash, Streamlit and Gradio. Its focused on the creation of web applications for data analytics and machine learning.
-
-It aims to be as simple as Streamlit, but faster, more flexible and with a cleaner, easily-testable syntax. It provides separation of concerns between UI and business logic, enabling more complex applications.
-
-## Highlights
-
-### Simple
-Event handlers are defined as plain, easily-testable Python functions.
-
-```py
-def handle_increment(state):
- state["counter"] += 1
-
-wf.init_state({
- "counter": 0
-})
-```
-
-The event handler and state are linked to the UI using Framework Builder, the framework's visual editor.
-
-![Counter Increment example](/framework/public/introduction.counter.gif)
-
-### Developer-friendly
-- It's all contained in a standard Python package, just one `pip install` away.
-- User interfaces are saved as JSON, so they can be version controlled together with the rest of the application.
-- Use your local code editor and get instant refreshes when you save your code. Alternatively, use the provided web-based editor.
-- You edit the UI while your app is running. No hitting "Preview" and seeing something completely different to what you expected.
-
-### Fast
-- Event handling adds minimal overhead to your Python code (~1-2ms*).
-- Streaming (WebSockets) is used to synchronise frontend and backend states.
-- The script only runs once.
-- Non-blocking by default. Events are handled asynchronously in a thread pool running in a dedicated process.
-
-*End-to-end figure, including DOM mutation. Tested locally on a Macbook Air M2. [Measurement methodology](https://medium.com/@ramiromedina/measuring-time-elapsed-between-an-event-and-its-associated-dom-mutation-80431ad576e1).
-
-### Flexible
-- Elements are highly customisable with no CSS required, allowing for shadows, button icons, background colours, etc.
-- HTML elements with custom CSS can be included using the _HTML Element_ component. They can serve as containers for built-in components.
diff --git a/docs/framework/page-routes.mdx b/docs/framework/page-routes.mdx
index 6de994b24..8b7e17ac7 100644
--- a/docs/framework/page-routes.mdx
+++ b/docs/framework/page-routes.mdx
@@ -2,7 +2,7 @@
title: "Page routes"
---
-Framework apps can have multiple pages, with parametrised routes. Pages can be switched from the frontend or the backend.
+Framework apps can have multiple pages, with parametrised routes. Pages can be switched from the front-end or the back-end.
## Basic navigation
@@ -14,7 +14,7 @@ For basic page changes, assign a "Go to page" action to an event handler. For ex
### Backend-triggered page changes
-Trigger a page change from the backend using the `set_page` method of state.
+Trigger a page change from the back-end using the `set_page` method of state.
```py
# This event handler sends the user to a different page
@@ -35,7 +35,7 @@ You may want to share a URL that links directly to a specific resource within yo
You can do so by specifying parameters in the URL, known as route vars. Framework URLs contain the page key, followed by the route vars and their values. For example, `/#detailPage/product_id=32&country=AR`.
-### Adding vars to the URL from the backend
+### Adding vars to the URL from the back-end
You can set up variables that are displayed in the URL by passing a dictionary to the `set_route_vars` state method. Use `None` to clear specific keys.
diff --git a/docs/framework/product-description-generator.mdx b/docs/framework/product-description-generator.mdx
index 13c306246..6025a25b4 100644
--- a/docs/framework/product-description-generator.mdx
+++ b/docs/framework/product-description-generator.mdx
@@ -2,9 +2,9 @@
title: "Product description generator"
---
-In this tutorial, you will use Writer Framework to build a Saturn Snacks product description generator for a variety of food outlets. After adding the initial functionality of the app, you will also extend the app to include a chart of SEO keyword analysis and the ability for users to add their own food outlet.
+In this tutorial, you'll use the Writer Framework to build a Saturn Snacks product description generator for a variety of food outlets. After adding the initial functionality of the app, you'll also extend the app to include a chart of SEO keyword analysis and the ability for users to add their own food outlet.
-![Finished application](/framework/images/tutorial/pdg-finished-app.png)
+![Finished application](/framework/images/tutorial/product_desciption/pd_gen_1.png)
## Setting up your project
@@ -12,13 +12,13 @@ In this tutorial, you will use Writer Framework to build a Saturn Snacks product
### Creating a Writer app and getting your API key
-From the Home screen, click on Build an app.
+From the Home screen, click on **Build an app**.
-![Writer home screen](/framework/images/tutorial/home.png)
+![Writer home screen](/framework/images/tutorial/product_desciption/pd_gen_2.png)
Select Framework as the app type you’d like to create, enabling you to generate keys and build your app with the Writer Framework.
-![App type selection](/framework/images/tutorial/app-selection.png)
+![App type selection](/framework/images/tutorial/product_desciption/pd_gen_3.png)
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.
@@ -27,62 +27,57 @@ On the next screen, you can edit your Writer application name in the upper left.
Next, open your terminal and navigate to the directory where you want to create your application directory.
-To pass your API key to Writer Framework, you will 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. On Mac and Linux, you can run the following command, replacing `key` with your API key:
+
+
+ To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`. Here’s how you can set this variable in your terminal session:
+
+ ```sh On macOS/Linux
+ export WRITER_API_KEY=[key]
+ ```
-```
-export WRITER_API_KEY=[key]
-```
-
-
-On Windows, use the following:
-
-
-```
-set WRITER_API_KEY=[key]
-```
-
-
-To create the application, run the following command:
-
-
-```
-writer create product-description-app --template=pdg-tutorial
-```
-
-
-This command will set up a new project called `product-description-app` in the specified directory using a template created for this tutorial. To edit your project, run this command:
-
-
-```
-writer edit product-description-app
-```
-
-
-This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API.
+ ```sh On Windows
+ set WRITER_API_KEY=[key]
+ ```
+
+
+
+ Run the following command to create your application. Replace `product-description-app` with your desired project name and `pdg-tutorial` with the template you wish to use:
-By default, the Writer Framework Builder is accessible at `localhost:3006`. If that port is in use, run the `edit` command again with the `port` flag to specify your desired port:
+ ```
+ writer create product-description-app --template=pdg-tutorial
+ ```
+ This command sets up a new project called `product-description-app` in the specified directory using a template designed for this tutorial.
+
+
+ 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:3006`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup.
-```
-writer edit product-description-app –port=3007
-```
+
+ ```bash Standard port
+ writer edit product-description-app
+ ```
+ ```bash Custom port
+ writer edit product-description-app –port=3007
+ ```
+
+
+
-Open this address in your browser to view your default application setup.
-## Introduction to the Application Setup
+## Introduction to the application setup
When you first start up the application, you're going to see two main layout items provided by the template:
-* A header component with the name of the application
-* A column container that will house most of the UI of the app
+1. A Header component with the name of the application
+2. A Column container that'll house most of the UI of the app
-The left column includes a form that has three text inputs and a button. These three text inputs are bound to corresponding state elements. The right column contains a message component for loading and status messages, as well as an empty tab container which you'll use to display the product descriptions of the various outlets.
+The left column includes a form that has three text inputs and a button. These three text inputs are bound to corresponding state elements. The right column contains a Message component for loading and status messages, as well as an empty Tab container which you'll use to display the product descriptions of the various outlets.
### Code overview
-Looking at the code in `main.py`, you'll see that the template already imported the Writer Framework, the AI module, and the product description prompts that you will use throughout this tutorial.
+Looking at the code in `main.py`, you'll see that the template already imported the Writer Framework, the AI module, and the product description prompts that you'll use throughout this tutorial.
```python
import writer as wf
@@ -105,99 +100,110 @@ wf.init_state({
})
```
-The form elements and the message have been initialized as strings. You will add to this state dictionary throughout the tutorial.
+The form elements and the message have been initialized as strings. You'll add to this state dictionary throughout the tutorial.
-## Implementing the Product Description Generator
+## Implementing the Product description generator
Your first goal is to generate product descriptions for the various food outlets, with each outlet appearing as a tab to the right of the form.
-![Finished product description tabs](/framework/images/tutorial/pdg-finished-descriptions.png)
+![Finished product description tabs](/framework/images/tutorial/product_desciption/pd_gen_4.png)
### Setting up the code
-First, paste the following method on line 5 after all of the imports:
-
-```python
-def _generate_product_description(base_prompt, product_info):
- prompt = base_prompt.format(**product_info)
- description = writer.ai.complete(prompt)
- return description
-```
-
-This helper function accepts a base prompt as well as the product information from the form on the page. Note that the product description helper has an underscore at the beginning of its name, indicating that it's a private method not exposed to the UI.
-
-Next, you’ll initialize some additional state elements and create a handler for the button on the form.
-
-First, update `wf.init_state()` to include the `product_description` dictionary:
-
-```python
-wf.init_state({
- "form": {
- "title": "",
- "description": "",
- "keywords": ""
- },
- "message": "Fill in the inputs and click \"Generate\" to get started.",
- "product_descriptions": {
- "visible": False,
- "outlets": {}
- }
-})
-```
-
-This dictionary includes a variable called `visible` that will allow you to control whether the product description tabs are hidden or visible. It also includes an empty dictionary called `outlets` to which the button click handler will add.
-
-To add the button click handler, paste the following method beneath `_generate_product_description`:
-
-```python
-def handle_click(state):
- state["product_descriptions"]["visible"] = False
-
-
- # Loop through all the base prompts to generate versions tailored to each outlet
- for outlet, base_prompt in base_prompts.items():
- state["message"] = f"% Generating product description for {outlet}..."
- product_description = _generate_product_description(base_prompt, state["form"].to_dict())
- state["product_descriptions"]["outlets"][outlet] = product_description
-
-
- state["product_descriptions"]["visible"] = True
- state["message"] = ""
-```
-
-This handler will loop through each imported base prompt, format it with the form information, and pass it to the helper method. It then adds the resulting product description to the `state["product_descriptions"]["outlets"]` dictionary. The click handler method also includes some UI touches, such as showing and hiding the product descriptions and adding and removing loading messages to the message component.
-
-
-### Setting Up the User Interface
-
-You can now set up the UI to iterate over the product descriptions dictionary and create tabs. To do this, open the User iInterface. In the toolkit, drag a Repeater component from the Other section into the empty Tab Container.
-
-Click on the Repeater component to open its component settings. Under Properties, add `@{product_descriptions.outlets}` as the Repeater object to be used for repeating the child components. Replace the default “Key variable name” with `itemID`. You can leave “Value variable name” as `item`.
-
-
-
-Next, from the Layout section of the toolkit, drag a Tab component into the Repeater. Click on the Tab to bring up the component settings and add `@{itemID}` to the Name property. This will display the outlet name on the tab.
-
-
-
-To display the resulting text from the call to the AI module, drag a Text component from the Content section of the Toolkit into the Tab. Click on the Text component to open the Component settings and set the Text property to `@{item}`. You may also choose to set “Use Markdown” to “yes.”
-
-
-
-To control the visibility of the Tab Container, click on the Tab Container to bring up its component settings. Scroll to the bottom and, under Visibility, click “Custom” and add `product_descriptions.visible` to the Visibility value input.
-
-
-
-Lastly, to wire up the form, click on the Generate button inside the form to bring up its component settings. In the Events section, select `handle_click` for the `wf-click` event.
-
-
-
-Now, click Preview in the top toolbar, enter some test data, and click the Generate button. You should see a loading message, as well as three example food outlets displayed in the tabs. The loading message should disappear when everything is loaded, and the tab should remain visible once the data has loaded.
-
-Great work!
-
+First, integrate new functionality into your code for generating product descriptions.
+
+
+
+ Paste the following method on line 5 after all of the imports to create a helper function for generating product descriptions:
+
+ ```python
+ def _generate_product_description(base_prompt, product_info):
+ prompt = base_prompt.format(**product_info)
+ description = writer.ai.complete(prompt)
+ return description
+ ```
+
+ This function, `_generate_product_description`, accepts a base prompt and the product information from a form on the page. The underscore at the beginning of its name indicates that it's a private method not exposed to the UI.
+
+
+ Update the `wf.init_state()` to include a `product_description` dictionary with visibility control and outlets for descriptions:
+
+ ```python
+ wf.init_state({
+ "form": {
+ "title": "",
+ "description": "",
+ "keywords": ""
+ },
+ "message": "Fill in the inputs and click \"Generate\" to get started.",
+ "product_descriptions": {
+ "visible": False,
+ "outlets": {}
+ }
+ })
+ ```
+
+ This setup includes a variable `visible` to control whether product description tabs are shown or hidden, and an empty dictionary `outlets` for storing descriptions.
+
+
+ Paste the following method beneath `_generate_product_description` to handle button clicks and generate descriptions:
+
+ ```python
+ def handle_click(state):
+ state["product_descriptions"]["visible"] = False
+
+ # Loop through all the base prompts to generate versions tailored to each outlet
+ for outlet, base_prompt in base_prompts.items():
+ state["message"] = f"% Generating product description for {outlet}..."
+ product_description = _generate_product_description(base_prompt, state["form"].to_dict())
+ state["product_descriptions"]["outlets"][outlet] = product_description
+
+ state["product_descriptions"]["visible"] = True
+ state["message"] = ""
+ ```
+
+ This handler will loop through each imported base prompt, format it with the form information, and pass it to the helper method. The handler also manages UI interactions, such as displaying and hiding product descriptions and managing loading messages.
+
+
+
+### Setting up the user interface
+
+You can now set up the UI to iterate over the product descriptions dictionary and create tabs. Begin by opening the User Interface.
+
+
+
+ In the toolkit, drag a Repeater component from the Other section into the empty Tab Container. Click on the Repeater component to open its component settings. Under Properties, add `@{product_descriptions.outlets}` as the Repeater object to be used for repeating the child components. Replace the default “Key variable name” with `itemID`. You can leave “Value variable name” as `item`.
+
+ ![Repeater settings](/framework/images/tutorial/product_desciption/pd_gen_5.png)
+
+
+ From the Layout section of the toolkit, drag a Tab component into the Repeater. Click on the Tab to bring up the component settings and add `@{itemID}` to the Name property to display the outlet name on the tab.
+
+ ![Tab settings](/framework/images/tutorial/product_desciption/pd_gen_6.png)
+
+
+ Drag a Text component from the Content section of the Toolkit into the Tab. Click on the Text component to open the Component settings and set the Text property to `@{item}`. You may also choose to set “Use Markdown” to “yes.”
+
+ ![Text settings](/framework/images/tutorial/product_desciption/pd_gen_7.png)
+
+
+ Click on the Tab container to bring up its Component settings. Scroll to the bottom and, under Visibility, click “Custom” and add `product_descriptions.visible` to the Visibility value input.
+
+ ![Tab container visibility settings](/framework/images/tutorial/product_desciption/pd_gen_8.png)
+
+
+ Click on the Generate button inside the form to bring up its Component settings. In the Events section, select `handle_click` for the `wf-click` event.
+
+ ![Button settings](/framework/images/tutorial/product_desciption/pd_gen_9.png)
+
+
+ Click **Preview** in the top toolbar, enter some test data, and click the **Generate** button. You should see a loading message, as well as three example food outlets displayed in the tabs. The loading message should disappear when everything is loaded, and the tab should remain visible once the data has loaded.
+
+ Great work!
+
+
## Expanding the application: SEO keyword analysis
@@ -236,15 +242,15 @@ This code sets the loading message and passes all of the product descriptions to
### Adding SEO analysis to the UI
-To update the UI to display this chart, first drag a new tab from the Layout section of the toolkit into the Tab Container. This tab should not be inside of the Repeater, but can be either before or after it. Click on the tab to open the component settings, and change the name to “SEO Analysis.” If you'd like, you can also set the Visibility to “Custom” and set `seo_analysis` as the visibility value.
+To update the UI to display this chart, first drag a new tab from the Layout section of the toolkit into the Tab container. This tab should not be inside of the Repeater, but can be either before or after it. Click on the tab to open the component settings, and change the name to “SEO Analysis.” If you'd like, you can also set the Visibility to “Custom” and set `seo_analysis` as the visibility value.
-
+![SEO Tab](/framework/images/tutorial/product_desciption/pd_gen_10.png)
-To display the chart, drag a Plotly Graph component from the Content section of the toolkit into your new Tab. Click on the component to bring up the component settings. The Plotly graph component accepts a graph specification. Add `@{seo_analysis}` to pass the LLM-generated graph specification to the component.
+To display the chart, drag a Plotly graph component from the Content section of the toolkit into your new tab. Click on the component to bring up the component settings. The Plotly graph component accepts a graph specification. Add `@{seo_analysis}` to pass the LLM-generated graph specification to the component.
Click preview, add some data to the form, and click generate. You should see a new SEO analysis tab appear with a nicely formatted and labeled chart.
-![SEO analysis tab and chart](/framework/images/tutorial/pdg-seo-chart.png)
+![SEO analysis tab and chart](/framework/images/tutorial/product_desciption/pd_gen_11.png)
## Extending the application: user-added outlet
@@ -260,8 +266,7 @@ To create the inputs for the form, drag a Text Input and a Number Input from the
Finally, add a Button from the Other section of the toolkit to the bottom of the new section. Click on the button and change the text to “Add and Generate.” You can also add `laps` or another [Material Symbols](https://fonts.google.com/icons) ID to the Icon input if you wish.
-
-
+![Add outlet form](/framework/images/tutorial/product_desciption/pd_gen_12.png)
### Updating the code
@@ -330,19 +335,27 @@ This method formats the input from both forms into the imported `user_prompt` an
### Binding the elements and handler to the UI
-
-Finally, bind the state elements and the click handler to the UI.
-
-First, click on the “Outlet name” Text Input and, under Binding, set the state element to `outlet_form.name`. Click on the “Character max” Text Input and change the state element binding to `outlet_form.character_max`. Then, click on the “Add and Generate” Button and select `handle_add_outlet` to the `wf-click` event in the Events section of the component settings.
-
-Finally, if you'd like to hide this form until some of the descriptions have been generated, click on the Section for the form, and in the Visibility section, click “Custom” and add `product_descriptions.visible` as the “Visibility value.”
+Finalize your setup by binding the state elements and configuring the click handler to the UI components.
+
+
+
+ - **Outlet Name**: Click on the “Outlet name” Text Input component. In the Binding section of the component settings, set the state element to `outlet_form.name`.
+ - **Character Max**: Move to the “Character max” Text Input. Update its state element binding to `outlet_form.character_max`.
+
+
+ Click on the **Add and Generate** Button. In the Events section of the component settings, select `handle_add_outlet` for the `wf-click` event.
+
+
+ To conditionally display the form based on whether descriptions have been generated, click on the Section containing the form. In the Visibility section, choose “Custom” and set `product_descriptions.visible` as the “Visibility value.”
+
+
### Testing the finished product
-To see the result of your hard work, click Preview in the top toolbar, enter some information into the original product description form, and click “Generate.” The “Add an outlet” form should appear once the product descriptions have been generated. Add a new example outlet name and a character max and click “Add and Generate.” You should see a new tab appear with your new outlet, as well as an updated SEO analysis chart.
+To see the result of your hard work, click **Preview** in the top toolbar, enter some information into the original product description form, and click **Generate**. The “Add an outlet” form should appear once the product descriptions have been generated. Add a new example outlet name and a character max and click “Add and Generate.” You should see a new tab appear with your new outlet, as well as an updated SEO analysis chart.
-![Finished application](/framework/images/tutorial/pdg-finished-app.png)
+![Finished application](/framework/images/tutorial/product_desciption/pd_gen_13.png)
You can add whatever additional form inputs you wish to the outlet form, but be sure to update `user_prompt` in the `prompts.py` file using your favorite editor.
@@ -359,9 +372,9 @@ writer deploy product-description-app
You’ll be prompted for your API key.
-Once the application has been deployed, the CLI will return with the URL of your live application.
+Once the application is deployed, the CLI will return with the URL of your live application.
## Conclusion
-You’ve now built a full application with Writer Framework and the Writer AI module. Congratulations! This application 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.
\ No newline at end of file
+You’ve now built a full application with the Writer Framework and the Writer AI module. Congratulations! This application 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.
\ No newline at end of file
diff --git a/docs/framework/quickstart-tutorial.mdx b/docs/framework/quickstart-tutorial.mdx
index 9d2aebcd7..a05f5d75a 100644
--- a/docs/framework/quickstart-tutorial.mdx
+++ b/docs/framework/quickstart-tutorial.mdx
@@ -128,7 +128,7 @@ initial_state = wf.init_state({
```
For now, only the Framework import is needed, but the rest will be used later on. Notice that after pasting this code into the editor, when we click on `Save and run`, the header of our application will immediately change to "Logistic regression visualiser". This is because the Header has in its text property the value `@{my_app.title}`, which is a template syntax that uses a value from the state.
-The rest of the keys and values from the `initial_state` are names and initial values for components that will be used for communication between the app UI and the backend. Assigning those keys to components is called binding, and it is done by clicking on each component and filling property "State element" in the "Binding" menu. Let's use these bindings for our 3 _Slider Input_ components:
+The rest of the keys and values from the `initial_state` are names and initial values for components that will be used for communication between the app UI and the back-end. Assigning those keys to components is called binding, and it is done by clicking on each component and filling property "State element" in the "Binding" menu. Let's use these bindings for our 3 _Slider Input_ components:
| Property | Value (Slider 1) | Value (Slider 2) | Value (Slider 3) |
| :-----------: | :------------------- | :------------------- | :------------------- |
@@ -212,7 +212,7 @@ The algorithm will generate one or many lines depending on how many groups we ha
def _line(x0, coef, intercept, c):
return (-(x0 * coef[c, 0]) - intercept[c]) / coef[c, 1]
```
-This function is a helper function meant to be used on the backend. The underscore at the beginning of its name tells Framework that this function is private, and the frontend won't know about its existence.
+This function is a helper function meant to be used on the back-end. The underscore at the beginning of its name tells Framework that this function is private, and the front-end won't know about its existence.
To make the plot more readable, let's quickly define some colours for our plots:
@@ -288,7 +288,7 @@ Now, let's create a plot for our logistic regressions. For that, we will use `pl
```
After saving and running the code, we should get something like this:
-But as you could notice, when we change our slider values, nothing happens. It's because our function is currently called only once on app initialisation and not after input changes. To change this behaviour, let's set the event handler for `wf-number-change` in all slider inputs to `update`, which is the name of our function in the Python code. Notice that `_line` is not visible there because it's private to the backend. If we would change its name to `line`, then it would be visible on this list.
+But as you could notice, when we change our slider values, nothing happens. It's because our function is currently called only once on app initialisation and not after input changes. To change this behaviour, let's set the event handler for `wf-number-change` in all slider inputs to `update`, which is the name of our function in the Python code. Notice that `_line` is not visible there because it's private to the back-end. If we would change its name to `line`, then it would be visible on this list.
Set also event handler for dropdown input `wf-option-change` and `wf-click` in the button, also to `update`.
diff --git a/docs/framework/getting-started.mdx b/docs/framework/quickstart.mdx
similarity index 66%
rename from docs/framework/getting-started.mdx
rename to docs/framework/quickstart.mdx
index 753738e85..4ff4193ed 100644
--- a/docs/framework/getting-started.mdx
+++ b/docs/framework/quickstart.mdx
@@ -5,7 +5,7 @@ title: "Quickstart"
## Install it and run the demo app
-It works on Linux, Mac and Windows. Python 3.9.2 or higher is required. We recommend using a virtual environment.
+It works on Linux, Mac, and Windows. Python 3.9.2 or higher is required. We recommend using a virtual environment.
@@ -38,9 +38,9 @@ writer create testapp
A Framework app is a folder with the following items.
-1. `main.py`. The entry point for the app. You can import anything you need from here.
-2. `ui.json`. Contains the UI component declarations. Maintained by Builder, the framework's visual editor.
-3. `static/`. This folder contains frontend-facing static files which you might want to distribute with your app. For example, images and stylesheets.
+1. `main.py` - The entry point for the app. You can import anything you need from here.
+2. `ui.json` - Contains the UI component declarations. Maintained by the Writer Framework's visual editor.
+3. `static/` - This folder contains front-end static files which you might want to distribute with your app. For example, images and stylesheets.
## Start the editor
@@ -53,10 +53,10 @@ writer edit [path]
writer edit testapp
```
-This command will provide you with a local URL which you can use to access Builder.
+This command will provide you with a local URL which you can use to access the Builder.
-It's not recommended to expose Framework to the Internet. If you need to access Builder remotely, we recommend setting up a SSH tunnel. By default, requests from non-local origins are rejected, as a security measure to protect against drive-by attacks.
+It's not recommended to expose Framework to the Internet. If you need to access the Builder remotely, we recommend setting up an SSH tunnel. By default, requests from non-local origins are rejected, as a security measure to protect against drive-by attacks.
If you need to disable this protection, use the flag `--enable-remote-edit`.
@@ -83,11 +83,11 @@ If you need to run Framework as a module, you can use the `writer.command_line`
python -m writer.command_line run my_app
```
-## Writer Cloud
+## Deploying on Writer cloud
Writer provides a quick and fast way to deploy your apps via the Writer cloud.
```sh
writer deploy
```
-You will be asked to enter your API key. To find your key please log into your [App Studio account](https://app.writer.com/aistudio) and either create a new framework app by going through the create app workflow or choose an existing framework app from your home screen. For other deployment options, see [Deploy with Docker](/framework/deploy-with-docker)
+You’ll be asked to enter your API key. To find your key, log in to your [AI Studio account](https://app.writer.com/aistudio) and either create a new framework app by going through the create app workflow or choose an existing framework app from your home screen. For other deployment options, see [Deploy with Docker](/framework/deploy-with-docker).
\ No newline at end of file
diff --git a/docs/framework/repeater.mdx b/docs/framework/repeater.mdx
index a849914d5..261618cfc 100644
--- a/docs/framework/repeater.mdx
+++ b/docs/framework/repeater.mdx
@@ -12,7 +12,7 @@ Each iteration is rendered with a different **context**, a dictionary containing
### Food Selector example
-![Repeater example](./images/repeater.example.png)
+![Repeater example](/framework/images/repeater.example.png)
Given the state below, the contents of _Repeater_ will be repeated 3 times. For the first iteration, `itemId` will equal `Banana`, and `item` will equal `{"type": "fruit", "colour": "yellow"}`. Components inside _Repeater_ will be able to access this data using references such as `@{itemId}` and `@{item.type}`.
diff --git a/docs/framework/social-post-generator.mdx b/docs/framework/social-post-generator.mdx
index f9c73b578..f3fbab824 100644
--- a/docs/framework/social-post-generator.mdx
+++ b/docs/framework/social-post-generator.mdx
@@ -2,9 +2,9 @@
title: "Social post generator"
---
-In this tutorial, you will use 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 application that generates social media posts and tags based on user input.
-![Finished social post generator application](/framework/images/tutorial/social-finished-app.png)
+![Finished social post generator application](/framework/images/tutorial/social_post/sp_gen_1.png)
## Setting up your project
@@ -12,65 +12,58 @@ In this tutorial, you will use Writer Framework to build an application that gen
### Creating a Writer app and getting your API key
-From the Home screen, click on Build an app.
+From the Home screen, click on **Build an app**.
-![Writer home screen](/framework/images/tutorial/home.png)
+![Writer home screen](/framework/images/tutorial/social_post/sp_gen_2.png)
-Select Framework as the app type you’d like to create, enabling you to generate keys and build your app with the Writer Framework.
+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
-![App type selection](/framework/images/tutorial/app-selection.png)
-
-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.
+![App type selection](/framework/images/tutorial/social_post/sp_gen_3.png)
+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.
### Creating the application
Next, open your terminal and navigate to the directory where you want to create your application directory.
-To pass your API key to Writer Framework, you will 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. On Mac and Linux, you can run the following command, replacing `key` with your API key:
-
-
-```
-export WRITER_API_KEY=[key]
-```
-
-
-On Windows, use the following:
-
-
-```
-set WRITER_API_KEY=[key]
-```
-
-
-To create the application, run the following command:
-
-
-```
-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. To edit your project, run this command:
-
-
-```
-writer edit social-generator
-```
-
-
-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:3006`. If that port is in use, run the `edit` command again with the `port` flag to specify your desired port:
-
-
-```
-writer edit social-generator –port=3007
-```
-
-
-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`. An easy way to do this is to export the variable for your terminal session.
+
+
+ ```sh On macOS/Linux
+ export WRITER_API_KEY=[key]
+ ```
+
+ ```sh On Windows
+ set WRITER_API_KEY=[key]
+ ```
+
+
+
+
+ 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.
+
+
+ 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:3006`. 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
+ ```
+
+ ```bash Custom port
+ writer edit social-generator –port=3007
+ ```
+
+
+
## Building the UI
@@ -89,111 +82,123 @@ wf.init_state({
})
```
-Click the provided Section component to open its Component Settings and clear out the default title.
+Click the provided Section component to open its Component settings and clear out the default title.
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)`.
-Next, drag a Text Input component into the Section and label it with something like, “Topic for social posts.” Then, drag a Button component underneath the Text Input. You can replace the button text in the Component Settings panel with “Generate posts.” In the Icon field, add “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.”
-To display loading messages, drag a Messages component underneath the Button. Click the component to bring up the component settings and update the Loading color to `RGB(212,255,242)`.
+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)`.
### Generated text layout
-You’ll next build the layout for where the generated text displays. Drag a new Section component inside of the current Section component underneath the Messages component. Delete the Title text and update the Container background color to `RGB(246,239,253)`.
+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)`.
Next, drag a Tags component from the Content section of the Toolkit into the new Section.
-Then, drag a Separator component underneath the Tags component.
+Then, drag a Separator component below the Tags component.
-Finally, drag a Text component underneath the button. This is where the text generated by the LLM will appear.
+Finally, drag a Text component below the button. This is where the text generated by the LLM will appear.
-![Initial UI layout](/framework/images/tutorial/social-initial-ui-layout.png)
+![Initial UI layout](/framework/images/tutorial/social_post/sp_gen_4.png)
## Updating the code
-With the UI set up, you’ll next add some code to add the user input to state, pass prompts to an LLM when the user clicks the button, and bind the resulting tags and text to state.
-
-First, open the code editor. The `ai-starter` template already imports the Writer Framework and Writer AI packages:
-
-
-```
-import writer as wf
-import writer.ai
-```
-
-
-To create the hashtags, you’ll also need a regular expression library called `re`. Import that library at the top of the file:
-
-
-```
-import re
-```
-
-
-Initialize your application state by adding items to the state dictionary for `posts`, `topic`, and `message`. Your `intial_state` will look like this:
-
-```python
-wf.init_state({
- "my_app": {
- "title": "SOCIAL POST GENERATOR"
- },
- "posts": "",
- "topic": "writing",
- "message": ""
-})
-```
-
-You can enter any default topic you wish.
-
-Next, add a method called `handle_button_click` to pass the user’s input to the Writer AI module. Copy and paste this code:
-
-```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)
-
- 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"] = ""
-```
-
-This code first sets `state["message"]` to some loading text for a better user experience. Then, it uses text interpolation to create a `prompt` variable for generating the social posts. This practice makes it easy to create long prompts without losing readability. Next, the method uses the AI module’s `complete` method with a string prompt as an argument to call Writer’s text completion endpoint to generate the social posts. It repeats the same process for generating the hashtags, with some regular expression magic to pull the hashtags into a dictionary. Finally, it clears the status message.
+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.
+
+
+
+ Open your code editor. The `ai-starter` template already includes necessary imports from the Writer Framework and Writer AI packages:
+
+ ```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:
+
+ ```
+ import re
+ ```
+
+
+ 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.
+
+
+ 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)
+
+ 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"] = ""
+ ```
+
+ This function sets a loading message, generates social posts and hashtags using AI, and clears the message once processing is complete.
+
+
## Binding to the UI components
The last task is to bind all of the state elements and the click handler to the UI.
-First, click on the Text Input component to bring up the Component Settings panel. In the Bindings section, type `topic`.
-
-
-
-Next, 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.
+
+
+ Click on the **Text Input component** to bring up the Component settings panel. In the Bindings section, type `topic`.
-
+ ![Text input settings](/framework/images/tutorial/social_post/sp_gen_5.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.
-Click on the Message component and set the Text to `@{message}`. Under Visibility, choose “Custom” and set it to `message`.
+ ![Text input settings](/framework/images/tutorial/social_post/sp_gen_6.png)
+
+
+ Click on the **Message component** and set the Text to `@{message}`. Under Visibility, choose “Custom” and set it to `message`.
+
+
+ Click on the **Tags component**. Under General, click “JSON” for Tags and set the object to `@{tags}`.
-Next, 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)
+
+
+ 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)
+
+
+ Finally, click on the **Section** that contains the Tags and Text components. Under Visibility, choose **Custom** and set it to `posts`.
+
+
-Then, 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.
+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)
-Finally, click on the Section that contains the Tags and Text components. Under Visibility, choose “Custom” and set it to `posts`.
-
-When you save and run this code, your social media posts should will generate in the text section you created earlier.
-
-![Finished social post generator application](/framework/images/tutorial/social-finished-app.png)
## Deploying the application
@@ -201,14 +206,14 @@ When you save and run this code, your social media posts should will generate in
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:
-```
+```sh
writer deploy social-generator
```
-Once the application has been deployed, the CLI will return with the URL of your live application.
+Once the application is deployed, the CLI will return with the URL of your live application.
## Conclusion
-That's all it takes to set up a basic application with the Writer platform. 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.
\ No newline at end of file
+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.
\ No newline at end of file
diff --git a/docs/framework/state-schema.mdx b/docs/framework/state-schema.mdx
index aae9be45a..92b8add75 100644
--- a/docs/framework/state-schema.mdx
+++ b/docs/framework/state-schema.mdx
@@ -99,7 +99,7 @@ initial_state = wf.init_state({
## Type checking
-A schema allows you to check the integrity of your backend using the type system.
+A schema allows you to check the integrity of your back-end using the type system.
The code below will raise an error with mypy.
```bash
diff --git a/docs/framework/stylesheets.mdx b/docs/framework/stylesheets.mdx
index ff2e36a9a..133a09602 100644
--- a/docs/framework/stylesheets.mdx
+++ b/docs/framework/stylesheets.mdx
@@ -2,13 +2,13 @@
title: "Stylesheets"
---
-The appearance of your application can be fully customised via CSS stylesheets. These are dynamically linked during runtime and can be switched from the backend, targeting all or specific sessions.
+The appearance of your application can be fully customised via CSS stylesheets. These are dynamically linked during runtime and can be switched from the back-end, targeting all or specific sessions.
## Importing a stylesheet
-Stylesheet imports are triggered via Framework's `mail`, similarly to other features discussed in [Backend-initiated actions](/backend-initiated-actions). When the import is triggered, the frontend downloads the specified stylesheet and creates a `style` element with its contents.
+Stylesheet imports are triggered via Framework's `mail`, similarly to other features discussed in [Backend-initiated actions](/framework/backend-initiated-actions). When the import is triggered, the front-end downloads the specified stylesheet and creates a `style` element with its contents.
-The `import_stylesheet` method takes the `stylesheet_key` and `path` arguments. The first works as an identifier that will let you override the stylesheet later if needed. The second is the path to the CSS file.The path specified needs to be available to the frontend, so storing it in the `/static` folder of your app is recommended.
+The `import_stylesheet` method takes the `stylesheet_key` and `path` arguments. The first works as an identifier that will let you override the stylesheet later if needed. The second is the path to the CSS file.The path specified needs to be available to the front-end, so storing it in the `/static` folder of your app is recommended.
The following code imports a stylesheet when handling an event.
@@ -27,22 +27,20 @@ initial_state = wf.init_state({
initial_state.import_stylesheet("theme", "/static/custom.css")
```
-
-Use versions to avoid caching
-
-During development time, stylesheets may be cached by your browser, preventing updates from being reflected. Append a querystring to bust the cache, e.g. use `/static/custom.css?3`.
-
+
+Use versions to avoid caching. During development time, stylesheets may be cached by your browser, preventing updates from being reflected. Append a querystring to bust the cache, e.g. use `/static/custom.css?3`.
+
## Applying CSS classes
-You can use the property *Custom CSS classes* in Builder's *Component Settings* to apply classes to a component, separated by spaces. Internally, this will apply the classes to the root HTML element of the rendered component.
+You can use the property *Custom CSS classes* in the Builder's *Component Settings* to apply classes to a component, separated by spaces. Internally, this will apply the classes to the root HTML element of the rendered component.
![Stylesheets - Component Settings](/framework/images/stylesheets.component-settings.png)
## Tips for effective stylesheets
-The CSS code for the class used earlier, `bubblegum`, can be found below. Note how the `!important` flag is used when targetting style attributes that are configurable via Builder. If the flag isn't included, these declarations will not work, because built-in Framework styling is of higher specificity.
+The CSS code for the class used earlier, `bubblegum`, can be found below. Note how the `!important` flag is used when targetting style attributes that are configurable via the Builder. If the flag isn't included, these declarations will not work, because built-in Framework styling is of higher specificity.
```css
.bubblegum {
@@ -58,9 +56,7 @@ The CSS code for the class used earlier, `bubblegum`, can be found below. Note h
```
-Component structure may change
-
-When targeting specific HTML elements inside components, take into account that the internal structure of components may change across Framework versions.
+Component structure may change. When targeting specific HTML elements inside components, take into account that the internal structure of components may change across Framework versions.
Alternatively, you can override Framework's style variables. This behaves slightly differently though; style variables are inherited by children components. For example, if a *Section* has been assigned the `bubblegum` class, its children will also have a pink background by default.
diff --git a/docs/framework/testing.mdx b/docs/framework/testing.mdx
index 7fb49a13f..534782319 100644
--- a/docs/framework/testing.mdx
+++ b/docs/framework/testing.mdx
@@ -27,8 +27,8 @@ artificial_state = StreamsyncState({
The code of a Framework application basically consists of two things:
-- Initial state
-- Event handlers
+1. Initial state
+2. Event handlers
It's straightforward to test both, as shown below.
diff --git a/mint.json b/mint.json
index 24444e694..96eb0d947 100644
--- a/mint.json
+++ b/mint.json
@@ -30,7 +30,7 @@
"group": "Getting started",
"pages": [
"docs/framework/introduction",
- "docs/framework/getting-started",
+ "docs/framework/quickstart",
"docs/framework/ai-module"
]
},