├── .dockerignore ├── .gitignore ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── docker-compose.yml ├── dockerfile ├── docs ├── capabilities.md └── images │ ├── lidamodules.jpg │ ├── lidarch.png │ └── lidascreen.png ├── lida ├── __init__.py ├── cli.py ├── components │ ├── __init__.py │ ├── executor.py │ ├── goal.py │ ├── infographer.py │ ├── manager.py │ ├── persona.py │ ├── scaffold.py │ ├── summarizer.py │ └── viz │ │ ├── __init__.py │ │ ├── vizeditor.py │ │ ├── vizevaluator.py │ │ ├── vizexplainer.py │ │ ├── vizgenerator.py │ │ ├── vizrecommender.py │ │ └── vizrepairer.py ├── datamodel.py ├── utils.py ├── version.py └── web │ ├── .gitignore │ ├── __init__.py │ ├── app.py │ └── ui │ ├── 404 │ └── index.html │ ├── 21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js │ ├── 21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js.map │ ├── 404.html │ ├── 81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js │ ├── 81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js.LICENSE.txt │ ├── 81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js.map │ ├── app-80dcacffbc4d71399cc7.js │ ├── app-80dcacffbc4d71399cc7.js.map │ ├── chunk-map.json │ ├── component---src-pages-404-tsx-271998ff555bf33bd7ce.js │ ├── component---src-pages-404-tsx-271998ff555bf33bd7ce.js.map │ ├── component---src-pages-demo-tsx-54fd6da10fa870d8d843.js │ ├── component---src-pages-demo-tsx-54fd6da10fa870d8d843.js.LICENSE.txt │ ├── component---src-pages-demo-tsx-54fd6da10fa870d8d843.js.map │ ├── component---src-pages-index-tsx-36a4bd360cacad51120f.js │ ├── component---src-pages-index-tsx-36a4bd360cacad51120f.js.LICENSE.txt │ ├── component---src-pages-index-tsx-36a4bd360cacad51120f.js.map │ ├── component---src-pages-login-tsx-bbe1cb64566ee588bf48.js │ ├── component---src-pages-login-tsx-bbe1cb64566ee588bf48.js.map │ ├── demo │ └── index.html │ ├── favicon-32x32.png │ ├── files │ ├── infographics_small.jpg │ └── lidamodules.jpg │ ├── framework-adcf09f5896f386aa38b.js │ ├── framework-adcf09f5896f386aa38b.js.LICENSE.txt │ ├── framework-adcf09f5896f386aa38b.js.map │ ├── icons │ ├── icon-144x144.png │ ├── icon-192x192.png │ ├── icon-256x256.png │ ├── icon-384x384.png │ ├── icon-48x48.png │ ├── icon-512x512.png │ ├── icon-72x72.png │ └── icon-96x96.png │ ├── images │ ├── default.png │ ├── screen.png │ └── videoscreen.png │ ├── index.html │ ├── login │ └── index.html │ ├── manifest.webmanifest │ ├── page-data │ ├── 404 │ │ └── page-data.json │ ├── 404.html │ │ └── page-data.json │ ├── app-data.json │ ├── demo │ │ └── page-data.json │ ├── index │ │ └── page-data.json │ ├── login │ │ └── page-data.json │ └── sq │ │ └── d │ │ └── 1865044719.json │ ├── polyfill-9f027554f9c426b688ff.js │ ├── polyfill-9f027554f9c426b688ff.js.map │ ├── sitemap │ ├── sitemap-0.xml │ └── sitemap-index.xml │ ├── styles.15bd7f1a07f6d77699dc.css │ ├── webpack-runtime-8958b079d5c4b6876c66.js │ ├── webpack-runtime-8958b079d5c4b6876c66.js.map │ ├── webpack.stats.json │ └── ~partytown │ ├── debug │ ├── partytown-atomics.js │ ├── partytown-media.js │ ├── partytown-sandbox-sw.js │ ├── partytown-sw.js │ ├── partytown-ww-atomics.js │ ├── partytown-ww-sw.js │ └── partytown.js │ ├── partytown-atomics.js │ ├── partytown-media.js │ ├── partytown-sw.js │ └── partytown.js ├── notebooks └── tutorial.ipynb ├── pyproject.toml ├── requirements.txt └── tests └── test_components.py /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.pyo 4 | *.log 5 | .git -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | .vscode 3 | frontend 4 | notebooks/test.ipynb 5 | notebooks/experimental/* 6 | .azure 7 | .release.sh 8 | .pylintrc 9 | lida/generators/cache/cache.* 10 | lida/generators/cache/* 11 | lib/samples/ 12 | lida/web/files 13 | lida/web/data 14 | examples/experiment_results.json 15 | examples/data 16 | data 17 | .DS_Store 18 | test.py 19 | experiments/data 20 | test.py 21 | .azure 22 | 23 | # Byte-compiled / optimized / DLL files 24 | __pycache__/ 25 | *.py[cod] 26 | *$py.class 27 | 28 | # C extensions 29 | *.so 30 | 31 | # Distribution / packaging 32 | .Python 33 | # build/ 34 | develop-eggs/ 35 | dist/ 36 | downloads/ 37 | eggs/ 38 | .eggs/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | wheels/ 44 | pip-wheel-metadata/ 45 | share/python-wheels/ 46 | *.egg-info/ 47 | .installed.cfg 48 | *.egg 49 | MANIFEST 50 | .env 51 | 52 | # PyInstaller 53 | # Usually these files are written by a python script from a template 54 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 55 | *.manifest 56 | *.spec 57 | 58 | # Installer logs 59 | pip-log.txt 60 | pip-delete-this-directory.txt 61 | 62 | # Unit test / coverage reports 63 | htmlcov/ 64 | .tox/ 65 | .nox/ 66 | .coverage 67 | .coverage.* 68 | .cache 69 | nosetests.xml 70 | coverage.xml 71 | *.cover 72 | *.py,cover 73 | .hypothesis/ 74 | .pytest_cache/ 75 | 76 | # Translations 77 | *.mo 78 | *.pot 79 | 80 | # Django stuff: 81 | *.log 82 | local_settings.py 83 | db.sqlite3 84 | db.sqlite3-journal 85 | 86 | # Flask stuff: 87 | instance/ 88 | .webassets-cache 89 | 90 | # Scrapy stuff: 91 | .scrapy 92 | 93 | # Sphinx documentation 94 | docs/_build/ 95 | 96 | # PyBuilder 97 | target/ 98 | 99 | # Jupyter Notebook 100 | .ipynb_checkpoints 101 | 102 | # IPython 103 | profile_default/ 104 | ipython_config.py 105 | 106 | # pyenv 107 | .python-version 108 | 109 | # pipenv 110 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 111 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 112 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 113 | # install all needed dependencies. 114 | #Pipfile.lock 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: Dibia 5 | given-names: Victor 6 | title: "LIDA: A Tool for Automatic Generation of Grammar-Agnostic Visualizations and Infographics using Large Language Models" 7 | version: 1.0.0 8 | date-released: 2023-07-01 9 | url: "https://aclanthology.org/2023.acl-demo.11" 10 | doi: "10.18653/v1/2023.acl-demo.11" 11 | conference: 12 | name: "Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics (Volume 3: System Demonstrations)" 13 | month: jul 14 | year: 2023 15 | address: "Toronto, Canada" 16 | publisher: "Association for Computational Linguistics" 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include lida/web/ui * 2 | recursive-exclude notebooks * 3 | recursive-exclude docs * 4 | recursive-exclude tests * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LIDA: Automatic Generation of Visualizations and Infographics using Large Language Models 2 | 3 | [![PyPI version](https://badge.fury.io/py/lida.svg)](https://badge.fury.io/py/lida) 4 | [![arXiv](https://img.shields.io/badge/arXiv-2303.02927-.svg)](https://arxiv.org/abs/2303.02927) 5 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/lida?label=pypi%20downloads) 6 | 7 | 8 | Open In Colab 9 | 10 | 11 | 12 | 13 | LIDA is a library for generating data visualizations and data-faithful infographics. LIDA is grammar agnostic (will work with any programming language and visualization libraries e.g. matplotlib, seaborn, altair, d3 etc) and works with multiple large language model providers (OpenAI, Azure OpenAI, PaLM, Cohere, Huggingface). Details on the components of LIDA are described in the [paper here](https://arxiv.org/abs/2303.02927) and in this tutorial [notebook](notebooks/tutorial.ipynb). See the project page [here](https://microsoft.github.io/lida/) for updates!. 14 | 15 | > **Note on Code Execution:** 16 | > To create visualizations, LIDA _generates_ and _executes_ code. 17 | > Ensure that you run LIDA in a secure environment. 18 | 19 | ## Features 20 | 21 | ![lida components](https://github.com/microsoft/lida/blob/main/docs/images/lidamodules.jpg?raw=true) 22 | 23 | LIDA treats _**visualizations as code**_ and provides a clean api for generating, executing, editing, explaining, evaluating and repairing visualization code. 24 | 25 | - [x] Data Summarization 26 | - [x] Goal Generation 27 | - [x] Visualization Generation 28 | - [x] Visualization Editing 29 | - [x] Visualization Explanation 30 | - [x] Visualization Evaluation and Repair 31 | - [x] Visualization Recommendation 32 | - [x] Infographic Generation (beta) # pip install lida[infographics] 33 | 34 | ```python 35 | 36 | from lida import Manager, llm 37 | 38 | lida = Manager(text_gen = llm("openai")) # palm, cohere .. 39 | summary = lida.summarize("data/cars.csv") 40 | goals = lida.goals(summary, n=2) # exploratory data analysis 41 | charts = lida.visualize(summary=summary, goal=goals[0]) # exploratory data analysis 42 | ``` 43 | 44 | ## Getting Started 45 | 46 | Setup and verify that your python environment is **`python 3.10`** or higher (preferably, use [Conda](https://docs.conda.io/en/main/miniconda.html#installing)). Install the library via pip. 47 | 48 | ```bash 49 | pip install -U lida 50 | ``` 51 | 52 | LIDA depends on `llmx` and `openai`. If you had these libraries installed previously, consider updating them. 53 | 54 | ```bash 55 | pip install -U llmx openai 56 | ``` 57 | 58 | Once requirements are met, setup your api key. Learn more about setting up keys for other LLM providers [here](https://github.com/victordibia/llmx). 59 | 60 | ```bash 61 | export OPENAI_API_KEY= 62 | ``` 63 | 64 | Alternatively you can install the library in dev model by cloning this repo and running `pip install -e .` in the repository root. 65 | 66 | ## Web API and UI 67 | 68 | LIDA comes with an optional bundled ui and web api that you can explore by running the following command: 69 | 70 | ```bash 71 | lida ui --port=8080 --docs 72 | ``` 73 | 74 | Then navigate to http://localhost:8080/ in your browser. To view the web api specification, add the `--docs` option to the cli command, and navigate to `http://localhost:8080/api/docs` in your browser. 75 | 76 | The fastest and recommended way to get started after installation will be to try out the web ui above or run the [tutorial notebook](notebooks/tutorial.ipynb). 77 | 78 | ## Building the Web API and UI with Docker 79 | 80 | The LIDA web api and ui can be setup using docker and the command below (ensure that you have docker installed, and you have set your `OPENAI_API_KEY` environment variable). 81 | 82 | ```bash 83 | docker compose up 84 | ``` 85 | 86 | ### Data Summarization 87 | 88 | Given a dataset, generate a compact summary of the data. 89 | 90 | ```python 91 | from lida import Manager 92 | 93 | lida = Manager() 94 | summary = lida.summarize("data/cars.json") # generate data summary 95 | ``` 96 | 97 | ### Goal Generation 98 | 99 | Generate a set of visualization goals given a data summary. 100 | 101 | ```python 102 | goals = lida.goals(summary, n=5, persona="ceo with aerodynamics background") # generate goals 103 | ``` 104 | 105 | Add a `persona` parameter to generate goals based on that persona. 106 | 107 | ### Visualization Generation 108 | 109 | Generate, refine, execute and filter visualization code given a data summary and visualization goal. Note that LIDA represents **visualizations as code**. 110 | 111 | ```python 112 | # generate charts (generate and execute visualization code) 113 | charts = lida.visualize(summary=summary, goal=goals[0], library="matplotlib") # seaborn, ggplot .. 114 | ``` 115 | 116 | ### Visualization Editing 117 | 118 | Given a visualization, edit the visualization using natural language. 119 | 120 | ```python 121 | # modify chart using natural language 122 | instructions = ["convert this to a bar chart", "change the color to red", "change y axes label to Fuel Efficiency", "translate the title to french"] 123 | edited_charts = lida.edit(code=code, summary=summary, instructions=instructions, library=library, textgen_config=textgen_config) 124 | 125 | ``` 126 | 127 | ### Visualization Explanation 128 | 129 | Given a visualization, generate a natural language explanation of the visualization code (accessibility, data transformations applied, visualization code) 130 | 131 | ```python 132 | # generate explanation for chart 133 | explanation = lida.explain(code=charts[0].code, summary=summary) 134 | ``` 135 | 136 | ### Visualization Evaluation and Repair 137 | 138 | Given a visualization, evaluate to find repair instructions (which may be human authored, or generated), repair the visualization. 139 | 140 | ```python 141 | evaluations = lida.evaluate(code=code, goal=goals[i], library=library) 142 | ``` 143 | 144 | ### Visualization Recommendation 145 | 146 | Given a dataset, generate a set of recommended visualizations. 147 | 148 | ```python 149 | recommendations = lida.recommend(code=code, summary=summary, n=2, textgen_config=textgen_config) 150 | ``` 151 | 152 | ### Infographic Generation [WIP] 153 | 154 | Given a visualization, generate a data-faithful infographic. This methods should be considered experimental, and uses stable diffusion models from the [peacasso](https://github.com/victordibia/peacasso) library. You will need to run `pip install lida[infographics]` to install the required dependencies. 155 | 156 | ```python 157 | infographics = lida.infographics(visualization = charts[0].raster, n=3, style_prompt="line art") 158 | ``` 159 | 160 | ## Using LIDA with Locally Hosted LLMs (HuggingFace) 161 | 162 | LIDA uses the [llmx](https://github.com/victordibia/llmx) library as its interface for text generation. llmx supports multiple local models including HuggingFace models. You can use the huggingface models directly (assuming you have a gpu) or connect to an openai compatible local model endpoint e.g. using the excellent [vllm](https://vllm.readthedocs.io/en/latest/) library. 163 | 164 | #### Using HuggingFace Models Directly 165 | 166 | ```python 167 | !pip3 install --upgrade llmx==0.0.17a0 168 | 169 | # Restart the colab session 170 | 171 | from lida import Manager 172 | from llmx import llm 173 | text_gen = llm(provider="hf", model="uukuguy/speechless-llama2-hermes-orca-platypus-13b", device_map="auto") 174 | lida = Manager(text_gen=text_gen) 175 | # now you can call lida methods as above e.g. 176 | sumamry = lida.summarize("data/cars.csv") # .... 177 | ``` 178 | 179 | #### Using an OpenAI Compatible Endpoint e.g. [vllm server](https://vllm.readthedocs.io/en/latest/getting_started/quickstart.html#openai-compatible-server) 180 | 181 | ```python 182 | from lida import Manager, TextGenerationConfig , llm 183 | 184 | model_name = "uukuguy/speechless-llama2-hermes-orca-platypus-13b" 185 | model_details = [{'name': model_name, 'max_tokens': 2596, 'model': {'provider': 'openai', 'parameters': {'model': model_name}}}] 186 | 187 | # assuming your vllm endpoint is running on localhost:8000 188 | text_gen = llm(provider="openai", api_base="http://localhost:8000/v1", api_key="EMPTY", models=model_details) 189 | lida = Manager(text_gen = text_gen) 190 | ``` 191 | 192 | ## Important Notes / Caveats / FAQs 193 | 194 | - LIDA generates and executes code based on provided input. Ensure that you run LIDA in a secure environment with appropriate permissions. 195 | - LIDA currently works best with datasets that have a small number of columns (<= 10). This is mainly due to the limited context size for most models. For larger datasets, consider preprocessing your dataset to use a subset of the columns. 196 | - LIDA assumes the dataset exists and is in a format that can be loaded into a pandas dataframe. For example, a csv file, or a json file with a list of objects. In practices the right dataset may need to be curated and preprocessed to ensure that it is suitable for the task at hand. 197 | - Smaller LLMs (e.g., OSS LLMs on Huggingface) have limited instruction following capabilities and may not work well with LIDA. LIDA works best with larger LLMs (e.g., OpenAI GPT 3.5, GPT 4). 198 | - How reliable is the LIDA approach? The LIDA [paper](https://aclanthology.org/2023.acl-demo.11/) describes experiments that evaluate the reliability of LIDA using a visualization error rate metric. With the current version of prompts, data summarization techniques, preprocessing/postprocessing logic and LLMs, LIDA has an error rate of < 3.5% on over 2200 visualizations generated (compared to a baseline of over 10% error rate). This area is work in progress. 199 | - Can I build my own apps with LIDA? Yes! You can either use the python api directly in your app or setup a web api endpoint and use the web api in your app. See the [web api](#web-api-and-ui) section for more details. 200 | - How is LIDA related to OpenAI Code Interpreter: LIDA shares several similarities with code interpreter in the sense that both involve writing and executing code to address user intent. LIDA differs in its focus on visualization, providing a modular api for developer reuse and providing evaluation metrics on the visualization use case. 201 | 202 | Naturally, some of the limitations above could be addressed by a much welcomed PR. 203 | 204 | ## Community Examples Built with LIDA 205 | 206 | - LIDA + Streamlit: [lida-streamlit](https://github.com/lida-project/lida-streamlit), 207 | 208 | ## Documentation and Citation 209 | 210 | A short paper describing LIDA (Accepted at ACL 2023 Conference) is available [here](https://arxiv.org/abs/2303.02927). 211 | 212 | ```bibtex 213 | @inproceedings{dibia2023lida, 214 | title = "{LIDA}: A Tool for Automatic Generation of Grammar-Agnostic Visualizations and Infographics using Large Language Models", 215 | author = "Dibia, Victor", 216 | booktitle = "Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics (Volume 3: System Demonstrations)", 217 | month = jul, 218 | year = "2023", 219 | address = "Toronto, Canada", 220 | publisher = "Association for Computational Linguistics", 221 | url = "https://aclanthology.org/2023.acl-demo.11", 222 | doi = "10.18653/v1/2023.acl-demo.11", 223 | pages = "113--126", 224 | } 225 | ``` 226 | 227 | LIDA builds on insights in automatic generation of visualization from an earlier paper - [Data2Vis: Automatic Generation of Data Visualizations Using Sequence to Sequence Recurrent Neural Networks](https://arxiv.org/abs/1804.03126). 228 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please consult the project readme or open an issue. 10 | 11 | ## Microsoft Support Policy 12 | 13 | Support for this project is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | web: 4 | image: web-ui 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - "8080:8080" 10 | environment: 11 | - OPENAI_API_KEY -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.10-slim 3 | 4 | # Set environment variables 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONBUFFERED 1 7 | 8 | # Set the working directory in the container 9 | WORKDIR /app 10 | 11 | # Install requirements 12 | RUN pip install --no-cache-dir lida 13 | 14 | # Expose the port that the application will listen on 15 | EXPOSE 8080 16 | 17 | # Start the Web UI 18 | CMD ["lida", "ui", "--host", "0.0.0.0", "--port", "8080", "--docs"] -------------------------------------------------------------------------------- /docs/capabilities.md: -------------------------------------------------------------------------------- 1 | This README provides an overview of the current capabilities of LIDA. 2 | 3 | ### Core Capabilities 4 | 5 | These are the fundamental features that form the primary functionality of LIDA related to visualizations and infographics. 6 | 7 | | Core Feature | Description | Status | 8 | | --------------------------------- | ----------------------------------------------------------------------- | ------ | 9 | | Data Summarization | Generates a compact summary of the data. | ✅ | 10 | | Goal Generation | Produces a set of visualization goals from a data summary. | ✅ | 11 | | Visualization Generation | Creates and executes visualization code based on data summary and goal. | ✅ | 12 | | Visualization Editing | Modifies visualizations using natural language instructions. | ✅ | 13 | | Visualization Explanation | Generates natural language explanations of visualization code. | ✅ | 14 | | Visualization Evaluation & Repair | Evaluates visualizations and provides repair instructions. | ✅ | 15 | | Visualization Recommendation | Recommends a set of visualizations based on a dataset. | ✅ | 16 | | Infographic Generation | Converts visualizations to data-faithful infographics. | 🚧 | 17 | 18 | > ⚠️ **Note**: LIDA is currently optimized for generating visualizations i.e. tasks for which the output is a visualization. It may not be the best tool for tasks that do not involve visualizations, such as creating machine learning models (e.g., create a time series model for forecasting), data analysis with a single value answer (what is square root of the smallest value in the dataset). This may be supported in the future. 19 | 20 | ### Other Capabilities 21 | 22 | These features support the core capabilities and provide additional utility and flexibility. 23 | 24 | | Other Feature | Description | Status | Notes | 25 | | ----------------------------- | -------------------------------------------------------------------------------------- | ------ | ----------------------------------------------------- | 26 | | Grammar-Agnostic | Works with any programming language and visualization library. | ✅ | | 27 | | Multi-LLM Provider Support | Compatible with various large language model providers like OpenAI, Azure OpenAI, etc. | ✅ | | 28 | | Python API | Provides a Python-based API for generating visualizations & infographics. | ✅ | Requires Python 3.10 or higher. | 29 | | Web API & UI | Optional user interface and web API included for exploration. | ✅ | Setup via Docker; accessible via localhost. | 30 | | Docker Support | Can be set up and run using Docker. | ✅ | Facilitates deployment and containerization. | 31 | | HuggingFace Model Integration | Supports using HuggingFace models for text generation. | ✅ | User can opt for direct use or via a local endpoint. | 32 | | Security Note | Generates and executes code; should be run in a secure environment. | ⚠️ | Proper permissions management is crucial. | 33 | | Community Expansion | Encourages community contributions and extensions of the tool. | ✅ | Examples available, e.g., lida-streamlit. | 34 | | Documentation & Citation | Well-documented with available academic paper citation. | ✅ | Provides theoretical background and use case details. | 35 | 36 | Symbols used: 37 | 38 | - ✅ Feature is included and functional. 39 | - 🚧 Feature is still in development or beta stage. 40 | - ⚠️ Feature requires careful handling due to security implications. 41 | -------------------------------------------------------------------------------- /docs/images/lidamodules.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/docs/images/lidamodules.jpg -------------------------------------------------------------------------------- /docs/images/lidarch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/docs/images/lidarch.png -------------------------------------------------------------------------------- /docs/images/lidascreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/docs/images/lidascreen.png -------------------------------------------------------------------------------- /lida/__init__.py: -------------------------------------------------------------------------------- 1 | from llmx import TextGenerationConfig, llm, TextGenerator 2 | from .components.manager import Manager 3 | 4 | 5 | __all__ = ["TextGenerationConfig", "llm", "TextGenerator", "Manager"] 6 | -------------------------------------------------------------------------------- /lida/cli.py: -------------------------------------------------------------------------------- 1 | import typer 2 | import uvicorn 3 | import os 4 | from typing_extensions import Annotated 5 | from llmx import providers 6 | 7 | # from lida.web.backend.app import launch 8 | 9 | app = typer.Typer() 10 | 11 | 12 | @app.command() 13 | def ui(host: str = "127.0.0.1", 14 | port: int = 8081, 15 | workers: int = 1, 16 | reload: Annotated[bool, typer.Option("--reload")] = True, 17 | docs: bool = False): 18 | """ 19 | Launch the lida .Pass in parameters host, port, workers, and reload to override the default values. 20 | """ 21 | 22 | os.environ["LIDA_API_DOCS"] = str(docs) 23 | 24 | uvicorn.run( 25 | "lida.web.app:app", 26 | host=host, 27 | port=port, 28 | workers=workers, 29 | reload=reload, 30 | ) 31 | 32 | 33 | @app.command() 34 | def models(): 35 | print("A list of supported providers:") 36 | for provider in providers.items(): 37 | print(f"Provider: {provider[1]['name']}") 38 | for model in provider[1]["models"]: 39 | print(f" - {model['name']}") 40 | 41 | 42 | def run(): 43 | app() 44 | 45 | 46 | if __name__ == "__main__": 47 | app() 48 | -------------------------------------------------------------------------------- /lida/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .summarizer import Summarizer 2 | from .viz import * 3 | from .goal import * 4 | from .scaffold import * 5 | from .executor import * 6 | from .manager import * 7 | from .persona import * 8 | -------------------------------------------------------------------------------- /lida/components/executor.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import base64 3 | import importlib 4 | import io 5 | import os 6 | import re 7 | import traceback 8 | from typing import Any, List 9 | 10 | import matplotlib.pyplot as plt 11 | import pandas as pd 12 | import plotly.io as pio 13 | 14 | from lida.datamodel import ChartExecutorResponse, Summary 15 | 16 | 17 | def preprocess_code(code: str) -> str: 18 | """Preprocess code to remove any preamble and explanation text""" 19 | 20 | code = code.replace("", "") 21 | code = code.replace("", "") 22 | code = code.replace("", "") 23 | 24 | # remove all text after chart = plot(data) 25 | if "chart = plot(data)" in code: 26 | # print(code) 27 | index = code.find("chart = plot(data)") 28 | if index != -1: 29 | code = code[: index + len("chart = plot(data)")] 30 | 31 | if "```" in code: 32 | pattern = r"```(?:\w+\n)?([\s\S]+?)```" 33 | matches = re.findall(pattern, code) 34 | if matches: 35 | code = matches[0] 36 | # code = code.replace("```", "") 37 | # return code 38 | 39 | if "import" in code: 40 | # return only text after the first import statement 41 | index = code.find("import") 42 | if index != -1: 43 | code = code[index:] 44 | 45 | code = code.replace("```", "") 46 | if "chart = plot(data)" not in code: 47 | code = code + "\nchart = plot(data)" 48 | return code 49 | 50 | 51 | def get_globals_dict(code_string, data): 52 | # Parse the code string into an AST 53 | tree = ast.parse(code_string) 54 | # Extract the names of the imported modules and their aliases 55 | imported_modules = [] 56 | for node in tree.body: 57 | if isinstance(node, ast.Import): 58 | for alias in node.names: 59 | module = importlib.import_module(alias.name) 60 | imported_modules.append((alias.name, alias.asname, module)) 61 | elif isinstance(node, ast.ImportFrom): 62 | module = importlib.import_module(node.module) 63 | for alias in node.names: 64 | obj = getattr(module, alias.name) 65 | imported_modules.append( 66 | (f"{node.module}.{alias.name}", alias.asname, obj) 67 | ) 68 | 69 | # Import the required modules into a dictionary 70 | globals_dict = {} 71 | for module_name, alias, obj in imported_modules: 72 | if alias: 73 | globals_dict[alias] = obj 74 | else: 75 | globals_dict[module_name.split(".")[-1]] = obj 76 | 77 | ex_dicts = {"pd": pd, "data": data, "plt": plt} 78 | globals_dict.update(ex_dicts) 79 | return globals_dict 80 | 81 | 82 | class ChartExecutor: 83 | """Execute code and return chart object""" 84 | 85 | def __init__(self) -> None: 86 | pass 87 | 88 | def execute( 89 | self, 90 | code_specs: List[str], 91 | data: Any, 92 | summary: Summary, 93 | library="altair", 94 | return_error: bool = False, 95 | ) -> Any: 96 | """Validate and convert code""" 97 | 98 | # # check if user has given permission to execute code. if env variable 99 | # # LIDA_ALLOW_CODE_EVAL is set to '1'. Else raise exception 100 | # if os.environ.get("LIDA_ALLOW_CODE_EVAL") != '1': 101 | # raise Exception( 102 | # "Permission to execute code not granted. Please set the environment variable LIDA_ALLOW_CODE_EVAL to '1' to allow code execution.") 103 | 104 | if isinstance(summary, dict): 105 | summary = Summary(**summary) 106 | 107 | charts = [] 108 | code_spec_copy = code_specs.copy() 109 | code_specs = [preprocess_code(code) for code in code_specs] 110 | if library == "altair": 111 | for code in code_specs: 112 | try: 113 | ex_locals = get_globals_dict(code, data) 114 | exec(code, ex_locals) 115 | chart = ex_locals["chart"] 116 | vega_spec = chart.to_dict() 117 | del vega_spec["data"] 118 | if "datasets" in vega_spec: 119 | del vega_spec["datasets"] 120 | 121 | vega_spec["data"] = {"url": f"/files/data/{summary.file_name}"} 122 | charts.append( 123 | ChartExecutorResponse( 124 | spec=vega_spec, 125 | status=True, 126 | raster=None, 127 | code=code, 128 | library=library, 129 | ) 130 | ) 131 | except Exception as exception_error: 132 | print(code_spec_copy, "\n===========\n") 133 | print(exception_error) 134 | print(traceback.format_exc()) 135 | if return_error: 136 | charts.append( 137 | ChartExecutorResponse( 138 | spec=None, 139 | status=False, 140 | raster=None, 141 | code=code, 142 | library=library, 143 | error={ 144 | "message": str(exception_error), 145 | "traceback": traceback.format_exc(), 146 | }, 147 | ) 148 | ) 149 | return charts 150 | elif library == "matplotlib" or library == "seaborn": 151 | # print colum dtypes 152 | for code in code_specs: 153 | try: 154 | ex_locals = get_globals_dict(code, data) 155 | # print(ex_locals) 156 | exec(code, ex_locals) 157 | chart = ex_locals["chart"] 158 | if plt: 159 | buf = io.BytesIO() 160 | plt.box(False) 161 | plt.grid(color="lightgray", linestyle="dashed", zorder=-10) 162 | # try: 163 | # plt.draw() 164 | # # plt.tight_layout() 165 | # except AttributeError: 166 | # print("Warning: tight_layout encountered an error. The layout may not be optimal.") 167 | # pass 168 | 169 | plt.savefig(buf, format="png", dpi=100, pad_inches=0.2) 170 | buf.seek(0) 171 | plot_data = base64.b64encode(buf.read()).decode("ascii") 172 | plt.close() 173 | charts.append( 174 | ChartExecutorResponse( 175 | spec=None, 176 | status=True, 177 | raster=plot_data, 178 | code=code, 179 | library=library, 180 | ) 181 | ) 182 | except Exception as exception_error: 183 | print(code_spec_copy[0]) 184 | print("****\n", str(exception_error)) 185 | # print(traceback.format_exc()) 186 | if return_error: 187 | charts.append( 188 | ChartExecutorResponse( 189 | spec=None, 190 | status=False, 191 | raster=None, 192 | code=code, 193 | library=library, 194 | error={ 195 | "message": str(exception_error), 196 | "traceback": traceback.format_exc(), 197 | }, 198 | ) 199 | ) 200 | return charts 201 | elif library == "ggplot": 202 | # print colum dtypes 203 | for code in code_specs: 204 | try: 205 | ex_locals = get_globals_dict(code, data) 206 | exec(code, ex_locals) 207 | chart = ex_locals["chart"] 208 | if plt: 209 | buf = io.BytesIO() 210 | chart.save(buf, format="png") 211 | plot_data = base64.b64encode(buf.getvalue()).decode("utf-8") 212 | charts.append( 213 | ChartExecutorResponse( 214 | spec=None, 215 | status=True, 216 | raster=plot_data, 217 | code=code, 218 | library=library, 219 | ) 220 | ) 221 | except Exception as exception_error: 222 | print(code) 223 | print(traceback.format_exc()) 224 | if return_error: 225 | charts.append( 226 | ChartExecutorResponse( 227 | spec=None, 228 | status=False, 229 | raster=None, 230 | code=code, 231 | library=library, 232 | error={ 233 | "message": str(exception_error), 234 | "traceback": traceback.format_exc(), 235 | }, 236 | ) 237 | ) 238 | return charts 239 | 240 | elif library == "plotly": 241 | for code in code_specs: 242 | try: 243 | ex_locals = get_globals_dict(code, data) 244 | exec(code, ex_locals) 245 | chart = ex_locals["chart"] 246 | 247 | if pio: 248 | chart_bytes = pio.to_image(chart, 'png') 249 | plot_data = base64.b64encode(chart_bytes).decode('utf-8') 250 | 251 | charts.append( 252 | ChartExecutorResponse( 253 | spec=None, 254 | status=True, 255 | raster=plot_data, 256 | code=code, 257 | library=library, 258 | ) 259 | ) 260 | except Exception as exception_error: 261 | print(code) 262 | print(traceback.format_exc()) 263 | if return_error: 264 | charts.append( 265 | ChartExecutorResponse( 266 | spec=None, 267 | status=False, 268 | raster=None, 269 | code=code, 270 | library=library, 271 | error={ 272 | "message": str(exception_error), 273 | "traceback": traceback.format_exc(), 274 | }, 275 | ) 276 | ) 277 | return charts 278 | 279 | else: 280 | raise Exception( 281 | f"Unsupported library. Supported libraries are altair, matplotlib, seaborn, ggplot, plotly. You provided {library}" 282 | ) 283 | -------------------------------------------------------------------------------- /lida/components/goal.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from lida.utils import clean_code_snippet 4 | from llmx import TextGenerator 5 | from lida.datamodel import Goal, TextGenerationConfig, Persona 6 | 7 | 8 | SYSTEM_INSTRUCTIONS = """ 9 | You are a an experienced data analyst who can generate a given number of insightful GOALS about data, when given a summary of the data, and a specified persona. The VISUALIZATIONS YOU RECOMMEND MUST FOLLOW VISUALIZATION BEST PRACTICES (e.g., must use bar charts instead of pie charts for comparing quantities) AND BE MEANINGFUL (e.g., plot longitude and latitude on maps where appropriate). They must also be relevant to the specified persona. Each goal must include a question, a visualization (THE VISUALIZATION MUST REFERENCE THE EXACT COLUMN FIELDS FROM THE SUMMARY), and a rationale (JUSTIFICATION FOR WHICH dataset FIELDS ARE USED and what we will learn from the visualization). Each goal MUST mention the exact fields from the dataset summary above 10 | """ 11 | 12 | FORMAT_INSTRUCTIONS = """ 13 | THE OUTPUT MUST BE A CODE SNIPPET OF A VALID LIST OF JSON OBJECTS. IT MUST USE THE FOLLOWING FORMAT: 14 | 15 | ```[ 16 | { "index": 0, "question": "What is the distribution of X", "visualization": "histogram of X", "rationale": "This tells about "} .. 17 | ] 18 | ``` 19 | THE OUTPUT SHOULD ONLY USE THE JSON FORMAT ABOVE. 20 | """ 21 | 22 | logger = logging.getLogger("lida") 23 | 24 | 25 | class GoalExplorer(): 26 | """Generate goals given a summary of data""" 27 | 28 | def __init__(self) -> None: 29 | pass 30 | 31 | def generate(self, summary: dict, textgen_config: TextGenerationConfig, 32 | text_gen: TextGenerator, n=5, persona: Persona = None) -> list[Goal]: 33 | """Generate goals given a summary of data""" 34 | 35 | user_prompt = f"""The number of GOALS to generate is {n}. The goals should be based on the data summary below, \n\n . 36 | {summary} \n\n""" 37 | 38 | if not persona: 39 | persona = Persona( 40 | persona="A highly skilled data analyst who can come up with complex, insightful goals about data", 41 | rationale="") 42 | 43 | user_prompt += f"""\n The generated goals SHOULD BE FOCUSED ON THE INTERESTS AND PERSPECTIVE of a '{persona.persona} persona, who is insterested in complex, insightful goals about the data. \n""" 44 | 45 | messages = [ 46 | {"role": "system", "content": SYSTEM_INSTRUCTIONS}, 47 | {"role": "assistant", 48 | "content": 49 | f"{user_prompt}\n\n {FORMAT_INSTRUCTIONS} \n\n. The generated {n} goals are: \n "}] 50 | 51 | result: list[Goal] = text_gen.generate(messages=messages, config=textgen_config) 52 | 53 | try: 54 | json_string = clean_code_snippet(result.text[0]["content"]) 55 | result = json.loads(json_string) 56 | # cast each item in the list to a Goal object 57 | if isinstance(result, dict): 58 | result = [result] 59 | result = [Goal(**x) for x in result] 60 | except json.decoder.JSONDecodeError: 61 | logger.info(f"Error decoding JSON: {result.text[0]['content']}") 62 | print(f"Error decoding JSON: {result.text[0]['content']}") 63 | raise ValueError( 64 | "The model did not return a valid JSON object while attempting generate goals. Consider using a larger model or a model with higher max token length.") 65 | return result 66 | -------------------------------------------------------------------------------- /lida/components/infographer.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from typing import Any, List, Union 4 | import PIL 5 | from peacasso.generator import ImageGenerator 6 | from peacasso.datamodel import GeneratorConfig, ModelConfig 7 | from peacasso.utils import base64_to_pil, pil_to_base64 8 | import torch 9 | 10 | logger = logging.getLogger("lida") 11 | 12 | 13 | class Infographer(): 14 | """Generate infographics given a visualization and a summary of data""" 15 | 16 | def __init__(self, model_config: ModelConfig = None) -> None: 17 | self.model = None 18 | self.model_config = model_config or ModelConfig( 19 | device="cuda", 20 | model="runwayml/stable-diffusion-v1-5", 21 | revision="main" 22 | ) 23 | 24 | def load_model(self) -> None: 25 | """Load image generator model from config""" 26 | self.model = ImageGenerator(model_config=self.model_config) 27 | 28 | def generate( 29 | self, visualization: Union[torch.FloatTensor, PIL.Image.Image, str], 30 | n: int, style_prompt: Union[str, List[str]] = "line art pastel", 31 | return_pil: bool = True 32 | ) -> List[Any]: 33 | """Generate a an infographic, given a visualization and style""" 34 | 35 | if isinstance(visualization, str): 36 | try: 37 | visualization, _ = base64_to_pil(visualization) 38 | except Exception as pil_exception: 39 | logger.error(pil_exception) 40 | raise ValueError( 41 | f'Could not convert provided visualization to PIL image, {str(pil_exception)}') from pil_exception 42 | self.load_model() 43 | 44 | gen_config = GeneratorConfig( 45 | prompt=style_prompt, 46 | num_images=n, 47 | width=512, 48 | height=512, 49 | guidance_scale=7.5, 50 | num_inference_steps=50, 51 | init_image=visualization, 52 | return_intermediates=False, 53 | seed=2147483647, 54 | use_prompt_weights=False, 55 | negative_prompt="text, background shapes or lines, title, words, characters, titles, letters", 56 | strength=0.6, 57 | filter_nsfw=False) 58 | 59 | result = self.model.generate(gen_config) 60 | if not return_pil: 61 | result["images"] = [pil_to_base64(img) for img in result["images"]] 62 | return result 63 | -------------------------------------------------------------------------------- /lida/components/manager.py: -------------------------------------------------------------------------------- 1 | # Visualization manager class that handles the visualization of the data with the following methods 2 | 3 | # summarize data given a df 4 | # generate goals given a summary 5 | # generate generate visualization specifications given a summary and a goal 6 | # execute the specification given some data 7 | 8 | import os 9 | from typing import List, Union 10 | import logging 11 | 12 | import pandas as pd 13 | from llmx import llm, TextGenerator 14 | from lida.datamodel import Goal, Summary, TextGenerationConfig, Persona 15 | from lida.utils import read_dataframe 16 | from ..components.summarizer import Summarizer 17 | from ..components.goal import GoalExplorer 18 | from ..components.persona import PersonaExplorer 19 | from ..components.executor import ChartExecutor 20 | from ..components.viz import VizGenerator, VizEditor, VizExplainer, VizEvaluator, VizRepairer, VizRecommender 21 | 22 | import lida.web as lida 23 | 24 | 25 | logger = logging.getLogger("lida") 26 | 27 | 28 | class Manager(object): 29 | def __init__(self, text_gen: TextGenerator = None) -> None: 30 | """ 31 | Initialize the Manager object. 32 | 33 | Args: 34 | text_gen (TextGenerator, optional): Text generator object. Defaults to None. 35 | """ 36 | 37 | self.text_gen = text_gen or llm() 38 | 39 | self.summarizer = Summarizer() 40 | self.goal = GoalExplorer() 41 | self.vizgen = VizGenerator() 42 | self.vizeditor = VizEditor() 43 | self.executor = ChartExecutor() 44 | self.explainer = VizExplainer() 45 | self.evaluator = VizEvaluator() 46 | self.repairer = VizRepairer() 47 | self.recommender = VizRecommender() 48 | self.data = None 49 | self.infographer = None 50 | self.persona = PersonaExplorer() 51 | 52 | def check_textgen(self, config: TextGenerationConfig): 53 | """ 54 | Check if self.text_gen is the same as the config passed in. If not, update self.text_gen. 55 | 56 | Args: 57 | config (TextGenerationConfig): Text generation configuration. 58 | """ 59 | if config.provider is None: 60 | config.provider = self.text_gen.provider or "openai" 61 | logger.info("Provider is not set, using default provider - %s", config.provider) 62 | return 63 | 64 | if self.text_gen.provider != config.provider: 65 | 66 | logger.info( 67 | "Switching Text Generator Provider from %s to %s", 68 | self.text_gen.provider, 69 | config.provider) 70 | self.text_gen = llm(provider=config.provider) 71 | 72 | def summarize( 73 | self, 74 | data: Union[pd.DataFrame, str], 75 | file_name="", 76 | n_samples: int = 3, 77 | summary_method: str = "default", 78 | textgen_config: TextGenerationConfig = TextGenerationConfig(n=1, temperature=0), 79 | ) -> Summary: 80 | """ 81 | Summarize data given a DataFrame or file path. 82 | 83 | Args: 84 | data (Union[pd.DataFrame, str]): Input data, either a DataFrame or file path. 85 | file_name (str, optional): Name of the file if data is loaded from a file path. Defaults to "". 86 | n_samples (int, optional): Number of summary samples to generate. Defaults to 3. 87 | summary_method (str, optional): Summary method to use. Defaults to "default". 88 | textgen_config (TextGenerationConfig, optional): Text generation configuration. Defaults to TextGenerationConfig(n=1, temperature=0). 89 | 90 | Returns: 91 | Summary: Summary object containing the generated summary. 92 | 93 | Example of Summary: 94 | 95 | {'name': 'cars.csv', 96 | 'file_name': 'cars.csv', 97 | 'dataset_description': '', 98 | 'fields': [{'column': 'Name', 99 | 'properties': {'dtype': 'string', 100 | 'samples': ['Nissan Altima S 4dr', 101 | 'Mercury Marauder 4dr', 102 | 'Toyota Prius 4dr (gas/electric)'], 103 | 'num_unique_values': 385, 104 | 'semantic_type': '', 105 | 'description': ''}}, 106 | {'column': 'Type', 107 | 'properties': {'dtype': 'category', 108 | 'samples': ['SUV', 'Minivan', 'Sports Car'], 109 | 'num_unique_values': 5, 110 | 'semantic_type': '', 111 | 'description': ''}}, 112 | {'column': 'AWD', 113 | 'properties': {'dtype': 'number', 114 | 'std': 0, 115 | 'min': 0, 116 | 'max': 1, 117 | 'samples': [1, 0], 118 | 'num_unique_values': 2, 119 | 'semantic_type': '', 120 | 'description': ''}}, 121 | } 122 | 123 | """ 124 | self.check_textgen(config=textgen_config) 125 | 126 | if isinstance(data, str): 127 | file_name = data.split("/")[-1] 128 | data = read_dataframe(data) 129 | 130 | self.data = data 131 | return self.summarizer.summarize( 132 | data=self.data, text_gen=self.text_gen, file_name=file_name, n_samples=n_samples, 133 | summary_method=summary_method, textgen_config=textgen_config) 134 | 135 | def goals( 136 | self, 137 | summary: Summary, 138 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 139 | n: int = 5, 140 | persona: Persona = None 141 | ) -> List[Goal]: 142 | """ 143 | Generate goals based on a summary. 144 | 145 | Args: 146 | summary (Summary): Input summary. 147 | textgen_config (TextGenerationConfig, optional): Text generation configuration. Defaults to TextGenerationConfig(). 148 | n (int, optional): Number of goals to generate. Defaults to 5. 149 | persona (Persona, str, dict, optional): Persona information. Defaults to None. 150 | 151 | Returns: 152 | List[Goal]: List of generated goals. 153 | 154 | Example of list of goals: 155 | 156 | Goal 0 157 | Question: What is the distribution of Retail_Price? 158 | 159 | Visualization: histogram of Retail_Price 160 | 161 | Rationale: This tells about the spread of prices of cars in the dataset. 162 | 163 | Goal 1 164 | Question: What is the distribution of Horsepower_HP_? 165 | 166 | Visualization: box plot of Horsepower_HP_ 167 | 168 | Rationale: This tells about the distribution of horsepower of cars in the dataset. 169 | """ 170 | self.check_textgen(config=textgen_config) 171 | 172 | if isinstance(persona, dict): 173 | persona = Persona(**persona) 174 | if isinstance(persona, str): 175 | persona = Persona(persona=persona, rationale="") 176 | 177 | return self.goal.generate(summary=summary, text_gen=self.text_gen, 178 | textgen_config=textgen_config, n=n, persona=persona) 179 | 180 | def personas( 181 | self, summary, textgen_config: TextGenerationConfig = TextGenerationConfig(), 182 | n=5): 183 | self.check_textgen(config=textgen_config) 184 | 185 | return self.persona.generate(summary=summary, text_gen=self.text_gen, 186 | textgen_config=textgen_config, n=n) 187 | 188 | def visualize( 189 | self, 190 | summary, 191 | goal, 192 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 193 | library="seaborn", 194 | return_error: bool = False, 195 | ): 196 | if isinstance(goal, dict): 197 | goal = Goal(**goal) 198 | if isinstance(goal, str): 199 | goal = Goal(question=goal, visualization=goal, rationale="") 200 | 201 | self.check_textgen(config=textgen_config) 202 | code_specs = self.vizgen.generate( 203 | summary=summary, goal=goal, textgen_config=textgen_config, text_gen=self.text_gen, 204 | library=library) 205 | charts = self.execute( 206 | code_specs=code_specs, 207 | data=self.data, 208 | summary=summary, 209 | library=library, 210 | return_error=return_error, 211 | ) 212 | return charts 213 | 214 | def execute( 215 | self, 216 | code_specs, 217 | data, 218 | summary: Summary, 219 | library: str = "seaborn", 220 | return_error: bool = False, 221 | ): 222 | 223 | if data is None: 224 | root_file_path = os.path.dirname(os.path.abspath(lida.__file__)) 225 | print(root_file_path) 226 | data = read_dataframe( 227 | os.path.join(root_file_path, "files/data", summary.file_name) 228 | ) 229 | 230 | # col_properties = summary.properties 231 | 232 | return self.executor.execute( 233 | code_specs=code_specs, 234 | data=data, 235 | summary=summary, 236 | library=library, 237 | return_error=return_error, 238 | ) 239 | 240 | def edit( 241 | self, 242 | code, 243 | summary: Summary, 244 | instructions: List[str], 245 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 246 | library: str = "seaborn", 247 | return_error: bool = False, 248 | ): 249 | """Edit a visualization code given a set of instructions 250 | 251 | Args: 252 | code (_type_): _description_ 253 | instructions (List[Dict]): A list of instructions 254 | 255 | Returns: 256 | _type_: _description_ 257 | """ 258 | 259 | self.check_textgen(config=textgen_config) 260 | 261 | if isinstance(instructions, str): 262 | instructions = [instructions] 263 | 264 | code_specs = self.vizeditor.generate( 265 | code=code, 266 | summary=summary, 267 | instructions=instructions, 268 | textgen_config=textgen_config, 269 | text_gen=self.text_gen, 270 | library=library, 271 | ) 272 | 273 | charts = self.execute( 274 | code_specs=code_specs, 275 | data=self.data, 276 | summary=summary, 277 | library=library, 278 | return_error=return_error, 279 | ) 280 | return charts 281 | 282 | def repair( 283 | self, 284 | code, 285 | goal: Goal, 286 | summary: Summary, 287 | feedback, 288 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 289 | library: str = "seaborn", 290 | return_error: bool = False, 291 | ): 292 | """ Repair a visulization given some feedback""" 293 | self.check_textgen(config=textgen_config) 294 | code_specs = self.repairer.generate( 295 | code=code, 296 | feedback=feedback, 297 | goal=goal, 298 | summary=summary, 299 | textgen_config=textgen_config, 300 | text_gen=self.text_gen, 301 | library=library, 302 | ) 303 | charts = self.execute( 304 | code_specs=code_specs, 305 | data=self.data, 306 | summary=summary, 307 | library=library, 308 | return_error=return_error, 309 | ) 310 | return charts 311 | 312 | def explain( 313 | self, 314 | code, 315 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 316 | library: str = "seaborn", 317 | ): 318 | """Explain a visualization code given a set of instructions 319 | 320 | Args: 321 | code (_type_): _description_ 322 | instructions (List[Dict]): A list of instructions 323 | 324 | Returns: 325 | _type_: _description_ 326 | """ 327 | self.check_textgen(config=textgen_config) 328 | return self.explainer.generate( 329 | code=code, 330 | textgen_config=textgen_config, 331 | text_gen=self.text_gen, 332 | library=library, 333 | ) 334 | 335 | def evaluate( 336 | self, 337 | code, 338 | goal: Goal, 339 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 340 | library: str = "seaborn", 341 | ): 342 | """Evaluate a visualization code given a goal 343 | 344 | Args: 345 | code (_type_): _description_ 346 | goal (Goal): A visualization goal 347 | 348 | Returns: 349 | _type_: _description_ 350 | """ 351 | 352 | self.check_textgen(config=textgen_config) 353 | 354 | return self.evaluator.generate( 355 | code=code, 356 | goal=goal, 357 | textgen_config=textgen_config, 358 | text_gen=self.text_gen, 359 | library=library, 360 | ) 361 | 362 | def recommend( 363 | self, 364 | code, 365 | summary: Summary, 366 | n=4, 367 | textgen_config: TextGenerationConfig = TextGenerationConfig(), 368 | library: str = "seaborn", 369 | return_error: bool = False, 370 | ): 371 | """Edit a visualization code given a set of instructions 372 | 373 | Args: 374 | code (_type_): _description_ 375 | instructions (List[Dict]): A list of instructions 376 | 377 | Returns: 378 | _type_: _description_ 379 | """ 380 | 381 | self.check_textgen(config=textgen_config) 382 | 383 | code_specs = self.recommender.generate( 384 | code=code, 385 | summary=summary, 386 | n=n, 387 | textgen_config=textgen_config, 388 | text_gen=self.text_gen, 389 | library=library, 390 | ) 391 | charts = self.execute( 392 | code_specs=code_specs, 393 | data=self.data, 394 | summary=summary, 395 | library=library, 396 | return_error=return_error, 397 | ) 398 | return charts 399 | 400 | def infographics(self, visualization: str, n: int = 1, 401 | style_prompt: Union[str, List[str]] = "", 402 | return_pil: bool = False 403 | ): 404 | """ 405 | Generate infographics using the peacasso package. 406 | 407 | Args: 408 | visualization (str): A visualization code 409 | n (int, optional): The number of infographics to generate. Defaults to 1. 410 | style_prompt (Union[str, List[str]], optional): A style prompt or list of style prompts. Defaults to "". 411 | 412 | Raises: 413 | ImportError: If the peacasso package is not installed. 414 | """ 415 | 416 | try: 417 | import peacasso 418 | 419 | except ImportError as exc: 420 | raise ImportError( 421 | 'Please install lida with infographics support. pip install lida[infographics]. You will also need a GPU runtime.' 422 | ) from exc 423 | 424 | from ..components.infographer import Infographer 425 | 426 | if self.infographer is None: 427 | logger.info("Initializing Infographer") 428 | self.infographer = Infographer() 429 | return self.infographer.generate( 430 | visualization=visualization, n=n, style_prompt=style_prompt, return_pil=return_pil) 431 | -------------------------------------------------------------------------------- /lida/components/persona.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from lida.utils import clean_code_snippet 4 | from llmx import TextGenerator 5 | from lida.datamodel import Persona, TextGenerationConfig 6 | 7 | 8 | system_prompt = """You are an experienced data analyst who can take a dataset summary and generate a list of n personas (e.g., ceo or accountant for finance related data, economist for population or gdp related data, doctors for health data, or just users) that might be critical stakeholders in exploring some data and describe rationale for why they are critical. The personas should be prioritized based on their relevance to the data. Think step by step. 9 | 10 | Your response should be perfect JSON in the following format: 11 | ```[{"persona": "persona1", "rationale": "..."},{"persona": "persona1", "rationale": "..."}]``` 12 | """ 13 | 14 | logger = logging.getLogger("lida") 15 | 16 | 17 | class PersonaExplorer(): 18 | """Generate personas given a summary of data""" 19 | 20 | def __init__(self) -> None: 21 | pass 22 | 23 | def generate(self, summary: dict, textgen_config: TextGenerationConfig, 24 | text_gen: TextGenerator, n=5) -> list[Persona]: 25 | """Generate personas given a summary of data""" 26 | 27 | user_prompt = f"""The number of PERSONAs to generate is {n}. Generate {n} personas in the right format given the data summary below,\n . 28 | {summary} \n""" + """ 29 | 30 | . 31 | """ 32 | 33 | messages = [ 34 | {"role": "system", "content": system_prompt}, 35 | {"role": "assistant", "content": user_prompt}, 36 | ] 37 | 38 | result = text_gen.generate(messages=messages, config=textgen_config) 39 | 40 | try: 41 | json_string = clean_code_snippet(result.text[0]["content"]) 42 | result = json.loads(json_string) 43 | # cast each item in the list to a Goal object 44 | if isinstance(result, dict): 45 | result = [result] 46 | result = [Persona(**x) for x in result] 47 | except json.decoder.JSONDecodeError: 48 | logger.info(f"Error decoding JSON: {result.text[0]['content']}") 49 | print(f"Error decoding JSON: {result.text[0]['content']}") 50 | raise ValueError( 51 | "The model did not return a valid JSON object while attempting generate personas. Consider using a larger model or a model with higher max token length.") 52 | return result 53 | -------------------------------------------------------------------------------- /lida/components/scaffold.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict 2 | 3 | from lida.datamodel import Goal 4 | 5 | 6 | # if len(plt.xticks()[0])) > 20 assuming plot is made with plt or 7 | # len(ax.get_xticks()) > 20 assuming plot is made with ax, set a max of 20 8 | # ticks on x axis, ticker.MaxNLocator(20) 9 | 10 | class ChartScaffold(object): 11 | """Return code scaffold for charts in multiple visualization libraries""" 12 | 13 | def __init__( 14 | self, 15 | ) -> None: 16 | 17 | pass 18 | 19 | def get_template(self, goal: Goal, library: str): 20 | 21 | general_instructions = f"If the solution requires a single value (e.g. max, min, median, first, last etc), ALWAYS add a line (axvline or axhline) to the chart, ALWAYS with a legend containing the single value (formatted with 0.2F). If using a where semantic_type=date, YOU MUST APPLY the following transform before using that column i) convert date fields to date types using data[''] = pd.to_datetime(data[], errors='coerce'), ALWAYS use errors='coerce' ii) drop the rows with NaT values data = data[pd.notna(data[])] iii) convert field to right time format for plotting. ALWAYS make sure the x-axis labels are legible (e.g., rotate when needed). Solve the task carefully by completing ONLY the AND section. Given the dataset summary, the plot(data) method should generate a {library} chart ({goal.visualization}) that addresses this goal: {goal.question}. DO NOT WRITE ANY CODE TO LOAD THE DATA. The data is already loaded and available in the variable data." 22 | 23 | matplotlib_instructions = f" {general_instructions} DO NOT include plt.show(). The plot method must return a matplotlib object (plt). Think step by step. \n" 24 | 25 | if library == "matplotlib": 26 | instructions = { 27 | "role": "assistant", 28 | "content": f" {matplotlib_instructions}. Use BaseMap for charts that require a map. "} 29 | template = \ 30 | f""" 31 | import matplotlib.pyplot as plt 32 | import pandas as pd 33 | 34 | # plan - 35 | def plot(data: pd.DataFrame): 36 | # only modify this section 37 | plt.title('{goal.question}', wrap=True) 38 | return plt; 39 | 40 | chart = plot(data) # data already contains the data to be plotted. Always include this line. No additional code beyond this line.""" 41 | elif library == "seaborn": 42 | instructions = { 43 | "role": "assistant", 44 | "content": f"{matplotlib_instructions}. Use BaseMap for charts that require a map. "} 45 | 46 | template = \ 47 | f""" 48 | import seaborn as sns 49 | import pandas as pd 50 | import matplotlib.pyplot as plt 51 | 52 | # solution plan 53 | # i. .. 54 | def plot(data: pd.DataFrame): 55 | 56 | # only modify this section 57 | plt.title('{goal.question}', wrap=True) 58 | return plt; 59 | 60 | chart = plot(data) # data already contains the data to be plotted. Always include this line. No additional code beyond this line.""" 61 | 62 | elif library == "ggplot": 63 | instructions = { 64 | "role": "assistant", 65 | "content": f"{general_instructions}. The plot method must return a ggplot object (chart)`. Think step by step.p. \n", 66 | } 67 | 68 | template = \ 69 | f""" 70 | import plotnine as p9 71 | 72 | def plot(data: pd.DataFrame): 73 | chart = 74 | 75 | return chart; 76 | 77 | chart = plot(data) # data already contains the data to be plotted. Always include this line. No additional code beyond this line.. """ 78 | 79 | elif library == "altair": 80 | instructions = { 81 | "role": "system", 82 | "content": f"{general_instructions}. Always add a type that is BASED on semantic_type to each field such as :Q, :O, :N, :T, :G. Use :T if semantic_type is year or date. The plot method must return an altair object (chart)`. Think step by step. \n", 83 | } 84 | template = \ 85 | """ 86 | import altair as alt 87 | 88 | def plot(data: pd.DataFrame): 89 | # only modify this section 90 | return chart 91 | chart = plot(data) # data already contains the data to be plotted. Always include this line. No additional code beyond this line.. 92 | """ 93 | 94 | elif library == "plotly": 95 | instructions = { 96 | "role": "system", 97 | "content": f"{general_instructions} If calculating metrics such as mean, median, mode, etc. ALWAYS use the option 'numeric_only=True' when applicable and available, AVOID visualizations that require nbformat library. DO NOT inlcude fig.show(). The plot method must return an plotly figure object (fig)`. Think step by step. \n.", 98 | } 99 | template = \ 100 | """ 101 | import plotly.express as px 102 | 103 | def plot(data: pd.DataFrame): 104 | fig = # only modify this section 105 | 106 | return chart 107 | chart = plot(data) # variable data already contains the data to be plotted and should not be loaded again. Always include this line. No additional code beyond this line.. 108 | """ 109 | 110 | else: 111 | raise ValueError( 112 | "Unsupported library. Choose from 'matplotlib', 'seaborn', 'plotly', 'bokeh', 'ggplot', 'altair'." 113 | ) 114 | 115 | return template, instructions 116 | -------------------------------------------------------------------------------- /lida/components/summarizer.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from typing import Union 4 | import pandas as pd 5 | from lida.utils import clean_code_snippet, read_dataframe 6 | from lida.datamodel import TextGenerationConfig 7 | from llmx import TextGenerator 8 | import warnings 9 | 10 | system_prompt = """ 11 | You are an experienced data analyst that can annotate datasets. Your instructions are as follows: 12 | i) ALWAYS generate the name of the dataset and the dataset_description 13 | ii) ALWAYS generate a field description. 14 | iii.) ALWAYS generate a semantic_type (a single word) for each field given its values e.g. company, city, number, supplier, location, gender, longitude, latitude, url, ip address, zip code, email, etc 15 | You must return an updated JSON dictionary without any preamble or explanation. 16 | """ 17 | 18 | logger = logging.getLogger("lida") 19 | 20 | 21 | class Summarizer(): 22 | def __init__(self) -> None: 23 | self.summary = None 24 | 25 | def check_type(self, dtype: str, value): 26 | """Cast value to right type to ensure it is JSON serializable""" 27 | if "float" in str(dtype): 28 | return float(value) 29 | elif "int" in str(dtype): 30 | return int(value) 31 | else: 32 | return value 33 | 34 | def get_column_properties(self, df: pd.DataFrame, n_samples: int = 3) -> list[dict]: 35 | """Get properties of each column in a pandas DataFrame""" 36 | properties_list = [] 37 | for column in df.columns: 38 | dtype = df[column].dtype 39 | properties = {} 40 | if dtype in [int, float, complex]: 41 | properties["dtype"] = "number" 42 | properties["std"] = self.check_type(dtype, df[column].std()) 43 | properties["min"] = self.check_type(dtype, df[column].min()) 44 | properties["max"] = self.check_type(dtype, df[column].max()) 45 | 46 | elif dtype == bool: 47 | properties["dtype"] = "boolean" 48 | elif dtype == object: 49 | # Check if the string column can be cast to a valid datetime 50 | try: 51 | with warnings.catch_warnings(): 52 | warnings.simplefilter("ignore") 53 | pd.to_datetime(df[column], errors='raise') 54 | properties["dtype"] = "date" 55 | except ValueError: 56 | # Check if the string column has a limited number of values 57 | if df[column].nunique() / len(df[column]) < 0.5: 58 | properties["dtype"] = "category" 59 | else: 60 | properties["dtype"] = "string" 61 | elif pd.api.types.is_categorical_dtype(df[column]): 62 | properties["dtype"] = "category" 63 | elif pd.api.types.is_datetime64_any_dtype(df[column]): 64 | properties["dtype"] = "date" 65 | else: 66 | properties["dtype"] = str(dtype) 67 | 68 | # add min max if dtype is date 69 | if properties["dtype"] == "date": 70 | try: 71 | properties["min"] = df[column].min() 72 | properties["max"] = df[column].max() 73 | except TypeError: 74 | cast_date_col = pd.to_datetime(df[column], errors='coerce') 75 | properties["min"] = cast_date_col.min() 76 | properties["max"] = cast_date_col.max() 77 | # Add additional properties to the output dictionary 78 | nunique = df[column].nunique() 79 | if "samples" not in properties: 80 | non_null_values = df[column][df[column].notnull()].unique() 81 | n_samples = min(n_samples, len(non_null_values)) 82 | samples = pd.Series(non_null_values).sample( 83 | n_samples, random_state=42).tolist() 84 | properties["samples"] = samples 85 | properties["num_unique_values"] = nunique 86 | properties["semantic_type"] = "" 87 | properties["description"] = "" 88 | properties_list.append( 89 | {"column": column, "properties": properties}) 90 | 91 | return properties_list 92 | 93 | def enrich(self, base_summary: dict, text_gen: TextGenerator, 94 | textgen_config: TextGenerationConfig) -> dict: 95 | """Enrich the data summary with descriptions""" 96 | logger.info(f"Enriching the data summary with descriptions") 97 | 98 | messages = [ 99 | {"role": "system", "content": system_prompt}, 100 | {"role": "assistant", "content": f""" 101 | Annotate the dictionary below. Only return a JSON object. 102 | {base_summary} 103 | """}, 104 | ] 105 | 106 | response = text_gen.generate(messages=messages, config=textgen_config) 107 | enriched_summary = base_summary 108 | try: 109 | json_string = clean_code_snippet(response.text[0]["content"]) 110 | enriched_summary = json.loads(json_string) 111 | except json.decoder.JSONDecodeError: 112 | error_msg = f"The model did not return a valid JSON object while attempting to generate an enriched data summary. Consider using a default summary or a larger model with higher max token length. | {response.text[0]['content']}" 113 | logger.info(error_msg) 114 | print(response.text[0]["content"]) 115 | raise ValueError(error_msg + "" + response.usage) 116 | return enriched_summary 117 | 118 | def summarize( 119 | self, data: Union[pd.DataFrame, str], 120 | text_gen: TextGenerator, file_name="", n_samples: int = 3, 121 | textgen_config=TextGenerationConfig(n=1), 122 | summary_method: str = "default", encoding: str = 'utf-8') -> dict: 123 | """Summarize data from a pandas DataFrame or a file location""" 124 | 125 | # if data is a file path, read it into a pandas DataFrame, set file_name to the file name 126 | if isinstance(data, str): 127 | file_name = data.split("/")[-1] 128 | # modified to include encoding 129 | data = read_dataframe(data, encoding=encoding) 130 | data_properties = self.get_column_properties(data, n_samples) 131 | 132 | # default single stage summary construction 133 | base_summary = { 134 | "name": file_name, 135 | "file_name": file_name, 136 | "dataset_description": "", 137 | "fields": data_properties, 138 | } 139 | 140 | data_summary = base_summary 141 | 142 | if summary_method == "llm": 143 | # two stage summarization with llm enrichment 144 | data_summary = self.enrich( 145 | base_summary, 146 | text_gen=text_gen, 147 | textgen_config=textgen_config) 148 | elif summary_method == "columns": 149 | # no enrichment, only column names 150 | data_summary = { 151 | "name": file_name, 152 | "file_name": file_name, 153 | "dataset_description": "" 154 | } 155 | 156 | data_summary["field_names"] = data.columns.tolist() 157 | data_summary["file_name"] = file_name 158 | 159 | return data_summary 160 | -------------------------------------------------------------------------------- /lida/components/viz/__init__.py: -------------------------------------------------------------------------------- 1 | from .vizeditor import * 2 | from .vizexplainer import * 3 | from .vizgenerator import * 4 | from .vizevaluator import * 5 | from .vizrepairer import * 6 | from .vizrecommender import * 7 | -------------------------------------------------------------------------------- /lida/components/viz/vizeditor.py: -------------------------------------------------------------------------------- 1 | from llmx import TextGenerator, TextGenerationConfig, TextGenerationResponse 2 | from ..scaffold import ChartScaffold 3 | from lida.datamodel import Goal, Summary 4 | 5 | 6 | system_prompt = """ 7 | You are a high skilled visualization assistant that can modify a provided visualization code based on a set of instructions. You MUST return a full program. DO NOT include any preamble text. Do not include explanations or prose. 8 | """ 9 | 10 | 11 | class VizEditor(object): 12 | """Generate visualizations from prompt""" 13 | 14 | def __init__( 15 | self, 16 | ) -> None: 17 | self.scaffold = ChartScaffold() 18 | 19 | def generate( 20 | self, code: str, summary: Summary, instructions: list[str], 21 | textgen_config: TextGenerationConfig, text_gen: TextGenerator, library='altair'): 22 | """Edit a code spec based on instructions""" 23 | 24 | instruction_string = "" 25 | for i, instruction in enumerate(instructions): 26 | instruction_string += f"{i+1}. {instruction} \n" 27 | 28 | library_template, library_instructions = self.scaffold.get_template(Goal( 29 | index=0, 30 | question="", 31 | visualization="", 32 | rationale=""), library) 33 | # print("instructions", instructions) 34 | 35 | messages = [ 36 | { 37 | "role": "system", "content": system_prompt}, { 38 | "role": "system", "content": f"The dataset summary is : \n\n {summary} \n\n"}, { 39 | "role": "system", "content": f"The modifications you make MUST BE CORRECT and based on the '{library}' library and also follow these instructions \n\n{library_instructions} \n\n. The resulting code MUST use the following template \n\n {library_template} \n\n "}, { 40 | "role": "user", "content": f"ALL ADDITIONAL LIBRARIES USED MUST BE IMPORTED.\n The code to be modified is: \n\n{code} \n\n. YOU MUST THINK STEP BY STEP, AND CAREFULLY MODIFY ONLY the content of the plot(..) method TO MEET EACH OF THE FOLLOWING INSTRUCTIONS: \n\n {instruction_string} \n\n. The completed modified code THAT FOLLOWS THE TEMPLATE above is. \n"}] 41 | 42 | completions: TextGenerationResponse = text_gen.generate( 43 | messages=messages, config=textgen_config) 44 | return [x['content'] for x in completions.text] 45 | -------------------------------------------------------------------------------- /lida/components/viz/vizevaluator.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from ...utils import clean_code_snippet 4 | from llmx import TextGenerator, TextGenerationConfig, TextGenerationResponse 5 | 6 | from lida.datamodel import Goal 7 | 8 | system_prompt = """ 9 | You are a helpful assistant highly skilled in evaluating the quality of a given visualization code by providing a score from 1 (bad) - 10 (good) while providing clear rationale. YOU MUST CONSIDER VISUALIZATION BEST PRACTICES for each evaluation. Specifically, you can carefully evaluate the code across the following dimensions 10 | - bugs (bugs): are there bugs, logic errors, syntax error or typos? Are there any reasons why the code may fail to compile? How should it be fixed? If ANY bug exists, the bug score MUST be less than 5. 11 | - Data transformation (transformation): Is the data transformed appropriately for the visualization type? E.g., is the dataset appropriated filtered, aggregated, or grouped if needed? 12 | - Goal compliance (compliance): how well the code meets the specified visualization goals? 13 | - Visualization type (type): CONSIDERING BEST PRACTICES, is the visualization type appropriate for the data and intent? Is there a visualization type that would be more effective in conveying insights? If a different visualization type is more appropriate, the score MUST be less than 5. 14 | - Data encoding (encoding): Is the data encoded appropriately for the visualization type? 15 | - aesthetics (aesthetics): Are the aesthetics of the visualization appropriate for the visualization type and the data? 16 | 17 | You must provide a score for each of the above dimensions. Assume that data in chart = plot(data) contains a valid dataframe for the dataset. The `plot` function returns a chart (e.g., matplotlib, seaborn etc object). 18 | 19 | Your OUTPUT MUST BE A VALID JSON LIST OF OBJECTS in the format: 20 | 21 | ```[ 22 | { "dimension": "bugs", "score": x , "rationale": " .."}, { "dimension": "transformation", "score": x, "rationale": " .."}, { "dimension": "compliance", "score": x, "rationale": " .."},{ "dimension": "type", "score": x, "rationale": " .."}, { "dimension": "encoding", "score": x, "rationale": " .."}, { "dimension": "aesthetics", "score": x, "rationale": " .."} 23 | ] 24 | ``` 25 | """ 26 | 27 | 28 | class VizEvaluator(object): 29 | """Generate visualizations Explanations given some code""" 30 | 31 | def __init__( 32 | self, 33 | ) -> None: 34 | pass 35 | 36 | def generate(self, code: str, goal: Goal, 37 | textgen_config: TextGenerationConfig, text_gen: TextGenerator, library='altair'): 38 | """Generate a visualization explanation given some code""" 39 | 40 | messages = [ 41 | {"role": "system", "content": system_prompt}, 42 | {"role": "assistant", 43 | "content": f"Generate an evaluation given the goal and code below in {library}. The specified goal is \n\n {goal.question} \n\n and the visualization code is \n\n {code} \n\n. Now, evaluate the code based on the 6 dimensions above. \n. THE SCORE YOU ASSIGN MUST BE MEANINGFUL AND BACKED BY CLEAR RATIONALE. A SCORE OF 1 IS POOR AND A SCORE OF 10 IS VERY GOOD. The structured evaluation is below ."}, 44 | ] 45 | 46 | # print(messages) 47 | completions: TextGenerationResponse = text_gen.generate( 48 | messages=messages, config=textgen_config) 49 | 50 | completions = [clean_code_snippet(x['content']) for x in completions.text] 51 | evaluations = [] 52 | for completion in completions: 53 | try: 54 | evaluation = json.loads(completion) 55 | evaluations.append(evaluation) 56 | except Exception as json_error: 57 | print("Error parsing evaluation data", completion, str(json_error)) 58 | return evaluations 59 | -------------------------------------------------------------------------------- /lida/components/viz/vizexplainer.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from lida.utils import clean_code_snippet 4 | from llmx import TextGenerator, TextGenerationConfig, TextGenerationResponse 5 | from ..scaffold import ChartScaffold 6 | 7 | 8 | system_prompt = """ 9 | You are a helpful assistant highly skilled in providing helpful, structured explanations of visualization of the plot(data: pd.DataFrame) method in the provided code. You divide the code into sections and provide a description of each section and an explanation. The first section should be named "accessibility" and describe the physical appearance of the chart (colors, chart type etc), the goal of the chart, as well the main insights from the chart. 10 | You can explain code across the following 3 dimensions: 11 | 1. accessibility: the physical appearance of the chart (colors, chart type etc), the goal of the chart, as well the main insights from the chart. 12 | 2. transformation: This should describe the section of the code that applies any kind of data transformation (filtering, aggregation, grouping, null value handling etc) 13 | 3. visualization: step by step description of the code that creates or modifies the presented visualization. 14 | 15 | """ 16 | 17 | format_instructions = """ 18 | Your output MUST be perfect JSON in THE FORM OF A VALID LIST of JSON OBJECTS WITH PROPERLY ESCAPED SPECIAL CHARACTERS e.g., 19 | 20 | ```[ 21 | {"section": "accessibility", "code": "None", "explanation": ".."} , {"section": "transformation", "code": "..", "explanation": ".."} , {"section": "visualization", "code": "..", "explanation": ".."} 22 | ] ``` 23 | 24 | The code part of the dictionary must come from the supplied code and should cover the explanation. The explanation part of the dictionary must be a string. The section part of the dictionary must be one of "accessibility", "transformation", "visualization" with no repetition. THE LIST MUST HAVE EXACTLY 3 JSON OBJECTS [{}, {}, {}]. THE GENERATED JSON MUST BE A LIST IE START AND END WITH A SQUARE BRACKET. 25 | """ 26 | 27 | 28 | class VizExplainer(object): 29 | """Generate visualizations Explanations given some code""" 30 | 31 | def __init__( 32 | self, 33 | ) -> None: 34 | self.scaffold = ChartScaffold() 35 | 36 | def generate( 37 | self, code: str, 38 | textgen_config: TextGenerationConfig, text_gen: TextGenerator, library='seaborn'): 39 | """Generate a visualization explanation given some code""" 40 | 41 | messages = [ 42 | {"role": "system", "content": system_prompt}, 43 | {"role": "assistant", "content": f"The code to be explained is {code}.\n=======\n"}, 44 | {"role": "user", 45 | "content": f"{format_instructions}. \n\n. The structured explanation for the code above is \n\n"} 46 | ] 47 | 48 | completions: TextGenerationResponse = text_gen.generate( 49 | messages=messages, config=textgen_config) 50 | 51 | completions = [clean_code_snippet(x['content']) for x in completions.text] 52 | explanations = [] 53 | 54 | for completion in completions: 55 | try: 56 | exp = json.loads(completion) 57 | explanations.append(exp) 58 | except Exception as e: 59 | print("Error parsing completion", completion, str(e)) 60 | return explanations 61 | -------------------------------------------------------------------------------- /lida/components/viz/vizgenerator.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict 2 | from typing import Dict 3 | from llmx import TextGenerator, TextGenerationConfig, TextGenerationResponse 4 | 5 | from ..scaffold import ChartScaffold 6 | from lida.datamodel import Goal 7 | 8 | 9 | system_prompt = """ 10 | You are a helpful assistant highly skilled in writing PERFECT code for visualizations. Given some code template, you complete the template to generate a visualization given the dataset and the goal described. The code you write MUST FOLLOW VISUALIZATION BEST PRACTICES ie. meet the specified goal, apply the right transformation, use the right visualization type, use the right data encoding, and use the right aesthetics (e.g., ensure axis are legible). The transformations you apply MUST be correct and the fields you use MUST be correct. The visualization CODE MUST BE CORRECT and MUST NOT CONTAIN ANY SYNTAX OR LOGIC ERRORS (e.g., it must consider the field types and use them correctly). You MUST first generate a brief plan for how you would solve the task e.g. what transformations you would apply e.g. if you need to construct a new column, what fields you would use, what visualization type you would use, what aesthetics you would use, etc. . 11 | """ 12 | 13 | 14 | class VizGenerator(object): 15 | """Generate visualizations from prompt""" 16 | 17 | def __init__( 18 | self 19 | ) -> None: 20 | 21 | self.scaffold = ChartScaffold() 22 | 23 | def generate(self, summary: Dict, goal: Goal, 24 | textgen_config: TextGenerationConfig, text_gen: TextGenerator, library='altair'): 25 | """Generate visualization code given a summary and a goal""" 26 | 27 | library_template, library_instructions = self.scaffold.get_template(goal, library) 28 | messages = [ 29 | {"role": "system", "content": system_prompt}, 30 | {"role": "system", "content": f"The dataset summary is : {summary} \n\n"}, 31 | library_instructions, 32 | {"role": "user", 33 | "content": 34 | f"Always add a legend with various colors where appropriate. The visualization code MUST only use data fields that exist in the dataset (field_names) or fields that are transformations based on existing field_names). Only use variables that have been defined in the code or are in the dataset summary. You MUST return a FULL PYTHON PROGRAM ENCLOSED IN BACKTICKS ``` that starts with an import statement. DO NOT add any explanation. \n\n THE GENERATED CODE SOLUTION SHOULD BE CREATED BY MODIFYING THE SPECIFIED PARTS OF THE TEMPLATE BELOW \n\n {library_template} \n\n.The FINAL COMPLETED CODE BASED ON THE TEMPLATE above is ... \n\n"}] 35 | 36 | completions: TextGenerationResponse = text_gen.generate( 37 | messages=messages, config=textgen_config) 38 | response = [x['content'] for x in completions.text] 39 | 40 | return response 41 | -------------------------------------------------------------------------------- /lida/components/viz/vizrecommender.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from lida.utils import clean_code_snippet 4 | from ..scaffold import ChartScaffold 5 | from llmx import TextGenerator, TextGenerationConfig, TextGenerationResponse 6 | # from lida.modules.scaffold import ChartScaffold 7 | from lida.datamodel import Goal, Summary 8 | 9 | 10 | system_prompt = """ 11 | 12 | You are a helpful assistant highly skilled in recommending a DIVERSE set of visualization code. Your input is an example visualization code, a summary of a dataset and an example visualization goal that the user has already seen. Given this input, your task is to recommend additional visualizations that a user may be interested. Your recommendation may consider different types of valid data aggregations, chart types, clearer ways of displaying information and uses different variables from the data summary. THE CODE YOU GENERATE MUST BE CORRECT (follow the language syntax and syntax of the visualization grammar) AND FOLLOW VISUALIZATION BEST PRACTICES. 13 | 14 | Your output MUST be a n code snippets separated by ******* (5 asterisks). Each snippet MUST BE AN independent code snippet (with one plot method) similar to the example code. For example 15 | 16 | ```python 17 | # code snippet 1 18 | import ... 19 | .... 20 | ``` 21 | ***** 22 | 23 | ```python 24 | # code snippet 2 25 | import ... 26 | .... 27 | ``` 28 | 29 | ```python 30 | # code snippet n 31 | import ... 32 | .... 33 | ``` 34 | 35 | 36 | """ 37 | 38 | logger = logging.getLogger("lida") 39 | 40 | 41 | class VizRecommender(object): 42 | """Generate visualizations from prompt""" 43 | 44 | def __init__( 45 | self, 46 | ) -> None: 47 | self.scaffold = ChartScaffold() 48 | 49 | def generate( 50 | self, code: str, summary: Summary, 51 | textgen_config: TextGenerationConfig, 52 | text_gen: TextGenerator, 53 | n=3, 54 | library='seaborn'): 55 | """Recommend a code spec based on existing visualization""" 56 | 57 | library_template, library_instructions = self.scaffold.get_template(Goal( 58 | index=0, 59 | question="", 60 | visualization="", 61 | rationale=""), library) 62 | 63 | structure_instruction = f""" 64 | EACH CODE SNIPPET MUST BE A FULL PROGRAM (IT MUST IMPORT ALL THE LIBRARIES THAT ARE USED AND MUST CONTAIN plot(data) method). IT MUST FOLLOW THE STRUCTURE BELOW AND ONLY MODIFY THE INDICATED SECTIONS. \n\n {library_template} \n\n. 65 | """ 66 | 67 | messages = [ 68 | {"role": "system", "content": system_prompt}, 69 | {"role": "system", "content": structure_instruction}, 70 | {"role": "system", "content": f"The dataset summary is : \n\n {summary} \n\n"}, 71 | {"role": "system", 72 | "content": 73 | f"An example visualization code is: \n\n ```{code}``` \n\n. You MUST use only the {library} library. \n"}, 74 | {"role": "user", "content": f"Recommend {n} (n=({n})) visualizations in the format specified. \n."}] 75 | 76 | textgen_config.messages = messages 77 | result: TextGenerationResponse = text_gen.generate( 78 | messages=messages, config=textgen_config) 79 | output = [] 80 | snippets = result.text[0]["content"].split("*****") 81 | for snippet in snippets: 82 | cleaned_snippet = clean_code_snippet(snippet) 83 | if len(cleaned_snippet) > 4: 84 | output.append(cleaned_snippet) 85 | 86 | return output 87 | -------------------------------------------------------------------------------- /lida/components/viz/vizrepairer.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Union 2 | from llmx import TextGenerator, TextGenerationConfig, TextGenerationResponse 3 | 4 | from ..scaffold import ChartScaffold 5 | from lida.datamodel import Goal, Summary 6 | 7 | system_prompt = """ 8 | You are a helpful assistant highly skilled in revising visualization code to improve the quality of the code and visualization based on feedback. Assume that data in plot(data) contains a valid dataframe. 9 | You MUST return a full program. DO NOT include any preamble text. Do not include explanations or prose. 10 | """ 11 | 12 | 13 | class VizRepairer(object): 14 | """Fix visualization code based on feedback""" 15 | 16 | def __init__( 17 | self, 18 | ) -> None: 19 | self.scaffold = ChartScaffold() 20 | 21 | def generate( 22 | self, code: str, feedback: Union[str, Dict, List[Dict]], 23 | goal: Goal, summary: Summary, textgen_config: TextGenerationConfig, 24 | text_gen: TextGenerator, library='altair',): 25 | """Fix a code spec based on feedback""" 26 | library_template, library_instructions = self.scaffold.get_template(Goal( 27 | index=0, 28 | question="", 29 | visualization="", 30 | rationale=""), library) 31 | messages = [ 32 | {"role": "system", "content": system_prompt}, 33 | {"role": "system", "content": f"The dataset summary is : {summary}. \n . The original goal was: {goal}."}, 34 | {"role": "system", 35 | "content": 36 | f"You MUST use only the {library}. The resulting code MUST use the following template {library_template}. Only use variables that have been defined in the code or are in the dataset summary"}, 37 | {"role": "user", "content": f"The existing code to be fixed is: {code}. \n Fix the code above to address the feedback: {feedback}. ONLY apply feedback that are CORRECT."}] 38 | 39 | # library with the following instructions {library_instructions} 40 | 41 | completions: TextGenerationResponse = text_gen.generate( 42 | messages=messages, config=textgen_config) 43 | return [x['content'] for x in completions.text] 44 | -------------------------------------------------------------------------------- /lida/datamodel.py: -------------------------------------------------------------------------------- 1 | # from dataclasses import dataclass 2 | import base64 3 | from dataclasses import field 4 | from typing import Any, Dict, List, Optional, Union 5 | 6 | from llmx import TextGenerationConfig 7 | from pydantic.dataclasses import dataclass 8 | 9 | 10 | @dataclass 11 | class VizGeneratorConfig: 12 | """Configuration for a visualization generation""" 13 | 14 | hypothesis: str 15 | data_summary: Optional[str] = "" 16 | data_filename: Optional[str] = "cars.csv" 17 | 18 | 19 | @dataclass 20 | class CompletionResult: 21 | text: str 22 | logprobs: Optional[List[float]] 23 | prompt: str 24 | suffix: str 25 | 26 | 27 | @dataclass 28 | class UploadUrl: 29 | """Response from a text generation""" 30 | 31 | url: str 32 | 33 | 34 | @dataclass 35 | class Goal: 36 | """A visualization goal""" 37 | question: str 38 | visualization: str 39 | rationale: str 40 | index: Optional[int] = 0 41 | 42 | def _repr_markdown_(self): 43 | return f""" 44 | ### Goal {self.index} 45 | --- 46 | **Question:** {self.question} 47 | 48 | **Visualization:** `{self.visualization}` 49 | 50 | **Rationale:** {self.rationale} 51 | """ 52 | 53 | 54 | @dataclass 55 | class Summary: 56 | """A summary of a dataset""" 57 | 58 | name: str 59 | file_name: str 60 | dataset_description: str 61 | field_names: List[Any] 62 | fields: Optional[List[Any]] = None 63 | 64 | def _repr_markdown_(self): 65 | field_lines = "\n".join([f"- **{name}:** {field}" for name, 66 | field in zip(self.field_names, self.fields)]) 67 | return f""" 68 | ## Dataset Summary 69 | 70 | --- 71 | 72 | **Name:** {self.name} 73 | 74 | **File Name:** {self.file_name} 75 | 76 | **Dataset Description:** 77 | 78 | {self.dataset_description} 79 | 80 | **Fields:** 81 | 82 | {field_lines} 83 | """ 84 | 85 | 86 | @dataclass 87 | class Persona: 88 | """A persona""" 89 | persona: str 90 | rationale: str 91 | 92 | def _repr_markdown_(self): 93 | return f""" 94 | ### Persona 95 | --- 96 | 97 | **Persona:** {self.persona} 98 | 99 | **Rationale:** {self.rationale} 100 | """ 101 | 102 | 103 | @dataclass 104 | class GoalWebRequest: 105 | """A Goal Web Request""" 106 | 107 | summary: Summary 108 | textgen_config: Optional[TextGenerationConfig] = field( 109 | default_factory=TextGenerationConfig 110 | ) 111 | n: int = 5 112 | 113 | 114 | @dataclass 115 | class VisualizeWebRequest: 116 | """A Visualize Web Request""" 117 | 118 | summary: Summary 119 | goal: Goal 120 | library: str = "seaborn" 121 | textgen_config: Optional[TextGenerationConfig] = field( 122 | default_factory=TextGenerationConfig 123 | ) 124 | 125 | 126 | @dataclass 127 | class VisualizeRecommendRequest: 128 | """A Visualize Recommendation Request""" 129 | 130 | summary: Summary 131 | code: str 132 | library: str = "seaborn" 133 | textgen_config: Optional[TextGenerationConfig] = field( 134 | default_factory=TextGenerationConfig 135 | ) 136 | 137 | 138 | @dataclass 139 | class VisualizeEditWebRequest: 140 | """A Visualize Edit Web Request""" 141 | 142 | summary: Summary 143 | code: str 144 | instructions: Union[str, List[str]] 145 | library: str = "seaborn" 146 | textgen_config: Optional[TextGenerationConfig] = field( 147 | default_factory=TextGenerationConfig 148 | ) 149 | 150 | 151 | @dataclass 152 | class VisualizeRepairWebRequest: 153 | """A Visualize Repair Web Request""" 154 | 155 | feedback: Optional[Union[str, List[str], List[Dict]]] 156 | code: str 157 | goal: Goal 158 | summary: Summary 159 | library: str = "seaborn" 160 | textgen_config: Optional[TextGenerationConfig] = field( 161 | default_factory=TextGenerationConfig 162 | ) 163 | 164 | 165 | @dataclass 166 | class VisualizeExplainWebRequest: 167 | """A Visualize Explain Web Request""" 168 | 169 | code: str 170 | library: str = "seaborn" 171 | textgen_config: Optional[TextGenerationConfig] = field( 172 | default_factory=TextGenerationConfig 173 | ) 174 | 175 | 176 | @dataclass 177 | class VisualizeEvalWebRequest: 178 | """A Visualize Eval Web Request""" 179 | 180 | code: str 181 | goal: Goal 182 | library: str = "seaborn" 183 | textgen_config: Optional[TextGenerationConfig] = field( 184 | default_factory=TextGenerationConfig 185 | ) 186 | 187 | 188 | @dataclass 189 | class ChartExecutorResponse: 190 | """Response from a visualization execution""" 191 | 192 | spec: Optional[Union[str, Dict]] # interactive specification e.g. vegalite 193 | status: bool # True if successful 194 | raster: Optional[str] # base64 encoded image 195 | code: str # code used to generate the visualization 196 | library: str # library used to generate the visualization 197 | error: Optional[Dict] = None # error message if status is False 198 | 199 | def _repr_mimebundle_(self, include=None, exclude=None): 200 | bundle = {"text/plain": self.code} 201 | if self.raster is not None: 202 | bundle["image/png"] = self.raster 203 | if self.spec is not None: 204 | bundle["application/vnd.vegalite.v5+json"] = self.spec 205 | 206 | return bundle 207 | 208 | def savefig(self, path): 209 | """Save the raster image to a specified path if it exists""" 210 | if self.raster: 211 | with open(path, 'wb') as f: 212 | f.write(base64.b64decode(self.raster)) 213 | else: 214 | raise FileNotFoundError("No raster image to save") 215 | 216 | 217 | @dataclass 218 | class SummaryUrlRequest: 219 | """A request for generating a summary with file url""" 220 | 221 | url: str 222 | textgen_config: Optional[TextGenerationConfig] = field( 223 | default_factory=TextGenerationConfig 224 | ) 225 | 226 | 227 | @dataclass 228 | class InfographicsRequest: 229 | """A request for infographics generation""" 230 | 231 | visualization: str 232 | n: int = 1 233 | style_prompt: Union[str, List[str]] = "" 234 | # return_pil: bool = False 235 | -------------------------------------------------------------------------------- /lida/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import logging 4 | from typing import Any, List, Tuple, Union 5 | import os 6 | import io 7 | import numpy as np 8 | import pandas as pd 9 | import re 10 | import matplotlib.pyplot as plt 11 | import tiktoken 12 | from diskcache import Cache 13 | import hashlib 14 | import io 15 | 16 | logger = logging.getLogger("lida") 17 | 18 | 19 | def get_dirs(path: str) -> List[str]: 20 | return next(os.walk(path))[1] 21 | 22 | 23 | def clean_column_name(col_name: str) -> str: 24 | """ 25 | Clean a single column name by replacing special characters and spaces with underscores. 26 | 27 | :param col_name: The name of the column to be cleaned. 28 | :return: A sanitized string valid as a column name. 29 | """ 30 | return re.sub(r'[^0-9a-zA-Z_]', '_', col_name) 31 | 32 | 33 | def clean_column_names(df: pd.DataFrame) -> pd.DataFrame: 34 | """ 35 | Clean all column names in the given DataFrame. 36 | 37 | :param df: The DataFrame with possibly dirty column names. 38 | :return: A copy of the DataFrame with clean column names. 39 | """ 40 | cleaned_df = df.copy() 41 | cleaned_df.columns = [clean_column_name(col) for col in cleaned_df.columns] 42 | return cleaned_df 43 | 44 | 45 | def read_dataframe(file_location: str, encoding: str = 'utf-8') -> pd.DataFrame: 46 | """ 47 | Read a dataframe from a given file location and clean its column names. 48 | It also samples down to 4500 rows if the data exceeds that limit. 49 | 50 | :param file_location: The path to the file containing the data. 51 | :param encoding: Encoding to use for the file reading. 52 | :return: A cleaned DataFrame. 53 | """ 54 | file_extension = file_location.split('.')[-1] 55 | 56 | read_funcs = { 57 | 'json': lambda: pd.read_json(file_location, orient='records', encoding=encoding), 58 | 'csv': lambda: pd.read_csv(file_location, encoding=encoding), 59 | 'xls': lambda: pd.read_excel(file_location, encoding=encoding), 60 | 'xlsx': lambda: pd.read_excel(file_location, encoding=encoding), 61 | 'parquet': pd.read_parquet, 62 | 'feather': pd.read_feather, 63 | 'tsv': lambda: pd.read_csv(file_location, sep="\t", encoding=encoding) 64 | } 65 | 66 | if file_extension not in read_funcs: 67 | raise ValueError('Unsupported file type') 68 | 69 | try: 70 | df = read_funcs[file_extension]() 71 | except Exception as e: 72 | logger.error(f"Failed to read file: {file_location}. Error: {e}") 73 | raise 74 | 75 | # Clean column names 76 | cleaned_df = clean_column_names(df) 77 | 78 | # Sample down to 4500 rows if necessary 79 | if len(cleaned_df) > 4500: 80 | logger.info( 81 | "Dataframe has more than 4500 rows. We will sample 4500 rows.") 82 | cleaned_df = cleaned_df.sample(4500) 83 | 84 | if cleaned_df.columns.tolist() != df.columns.tolist(): 85 | write_funcs = { 86 | 'csv': lambda: cleaned_df.to_csv(file_location, index=False, encoding=encoding), 87 | 'xls': lambda: cleaned_df.to_excel(file_location, index=False), 88 | 'xlsx': lambda: cleaned_df.to_excel(file_location, index=False), 89 | 'parquet': lambda: cleaned_df.to_parquet(file_location, index=False), 90 | 'feather': lambda: cleaned_df.to_feather(file_location, index=False), 91 | 'json': lambda: cleaned_df.to_json(file_location, orient='records', index=False, default_handler=str), 92 | 'tsv': lambda: cleaned_df.to_csv(file_location, index=False, sep='\t', encoding=encoding) 93 | } 94 | 95 | if file_extension not in write_funcs: 96 | raise ValueError('Unsupported file type') 97 | 98 | try: 99 | write_funcs[file_extension]() 100 | except Exception as e: 101 | logger.error(f"Failed to write file: {file_location}. Error: {e}") 102 | raise 103 | 104 | return cleaned_df 105 | 106 | 107 | def file_to_df(file_location: str): 108 | """ Get summary of data from file location """ 109 | file_name = file_location.split("/")[-1] 110 | df = None 111 | if "csv" in file_name: 112 | df = pd.read_csv(file_location) 113 | elif "xlsx" in file_name: 114 | df = pd.read_excel(file_location) 115 | elif "json" in file_name: 116 | df = pd.read_json(file_location, orient="records") 117 | elif "parquet" in file_name: 118 | df = pd.read_parquet(file_location) 119 | elif "feather" in file_name: 120 | df = pd.read_feather(file_location) 121 | 122 | return df 123 | 124 | 125 | def plot_raster(rasters: Union[str, List[str]], figsize: Tuple[int, int] = (10, 10)): 126 | """ 127 | Plot a series of base64-encoded raster images in a horizontal layout. 128 | 129 | Args: 130 | rasters: A single base64 string or a list of base64-encoded strings representing the images. 131 | figsize: A tuple indicating the size of the figure to display. 132 | """ 133 | plt.figure(figsize=figsize) 134 | 135 | if isinstance(rasters, str): 136 | rasters = [rasters] 137 | 138 | images = [] 139 | 140 | # Find the max height for resizing 141 | max_height = 0 142 | for raster in rasters: 143 | decoded_image = base64.b64decode(raster) 144 | image = plt.imread(io.BytesIO(decoded_image), format='PNG') 145 | 146 | max_height = max(max_height, image.shape[0]) 147 | 148 | # Resize images to max_height while preserving the aspect ratio and alpha channel if it exists 149 | for raster in rasters: 150 | decoded_image = base64.b64decode(raster) 151 | image = plt.imread(io.BytesIO(decoded_image), format='PNG') 152 | 153 | aspect_ratio = image.shape[1] / image.shape[0] 154 | new_width = int(max_height * aspect_ratio) 155 | image_resized = np.array([np.interp(np.linspace( 156 | 0, len(row), new_width), np.arange(0, len(row)), row) for row in image]) 157 | 158 | if image_resized.shape[2] == 4: # If RGBA, preserve alpha channel 159 | alpha_channel = image_resized[:, :, 3:] 160 | # Drop the alpha for visualization 161 | image_resized = image_resized[:, :, :3] 162 | image_resized = np.clip(image_resized, 0, 1) 163 | image_resized = np.concatenate( 164 | (image_resized, alpha_channel), axis=2) 165 | 166 | images.append(image_resized) 167 | 168 | # Concatenate images along the width 169 | concatenated_image = np.concatenate(images, axis=1) 170 | 171 | plt.imshow(concatenated_image) 172 | plt.axis('off') 173 | plt.show() 174 | 175 | 176 | def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301"): 177 | """Returns the number of tokens used by a list of messages.""" 178 | try: 179 | encoding = tiktoken.encoding_for_model(model) 180 | except KeyError: 181 | encoding = tiktoken.get_encoding("cl100k_base") 182 | if model == "gpt-3.5-turbo-0301": # note: future models may deviate from this 183 | num_tokens = 0 184 | for message in messages: 185 | # every message follows {role/name}\n{content}\n 186 | num_tokens += 4 187 | for key, value in message.items(): 188 | num_tokens += len(encoding.encode(value)) 189 | if key == "name": # if there's a name, the role is omitted 190 | num_tokens += -1 # role is always required and always 1 token 191 | num_tokens += 2 # every reply is primed with assistant 192 | return num_tokens 193 | else: 194 | raise NotImplementedError( 195 | f"""num_tokens_from_messages() is not presently implemented for model {model}.""") 196 | 197 | 198 | def cache_request(cache: Cache, params: Any, values: Any = None) -> Any: 199 | # Generate a unique key for the request 200 | 201 | key = hashlib.md5(json.dumps( 202 | params, sort_keys=True).encode("utf-8")).hexdigest() 203 | # Check if the request is cached 204 | if key in cache and values is None: 205 | print("retrieving from cache") 206 | return cache[key] 207 | 208 | # Cache the provided values and return them 209 | if values: 210 | print("saving to cache") 211 | cache[key] = values 212 | return values 213 | 214 | 215 | def clean_code_snippet(code_string): 216 | # Extract code snippet using regex 217 | cleaned_snippet = re.search(r'```(?:\w+)?\s*([\s\S]*?)\s*```', code_string) 218 | 219 | if cleaned_snippet: 220 | cleaned_snippet = cleaned_snippet.group(1) 221 | else: 222 | cleaned_snippet = code_string 223 | 224 | # remove non-printable characters 225 | # cleaned_snippet = re.sub(r'[\x00-\x1F]+', ' ', cleaned_snippet) 226 | 227 | return cleaned_snippet 228 | -------------------------------------------------------------------------------- /lida/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.0.14" 2 | -------------------------------------------------------------------------------- /lida/web/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | samples/ 3 | lida/web/backend/files 4 | examples/experiment_results.json 5 | examples/data 6 | data 7 | .DS_Store 8 | test.py 9 | experiments/data 10 | test.py 11 | .azure 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | # build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | MANIFEST 41 | .env 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .nox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | *.py,cover 64 | .hypothesis/ 65 | .pytest_cache/ 66 | 67 | # Translations 68 | *.mo 69 | *.pot 70 | 71 | # Django stuff: 72 | *.log 73 | local_settings.py 74 | db.sqlite3 75 | db.sqlite3-journal 76 | 77 | # Flask stuff: 78 | instance/ 79 | .webassets-cache 80 | 81 | # Scrapy stuff: 82 | .scrapy 83 | 84 | # Sphinx documentation 85 | docs/_build/ 86 | 87 | # PyBuilder 88 | target/ 89 | 90 | # Jupyter Notebook 91 | .ipynb_checkpoints 92 | 93 | # IPython 94 | profile_default/ 95 | ipython_config.py 96 | 97 | # pyenv 98 | .python-version 99 | 100 | # pipenv 101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 104 | # install all needed dependencies. 105 | #Pipfile.lock 106 | 107 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 108 | __pypackages__/ 109 | 110 | # Celery stuff 111 | celerybeat-schedule 112 | celerybeat.pid 113 | 114 | # SageMath parsed files 115 | *.sage.py 116 | 117 | # Environments 118 | .env 119 | .venv 120 | env/ 121 | venv/ 122 | ENV/ 123 | env.bak/ 124 | venv.bak/ 125 | 126 | # Spyder project settings 127 | .spyderproject 128 | .spyproject 129 | 130 | # Rope project settings 131 | .ropeproject 132 | 133 | # mkdocs documentation 134 | /site 135 | 136 | # mypy 137 | .mypy_cache/ 138 | .dmypy.json 139 | dmypy.json 140 | 141 | # Pyre type checker 142 | .pyre/ 143 | -------------------------------------------------------------------------------- /lida/web/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | 3 | """Handle web backend API for lida. 4 | """ 5 | -------------------------------------------------------------------------------- /lida/web/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import logging 4 | import requests 5 | from fastapi import FastAPI, UploadFile 6 | from fastapi.staticfiles import StaticFiles 7 | from fastapi.middleware.cors import CORSMiddleware 8 | import traceback 9 | 10 | from llmx import llm, providers 11 | from ..datamodel import GoalWebRequest, SummaryUrlRequest, TextGenerationConfig, UploadUrl, VisualizeEditWebRequest, VisualizeEvalWebRequest, VisualizeExplainWebRequest, VisualizeRecommendRequest, VisualizeRepairWebRequest, VisualizeWebRequest, InfographicsRequest 12 | from ..components import Manager 13 | 14 | 15 | # instantiate model and generator 16 | textgen = llm() 17 | logger = logging.getLogger("lida") 18 | api_docs = os.environ.get("LIDA_API_DOCS", "False") == "True" 19 | 20 | 21 | lida = Manager(text_gen=textgen) 22 | app = FastAPI() 23 | # allow cross origin requests for testing on localhost:800* ports only 24 | app.add_middleware( 25 | CORSMiddleware, 26 | allow_origins=["http://localhost:8000", "http://127.0.0.1:8000", "http://localhost:8001"], 27 | allow_credentials=True, 28 | allow_methods=["*"], 29 | allow_headers=["*"], 30 | ) 31 | api = FastAPI(root_path="/api", docs_url="/docs" if api_docs else None, redoc_url=None) 32 | app.mount("/api", api) 33 | 34 | 35 | root_file_path = os.path.dirname(os.path.abspath(__file__)) 36 | static_folder_root = os.path.join(root_file_path, "ui") 37 | files_static_root = os.path.join(root_file_path, "files/") 38 | data_folder = os.path.join(root_file_path, "files/data") 39 | os.makedirs(data_folder, exist_ok=True) 40 | os.makedirs(files_static_root, exist_ok=True) 41 | os.makedirs(static_folder_root, exist_ok=True) 42 | 43 | 44 | # mount lida front end UI files 45 | app.mount("/", StaticFiles(directory=static_folder_root, html=True), name="ui") 46 | api.mount("/files", StaticFiles(directory=files_static_root, html=True), name="files") 47 | 48 | 49 | # def check_model 50 | 51 | @api.post("/visualize") 52 | async def visualize_data(req: VisualizeWebRequest) -> dict: 53 | """Generate goals given a dataset summary""" 54 | try: 55 | # print(req.textgen_config) 56 | charts = lida.visualize( 57 | summary=req.summary, 58 | goal=req.goal, 59 | textgen_config=req.textgen_config if req.textgen_config else TextGenerationConfig(), 60 | library=req.library, return_error=True) 61 | print("found charts: ", len(charts), " for goal: ") 62 | if len(charts) == 0: 63 | return {"status": False, "message": "No charts generated"} 64 | return {"status": True, "charts": charts, 65 | "message": "Successfully generated charts."} 66 | 67 | except Exception as exception_error: 68 | logger.error(f"Error generating visualization goals: {str(exception_error)}") 69 | return {"status": False, 70 | "message": f"Error generating visualization goals. {str(exception_error)}"} 71 | 72 | 73 | @api.post("/visualize/edit") 74 | async def edit_visualization(req: VisualizeEditWebRequest) -> dict: 75 | """Given a visualization code, and a goal, generate a new visualization""" 76 | try: 77 | textgen_config = req.textgen_config if req.textgen_config else TextGenerationConfig() 78 | charts = lida.edit( 79 | code=req.code, 80 | summary=req.summary, 81 | instructions=req.instructions, 82 | textgen_config=textgen_config, 83 | library=req.library, return_error=True) 84 | 85 | # charts = [asdict(chart) for chart in charts] 86 | if len(charts) == 0: 87 | return {"status": False, "message": "No charts generated"} 88 | return {"status": True, "charts": charts, 89 | "message": f"Successfully edited charts."} 90 | 91 | except Exception as exception_error: 92 | logger.error(f"Error generating visualization edits: {str(exception_error)}") 93 | print(traceback.print_exc()) 94 | return {"status": False, 95 | "message": f"Error generating visualization edits."} 96 | 97 | 98 | @api.post("/visualize/repair") 99 | async def repair_visualization(req: VisualizeRepairWebRequest) -> dict: 100 | """ Given a visualization goal and some feedback, generate a new visualization that addresses the feedback""" 101 | 102 | try: 103 | 104 | charts = lida.repair( 105 | code=req.code, 106 | feedback=req.feedback, 107 | goal=req.goal, 108 | summary=req.summary, 109 | textgen_config=req.textgen_config if req.textgen_config else TextGenerationConfig(), 110 | library=req.library, 111 | return_error=True 112 | ) 113 | 114 | if len(charts) == 0: 115 | return {"status": False, "message": "No charts generated"} 116 | return {"status": True, "charts": charts, 117 | "message": "Successfully generated chart repairs"} 118 | 119 | except Exception as exception_error: 120 | logger.error(f"Error generating visualization repairs: {str(exception_error)}") 121 | return {"status": False, 122 | "message": f"Error generating visualization repairs."} 123 | 124 | 125 | @api.post("/visualize/explain") 126 | async def explain_visualization(req: VisualizeExplainWebRequest) -> dict: 127 | """Given a visualization code, provide an explanation of the code""" 128 | textgen_config = req.textgen_config if req.textgen_config else TextGenerationConfig( 129 | n=1, 130 | temperature=0) 131 | 132 | try: 133 | explanations = lida.explain( 134 | code=req.code, 135 | textgen_config=textgen_config, 136 | library=req.library) 137 | return {"status": True, "explanations": explanations[0], 138 | "message": "Successfully generated explanations"} 139 | 140 | except Exception as exception_error: 141 | logger.error(f"Error generating visualization explanation: {str(exception_error)}") 142 | return {"status": False, 143 | "message": f"Error generating visualization explanation."} 144 | 145 | 146 | @api.post("/visualize/evaluate") 147 | async def evaluate_visualization(req: VisualizeEvalWebRequest) -> dict: 148 | """Given a visualization code, provide an evaluation of the code""" 149 | 150 | try: 151 | evaluations = lida.evaluate( 152 | code=req.code, 153 | goal=req.goal, 154 | textgen_config=req.textgen_config if req.textgen_config else TextGenerationConfig( 155 | n=1, 156 | temperature=0), 157 | library=req.library)[0] 158 | return {"status": True, "evaluations": evaluations, 159 | "message": "Successfully generated evaluation"} 160 | 161 | except Exception as exception_error: 162 | logger.error(f"Error generating visualization evaluation: {str(exception_error)}") 163 | return {"status": False, 164 | "message": f"Error generating visualization evaluation. {str(exception_error)}"} 165 | 166 | 167 | @api.post("/visualize/recommend") 168 | async def recommend_visualization(req: VisualizeRecommendRequest) -> dict: 169 | """Given a dataset summary, generate a visualization recommendations""" 170 | 171 | try: 172 | textgen_config = req.textgen_config if req.textgen_config else TextGenerationConfig() 173 | charts = lida.recommend( 174 | summary=req.summary, 175 | code=req.code, 176 | textgen_config=textgen_config, 177 | library=req.library, 178 | return_error=True) 179 | 180 | if len(charts) == 0: 181 | return {"status": False, "message": "No charts generated"} 182 | return {"status": True, "charts": charts, 183 | "message": "Successfully generated chart recommendation"} 184 | 185 | except Exception as exception_error: 186 | logger.error(f"Error generating visualization recommendation: {str(exception_error)}") 187 | return {"status": False, 188 | "message": f"Error generating visualization recommendation."} 189 | 190 | 191 | @api.post("/text/generate") 192 | async def generate_text(textgen_config: TextGenerationConfig) -> dict: 193 | """Generate text given some prompt""" 194 | 195 | try: 196 | completions = textgen.generate(textgen_config) 197 | return {"status": True, "completions": completions.text} 198 | except Exception as exception_error: 199 | logger.error(f"Error generating text: {str(exception_error)}") 200 | return {"status": False, "message": f"Error generating text."} 201 | 202 | 203 | @api.post("/goal") 204 | async def generate_goal(req: GoalWebRequest) -> dict: 205 | """Generate goals given a dataset summary""" 206 | try: 207 | textgen_config = req.textgen_config if req.textgen_config else TextGenerationConfig() 208 | goals = lida.goals(req.summary, n=req.n, textgen_config=textgen_config) 209 | return {"status": True, "data": goals, 210 | "message": f"Successfully generated {len(goals)} goals"} 211 | except Exception as exception_error: 212 | logger.error(f"Error generating goals: {str(exception_error)}") 213 | # Check for a specific error message related to context length 214 | if "context length" in str(exception_error).lower(): 215 | return { 216 | "status": False, 217 | "message": "The dataset you uploaded has too many columns. Please upload a dataset with fewer columns and try again." 218 | } 219 | 220 | # For other exceptions 221 | return { 222 | "status": False, 223 | "message": f"Error generating visualization goals. {exception_error}" 224 | } 225 | 226 | 227 | @api.post("/summarize") 228 | async def upload_file(file: UploadFile): 229 | """ Upload a file and return a summary of the data """ 230 | # allow csv, excel, json 231 | allowed_types = ["text/csv", "application/vnd.ms-excel", "application/json"] 232 | 233 | # print("file: ", file) 234 | # check file type 235 | if file.content_type not in allowed_types: 236 | return {"status": False, 237 | "message": f"Uploaded file type ({file.content_type}) not allowed. Allowed types are: csv, excel, json"} 238 | 239 | try: 240 | 241 | # save file to files folder 242 | file_location = os.path.join(data_folder, file.filename) 243 | # open file without deleting existing contents 244 | with open(file_location, "wb+") as file_object: 245 | file_object.write(file.file.read()) 246 | 247 | # summarize 248 | textgen_config = TextGenerationConfig(n=1, temperature=0) 249 | summary = lida.summarize( 250 | data=file_location, 251 | file_name=file.filename, 252 | summary_method="llm", 253 | textgen_config=textgen_config) 254 | return {"status": True, "summary": summary, "data_filename": file.filename} 255 | except Exception as exception_error: 256 | logger.error(f"Error processing file: {str(exception_error)}") 257 | return {"status": False, "message": f"Error processing file."} 258 | 259 | 260 | # upload via url 261 | @api.post("/summarize/url") 262 | async def upload_file_via_url(req: SummaryUrlRequest) -> dict: 263 | """ Upload a file from a url and return a summary of the data """ 264 | url = req.url 265 | textgen_config = req.textgen_config if req.textgen_config else TextGenerationConfig( 266 | n=1, temperature=0) 267 | file_name = url.split("/")[-1] 268 | file_location = os.path.join(data_folder, file_name) 269 | 270 | # download file 271 | url_response = requests.get(url, allow_redirects=True, timeout=1000) 272 | open(file_location, "wb").write(url_response.content) 273 | try: 274 | 275 | summary = lida.summarize( 276 | data=file_location, 277 | file_name=file_name, 278 | summary_method="llm", 279 | textgen_config=textgen_config) 280 | return {"status": True, "summary": summary, "data_filename": file_name} 281 | except Exception as exception_error: 282 | # traceback.print_exc() 283 | logger.error(f"Error processing file: {str(exception_error)}") 284 | return {"status": False, "message": f"Error processing file."} 285 | 286 | # convert image to infographics 287 | 288 | 289 | @api.post("/infographer") 290 | async def generate_infographics(req: InfographicsRequest) -> dict: 291 | """Generate infographics using the peacasso package""" 292 | try: 293 | result = lida.infographics( 294 | visualization=req.visualization, 295 | n=req.n, 296 | style_prompt=req.style_prompt 297 | # return_pil=req.return_pil 298 | ) 299 | return {"status": True, "result": result, "message": "Successfully generated infographics"} 300 | except Exception as exception_error: 301 | logger.error(f"Error generating infographics: {str(exception_error)}") 302 | return {"status": False, 303 | "message": f"Error generating infographics. {str(exception_error)}"} 304 | 305 | # list supported models 306 | 307 | 308 | @api.get("/models") 309 | def list_models() -> dict: 310 | return {"status": True, "data": providers, "message": "Successfully listed models"} 311 | -------------------------------------------------------------------------------- /lida/web/ui/81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2018 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | 7 | /** @license React v16.13.1 8 | * react-is.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | -------------------------------------------------------------------------------- /lida/web/ui/chunk-map.json: -------------------------------------------------------------------------------- 1 | {"polyfill":["/polyfill-9f027554f9c426b688ff.js"],"app":["/app-80dcacffbc4d71399cc7.js"],"component---src-pages-404-tsx":["/component---src-pages-404-tsx-271998ff555bf33bd7ce.js"],"component---src-pages-demo-tsx":["/component---src-pages-demo-tsx-54fd6da10fa870d8d843.js"],"component---src-pages-index-tsx":["/component---src-pages-index-tsx-36a4bd360cacad51120f.js"],"component---src-pages-login-tsx":["/component---src-pages-login-tsx-bbe1cb64566ee588bf48.js"]} -------------------------------------------------------------------------------- /lida/web/ui/component---src-pages-404-tsx-271998ff555bf33bd7ce.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunklida=self.webpackChunklida||[]).push([[218],{32513:function(e,t,n){n.r(t);var l=n(67294),a=n(71082),o={color:"#232129",padding:"96px",fontFamily:"-apple-system, Roboto, sans-serif, serif"},r={marginTop:0,marginBottom:64,maxWidth:320},i={marginBottom:48};t.default=function(){return l.createElement("main",{style:o},l.createElement("title",null,"Not found"),l.createElement("h1",{style:r},"Page not found"),l.createElement("p",{style:i},"Sorry"," ",l.createElement("span",{role:"img","aria-label":"Pensive emoji"},"😔")," ","we couldn’t find what you were looking for.",l.createElement("br",null),null,l.createElement("br",null),l.createElement(a.Link,{to:"/"},"Go home"),"."))}}}]); 2 | //# sourceMappingURL=component---src-pages-404-tsx-271998ff555bf33bd7ce.js.map -------------------------------------------------------------------------------- /lida/web/ui/component---src-pages-404-tsx-271998ff555bf33bd7ce.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"component---src-pages-404-tsx-271998ff555bf33bd7ce.js","mappings":"mIAIMA,EAAa,CACjBC,MAAO,UACPC,QAAS,OACTC,WAAY,4CAERC,EAAgB,CACpBC,UAAW,EACXC,aAAc,GACdC,SAAU,KAGNC,EAAkB,CACtBF,aAAc,IAqChB,UA1BqB,WACnB,OACEG,EAAAA,cAAA,QAAMC,MAAOV,GACXS,EAAAA,cAAA,aAAO,aACPA,EAAAA,cAAA,MAAIC,MAAON,GAAe,kBAC1BK,EAAAA,cAAA,KAAGC,MAAOF,GAAiB,QACnB,IACNC,EAAAA,cAAA,QAAME,KAAK,MAAM,aAAW,iBAAgB,MAEpC,IAAI,8CAEZF,EAAAA,cAAA,WAOI,KACJA,EAAAA,cAAA,WACAA,EAAAA,cAACG,EAAAA,KAAI,CAACC,GAAG,KAAI,WAAc,KAInC,C","sources":["webpack://lida/./src/pages/404.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { Link } from \"gatsby\"\n\n// styles\nconst pageStyles = {\n color: \"#232129\",\n padding: \"96px\",\n fontFamily: \"-apple-system, Roboto, sans-serif, serif\",\n}\nconst headingStyles = {\n marginTop: 0,\n marginBottom: 64,\n maxWidth: 320,\n}\n\nconst paragraphStyles = {\n marginBottom: 48,\n}\nconst codeStyles = {\n color: \"#8A6534\",\n padding: 4,\n backgroundColor: \"#FFF4DB\",\n fontSize: \"1.25rem\",\n borderRadius: 4,\n}\n\n// markup\nconst NotFoundPage = () => {\n return (\n
\n Not found\n

Page not found

\n

\n Sorry{\" \"}\n \n 😔\n {\" \"}\n we couldn’t find what you were looking for.\n
\n {process.env.NODE_ENV === \"development\" ? (\n <>\n
\n Try creating a page in src/pages/.\n
\n \n ) : null}\n
\n Go home.\n

\n
\n )\n}\n\nexport default NotFoundPage\n"],"names":["pageStyles","color","padding","fontFamily","headingStyles","marginTop","marginBottom","maxWidth","paragraphStyles","React","style","role","Link","to"],"sourceRoot":""} -------------------------------------------------------------------------------- /lida/web/ui/component---src-pages-demo-tsx-54fd6da10fa870d8d843.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * https://github.com/Starcounter-Jack/JSON-Patch 3 | * (c) 2017-2021 Joachim Wester 4 | * MIT license 5 | */ 6 | 7 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 8 | -------------------------------------------------------------------------------- /lida/web/ui/component---src-pages-index-tsx-36a4bd360cacad51120f.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * github-buttons v2.27.0 3 | * (c) 2023 なつき 4 | * @license BSD-2-Clause 5 | */ 6 | -------------------------------------------------------------------------------- /lida/web/ui/component---src-pages-login-tsx-bbe1cb64566ee588bf48.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunklida=self.webpackChunklida||[]).push([[47],{32493:function(e,n,t){t.r(n);var l=t(67294),a=t(99286),u=t(81146);n.default=function(){return l.createElement(a.Z,{title:"Sign In",showHeader:!1},l.createElement(u.Z,null))}}}]); 2 | //# sourceMappingURL=component---src-pages-login-tsx-bbe1cb64566ee588bf48.js.map -------------------------------------------------------------------------------- /lida/web/ui/component---src-pages-login-tsx-bbe1cb64566ee588bf48.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"component---src-pages-login-tsx-bbe1cb64566ee588bf48.js","mappings":"6IAgBA,UATkB,WAEhB,OACEA,EAAAA,cAACC,EAAAA,EAAM,CAACC,MAAM,UAAUC,YAAY,GAClCH,EAAAA,cAACI,EAAAA,EAAS,MAGhB,C","sources":["webpack://lida/./src/pages/login.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { BeakerIcon, InformationCircleIcon } from '@heroicons/react/outline';\nimport SearchView from \"../components/search\";\nimport Layout from \"../components/layout\";\nimport LoginView from \"../components/login\";\n\n// markup\nconst LoginPage = () => {\n const pageTitle = \"Interaction Data Viewer\";\n return (\n \n \n \n );\n};\n\nexport default LoginPage;\n"],"names":["React","Layout","title","showHeader","LoginView"],"sourceRoot":""} -------------------------------------------------------------------------------- /lida/web/ui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/favicon-32x32.png -------------------------------------------------------------------------------- /lida/web/ui/files/infographics_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/files/infographics_small.jpg -------------------------------------------------------------------------------- /lida/web/ui/files/lidamodules.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/files/lidamodules.jpg -------------------------------------------------------------------------------- /lida/web/ui/framework-adcf09f5896f386aa38b.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** @license React v0.20.2 2 | * scheduler.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 10 | /** @license React v17.0.2 11 | * react-dom.production.min.js 12 | * 13 | * Copyright (c) Facebook, Inc. and its affiliates. 14 | * 15 | * This source code is licensed under the MIT license found in the 16 | * LICENSE file in the root directory of this source tree. 17 | */ 18 | 19 | /** @license React v17.0.2 20 | * react.production.min.js 21 | * 22 | * Copyright (c) Facebook, Inc. and its affiliates. 23 | * 24 | * This source code is licensed under the MIT license found in the 25 | * LICENSE file in the root directory of this source tree. 26 | */ 27 | -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-144x144.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-192x192.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-256x256.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-384x384.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-48x48.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-512x512.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-72x72.png -------------------------------------------------------------------------------- /lida/web/ui/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/icons/icon-96x96.png -------------------------------------------------------------------------------- /lida/web/ui/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/images/default.png -------------------------------------------------------------------------------- /lida/web/ui/images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/images/screen.png -------------------------------------------------------------------------------- /lida/web/ui/images/videoscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/lida/d892e20be0cf8263644f2575f097ccecebebf812/lida/web/ui/images/videoscreen.png -------------------------------------------------------------------------------- /lida/web/ui/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | {"icons":[{"src":"icons/icon-48x48.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"48x48","type":"image/png"},{"src":"icons/icon-72x72.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"72x72","type":"image/png"},{"src":"icons/icon-96x96.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"96x96","type":"image/png"},{"src":"icons/icon-144x144.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"144x144","type":"image/png"},{"src":"icons/icon-192x192.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"192x192","type":"image/png"},{"src":"icons/icon-256x256.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"256x256","type":"image/png"},{"src":"icons/icon-384x384.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"384x384","type":"image/png"},{"src":"icons/icon-512x512.png?v=ec9820d5698bdcb03284cb1dd7c5625d","sizes":"512x512","type":"image/png"}]} -------------------------------------------------------------------------------- /lida/web/ui/page-data/404.html/page-data.json: -------------------------------------------------------------------------------- 1 | {"componentChunkName":"component---src-pages-404-tsx","path":"/404.html","result":{"pageContext":{}},"staticQueryHashes":[]} -------------------------------------------------------------------------------- /lida/web/ui/page-data/404/page-data.json: -------------------------------------------------------------------------------- 1 | {"componentChunkName":"component---src-pages-404-tsx","path":"/404/","result":{"pageContext":{}},"staticQueryHashes":[]} -------------------------------------------------------------------------------- /lida/web/ui/page-data/app-data.json: -------------------------------------------------------------------------------- 1 | {"webpackCompilationHash":"ebabbdb9cf322b211c7f"} 2 | -------------------------------------------------------------------------------- /lida/web/ui/page-data/demo/page-data.json: -------------------------------------------------------------------------------- 1 | {"componentChunkName":"component---src-pages-demo-tsx","path":"/demo/","result":{"data":{"site":{"siteMetadata":{"description":"Generate visualizations from data","title":"LIDA"}}},"pageContext":{}},"staticQueryHashes":["1865044719"]} -------------------------------------------------------------------------------- /lida/web/ui/page-data/index/page-data.json: -------------------------------------------------------------------------------- 1 | {"componentChunkName":"component---src-pages-index-tsx","path":"/","result":{"data":{"site":{"siteMetadata":{"description":"Generate visualizations from data","title":"LIDA"}}},"pageContext":{}},"staticQueryHashes":["1865044719"]} -------------------------------------------------------------------------------- /lida/web/ui/page-data/login/page-data.json: -------------------------------------------------------------------------------- 1 | {"componentChunkName":"component---src-pages-login-tsx","path":"/login/","result":{"pageContext":{}},"staticQueryHashes":["1865044719"]} -------------------------------------------------------------------------------- /lida/web/ui/page-data/sq/d/1865044719.json: -------------------------------------------------------------------------------- 1 | {"data":{"site":{"siteMetadata":{"title":"LIDA","description":"Generate visualizations from data","twitterUsername":"@vykthur","image":"/images/screen.png","siteUrl":"https://microsoft.github.io/lida"}}}} -------------------------------------------------------------------------------- /lida/web/ui/sitemap/sitemap-0.xml: -------------------------------------------------------------------------------- 1 | https://microsoft.github.io/demo/daily0.7https://microsoft.github.io/daily0.7https://microsoft.github.io/login/daily0.7 -------------------------------------------------------------------------------- /lida/web/ui/sitemap/sitemap-index.xml: -------------------------------------------------------------------------------- 1 | https://microsoft.github.io/sitemap/sitemap-0.xml -------------------------------------------------------------------------------- /lida/web/ui/webpack-runtime-8958b079d5c4b6876c66.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var e,t,n,r,o,i={},u={};function c(e){var t=u[e];if(void 0!==t)return t.exports;var n=u[e]={exports:{}};return i[e].call(n.exports,n,n.exports,c),n.exports}c.m=i,e=[],c.O=function(t,n,r,o){if(!n){var i=1/0;for(s=0;s=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[f])}))?n.splice(f--,1):(u=!1,o0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},c.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var i={};t=t||[null,n({}),n([]),n(n)];for(var u=2&r&&e;"object"==typeof u&&!~t.indexOf(u);u=n(u))Object.getOwnPropertyNames(u).forEach((function(t){i[t]=function(){return e[t]}}));return i.default=function(){return e},c.d(o,i),o},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return{47:"component---src-pages-login-tsx",218:"component---src-pages-404-tsx",308:"21614df092a0a42959abd49fa5ffec5702fa9463",347:"81e257386408544e35976acc2a4075b730ed48a4",514:"component---src-pages-demo-tsx",691:"component---src-pages-index-tsx"}[e]+"-"+{47:"bbe1cb64566ee588bf48",218:"271998ff555bf33bd7ce",308:"ea1bf954f60f23d1e6a7",347:"326068d326429305ec9e",514:"54fd6da10fa870d8d843",691:"36a4bd360cacad51120f"}[e]+".js"},c.miniCssF=function(e){return"styles.15bd7f1a07f6d77699dc.css"},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="lida:",c.l=function(e,t,n,i){if(r[e])r[e].push(t);else{var u,f;if(void 0!==n)for(var a=document.getElementsByTagName("script"),s=0;s 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var getProto = Object.getPrototypeOf ? function(obj) { return Object.getPrototypeOf(obj); } : function(obj) { return obj.__proto__; };\nvar leafPrototypes;\n// create a fake namespace object\n// mode & 1: value is a module id, require it\n// mode & 2: merge all properties of value into the ns\n// mode & 4: return value when already ns object\n// mode & 16: return value when it's Promise-like\n// mode & 8|1: behave like require\n__webpack_require__.t = function(value, mode) {\n\tif(mode & 1) value = this(value);\n\tif(mode & 8) return value;\n\tif(typeof value === 'object' && value) {\n\t\tif((mode & 4) && value.__esModule) return value;\n\t\tif((mode & 16) && typeof value.then === 'function') return value;\n\t}\n\tvar ns = Object.create(null);\n\t__webpack_require__.r(ns);\n\tvar def = {};\n\tleafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];\n\tfor(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {\n\t\tObject.getOwnPropertyNames(current).forEach(function(key) { def[key] = function() { return value[key]; }; });\n\t}\n\tdef['default'] = function() { return value; };\n\t__webpack_require__.d(ns, def);\n\treturn ns;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"lida:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = function(url, done, key, chunkId) {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tscript.timeout = 120;\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = function(prev, event) {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach(function(fn) { return fn(event); });\n\t\tif(prev) return prev(event);\n\t}\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = function(chunkId) {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + {\"47\":\"component---src-pages-login-tsx\",\"218\":\"component---src-pages-404-tsx\",\"308\":\"21614df092a0a42959abd49fa5ffec5702fa9463\",\"347\":\"81e257386408544e35976acc2a4075b730ed48a4\",\"514\":\"component---src-pages-demo-tsx\",\"691\":\"component---src-pages-index-tsx\"}[chunkId] + \"-\" + {\"47\":\"bbe1cb64566ee588bf48\",\"218\":\"271998ff555bf33bd7ce\",\"308\":\"ea1bf954f60f23d1e6a7\",\"347\":\"326068d326429305ec9e\",\"514\":\"54fd6da10fa870d8d843\",\"691\":\"36a4bd360cacad51120f\"}[chunkId] + \".js\";\n};","// This function allow to reference all chunks\n__webpack_require__.miniCssF = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + \"styles\" + \".\" + \"15bd7f1a07f6d77699dc\" + \".css\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"/\";","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t658: 0,\n\t532: 0\n};\n\n__webpack_require__.f.j = function(chunkId, promises) {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(!/^(532|658)$/.test(chunkId)) {\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = function(event) {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t} else installedChunks[chunkId] = 0;\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunklida\"] = self[\"webpackChunklida\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"],"names":["deferred","leafPrototypes","getProto","inProgress","dataWebpackPrefix","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","call","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","length","fulfilled","j","Object","keys","every","key","splice","r","n","getter","__esModule","d","a","getPrototypeOf","obj","__proto__","t","value","mode","this","then","ns","create","def","current","indexOf","getOwnPropertyNames","forEach","definition","o","defineProperty","enumerable","get","f","e","chunkId","Promise","all","reduce","promises","u","miniCssF","g","globalThis","Function","window","prop","prototype","hasOwnProperty","l","url","done","push","script","needAttach","scripts","document","getElementsByTagName","s","getAttribute","createElement","charset","timeout","nc","setAttribute","src","onScriptComplete","prev","event","onerror","onload","clearTimeout","doneFns","parentNode","removeChild","setTimeout","bind","type","target","head","appendChild","Symbol","toStringTag","p","installedChunks","installedChunkData","test","promise","resolve","reject","error","Error","errorType","realSrc","message","name","request","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","id","chunkLoadingGlobal","self"],"sourceRoot":""} -------------------------------------------------------------------------------- /lida/web/ui/webpack.stats.json: -------------------------------------------------------------------------------- 1 | {"name":"build-javascript","namedChunkGroups":{"polyfill":{"name":"polyfill","assets":[{"name":"webpack-runtime-8958b079d5c4b6876c66.js","size":4031},{"name":"polyfill-9f027554f9c426b688ff.js","size":84996}],"filteredAssets":0,"assetsSize":89027,"filteredAuxiliaryAssets":2,"auxiliaryAssetsSize":226592},"app":{"name":"app","assets":[{"name":"webpack-runtime-8958b079d5c4b6876c66.js","size":4031},{"name":"framework-adcf09f5896f386aa38b.js","size":129870},{"name":"styles.15bd7f1a07f6d77699dc.css","size":28926},{"name":"app-80dcacffbc4d71399cc7.js","size":92843}],"filteredAssets":0,"assetsSize":255670,"filteredAuxiliaryAssets":3,"auxiliaryAssetsSize":710580},"component---src-pages-404-tsx":{"name":"component---src-pages-404-tsx","assets":[{"name":"component---src-pages-404-tsx-271998ff555bf33bd7ce.js","size":796}],"filteredAssets":0,"assetsSize":796,"filteredAuxiliaryAssets":1,"auxiliaryAssetsSize":1986},"component---src-pages-demo-tsx":{"name":"component---src-pages-demo-tsx","assets":[{"name":"21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js","size":75617},{"name":"81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js","size":1023442},{"name":"component---src-pages-demo-tsx-54fd6da10fa870d8d843.js","size":2575854}],"filteredAssets":0,"assetsSize":3674913,"filteredAuxiliaryAssets":3,"auxiliaryAssetsSize":13534234},"component---src-pages-index-tsx":{"name":"component---src-pages-index-tsx","assets":[{"name":"21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js","size":75617},{"name":"81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js","size":1023442},{"name":"component---src-pages-index-tsx-36a4bd360cacad51120f.js","size":58467}],"filteredAssets":0,"assetsSize":1157526,"filteredAuxiliaryAssets":3,"auxiliaryAssetsSize":2611893},"component---src-pages-login-tsx":{"name":"component---src-pages-login-tsx","assets":[{"name":"21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js","size":75617},{"name":"component---src-pages-login-tsx-bbe1cb64566ee588bf48.js","size":334}],"filteredAssets":0,"assetsSize":75951,"filteredAuxiliaryAssets":2,"auxiliaryAssetsSize":233040}},"assetsByChunkName":{"polyfill":["webpack-runtime-8958b079d5c4b6876c66.js","polyfill-9f027554f9c426b688ff.js"],"app":["webpack-runtime-8958b079d5c4b6876c66.js","framework-adcf09f5896f386aa38b.js","styles.15bd7f1a07f6d77699dc.css","app-80dcacffbc4d71399cc7.js"],"component---src-pages-404-tsx":["component---src-pages-404-tsx-271998ff555bf33bd7ce.js"],"component---src-pages-demo-tsx":["21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js","81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js","component---src-pages-demo-tsx-54fd6da10fa870d8d843.js"],"component---src-pages-index-tsx":["21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js","81e257386408544e35976acc2a4075b730ed48a4-326068d326429305ec9e.js","component---src-pages-index-tsx-36a4bd360cacad51120f.js"],"component---src-pages-login-tsx":["21614df092a0a42959abd49fa5ffec5702fa9463-ea1bf954f60f23d1e6a7.js","component---src-pages-login-tsx-bbe1cb64566ee588bf48.js"]},"childAssetsByChunkName":{}} -------------------------------------------------------------------------------- /lida/web/ui/~partytown/debug/partytown-media.js: -------------------------------------------------------------------------------- 1 | /* Partytown 0.5.4 - MIT builder.io */ 2 | (self => { 3 | const [getter, setter, callMethod, constructGlobal, definePrototypePropertyDescriptor, randomId, WinIdKey, InstanceIdKey, ApplyPathKey] = self.$bridgeToMedia$; 4 | delete self.$bridgeToMedia$; 5 | const ContextKey = Symbol(); 6 | const MediaSourceKey = Symbol(); 7 | const ReadyStateKey = Symbol(); 8 | const SourceBuffersKey = Symbol(); 9 | const SourceBufferTasksKey = Symbol(); 10 | const TimeRangesKey = Symbol(); 11 | const EMPTY_ARRAY = []; 12 | const defineCstr = (win, cstrName, Cstr) => win[cstrName] = defineCstrName(cstrName, Cstr); 13 | const defineCstrName = (cstrName, Cstr) => Object.defineProperty(Cstr, "name", { 14 | value: cstrName 15 | }); 16 | const initCanvas = (WorkerBase, win) => { 17 | const HTMLCanvasDescriptorMap = { 18 | getContext: { 19 | value(contextType, contextAttributes) { 20 | this[ContextKey] || (this[ContextKey] = (contextType.includes("webgl") ? createContextWebGL : createContext2D)(this, contextType, contextAttributes)); 21 | return this[ContextKey]; 22 | } 23 | } 24 | }; 25 | const WorkerCanvasGradient = defineCstr(win, "CanvasGradient", class extends WorkerBase { 26 | addColorStop(...args) { 27 | callMethod(this, [ "addColorStop" ], args, 2); 28 | } 29 | }); 30 | const WorkerCanvasPattern = defineCstr(win, "CanvasPattern", class extends WorkerBase { 31 | setTransform(...args) { 32 | callMethod(this, [ "setTransform" ], args, 2); 33 | } 34 | }); 35 | const createContext2D = (canvasInstance, contextType, contextAttributes) => { 36 | const winId = canvasInstance[WinIdKey]; 37 | const ctxInstanceId = randomId(); 38 | const ctxInstance = { 39 | [WinIdKey]: winId, 40 | [InstanceIdKey]: ctxInstanceId, 41 | [ApplyPathKey]: [] 42 | }; 43 | const ctx = callMethod(canvasInstance, [ "getContext" ], [ contextType, contextAttributes ], 1, ctxInstanceId); 44 | const ctx2dGetterMethods = "getContextAttributes,getImageData,getLineDash,getTransform,isPointInPath,isPointInStroke,measureText".split(","); 45 | const CanvasRenderingContext2D = { 46 | get: (target, propName) => "string" == typeof propName && propName in ctx ? "function" == typeof ctx[propName] ? (...args) => { 47 | if (propName.startsWith("create")) { 48 | const instanceId = randomId(); 49 | callMethod(ctxInstance, [ propName ], args, 2, instanceId); 50 | if ("createImageData" === propName || "createPattern" === propName) { 51 | (api => { 52 | console.warn(`${api} not implemented`); 53 | })(`${propName}()`); 54 | return { 55 | setTransform: () => {} 56 | }; 57 | } 58 | return new WorkerCanvasGradient(winId, instanceId); 59 | } 60 | const methodCallType = ctx2dGetterMethods.includes(propName) ? 1 : 2; 61 | return callMethod(ctxInstance, [ propName ], args, methodCallType); 62 | } : ctx[propName] : target[propName], 63 | set(target, propName, value) { 64 | if ("string" == typeof propName && propName in ctx) { 65 | ctx[propName] !== value && "function" != typeof value && setter(ctxInstance, [ propName ], value); 66 | ctx[propName] = value; 67 | } else { 68 | target[propName] = value; 69 | } 70 | return true; 71 | } 72 | }; 73 | return new Proxy(ctx, CanvasRenderingContext2D); 74 | }; 75 | const createContextWebGL = (canvasInstance, contextType, contextAttributes) => { 76 | const winId = canvasInstance[WinIdKey]; 77 | const ctxInstanceId = randomId(); 78 | const ctxInstance = { 79 | [WinIdKey]: winId, 80 | [InstanceIdKey]: ctxInstanceId, 81 | [ApplyPathKey]: [] 82 | }; 83 | const ctx = callMethod(canvasInstance, [ "getContext" ], [ contextType, contextAttributes ], 1, ctxInstanceId); 84 | const WebGLRenderingContextHandler = { 85 | get: (target, propName) => "string" == typeof propName ? "function" != typeof ctx[propName] ? ctx[propName] : (...args) => callMethod(ctxInstance, [ propName ], args, getWebGlMethodCallType(propName)) : target[propName], 86 | set(target, propName, value) { 87 | if ("string" == typeof propName && propName in ctx) { 88 | ctx[propName] !== value && "function" != typeof value && setter(ctxInstance, [ propName ], value); 89 | ctx[propName] = value; 90 | } else { 91 | target[propName] = value; 92 | } 93 | return true; 94 | } 95 | }; 96 | return new Proxy(ctx, WebGLRenderingContextHandler); 97 | }; 98 | const ctxWebGLGetterMethods = "checkFramebufferStatus,makeXRCompatible".split(","); 99 | const getWebGlMethodCallType = methodName => methodName.startsWith("create") || methodName.startsWith("get") || methodName.startsWith("is") || ctxWebGLGetterMethods.includes(methodName) ? 1 : 2; 100 | defineCstr(win, "CanvasGradient", WorkerCanvasGradient); 101 | defineCstr(win, "CanvasPattern", WorkerCanvasPattern); 102 | definePrototypePropertyDescriptor(win.HTMLCanvasElement, HTMLCanvasDescriptorMap); 103 | }; 104 | const initMedia = (WorkerBase, WorkerEventTargetProxy, env, win) => { 105 | var _a, _b; 106 | win.Audio = defineCstrName("HTMLAudioElement", class { 107 | constructor(src) { 108 | const audio = env.$createNode$("audio", randomId()); 109 | audio.src = src; 110 | return audio; 111 | } 112 | }); 113 | const WorkerAudioTrack = class extends WorkerBase { 114 | get enabled() { 115 | return getter(this, [ "enabled" ]); 116 | } 117 | set enabled(value) { 118 | setter(this, [ "enabled" ], value); 119 | } 120 | get id() { 121 | return getter(this, [ "id" ]); 122 | } 123 | get kind() { 124 | return getter(this, [ "kind" ]); 125 | } 126 | get label() { 127 | return getter(this, [ "label" ]); 128 | } 129 | get language() { 130 | return getter(this, [ "language" ]); 131 | } 132 | get sourceBuffer() { 133 | return new WorkerSourceBuffer(this); 134 | } 135 | }; 136 | const WorkerAudioTrackList = class { 137 | constructor(mediaElm) { 138 | const winId = mediaElm[WinIdKey]; 139 | const instanceId = mediaElm[InstanceIdKey]; 140 | const instance = { 141 | addEventListener(...args) { 142 | callMethod(mediaElm, [ "audioTracks", "addEventListener" ], args, 3); 143 | }, 144 | getTrackById: (...args) => callMethod(mediaElm, [ "audioTracks", "getTrackById" ], args), 145 | get length() { 146 | return getter(mediaElm, [ "audioTracks", "length" ]); 147 | }, 148 | removeEventListener(...args) { 149 | callMethod(mediaElm, [ "audioTracks", "removeEventListener" ], args, 3); 150 | } 151 | }; 152 | return new Proxy(instance, { 153 | get: (target, propName) => "number" == typeof propName ? new WorkerAudioTrack(winId, instanceId, [ "audioTracks", propName ]) : target[propName] 154 | }); 155 | } 156 | }; 157 | const WorkerSourceBufferList = defineCstr(win, "SourceBufferList", class extends Array { 158 | constructor(mediaSource) { 159 | super(); 160 | this[MediaSourceKey] = mediaSource; 161 | } 162 | addEventListener(...args) { 163 | callMethod(this[MediaSourceKey], [ "sourceBuffers", "addEventListener" ], args, 3); 164 | } 165 | removeEventListener(...args) { 166 | callMethod(this[MediaSourceKey], [ "sourceBuffers", "removeEventListener" ], args, 3); 167 | } 168 | }); 169 | const WorkerSourceBuffer = defineCstr(win, "SourceBuffer", (_b = class extends WorkerEventTargetProxy { 170 | constructor(mediaSource) { 171 | super(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ "sourceBuffers" ]); 172 | this[_a] = []; 173 | this[MediaSourceKey] = mediaSource; 174 | } 175 | abort() { 176 | const sbIndex = getSourceBufferIndex(this); 177 | callMethod(this, [ sbIndex, "appendWindowStart" ], EMPTY_ARRAY, 1); 178 | } 179 | addEventListener(...args) { 180 | const sbIndex = getSourceBufferIndex(this); 181 | callMethod(this, [ sbIndex, "addEventListener" ], args, 3); 182 | } 183 | appendBuffer(buf) { 184 | this[SourceBufferTasksKey].push([ "appendBuffer", [ buf ], buf ]); 185 | drainSourceBufferQueue(this); 186 | } 187 | get appendWindowStart() { 188 | const sbIndex = getSourceBufferIndex(this); 189 | return getter(this, [ sbIndex, "appendWindowStart" ]); 190 | } 191 | set appendWindowStart(value) { 192 | const sbIndex = getSourceBufferIndex(this); 193 | setter(this, [ sbIndex, "appendWindowStart" ], value); 194 | } 195 | get appendWindowEnd() { 196 | const sbIndex = getSourceBufferIndex(this); 197 | return getter(this, [ sbIndex, "appendWindowEnd" ]); 198 | } 199 | set appendWindowEnd(value) { 200 | const sbIndex = getSourceBufferIndex(this); 201 | setter(this, [ sbIndex, "appendWindowEnd" ], value); 202 | } 203 | get buffered() { 204 | const mediaSource = this[MediaSourceKey]; 205 | const sbIndex = getSourceBufferIndex(this); 206 | const timeRanges = new WorkerTimeRanges(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ "sourceBuffers", sbIndex, "buffered" ]); 207 | return timeRanges; 208 | } 209 | changeType(mimeType) { 210 | const sbIndex = getSourceBufferIndex(this); 211 | callMethod(this, [ sbIndex, "changeType" ], [ mimeType ], 2); 212 | } 213 | get mode() { 214 | const sbIndex = getSourceBufferIndex(this); 215 | return getter(this, [ sbIndex, "mode" ]); 216 | } 217 | set mode(value) { 218 | const sbIndex = getSourceBufferIndex(this); 219 | setter(this, [ sbIndex, "mode" ], value); 220 | } 221 | remove(start, end) { 222 | this[SourceBufferTasksKey].push([ "remove", [ start, end ] ]); 223 | drainSourceBufferQueue(this); 224 | } 225 | removeEventListener(...args) { 226 | const sbIndex = getSourceBufferIndex(this); 227 | callMethod(this, [ sbIndex, "removeEventListener" ], args, 3); 228 | } 229 | get timestampOffset() { 230 | const sbIndex = getSourceBufferIndex(this); 231 | return getter(this, [ sbIndex, "timestampOffset" ]); 232 | } 233 | set timestampOffset(value) { 234 | const sbIndex = getSourceBufferIndex(this); 235 | setter(this, [ sbIndex, "timestampOffset" ], value); 236 | } 237 | get updating() { 238 | const sbIndex = getSourceBufferIndex(this); 239 | return getter(this, [ sbIndex, "updating" ]); 240 | } 241 | }, _a = SourceBufferTasksKey, _b)); 242 | const WorkerTimeRanges = defineCstr(win, "TimeRanges", class extends WorkerBase { 243 | start(...args) { 244 | return callMethod(this, [ "start" ], args); 245 | } 246 | end(...args) { 247 | return callMethod(this, [ "end" ], args); 248 | } 249 | get length() { 250 | return getter(this, [ "length" ]); 251 | } 252 | }); 253 | const getSourceBufferIndex = sourceBuffer => { 254 | if (sourceBuffer) { 255 | const mediaSource = sourceBuffer[MediaSourceKey]; 256 | const sourceBufferList = mediaSource[SourceBuffersKey]; 257 | return sourceBufferList.indexOf(sourceBuffer); 258 | } 259 | return -1; 260 | }; 261 | const drainSourceBufferQueue = sourceBuffer => { 262 | if (sourceBuffer[SourceBufferTasksKey].length) { 263 | if (!sourceBuffer.updating) { 264 | const task = sourceBuffer[SourceBufferTasksKey].shift(); 265 | if (task) { 266 | const sbIndex = getSourceBufferIndex(sourceBuffer); 267 | callMethod(sourceBuffer, [ sbIndex, task[0] ], task[1], 3, void 0, task[2]); 268 | } 269 | } 270 | setTimeout((() => drainSourceBufferQueue(sourceBuffer)), 50); 271 | } 272 | }; 273 | const HTMLMediaDescriptorMap = { 274 | buffered: { 275 | get() { 276 | if (!this[TimeRangesKey]) { 277 | this[TimeRangesKey] = new WorkerTimeRanges(this[WinIdKey], this[InstanceIdKey], [ "buffered" ]); 278 | setTimeout((() => { 279 | this[TimeRangesKey] = void 0; 280 | }), 5e3); 281 | } 282 | return this[TimeRangesKey]; 283 | } 284 | }, 285 | readyState: { 286 | get() { 287 | if (4 === this[ReadyStateKey]) { 288 | return 4; 289 | } 290 | if ("number" != typeof this[ReadyStateKey]) { 291 | this[ReadyStateKey] = getter(this, [ "readyState" ]); 292 | setTimeout((() => { 293 | this[ReadyStateKey] = void 0; 294 | }), 1e3); 295 | } 296 | return this[ReadyStateKey]; 297 | } 298 | } 299 | }; 300 | defineCstr(win, "MediaSource", class extends WorkerEventTargetProxy { 301 | constructor() { 302 | super(env.$winId$); 303 | this[SourceBuffersKey] = new WorkerSourceBufferList(this); 304 | constructGlobal(this, "MediaSource", EMPTY_ARRAY); 305 | } 306 | get activeSourceBuffers() { 307 | return []; 308 | } 309 | addSourceBuffer(mimeType) { 310 | const sourceBuffer = new WorkerSourceBuffer(this); 311 | this[SourceBuffersKey].push(sourceBuffer); 312 | callMethod(this, [ "addSourceBuffer" ], [ mimeType ]); 313 | return sourceBuffer; 314 | } 315 | clearLiveSeekableRange() { 316 | callMethod(this, [ "clearLiveSeekableRange" ], EMPTY_ARRAY, 2); 317 | } 318 | get duration() { 319 | return getter(this, [ "duration" ]); 320 | } 321 | set duration(value) { 322 | setter(this, [ "duration" ], value); 323 | } 324 | endOfStream(endOfStreamError) { 325 | callMethod(this, [ "endOfStream" ], [ endOfStreamError ], 3); 326 | } 327 | get readyState() { 328 | return getter(this, [ "readyState" ]); 329 | } 330 | removeSourceBuffer(sourceBuffer) { 331 | const index = getSourceBufferIndex(sourceBuffer); 332 | if (index > -1) { 333 | this[SourceBuffersKey].splice(index, 1); 334 | callMethod(this, [ "removeSourceBuffer" ], [ index ], 1); 335 | } 336 | } 337 | setLiveSeekableRange(start, end) { 338 | callMethod(this, [ "setLiveSeekableRange" ], [ start, end ], 2); 339 | } 340 | get sourceBuffers() { 341 | return this[SourceBuffersKey]; 342 | } 343 | static isTypeSupported(mimeType) { 344 | if (!isStaticTypeSupported.has(mimeType)) { 345 | const isSupported = callMethod(win, [ "MediaSource", "isTypeSupported" ], [ mimeType ]); 346 | isStaticTypeSupported.set(mimeType, isSupported); 347 | } 348 | return isStaticTypeSupported.get(mimeType); 349 | } 350 | }); 351 | const winURL = win.URL = defineCstrName("URL", class extends URL {}); 352 | const hasAudioTracks = "audioTracks" in win.HTMLMediaElement.prototype; 353 | if (hasAudioTracks) { 354 | defineCstr(win, "AudioTrackList", WorkerAudioTrackList); 355 | defineCstr(win, "AudioTrack", WorkerAudioTrack); 356 | HTMLMediaDescriptorMap.audioTracks = { 357 | get() { 358 | return new WorkerAudioTrackList(this); 359 | } 360 | }; 361 | } 362 | definePrototypePropertyDescriptor(win.HTMLMediaElement, HTMLMediaDescriptorMap); 363 | winURL.createObjectURL = obj => callMethod(win, [ "URL", "createObjectURL" ], [ obj ]); 364 | winURL.revokeObjectURL = obj => callMethod(win, [ "URL", "revokeObjectURL" ], [ obj ]); 365 | }; 366 | const isStaticTypeSupported = new Map; 367 | self.$bridgeFromMedia$ = (WorkerBase, WorkerEventTargetProxy, env, win, windowMediaConstructors) => { 368 | windowMediaConstructors.map((mediaCstrName => { 369 | delete win[mediaCstrName]; 370 | })); 371 | initCanvas(WorkerBase, win); 372 | initMedia(WorkerBase, WorkerEventTargetProxy, env, win); 373 | }; 374 | })(self); 375 | -------------------------------------------------------------------------------- /lida/web/ui/~partytown/debug/partytown-sw.js: -------------------------------------------------------------------------------- 1 | /* Partytown 0.5.4 - MIT builder.io */ 2 | const resolves = new Map; 3 | 4 | const swMessageError = (accessReq, $error$) => ({ 5 | $msgId$: accessReq.$msgId$, 6 | $error$: $error$ 7 | }); 8 | 9 | const httpRequestFromWebWorker = req => new Promise((async resolve => { 10 | const accessReq = await req.clone().json(); 11 | const responseData = await (accessReq => new Promise((async resolve => { 12 | const clients = await self.clients.matchAll(); 13 | const client = [ ...clients ].sort(((a, b) => a.url > b.url ? -1 : a.url < b.url ? 1 : 0))[0]; 14 | if (client) { 15 | const timeout = 12e4; 16 | const msgResolve = [ resolve, setTimeout((() => { 17 | resolves.delete(accessReq.$msgId$); 18 | resolve(swMessageError(accessReq, "Timeout")); 19 | }), timeout) ]; 20 | resolves.set(accessReq.$msgId$, msgResolve); 21 | client.postMessage(accessReq); 22 | } else { 23 | resolve(swMessageError(accessReq, "NoParty")); 24 | } 25 | })))(accessReq); 26 | resolve(response(JSON.stringify(responseData), "application/json")); 27 | })); 28 | 29 | const response = (body, contentType) => new Response(body, { 30 | headers: { 31 | "content-type": contentType || "text/html", 32 | "Cache-Control": "no-store" 33 | } 34 | }); 35 | 36 | self.oninstall = () => self.skipWaiting(); 37 | 38 | self.onactivate = () => self.clients.claim(); 39 | 40 | self.onmessage = ev => { 41 | const accessRsp = ev.data; 42 | const r = resolves.get(accessRsp.$msgId$); 43 | if (r) { 44 | resolves.delete(accessRsp.$msgId$); 45 | clearTimeout(r[1]); 46 | r[0](accessRsp); 47 | } 48 | }; 49 | 50 | self.onfetch = ev => { 51 | const req = ev.request; 52 | const url = new URL(req.url); 53 | const pathname = url.pathname; 54 | if (pathname.endsWith("sw.html")) { 55 | ev.respondWith(response('