├── .gitignore ├── README.md ├── code-generator ├── README.md ├── extensions │ ├── style.css │ └── templates.umd.js ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ ├── custom.css │ ├── custom.js │ └── favicon.png └── ui.json ├── finance-content-checker ├── .wf │ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl │ ├── components-root.jsonl │ ├── components-workflows_root.jsonl │ └── metadata.json ├── README.md ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ ├── Writer_Logo_black.svg │ ├── custom.css │ └── favicon.png └── utils.py ├── finance-dashboard ├── .env.example ├── .wf │ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl │ ├── components-root.jsonl │ ├── components-workflows_root.jsonl │ └── metadata.json ├── README.md ├── charts.py ├── daily_APPLE.csv ├── daily_IBM.csv ├── earnings-data.json ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ ├── custom.css │ └── favicon.png └── stock_data.py ├── financial-tools-chat ├── .env.example ├── .wf │ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl │ ├── components-root.jsonl │ ├── components-workflows_root.jsonl │ └── metadata.json ├── README.md ├── chat_tools.py ├── earnings-data.json ├── earnings-prompt.txt ├── main.py ├── prompts.py ├── pyproject.toml └── static │ ├── README.md │ ├── custom.css │ ├── custom.js │ └── favicon.png ├── hacker-news-social-listener ├── .env.example ├── .gitignore ├── .wf │ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl │ ├── components-root.jsonl │ ├── components-workflows_root.jsonl │ └── metadata.json ├── README.md ├── main.py ├── prompts.py ├── pyproject.toml └── static │ ├── README.md │ ├── custom.css │ └── favicon.png ├── hf-spaces-starter ├── Dockerfile ├── README.md └── images │ ├── finance-dashboard-hf.png │ └── new_space.png ├── localized-promo-dashboard ├── README.md ├── data │ ├── default_segments.csv │ └── segments.csv ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ └── favicon.png └── ui.json ├── palmyra-comparison ├── .env.example ├── .wf │ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl │ ├── components-root.jsonl │ ├── components-workflows_root.jsonl │ ├── components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl │ └── metadata.json ├── README.md ├── main.py ├── pyproject.toml └── static │ ├── README.md │ ├── custom.css │ └── favicon.png ├── patient-portal ├── README.md ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ └── favicon.png └── ui.json ├── pd-embedded-chat ├── README.md ├── main.py ├── pyproject.toml ├── static │ ├── README.md │ ├── beverage-descriptions.csv │ ├── candy-descriptions.csv │ └── favicon.png └── ui.json ├── pdp-generator ├── README.md ├── data │ ├── output-html.html │ └── running_shoes.xlsx ├── html_template.py ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ ├── custom.css │ ├── favicon.png │ └── writer_logo.png └── ui.json ├── portfolio-rebalance ├── README.md ├── example_data │ ├── portfolio_data.pdf │ └── portfolio_data.xlsx ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ ├── favicon.png │ └── writer_logo.png └── ui.json ├── prescribing-info-app ├── .env.example ├── .wf │ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl │ ├── components-page-1-181cq1mpgw34i8dl.jsonl │ ├── components-root.jsonl │ ├── components-workflows_root.jsonl │ ├── components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl │ └── metadata.json ├── README.md ├── main.py ├── prompts.py ├── pyproject.toml ├── static │ ├── README.md │ ├── custom.css │ └── favicon.png └── utils.py └── release-notes-generator ├── README.md ├── end ├── README.md ├── data │ ├── output-html.html │ └── test-data.csv ├── html_template.py ├── main.py ├── prompts.py ├── pyproject.toml ├── sample-input │ └── test-data.csv ├── static │ ├── README.md │ ├── Writer_Logo_black.svg │ ├── custom.css │ └── favicon.png └── ui.json └── start ├── README.md ├── data └── test-data.csv ├── html_template.py ├── main.py ├── prompts.py ├── pyproject.toml ├── sample-input └── test-data.csv ├── static ├── README.md ├── Writer_Logo_black.svg ├── custom.css └── favicon.png └── ui.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Poetry 7 | .venv/ 8 | poetry.lock 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # IDEs and editors 14 | .idea/ 15 | .vscode/ 16 | *.swp 17 | *.swo 18 | 19 | # Distribution / packaging 20 | dist/ 21 | build/ 22 | *.egg-info/ 23 | *.egg 24 | 25 | # Unit test / coverage reports 26 | htmlcov/ 27 | .coverage 28 | .pytest_cache/ 29 | 30 | # Environments 31 | .env 32 | .venv 33 | env/ 34 | venv/ 35 | 36 | # Logs 37 | *.log 38 | 39 | # Miscellaneous 40 | *.bak 41 | *.tmp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writer Framework tutorials 2 | This is a repo for Writer Frameowrk tutorial and sample applications. When applicable, an application will contain a `start` and `end` folder. 3 | 4 | ## What is Writer Framework? 5 | 6 | Writer Framework is an open-source framework for creating AI applications. Build user interfaces using a visual editor; write the backend code in Python. 7 | 8 | Writer Framework is fast and flexible with a clean, easily-testable syntax. It provides separation of concerns between UI and business logic, enabling more complex applications. 9 | 10 | ## Installation and Quickstart 11 | 12 | Getting started with Writer Framework is easy. It works on Linux, Mac and Windows. 13 | 14 | ```sh 15 | pip install writer 16 | ``` 17 | 18 | We recommend using a virtual environment. 19 | 20 | ## Documentation 21 | 22 | Full documentation, including how to use Writer's AI module and deployment options, is available at [Writer](https://dev.writer.com/framework?utm_source=github&utm_medium=readme&utm_campaign=framework). 23 | 24 | ## About Writer 25 | 26 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). 27 | -------------------------------------------------------------------------------- /code-generator/README.md: -------------------------------------------------------------------------------- 1 | # Code generator 2 | Create React components in TypeScript based on user input using the [Writer Framework](https://dev.writer.com/framework). 3 | 4 | ## How to use 5 | 6 | 1. Input your app idea in the text input field. 7 | 2. The app will generate a React component based on your request. 8 | 3. The generated component will be displayed in the [CodeSandbox](https://codesandbox.io/). 9 | 4. You can modify the generated code as needed or click on the "Open Sandbox" button to open the code in CodeSandbox. 10 | 11 | ## Running the application 12 | First, install Writer Frameowrk: 13 | 14 | ```sh 15 | pip install writer 16 | ``` 17 | 18 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 19 | 20 | ```sh 21 | export WRITER_API_KEY=your-api-key 22 | ``` 23 | 24 | Then, navigate to this folder and run: 25 | 26 | ```sh 27 | writer edit . 28 | ``` 29 | 30 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 31 | 32 | ## About Writer 33 | 34 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). -------------------------------------------------------------------------------- /code-generator/extensions/style.css: -------------------------------------------------------------------------------- 1 | .CodeViewer[data-v-6b847114]{height:70vh} 2 | -------------------------------------------------------------------------------- /code-generator/main.py: -------------------------------------------------------------------------------- 1 | import writer as wf 2 | from prompts import prompt 3 | from writer.ai import Conversation 4 | 5 | 6 | def generate_app_code(state, payload): 7 | state["conversation"] += {"role": "user", "content": payload} 8 | 9 | state["app_code"] = "" 10 | 11 | for chunk in state["conversation"].stream_complete(): 12 | state["app_code"] += chunk["content"] 13 | clear_code_from_wrapping(state) 14 | state.call_frontend_function("scripts", "scrollToLastLine", []) 15 | 16 | state["conversation"] += {"role": "assistant", "content": state["app_code"]} 17 | 18 | 19 | def clear_code_from_wrapping(state): 20 | mapping = {"typescript": "", "javascript": "", "tsx": "", "jsx": "", "```": ""} 21 | for k, v in mapping.items(): 22 | state["app_code"] = state["app_code"].replace(k, v) 23 | 24 | 25 | initial_state = wf.init_state( 26 | { 27 | "my_app": {"title": "Code generator"}, 28 | "app_code": """ 29 | import React from 'react'; 30 | 31 | function App() { 32 | return ( 33 |
34 |

Hello World!

35 |
36 | ); 37 | } 38 | 39 | export default App; 40 | """, 41 | "conversation": Conversation( 42 | prompt_or_history=[{"role": "system", "content": prompt}], 43 | config={"model": "palmyra-x-004"}, 44 | ), 45 | } 46 | ) 47 | 48 | initial_state.import_stylesheet("style", "/static/custom.css?6") 49 | initial_state.import_frontend_module("scripts", "/static/custom.js?1") 50 | -------------------------------------------------------------------------------- /code-generator/prompts.py: -------------------------------------------------------------------------------- 1 | prompt = """ 2 | # CONTEXT # 3 | You are an expert front-end React developer who is fantastic at UI/UX design. 4 | You create expertly written React components that are both visually appealing and highly functional. 5 | You always think carefully about the code you write to ensure it is clean, efficient, and easy to maintain. 6 | 7 | # INSTRUCTIONS # 8 | Create an expertly written React component for whatever the user asks for. 9 | 10 | # ADDITIONAL GUIDELINES # 11 | - Ensure the component can be run in a standalone environment using a default export. 12 | - Use TypeScript for the component. 13 | - You may modify previous code if you are asked to do so. 14 | - Ensure the component is interactive and functional by creating state and handling events. 15 | - If you use any React imports such as useState or useEffect, ensure they are imported correctly. 16 | - For styling the component use appropriate spacing and padding and 17 | a consistent and visually appealing color scheme. Consider to use only inline styles. 18 | - Do not use any uninstalled libraries and packages. 19 | E.g. if you don't have "axios" installed do not import and try to use it. 20 | - If the user asks for a dashboard, graph, or chart, use the recharts library for this purpose. 21 | 22 | # USER REQUEST SAMPLE# 23 | Here is the user request sample: 24 | "Create a dashboard that shows sales data with sample data." 25 | 26 | # RESPONSE FORMAT # 27 | Write the code for the React component. Do not use a code-block for the response, i.e., 28 | do not enclose the code in backticks (```). Do not preamble or provide anything else. 29 | """ 30 | -------------------------------------------------------------------------------- /code-generator/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | writer = "^0.7.4" 11 | flake8 = "^7.1.1" 12 | isort = "^5.13.2" 13 | black = "^24.8.0" 14 | pre-commit = "^3.8.0" 15 | 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /code-generator/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /code-generator/static/custom.css: -------------------------------------------------------------------------------- 1 | .ideas-input{ 2 | max-width: 100% !important; 3 | } 4 | -------------------------------------------------------------------------------- /code-generator/static/custom.js: -------------------------------------------------------------------------------- 1 | export function scrollToLastLine(){ 2 | const lines = document.getElementsByClassName("cm-line") 3 | if(lines.length > 0) 4 | lines[lines.length - 1].scrollIntoView(); 5 | } 6 | -------------------------------------------------------------------------------- /code-generator/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/code-generator/static/favicon.png -------------------------------------------------------------------------------- /code-generator/ui.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "writer_version": "0.7.4" 4 | }, 5 | "components": { 6 | "root": { 7 | "id": "root", 8 | "type": "root", 9 | "content": { 10 | "appName": "Code generator" 11 | }, 12 | "isCodeManaged": false, 13 | "position": 0, 14 | "handlers": {} 15 | }, 16 | "c0f99a9e-5004-4e75-a6c6-36f17490b134": { 17 | "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 18 | "type": "page", 19 | "content": { 20 | "pageMode": "compact" 21 | }, 22 | "isCodeManaged": false, 23 | "position": 0, 24 | "parentId": "root", 25 | "handlers": {} 26 | }, 27 | "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": { 28 | "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", 29 | "type": "header", 30 | "content": { 31 | "text": "Code generator" 32 | }, 33 | "isCodeManaged": false, 34 | "position": 0, 35 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 36 | "handlers": {} 37 | }, 38 | "wr3d3ksx98oph32v": { 39 | "id": "wr3d3ksx98oph32v", 40 | "type": "custom_codeviewer", 41 | "content": { 42 | "code": "@{app_code}" 43 | }, 44 | "isCodeManaged": false, 45 | "position": 2, 46 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 47 | "handlers": {} 48 | }, 49 | "4ymyn6cupdlc2gp3": { 50 | "id": "4ymyn6cupdlc2gp3", 51 | "type": "textinput", 52 | "content": { 53 | "label": "", 54 | "placeholder": "Input app ideas...", 55 | "cssClasses": "ideas-input" 56 | }, 57 | "isCodeManaged": false, 58 | "position": 1, 59 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 60 | "handlers": { 61 | "wf-change-finish": "generate_app_code" 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /finance-content-checker/.wf/components-root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "root", "type": "root", "content": {"appName": "Finance Copy Compliance Checker"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /finance-content-checker/.wf/components-workflows_root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /finance-content-checker/.wf/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "writer_version": "0.8.2" 3 | } -------------------------------------------------------------------------------- /finance-content-checker/README.md: -------------------------------------------------------------------------------- 1 | # Finance copy compliance checker 2 | This application allows the user to paste in text and analyze it based on compliance guidelines. 3 | 4 | Note: the prompts in `prompts.py` are tightly coupled to the formatting of the sample data. If you want to use a different format, be sure to update the prompts accordingy. 5 | 6 | ## Running the application 7 | First, install Writer Frameowrk: 8 | 9 | ```sh 10 | pip install writer 11 | ``` 12 | 13 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 14 | 15 | ```sh 16 | export WRITER_API_KEY=your-api-key 17 | ``` 18 | 19 | Then, navigate to this folder and run: 20 | 21 | ```sh 22 | writer edit . 23 | ``` 24 | 25 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 26 | 27 | ## About Writer 28 | 29 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). -------------------------------------------------------------------------------- /finance-content-checker/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = "0.8.2" 11 | python-dotenv = "^1.0.1" 12 | 13 | 14 | [build-system] 15 | requires = ["poetry-core"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /finance-content-checker/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /finance-content-checker/static/Writer_Logo_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /finance-content-checker/static/custom.css: -------------------------------------------------------------------------------- 1 | 2 | .description{ 3 | padding-bottom: 1em; 4 | padding-top: 1em; 5 | margin-left: 3em; 6 | color:darkgray 7 | } 8 | 9 | -------------------------------------------------------------------------------- /finance-content-checker/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/finance-content-checker/static/favicon.png -------------------------------------------------------------------------------- /finance-content-checker/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from prompts import _we_pronoun_prompt, _outcome_language_prompt, _hyperbole_prompt 4 | 5 | # Map data dictionary into output boxes 6 | def set_page_output(data,state,rule): 7 | state["output"][rule]["text"]= data["text"] 8 | state["output"][rule]["description"] = data["description"] 9 | state["output"][rule]["guideline"] = data["guideline"] 10 | state["output"][rule]["suggestion"] = data["suggestion"] 11 | 12 | 13 | def highlight_string(state,rule): 14 | state["html_content"] = state["content"].replace('\n\n', '

') 15 | html_paragraph = state["html_content"] 16 | search_string = state["output"][rule]["text"] 17 | highlighted_html = highlight_string_in_html(html_paragraph, search_string) 18 | state["html_content"] = highlighted_html 19 | 20 | 21 | def convert_string_to_dict_list(input_string): 22 | try: 23 | # # Properly format the string to make it a valid JSON array 24 | input_string = input_string.replace("```json", "").replace("```", "") 25 | # Convert the formatted string into a list of dictionaries 26 | data_list = json.loads(input_string) 27 | return data_list 28 | except json.JSONDecodeError as e: 29 | print(e) 30 | return [] 31 | 32 | 33 | def highlight_string_in_html(html_paragraph, search_string): 34 | # Escape special characters in the search string to safely use it in a regular expression 35 | escaped_search_string = re.escape(search_string) 36 | 37 | # Define the replacement pattern, wrapping the search string in a span with a light orange background 38 | replacement_pattern = f'{search_string}' 39 | 40 | # Use re.sub() to replace occurrences of the search string with the replacement pattern 41 | highlighted_html = re.sub(escaped_search_string, replacement_pattern, html_paragraph, flags=re.IGNORECASE) 42 | 43 | return highlighted_html 44 | -------------------------------------------------------------------------------- /finance-dashboard/.env.example: -------------------------------------------------------------------------------- 1 | WRITER_API_KEY=your-api-key 2 | -------------------------------------------------------------------------------- /finance-dashboard/.wf/components-root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "root", "type": "root", "content": {"appName": "Finance Dashboard"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /finance-dashboard/.wf/components-workflows_root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /finance-dashboard/.wf/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "writer_version": "0.8.3rc2" 3 | } -------------------------------------------------------------------------------- /finance-dashboard/README.md: -------------------------------------------------------------------------------- 1 | # Financial research dashboard 2 | This application pulls in realtime stock data and analyzes it with Palmyra. 3 | 4 | ## Running the application 5 | First, install Writer Frameowrk: 6 | 7 | ```sh 8 | pip install writer 9 | ``` 10 | 11 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create a Framework API key. To pass your API key to the Writer Framework, you'll need to make a copy of `.env.example`, remove the `.example` extension, and update `WRITER_API_KEY`: 12 | 13 | ``` 14 | WRITER_API_KEY=your-api-key 15 | ``` 16 | 17 | Then, navigate to this folder and run: 18 | 19 | ```sh 20 | writer edit . 21 | ``` 22 | 23 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 24 | 25 | ## About Writer 26 | 27 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). 28 | -------------------------------------------------------------------------------- /finance-dashboard/charts.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import plotly.express as px 3 | import plotly.graph_objects as go 4 | from plotly.subplots import make_subplots 5 | 6 | 7 | def handle_click(state, context: dict, ui): 8 | # Resetting the classes for active button 9 | if state["active_button"]: 10 | active_button = ui.find(state["active_button"]) 11 | active_button.content["cssClasses"] = "" 12 | 13 | event_target = context["target"] 14 | button = ui.find(event_target) 15 | 16 | # Storing the clicked button ID in state 17 | state["active_button"] = event_target 18 | 19 | button_text = button.content["text"] 20 | _handle_time_period(state, button_text) 21 | button.content["cssClasses"] = "button-click" 22 | 23 | button_max = ui.find("e13teponreio9yyz") 24 | button_max.content["cssClasses"] = "" 25 | 26 | ui.component_tree.updated = True 27 | 28 | 29 | def _handle_time_period(state, period): 30 | state["main_df_subset"] = state["main_df"] 31 | if period == "5D": 32 | state["main_df_subset"] = state["main_df_subset"][:5] 33 | elif period == "1M": 34 | state["main_df_subset"] = state["main_df_subset"][:30] 35 | elif period == "3M": 36 | state["main_df_subset"] = state["main_df_subset"][:90] 37 | elif period == "1Y": 38 | state["main_df_subset"] = state["main_df_subset"][:360] 39 | elif period == "5Y": 40 | state["main_df_subset"] = state["main_df_subset"][:1800] 41 | elif period == "Max": 42 | # No need to slice, already has the full data 43 | pass 44 | update_scatter_chart(state) 45 | 46 | 47 | def update_scatter_chart(state): 48 | fig = px.line(state["main_df_subset"], x="Date", y="Open", height=400) 49 | 50 | df1 = state["main_df_subset"] 51 | df2 = state["another_df"] 52 | df2 = df2.head(len(df1)) 53 | 54 | # Add a new column to each dataframe to identify the source 55 | df1["Source"] = "Main_DF" 56 | df2["Source"] = "Another_DF" 57 | 58 | # Concatenate the dataframes 59 | combined_df = pd.concat([df1, df2]) 60 | state["main_df_subset"] = combined_df 61 | 62 | # Plot the lines 63 | fig = make_subplots(specs=[[{"secondary_y": True}]]) 64 | 65 | # Add traces for the primary y-axis (Main_DF) 66 | fig.add_trace( 67 | go.Scatter(x=df1["Date"], y=df1["Open"], name=state["symbol"], mode="lines"), 68 | secondary_y=False, 69 | ) 70 | 71 | # Add traces for the secondary y-axis (Another_DF) 72 | fig.add_trace( 73 | go.Scatter(x=df2["Date"], y=df2["Open"], name="S&P 500", mode="lines"), 74 | secondary_y=True, 75 | ) 76 | 77 | # Set axis titles 78 | fig.update_yaxes(title_text=f"{state['symbol']} Stock Price", secondary_y=False) 79 | fig.update_yaxes(title_text="S&P 500", secondary_y=True) 80 | 81 | # Update layout 82 | fig.update_layout( 83 | height=550, 84 | title_text=f"{state['symbol']} Stock vs the S&P 500", 85 | title_x=0.5, 86 | title_y=0.9, 87 | legend=dict( 88 | orientation="h", 89 | yanchor="top", 90 | y=-0.2, # Adjust this value as needed 91 | xanchor="center", 92 | x=0.5, 93 | ), 94 | ) 95 | 96 | state["scatter_chart"] = fig 97 | 98 | 99 | def update_bar_graph(state): 100 | fig = px.line(state["main_df_subset"], x="Date", y="Open", height=400) 101 | 102 | df = state["income_statement_df"] 103 | selected_metrics = ["Total Revenue", "Net Income", "Operating Income"] 104 | df_filtered = df.loc[selected_metrics] 105 | 106 | # Transpose the DataFrame for easier plotting 107 | df_transposed = df_filtered.transpose().reset_index() 108 | df_transposed = df_transposed.melt( 109 | id_vars=["index"], var_name="Metric", value_name="Value" 110 | ) 111 | 112 | # Create the bar graph using Plotly Express 113 | fig = px.bar( 114 | df_transposed, 115 | x="index", 116 | y="Value", 117 | color="Metric", 118 | barmode="group", 119 | labels={"index": "", "Value": ""}, 120 | title="Summary of Quarterly Income Statement", 121 | ) 122 | 123 | fig.update_layout( 124 | legend=dict(orientation="h", yanchor="top", y=-0.2, xanchor="center", x=0.5) 125 | ) 126 | 127 | state["bar_graph"] = fig 128 | -------------------------------------------------------------------------------- /finance-dashboard/daily_APPLE.csv: -------------------------------------------------------------------------------- 1 | timestamp,open,high,low,close,volume 2 | 2024-06-17,213.3700,218.9500,212.7200,216.6700,93728300 3 | 2024-06-14,213.8500,215.1700,211.3000,212.4900,70122748 4 | 2024-06-13,214.7400,216.7500,211.6000,214.2400,97862729 5 | 2024-06-12,207.3700,220.2000,206.9000,213.0700,198134293 6 | 2024-06-11,193.6500,207.1600,193.6300,207.1500,172373296 7 | 2024-06-10,196.9000,197.3000,192.1500,193.1200,97262077 8 | 2024-06-07,194.6500,196.9400,194.1400,196.8900,53103912 9 | 2024-06-06,195.6850,196.5000,194.1700,194.4800,41181753 10 | 2024-06-05,195.4000,196.9000,194.8700,195.8700,54156785 11 | 2024-06-04,194.6350,195.3200,193.0342,194.3500,47471445 12 | 2024-06-03,192.9000,194.9900,192.5200,194.0300,50080539 13 | 2024-05-31,191.4400,192.5700,189.9100,192.2500,75158277 14 | 2024-05-30,190.7600,192.1800,190.6300,191.2900,49947941 15 | 2024-05-29,189.6100,192.2470,189.5100,190.2900,53068016 16 | 2024-05-28,191.5100,193.0000,189.1000,189.9900,52280051 17 | 2024-05-24,188.8200,190.5800,188.0404,189.9800,36326975 18 | 2024-05-23,190.9800,191.0000,186.6250,186.8800,51005924 19 | 2024-05-22,192.2650,192.8231,190.2700,190.9000,34648547 20 | 2024-05-21,191.0900,192.7300,190.9201,192.3500,42309401 21 | 2024-05-20,189.3250,191.9199,189.0100,191.0400,44361275 22 | 2024-05-17,189.5100,190.8100,189.1800,189.8700,41282925 23 | 2024-05-16,190.4700,191.0950,189.6601,189.8400,52845230 24 | 2024-05-15,187.9100,190.6500,187.3700,189.7200,70399988 25 | 2024-05-14,187.5100,188.3000,186.2900,187.4300,52393619 26 | 2024-05-13,185.4350,187.1000,184.6200,186.2800,72044809 27 | 2024-05-10,184.9000,185.0900,182.1300,183.0500,50759496 28 | 2024-05-09,182.5600,184.6600,182.1100,184.5700,48982972 29 | 2024-05-08,182.8500,183.0700,181.4500,182.7400,45057087 30 | 2024-05-07,183.4500,184.9000,181.3200,182.4000,77305771 31 | 2024-05-06,182.3540,184.2000,180.4200,181.7100,78569667 32 | 2024-05-03,186.6450,187.0000,182.6600,183.3800,163224109 33 | 2024-05-02,172.5100,173.4150,170.8900,173.0300,94214915 34 | 2024-05-01,169.5800,172.7050,169.1100,169.3000,50383147 35 | 2024-04-30,173.3300,174.9900,170.0000,170.3300,65934776 36 | 2024-04-29,173.3700,176.0300,173.1000,173.5000,68169419 37 | 2024-04-26,169.8800,171.3400,169.1800,169.3000,44838354 38 | 2024-04-25,169.5250,170.6100,168.1511,169.8900,50558329 39 | 2024-04-24,166.5400,169.3000,166.2100,169.0200,48251835 40 | 2024-04-23,165.3500,167.0500,164.9200,166.9000,49537761 41 | 2024-04-22,165.5150,167.2600,164.7700,165.8400,48116443 42 | 2024-04-19,166.2100,166.4000,164.0750,165.0000,68149377 43 | 2024-04-18,168.0300,168.6400,166.5500,167.0400,43122903 44 | 2024-04-17,169.6100,170.6500,168.0000,168.0000,50901210 45 | 2024-04-16,171.7500,173.7600,168.2700,169.3800,73711235 46 | 2024-04-15,175.3600,176.6300,172.5000,172.6900,73531773 47 | 2024-04-12,174.2600,178.3600,174.2100,176.5500,101670886 48 | 2024-04-11,168.3400,175.4600,168.1600,175.0400,91070275 49 | 2024-04-10,168.8000,169.0900,167.1100,167.7800,49709336 50 | 2024-04-09,168.7000,170.0800,168.3500,169.6700,42231444 51 | 2024-04-08,169.0300,169.2000,168.2400,168.4500,37216858 52 | 2024-04-05,169.5900,170.3900,168.9500,169.5800,41975776 53 | 2024-04-04,170.2900,171.9200,168.8200,168.8200,53355055 54 | 2024-04-03,168.7900,170.6800,168.5800,169.6500,45571129 55 | 2024-04-02,169.0800,169.3400,168.2302,168.8400,49013991 56 | 2024-04-01,171.1900,171.2500,169.4750,170.0300,43772506 57 | 2024-03-28,171.7500,172.2300,170.5100,171.4800,65672690 58 | 2024-03-27,170.4100,173.6000,170.1100,173.3100,60273265 59 | 2024-03-26,170.0000,171.4200,169.5800,169.7100,57388449 60 | 2024-03-25,170.5650,171.9400,169.4500,170.8500,54288328 61 | 2024-03-22,171.7600,173.0500,170.0600,172.2800,71160138 62 | 2024-03-21,177.0500,177.4900,170.8400,171.3700,106181270 63 | 2024-03-20,175.7200,178.6700,175.0900,178.6700,53423102 64 | 2024-03-19,174.3400,176.6050,173.0300,176.0800,55215244 65 | 2024-03-18,175.5700,177.7100,173.5200,173.7200,75604184 66 | 2024-03-15,171.1700,172.6200,170.2850,172.6200,121752699 67 | 2024-03-14,172.9100,174.3078,172.0500,173.0000,72571635 68 | 2024-03-13,172.7700,173.1850,170.7600,171.1300,51948951 69 | 2024-03-12,173.1500,174.0300,171.0100,173.2300,59544927 70 | 2024-03-11,172.9400,174.3800,172.0500,172.7500,58929918 71 | 2024-03-08,169.0000,173.7000,168.9400,170.7300,76267041 72 | 2024-03-07,169.1500,170.7300,168.4900,169.0000,71765061 73 | 2024-03-06,171.0600,171.2400,168.6800,169.1200,68587707 74 | 2024-03-05,170.7600,172.0400,169.6200,170.1200,95132355 75 | 2024-03-04,176.1500,176.9000,173.7900,175.1000,81510101 76 | 2024-03-01,179.5500,180.5300,177.3800,179.6600,73563082 77 | 2024-02-29,181.2700,182.5700,179.5300,180.7500,136682597 78 | 2024-02-28,182.5100,183.1200,180.1300,181.4200,48953939 79 | 2024-02-27,181.1000,183.9225,179.5600,182.6300,54318851 80 | 2024-02-26,182.2400,182.7600,180.6500,181.1600,40867421 81 | 2024-02-23,185.0100,185.0400,182.2300,182.5200,45119677 82 | 2024-02-22,183.4800,184.9550,182.4600,184.3700,52292208 83 | 2024-02-21,181.9400,182.8888,180.6600,182.3200,41529674 84 | 2024-02-20,181.7900,182.4300,180.0000,181.5600,53665553 85 | 2024-02-16,183.4200,184.8500,181.6650,182.3100,49752465 86 | 2024-02-15,183.5500,184.4900,181.3500,183.8600,65434496 87 | 2024-02-14,185.3200,185.5300,182.4400,184.1500,54630517 88 | 2024-02-13,185.7700,186.2100,183.5128,185.0400,56529529 89 | 2024-02-12,188.4150,188.6700,186.7900,187.1500,41781934 90 | 2024-02-09,188.6500,189.9900,188.0000,188.8500,45155216 91 | 2024-02-08,189.3850,189.5350,187.3500,188.3200,40962046 92 | 2024-02-07,190.6400,191.0500,188.6100,189.4100,53438955 93 | 2024-02-06,186.8600,189.3100,186.7695,189.3000,43490759 94 | 2024-02-05,188.1500,189.2500,185.8400,187.6800,69668820 95 | 2024-02-02,179.8600,187.3300,179.2500,185.8500,102551680 96 | 2024-02-01,183.9850,186.9500,183.8200,186.8600,64885408 97 | 2024-01-31,187.0400,187.0950,184.3500,184.4000,55467803 98 | 2024-01-30,190.9400,191.8000,187.4700,188.0400,55859370 99 | 2024-01-29,192.0100,192.2000,189.5800,191.7300,47145622 100 | 2024-01-26,194.2700,194.7600,191.9400,192.4200,44594011 101 | 2024-01-25,195.2200,196.2675,193.1125,194.1700,54822126 102 | -------------------------------------------------------------------------------- /finance-dashboard/daily_IBM.csv: -------------------------------------------------------------------------------- 1 | timestamp,open,high,low,close,volume 2 | 2024-06-17,168.7600,169.7200,167.5000,169.5000,3239815 3 | 2024-06-14,168.2900,169.4700,167.2300,169.2100,2777717 4 | 2024-06-13,169.0100,169.5900,168.3350,169.1200,3525717 5 | 2024-06-12,171.3500,172.4700,168.1010,169.0000,3522698 6 | 2024-06-11,169.9800,170.0000,166.8100,169.3200,2951251 7 | 2024-06-10,169.5500,170.7600,168.8800,170.3800,3444684 8 | 2024-06-07,168.1800,171.3050,168.0600,170.0100,3475495 9 | 2024-06-06,167.3800,168.4400,166.8000,168.2000,2207263 10 | 2024-06-05,166.4100,167.7900,165.7800,167.3800,3049377 11 | 2024-06-04,164.6000,166.4000,163.8800,165.8100,2594203 12 | 2024-06-03,166.5400,166.7800,163.5300,165.2800,2776058 13 | 2024-05-31,165.7000,166.9700,163.8400,166.8500,4905002 14 | 2024-05-30,165.5600,166.7300,164.2300,165.6300,3852963 15 | 2024-05-29,168.0000,168.6300,166.2100,167.0500,4206576 16 | 2024-05-28,170.4400,171.0850,168.6500,169.6600,2629645 17 | 2024-05-24,171.4800,172.0100,170.2100,170.8900,2587829 18 | 2024-05-23,175.3900,175.4600,170.4350,170.6700,3341335 19 | 2024-05-22,173.3900,174.9900,172.7600,173.6900,3294900 20 | 2024-05-21,169.9400,174.9700,169.9400,173.4700,6459800 21 | 2024-05-20,169.0000,170.1600,168.3800,169.9200,2726261 22 | 2024-05-17,168.9700,169.1100,167.3300,169.0300,2956387 23 | 2024-05-16,168.2600,169.6300,167.7900,168.9700,3492267 24 | 2024-05-15,167.9400,168.3500,167.3400,168.2600,4468823 25 | 2024-05-14,167.8600,168.1300,166.4800,167.3600,2600967 26 | 2024-05-13,167.5000,168.0600,166.7600,167.5600,2414859 27 | 2024-05-10,167.1300,168.0700,166.3200,167.1500,2255370 28 | 2024-05-09,167.5000,167.5500,165.8800,166.2700,4266616 29 | 2024-05-08,168.0100,170.2600,167.9000,169.9000,3522011 30 | 2024-05-07,169.0000,169.2900,167.9400,168.3800,3155260 31 | 2024-05-06,166.5000,168.6700,166.3800,168.6100,4222266 32 | 2024-05-03,165.0000,166.6100,164.9200,165.7100,3400405 33 | 2024-05-02,164.3500,164.8800,162.6200,164.6900,3829853 34 | 2024-05-01,165.6900,166.2700,164.3000,164.4300,4030960 35 | 2024-04-30,166.4900,166.7600,165.2605,166.2000,6011634 36 | 2024-04-29,167.4000,168.2200,166.2250,167.4300,5263342 37 | 2024-04-26,167.5000,167.8700,165.7300,167.1300,8983796 38 | 2024-04-25,168.2000,172.4500,165.6600,168.9100,16702150 39 | 2024-04-24,183.1700,184.2900,181.4000,184.1000,7616643 40 | 2024-04-23,182.7300,184.6800,179.0000,182.1900,5950229 41 | 2024-04-22,182.4500,183.3150,180.4500,181.9000,3076451 42 | 2024-04-19,182.4300,182.8000,180.5700,181.5800,3037990 43 | 2024-04-18,182.3500,183.4600,180.1700,181.4700,2886733 44 | 2024-04-17,184.1600,184.6700,181.7800,183.1000,3003033 45 | 2024-04-16,185.5900,185.7100,182.8600,183.7500,4473654 46 | 2024-04-15,185.5700,187.4800,180.8800,181.2500,3528140 47 | 2024-04-12,184.0000,185.1699,181.6850,182.2700,3547378 48 | 2024-04-11,186.0400,186.7950,184.5800,185.9000,2861736 49 | 2024-04-10,187.4200,187.9150,185.5200,186.0400,3081915 50 | 2024-04-09,190.5400,191.2500,186.6600,189.3100,2790673 51 | 2024-04-08,189.2400,190.2400,188.9118,189.8200,2673611 52 | 2024-04-05,188.5900,190.3200,188.0200,189.1400,2012428 53 | 2024-04-04,192.0000,193.2800,187.3400,187.9400,2924438 54 | 2024-04-03,188.6000,191.3500,188.4850,190.9000,2818910 55 | 2024-04-02,189.1400,189.8000,187.6000,188.8800,2689711 56 | 2024-04-01,190.0000,190.4600,188.5200,189.8300,2362586 57 | 2024-03-28,190.9400,191.9299,190.3400,190.9600,3742169 58 | 2024-03-27,189.6000,190.9600,188.6000,190.8000,3693305 59 | 2024-03-26,189.0200,190.0000,188.5000,188.5000,4229535 60 | 2024-03-25,190.2600,190.8200,188.7500,188.7900,3718289 61 | 2024-03-22,192.0000,192.9850,190.5100,190.8400,3988398 62 | 2024-03-21,193.0000,193.3700,190.0100,191.9000,6013561 63 | 2024-03-20,192.8700,193.9800,191.3100,193.9600,3238643 64 | 2024-03-19,191.4900,193.5800,190.2800,193.3400,5317341 65 | 2024-03-18,191.7000,193.2300,190.3200,191.6900,5410562 66 | 2024-03-15,191.9900,193.0573,190.7000,191.0700,8828184 67 | 2024-03-14,196.9500,197.7480,192.1200,193.4300,4102202 68 | 2024-03-13,197.5500,198.1000,195.3200,196.7000,3960737 69 | 2024-03-12,192.4600,199.1800,192.1500,197.7800,5862512 70 | 2024-03-11,195.0900,195.3800,190.8800,191.7300,4712688 71 | 2024-03-08,196.0600,197.7700,194.3800,195.9500,3943113 72 | 2024-03-07,197.5800,198.7300,196.1400,196.5400,4604458 73 | 2024-03-06,193.5000,198.1300,192.9600,196.1600,6945818 74 | 2024-03-05,192.0000,193.9400,190.5700,191.9500,5653641 75 | 2024-03-04,187.7600,193.8980,187.6000,193.0600,7938266 76 | 2024-03-01,185.4900,188.3800,185.1800,188.2000,4018354 77 | 2024-02-29,186.1500,186.8495,184.6900,185.0300,6458487 78 | 2024-02-28,184.6300,185.3700,183.5500,185.3000,3216345 79 | 2024-02-27,184.1600,185.1300,182.6200,184.8700,3641378 80 | 2024-02-26,185.6000,186.1250,184.0600,184.1300,4620815 81 | 2024-02-23,184.9000,186.4550,184.5700,185.7200,3433800 82 | 2024-02-22,182.4500,184.5500,181.9300,184.2100,5078398 83 | 2024-02-21,182.5600,183.0300,178.7500,179.7000,4728473 84 | 2024-02-20,187.6400,188.7700,183.0600,183.4400,4247181 85 | 2024-02-16,186.6300,188.9500,185.9452,187.6400,4842840 86 | 2024-02-15,183.6200,186.9800,183.6200,186.8700,4714301 87 | 2024-02-14,185.0000,185.0000,182.2600,183.5700,3173391 88 | 2024-02-13,184.2800,184.7700,182.3600,183.7000,4290453 89 | 2024-02-12,185.9000,186.4800,184.0300,186.1600,4724021 90 | 2024-02-09,184.4400,187.1800,183.8500,186.3400,5064641 91 | 2024-02-08,182.6300,184.5500,181.4900,184.3600,5161185 92 | 2024-02-07,183.3400,184.0200,182.6250,183.7400,4841188 93 | 2024-02-06,183.5500,184.6800,183.0400,183.4100,3338196 94 | 2024-02-05,185.5100,185.7800,183.2550,183.4200,4379602 95 | 2024-02-02,187.1000,187.3900,185.6150,185.7900,4055411 96 | 2024-02-01,183.6300,187.5100,182.7100,186.9000,4669444 97 | 2024-01-31,187.0500,187.6500,183.1400,183.6600,8876055 98 | 2024-01-30,187.7100,188.6500,186.7700,187.8700,4575058 99 | 2024-01-29,187.4600,189.4600,186.0500,187.1400,6107908 100 | 2024-01-26,191.3100,192.3896,186.1600,187.4200,9895941 101 | 2024-01-25,184.9600,196.9000,184.8300,190.4300,29596239 102 | -------------------------------------------------------------------------------- /finance-dashboard/prompts.py: -------------------------------------------------------------------------------- 1 | # BASE PROMPTS (TAILORED TO EACH COMPANY) 2 | 3 | # base_prompts = {} 4 | 5 | stock_prompts = """ 6 | 7 | Variables: 8 | 9 | {language}, {stock_name}, {words}, {stock_data} 10 | 11 | ************************ 12 | 13 | Prompt: 14 | You will be acting as a stock market analyst. When I provide the stock data in the specified format 15 | between the tags, your task is to carefully review the data and provide insights, 16 | trends, and financial analysis for the stock specified in the {stock_name} variable. 17 | 18 | 19 | {stock_data} 20 | 21 | 22 | Please analyze the stock data concisely in {words} words using paragraphs, considering the following 23 | steps: 24 | 25 | 1. Identify any notable trends in the stock's price, volume, or other key metrics over the given 26 | time period. Discuss potential reasons behind these trends. 27 | 28 | 2. Compare the stock's performance and financial metrics to industry averages and key competitors. 29 | Discuss how the stock stacks up against its peers. 30 | 31 | 3. Based on your analysis, provide an overall assessment of the stock's current position and future 32 | prospects. Consider factors such as growth potential, risk level, and market sentiment. 33 | 34 | Before giving your final recommendation, please provide detailed reasoning and analysis to support 35 | your conclusions inside tags. 36 | 37 | Finally, offer a clear recommendation inside tags on whether to buy, hold, or sell 38 | {stock_name}, taking into account both the stock's current valuation and its long-term potential. 39 | 40 | Remember to base your analysis and recommendation solely on the provided stock data for 41 | {stock_name}. If there is insufficient information to draw a conclusion, state this limitation in 42 | your response. 43 | 44 | Output the analysis in {language}. 45 | 46 | 47 | """ 48 | 49 | income_prompts = """ 50 | 51 | Variables: 52 | 53 | {income_statement_data}, {stock_name} 54 | 55 | ************************ 56 | 57 | Prompt: 58 | You are a financial analyst tasked with analyzing the income statement of a 59 | company. To assist in your analysis, you have been provided with the following data: 60 | 61 | 62 | {stock_name} 63 | 64 | 65 | 66 | {income_statement_data} 67 | 68 | 69 | Using the provided data, please conduct an executive summary analysis in 100 words, using paragraphs, of the company's financial health and 70 | future prospects. Your analysis should include: 71 | 72 | 73 | - Analyze key financial metrics from the income statement, such as revenue growth, profit margins, 74 | and expenses 75 | - Assess the company's profitability and efficiency based on the income statement data 76 | - Identify any potential risks or opportunities for the company based on the financial data 77 | - Consider industry trends and market conditions that may impact the company's performance 78 | 79 | 80 | After conducting your analysis, please provide your findings and conclusions in the following 81 | format: 82 | 83 | 84 | Income Statement Analysis: 85 | - [Discuss the company's revenue growth and profitability based on the income statement data] 86 | - [Identify any significant changes or trends in expenses or profit margins] 87 | - [Assess the company's overall financial health and efficiency based on the income statement 88 | metrics] 89 | 90 | Risks and Opportunities: 91 | - [Identify any potential risks or challenges the company may face based on the financial data] 92 | - [Discuss any opportunities for growth or improvement based on the analysis] 93 | 94 | Conclusion: 95 | - [Provide a summary of your overall assessment of the company's financial performance and future 96 | prospects] 97 | - [Include any recommendations or insights for investors or stakeholders] 98 | 99 | 100 | Please ensure that your analysis is clear, concise, and well-supported by the provided financial 101 | data. Use specific examples and figures from the stock data and income statement to support your 102 | conclusions. 103 | 104 | """ 105 | 106 | earnings_prompt = """ 107 | 108 | Variables: 109 | 110 | {earnings_transcript} 111 | 112 | ************************ 113 | 114 | Prompt: 115 | You are an expert at analyzing quarterly earnings reports. Please carefully review the following earnings call transcript: 116 | 117 | 118 | {earnings_transcript} 119 | 120 | 121 | After reading through the transcript, take some time to think through the key insights and takeaways 122 | in a . Consider the following aspects: 123 | - Financial performance: How did the company perform financially this quarter? Were revenue, 124 | profits, margins, etc. up or down compared to prior periods? Did they meet, exceed or fall short of 125 | expectations? 126 | - Future outlook: What is the company's outlook for upcoming quarters? Are they optimistic or 127 | cautious? What are their projections for key metrics? 128 | - Strategic initiatives: Did the company discuss any major strategic initiatives, partnerships, new 129 | products, expansion plans or other projects? What is the rationale and potential impact? 130 | - Significant changes: Were there any notable leadership changes, restructurings, pivots in strategy 131 | or other significant developments disclosed? 132 | 133 | 134 | Once you've thought through the main points, please provide a summary of the key insights from the 135 | earnings call. The summary should concisely hit on the major takeaways around 136 | financial performance, outlook, strategy and changes while including specific details that support 137 | the main points. Aim for around 4-6 paragraphs. 138 | 139 | Remember, your goal is to extract and communicate the most important information from the earnings 140 | call in a clear, insightful executive summary. Focus on the high-level story and don't get too in 141 | the weeds with minor details. Put yourself in the shoes of an analyst or investor and highlight what 142 | you think they would care about most. 143 | 144 | """ 145 | -------------------------------------------------------------------------------- /finance-dashboard/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = "^0.8.3rc2" 11 | python-dotenv = "^1.0.1" 12 | pandas = "^2.2.3" 13 | plotly = "^5.24.1" 14 | yfinance = "0.2.42" 15 | pre-commit = "^4.0.1" 16 | flake8 = "^7.1.1" 17 | black = "^24.10.0" 18 | isort = "^5.13.2" 19 | 20 | [build-system] 21 | requires = ["poetry-core"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /finance-dashboard/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /finance-dashboard/static/custom.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-align: right !important; 3 | } 4 | 5 | .button-click { 6 | background-color: #5551ff !important; 7 | color: white !important; 8 | } 9 | 10 | .custom-iframe { 11 | height: 700px !important; 12 | } 13 | -------------------------------------------------------------------------------- /finance-dashboard/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/finance-dashboard/static/favicon.png -------------------------------------------------------------------------------- /finance-dashboard/stock_data.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | import pandas as pd 5 | import yfinance as yf 6 | from charts import update_bar_graph 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | 12 | # Download stock data and format into a DataFrame 13 | def download_data(state): 14 | df = yf.download(state["symbol"], period="max", interval="1d") 15 | df = df.reset_index() 16 | if type(df.columns) is pd.MultiIndex: 17 | df.columns = df.columns.droplevel(1) 18 | df = df.sort_values(by="Date", ascending=False) 19 | df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2}) 20 | df["Date"] = pd.to_datetime(df["Date"]) 21 | state["main_df_subset"] = df 22 | state["main_df"] = state["main_df_subset"] 23 | 24 | 25 | # Download S&P 500 data and format into a DataFrame 26 | def download_sp500(state): 27 | df = yf.download(tickers="^GSPC", period="max", interval="1d") 28 | df = df.reset_index() 29 | if type(df.columns) is pd.MultiIndex: 30 | df.columns = df.columns.droplevel(1) 31 | df = df.sort_values(by="Date", ascending=False) 32 | df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2}) 33 | df["Date"] = pd.to_datetime(df["Date"]) 34 | state["another_df"] = df 35 | 36 | 37 | # Retrieve latest stock news 38 | def stock_news(state): 39 | msft = yf.Ticker(state["symbol"]) 40 | articles = {} 41 | data = msft.news 42 | latest_articles = sorted( 43 | data, key=lambda x: x["providerPublishTime"], reverse=True 44 | )[:4] 45 | 46 | for item in latest_articles: 47 | provider_publish_time = item.get("providerPublishTime", "") 48 | if provider_publish_time: 49 | # Convert the timestamp to a readable date 50 | formatted_date = datetime.fromtimestamp(provider_publish_time) 51 | readable_date = formatted_date.strftime("%B %d, %Y at %H:%M") 52 | else: 53 | readable_date = "Date not available" 54 | 55 | title = item.get("title", "No Title") 56 | articles[title] = { 57 | "source": item.get("publisher", ""), 58 | "published_at": readable_date, 59 | "url": item.get("link", ""), 60 | } 61 | state["articles"] = articles 62 | 63 | 64 | # Retrieve income statement data 65 | def income_statement(state): 66 | quarterly_income_stmt = yf.Ticker(state["symbol"]).quarterly_income_stmt 67 | df = pd.DataFrame(quarterly_income_stmt) 68 | df.columns = pd.to_datetime(df.columns).strftime("%Y-%m-%d") 69 | state["income_statement_df"] = df 70 | update_bar_graph(state) 71 | show_fin_metrics(state) 72 | 73 | 74 | # Show financial metrics 75 | def show_fin_metrics(state): 76 | stock = yf.Ticker(state["symbol"]) 77 | operating_margins = stock.info["operatingMargins"] 78 | gross_margin = stock.info["grossMargins"] 79 | ebitda_margin = stock.info["ebitdaMargins"] 80 | 81 | state["operating_margin"] = f"{operating_margins * 100:.2f}%" 82 | state["gross_margin"] = f"{gross_margin * 100:.2f}%" 83 | state["ebitda_margin"] = f"{ebitda_margin * 100:.2f}%" 84 | 85 | 86 | def _one_day_data(state): 87 | state["last_24_hours_open"] = round(state["main_df"]["Open"].iloc[0], 2) 88 | state["last_24_hours_high"] = round(state["main_df"]["High"].iloc[0], 2) 89 | state["last_24_hours_low"] = round(state["main_df"]["Low"].iloc[0], 2) 90 | 91 | 92 | # Retrieve earnings call transcript from JSON file 93 | # You could replace this with a call to an API like Financial Modeling Prep 94 | def earnings_calls(state): 95 | ticker = state["symbol"] 96 | with open("earnings-data.json", "r") as file: 97 | earnings_transcript = json.load(file) 98 | if earnings_transcript: 99 | for item in earnings_transcript: 100 | if item["symbol"] == ticker: 101 | state["earnings_transcript"] = item["content"] 102 | else: 103 | print("No earnings transcript found.") 104 | -------------------------------------------------------------------------------- /financial-tools-chat/.env.example: -------------------------------------------------------------------------------- 1 | WRITER_API_KEY=your_writer_api_key 2 | EARNINGS_ANALYSIS_APP_ID=your_zero_code_app_id 3 | -------------------------------------------------------------------------------- /financial-tools-chat/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0} 2 | {"id": "4kukoixkdb66ixah", "type": "header", "content": {"text": "Financial tools chat"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0} 3 | {"id": "kldz702eixpb8xnq", "type": "columns", "content": {"cssClasses": "column-container"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1} 4 | {"id": "4nklk9e1wqetdhln", "type": "column", "content": {"width": "45"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 0} 5 | {"id": "pi01zfrzcoxpiqxu", "type": "chatbot", "content": {"avatarBackgroundColor": "#000000", "buttonColor": "#000000", "conversation": "@{conversation}", "cssClasses": "chat", "useMarkdown": "yes"}, "handlers": {"wf-chatbot-message": "message_handler"}, "isCodeManaged": false, "parentId": "4nklk9e1wqetdhln", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} 6 | {"id": "52rbj5jd79undjij", "type": "column", "content": {"width": "50"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 1, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": false}} 7 | {"id": "e8iafahde157xsgr", "type": "tabs", "content": {"cssClasses": "visualization"}, "handlers": {}, "isCodeManaged": false, "parentId": "52rbj5jd79undjij", "position": 0} 8 | {"id": "stock", "type": "tab", "content": {"cssClasses": "", "name": "Stock"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 0, "visible": {"binding": "", "expression": "custom", "reversed": false}} 9 | {"id": "er0t7muikaatylvt", "type": "plotlygraph", "content": {"cssClasses": "stock", "spec": "@{stock_chart}"}, "handlers": {}, "isCodeManaged": false, "parentId": "stock", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} 10 | {"id": "4n2cykskypjd6qo4", "type": "text", "content": {"text": "@{stock_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "stock", "position": 1} 11 | {"id": "income", "type": "tab", "content": {"cssClasses": "", "name": "Income"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}} 12 | {"id": "rqbqheyr0o5hs4fj", "type": "plotlygraph", "content": {"cssClasses": "income", "spec": "@{income_chart}"}, "handlers": {}, "isCodeManaged": false, "parentId": "income", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} 13 | {"id": "ssw8zcojyuup2aqn", "type": "text", "content": {"text": "@{income_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "income", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}} 14 | {"id": "earnings", "type": "tab", "content": {"cssClasses": "", "name": "Earnings"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}} 15 | {"id": "3um69zhvgxb6efbh", "type": "text", "content": {"text": "@{earnings_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "earnings", "position": 0} 16 | {"id": "qwoll83rzy43r3rr", "type": "column", "content": {"cssClasses": "buttons", "width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}} 17 | {"id": "19f02wo3koxaipwp", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#000000", "cssClasses": "", "icon": "arrow_back_ios", "text": ""}, "handlers": {"wf-click": "open_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 0, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": true}} 18 | {"id": "72yjtz3kkrb5zr16", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "cssClasses": "", "icon": "arrow_forward_ios", "text": ""}, "handlers": {"wf-click": "close_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 1, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": false}} 19 | {"id": "kphyjxow4ppazmw1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#000000", "icon": "delete", "text": ""}, "handlers": {"wf-click": "clear_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 2} 20 | -------------------------------------------------------------------------------- /financial-tools-chat/.wf/components-root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "root", "type": "root", "content": {"appName": "Financial tools chat"}, "handlers": {}, "isCodeManaged": false, "position": 0} -------------------------------------------------------------------------------- /financial-tools-chat/.wf/components-workflows_root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /financial-tools-chat/.wf/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "writer_version": "0.8.1" 3 | } -------------------------------------------------------------------------------- /financial-tools-chat/README.md: -------------------------------------------------------------------------------- 1 | # Financial Tools Chat 2 | 3 | A conversational interface for financial research tools. This application was created using Writer Framework and uses the Palmyra Financial model. 4 | 5 | ## Description 6 | 7 | Financial Tools Chat is an interactive chat application that helps users with various financial research tools. Users can interact with the application through a chat interface to research and analyze: 8 | - Stock price data 9 | - Income statements 10 | - Earnings reports (Note: Earnings reports are stored locally in `earnings-data.json`. Feel free to swap this out with a call to an API that provides earnings data.) 11 | 12 | ## Prerequisites 13 | First, ensure you have Poetry installed. Then, in the project directory, install the dependencies by running: 14 | 15 | ```sh 16 | poetry install 17 | ``` 18 | 19 | ### Sign up for Writer AI Studio 20 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new API Key. 21 | 22 | To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 23 | ```sh 24 | export WRITER_API_KEY=your-api-key 25 | ``` 26 | 27 | You can also set the `WRITER_API_KEY` in the `.env` file. 28 | 29 | ### Create a no-code text generation application 30 | You'll also need to [create a no-code text generation application](https://dev.writer.com/no-code/building-a-text-generation-app) in Writer AI Studio and add the Palmyra Financial model to it to run the earnings call analysis. Add two fields to the application: `name` (for the symbol) and `data` (for the earnings report). You can use the `earnings-prompt.txt` file as a prompt for the earnings call analysis. 31 | 32 | ## Edit and run the application 33 | To make changes or edit the application, navigate to root folder and use the following command: 34 | 35 | 36 | ```sh 37 | writer edit . 38 | ``` 39 | 40 | Once you're ready to run the application, execute: 41 | 42 | ```sh 43 | writer run . 44 | ``` 45 | 46 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 47 | 48 | ## About Writer 49 | 50 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). 51 | -------------------------------------------------------------------------------- /financial-tools-chat/chat_tools.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from io import StringIO 4 | 5 | import pandas as pd 6 | import plotly.express as px 7 | import plotly.graph_objects as go 8 | import yfinance as yf 9 | from dotenv import load_dotenv 10 | from plotly.subplots import make_subplots 11 | from prompts import income_prompt, stock_prompt 12 | from writer.ai import apps, complete 13 | 14 | load_dotenv() 15 | 16 | 17 | def _get_stock_analysis(symbol: str) -> str: 18 | config = {"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192} 19 | data = _get_stock_data(symbol) 20 | prompt = stock_prompt.format(name=symbol, data=data) 21 | stock_analysis = complete(prompt, config=config) 22 | return stock_analysis 23 | 24 | 25 | def _get_stock_data(symbol: str) -> str: 26 | df = yf.download(symbol, period="5y", interval="5d") 27 | df = df.reset_index() 28 | df = df.sort_values(by="Date", ascending=True) 29 | df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2}) 30 | df["Date"] = pd.to_datetime(df["Date"]) 31 | return df.to_csv(index=False) 32 | 33 | 34 | def _get_income_analysis(symbol: str) -> str: 35 | config = {"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192} 36 | data = _get_income_data(symbol) 37 | prompt = income_prompt.format(name=symbol, data=data) 38 | income_analysis = complete(prompt, config=config) 39 | return income_analysis 40 | 41 | 42 | def _get_income_data(symbol: str) -> str: 43 | quarterly_income_stmt = yf.Ticker(symbol).quarterly_income_stmt 44 | df = pd.DataFrame(quarterly_income_stmt) 45 | df.columns = pd.to_datetime(df.columns).strftime("%Y-%m-%d") 46 | return df.to_csv() 47 | 48 | 49 | def _get_earnings_analysis(symbol: str) -> str: 50 | data = _get_earnings_data(symbol) 51 | earnings_analysis = apps.generate_content( 52 | application_id=os.getenv("EARNINGS_ANALYSIS_APP_ID", ""), 53 | input_dict={ 54 | "name": symbol, 55 | "data": data, 56 | }, 57 | ) 58 | return earnings_analysis 59 | 60 | 61 | def _get_earnings_data(symbol: str) -> str: 62 | with open("earnings-data.json", "r") as file: 63 | earnings_transcript = json.load(file) 64 | if earnings_transcript: 65 | for item in earnings_transcript: 66 | if item["symbol"] == symbol: 67 | return item["content"] 68 | else: 69 | return "No earnings transcript found." 70 | 71 | 72 | def get_stock_chart(symbol: str) -> str: 73 | symbol_io = StringIO(_get_stock_data(symbol)) 74 | sp500_io = StringIO(_get_stock_data("^GSPC")) 75 | 76 | symbol_df = pd.read_csv(symbol_io) 77 | sp500_df = pd.read_csv(sp500_io) 78 | 79 | fig = make_subplots(specs=[[{"secondary_y": True}]]) 80 | 81 | fig.add_trace( 82 | go.Scatter(x=symbol_df["Date"], y=symbol_df["Open"], name=symbol, mode="lines"), 83 | secondary_y=False, 84 | ) 85 | 86 | fig.add_trace( 87 | go.Scatter( 88 | x=sp500_df["Date"], y=sp500_df["Open"], name="S&P 500", mode="lines" 89 | ), 90 | secondary_y=True, 91 | ) 92 | 93 | fig.update_yaxes(title_text=f"{symbol} Stock Price", secondary_y=False) 94 | fig.update_yaxes(title_text="S&P 500", secondary_y=True) 95 | 96 | fig.update_layout( 97 | height=450, 98 | title_text=f"{symbol} Stock vs the S&P 500", 99 | title_x=0.5, 100 | title_y=0.9, 101 | legend=dict( 102 | orientation="h", 103 | yanchor="top", 104 | y=-0.2, 105 | xanchor="center", 106 | x=0.5, 107 | ), 108 | ) 109 | 110 | return fig.to_json() 111 | 112 | 113 | def get_income_chart(symbol: str) -> str: 114 | string_io = StringIO(_get_income_data(symbol)) 115 | 116 | df = pd.read_csv(string_io, index_col=0) 117 | df_filtered = df.loc[["Total Revenue", "Net Income", "Operating Income"]] 118 | 119 | df_transposed = df_filtered.transpose().reset_index() 120 | df_transposed = df_transposed.melt( 121 | id_vars=["index"], var_name="Metric", value_name="Value" 122 | ) 123 | 124 | fig = px.bar( 125 | df_transposed, 126 | x="index", 127 | y="Value", 128 | color="Metric", 129 | barmode="group", 130 | labels={"index": "", "Value": ""}, 131 | title=f"Summary of Quarterly Income Statement for {symbol}", 132 | ) 133 | 134 | fig.update_layout( 135 | height=400, 136 | legend=dict(orientation="h", yanchor="top", y=-0.2, xanchor="center", x=0.5), 137 | ) 138 | 139 | return fig.to_json() 140 | 141 | 142 | stock_analysis_tool = { 143 | "type": "function", 144 | "name": "get_stock_analysis", 145 | "callable": _get_stock_analysis, 146 | "parameters": { 147 | "symbol": { 148 | "type": "string", 149 | "description": "Symbol to compose stock data analysis.", 150 | }, 151 | }, 152 | } 153 | 154 | 155 | income_analysis_tool = { 156 | "type": "function", 157 | "name": "get_income_analysis", 158 | "callable": _get_income_analysis, 159 | "parameters": { 160 | "symbol": { 161 | "type": "string", 162 | "description": "Symbol to compose income statement analysis.", 163 | }, 164 | }, 165 | } 166 | 167 | 168 | earnings_analysis_tool = { 169 | "type": "function", 170 | "name": "get_earnings_analysis", 171 | "callable": _get_earnings_analysis, 172 | "parameters": { 173 | "symbol": { 174 | "type": "string", 175 | "description": "Symbol to fetch earnings data and compose analysis.", 176 | }, 177 | }, 178 | } 179 | 180 | 181 | tools = [ 182 | stock_analysis_tool, 183 | income_analysis_tool, 184 | earnings_analysis_tool, 185 | ] 186 | -------------------------------------------------------------------------------- /financial-tools-chat/earnings-prompt.txt: -------------------------------------------------------------------------------- 1 | # CONTEXT # 2 | You are an expert at analyzing quarterly earnings reports. You are creating financial analysis report based on earning statements of specific symbol, provided at data section. Furthermore, you are acting really carefully, review the data and provide insights, trends, and financial analysis. 3 | 4 | # INSTRUCTIONS # 5 | Create an expertly written financial analysis. 6 | 7 | # DATA # 8 | Symbol of earnings data: {name} 9 | Earnings report: {data} 10 | 11 | # ADDITIONAL GUIDELINES # 12 | - Say a few words about financial performance: How did the company perform financially this quarter? Were revenue, profits, margins, etc. up or down compared to prior periods? Did they meet, exceed, or fall short of expectations? 13 | - Provide future outlook: What is the company's outlook for upcoming quarters? Are they optimistic or cautious? What are their projections for key metrics? 14 | - Compose some strategic initiatives: Did the company discuss any major strategic initiatives, partnerships, new products, expansion plans or other projects? What is the rationale and potential impact? 15 | - Outline significant changes: Were there any notable leadership changes, restructurings, pivots in strategy or other significant developments disclosed? 16 | - Use some numbers from provided data in your analysis report. 17 | - Remember to base your analysis and recommendation solely on the provided earnings report for {name}. If there is insufficient information to draw a conclusion, state this limitation in your response. 18 | 19 | # USER REQUEST SAMPLE # 20 | Here is the user request sample: "What you can say about IBM doings based on their earnings report?" 21 | 22 | # RESPONSE FORMAT # 23 | Highlight headers, topics, main metrics and ideas. Use .md markup to style your text. -------------------------------------------------------------------------------- /financial-tools-chat/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import plotly.express as px 5 | import writer 6 | from chat_tools import get_income_chart, get_stock_chart, tools 7 | from dotenv import load_dotenv 8 | from writer.ai import Conversation 9 | 10 | load_dotenv() 11 | 12 | writer.ai.init(os.getenv("WRITER_API_KEY", "")) 13 | 14 | 15 | def message_handler(payload, state): 16 | try: 17 | state.call_frontend_function("scripts", "enableDisableTextarea", ["true"]) 18 | state["conversation"] += payload 19 | 20 | response = state["conversation"].stream_complete(tools=tools) 21 | 22 | for chunk in response: 23 | state["conversation"] += chunk 24 | 25 | for tool_call in _get_tool_call_results(state).values(): 26 | _visualize_response(state, tool_call) 27 | 28 | state.call_frontend_function("scripts", "enableDisableTextarea", ["false"]) 29 | except Exception as e: 30 | state["conversation"] += { 31 | "role": "assistant", 32 | "content": "Sorry, we faced unexpected error. Please try again!", 33 | } 34 | state.call_frontend_function("scripts", "enableDisableTextarea", ["false"]) 35 | raise e 36 | 37 | 38 | def _get_tool_call_results(state): 39 | tool_calls = {} 40 | for message in reversed(state["conversation"].messages): 41 | try: 42 | if message["role"] == "tool": 43 | tool_calls.update({message["name"]: message}) 44 | elif message["role"] == "assistant" and message["tool_calls"]: 45 | for tool_call in message["tool_calls"]: 46 | tool_name = tool_call["function"]["name"] 47 | tool_args = tool_call["function"]["arguments"] 48 | tool_calls[tool_name].update({"arguments": json.loads(tool_args)}) 49 | elif message["role"] == "user": 50 | break 51 | except KeyError as e: 52 | raise KeyError("Key error during fetching tool calls") from e 53 | return tool_calls 54 | 55 | 56 | def _visualize_response(state, tool_call): 57 | _update_text(state, tool_call["name"].split("_")[1], tool_call["content"]) 58 | _update_graphic( 59 | state, tool_call["name"].split("_")[1], tool_call["arguments"]["symbol"] 60 | ) 61 | state.call_frontend_function( 62 | "scripts", "focusOnTab", [tool_call["name"].split("_")[1]] 63 | ) 64 | state["visual_block_visible"] = True 65 | 66 | 67 | def _update_text(state, property_name, text): 68 | state[property_name + "_analysis"] = text 69 | 70 | 71 | def _update_graphic(state, property_name, symbol_name): 72 | graphic = "" 73 | if property_name == "stock": 74 | graphic = get_stock_chart(symbol_name) 75 | elif property_name == "income": 76 | graphic = get_income_chart(symbol_name) 77 | state[property_name + "_chart"] = graphic 78 | 79 | 80 | def _get_chart_placeholder(): 81 | df = px.data.stocks() 82 | df.columns.values[1] = "TEST" 83 | fig = px.line(df, x="date", y="TEST") 84 | 85 | fig.update_layout( 86 | height=400, 87 | title_x=0.5, 88 | title_y=0.9, 89 | legend=dict( 90 | orientation="h", 91 | yanchor="top", 92 | y=-0.2, 93 | xanchor="center", 94 | x=0.5, 95 | ), 96 | ) 97 | 98 | return fig.to_json() 99 | 100 | 101 | def clear_visualization(state): 102 | tab_names = ["stock", "income", "earnings"] 103 | 104 | for tab_name in tab_names: 105 | state[tab_name + "_analysis"] = "Analysis will appear here." 106 | state[tab_name + "_chart"] = _get_chart_placeholder() 107 | 108 | state["visual_block_visible"] = False 109 | 110 | 111 | def close_visualization(state): 112 | state["visual_block_visible"] = False 113 | 114 | 115 | def open_visualization(state): 116 | state["visual_block_visible"] = True 117 | 118 | 119 | initial_state = writer.init_state( 120 | { 121 | "conversation": Conversation( 122 | [ 123 | {"role": "assistant", "content": "Hi there. I can analyze stock price data, income statements, and earnings reports. How can I help?"}, 124 | ], 125 | ), 126 | "visual_block_visible": False, 127 | "stock_analysis": "Analysis will appear here.", 128 | "stock_chart": _get_chart_placeholder(), 129 | "income_analysis": "Analysis will appear here.", 130 | "income_chart": _get_chart_placeholder(), 131 | "earnings_analysis": "Analysis will appear here.", 132 | } 133 | ) 134 | 135 | initial_state.import_stylesheet("style", "/static/custom.css") 136 | initial_state.import_frontend_module("scripts", "/static/custom.js") 137 | -------------------------------------------------------------------------------- /financial-tools-chat/prompts.py: -------------------------------------------------------------------------------- 1 | stock_prompt = """ 2 | # CONTEXT # 3 | You are an expert of a financial analysis. You are creating financial analysis report based on stock 4 | data of specific symbol, provided at data section. Furthermore, you are acting really carefully, review 5 | the data and provide insights, trends, and financial analysis for the stock. 6 | 7 | # INSTRUCTIONS # 8 | Create an expertly written financial analysis. 9 | 10 | # DATA # 11 | Symbol of stock data: {name} 12 | Stock data: {data} 13 | 14 | # ADDITIONAL GUIDELINES # 15 | - Do not include any additional data in your response. Use only provided data. 16 | - Identify any notable trends in the stock's price, volume, or other key metrics over 17 | the given time period. Discuss potential reasons behind these trends. 18 | - Compare the stock's performance and financial metrics to industry averages and key 19 | competitors. Discuss how the stock stacks up against its peers. 20 | - Based on your analysis, provide an overall assessment of the stock's current position 21 | and future prospects. Consider factors such as growth potential, risk level, and market sentiment. 22 | - Use some numbers from provided data in your analysis report. 23 | - Remember to base your analysis and recommendation solely on the provided stock data for {name}. 24 | If there is insufficient information to draw a conclusion, state this limitation in your response. 25 | 26 | # USER REQUEST SAMPLE # 27 | Here is the user request sample: "Tell me about Tesla stock" 28 | 29 | # RESPONSE FORMAT # 30 | Highlight headers, topics, main metrics and ideas. Use .md markup to style your text. 31 | """ 32 | 33 | income_prompt = """ 34 | # CONTEXT # 35 | You are an expert of a financial analysis. You are creating financial analysis report based on income 36 | statement data of specific symbol, provided at data section. You are acting really carefully, review 37 | the data and provide insights, trends, and financial analysis for the incomes. 38 | 39 | # INSTRUCTIONS # 40 | Create an expertly written financial analysis. 41 | 42 | # DATA # 43 | Symbol of income statements data: {name} 44 | Income statements data: {data} 45 | 46 | # ADDITIONAL GUIDELINES # 47 | - Do not include any additional data in your response. Use only provided data. 48 | - Analyze key financial metrics from the income statement, such as revenue growth, profit margins, and expenses. 49 | - Assess the company's profitability and efficiency based on the income statement data. 50 | - Identify any potential risks or opportunities for the company based on the financial data. 51 | - Consider industry trends and market conditions that may impact the company's performance. 52 | - Use some numbers from provided data in your analysis report. 53 | - Remember to base your analysis and recommendation solely on the provided income data for {name}. 54 | If there is insufficient information to draw a conclusion, state this limitation in your response. 55 | 56 | # USER REQUEST SAMPLE # 57 | Here is the user request sample: "Hey, I want to know something about apple incomes" 58 | 59 | # RESPONSE FORMAT # 60 | Highlight headers, topics, main metrics and ideas. Use .md markup to style your text. 61 | """ 62 | -------------------------------------------------------------------------------- /financial-tools-chat/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | writer = "0.8.2" 11 | openai = "1.51.2" 12 | pre-commit = "^3.8.0" 13 | flake8 = "7.1.1" 14 | black = "^24.8.0" 15 | isort = "^5.13.2" 16 | markdown = "^3.7" 17 | python-dotenv = "1.0.1" 18 | writer-sdk = "^1.2.0" 19 | annotated-types = "0.7.0" 20 | anyio = "4.6.0" 21 | authlib = "1.3.2" 22 | beautifulsoup4 = "4.12.3" 23 | certifi = "2024.8.30" 24 | cffi = "1.17.1" 25 | charset-normalizer = "3.4.0" 26 | click = "8.1.7" 27 | cryptography = "43.0.1" 28 | distro = "1.9.0" 29 | fastapi = "0.115.0" 30 | frozendict = "2.4.5" 31 | gitignore-parser = "0.1.11" 32 | h11 = "0.14.0" 33 | html5lib = "1.1" 34 | httpcore = "1.0.6" 35 | httpx = "0.27.2" 36 | idna = "3.10" 37 | jiter = "0.6.1" 38 | lxml = "5.3.0" 39 | markupsafe = "3.0.1" 40 | mccabe = "0.7.0" 41 | multitasking = "0.0.11" 42 | mypy-extensions = "1.0.0" 43 | numpy = "1.26.4" 44 | packaging = "24.1" 45 | pandas = "2.2.3" 46 | peewee = "3.17.6" 47 | platformdirs = "4.3.6" 48 | plotly = "5.24.1" 49 | pyarrow = "15.0.2" 50 | pycodestyle = "2.12.1" 51 | pycparser = "2.22" 52 | pydantic = "2.9.2" 53 | pydantic-core = "2.23.4" 54 | pyflakes = "3.2.0" 55 | python-dateutil = "2.9.0.post0" 56 | python-multipart = "0.0.12" 57 | pytz = "2024.2" 58 | requests = "2.32.3" 59 | six = "1.16.0" 60 | sniffio = "1.3.1" 61 | soupsieve = "2.6" 62 | starlette = "0.38.6" 63 | tenacity = "9.0.0" 64 | tqdm = "4.66.5" 65 | typing-extensions = "4.12.2" 66 | tzdata = "2024.2" 67 | urllib3 = "2.2.3" 68 | uvicorn = "0.31.0" 69 | watchdog = "3.0.0" 70 | webencodings = "0.5.1" 71 | websockets = "12.0" 72 | writerai = "3.4.0" 73 | yfinance = "0.2.44" 74 | 75 | 76 | [build-system] 77 | requires = ["poetry-core"] 78 | build-backend = "poetry.core.masonry.api" 79 | -------------------------------------------------------------------------------- /financial-tools-chat/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /financial-tools-chat/static/custom.css: -------------------------------------------------------------------------------- 1 | .visualization{ 2 | height: 80vh !important; 3 | overflow-y: auto !important; 4 | } 5 | 6 | .column-container{ 7 | width: 71vw !important; 8 | } 9 | -------------------------------------------------------------------------------- /financial-tools-chat/static/custom.js: -------------------------------------------------------------------------------- 1 | const chat = document.getElementsByClassName("chat") 2 | 3 | export function enableDisableTextarea(disable){ 4 | const isDisabled = (disable === 'true') 5 | 6 | chat["0"].getElementsByTagName("textarea")["0"].disabled = isDisabled 7 | chat["0"].getElementsByTagName("button")["0"].disabled = isDisabled 8 | } 9 | 10 | 11 | 12 | export function focusOnTab(tabId){ 13 | const tabDivs = document.querySelectorAll('[data-writer-id]'); 14 | for (const tabDivElement of tabDivs) 15 | if(tabDivElement.getAttribute("data-writer-id") === tabId) { 16 | const button = tabDivElement.getElementsByTagName("button")[0]; 17 | console.log(button) 18 | button.click(); 19 | break; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /financial-tools-chat/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/financial-tools-chat/static/favicon.png -------------------------------------------------------------------------------- /hacker-news-social-listener/.env.example: -------------------------------------------------------------------------------- 1 | WRITER_API_KEY=your_writer_api_key 2 | GRAPH_ID=your_graph_id 3 | HACKERNEWS_API_URL=https://hacker-news.firebaseio.com/v0 4 | -------------------------------------------------------------------------------- /hacker-news-social-listener/.gitignore: -------------------------------------------------------------------------------- 1 | static/hackernews_comments.csv 2 | static/hackernews_posts.csv 3 | -------------------------------------------------------------------------------- /hacker-news-social-listener/.wf/components-root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "root", "type": "root", "content": {"appName": "Hacker News Listener"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /hacker-news-social-listener/.wf/components-workflows_root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /hacker-news-social-listener/.wf/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "writer_version": "0.8.2" 3 | } -------------------------------------------------------------------------------- /hacker-news-social-listener/README.md: -------------------------------------------------------------------------------- 1 | # Hacker News Listener 2 | This application is built using the Writer Framework and is designed to scrape the top posts and comments from Hacker News. It processes the data, uploads it to a Writer Knowledge Graph for further analysis, and generates AI-powered insights based on the content of the posts. 3 | 4 | ## Usage 5 | 6 | 1. Select the number of items you wish to process. 7 | 2. The application will generate raw data with analysis of posts and comments. 8 | 3. Ask specific questions using the Knowledge Graph chat. 9 | 4. Generate a detailed report from the processed data using the Prepared Report feature. 10 | 11 | ## Running the application 12 | First, ensure you have Poetry installed. Then, in the project directory, install the dependencies by running: 13 | 14 | ```sh 15 | poetry install 16 | ``` 17 | 18 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel), create a new API Key and Knowledge Graph. To pass your API key and Knowledge Graph to the Writer Framework, you'll need to set an environment variables called `WRITER_API_KEY` and `GRAPH_ID`: 19 | ```sh 20 | export WRITER_API_KEY=your-api-key 21 | ``` 22 | ```sh 23 | export GRAPH_ID=your-graph-id 24 | ``` 25 | 26 | You can also set the `WRITER_API_KEY` and `GRAPH_ID` in the `.env` file. 27 | 28 | To make changes or edit the application, navigate to root folder and use the following command: 29 | 30 | 31 | ```sh 32 | writer edit . 33 | ``` 34 | 35 | Once you're ready to run the application, execute: 36 | 37 | ```sh 38 | writer run . 39 | ``` 40 | 41 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 42 | 43 | ## About Writer 44 | 45 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). 46 | -------------------------------------------------------------------------------- /hacker-news-social-listener/prompts.py: -------------------------------------------------------------------------------- 1 | def report_prompt(posts, comments): 2 | return f""" 3 | # CONTEXT # 4 | You are an expert at analyzing large amounts of posts and comments 5 | at social network for software developers called HackerNews. You are creating 6 | summary reports of provided data. Furthermore, you are acting 7 | really carefully outlining main trends, top posts and comments, 8 | most famous topics and development approaches. 9 | 10 | # INSTRUCTIONS # 11 | Create an expertly written summary report. 12 | 13 | # DATA # 14 | Here are the posts and comments you will use to create the report. 15 | 16 | Posts: 17 | {posts} 18 | 19 | Comments: 20 | {comments} 21 | 22 | # ADDITIONAL GUIDELINES # 23 | - Reflect only top posts and comments. DO NOT reflect all data in your 24 | report. 25 | - FIT your report in 10-15 paragraphs. This is also very IMPORTANT. 26 | - Say a few words about posts reflected at report. 27 | - Provide some analysis of trends you are surveying: if users consider 28 | theme useful or not, if they are pleased with it and so on. 29 | - Outline most interesting, discussed and high rated comments and posts. 30 | 31 | # RESPONSE FORMAT # 32 | 33 | Highlight headers, topics, main ideas. Use .md markup to style your text. 34 | """ 35 | -------------------------------------------------------------------------------- /hacker-news-social-listener/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = "^0.8.2" 11 | praw = "^7.7.1" 12 | black = "^24.8.0" 13 | flake8 = "^7.1.1" 14 | isort = "^5.13.2" 15 | pre-commit = "^3.8.0" 16 | python-dotenv = "^1.0.1" 17 | aiohttp = "^3.10.10" 18 | asyncio = "^3.4.3" 19 | 20 | 21 | [build-system] 22 | requires = ["poetry-core"] 23 | build-backend = "poetry.core.masonry.api" 24 | -------------------------------------------------------------------------------- /hacker-news-social-listener/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /hacker-news-social-listener/static/custom.css: -------------------------------------------------------------------------------- 1 | .file{ 2 | height: 60vh !important; 3 | } 4 | 5 | .file-text{ 6 | height: 51vh !important; 7 | overflow-y: auto !important; 8 | } 9 | 10 | .files-list{ 11 | height: 79vh !important; 12 | overflow-y: auto !important; 13 | } 14 | 15 | .CoreSection__title h3{ 16 | font-size: 1.05rem !important; 17 | } 18 | -------------------------------------------------------------------------------- /hacker-news-social-listener/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/hacker-news-social-listener/static/favicon.png -------------------------------------------------------------------------------- /hf-spaces-starter/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build stage 2 | FROM python:3.11-slim-buster AS Build 3 | 4 | # Set environment variables for Python and Poetry 5 | ENV PYTHONUNBUFFERED=1 \ 6 | PIP_NO_CACHE_DIR=1 \ 7 | POETRY_NO_INTERACTION=1 \ 8 | POETRY_VIRTUALENVS_CREATE=false \ 9 | POETRY_VERSION=1.7.1 10 | 11 | # Set the working directory in the container 12 | WORKDIR /app 13 | 14 | # Copy the dependencies file to the working directory 15 | COPY ./pyproject.toml /app/ 16 | 17 | # Update, install dependencies, and prepare the Python environment 18 | RUN apt-get update && \ 19 | apt-get install -y gcc g++ unixodbc-dev && \ 20 | pip install "poetry==$POETRY_VERSION" && \ 21 | poetry export --without-hashes --format requirements.txt --output requirements.txt && \ 22 | python3 -m pip wheel --no-cache-dir --no-deps -w /app/wheels -r requirements.txt 23 | 24 | # Runtime stage 25 | FROM python:3.11-slim-buster AS Run 26 | 27 | # Set environment variables for Python and Poetry 28 | ENV HOME=/home/user \ 29 | PATH=/home/user/.local/bin:$PATH 30 | 31 | # Create a non-root user 32 | RUN useradd -m -u 1000 user 33 | 34 | # Switch to the non-root user 35 | USER user 36 | 37 | # Copy wheel files from the build stage 38 | COPY --from=build /app/wheels $HOME/app/wheels 39 | 40 | # Set the working directory to where the wheels are 41 | WORKDIR $HOME/app/wheels 42 | 43 | # Install the wheel files 44 | RUN pip3 --no-cache-dir install *.whl 45 | 46 | # Copy the application files to the working directory (change to your app name) 47 | COPY --chown=user ./your-app-name $HOME/app 48 | 49 | # Set the working directory to the application files 50 | WORKDIR $HOME/app 51 | 52 | # Specify the command to run the application 53 | ENTRYPOINT [ "writer", "run" ] 54 | 55 | # Expose the port the app runs on 56 | EXPOSE 8080 57 | 58 | # Set the default command to run the app 59 | CMD [ ".", "--port", "8080", "--host", "0.0.0.0" ] -------------------------------------------------------------------------------- /hf-spaces-starter/images/finance-dashboard-hf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/hf-spaces-starter/images/finance-dashboard-hf.png -------------------------------------------------------------------------------- /hf-spaces-starter/images/new_space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/hf-spaces-starter/images/new_space.png -------------------------------------------------------------------------------- /localized-promo-dashboard/README.md: -------------------------------------------------------------------------------- 1 | First presented during AILF London on May 30th, 2024. -------------------------------------------------------------------------------- /localized-promo-dashboard/data/default_segments.csv: -------------------------------------------------------------------------------- 1 | City,Segment,Revenue 2 | Germany,School,8234 3 | Germany,University,7321 4 | Germany,Work,10294 5 | Germany,Retired,5893 6 | Brazil,School,9321 7 | Brazil,University,7214 8 | Brazil,Work,12321 9 | Brazil,Retired,7821 10 | -------------------------------------------------------------------------------- /localized-promo-dashboard/data/segments.csv: -------------------------------------------------------------------------------- 1 | City,Segment,Revenue 2 | UK,School,1075 3 | UK,University,5276 4 | UK,Work,4600 5 | UK,Retired,1051 6 | Australia,School,9916 7 | Australia,University,6063 8 | Australia,Work,9892 9 | Australia,Retired,5828 10 | US,School,36324 11 | US,University,4732 12 | US,Work,35848 13 | US,Retired,22992 14 | Spain,School,2932 15 | Spain,University,1819 16 | Spain,Work,7322 17 | Spain,Retired,3672 18 | Poland,School,1932 19 | Poland,University,2819 20 | Poland,Work,3322 21 | Poland,Retired,4672 22 | -------------------------------------------------------------------------------- /localized-promo-dashboard/main.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pandas as pd 4 | import plotly.express as px 5 | import writer as wf 6 | import writer.ai 7 | from prompts import base_prompt 8 | 9 | 10 | def handle_file_upload_click(payload, state): 11 | _save_file(state, payload[0]) 12 | _generate_output_data(state, state["file"]["file_path"]) 13 | 14 | 15 | def _save_file(state, file): 16 | name = file.get("name") 17 | state["file"]["name"] = name 18 | state["file"]["file_path"] = f"data/{name}" 19 | file_data = file.get("data") 20 | with open(f"data/{name}", "wb") as file_handle: 21 | file_handle.write(file_data) 22 | 23 | 24 | def _generate_output_data(state, file_path): 25 | state["data_frame"] = pd.read_csv(file_path) 26 | _set_default_input(state) 27 | _generate_segments_chart(state) 28 | _generate_promo_materials(state) 29 | 30 | 31 | def _set_default_input(state): 32 | if not state["data_frame"].empty: 33 | state["input_data"]["location"] = state["data_frame"].at[0, "City"] 34 | state["input_data"]["segment"] = state["data_frame"].at[0, "Segment"] 35 | 36 | 37 | def handle_chart_click(payload, state): 38 | point_number = payload[0]["pointNumber"] 39 | fig = state["generated_charts"]["segments"] 40 | point_id = fig.data[0]["ids"][point_number] 41 | point_segments = point_id.split("/") 42 | 43 | if len(point_segments) < 3: 44 | return 45 | _all, location, segment = point_segments 46 | 47 | if ( 48 | state["input_data"]["location"] == location 49 | and state["input_data"]["segment"] == segment 50 | ): 51 | return 52 | 53 | state["input_data"]["location"] = location 54 | state["input_data"]["segment"] = segment 55 | 56 | _generate_promo_materials(state) 57 | 58 | 59 | def _generate_promo_materials(state): 60 | state["available"] = False 61 | state["message"] = "% Generating promotional material..." 62 | 63 | social_post = _generate_post( 64 | state["base_prompt"].format(**state["input_data"].to_dict()) 65 | ) 66 | hashtags = _generate_post_hashtags(social_post) 67 | promo_email = _generate_promo_email(social_post) 68 | blog_post = _generate_blog_post(social_post) 69 | 70 | state["generated_data"]["social_post"] = social_post 71 | state["generated_data"]["hashtags"] = { 72 | item: item for item in re.findall(r"#\w+", hashtags) 73 | } 74 | state["generated_data"]["promo_email"] = promo_email 75 | state["generated_data"]["blog_post"] = blog_post 76 | 77 | state["message"] = "" 78 | state["available"] = True 79 | 80 | 81 | def _generate_post(prompt): 82 | social_post = writer.ai.complete(prompt) 83 | return social_post 84 | 85 | 86 | def _generate_post_hashtags(social_post): 87 | prompt = ( 88 | f"Based on the following social media post, output ten hashtags in the language of the post, " 89 | f"for example #dogs #cats #food #pets etc:\n{social_post}" 90 | ) 91 | hashtags = writer.ai.complete(prompt) 92 | return hashtags 93 | 94 | 95 | def _generate_promo_email(social_post): 96 | prompt = f"Based on the following social media post, create a promotional email:\n{social_post}" 97 | promo_email = writer.ai.complete(prompt) 98 | return promo_email 99 | 100 | 101 | def _generate_blog_post(social_post): 102 | prompt = ( 103 | f"Based on the following social media post, create a blog post:\n{social_post}" 104 | ) 105 | blog_post = writer.ai.complete(prompt) 106 | return blog_post 107 | 108 | 109 | def _generate_segments_chart(state): 110 | colors = [ 111 | "#FFCFC2", 112 | "#B5EEEE", 113 | "#E4E9FF", 114 | "#FFD0F7", 115 | "#CECFFF", 116 | "#F2FFA2", 117 | "#B1EFDD", 118 | "#E5D1FA", 119 | "#FFE3FA", 120 | ] 121 | fig = px.treemap( 122 | state["data_frame"], 123 | path=[px.Constant("all"), "City", "Segment"], 124 | values="Revenue", 125 | hover_data=["City", "Segment"], 126 | color_discrete_sequence=colors, 127 | ) 128 | state["generated_charts"]["segments"] = fig.update_layout( 129 | margin=dict(l=0, r=0, t=0, b=0), paper_bgcolor="#F5F5F9" 130 | ) 131 | 132 | 133 | def _init_default_output_fields(state): 134 | _generate_output_data(state, "data/default_segments.csv") 135 | _set_default_input(state) 136 | 137 | 138 | initial_state = wf.init_state( 139 | { 140 | "base_prompt": base_prompt, 141 | "file": {"name": "", "file_path": ""}, 142 | "input_data": { 143 | "location": "", 144 | "segment": "", 145 | "product_description": "Nomnom the cereal bar made in London, UK, crafted only with natural ingredients.", 146 | }, 147 | "generated_data": {"social_post": "", "promo_email": "", "blog_post": ""}, 148 | "generated_charts": { 149 | "segments": None, 150 | }, 151 | "message": "", 152 | "available": False, 153 | } 154 | ) 155 | 156 | 157 | _init_default_output_fields(initial_state) 158 | -------------------------------------------------------------------------------- /localized-promo-dashboard/prompts.py: -------------------------------------------------------------------------------- 1 | base_prompt = """ 2 | Output in the language of the location {location}. 3 | 4 | Write a promotional social media post for the product presented below the dashes. Include emojis. 5 | 6 | Target, specifically, the segment "{segment}". 7 | 8 | The market segments are as follows: 9 | - School: Grade school or high school students 10 | - University: University students 11 | - Work: People of working age 12 | - Retired: People who are retired 13 | 14 | Connect to the audience by using very strong references to {location}, but don't change its 15 | place of origin nor mention product features not present in the product description below. 16 | 17 | Use markdown syntax for the output. 18 | 19 | ----- 20 | {product_description} 21 | """ 22 | -------------------------------------------------------------------------------- /localized-promo-dashboard/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = {version = "^0.6.0rc5"} 11 | flake8 = "^7.1.1" 12 | black = "^24.8.0" 13 | isort = "^5.13.2" 14 | pre-commit = "^3.8.0" 15 | 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /localized-promo-dashboard/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /localized-promo-dashboard/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/localized-promo-dashboard/static/favicon.png -------------------------------------------------------------------------------- /palmyra-comparison/.env.example: -------------------------------------------------------------------------------- 1 | WRITER_API_KEY=your Writer api key -------------------------------------------------------------------------------- /palmyra-comparison/.wf/components-root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "root", "type": "root", "content": {"appName": "Palmyra Comparison"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /palmyra-comparison/.wf/components-workflows_root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /palmyra-comparison/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "lfltcky7l1fsm6j2", "type": "workflows_workflow", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "workflows_root", "position": 0} 2 | -------------------------------------------------------------------------------- /palmyra-comparison/.wf/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "writer_version": "0.8.2" 3 | } -------------------------------------------------------------------------------- /palmyra-comparison/README.md: -------------------------------------------------------------------------------- 1 | This app was created using Writer Framework. 2 | 3 | To learn more about it, visit https://dev.writer.com/framework 4 | -------------------------------------------------------------------------------- /palmyra-comparison/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import writer as wf 4 | from dotenv import load_dotenv 5 | from writer import WriterState 6 | from writerai import AsyncWriter 7 | from writerai.types.chat_chat_params import Message 8 | 9 | load_dotenv() 10 | 11 | def handle_user_input(payload: str, state: WriterState) -> None: 12 | asyncio.run(_ask_models(payload, state)) 13 | 14 | 15 | def handle_prompt_button(context: dict, state: WriterState) -> None: 16 | state["user-prompt"] = state[context["target"]+'-full'] 17 | asyncio.run(_ask_models(state[context["target"]+'-full'], state)) 18 | 19 | 20 | def handle_dropdown_choice(context: dict, payload: str, state: WriterState) -> None: 21 | if "right" in context["target"]: 22 | state["palmyra-description-right"] = state["palmyra-models-description"][payload] 23 | else: 24 | state["palmyra-description-left"] = state["palmyra-models-description"][payload] 25 | 26 | 27 | async def _ask_models(prompt: str, state: WriterState) -> None: 28 | state["palmyra-left-conversation"].append(Message(role="user", content=prompt)) 29 | state["palmyra-right-conversation"].append(Message(role="user", content=prompt)) 30 | 31 | async_writer_client = AsyncWriter() 32 | 33 | await asyncio.gather( 34 | _perform_async_streaming(async_writer_client, state["palmyra-left-model"], "left", state), 35 | _perform_async_streaming(async_writer_client, state["palmyra-right-model"], "right", state), 36 | ) 37 | 38 | 39 | async def _perform_async_streaming(client: AsyncWriter, model: str, side: str, state: WriterState) -> None: 40 | try: 41 | response = await client.chat.chat( 42 | model=model, 43 | messages=state[f"palmyra-{side}-conversation"], 44 | stream=True, 45 | max_tokens=state[f"palmyra-{side}-max-tokens"], 46 | temperature=state[f"palmyra-{side}-temperature"], 47 | ) 48 | 49 | response_message = "" 50 | state[f"palmyra-{side}-response"] = "" 51 | 52 | async for message in response: 53 | content = message.choices[0].delta.content 54 | content = content if content is not None else "" 55 | response_message += content 56 | state[f"palmyra-{side}-response"] += content 57 | 58 | state[f"palmyra-{side}-conversation"].append(Message(role="assistant", content=response_message)) 59 | 60 | except Exception as e: 61 | response_message = "Something went wrong. Please, try again." 62 | state[f"palmyra-{side}-conversation"].append(Message(role="assistant", content=response_message)) 63 | raise e 64 | 65 | palmyra_models_description = { 66 | "palmyra-x-004": "**Palmyra X 004** is our newest and most advanced language model with a large context window of up to 128,000 tokens. This model excels in processing and understanding complex tasks, making it ideal for workflow automation, coding tasks, and data analysis.", 67 | "palmyra-med": "**Palmyra Medical** is the latest version of our healthcare model and the most accurate in the market. The Writer full-stack generative AI platform is used by the world’s leading Fortune 50 healthcare companies to help improve patient outcomes with powerful AI that are infused with deep medical knowledge.", 68 | "palmyra-fin-32k": "**Palmyra Financial** is Writer’s specialized language model for the finance industry, designed to support critical financial workflows with precision in terminology and document analysis. Palmyra Fin empowers financial organizations to streamline processes and make data-driven decisions confidently.", 69 | "palmyra-creative": "**Palmyra Creative** is Writer's purpose-built language model, engineered to elevate creative thinking and writing across diverse professional contexts. With capabilities that amplify originality and adaptability, it caters to industries and teams where innovation drives success. ", 70 | } 71 | 72 | initial_state = wf.init_state( 73 | { 74 | "palmyra-models": { 75 | "palmyra-x-004": "Palmyra X 004", 76 | "palmyra-med": "Palmyra Medical", 77 | "palmyra-fin-32k": "Palmyra Financial", 78 | "palmyra-creative": "Palmyra Creative", 79 | }, 80 | "palmyra-models-description": palmyra_models_description, 81 | "palmyra-left-conversation": [], 82 | "palmyra-left-response": "Model response will appear here...", 83 | "palmyra-left-model": "palmyra-x-004", 84 | "palmyra-left-temperature": 0.7, 85 | "palmyra-left-max-tokens": 16384, 86 | "palmyra-description-left": palmyra_models_description["palmyra-x-004"], 87 | "palmyra-right-conversation": [], 88 | "palmyra-right-response": "Model response will appear here...", 89 | "palmyra-right-model": "palmyra-creative", 90 | "palmyra-right-temperature": 0.7, 91 | "palmyra-right-max-tokens": 16384, 92 | "palmyra-description-right": palmyra_models_description["palmyra-creative"], 93 | "user-prompt": "", 94 | "prompt-left-button": "Brainstorm bakery strategies", 95 | "prompt-left-button-full": "Imagine you're a struggling small-town bakery competing with a chain that opened across the street. Brainstorm unconventional strategies to win over customers without lowering prices.", 96 | "prompt-center-button": "Explain AI to a high schooler", 97 | "prompt-center-button-full": "Write a guide for a programmer who wants to explain their AI side project to a high schooler. The explanation must be engaging, simple, and use humorous analogies, while avoiding technical jargon.", 98 | "prompt-right-button": "Zero gravity game", 99 | "prompt-right-button-full": "Design a game that could only exist in zero gravity.", 100 | } 101 | ) 102 | 103 | initial_state.import_stylesheet("style", "/static/custom.css") 104 | -------------------------------------------------------------------------------- /palmyra-comparison/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | writer = "0.8.2" 11 | python-dotenv = "^1.0.1" 12 | 13 | 14 | [build-system] 15 | requires = ["poetry-core"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /palmyra-comparison/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /palmyra-comparison/static/custom.css: -------------------------------------------------------------------------------- 1 | .model-response { 2 | height: 55vh !important; 3 | overflow-y: auto !important; 4 | } 5 | 6 | .prompt-input{ 7 | max-width: 90vw !important; 8 | } 9 | 10 | .prompt-button{ 11 | width: 100vw !important; 12 | } 13 | 14 | .WdsDropdownInput{ 15 | width: 100vw !important; 16 | } -------------------------------------------------------------------------------- /palmyra-comparison/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/palmyra-comparison/static/favicon.png -------------------------------------------------------------------------------- /patient-portal/README.md: -------------------------------------------------------------------------------- 1 | # Patient interaction portal 2 | This application simulates a chat between a doctor and a patient. Clicking the generate button allows the doctor to generate SOAP notes and extract ICD-10 codes using Palmyra-Med. 3 | 4 | The main functionality of the app is in `main.py`. Prompts are in `prompts.py`. 5 | 6 | ## Running the application 7 | First, install Writer Frameowrk: 8 | 9 | ```sh 10 | pip install writer 11 | ``` 12 | 13 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 14 | 15 | ```sh 16 | export WRITER_API_KEY=your-api-key 17 | ``` 18 | 19 | Then, navigate to this folder and run: 20 | 21 | ```sh 22 | writer edit . 23 | ``` 24 | 25 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 26 | 27 | ## About Writer 28 | 29 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). -------------------------------------------------------------------------------- /patient-portal/main.py: -------------------------------------------------------------------------------- 1 | import writer as wf 2 | import writer.ai 3 | from prompts import soap_notes_prompt, icd_codes_prompt 4 | 5 | # Chat event handler 6 | def handle_simple_message(state, payload): 7 | state["conversation"] += payload 8 | 9 | for chunk in state["conversation"].stream_complete(): 10 | state["conversation"] += chunk 11 | 12 | # Generate button event handler 13 | def handle_generate_button(state): 14 | state["message"] = "% Generating SOAP notes..." 15 | conversation_history = state["conversation"].serialized_messages 16 | notes_prompt = soap_notes_prompt(conversation_history) 17 | state["notes"] = writer.ai.complete(notes_prompt, config={"model": "palmyra-med-32k", "max_tokens": 1000}) 18 | 19 | state["message"] = "% Generating ICD-10 codes..." 20 | codes_prompt = icd_codes_prompt(conversation_history, state["notes"]) 21 | state["icd_codes"] = writer.ai.complete(codes_prompt, config={"model": "palmyra-med-32k", "max_tokens": 1000}) 22 | state["message"] = None 23 | 24 | # Sample chat history between doctor and patient 25 | history = [ 26 | {"role": "user", "content": "Hello. Please tell me about your syptoms."}, 27 | {"role": "assistant", "content": "Hello doctor, I've been having some severe lower back pain for a few days now."}, 28 | {"role": "user", "content": "I'm sorry to hear about your discomfort. Can you tell me if the pain is localized or does it spread to other areas?"}, 29 | {"role": "assistant", "content": "It's mostly in the lower back, but sometimes I feel it shooting down my left leg."}, 30 | {"role": "user", "content": "Does anything in particular seem to trigger the pain or make it worse?"}, 31 | {"role": "assistant", "content": "It gets worse when I'm sitting for long periods or when I try to stand up."}, 32 | {"role": "user", "content": "Have you tried any treatments at home, like applying heat or taking any over-the-counter medications?"}, 33 | {"role": "assistant", "content": "I've used a heating pad and taken ibuprofen, but they only provide temporary relief."}, 34 | {"role": "user", "content": "Given the nature of your symptoms, it sounds like it could be related to sciatica, especially with the pain radiating down your leg. I'm going to prescribe a medication called gabapentin to help manage the pain and nerve symptoms."}, 35 | {"role": "assistant", "content": "Thank you, doctor. How often should I take the medication?"}, 36 | {"role": "user", "content": "I'll start you on a low dose, and we can adjust it based on how you respond. You'll take it three times a day. It's important to take it exactly as prescribed to manage your symptoms effectively."}, 37 | {"role": "assistant", "content": "Understood. Should I continue with the heating pad and ibuprofen as well?"}, 38 | {"role": "user", "content": "Yes, you can continue those as needed for additional relief. We'll review everything during your follow-up visit."}, 39 | {"role": "assistant", "content": "I appreciate your help. I'll start the medication and keep you updated on how I feel."} 40 | ] 41 | 42 | # Initialize the state with the conversation history and initial values 43 | wf.init_state({ 44 | "my_app": { 45 | "title": "PATIENT INTERACTION PORTAL" 46 | }, 47 | "conversation": writer.ai.Conversation(history, config={"model": "palmyra-med-32k"}), 48 | "notes": "SOAP notes from patient interaction will appear here.", 49 | "icd_codes": "Extracted ICD-10 codes will appear here.", 50 | "message": None 51 | }) 52 | -------------------------------------------------------------------------------- /patient-portal/prompts.py: -------------------------------------------------------------------------------- 1 | def soap_notes_prompt(history): 2 | return f''' 3 | System: You are an expert at reviewing medical conversations and generating SOAP format medical notes summarizing those conversations. You will be given a conversation between a doctor and their patient. Review the conversation and create SOAP notes summarizing the encounter. 4 | 5 | Here are examples of SOAP notes. Your SOAP notes must be in the same exact format as these examples: 6 | 7 | 8 | ### Lower back pain 9 | Patient experiencing severe lower back pain, which occasionally radiates down the left leg, indicative of possible sciatica. The symptoms have been present for a few days. The pain intensifies when sitting for prolonged periods or when attempting to stand. Home remedies such as a heating pad and ibuprofen have been tried, providing only temporary relief.\n\n 10 | 11 | Home care reviewed including continuing the use of a heating pad and over-the-counter ibuprofen for pain management. Advised to maintain moderate physical activity as tolerated and avoid prolonged sitting or standing in one position.\n\n\n 12 | 13 | #### Prescription sent \n 14 | Gabapentin \n 15 | Take 1 capsule three times a day \n 16 | Disp: #90 capsules \n 17 | Refills: 1 \n\n 18 | 19 | Patient is advised to monitor symptoms and report any increase in pain or new symptoms. A follow-up visit is recommended to assess the effectiveness of the treatment and make any necessary adjustments. 20 | 21 | 22 | 23 | ### Adult Sinus 24 | Patient experiencing worsening sinus congestion, drainage, and headache for the past 5 days. The patient reports pain around the forehead and cheeks, particularly when bending over, but no fever is present. Home remedies including saline nasal spray and over-the-counter decongestants have been tried with minimal relief.\n\n 25 | 26 | Home care reviewed including continuing the use of saline nasal spray and maintaining hydration. Advised to avoid allergens and pollutants where possible and to use steam inhalation to help alleviate congestion.\n\n\n 27 | 28 | #### Prescriptions sent \n\n 29 | Amoxicillin 500 mg \n 30 | Take 1 capsule three times a day \n 31 | Disp: #30 capsules \n 32 | Refills: 0\n\n 33 | 34 | Fluticasone Propionate Nasal Spray \n 35 | Use two sprays in each nostril once a day \n 36 | Disp: #1 bottle \n 37 | Refills: 1\n\n 38 | 39 | Patient is advised to complete the full course of antibiotics and to monitor symptoms closely. A follow-up is recommended if symptoms persist beyond the treatment period or worsen. 40 | 41 | 42 | Here is the conversation you need to review: 43 | 44 | 45 | {history} 46 | 47 | 48 | Only give me the notes. Do not give me a title nor start your notes with "SOAP Notes". Do use Markdown formatting for your notes, including line breaks and an empty line between each paragraph. 49 | 50 | You must only use information from the conversation to write your SOAP notes. Do not make up any information. Use the same exact format as the examples provided for your SOAP notes, starting with a category as a Markdown header (e.g. "### Lower back pain" or "### Adult Sinus"). If the doctor orders medication(s) within the conversation, list that at the end of the SOAP notes, exactly like the examples. ONLY INCLUDE A MEDICATION IF IT WAS ORDERED - JUST BECAUSE A MEDICATION IS MENTIONED DOES NOT MEAN IT WAS ORDERED. 51 | 52 | If the patient's age is not mentioned in the conversation, then do not mention their age in your SOAP notes. 53 | 54 | Give me the SOAP notes now: 55 | ''' 56 | 57 | def icd_codes_prompt(history, notes): 58 | return f''' 59 | System: You are an expert at extracting ICD-10 codes from SOAP format medical notes. You will be given a conversation between a doctor and their patient, the resulting SOAP notes, and asked to extract all relevant ICD-10 codes. 60 | 61 | Here is the conversation you need to review: 62 | {history} 63 | 64 | Here are the SOAP notes you need to review: 65 | {notes} 66 | 67 | Extract all relevant ICD-10 codes from the conversation and SOAP notes. 68 | ''' -------------------------------------------------------------------------------- /patient-portal/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = {version = "^0.6.0"} 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /patient-portal/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /patient-portal/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/patient-portal/static/favicon.png -------------------------------------------------------------------------------- /pd-embedded-chat/README.md: -------------------------------------------------------------------------------- 1 | # Embedded Knowledge Graph chat 2 | This Writer Framework application lets you upload files to a Knowledge Graph and then use an embedded no-code chat application to query them with graph-based RAG. The sample data included in the `static` folder is for product descriptions, but you could use this application in any other domain. 3 | 4 | The main functionality of the app is in `main.py`. To run this application, you will need to do three things: 5 | 6 | 1. Create a Knowledge Graph, either in using AI Studio [no-code tools](https://support.writer.com/article/242-how-to-create-and-manage-a-knowledge-graph#How-to-create-a-new-Knowledge-Graph-dlpSX) or [using the API](https://dev.writer.com/api-guides/knowledge-graph). 7 | 2. Create a no-code chat application in AI Studio. You can [follow this guide on our docs](https://dev.writer.com/no-code/building-a-chat-app). Be sure to toggle "Knowledge Graph mode" in the app. 8 | 3. In the "Deploy" tab for the chat application, toggle the "Embed" option for the application and retrieve the embed URL. Check out [this document](https://dev.writer.com/no-code/deploying-an-app) to learn how to do this. The embed URL will go in the `iframe` component's `src` input. 9 | 10 | You may also want to check out the [Knowledge Graph API reference](https://dev.writer.com/api-guides/api-reference/kg-api/create-graph) and the [File API reference](https://dev.writer.com/api-guides/api-reference/file-api/upload-files). 11 | 12 | ## Running the application 13 | First, install Writer Frameowrk: 14 | 15 | ```sh 16 | pip install writer 17 | ``` 18 | 19 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 20 | 21 | ```sh 22 | export WRITER_API_KEY=your-api-key 23 | ``` 24 | 25 | Then, navigate to this folder and run: 26 | 27 | ```sh 28 | writer edit . 29 | ``` 30 | 31 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 32 | 33 | ## About Writer 34 | 35 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). -------------------------------------------------------------------------------- /pd-embedded-chat/main.py: -------------------------------------------------------------------------------- 1 | import writer as wf 2 | import writer.ai 3 | import time 4 | 5 | # Replace your Knowledge Graph ID here 6 | GRAPH_ID = "your-kg-id-here" 7 | 8 | # Adjust this timeout and sleep time as needed to avoid spamming the API 9 | MAX_TIMEOUT = 90 10 | SLEEP_TIME = 0.6 11 | 12 | # Load the files and let the user know 13 | def handle_file_upload(state, payload): 14 | state["files"] = payload 15 | if (len(state["files"]) == 1): 16 | state["message"] = f'File {state["files"][0]["name"]} ready to upload.' 17 | else: 18 | state["message"] = f'{len(state["files"])} files ready to upload.' 19 | 20 | # Upload and add file(s) to Knowledge Graph 21 | def handle_add_to_graph(state): 22 | # Retrieve the graph 23 | graph = writer.ai.retrieve_graph(GRAPH_ID) 24 | current_completed = graph.file_status.completed 25 | new_completed = current_completed + len(state["files"]) 26 | 27 | # Upload the files 28 | for file in state["files"]: 29 | state["message"] = f'% Kicking off upload of {file["name"]}...' 30 | uploaded_file = writer.ai.upload_file(data=file["data"], name=file["name"], type=file["type"]) 31 | graph.add_file(uploaded_file) 32 | 33 | # Poll the graph until all files are indexed and marked as completed 34 | state["message"] = "% Processing and indexing files in Knowledge Graph..." 35 | start_time = time.time() 36 | while (time.time() - start_time) < MAX_TIMEOUT: 37 | try: 38 | updated_graph = writer.ai.retrieve_graph(GRAPH_ID) 39 | if updated_graph.file_status.completed == new_completed: 40 | state["message"] = "+ File(s) successfully added to graph." 41 | return True 42 | except Exception as e: 43 | print(f"Error occurred: {e}") 44 | time.sleep(SLEEP_TIME) 45 | state["message"] = "- Indexing timed out. Please try again." 46 | 47 | # Initialise the state 48 | wf.init_state({ 49 | "my_app": { 50 | "title": "Chat with product descriptions" 51 | }, 52 | "message": "", 53 | "files": [] 54 | }) -------------------------------------------------------------------------------- /pd-embedded-chat/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = {version = "^0.6.0"} 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /pd-embedded-chat/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /pd-embedded-chat/static/beverage-descriptions.csv: -------------------------------------------------------------------------------- 1 | Name,Description,Feature 1,Feature 2,Feature 3,Key Ingredients 2 | Crimson Cherry Cooler,"Sip on the vibrant taste of Crimson Cherry Cooler, a refreshing blend of tart cherries and sparkling water. Perfect for a sunny day refreshment.",Tart cherry flavor,Sparkling water,Refreshing and vibrant,Cherries, carbonated water, sugar 3 | Azure Apple Fizz,"Dive into the crisp and bubbly Azure Apple Fizz. This sparkling apple cider offers a sweet, refreshing taste with every effervescent sip.",Crisp apple flavor,Effervescent bubbles,Sweet and refreshing,Apple juice, carbonated water, sugar 4 | Mango Melody Mix,"Tropical flavors await in Mango Melody Mix, a smooth blend of ripe mangoes and a hint of coconut. It's like a beach vacation in a bottle.",Ripe mango flavor,Hint of coconut,Smooth tropical blend,Mango puree, coconut water, sugar 5 | Lemon Lime Leap,"Experience the zesty burst of Lemon Lime Leap, a citrus-packed beverage that energizes and refreshes with its tangy flavor.",Zesty citrus flavor,Energizing and refreshing,Tangy taste,Lemon juice, lime juice, sugar, water 6 | Berry Bliss Brew,"Indulge in the sweet symphony of Berry Bliss Brew, a mixed berry tea that combines blueberries, raspberries, and strawberries in a delightful drink.",Mixed berry blend,Sweet and indulgent,Delightful and refreshing,Blueberry, raspberry, strawberry, water 7 | Ginger Gold Rush,"Spice up your day with Ginger Gold Rush, a bold ginger beer that packs a punch with its strong, spicy flavor and a hint of sweetness.",Strong ginger flavor,Spicy with a sweet hint,Bold and invigorating,Ginger, sugar, carbonated water 8 | Peach Sparkle Soda,"Fizz up your life with Peach Sparkle Soda, where juicy peaches meet bubbly soda in this sweet, sparkling treat.",Juicy peach flavor,Bubbly soda,Sweet and sparkling,Peach juice, carbonated water, sugar 9 | Cool Cucumber Quencher,"Refresh and hydrate with Cool Cucumber Quencher, a light and soothing drink infused with cucumber and a splash of mint.",Cucumber infusion,Light and soothing,Splash of mint,Cucumber, mint, water, sugar 10 | Vanilla Velvet Cream,"Savor the rich, creamy taste of Vanilla Velvet Cream, a luxurious vanilla bean milkshake that's smooth and decadently delicious.",Rich vanilla bean flavor,Creamy and luxurious,Smooth and delicious,Vanilla beans, milk, sugar 11 | Tropical Tangerine Twist,"Twist up your routine with Tropical Tangerine Twist, a vibrant blend of tangerine and tropical fruits that offers a refreshing escape.",Vibrant tangerine flavor,Blend of tropical fruits,Refreshing and vibrant,Tangerine, tropical fruits, water, sugar 12 | Pineapple Paradise Punch,"Escape to paradise with Pineapple Paradise Punch, a sweet and tangy pineapple drink that's perfect for sipping under the sun.",Sweet pineapple flavor,Tangy and refreshing,Perfect for sunny days,Pineapple juice, water, sugar 13 | Raspberry Ripple Refresher,"Enjoy the playful swirl of Raspberry Ripple Refresher, a raspberry lemonade that combines sweet raspberries with a tart lemon twist.",Sweet raspberries,Tart lemon twist,Playful and refreshing,Raspberry, lemon juice, sugar, water 14 | Mint Mojito Mocktail,"Cool down with a Mint Mojito Mocktail, a non-alcoholic version of the classic that's just as refreshing with mint, lime, and sparkling water.",Minty freshness,Non-alcoholic,Just as refreshing,Mint, lime, sugar, carbonated water 15 | Spiced Apple Cider,"Warm up with Spiced Apple Cider, a cozy beverage infused with cinnamon and nutmeg, perfect for chilly evenings.",Warm spiced flavor,Cozy and comforting,Perfect for chilly evenings,Apple cider, cinnamon, nutmeg 16 | Blackberry Bramble Blast,"Blast off with Blackberry Bramble Blast, a blackberry infused energy drink that boosts your energy with natural flavors and caffeine.",Blackberry infusion,Energy boosting,Natural flavors and caffeine,Blackberry juice, caffeine, sugar, water 17 | Kiwi Kale Smoothie,"Blend up a healthy treat with Kiwi Kale Smoothie, a nutrient-packed drink combining kiwi, kale, and a hint of honey for sweetness.",Nutrient-packed,Healthy and delicious,Hint of honey,Kiwi, kale, honey, water 18 | Iced Coconut Coffee,"Chill out with Iced Coconut Coffee, a cool blend of robust coffee and creamy coconut, perfect for coffee lovers looking for a tropical twist.",Robust coffee flavor,Creamy coconut blend,Cool and refreshing,Coffee, coconut milk, ice 19 | Strawberry Sunset Slush,"Watch the sunset with a Strawberry Sunset Slush, a frozen strawberry treat that cools and delights with its icy texture and sweet flavor.",Frozen strawberry treat,Icy texture,Sweet and cooling,Strawberry, ice, sugar 20 | Blueberry Basil Booster,"Boost your day with Blueberry Basil Booster, a unique blend of blueberries and basil that revitalizes and refreshes.",Unique blueberry and basil blend,Revitalizing and refreshing,Flavorful and unique,Blueberries, basil, water, sugar 21 | Pomegranate Power Potion,"Power up with Pomegranate Power Potion, a pomegranate-based energy drink that invigorates with antioxidants and a zesty flavor.",Pomegranate base,Antioxidant-rich,Invigorating and zesty,Pomegranate juice, caffeine, sugar 22 | Caramel Latte Luxe,"Luxuriate in Caramel Latte Luxe, a creamy latte with a rich caramel swirl, combining coffee's boldness with the sweetness of caramel.",Creamy latte,Rich caramel swirl,Luxurious and bold,Coffee, milk, caramel 23 | Saffron Sizzle Soda,"Experience the exotic with Saffron Sizzle Soda, a unique soda infused with saffron and a hint of lemon, offering a vibrant and unusual taste.",Saffron infusion,Unique and vibrant,Hint of lemon,Saffron, lemon, sugar, carbonated water 24 | Green Tea Glimmer,"Glimmer through your day with Green Tea Glimmer, a light and refreshing green tea enhanced with jasmine and a touch of honey.",Light green tea,Jasmine enhanced,Touch of honey,Green tea, jasmine, honey 25 | Watermelon Wave Water,"Ride the wave with Watermelon Wave Water, a hydrating watermelon water that quenches thirst with a hint of mint for extra freshness.",Hydrating watermelon water,Quenches thirst,Hint of mint,Watermelon, mint, water 26 | Grapefruit Grove Glimpse,"Get a glimpse of the grove with Grapefruit Grove Glimpse, a grapefruit juice blend that's tart, refreshing, and perfect for grapefruit lovers.",Tart grapefruit flavor,Refreshing juice blend,Perfect for grapefruit lovers,Grapefruit, water, sugar 27 | Almond Amaretto Affair,"Indulge in an Almond Amaretto Affair, a sweet almond-flavored coffee that combines the rich taste of amaretto for a decadent drink.",Sweet almond flavor,Rich amaretto taste,Decadent and indulgent,Coffee, almond flavor, amaretto 28 | Banana Berry Blast,"Go bananas with Banana Berry Blast, a smoothie that combines bananas and mixed berries for a fruity explosion of flavor.",Bananas and mixed berries,Fruity explosion,Smooth and delicious,Bananas, mixed berries, yogurt 29 | Cinnamon Citrus Cyclone,"Get swept away by Cinnamon Citrus Cyclone, a spiced citrus drink that energizes with its bold flavors and a dash of cinnamon.",Spiced citrus flavor,Energizes with bold flavors,Dash of cinnamon,Citrus juice, cinnamon, sugar, water 30 | Pear Peppermint Punch,"Freshen up with Pear Peppermint Punch, a pear-infused drink with a refreshing peppermint twist, perfect for a hot day.",Pear-infused,Refreshing peppermint twist,Perfect for hot days,Pear juice, peppermint, water 31 | Hibiscus Harmony Tea,"Find your harmony with Hibiscus Harmony Tea, a soothing herbal tea made from hibiscus flowers, offering a tart flavor and a floral aroma.",Soothing herbal tea,Tart flavor,Floral aroma,Hibiscus flowers, water, sugar -------------------------------------------------------------------------------- /pd-embedded-chat/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pd-embedded-chat/static/favicon.png -------------------------------------------------------------------------------- /pd-embedded-chat/ui.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "writer_version": "0.6.2rc3" 4 | }, 5 | "components": { 6 | "root": { 7 | "id": "root", 8 | "type": "root", 9 | "content": { 10 | "appName": "Product chat" 11 | }, 12 | "isCodeManaged": false, 13 | "position": 0, 14 | "handlers": {}, 15 | "visible": true 16 | }, 17 | "c0f99a9e-5004-4e75-a6c6-36f17490b134": { 18 | "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 19 | "type": "page", 20 | "content": { 21 | "pageMode": "compact" 22 | }, 23 | "isCodeManaged": false, 24 | "position": 0, 25 | "parentId": "root", 26 | "handlers": {}, 27 | "visible": true 28 | }, 29 | "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": { 30 | "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", 31 | "type": "header", 32 | "content": { 33 | "text": "@{my_app.title}" 34 | }, 35 | "isCodeManaged": false, 36 | "position": 0, 37 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 38 | "handlers": {}, 39 | "visible": true 40 | }, 41 | "ejpasds0o8qyjs1n": { 42 | "id": "ejpasds0o8qyjs1n", 43 | "type": "section", 44 | "content": { 45 | "title": "" 46 | }, 47 | "isCodeManaged": false, 48 | "position": 1, 49 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 50 | "handlers": {}, 51 | "visible": true 52 | }, 53 | "8j5hpsp5vf1qas41": { 54 | "id": "8j5hpsp5vf1qas41", 55 | "type": "iframe", 56 | "content": { 57 | "src": "" 58 | }, 59 | "isCodeManaged": false, 60 | "position": 3, 61 | "parentId": "ejpasds0o8qyjs1n", 62 | "handlers": {}, 63 | "visible": true 64 | }, 65 | "jaxy6w53bb8k58v6": { 66 | "id": "jaxy6w53bb8k58v6", 67 | "type": "fileinput", 68 | "content": { 69 | "label": "Please add your product description files.", 70 | "allowMultipleFiles": "yes" 71 | }, 72 | "isCodeManaged": false, 73 | "position": 1, 74 | "parentId": "ejpasds0o8qyjs1n", 75 | "handlers": { 76 | "wf-file-change": "handle_file_upload" 77 | }, 78 | "visible": true 79 | }, 80 | "lllmwrrajtpi5yr9": { 81 | "id": "lllmwrrajtpi5yr9", 82 | "type": "button", 83 | "content": { 84 | "text": "Upload and add to Knowledge Graph", 85 | "icon": "upload" 86 | }, 87 | "isCodeManaged": false, 88 | "position": 2, 89 | "parentId": "ejpasds0o8qyjs1n", 90 | "handlers": { 91 | "wf-click": "handle_add_to_graph" 92 | }, 93 | "visible": true 94 | }, 95 | "xqgibumseiw6k736": { 96 | "id": "xqgibumseiw6k736", 97 | "type": "message", 98 | "content": { 99 | "message": "@{message}" 100 | }, 101 | "isCodeManaged": false, 102 | "position": 0, 103 | "parentId": "ejpasds0o8qyjs1n", 104 | "handlers": {}, 105 | "visible": "message" 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /pdp-generator/README.md: -------------------------------------------------------------------------------- 1 | # Product description page generator and translator 2 | This Writer Framework application lets you upload product description data to generate formatted or translated product description page copy. 3 | 4 | ## Usage 5 | 6 | 1. Upload the data sample from the data folder of the app. Alternatively, you can upload your own data once the column names are the same as those in the sample file. 7 | 2. The application will iterate through the data set, rewrite the long description, and combine and format the features and specification fields. 8 | 3. The output is provided in downloadable HTML and Excel spreadsheets. 9 | 4. There are three languages currently configured for translation: Spanish, French and Hindi. Once the translation button is clicked the app will translate the Excel spreadsheet to all three. 10 | 11 | ## Running the application 12 | First, install Writer Frameowrk: 13 | 14 | ```sh 15 | pip install writer 16 | ``` 17 | 18 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 19 | 20 | ```sh 21 | export WRITER_API_KEY=your-api-key 22 | ``` 23 | 24 | Then, navigate to this folder and run: 25 | 26 | ```sh 27 | writer edit . 28 | ``` 29 | 30 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 31 | 32 | ## About Writer 33 | 34 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). 35 | -------------------------------------------------------------------------------- /pdp-generator/data/output-html.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Apex phoneix 39

5 |
6 |

Description: Lace up and conquer your runs with Apex Phoenix 39. Lightweight, durable, and oh-so-comfy, these shoes are your daily training essentials.

7 |
8 |

Product #: DA8725-001

9 |
10 |

Features

11 |

    12 |
  • Lightweight
  • 13 |
  • Durable rubber outsole
  • 14 |
  • Cushioned midsole
  • 15 |
  • Breathable mesh
  • 16 |

17 |
18 |

Specifications

19 |

    20 |
  • Weight : 255g
  • 21 |
  • Heel Drop : 10mm
  • 22 |

23 |
24 |
25 | 26 | 27 |
28 |
29 |

Terra support 22

30 |
31 |

Description: Experience plush, responsive runs with Terra Support 22. Its knit upper and boosting midsole provide long-distance comfort.

32 |
33 |

Product #: GZ0128

34 |
35 |

Features

36 |

    37 |
  • Comfortable
  • 38 |
  • Rubber outsole
  • 39 |
  • Boost midsole
  • 40 |
  • Primeknit upper
  • 41 |

42 |
43 |

Specifications

44 |

    45 |
  • Weight : 310g
  • 46 |
  • Heel Drop : 10mm
  • 47 |

48 |
49 |
50 | 51 | 52 |
53 |
54 |

Fly spirit 28

55 |
56 |

Description: Fly Spirit 28: Stability and comfort for overpronators.

57 |
58 |

Product #: 1011B189.001

59 |
60 |

Features

61 |

    62 |
  • Lightweight
  • 63 |
  • Plus outsole
  • 64 |
  • Engineered mesh
  • 65 |

66 |
67 |

Specifications

68 |

    69 |
  • Weight : 298g
  • 70 |
  • Heel Drop : 11mm
  • 71 |

72 |
73 |
74 | 75 | 76 |
77 |
78 |

Instinct Ghost 14

79 |
80 |

Description: Run naturally with Instinct Ghost 14. Smooth transitions and plush cushioning make every stride effortless.

81 |
82 |

Product #: M860A11

83 |
84 |

Features

85 |

    86 |
  • Eco-friendly
  • 87 |
  • DNA cushioning
  • 88 |
  • 3D Fit Print
  • 89 |

90 |
91 |

Specifications

92 |

    93 |
  • Weight : 300g
  • 94 |
  • Heel Drop : 12mm
  • 95 |

96 |
97 |
98 | 99 | 100 |
101 |
102 |

Rush One 8

103 |
104 |

Description: Rush One 8: Lightweight, responsive running shoes for a smooth ride.

105 |
106 |

Product #: S20650-10

107 |
108 |

Features

109 |

    110 |
  • Responsive foam midsole
  • 111 |
  • Breathable mesh upper
  • 112 |

113 |
114 |

Specifications

115 |

    116 |
  • Weight : 234g
  • 117 |
  • Heel Drop : 13mm
  • 118 |

119 |
120 |
121 | 122 | 123 |
124 |
125 |

Balance 860

126 |
127 |

Description: Glide through your runs with the Balance 860. Experience reliable support and cushioning, mile after mile.

128 |
129 |

Product #: GZ0311

130 |
131 |

Features

132 |

    133 |
  • Blown rubber outsole
  • 134 |
  • Engineered mesh upper
  • 135 |

136 |
137 |

Specifications

138 |

    139 |
  • Weight : 319g
  • 140 |
  • Heel Drop : 14mm
  • 141 |

142 |
143 |
144 | 145 | 146 |
147 |
148 |

Ride 14

149 |
150 |

Description: Ride the miles in comfort and responsiveness with Ride 14.

151 |
152 |

Product #: GR54T

153 |
154 |

Features

155 |

    156 |
  • Cushioned
  • 157 |
  • Cushioning
  • 158 |
  • FormFit upper
  • 159 |

160 |
161 |

Specifications

162 |

    163 |
  • Weight : 240g
  • 164 |
  • Heel Drop : 15mm
  • 165 |

166 |
167 |
168 | 169 | 170 |
171 |
172 |

Terra support 19

173 |
174 |

Description: Experience premium comfort and energy return with Terra Support 19.

175 |
176 |

Product #: FRT123

177 |
178 |

Features

179 |

    180 |
  • Durable
  • 181 |
  • Boosting midsole
  • 182 |
  • Primeknit upper
  • 183 |

184 |
185 |

Specifications

186 |

    187 |
  • Weight : 190g
  • 188 |
  • Heel Drop : 16mm
  • 189 |

190 |
191 |
192 | 193 | 194 |
195 |
196 |

Apex Free RN 5.0

197 |
198 |

Description: Feel the freedom of barefoot running with Apex Free RN 5.0. Its flexible design lets you move naturally with every stride.

199 |
200 |

Product #: 12HT5455TT

201 |
202 |

Features

203 |

    204 |
  • Flexible
  • 205 |
  • Lightweight midsole
  • 206 |
  • Apex Free RN 5.0
  • 207 |
  • Knit upper
  • 208 |

209 |
210 |

Specifications

211 |

    212 |
  • Weight : 245g
  • 213 |
  • Heel Drop : 17mm
  • 214 |

215 |
216 |
217 | 218 |
-------------------------------------------------------------------------------- /pdp-generator/data/running_shoes.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pdp-generator/data/running_shoes.xlsx -------------------------------------------------------------------------------- /pdp-generator/html_template.py: -------------------------------------------------------------------------------- 1 | def _format_output(name, desc, product_num, features, specifications): 2 | html = f""" 3 |
4 |
5 |

{name}

6 |
7 |

Description: {desc}

8 |
9 |

Product #: {product_num}

10 |
11 |

Features

12 |

{features}

13 |
14 |

Specifications

15 |

{specifications}

16 |
17 |
18 | 19 | """ 20 | return html 21 | -------------------------------------------------------------------------------- /pdp-generator/prompts.py: -------------------------------------------------------------------------------- 1 | def _get_features_prompt(features): 2 | prompt = f""" 3 | You are a Product description page writer and formatter. Take in the following inputs which describe the product's 4 | features and output a HTML bulleted list of the features. 5 | 6 | Instructions: 7 | 8 | - Do not output any Headings for anything else other than the bulleted list of features. 9 | - Do not make up anything. Just reformat the contents of the input into the feature list. 10 | - The Output MUST be formatted as a bulleted list. You will be penalized if you do not include bullets. 11 | - If any of the features are "nan" ignore those lines and do not include them in the output. 12 | 13 | Input: {features} 14 | 15 | Look at the example below to understand the required format of the output 16 | 17 | Example Output : 18 | 19 | - Simplifies the running experience with lightweight, easy-to-use design. 20 | - Enhances performance with features optimized for better energy return and cushioning. 21 | - Adapts to a wide variety of running conditions, providing versatility and comfort on different terrains. 22 | 23 | Output:  24 | 25 | Output: 26 | """.format( 27 | features=features 28 | ) 29 | return prompt 30 | 31 | 32 | def _get_specifications_prompt(specifications): 33 | prompt = f""" 34 | You are a Product description page writer and formatter. Take in the following inputs which describe the product's 35 | specifications and output a List of the specifications with the Specification title wrapped in HTML bold tags 36 | and the specification value next to it. Do not output any Headings for anything else other than 37 | the list of specifications. Do not make up anything. Just reformat the contents of the input into the specification 38 | list. 39 | 40 | Input: {specifications} 41 | 42 | Look at the example below to understand the required format of the output. Notice that the format is "Label: 43 | specification value". Ensure there is a colon between the label and the value. There must be a new line space 44 | "\n" between each item. 45 | 46 | You can format the output as a bulleted list. 47 | 48 | Example output: 49 | 50 | - Suitable for a variety of conditions: Road running, trail running, treadmill \n 51 | - Shoe Type: Neutral, Stability, Motion Control \n 52 | - Size Range: US 7-14 \n 53 | - Available in multiple widths: Narrow, Regular, Wide, Extra Wide \n 54 | - Ideal for: Long-distance running, daily training, competitive racing 55 | 56 | Output:  57 | """.format( 58 | specifications=specifications 59 | ) 60 | return prompt 61 | 62 | 63 | def _get_description_prompt(desc): 64 | prompt = f""" 65 | You are a Product description page writer and formatter. Take in the following long description for a product and 66 | slightly rewrite it to sound more like the examples below. 67 | 68 | Instructions: 69 | 70 | - Do not output any Headings for anything else other than the modified product description 71 | - Do not make up anything. Make sure none of the facts about the product are changed 72 | - The output length should be less than 150 characters 73 | - Learn the voice and structure from the examples below but do not copy any of the content into your output. 74 | Only reference the voice and structure of the examples. 75 | 76 | Examples: 77 | 78 | 1. Elevate your running game with the StrideMax Pro. Designed for seamless comfort and performance, 79 | these shoes make every run feel effortless. With adaptive cushioning, you’ll feel the difference 80 | from the very first step. 81 | 82 | 2. Take your daily jogs to the next level with SwiftRun 360. Engineered for both beginners and pros, these shoes offer 83 | a perfect blend of support and flexibility. Slip them on, and experience a smooth ride right out of the box. 84 | 85 | 3. Transform your training with the SpeedFlex Elite. Built for endurance and speed, these shoes provide optimal support 86 | in every stride. Easy to break in, they’ll have you hitting your personal bests in no time. 87 | 88 | Input: {desc} 89 | 90 | Output:  91 | 92 | Output: 93 | """.format( 94 | desc=desc 95 | ) 96 | return prompt 97 | 98 | 99 | def _get_translation_prompt(text, language): 100 | prompt = f""" 101 | You are a translation service that translates materials for multiple global markets. 102 | You translate text exactly as is, word for word. You do not paraphrase or rewrite anything 103 | with your translation. 104 | 105 | Translate the source provided to language without altering any of the formatting, including hyperlinks, 106 | lists, italicized text, and section headers. 107 | 108 | 109 | 1. DO NOT skip any sections and DO NOT paraphrase anything. 110 | 2. Make sure to retain the spacing formatting of the output and use the same headers and paragraphs in the output. 111 | If bullets, headers, section titles or specific spacing is used you MUST retain the same formatting in the output 112 | including line spaces and bullet points. Do not retain the Bold formatting. 113 | 3. DO NOT make up any information in the output. You will be heavily penalized for including details in your output 114 | that are not present in the original text. 115 | 4. DO not output instructions, only give the translated output of the source text from the input below. 116 | 5. For short pieces of text such as Names of shoes, do not include any additional text aside from the Name 117 | of the shoe. You will get a $200 tip if you follow this rule. 118 | 119 | 120 | source: {text} 121 | language: {language} 122 | 123 | 124 | Output: 125 | """.format( 126 | text=text, language=language 127 | ) 128 | return prompt 129 | -------------------------------------------------------------------------------- /pdp-generator/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = {version = "0.6.2rc2"} 11 | openpyxl = "^3.1.5" 12 | 13 | 14 | [build-system] 15 | requires = ["poetry-core"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /pdp-generator/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /pdp-generator/static/custom.css: -------------------------------------------------------------------------------- 1 | .category{ 2 | font-size: larger; 3 | padding-bottom: 1em; 4 | padding-top: 1em; 5 | font-weight: bold; 6 | } 7 | 8 | .summary{ 9 | padding-top: 1em; 10 | padding-bottom: 1em; 11 | margin-left: 1em; 12 | } 13 | 14 | .description{ 15 | margin-left: 3em; 16 | color:gray 17 | } 18 | 19 | .styled-table { 20 | width: 70%; 21 | max-width: 100%; 22 | margin: 20px auto; 23 | border-collapse: collapse; 24 | font-family: 'Arial', sans-serif; 25 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); 26 | } 27 | 28 | /* Styling for table headers */ 29 | .styled-table thead th { 30 | background-color: #009879; 31 | color: #ffffff; 32 | padding: 12px 15px; 33 | text-align: left; 34 | } 35 | 36 | /* Styling for table cells */ 37 | .styled-table tbody td { 38 | border: 1px solid #dddddd; 39 | padding: 12px 15px; 40 | color: #333333; 41 | } 42 | 43 | /* Alternate row styling for better readability */ 44 | .styled-table tbody tr:nth-child(even) { 45 | background-color: #f3f3f3; 46 | } 47 | 48 | /* Hover effect for rows */ 49 | .styled-table tbody tr:hover { 50 | background-color: #f1f1f1; 51 | } 52 | 53 | /* Styling for the scrolling container */ 54 | .scroll-container { 55 | overflow-y: auto; 56 | height: 300px; /* Adjust height as needed */ 57 | } 58 | 59 | .headerCell{ 60 | white-space: nowrap !important; 61 | } 62 | -------------------------------------------------------------------------------- /pdp-generator/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pdp-generator/static/favicon.png -------------------------------------------------------------------------------- /pdp-generator/static/writer_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pdp-generator/static/writer_logo.png -------------------------------------------------------------------------------- /portfolio-rebalance/README.md: -------------------------------------------------------------------------------- 1 | # Portfolio rebalancing recommendation app 2 | 3 | This Writer Framework application uses the powerful Palmyra-Fin model to analyze portfolio data and provide recommendations for rebalancing an investment portfolio. You can also download the analysis as a text file and clear results to start over. This app supports PDF or Excel files. 4 | 5 | ## Usage 6 | 7 | 1. Upload the data sample from the `example_data` folder of the app. Alternatively, you can upload your own portfolio data in Excel (.xls, .xlsx) or PDF format. Note that you may need adjust the prompts in `prompts.py` to achieve the best results. 8 | 2. Once the file is uploaded, the application analyzes your portfolio data, highlighting both positive and negative aspects such as stock selection, sector weightings, and more. 9 | 3. Based on this analysis, the app generates recommendations for rebalancing your portfolio to optimize performance. 10 | 11 | ## Running the application 12 | 13 | First, ensure you have Poetry installed. Then, in the project directory, install the dependencies by running: 14 | 15 | ```sh 16 | poetry install 17 | ``` 18 | 19 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new API Key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 20 | 21 | ```sh 22 | export WRITER_API_KEY=your-api-key 23 | ``` 24 | 25 | To make changes or edit the application, navigate to root folder and use the following command: 26 | 27 | 28 | ```sh 29 | writer edit . 30 | ``` 31 | 32 | Depending on how your environment is set up, you may need to run `writer` with `poetry run` like this: 33 | 34 | ```sh 35 | poetry run writer edit . 36 | ``` 37 | 38 | Once you're ready to run the application, execute: 39 | 40 | ```sh 41 | writer run . 42 | ``` 43 | 44 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction). 45 | 46 | ## About Writer 47 | 48 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). 49 | -------------------------------------------------------------------------------- /portfolio-rebalance/example_data/portfolio_data.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/example_data/portfolio_data.pdf -------------------------------------------------------------------------------- /portfolio-rebalance/example_data/portfolio_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/example_data/portfolio_data.xlsx -------------------------------------------------------------------------------- /portfolio-rebalance/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import shutil 4 | 5 | import pandas as pd 6 | import writer as wf 7 | import writer.ai 8 | from dotenv import load_dotenv 9 | 10 | from prompts import (generate_negative_concatenation_prompt, 11 | generate_negative_impacts_prompt, 12 | generate_negative_stock_selection_prompt, 13 | generate_positive_concatenation_prompt, 14 | generate_positive_impacts_prompt, 15 | generate_positive_sector_weighting_prompt, 16 | generate_positive_stock_selection_prompt, 17 | generate_rebalance_recommendation_prompt) 18 | 19 | load_dotenv() 20 | 21 | pd.options.mode.chained_assignment = None 22 | 23 | 24 | def handle_file_on_change(state, payload): 25 | clear_results(state) 26 | _save_file(state, payload[0]) 27 | 28 | file_extension = state["file"]["name"].split(".")[-1].lower() 29 | 30 | match file_extension: 31 | case "xlsx" | "xls": 32 | df = _read_excel(state) 33 | case "pdf": 34 | text_data = _read_pdf(state) 35 | case _: 36 | state["processing-message"] = "Unsupported file type" 37 | return 38 | 39 | state["processing-message"] = "Analyzing positive and negative impacts..." 40 | prompt = analyze_data( 41 | df if file_extension in ["xlsx", "xls"] else text_data 42 | ) 43 | 44 | state["processing-message"] = "Generating rebalancing recommendation..." 45 | state["analysis-result"] = "" 46 | 47 | # Using Palmyra-Fin model 48 | # for chunk in writer.ai.stream_complete(prompt, {"model": "palmyra-fin-32k", "max_tokens": 2048, "temperature": 0.7}): 49 | # state["analysis-result"] += chunk 50 | 51 | # Using Palmyra X 004 model 52 | conversation = writer.ai.Conversation([{"role": "user", "content": prompt}], {"model": "palmyra-x-004", "max_tokens": 2048, "temperature": 0.7}) 53 | for chunk in conversation.stream_complete(): 54 | if chunk.get("content"): 55 | state["analysis-result"] += chunk.get("content") 56 | 57 | state["visual_block_visible"] = True 58 | state["processing-message"] = "" 59 | 60 | 61 | def clear_results(state): 62 | state["analysis-result"] = "Your analysis will appear here." 63 | _delete_all_files(state) 64 | state["visual_block_visible"] = False 65 | 66 | 67 | def _save_file(state, file): 68 | name = file.get("name") 69 | state["file"]["name"] = name 70 | state["file"]["file_path"] = f"data/{name}" 71 | state["processing-message"] = f"File {name} saved." 72 | file_data = file.get("data") 73 | with open(f"data/{name}", "wb") as file_handle: 74 | file_handle.write(file_data) 75 | 76 | 77 | def _delete_all_files(state): 78 | directory = "data" 79 | 80 | if os.path.exists(directory): 81 | shutil.rmtree(directory) 82 | 83 | os.makedirs(directory) 84 | 85 | state["file"]["name"] = "" 86 | state["file"]["file_path"] = "" 87 | state["processing-message"] = "All files have been deleted." 88 | 89 | 90 | def _read_excel(state): 91 | data = pd.read_excel(state["file"]["file_path"]) 92 | df = pd.DataFrame(data) 93 | return df 94 | 95 | 96 | def _read_pdf(state): 97 | from PyPDF2 import PdfReader 98 | 99 | reader = PdfReader(state["file"]["file_path"]) 100 | text = "" 101 | for page in reader.pages: 102 | text += page.extract_text() 103 | return text 104 | 105 | # Create async tasks for each prompt and then generate the final prompt 106 | async def gather_results_and_generate_rebalance_prompt(data: str): 107 | async def complete_async(prompt): 108 | return await asyncio.to_thread(writer.ai.complete, prompt, {"model": "palmyra-fin-32k", "max_tokens": 3048, "temperature": 0.7}) 109 | 110 | positive_stock_selection_task = complete_async( 111 | generate_positive_stock_selection_prompt(data) 112 | ) 113 | 114 | positive_sector_weighting_task = complete_async( 115 | generate_positive_sector_weighting_prompt(data) 116 | ) 117 | 118 | positive_concatenation_task = complete_async( 119 | generate_positive_concatenation_prompt(data) 120 | ) 121 | 122 | positive_impacts_task = complete_async(generate_positive_impacts_prompt(data)) 123 | 124 | negative_stock_selection_task = complete_async( 125 | generate_negative_stock_selection_prompt(data) 126 | ) 127 | 128 | negative_concatenation_task = complete_async( 129 | generate_negative_concatenation_prompt(data) 130 | ) 131 | 132 | negative_impacts_task = complete_async(generate_negative_impacts_prompt(data)) 133 | 134 | ( 135 | positive_stock_selection, 136 | positive_sector_weighting, 137 | positive_concatenation, 138 | positive_impacts, 139 | negative_stock_selection, 140 | negative_concatenation, 141 | negative_impacts, 142 | ) = await asyncio.gather( 143 | positive_stock_selection_task, 144 | positive_sector_weighting_task, 145 | positive_concatenation_task, 146 | positive_impacts_task, 147 | negative_stock_selection_task, 148 | negative_concatenation_task, 149 | negative_impacts_task, 150 | ) 151 | 152 | final_prompt = generate_rebalance_recommendation_prompt( 153 | positive_stock_selection, 154 | positive_sector_weighting, 155 | positive_concatenation, 156 | positive_impacts, 157 | negative_stock_selection, 158 | negative_concatenation, 159 | negative_impacts, 160 | ) 161 | 162 | return final_prompt 163 | 164 | 165 | def analyze_data(data): 166 | if isinstance(data, pd.DataFrame): 167 | data_str = data.to_string() 168 | else: 169 | data_str = data 170 | 171 | return asyncio.run( 172 | gather_results_and_generate_rebalance_prompt(data_str) 173 | ) 174 | 175 | 176 | def handle_file_download(state): 177 | analysis_result = state["analysis-result"] 178 | 179 | if analysis_result: 180 | with open("data/analysis_result.txt", "w") as file_handle: 181 | file_handle.write(analysis_result) 182 | 183 | file_data = wf.pack_file("data/analysis_result.txt", "text/plain") 184 | state.file_download(file_data, "analysis_result.txt") 185 | 186 | 187 | initial_state = wf.init_state( 188 | { 189 | "image-path": "static/writer_logo.png", 190 | "app": {"title": "Recommendations for Rebalancing Portfolio"}, 191 | "file": {"name": "", "file_path": ""}, 192 | "analysis-result": "Your recommendations will appear here.", 193 | "processing-message": "", 194 | "visual_block_visible": False 195 | } 196 | ) 197 | 198 | 199 | initial_state.import_stylesheet("style", "/static/custom.css?") 200 | -------------------------------------------------------------------------------- /portfolio-rebalance/prompts.py: -------------------------------------------------------------------------------- 1 | def generate_positive_stock_selection_prompt(past_quarter_results): 2 | prompt = f""" 3 | For the ClearBridge Large Cap Growth Fund, when compared to the benchmark of Russell 1000 Growth, which stock sectors had the highest Selection + Interaction? Do not provide more than two sectors. 4 | 5 | Please only list the names of the sectors. Do not list more than two and do not put them in an ordered list. 6 | 7 | Use this document to help answer your question: {past_quarter_results} 8 | """ 9 | return prompt 10 | 11 | 12 | def generate_positive_sector_weighting_prompt(past_quarter_results): 13 | prompt = f""" 14 | For the ClearBridge Large Cap Growth Fund, which two sectors had the highest Total Effect? 15 | 16 | Please only list the names of the sectors. Do not provide more than two. 17 | 18 | Use this document to help answer your question: {past_quarter_results} 19 | """ 20 | return prompt 21 | 22 | 23 | def generate_positive_concatenation_prompt(past_quarter_results): 24 | prompt = f""" 25 | Stock sectors: {past_quarter_results} 26 | 27 | For the two stock sectors mentioned above, format a response as: "stock selection in the X sector and Y sector contributed to the performance", where X is the first sector mentioned above and Y is the second sector mentioned above. Do not capitalize the first word of the sentence. 28 | """ 29 | return prompt 30 | 31 | 32 | def generate_negative_stock_selection_prompt(past_quarter_results): 33 | prompt = f""" 34 | For the ClearBridge Large Cap Growth Fund, when compared to the benchmark of Russell 1000 Growth, which stock sectors had the lowest Selection + Interaction? Do not provide more than two sectors. 35 | 36 | Please only list the names of the sectors. Do not list more than two. 37 | 38 | Use this document to help answer your question: {past_quarter_results} 39 | """ 40 | return prompt 41 | 42 | 43 | def generate_negative_concatenation_prompt(negative_stock_selection): 44 | prompt = f""" 45 | Stock sectors: {negative_stock_selection} 46 | 47 | For the two stock sectors mentioned above, format a response as: "stock selection in the X sector and Y sector detracted from performance", where X is the first sector mentioned above and Y is the second sector mentioned above. Do not capitalize the first word of the sentence. 48 | """ 49 | return prompt 50 | 51 | 52 | def generate_positive_impacts_prompt(past_quarter_results): 53 | prompt = f""" 54 | Give an answer to the question: What individual stocks contributed the most to positive returns? Your answer should be in the format: "In terms of individual stocks, the greatest contributors to returns included the Portfolios' positions in [Company A], [Company B], [Company C], [Company D], and [Company E]." 55 | 56 | Here's the data sheet that can be used to answer that question: {past_quarter_results} 57 | 58 | Three things to keep in mind: 59 | 60 | 1. The data sheet has the full names of companies, but you only need to report on its short name. For example, instead of reporting as London Stock Exchange Group plc, you should just say London Stock Exchange Group. 61 | 2. You should only list a maximum of five individual stocks. 62 | 3. The examples above should strictly be used for format advice. Only use stocks mentioned in the data sheet in your response. 63 | 64 | Please provide your answer in this format. 65 | """ 66 | return prompt 67 | 68 | 69 | def generate_negative_impacts_prompt(past_quarter_results): 70 | prompt = f""" 71 | Give an answer to the question: What individual stocks detracted the most from returns? Your answer should be in the format: "In terms of individual stocks, the greatest detractors from returns included the Portfolios' positions in [Company A], [Company B], [Company C], [Company D], and [Company E]." 72 | 73 | Here's the data sheet that can be used to answer that question: {past_quarter_results} 74 | 75 | Three things to keep in mind: 76 | 77 | 1. The data sheet has the full names of companies, but you only need to report its short name. For example, instead of reporting as London Stock Exchange Group plc, you should just say London Stock Exchange Group. 78 | 2. You should only list a maximum of five individual stocks. 79 | 3. The examples above should strictly be used for format advice. Only use stocks mentioned in the data sheet in your response. 80 | 81 | Please provide your answer in this format. 82 | """ 83 | return prompt 84 | 85 | 86 | def generate_rebalance_recommendation_prompt( 87 | positive_stock_selection, 88 | positive_sector_weighting, 89 | positive_concatenation, 90 | positive_impacts, 91 | negative_stock_selection, 92 | negative_concatenation, 93 | negative_impacts, 94 | ): 95 | 96 | prompt = f""" 97 | Based on the past results from last quarter, please write a step-by-step analysis of how to rebalance the portfolio. 98 | 99 | Here are the results: 100 | 101 | Positive Stock Selection: 102 | For the last quarter relative to the benchmark, the highest positive stock selection was in the following sectors: {positive_stock_selection}. 103 | 104 | Positive Sector Weighting: 105 | The sectors with the highest total effect were: {positive_sector_weighting}. 106 | 107 | Positive Impacts: 108 | Stock selection in {positive_concatenation} contributed to the performance. 109 | In terms of individual stocks, the greatest contributors to returns included: {positive_impacts}. 110 | 111 | Negative Stock Selection: 112 | The sectors with the lowest stock selection and interaction were: {negative_stock_selection}. 113 | 114 | Negative Impacts: 115 | Stock selection in {negative_concatenation} detracted from the performance. 116 | In terms of individual stocks, the greatest detractors from returns included: {negative_impacts}. 117 | 118 | Based on this analysis, please provide your positive impacts, negative impacts and recommendations for rebalancing the portfolio. In your recommendation, be sure to name specific stocks as well as industries.Do not include any extraneous information around general portfolio rebalancing process or additional general resources. 119 | """ 120 | 121 | return prompt 122 | -------------------------------------------------------------------------------- /portfolio-rebalance/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | writer = "0.7.5" 11 | openpyxl = "3.1.5" 12 | pandas = "2.2.3" 13 | pypdf2 = "3.0.1" 14 | python-dotenv = "1.0.1" 15 | python-docx = "1.1.2" 16 | asyncio = "^3.4.3" 17 | 18 | 19 | [tool.poetry.group.dev.dependencies] 20 | flake8 = "^7.1.1" 21 | black = "^24.8.0" 22 | isort = "^5.13.2" 23 | 24 | [build-system] 25 | requires = ["poetry-core"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /portfolio-rebalance/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /portfolio-rebalance/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/static/favicon.png -------------------------------------------------------------------------------- /portfolio-rebalance/static/writer_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/static/writer_logo.png -------------------------------------------------------------------------------- /prescribing-info-app/.env.example: -------------------------------------------------------------------------------- 1 | WRITER_API_KEY=your_writer_api_key 2 | GRAPH_ID=your_writer_graph_id 3 | -------------------------------------------------------------------------------- /prescribing-info-app/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"key": "drug-info", "pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} 2 | {"id": "f0jnnqbgaq9egozo", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0} 3 | {"id": "5l0szzvnaaxlt8tl", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "f0jnnqbgaq9egozo", "position": 0} 4 | {"id": "se73vd8nc8w057x4", "type": "header", "content": {"text": "Prescribing Information"}, "handlers": {}, "isCodeManaged": false, "parentId": "5l0szzvnaaxlt8tl", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} 5 | {"id": "zh10210i1bawmqp3", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "f0jnnqbgaq9egozo", "position": 1} 6 | {"id": "820nw2mf596ezam3", "type": "horizontalstack", "content": {"contentHAlign": "right"}, "isCodeManaged": false, "parentId": "zh10210i1bawmqp3", "position": 0} 7 | {"id": "medicine", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Drug info \ud83d\udc8a"}, "handlers": {"wf-click": "$goToPage_drug-info"}, "isCodeManaged": false, "parentId": "820nw2mf596ezam3", "position": 0} 8 | {"id": "chat", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Chat \ud83d\udcac"}, "handlers": {"wf-click": "$goToPage_chat"}, "isCodeManaged": false, "parentId": "820nw2mf596ezam3", "position": 1} 9 | {"id": "c5f7bp0fjv7ew40w", "type": "section", "content": {"title": "Availiable drugs"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1} 10 | {"id": "bdts4z8blerl33fw", "type": "tags", "content": {"tags": "{\n \"Xeljanz\": \"Xeljanz\",\n \"Stelara\": \"Stelara\",\n \"Dupixent\": \"Dupixent\",\n \"Lucentis\": \"Lucentis\",\n \"Bavencio\": \"Bavencio\",\n \"Trulicity\": \"Trulicity\",\n \"Entresto\": \"Entresto\",\n \"Humira\": \"Humira\",\n \"Tagrisso\": \"Tagrisso\"\n}"}, "handlers": {"wf-tag-click": "drug_switch"}, "isCodeManaged": false, "parentId": "c5f7bp0fjv7ew40w", "position": 0} 11 | {"id": "mhhffykuw46c3xgi", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 2} 12 | {"id": "0mpz722m992c8h06", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 0} 13 | {"id": "fm9r15fyiysf5fhl", "type": "section", "content": {"title": "Adverse reactions"}, "handlers": {}, "isCodeManaged": false, "parentId": "0mpz722m992c8h06", "position": 0} 14 | {"id": "fdvy87wyzk0pyg27", "type": "text", "content": {"cssClasses": "adv-reactions-scroller", "text": "@{adverse_reactions}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "fm9r15fyiysf5fhl", "position": 0} 15 | {"id": "693ecrbqbc942f6x", "type": "column", "content": {"width": "3"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 1} 16 | {"id": "xpjwnx9jurnxuy4z", "type": "tabs", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "693ecrbqbc942f6x", "position": 0} 17 | {"id": "y1x1a8xb2cdwfg77", "type": "tab", "content": {"cssClasses": "", "name": "Full prescribing info"}, "handlers": {}, "isCodeManaged": false, "parentId": "xpjwnx9jurnxuy4z", "position": 0} 18 | {"id": "4eg5lxq1vle8m9h4", "type": "text", "content": {"cssClasses": "prescr-info-scroller", "text": "@{raw_prescribing_info}\n", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "y1x1a8xb2cdwfg77", "position": 0} 19 | {"id": "3pb0e6kmi4soloz8", "type": "tab", "content": {"cssClasses": "", "name": "Summary of prescribing info"}, "handlers": {}, "isCodeManaged": false, "parentId": "xpjwnx9jurnxuy4z", "position": 1} 20 | {"id": "jz845bowxcjm606y", "type": "text", "content": {"cssClasses": "prescr-info-scroller", "text": "@{prescribing_info_summary}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "3pb0e6kmi4soloz8", "position": 0} 21 | {"id": "ob300350xax1esrg", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 2} 22 | {"id": "b0sg45pzixq3ra3w", "type": "section", "content": {"cssClasses": "pubmed-scroller", "title": "Related PubMed articles"}, "handlers": {}, "isCodeManaged": false, "parentId": "ob300350xax1esrg", "position": 0} 23 | {"id": "hnmadv68pz0jeak8", "type": "repeater", "content": {"keyVariable": "itemId", "repeaterObject": "@{related_pubmed_articles}", "valueVariable": "item"}, "handlers": {}, "isCodeManaged": false, "parentId": "b0sg45pzixq3ra3w", "position": 0} 24 | {"id": "vowbh61n9cbzv8ko", "type": "tags", "content": {"seed": "0", "tags": "{\n \"1\": \"@{item.Publication Date}\"\n}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 0} 25 | {"id": "i4sm8m3k228y7faw", "type": "text", "content": {"text": "@{item.Title}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 1} 26 | {"id": "drihhplzfxomn2ga", "type": "link", "content": {"primaryTextColor": "#5551ff", "target": "", "text": "PubMed", "url": "@{item.Article Link}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 2} 27 | -------------------------------------------------------------------------------- /prescribing-info-app/.wf/components-page-1-181cq1mpgw34i8dl.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "181cq1mpgw34i8dl", "type": "page", "content": {"key": "chat", "pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 1, "visible": {"binding": "", "expression": true, "reversed": false}} 2 | {"id": "b6cillcjhtpg71tk", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "181cq1mpgw34i8dl", "position": 0} 3 | {"id": "5dxznhfw18bkp88o", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "b6cillcjhtpg71tk", "position": 0} 4 | {"id": "66m0u100zicdr0j3", "type": "header", "content": {"text": "Chat with Prescribing Information"}, "handlers": {}, "isCodeManaged": false, "parentId": "5dxznhfw18bkp88o", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} 5 | {"id": "ptn01trkgrzmc11v", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "b6cillcjhtpg71tk", "position": 1} 6 | {"id": "y1x6gru1icypiguh", "type": "horizontalstack", "content": {"contentHAlign": "right"}, "isCodeManaged": false, "parentId": "ptn01trkgrzmc11v", "position": 0} 7 | {"id": "medicine1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Drug info \ud83d\udc8a"}, "handlers": {"wf-click": "$goToPage_drug-info"}, "isCodeManaged": false, "parentId": "y1x6gru1icypiguh", "position": 0} 8 | {"id": "chat1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Chat \ud83d\udcac"}, "handlers": {"wf-click": "$goToPage_chat"}, "isCodeManaged": false, "parentId": "y1x6gru1icypiguh", "position": 1} 9 | {"id": "3yv1o3158qk6ewrp", "type": "section", "content": {"title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "181cq1mpgw34i8dl", "position": 1} 10 | {"id": "0na7dihjazzdlbjf", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "3yv1o3158qk6ewrp", "position": 0} 11 | {"id": "yl0x2lefilp1xnl6", "type": "column", "content": {"width": "13"}, "handlers": {}, "isCodeManaged": false, "parentId": "0na7dihjazzdlbjf", "position": 0} 12 | {"id": "t76ns9nd93w1aaac", "type": "chatbot", "content": {"conversation": "@{conversation}", "useMarkdown": "yes"}, "handlers": {"wf-chatbot-message": "user_message"}, "isCodeManaged": false, "parentId": "yl0x2lefilp1xnl6", "position": 0} 13 | {"id": "4dyb1959ehmz6hoh", "type": "text", "content": {"text": "@{contributing_sources_button_text}"}, "handlers": {"wf-click": "contributing_sources_change_vis"}, "isCodeManaged": false, "parentId": "yl0x2lefilp1xnl6", "position": 1} 14 | {"id": "6gunwa2x9r6fw6c4", "type": "column", "content": {"title": "Contributing sources", "width": "7"}, "handlers": {}, "isCodeManaged": false, "parentId": "0na7dihjazzdlbjf", "position": 1, "visible": {"binding": "contributing_sources_vis", "expression": "custom", "reversed": false}} 15 | {"id": "rfpjjy0krwyn5cfo", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "6gunwa2x9r6fw6c4", "position": 0} 16 | {"id": "h8gtll4cw37fh3hf", "type": "column", "content": {"contentPadding": "0px 5px 0px 0px", "cssClasses": "files-list", "width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "rfpjjy0krwyn5cfo", "position": 0} 17 | {"id": "gq06l9kpvbeqkee1", "type": "repeater", "content": {"keyVariable": "index", "repeaterObject": "@{contributing_sources}", "valueVariable": "file"}, "handlers": {}, "isCodeManaged": false, "parentId": "h8gtll4cw37fh3hf", "position": 0} 18 | {"id": "0ggjzzhqd8vgttkg", "type": "section", "content": {"cssClasses": "@{file.file_css}", "isCollapsible": "", "startCollapsed": "", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "gq06l9kpvbeqkee1", "position": 0} 19 | {"id": "avu7ft9wkylm5mho", "type": "text", "content": {"text": "#### @{file.name}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "0ggjzzhqd8vgttkg", "position": 0} 20 | {"id": "nnirvi5yma8wsswp", "type": "text", "content": {"cssClasses": "@{file.content_css}", "text": "@{file.content}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "0ggjzzhqd8vgttkg", "position": 1} 21 | -------------------------------------------------------------------------------- /prescribing-info-app/.wf/components-root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "root", "type": "root", "content": {"appName": "Prescribing Information App"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /prescribing-info-app/.wf/components-workflows_root.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}} -------------------------------------------------------------------------------- /prescribing-info-app/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl: -------------------------------------------------------------------------------- 1 | {"id": "lfltcky7l1fsm6j2", "type": "workflows_workflow", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "workflows_root", "position": 0} 2 | -------------------------------------------------------------------------------- /prescribing-info-app/.wf/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "writer_version": "0.8.2" 3 | } -------------------------------------------------------------------------------- /prescribing-info-app/README.md: -------------------------------------------------------------------------------- 1 | # Prescribing Information App 2 | 3 | This is a Python application built with [Writer Framework](https://github.com/writer/writer-framework) that provides detailed prescribing information, side effects, and related PubMed articles for medications. The app uses the OpenFDA API and PubMed database to gather comprehensive drug information and present it in an easily digestible format. This application uses [Palmyra Med](https://huggingface.co/Writer/Palmyra-Med-70B) to summarize the prescribing information and our [graph-based RAG tool](https://dev.writer.com/api-guides/kg-chat) powered by Palmyra X 004 to enable users to ask questions about the fetched information. 4 | 5 | To learn more about Writer Framework, check out our [comprehensive docs](https://dev.writer.com/framework/introduction). 6 | 7 | ## Features 8 | 9 | - Fetch and display full prescribing information from OpenFDA API 10 | - Show drug side effects and adverse reactions 11 | - Search and display related PubMed articles 12 | - Automatic summarization of prescribing information using Palmyra Med 13 | - Interactive chat interface for drug-related queries using our graph-based RAG tool 14 | - Display of contributing sources for information 15 | 16 | ## Prerequisites 17 | 18 | - Python 3.11 or higher 19 | - Poetry for dependency management 20 | - Writer Framework API Key (follow the [Writer Framework Quickstart guide](https://dev.writer.com/framework/quickstart) to get one) 21 | - Knowledge Graph ID (you can create a Knowledge Graph [through AI Studio](https://support.writer.com/article/242-how-to-create-and-manage-a-knowledge-graph) or [through the API](https://dev.writer.com/api-guides/knowledge-graph)) 22 | 23 | ## Installation 24 | 25 | 1. Clone the repository and navigate to the project directory: 26 | 27 | ```sh 28 | cd prescribing-info-app 29 | ``` 30 | 31 | 2. Install dependencies using Poetry: 32 | 33 | ```sh 34 | poetry install 35 | ``` 36 | 37 | 3. Create a `.env` file in the project root and add your credentials: 38 | 39 | ```sh 40 | WRITER_API_KEY=your-api-key 41 | GRAPH_ID=your-graph-id 42 | ``` 43 | 44 | ## Usage 45 | 46 | 1. Activate the Poetry virtual environment: 47 | 48 | ```sh 49 | poetry shell 50 | ``` 51 | 52 | 2. Run the application: 53 | 54 | ```sh 55 | writer run . 56 | ``` 57 | 58 | 3. To make changes or edit the application: 59 | 60 | ```sh 61 | writer edit . 62 | ``` 63 | 64 | ## Project Structure 65 | 66 | - `main.py` - Core application logic and Writer Framework integration 67 | - `utils.py` - Utility functions for API interactions and data processing 68 | - `prompts.py` - Prompt templates 69 | - `static/` - Static files and CSS 70 | - `.env` - Environment variables (not tracked in git) 71 | 72 | ## About Writer 73 | 74 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=devrel). -------------------------------------------------------------------------------- /prescribing-info-app/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import os 4 | from datetime import datetime 5 | from typing import Optional 6 | 7 | import writer as wf 8 | from dotenv import load_dotenv 9 | from prompts import prescribing_summary_prompt 10 | from utils import ( 11 | _get_drug_side_effects, 12 | _get_full_prescribing_info, 13 | _get_pubmed_sheets, 14 | _parse_prescribing_info, 15 | _upload_file_and_add_to_graph, 16 | ) 17 | from writer import WriterState 18 | from writer.ai import Conversation, File, retrieve_file, retrieve_graph, stream_complete 19 | 20 | load_dotenv() 21 | 22 | GRAPH_ID = os.getenv("GRAPH_ID", "") 23 | 24 | 25 | def drug_switch(state: WriterState, payload: str) -> None: 26 | update_drug_data(state, payload) 27 | 28 | 29 | def update_drug_data(state: WriterState, drug_name: str) -> None: 30 | prescribing_info, adverse_reactions = asyncio.run( 31 | _get_drug_prescribing_info(drug_name) 32 | ) 33 | state["raw_prescribing_info"] = _parse_prescribing_info(prescribing_info[0]) 34 | state["adverse_reactions"] = adverse_reactions 35 | state["related_pubmed_articles"] = _get_pubmed_sheets(drug_name) 36 | 37 | state["prescribing_info_summary"] = "" 38 | 39 | for chunk in stream_complete( 40 | initial_text=prescribing_summary_prompt.format( 41 | prescribing_details=state["raw_prescribing_info"] 42 | ), 43 | config={"model": "palmyra-med"} 44 | ): 45 | state["prescribing_info_summary"] += chunk 46 | 47 | asyncio.run(_upload_drug_data_into_graph(state, drug_name)) 48 | 49 | 50 | async def _get_drug_prescribing_info(drug_name: str) -> (dict, str): 51 | return await asyncio.gather( 52 | _get_full_prescribing_info(drug_name), _get_drug_side_effects(drug_name) 53 | ) 54 | 55 | 56 | async def _upload_drug_data_into_graph(state: WriterState, drug_name: str) -> None: 57 | input_params = [ 58 | { 59 | "file_data": state["prescribing_info_summary"], 60 | "file_name": f"prescribing_info_summary_{drug_name}_{str(datetime.timestamp(datetime.now()))}", 61 | "graph_id": GRAPH_ID, 62 | }, 63 | { 64 | "file_data": state["raw_prescribing_info"], 65 | "file_name": f"raw_prescribing_info_{drug_name}_{str(datetime.timestamp(datetime.now()))}", 66 | "graph_id": GRAPH_ID, 67 | }, 68 | { 69 | "file_data": state["adverse_reactions"], 70 | "file_name": f"adverse_reactions_{drug_name}_{str(datetime.timestamp(datetime.now()))}", 71 | "graph_id": GRAPH_ID, 72 | }, 73 | { 74 | "file_data": json.dumps(state["related_pubmed_articles"], default=str), 75 | "file_name": f"pubmed_articles_{drug_name}_{str(datetime.timestamp(datetime.now()))}", 76 | "graph_id": GRAPH_ID, 77 | }, 78 | ] 79 | coroutines = [_upload_file_and_add_to_graph(**params) for params in input_params] 80 | await asyncio.gather(*coroutines) 81 | 82 | 83 | def _handle_contributing_sources(state: WriterState, graph_data: dict) -> None: 84 | sources = graph_data.get("sources") 85 | contributing_sources = {} 86 | if sources: 87 | for index, source in enumerate(sources): 88 | source_file = _get_file_from_graph(source["file_id"]) 89 | source_snippet = source["snippet"] 90 | 91 | raw_filename = f"📄 {source_file.name}" 92 | row_length = 30 93 | filename = " ".join( 94 | [ 95 | raw_filename[i : i + row_length] 96 | for i in range(0, len(raw_filename), row_length) 97 | ] 98 | ) 99 | 100 | contributing_sources.update( 101 | { 102 | str(index): { 103 | "name": filename, 104 | "file_css": "file", 105 | "content": source_snippet, 106 | "content_css": "file-text", 107 | } 108 | } 109 | ) 110 | 111 | state["contributing_sources"] = contributing_sources 112 | state["contributing_sources_vis"] = True 113 | state["contributing_sources_button_text"] = "View contributing sources ▸" 114 | 115 | 116 | def _get_file_from_graph(file_id: str) -> Optional[File]: 117 | try: 118 | return retrieve_file(file_id=file_id) 119 | except Exception as e: 120 | print(f"An error while file obtainment occurred: {str(e)}") 121 | return None 122 | 123 | 124 | def user_message(state: WriterState, payload: dict) -> None: 125 | try: 126 | state["conversation"] += payload 127 | graph = retrieve_graph(GRAPH_ID) 128 | response = state["conversation"].stream_complete( 129 | tools=graph, 130 | ) 131 | 132 | for chunk in response: 133 | state["conversation"] += chunk 134 | 135 | graph_data = state["conversation"].messages[-1].get("graph_data") 136 | if graph_data: 137 | _handle_contributing_sources(state, graph_data) 138 | 139 | except Exception as e: 140 | state["conversation"] += { 141 | "role": "assistant", 142 | "content": "Something went wrong. Please try again!", 143 | } 144 | raise e 145 | 146 | 147 | def contributing_sources_change_vis(state: WriterState) -> None: 148 | state["contributing_sources_vis"] = not state["contributing_sources_vis"] 149 | if state["contributing_sources_vis"]: 150 | state["contributing_sources_button_text"] = "View contributing sources ▸" 151 | else: 152 | state["contributing_sources_button_text"] = "View contributing sources ◂" 153 | 154 | 155 | initial_state = wf.init_state( 156 | { 157 | "adverse_reactions": "Drug adverse reactions will appear here...", 158 | "prescribing_info_summary": "Prescribing info summary will appear here...", 159 | "raw_prescribing_info": "Prescribing info will appear here...", 160 | "related_pubmed_articles": {}, 161 | "contributing_sources_button_text": "View contributing sources ◂", 162 | "contributing_sources_vis": False, 163 | "contributing_sources": {}, 164 | "conversation": Conversation( 165 | [ 166 | { 167 | "role": "assistant", 168 | "content": "Hi! I can answer your questions " 169 | "about the selected medication " 170 | "based on its prescribing information " 171 | "and related Pubmed articles.", 172 | }, 173 | ], 174 | ), 175 | } 176 | ) 177 | 178 | initial_state.import_stylesheet("style", "/static/custom.css") 179 | -------------------------------------------------------------------------------- /prescribing-info-app/prompts.py: -------------------------------------------------------------------------------- 1 | prescribing_summary_prompt = """ 2 | 3 | Variables: 4 | 5 | {prescribing_details} 6 | 7 | ************************ 8 | 9 | Prompt: 10 | Here is the prescribing details text to summarize: 11 | 12 | 13 | {prescribing_details} 14 | 15 | 16 | Please summarize the prescribing details text. 17 | 18 | """ 19 | -------------------------------------------------------------------------------- /prescribing-info-app/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | writer = "^0.8.2" 11 | bio = "^1.7.1" 12 | bs4 = "^0.0.2" 13 | python-dotenv = "^1.0.1" 14 | flake8 = "^7.1.1" 15 | isort = "^5.13.2" 16 | black = "^24.10.0" 17 | pre-commit = "^4.0.1" 18 | asyncio = "^3.4.3" 19 | aiohttp = "^3.11.11" 20 | 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /prescribing-info-app/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /prescribing-info-app/static/custom.css: -------------------------------------------------------------------------------- 1 | .adv-reactions-scroller{ 2 | height: 60.47vh !important; 3 | overflow-y: auto !important; 4 | } 5 | 6 | .prescr-info-scroller{ 7 | height: 60vh !important; 8 | overflow-y: auto !important; 9 | } 10 | 11 | .pubmed-scroller{ 12 | height: 69.96vh !important; 13 | overflow-y: auto !important; 14 | } 15 | 16 | .file{ 17 | overflow-x: auto !important; 18 | } 19 | 20 | .file-text{ 21 | height: 40vh !important; 22 | overflow-y: auto !important; 23 | } 24 | 25 | .files-list{ 26 | height: 79vh !important; 27 | overflow-y: auto !important; 28 | } 29 | -------------------------------------------------------------------------------- /prescribing-info-app/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/prescribing-info-app/static/favicon.png -------------------------------------------------------------------------------- /release-notes-generator/README.md: -------------------------------------------------------------------------------- 1 | # Release notes generator 2 | This application allows the user to upload a CSV of ticket data and labels and create nicely formatted release notes. 3 | 4 | Note: the prompts in `prompts.py` are tightly coupled to the formatting of the sample data. If you want to use a different CSV file with a different format, be sure to update the prompts accordingy. 5 | 6 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`: 7 | 8 | ```sh 9 | export WRITER_API_KEY=your-api-key 10 | ``` 11 | 12 | Read the [full tutorial](https://dev.writer.com/framework/release-description-generator) to learn how to build this application. 13 | 14 | ## About Writer 15 | 16 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework). -------------------------------------------------------------------------------- /release-notes-generator/end/README.md: -------------------------------------------------------------------------------- 1 | This app was created using Writer Framework. 2 | 3 | To learn more about it, visit https://dev.writer.com/framework 4 | -------------------------------------------------------------------------------- /release-notes-generator/end/data/output-html.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | New Feature 4 | 5 |
6 | New JS Challenge action for L7 DDoS auto mitigation 7 |

    8 |
  • L7 DDoS now supports JavaScript Challenge as a new mitigation option in addition to blocking.
  • 9 |
  • This provides customers with more flexibility in choosing how to mitigate volumetric DDoS attacks.
  • 10 |
  • The JavaScript Challenge action can be configured in the EnableDDoSDetectionSetting configuration.
  • 11 |
  • Once enabled, Akar will never create FastAcl even if blocking by IP, and will instead create an L7Acl rule with the JavaScript Challenge action.
  • 12 |

13 |
14 | 15 | 16 |
17 | Detailed Events in Synthetic Monitoring Service 18 |

    19 |
  • Synthetic monitoring customers can now view the verbose output/response of their synthetic monitor checks in the `Events` table. This will help them understand what issues and outages their end users are facing when using their applications.
  • 20 |

21 |
22 | 23 |
24 | 25 |
26 | Caveat 27 | 28 |
29 | Security event structure review and documentation 30 |

    31 |
  • Security event structure is being reviewed, formalized, and documented.
  • 32 |
  • All log fields will be documented, including Access Log, WAF Security Event, Service Policy, and Bot Defense Security Event.
  • 33 |
  • Irrelevant fields will be deleted or hidden from customers.
  • 34 |
  • A document describing all API access log fields will be created.
  • 35 |
  • The misleading field `calculated_action` will be renamed to `recommended_action`.
  • 36 |

37 |
38 | 39 | 40 |
41 | Node software changelog includes OS version updates 42 |

    43 |
  • Node software Changelog will now include OS version updates and a brief explanation for the version bump.
  • 44 |
  • This will help educate customers and inform their version upgrade decision planning.
  • 45 |
  • This change is part of our ongoing commitment to providing our customers with the best possible experience.
  • 46 |

47 |
48 | 49 | 50 |
51 | Dashboard report email display issues resolved 52 |

    53 |
  • The change made to replace padding with grid gap has caused issues in dashboard report emails.
  • 54 |
  • This issue can be seen in the Figma design.
  • 55 |
  • See the linked GitLab issue for more details.
  • 56 |
  • @dev1 and @dev2 are copied on this issue.
  • 57 |

58 |
59 | 60 |
61 | 62 |
63 | Fixed Issue 64 | 65 |
66 | Tenant access policy now supported on new console endpoint 67 |

    68 |
  • Fixed an issue where tenants with Tenant Access Policy set were unable to access the console due to incorrect client IP extraction.
  • 69 |
  • Added support for Tenant Access Policy on the new console endpoint.
  • 70 |

71 |
72 | 73 | 74 |
75 | Disallowed response code hardening 76 |

    77 |
  • Improved UI automation coverage for RBAC, clone objects testing, and disallowed response codes.
  • 78 |
  • Enhanced hardening measures to strengthen security and reliability.
  • 79 |
  • Streamlined user experience with consistent terminology and improved access to reference documentation.
  • 80 |
  • Introduced new status workflow for AWS VPC Site, Azure VNET Site, AWS TGW Site, and GCP VPC Site for simplified cloud configuration and validation.
  • 81 |

82 |
83 | 84 |
85 | -------------------------------------------------------------------------------- /release-notes-generator/end/html_template.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Pass in filtered df for a category eg. New Feature 4 | # This function loops through each row in the df and creates an element for each summary/description pair. 5 | def format_output(category,df ): 6 | all_elements_list = list() 7 | for _,row in df.iterrows(): 8 | all_elements_list.append(formatted_single_element(summary=row["Release-Notes-Summary"],description=row["Release-Notes-Description"])) 9 | all_elements = "\n".join(all_elements_list) 10 | 11 | 12 | html = f""" 13 |
14 | {category} 15 | {all_elements} 16 |
17 | """ 18 | return html 19 | 20 | def formatted_single_element(summary: str, description: str ) -> str: 21 | formatted_description = format_to_html(description) 22 | 23 | html = f""" 24 |
25 | {summary} 26 |

{formatted_description}

27 |
28 | """ 29 | return html 30 | 31 | def format_to_html(text): 32 | items = re.split(r'(?<=\.)\s*-\s*', text.strip('- ')) 33 | list_items = [f"
  • {item.strip()}
  • " for item in items if item.strip()] 34 | formatted_html = "\n".join(list_items) 35 | 36 | return f'
      \n{formatted_html}\n
    ' -------------------------------------------------------------------------------- /release-notes-generator/end/main.py: -------------------------------------------------------------------------------- 1 | import writer as wf 2 | import pandas as pd 3 | import writer.ai 4 | from prompts import get_release_notes_summary_prompt, get_release_notes_desc_prompt, get_category_prompt 5 | from html_template import format_output 6 | 7 | # Functions to call the Completions end point for category, summary, and description. The prompts used are imported from the prompts file 8 | def _get_category(desc, label): 9 | prompt = get_category_prompt(desc, label) 10 | label = writer.ai.complete(prompt) 11 | return label 12 | 13 | def _get_release_notes_summary(label, desc): 14 | prompt = get_release_notes_summary_prompt(label, desc) 15 | formatted_desc = writer.ai.complete( prompt) 16 | return formatted_desc 17 | 18 | def _get_release_notes_desc(label,desc): 19 | prompt = get_release_notes_desc_prompt(label,desc) 20 | formatted_desc = writer.ai.complete(prompt) 21 | return formatted_desc 22 | 23 | # Handles when file is uploaded 24 | def onchangefile_handler(state, payload): 25 | uploaded_file = payload[0] 26 | 27 | # Store file name and path in case we need to use it later 28 | name = uploaded_file.get("name") 29 | state["file"]["name"] = name 30 | state["step1"]["processing-message"] = f'+File {name} uploaded successfully.' 31 | state["file"]["file_path"] = f"data/{name}" 32 | file_data = uploaded_file.get("data") 33 | # Save the file to the data folder instead of reading it in memory 34 | with open(f"data/{name}", "wb") as file_handle: 35 | file_handle.write(file_data) 36 | 37 | # Save raw csv file and mark the upload step as complete 38 | data = pd.read_csv(state["file"]["file_path"]) 39 | df = pd.DataFrame(data) 40 | state["step1"]["raw_csv"] = df 41 | state["step1"]["generate-button-state"] = "no" 42 | 43 | # Handler for generate button 44 | def handle_generate_button_click(state): 45 | state["step1"]["generate-button-state"] = "yes" 46 | # Set Processing Message state to visible 47 | state["step1"]["processing-message-isVisible"] = True 48 | state["step1"]["processing-message"] = "%Hang tight, preparing to process your file" 49 | 50 | # Read in csv from state and iterate through each row to apply the completions endpoint functions 51 | notes_counter = 0 52 | df = _raw_csv_to_df(state) 53 | csv_row_count = df.shape[0] 54 | for index, row in df.iterrows(): 55 | df.at[index,"Primary-Category"] = _get_category(label=row["Labels"], desc=row["Description"]) 56 | df.at[index,"Release-Notes-Summary"] = _get_release_notes_summary(label=row["Labels"], desc=row["Description"]) 57 | df.at[index,"Release-Notes-Description"] = _get_release_notes_desc(label=row["Labels"], desc=row["Description"]) 58 | notes_counter += 1 59 | state["step1"]["processing-message"] = f'%Processing {notes_counter} of {csv_row_count} Release Notes' 60 | df_temp = df[["Primary-Category","Release-Notes-Summary","Release-Notes-Description"]] 61 | df_sorted = df_temp.sort_values(by='Primary-Category') 62 | 63 | # Update State to populate Release Notes table in Release Notes tab 64 | state["step2"]["release-notes"] = df_sorted 65 | 66 | # Complete step 1 and automatically direct the user to step 2 67 | state["step1"]["completed"] = "yes" 68 | state["step1"]["processing-message"] = "" 69 | 70 | # Create the HTML element for the Formatted Release Notes Tab 71 | html = _create_df_for_category(df_sorted,state) 72 | _write_html_to_file(html) 73 | state["step2"]["formatted-release-notes"] = html 74 | state["metrics"]["Total"] = df_sorted.shape[0] 75 | 76 | state["step1"]["generate-button-state"] = "no" 77 | 78 | # Back button handler 79 | def handle_back_button_click(state): 80 | state["step1"]["completed"] = "no" 81 | 82 | # Function to write the HTML to a file 83 | def _write_html_to_file(html): 84 | with open("data/output-html.html", "w") as file_handle: 85 | file_handle.write(html) 86 | 87 | # Handler for HTML file download button 88 | def handle_file_download(state): 89 | html_data = wf.pack_file("data/output-html.html","text/html") 90 | file_name = "output-html.html" 91 | state.file_download(html_data,file_name) 92 | 93 | # Get a list of all unique categories from the dataframe and 94 | # generate the HTML for each of the summaries and descriptions 95 | # This function returns a single string with all the elements for all categories. 96 | def _create_df_for_category(df,state): 97 | unique_categories = df['Primary-Category'] 98 | formatted_output_list = list() 99 | for category in set(unique_categories): 100 | # Filter by individual category. This will produce a df for each single category. 101 | df_category = df[df['Primary-Category']==category] 102 | categories = {" New Feature": "new_features", " Caveat": "caveats", " Fixed Issue": "fixed_issues" } 103 | curr_category = categories[category] 104 | state["metrics"][curr_category]= df_category.shape[0] 105 | formatted_output = format_output(category,df_category) 106 | formatted_output_list.append(formatted_output) 107 | return "".join(formatted_output_list) 108 | 109 | 110 | # Creating initial placeholder DataFrame 111 | placeholder_data = { 112 | 'Description': ['Description 1', 'Description 2', 'Description 3'], 113 | 'Label': ['Label 1', 'Label 2', 'Label 3'] 114 | } 115 | initial_df = pd.DataFrame(placeholder_data) 116 | 117 | # Function to read in the raw CSV file and return a DataFrame 118 | def _raw_csv_to_df(state): 119 | data = pd.read_csv(state["file"]["file_path"]) 120 | df = pd.DataFrame(data) 121 | return df 122 | 123 | # Initialise the state 124 | initial_state = wf.init_state({ 125 | "my_app": { 126 | "title": "Release Notes Generator" 127 | }, 128 | "logo_image_path" : 'static/Writer_Logo_black.svg', 129 | "file": { 130 | "name" : "", 131 | "file_path" : "" 132 | }, 133 | "metrics":{ 134 | "new_features": 0, 135 | "caveats": 0, 136 | "fixed_issues" : 0, 137 | "Total": 0 138 | }, 139 | "step1":{ 140 | "raw_csv": initial_df, 141 | "completed": "no", 142 | "generate-button-state": "yes", 143 | "processing-message" : None, 144 | "processing-message-isVisible": False, 145 | "styled-table": "

    csv table

    " 146 | }, 147 | "step2":{ 148 | "release-notes": None, 149 | "completed": "no", 150 | "formatted-release-notes":"notes should go here"}, 151 | }) 152 | 153 | # Import the custom CSS file 154 | initial_state.import_stylesheet(".description, .summary, .category, .list", "/static/custom.css") 155 | 156 | -------------------------------------------------------------------------------- /release-notes-generator/end/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = {version = "0.6.2rc2"} 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /release-notes-generator/end/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /release-notes-generator/end/static/Writer_Logo_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /release-notes-generator/end/static/custom.css: -------------------------------------------------------------------------------- 1 | .category{ 2 | font-size: larger; 3 | padding-bottom: 1em; 4 | padding-top: 1em; 5 | font-weight: bold; 6 | } 7 | 8 | .summary{ 9 | padding-top: 1em; 10 | padding-bottom: 1em; 11 | margin-left: 1em; 12 | 13 | } 14 | 15 | .description{ 16 | margin-left: 3em; 17 | color:gray; 18 | } 19 | 20 | .list { 21 | margin-left: 3em; 22 | } -------------------------------------------------------------------------------- /release-notes-generator/end/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/release-notes-generator/end/static/favicon.png -------------------------------------------------------------------------------- /release-notes-generator/start/README.md: -------------------------------------------------------------------------------- 1 | This app was created using Writer Framework. 2 | 3 | To learn more about it, visit https://developer.writer.com/framework 4 | -------------------------------------------------------------------------------- /release-notes-generator/start/html_template.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Pass in filtered df for a category eg. New Feature 4 | # This function loops through each row in the df and creates an element for each summary/description pair. 5 | def format_output(category,df ): 6 | all_elements_list = list() 7 | for _,row in df.iterrows(): 8 | all_elements_list.append(formatted_single_element(summary=row["Release-Notes-Summary"],description=row["Release-Notes-Description"])) 9 | all_elements = "\n".join(all_elements_list) 10 | 11 | 12 | html = f""" 13 |
    14 | {category} 15 | {all_elements} 16 |
    17 | """ 18 | return html 19 | 20 | def formatted_single_element(summary: str, description: str ) -> str: 21 | formatted_description = format_to_html(description) 22 | 23 | html = f""" 24 |
    25 | {summary} 26 |

    {formatted_description}

    27 |
    28 | """ 29 | return html 30 | 31 | def format_to_html(text): 32 | items = re.split(r'(?<=\.)\s*-\s*', text.strip('- ')) 33 | list_items = [f"
  • {item.strip()}
  • " for item in items if item.strip()] 34 | formatted_html = "\n".join(list_items) 35 | 36 | return f'
      \n{formatted_html}\n
    ' -------------------------------------------------------------------------------- /release-notes-generator/start/main.py: -------------------------------------------------------------------------------- 1 | import writer as wf 2 | import pandas as pd 3 | import writer.ai 4 | from prompts import get_release_notes_summary_prompt, get_release_notes_desc_prompt, get_category_prompt 5 | from html_template import format_output 6 | 7 | # Welcome to Writer Framework! 8 | # More documentation is available at https://developer.writer.com/framework 9 | 10 | 11 | # Initialise the state 12 | initial_state = wf.init_state({ 13 | "my_app": { 14 | "title": "RELEASE NOTES GENERATOR" 15 | }, 16 | "logo_image_path" : 'static/Writer_Logo_black.svg' 17 | }) -------------------------------------------------------------------------------- /release-notes-generator/start/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "writer-framework-default" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10.0" 10 | writer = {version = "0.6.2rc2"} 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /release-notes-generator/start/static/README.md: -------------------------------------------------------------------------------- 1 | # Serving static files 2 | 3 | You can use this folder to store files which will be served statically in the "/static" route. 4 | 5 | This is useful to store images and other files which will be served directly to the user of your application. 6 | 7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg". 8 | You can use this relative route as the source in an Image component. 9 | -------------------------------------------------------------------------------- /release-notes-generator/start/static/Writer_Logo_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /release-notes-generator/start/static/custom.css: -------------------------------------------------------------------------------- 1 | .category{ 2 | font-size: larger; 3 | padding-bottom: 1em; 4 | padding-top: 1em; 5 | font-weight: bold; 6 | } 7 | 8 | .summary{ 9 | padding-top: 1em; 10 | padding-bottom: 1em; 11 | margin-left: 1em; 12 | 13 | } 14 | 15 | .description{ 16 | margin-left: 3em; 17 | color:gray 18 | } 19 | 20 | .list { 21 | margin-left: 3em; 22 | } -------------------------------------------------------------------------------- /release-notes-generator/start/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/release-notes-generator/start/static/favicon.png -------------------------------------------------------------------------------- /release-notes-generator/start/ui.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "writer_version": "0.6.2rc2" 4 | }, 5 | "components": { 6 | "root": { 7 | "id": "root", 8 | "type": "root", 9 | "content": { 10 | "appName": "Release Notes Generator" 11 | }, 12 | "isCodeManaged": false, 13 | "position": 0, 14 | "handlers": {}, 15 | "visible": true 16 | }, 17 | "c0f99a9e-5004-4e75-a6c6-36f17490b134": { 18 | "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 19 | "type": "page", 20 | "content": { 21 | "pageMode": "compact", 22 | "emptinessColor": "#f0f0f0" 23 | }, 24 | "isCodeManaged": false, 25 | "position": 0, 26 | "parentId": "root", 27 | "handlers": {}, 28 | "visible": true 29 | }, 30 | "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": { 31 | "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", 32 | "type": "header", 33 | "content": { 34 | "text": "@{my_app.title}" 35 | }, 36 | "isCodeManaged": false, 37 | "position": 0, 38 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", 39 | "handlers": {}, 40 | "visible": true 41 | }, 42 | "ki84gpgr1ekijybj": { 43 | "id": "ki84gpgr1ekijybj", 44 | "type": "image", 45 | "content": { 46 | "src": "@{logo_image_path}", 47 | "caption": "", 48 | "maxWidth": "100" 49 | }, 50 | "isCodeManaged": false, 51 | "position": 0, 52 | "parentId": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf", 53 | "handlers": {}, 54 | "visible": true 55 | } 56 | } 57 | } --------------------------------------------------------------------------------