diff --git a/apps/ai-starter/README.md b/apps/ai-starter/README.md new file mode 100644 index 000000000..46b0f71ab --- /dev/null +++ b/apps/ai-starter/README.md @@ -0,0 +1,3 @@ +This app was created using Writer Framework. + +To learn more about it, visit https://developer.writer.com/framework diff --git a/apps/ai-starter/main.py b/apps/ai-starter/main.py new file mode 100644 index 000000000..1ce829bf6 --- /dev/null +++ b/apps/ai-starter/main.py @@ -0,0 +1,14 @@ +import writer as wf +import writer.ai + +# Welcome to Writer Framework! +# This template is a starting point for your AI apps. +# More documentation is available at https://developer.writer.com/framework + + +# Initialise the state +wf.init_state({ + "my_app": { + "title": "AI STARTER" + }, +}) \ No newline at end of file diff --git a/apps/ai-starter/pyproject.toml b/apps/ai-starter/pyproject.toml new file mode 100644 index 000000000..cc093aa90 --- /dev/null +++ b/apps/ai-starter/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "writer-framework-default" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10.0" +writer = {version = "^0.6.0"} + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/apps/ai-starter/static/README.md b/apps/ai-starter/static/README.md new file mode 100644 index 000000000..d5238a508 --- /dev/null +++ b/apps/ai-starter/static/README.md @@ -0,0 +1,8 @@ +# Serving static files + +You can use this folder to store files which will be served statically in the "/static" route. + +This is useful to store images and other files which will be served directly to the user of your application. + +For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". +You can use this relative route as the source in an Image component. diff --git a/apps/ai-starter/static/favicon.png b/apps/ai-starter/static/favicon.png new file mode 100644 index 000000000..43a14eb9f Binary files /dev/null and b/apps/ai-starter/static/favicon.png differ diff --git a/apps/ai-starter/ui.json b/apps/ai-starter/ui.json new file mode 100644 index 000000000..f127cddc9 --- /dev/null +++ b/apps/ai-starter/ui.json @@ -0,0 +1,54 @@ +{ + "metadata": { + "writer_version": "0.6.0rc3" + }, + "components": { + "root": { + "id": "root", + "type": "root", + "content": { + "appName": "AI Starter" + }, + "isCodeManaged": false, + "position": 0, + "handlers": {}, + "visible": true + }, + "c0f99a9e-5004-4e75-a6c6-36f17490b134": { + "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "type": "page", + "content": { + "pageMode": "compact" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "root", + "handlers": {}, + "visible": true + }, + "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": { + "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", + "type": "header", + "content": { + "text": "@{my_app.title}" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "handlers": {}, + "visible": true + }, + "ejpasds0o8qyjs1n": { + "id": "ejpasds0o8qyjs1n", + "type": "section", + "content": { + "title": "Section Title" + }, + "isCodeManaged": false, + "position": 1, + "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "handlers": {}, + "visible": true + } + } +} \ No newline at end of file diff --git a/apps/default/README.md b/apps/default/README.md index 45fcdaef8..46b0f71ab 100644 --- a/apps/default/README.md +++ b/apps/default/README.md @@ -1,21 +1,3 @@ -# Writer Framework app - -## Editing the project - -```bash -poetry install -poetry run writer edit . -``` - -## Running the project - -```bash -poetry run writer run . -``` - -## Deploying the project - -```bash -poetry run writer deploy . -``` +This app was created using Writer Framework. +To learn more about it, visit https://developer.writer.com/framework diff --git a/apps/default/main.py b/apps/default/main.py index 9e48fb9c7..8ead9564f 100644 --- a/apps/default/main.py +++ b/apps/default/main.py @@ -2,7 +2,7 @@ # This is a placeholder to get you started or refresh your memory. # Delete it or adapt it as necessary. -# Documentation is available at https://streamsync.cloud +# Documentation is available at https://developer.writer.com/framework # Shows in the log when the app starts print("Hello world!") diff --git a/apps/default/static/favicon.png b/apps/default/static/favicon.png index 33692a2ff..43a14eb9f 100644 Binary files a/apps/default/static/favicon.png and b/apps/default/static/favicon.png differ diff --git a/apps/hello/README.md b/apps/hello/README.md index 45fcdaef8..46b0f71ab 100644 --- a/apps/hello/README.md +++ b/apps/hello/README.md @@ -1,21 +1,3 @@ -# Writer Framework app - -## Editing the project - -```bash -poetry install -poetry run writer edit . -``` - -## Running the project - -```bash -poetry run writer run . -``` - -## Deploying the project - -```bash -poetry run writer deploy . -``` +This app was created using Writer Framework. +To learn more about it, visit https://developer.writer.com/framework diff --git a/apps/product-description-generator/README.md b/apps/product-description-generator/README.md new file mode 100644 index 000000000..46b0f71ab --- /dev/null +++ b/apps/product-description-generator/README.md @@ -0,0 +1,3 @@ +This app was created using Writer Framework. + +To learn more about it, visit https://developer.writer.com/framework diff --git a/apps/product-description-generator/main.py b/apps/product-description-generator/main.py new file mode 100644 index 000000000..06ac00424 --- /dev/null +++ b/apps/product-description-generator/main.py @@ -0,0 +1,17 @@ +import writer as wf +import writer.ai +from prompts import base_prompts, seo_keywords, user_prompt + +# This is the base template for the Product Description Generator tutorial. +# More documentation is available at https://developer.writer.com/framework + +# Initialize state here +wf.init_state({ + "form": { + "title": "", + "description": "", + "keywords": "" + }, + "message": "Fill in the inputs and click \"Generate\" to get started.", +}) + diff --git a/apps/product-description-generator/prompts.py b/apps/product-description-generator/prompts.py new file mode 100644 index 000000000..8869eb552 --- /dev/null +++ b/apps/product-description-generator/prompts.py @@ -0,0 +1,66 @@ +# BASE PROMPTS (TAILORED TO EACH COMPANY) + +base_prompts = {} + +base_prompts["Testco"] = """ +System Instruction: Create enticing product description pages for Saturn Snacking products to be featured on the Testco supermarket website in the UK. Utilize the provided data fields to ensure each description is comprehensive and tailored to the Saturn Snacking brand. The aim is to make these product offerings more attractive to customers, thereby increasing purchases. + +Prompt Details: Use the provided input data: + +{title} +{description} + +to generate detailed and enticing product descriptions. + +""" + +base_prompts["SuperEats"] = """ +System Instruction: Create enticing product description pages for Saturn Snacking products to be featured on the Super Eats platform, which is an online platform usually accessed via mobile phones. Utilize the provided data fields to ensure each description is comprehensive and tailored to the Saturn Snacking brand. The aim is to make these product offerings more attractive to customers, thereby increasing purchases. + +The description should be engaging, informative, and limited to a maximum of three sentences. This brief format should be suitable for customers browsing on the Super Eats platform, providing them with enough information to make an informed choice quickly. + +Prompt Details: Use the provided input data: + +{title} +{description} + +to generate one compact and enticing product description. + +""" + +base_prompts["Midl"] = """ +System Instruction: Create enticing product description pages for Saturn Snacking products to be featured on the Midl discount supermarket from Germany. Utilize the provided data fields to ensure each description is comprehensive and tailored to the Saturn Snacking brand. The aim is to make these product offerings more attractive to customers, thereby increasing purchases. + +Prompt Details: Use the provided input data: + +{title} +{description} + +to generate compact and enticing product descriptions. + +""" + +### SEO KEYWORD PROMPT +seo_keywords = """ +System Instruction: Analyze a group of product descriptions and identify the SEO keywords that are most frequently used. This analysis will help in understanding the common themes and keywords that are essential for optimizing the product descriptions for search engines. + +Prompt Details: Examine the following product descriptions and identify the SEO keywords that are most frequently used. + +{descriptions} + +Provide Plotly.js graph specification that includes a JSON dictionary of the top 10 keywords and the number of times they appear in the descriptions. The graph should be visually appealing and easy to interpret, highlighting the most relevant keywords. Use #5551ff as the color for the bars in the graph. + +""" + +# USER PROMPT TEMPLATE +user_prompt = """ +System Instruction: Create enticing product description pages for Saturn Snacking products to be featured on the {name} supermarket website. Utilize the provided data fields to ensure each description is comprehensive and tailored to the Saturn Snacking brand. The aim is to make these product offerings more attractive to customers, thereby increasing purchases. + +Prompt Details: Use the provided input data: + +{title} +{description} + +to generate detailed and enticing product descriptions. Ensure the description is no longer than {character_max} characters. + +""" \ No newline at end of file diff --git a/apps/product-description-generator/pyproject.toml b/apps/product-description-generator/pyproject.toml new file mode 100644 index 000000000..cc093aa90 --- /dev/null +++ b/apps/product-description-generator/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "writer-framework-default" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10.0" +writer = {version = "^0.6.0"} + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/apps/product-description-generator/static/README.md b/apps/product-description-generator/static/README.md new file mode 100644 index 000000000..d5238a508 --- /dev/null +++ b/apps/product-description-generator/static/README.md @@ -0,0 +1,8 @@ +# Serving static files + +You can use this folder to store files which will be served statically in the "/static" route. + +This is useful to store images and other files which will be served directly to the user of your application. + +For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". +You can use this relative route as the source in an Image component. diff --git a/apps/product-description-generator/static/favicon.png b/apps/product-description-generator/static/favicon.png new file mode 100644 index 000000000..43a14eb9f Binary files /dev/null and b/apps/product-description-generator/static/favicon.png differ diff --git a/apps/product-description-generator/ui.json b/apps/product-description-generator/ui.json new file mode 100644 index 000000000..ecff21b01 --- /dev/null +++ b/apps/product-description-generator/ui.json @@ -0,0 +1,169 @@ +{ + "metadata": { + "writer_version": "0.6.0rc3" + }, + "components": { + "root": { + "id": "root", + "type": "root", + "content": { + "appName": "Product Description Generator" + }, + "isCodeManaged": false, + "position": 0, + "handlers": {}, + "visible": true + }, + "c0f99a9e-5004-4e75-a6c6-36f17490b134": { + "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "type": "page", + "content": { + "pageMode": "compact" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "root", + "handlers": {}, + "visible": true + }, + "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": { + "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", + "type": "header", + "content": { + "text": "PRODUCT DESCRIPTION GENERATOR" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "handlers": {}, + "visible": true + }, + "wf7ucshlh1xd01rd": { + "id": "wf7ucshlh1xd01rd", + "type": "columns", + "content": {}, + "isCodeManaged": false, + "position": 1, + "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "handlers": {}, + "visible": true + }, + "wki2gol8f69jo2wr": { + "id": "wki2gol8f69jo2wr", + "type": "column", + "content": { + "width": "1" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "wf7ucshlh1xd01rd", + "handlers": {}, + "visible": true + }, + "dtueaw3s6jucxztj": { + "id": "dtueaw3s6jucxztj", + "type": "column", + "content": { + "width": "2" + }, + "isCodeManaged": false, + "position": 1, + "parentId": "wf7ucshlh1xd01rd", + "handlers": {}, + "visible": true + }, + "9951wujx1y63miao": { + "id": "9951wujx1y63miao", + "type": "section", + "content": { + "title": "" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "wki2gol8f69jo2wr", + "handlers": {}, + "visible": true + }, + "ot9efvpwoh7haksz": { + "id": "ot9efvpwoh7haksz", + "type": "tabs", + "content": {}, + "isCodeManaged": false, + "position": 1, + "parentId": "dtueaw3s6jucxztj", + "handlers": {} + }, + "kyvrec2azudr740n": { + "id": "kyvrec2azudr740n", + "type": "message", + "content": { + "message": "@{message}" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "dtueaw3s6jucxztj", + "handlers": {}, + "visible": true + }, + "c8um3pjausohruax": { + "id": "c8um3pjausohruax", + "type": "textinput", + "content": { + "label": "Name" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "9951wujx1y63miao", + "handlers": {}, + "visible": true, + "binding": { + "eventType": "wf-change", + "stateRef": "form.title" + } + }, + "s3aqi4bi8p4nc871": { + "id": "s3aqi4bi8p4nc871", + "type": "textinput", + "content": { + "label": "Description" + }, + "isCodeManaged": false, + "position": 1, + "parentId": "9951wujx1y63miao", + "handlers": {}, + "visible": true, + "binding": { + "eventType": "wf-change", + "stateRef": "form.description" + } + }, + "g9c82lcp1o8v0wof": { + "id": "g9c82lcp1o8v0wof", + "type": "button", + "content": { + "text": "Generate", + "icon": "laps" + }, + "isCodeManaged": false, + "position": 3, + "parentId": "9951wujx1y63miao", + "visible": true + }, + "zg72wu4k9394ah9q": { + "id": "zg72wu4k9394ah9q", + "type": "textinput", + "content": { + "label": "Keywords" + }, + "isCodeManaged": false, + "position": 2, + "parentId": "9951wujx1y63miao", + "handlers": {}, + "visible": true, + "binding": { + "eventType": "wf-change", + "stateRef": "form.keywords" + } + } + } +} \ No newline at end of file diff --git a/apps/quickstart/README.md b/apps/quickstart/README.md index 268c2e7bd..46b0f71ab 100644 --- a/apps/quickstart/README.md +++ b/apps/quickstart/README.md @@ -1,22 +1,3 @@ -# Writer Framework app - -## Editing the project - -```bash -poetry install -poetry run writer edit . -``` - -## Running the project - -```bash -poetry run writer run . -``` - -## Deploying the project - -```bash -poetry run writer deploy . -``` - +This app was created using Writer Framework. +To learn more about it, visit https://developer.writer.com/framework diff --git a/apps/quickstart/pyproject.toml b/apps/quickstart/pyproject.toml index edab2f446..cc093aa90 100644 --- a/apps/quickstart/pyproject.toml +++ b/apps/quickstart/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "writer-framework-quickstart" +name = "writer-framework-default" version = "0.1.0" description = "" authors = ["Your Name "] diff --git a/apps/quickstart/static/favicon.png b/apps/quickstart/static/favicon.png index 33692a2ff..43a14eb9f 100644 Binary files a/apps/quickstart/static/favicon.png and b/apps/quickstart/static/favicon.png differ diff --git a/apps/text-demo/README.md b/apps/text-demo/README.md new file mode 100644 index 000000000..46b0f71ab --- /dev/null +++ b/apps/text-demo/README.md @@ -0,0 +1,3 @@ +This app was created using Writer Framework. + +To learn more about it, visit https://developer.writer.com/framework diff --git a/apps/text-demo/main.py b/apps/text-demo/main.py new file mode 100644 index 000000000..6fcf9bb26 --- /dev/null +++ b/apps/text-demo/main.py @@ -0,0 +1,29 @@ +import re + +import writer as wf +import writer.ai + +# Welcome to Writer Framework! +# This is a simple app to get you started with text completion. +# More documentation is available at https://developer.writer.com + +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"] = "" + +# Initialize state here + +wf.init_state({ + "posts": "", + "topic": "writing", + "message": "" +}) \ No newline at end of file diff --git a/apps/text-demo/pyproject.toml b/apps/text-demo/pyproject.toml new file mode 100644 index 000000000..cc093aa90 --- /dev/null +++ b/apps/text-demo/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "writer-framework-default" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10.0" +writer = {version = "^0.6.0"} + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/apps/text-demo/static/README.md b/apps/text-demo/static/README.md new file mode 100644 index 000000000..d5238a508 --- /dev/null +++ b/apps/text-demo/static/README.md @@ -0,0 +1,8 @@ +# Serving static files + +You can use this folder to store files which will be served statically in the "/static" route. + +This is useful to store images and other files which will be served directly to the user of your application. + +For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". +You can use this relative route as the source in an Image component. diff --git a/apps/text-demo/static/favicon.png b/apps/text-demo/static/favicon.png new file mode 100644 index 000000000..43a14eb9f Binary files /dev/null and b/apps/text-demo/static/favicon.png differ diff --git a/apps/text-demo/ui.json b/apps/text-demo/ui.json new file mode 100644 index 000000000..f5b5c5e8b --- /dev/null +++ b/apps/text-demo/ui.json @@ -0,0 +1,149 @@ +{ + "metadata": { + "writer_version": "0.6.0rc4" + }, + "components": { + "root": { + "id": "root", + "type": "root", + "content": { + "appName": "Social Post Generator Demo", + "cssClasses": "" + }, + "isCodeManaged": false, + "position": 0, + "handlers": {}, + "visible": true + }, + "c0f99a9e-5004-4e75-a6c6-36f17490b134": { + "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "type": "page", + "content": { + "pageMode": "compact" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "root", + "handlers": {}, + "visible": true + }, + "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": { + "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", + "type": "header", + "content": { + "text": "SOCIAL POST GENERATOR" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "handlers": {}, + "visible": true + }, + "q237uluwl8idg8is": { + "id": "q237uluwl8idg8is", + "type": "text", + "content": { + "text": "@{posts}", + "useMarkdown": "yes" + }, + "isCodeManaged": false, + "position": 2, + "parentId": "4k8f6ju68i8qej2s", + "handlers": {}, + "visible": "" + }, + "xszjpksyo1sfdu0c": { + "id": "xszjpksyo1sfdu0c", + "type": "section", + "content": { + "title": "", + "containerBackgroundColor": "#EDE2FF", + "accentColor": "#721CC9" + }, + "isCodeManaged": false, + "position": 1, + "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", + "handlers": {}, + "visible": true + }, + "3fbhifcljb3mqnpv": { + "id": "3fbhifcljb3mqnpv", + "type": "textinput", + "content": { + "label": "Generate social posts about..." + }, + "isCodeManaged": false, + "position": 0, + "parentId": "xszjpksyo1sfdu0c", + "visible": true, + "binding": { + "eventType": "wf-change", + "stateRef": "topic" + } + }, + "v8o8h5estbx22uw7": { + "id": "v8o8h5estbx22uw7", + "type": "button", + "content": { + "text": "Generate posts", + "icon": "arrow_forward" + }, + "isCodeManaged": false, + "position": 1, + "parentId": "xszjpksyo1sfdu0c", + "handlers": { + "wf-click": "handle_button_click" + }, + "visible": true + }, + "hhmi0u8nksz57cbc": { + "id": "hhmi0u8nksz57cbc", + "type": "message", + "content": { + "message": "@{message}", + "loadingColor": "#D4FFF2" + }, + "isCodeManaged": false, + "position": 2, + "parentId": "xszjpksyo1sfdu0c", + "handlers": {}, + "visible": "message" + }, + "4k8f6ju68i8qej2s": { + "id": "4k8f6ju68i8qej2s", + "type": "section", + "content": { + "title": "", + "containerBackgroundColor": "#F6EFFD", + "containerShadow": "none" + }, + "isCodeManaged": false, + "position": 3, + "parentId": "xszjpksyo1sfdu0c", + "handlers": {}, + "visible": "posts" + }, + "o1753ot0w2n4704o": { + "id": "o1753ot0w2n4704o", + "type": "tags", + "content": { + "tags": "@{tags}" + }, + "isCodeManaged": false, + "position": 0, + "parentId": "4k8f6ju68i8qej2s", + "handlers": {}, + "visible": true + }, + "ywzboz2fup220b1d": { + "id": "ywzboz2fup220b1d", + "type": "separator", + "content": {}, + "isCodeManaged": false, + "position": 1, + "parentId": "4k8f6ju68i8qej2s", + "handlers": {}, + "visible": true + } + } +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index c5204ecde..a1c893e5e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -44,13 +44,13 @@ doc = ["docutils", "jinja2", "myst-parser", "numpydoc", "pillow (>=9,<10)", "pyd [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] @@ -1046,22 +1046,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "polars" -version = "0.20.26" +version = "0.20.29" description = "Blazingly fast DataFrame library" optional = false python-versions = ">=3.8" files = [ - {file = "polars-0.20.26-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:97d0e4b6ab6b47fa07798b447189ee9505d2085ec1a64a6aa8a65fdd429cd49f"}, - {file = "polars-0.20.26-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c270e366b4d8b672b204e7d48e39d255641d3d2b7bdc3a0ccd968cf53934657f"}, - {file = "polars-0.20.26-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db35d6eed508256a797c7f1b8e9dec4aae9c11b891797b2d38fac5627d072d34"}, - {file = "polars-0.20.26-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:25b00bd5cf44929722aa6389706559c5e8cedd6db2cfc38b27b706ed37e1b2af"}, - {file = "polars-0.20.26-cp38-abi3-win_amd64.whl", hash = "sha256:b22063acc815bc5c6d2e24292ff771ca0df306ecf97e8f6899924a1ec6d3f136"}, - {file = "polars-0.20.26.tar.gz", hash = "sha256:fa83d130562a5180a47f8763a7bb9f408dbbf51eafc1380e8a2951be8ce05a2c"}, + {file = "polars-0.20.29-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:45e0de4839fd45385781f5bc1bf3d53571df16e04aa6722af094d34f689efaf1"}, + {file = "polars-0.20.29-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:438e4c7e24a6d84dbebbe7a652dd818311ed23b7a3da384716b1db0f6d152e2a"}, + {file = "polars-0.20.29-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46dd26dd34cf4688025f821ac0591fc7fde24d6468c034228b0f74b76bb1bee3"}, + {file = "polars-0.20.29-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:ac94306437435047c7530bb13f8a5a89b22dff18fc9a7d46380ae96eb1a04714"}, + {file = "polars-0.20.29-cp38-abi3-win_amd64.whl", hash = "sha256:cd9b4728beb4744d0cfd947d08f5a1ce2cec9529208645e950299a5297b4b0bc"}, + {file = "polars-0.20.29.tar.gz", hash = "sha256:79d9789afc33b0618400790f66045e590be61d0f47f45567a63e1b8b76ac0382"}, ] [package.extras] adbc = ["adbc-driver-manager", "adbc-driver-sqlite"] -all = ["polars[adbc,async,cloudpickle,connectorx,deltalake,fastexcel,fsspec,gevent,iceberg,numpy,pandas,plot,pyarrow,pydantic,sqlalchemy,timezone,torch,xlsx2csv,xlsxwriter]"] +all = ["polars[adbc,async,cloudpickle,connectorx,deltalake,fastexcel,fsspec,gevent,iceberg,numpy,pandas,plot,pyarrow,pydantic,sqlalchemy,timezone,xlsx2csv,xlsxwriter]"] async = ["nest-asyncio"] cloudpickle = ["cloudpickle"] connectorx = ["connectorx (>=0.3.2)"] @@ -1080,7 +1080,6 @@ pydantic = ["pydantic"] pyxlsb = ["pyxlsb (>=1.0)"] sqlalchemy = ["pandas", "sqlalchemy"] timezone = ["backports-zoneinfo", "tzdata"] -torch = ["torch"] xlsx2csv = ["xlsx2csv (>=0.8.0)"] xlsxwriter = ["xlsxwriter"] @@ -1451,13 +1450,13 @@ rpds-py = ">=0.7.0" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.2" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"}, + {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"}, ] [package.dependencies] @@ -1751,13 +1750,13 @@ files = [ [[package]] name = "types-requests" -version = "2.31.0.20240406" +version = "2.32.0.20240523" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, - {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, + {file = "types-requests-2.32.0.20240523.tar.gz", hash = "sha256:26b8a6de32d9f561192b9942b41c0ab2d8010df5677ca8aa146289d11d505f57"}, + {file = "types_requests-2.32.0.20240523-py3-none-any.whl", hash = "sha256:f19ed0e2daa74302069bbbbf9e82902854ffa780bc790742a810a9aaa52f65ec"}, ] [package.dependencies] @@ -1765,13 +1764,13 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 1640630a6..653029d1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "writer" -version = "0.6.0rc3" +version = "0.6.0rc4" description = "Writer Framework helps you create performant data apps, via Python code and its built-in visual UI editor." authors = ["Writer, Inc. "] readme = "README.md" diff --git a/src/ui/src/wds/WdsButton.vue b/src/ui/src/wds/WdsButton.vue index 7b5258230..392b70759 100644 --- a/src/ui/src/wds/WdsButton.vue +++ b/src/ui/src/wds/WdsButton.vue @@ -21,6 +21,7 @@ button { color: var(--buttonTextColor); cursor: pointer; box-shadow: var(--buttonShadow); + outline: none; } button:hover, diff --git a/src/writer/ai.py b/src/writer/ai.py index 96ce9eb7c..acefefdc7 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -3,6 +3,7 @@ from httpx import Timeout from writerai import Writer +from writerai._exceptions import WriterError from writerai._streaming import Stream from writerai._types import Body, Headers, NotGiven, Query from writerai.types import Chat, Completion, StreamingData @@ -70,13 +71,20 @@ def __init__(self, token: Optional[str] = None): :param token: Optional; the default token for API authentication used if WRITER_API_KEY environment variable is not set up. + :raises RuntimeError: If an API key was not provided to initialize SDK client properly. """ - - self.client = Writer( - # This is the default and can be omitted - api_key=token, - ) - + try: + self.client = Writer( + # This is the default and can be omitted + api_key=token, + ) + except WriterError: + raise RuntimeError( + "Failed to acquire Writer API key. " + + "Provide it by either setting a WRITER_API_KEY" + + " environment variable, or by initializing the" + + " AI module explicitly: writer.ai.init(\"my-writer-api-key\")" + ) from None current_process = get_app_process() setattr(current_process, 'ai_manager', self) @@ -88,13 +96,11 @@ def acquire_instance(cls) -> 'WriterAIManager': :returns: The current instance of the manager. """ - instance: WriterAIManager current_process = get_app_process() - try: - instance = getattr(current_process, 'ai_manager') - except AttributeError: - # Instance was not initialized explicitly; creating a new one - instance = cls() + + # If instance was not created explicitly, we initialize a new one + instance: WriterAIManager = \ + getattr(current_process, 'ai_manager', cls()) return instance @classmethod diff --git a/src/writer/command_line.py b/src/writer/command_line.py index a9b37d57f..4225b016e 100644 --- a/src/writer/command_line.py +++ b/src/writer/command_line.py @@ -14,7 +14,7 @@ def main(): parser = argparse.ArgumentParser( description="Run, edit or create a Writer Framework app.") parser.add_argument("command", choices=[ - "run", "edit", "create", "hello", "deploy"]) + "run", "edit", "create", "hello", "deploy", "undeploy"]) parser.add_argument( "path", nargs="?", help="Path to the app's folder") parser.add_argument( @@ -48,7 +48,7 @@ def main(): _route(command, absolute_app_path, port, host, enable_remote_edit, enable_server_setup_hook, template_name, api_key) def _get_api_key(command, api_key: Optional[str]) -> Optional[str]: - if command in ("deploy") and api_key is None: + if command in ("deploy", "undeploy") and api_key is None: env_key = os.getenv("WRITER_API_KEY", None) if env_key is not None and env_key != "": return env_key @@ -95,6 +95,8 @@ def _route( host = "127.0.0.1" if command in ("deploy"): writer.deploy.deploy(absolute_app_path, api_key) + if command in ("undeploy"): + writer.deploy.undeploy(api_key) if command in ("edit"): writer.serve.serve( absolute_app_path, mode="edit", port=port, host=host, diff --git a/src/writer/deploy.py b/src/writer/deploy.py index d7e9361bf..fbc489f2a 100644 --- a/src/writer/deploy.py +++ b/src/writer/deploy.py @@ -11,17 +11,29 @@ WRITER_DEPLOY_URL = os.getenv("WRITER_DEPLOY_URL", "https://api.writer.com/v1/deployment/apps") -print("Deploying to:", WRITER_DEPLOY_URL) - def deploy(path, token): tar = pack_project(path) upload_package(tar, token) +def undeploy(token): + try: + print("Undeploying app") + with requests.delete(WRITER_DEPLOY_URL, headers={"Authorization": f"Bearer {token}"}) as resp: + resp.raise_for_status() + print("App undeployed") + sys.exit(0) + except Exception as e: + print("Error undeploying app") + print(e) + sys.exit(1) + def pack_project(path): print(f"Creating deployment package from path: {path}") files = [] - match = parse_gitignore(os.path.join(path, ".gitignore")) + def match(file_path) -> bool: return False + if os.path.exists(os.path.join(path, ".gitignore")): + match = parse_gitignore(os.path.join(path, ".gitignore")) for root, _, filenames in os.walk(path): for filename in filenames: if ".git" in root.split(os.path.sep): diff --git a/src/writer/serve.py b/src/writer/serve.py index 493102651..312922ce5 100644 --- a/src/writer/serve.py +++ b/src/writer/serve.py @@ -449,12 +449,23 @@ async def stream(websocket: WebSocket): def print_init_message(): - GREEN_TOKEN = "\033[92m" - END_TOKEN = "\033[0m" - - print(f"""{ GREEN_TOKEN } -Writer Framework v{VERSION} -{END_TOKEN}""") + print(f""" + + &@@@@@@@@@@ ,@@@@@@@@@@* @@@@@@@@@@ + .@@@@@@@@@@( &@@@@@@@@@@ *@@@@@@@@* + %@@@@@@@@@@ .@@@@@@@@@@( @@@@@@@@ + @@@@@@@@@@& #@@@@@@@@@@ @@@@@@ + ,@@@@@@@@@@, @@@@@@@@@@@ (@@@@( + &@@@@@@@@@@ .@@@@@@@@@@* @@@@ + @@@@@@@@@@% %@@@@@@@@@@ .@@, + (@@@@@@@@@@. @@@@@@@@@@% %& + @@@@@@@@@@& (@@@@@@@@@@ . + *@@@@@@@@@@, @@@@@@@@@@& + @@@@@@@@@@@ ,@@@@@@@@@@* + @@@@@@@@@@# %@@@@@@@@@@ + + +WRITER FRAMEWORK v{VERSION}""") def print_route_message(run_name: str, port: int, host: str): diff --git a/tests/backend/testapp/static/favicon.png b/tests/backend/testapp/static/favicon.png index 33692a2ff..43a14eb9f 100644 Binary files a/tests/backend/testapp/static/favicon.png and b/tests/backend/testapp/static/favicon.png differ diff --git a/tests/backend/testmultiapp/app1/static/favicon.png b/tests/backend/testmultiapp/app1/static/favicon.png index 33692a2ff..43a14eb9f 100644 Binary files a/tests/backend/testmultiapp/app1/static/favicon.png and b/tests/backend/testmultiapp/app1/static/favicon.png differ diff --git a/tests/backend/testmultiapp/app2/static/favicon.png b/tests/backend/testmultiapp/app2/static/favicon.png index 33692a2ff..43a14eb9f 100644 Binary files a/tests/backend/testmultiapp/app2/static/favicon.png and b/tests/backend/testmultiapp/app2/static/favicon.png differ