diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f72fcf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +COPY secrets.toml /app/.streamlit/secrets.toml +RUN pip install -r requirements.txt + +COPY . . + +CMD ["streamlit", "run", "graph_data_generator_streamlit/app.py", "--server.enableCORS", "false", "--browser.serverAddress", "0.0.0.0", "--browser.gatherUsageStats", "false", "--server.port", "8080"] \ No newline at end of file diff --git a/README.md b/README.md index a6736f1..2f56d86 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MOCK GRAPH DATA GENERATOR -Applet using [Streamlit](https://streamlit.io) to conveniently design and generate interwoven mock data. +Applet using [Streamlit](https://streamlit.io) to conveniently design and generate interwoven mock data. A running cloud instance of this can be found [here](https://dev.neo4j.com/mock-graph-data-generator) ## Install Poetry This applet uses [Poetry](https://python-poetry.org) for dependency management. @@ -9,7 +9,7 @@ This applet uses several packages that will auto-install if you use either the p 1. [graph-data-generator](https://pypi.org/project/graph-data-generator/) for generating the actual mock data from a .json configuration 2. [neo4j-uploader](https://pypi.org/project/neo4j-uploader/) for uploading generated .json output to a [Neo4j](https://neo4j.com/developer/) graph database instance -## Running +## Local Running ``` poetry update poetry run streamlit run graph_data_generator_streamlit/app.py @@ -17,3 +17,17 @@ poetry run streamlit run graph_data_generator_streamlit/app.py ## Testing with local packages `poetry add --editable /path/to/package` + +## Running in Google Cloud +- Set up a [Google Cloud account](https://cloud.google.com) +- Create a [Google Cloud Project](https://developers.google.com/workspace/guides/create-project) +- [Enable billing](https://cloud.google.com/billing/docs/how-to/modify-project) for that project +- Temporarily move any .streamlit/secret.toml file to the root folder director (same level as Dockerfile) +- Install [glcoud cli](https://cloud.google.com/sdk/docs/install) +- Run the following commands from the terminal of your local dev machine: +``` +gcloud builds submit --tag gcr.io/<google_cloud_project_id>/mock-graph-generator +gcloud run deploy --image gcr.io/<google_cloud_project_id>/mock-graph-generator --platform managed --allow-unauthenticated + +When completed, can move secrets.toml file back to .streamlit/ - that or maintain a separate external secrets.toml file just for Google Cloud +``` \ No newline at end of file diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..3c2d3f9 --- /dev/null +++ b/app.yaml @@ -0,0 +1,3 @@ +runtime: python311 + +entrypoint: streamlit run graph_data_generator_streamlit/app.py --server.enableCORS false --browser.serverAddress 0.0.0.0 --browser.gatherUsageStats false --server.port $PORT \ No newline at end of file diff --git a/graph_data_generator_streamlit/app.py b/graph_data_generator_streamlit/app.py index c838017..6c351ea 100644 --- a/graph_data_generator_streamlit/app.py +++ b/graph_data_generator_streamlit/app.py @@ -1,12 +1,10 @@ import streamlit as st from ui.instructions_ui import instructions_ui from ui.generate_ui import generate_ui -from ui.config_ui import config_ui from ui.design_ui import arrows_ui, generators_ui from ui.ideate_ui import ideate_ui from ui.export_ui import export_ui from ui.samples_ui import samples_list -from graph_data_generator import start_logging import logging # SETUP @@ -17,17 +15,25 @@ # Uncomment to start graph_data_generator logging # start_logging() -# LOAD any env -neo4j_uri = st.secrets.get("NEO4J_URI", None) +# LOAD optional env data +try: + neo4j_uri = st.secrets.get("NEO4J_URI", None) + neo4j_user = st.secrets.get("NEO4J_USER", None) + open_ai_key = st.secrets.get("OPENAI_API_KEY", None) + neo4j_pass = st.secrets.get("NEO4J_PASSWORD", None) +except: + neo4j_uri = None + neo4j_user = None + neo4j_pass = None + open_ai_key = None + pass + if "NEO4J_URI" not in st.session_state: st.session_state["NEO4J_URI"] = neo4j_uri -neo4j_user = st.secrets.get("NEO4J_USER", None) if "NEO4J_USER" not in st.session_state: st.session_state["NEO4J_USER"] = neo4j_user -password = st.secrets.get("NEO4J_PASSWORD", None) if "NEO4J_PASSWORD" not in st.session_state: - st.session_state["NEO4J_PASSWORD"] = password -open_ai_key = st.secrets.get("OPENAI_API_KEY", None) + st.session_state["NEO4J_PASSWORD"] = neo4j_pass if "OPENAI_API_KEY" not in st.session_state: st.session_state["OPENAI_API_KEY"] = open_ai_key diff --git a/graph_data_generator_streamlit/ui/export_ui.py b/graph_data_generator_streamlit/ui/export_ui.py index e897bd4..4609c88 100644 --- a/graph_data_generator_streamlit/ui/export_ui.py +++ b/graph_data_generator_streamlit/ui/export_ui.py @@ -1,9 +1,8 @@ import streamlit as st -from graph_data_generator import generators import graph_data_generator as gdg -from neo4j_uploader import upload, start_logging, stop_logging, UploadResult +from neo4j_uploader import upload, start_logging import json def export_ui(): diff --git a/graph_data_generator_streamlit/ui/ideate_ui.py b/graph_data_generator_streamlit/ui/ideate_ui.py index 5e39808..510c4a7 100644 --- a/graph_data_generator_streamlit/ui/ideate_ui.py +++ b/graph_data_generator_streamlit/ui/ideate_ui.py @@ -2,13 +2,13 @@ import streamlit as st from streamlit_agraph import agraph, Node, Edge, Config -import streamlit.components.v1 as components import logging import openai import json import random import base64 import ast +import os # Yes yes - move all the non-ui stuff into a controller or something already @@ -330,22 +330,19 @@ def ideate_ui(): # OPENAI TEXTFIELD new_open_ai_key = st.text_input(f'OpenAI KEY', type="password", value=st.session_state["OPENAI_API_KEY"]) + if new_open_ai_key is None or new_open_ai_key == "": + st.warning(f'OpenAI API Key required to use this feature') + return + # Set openAI key - openai.api_key = new_open_ai_key + openai.api_key = new_open_ai_key # This doesn't work in a Google Cloud Run instance + os.environ["OPENAI_API_KEY"] = new_open_ai_key # Display prompt for user input sample_prompt = "Sharks eat big fish. Big fish eat small fish. Small fish eat bugs." - run_openai = True - b1, b2 = st.columns(2) - with b1: - if st.button('Load Sample', key="graphgpt_sample"): - st.session_state["SAMPLE_PROMPT"] = sample_prompt - - with b2: - if st.button('Load Sample without OpenAI', key="graphgpt_sample_no_key"): - st.session_state["SAMPLE_PROMPT"] = sample_prompt - run_openai = False + if st.button('Load Sample', key="graphgpt_sample"): + st.session_state["SAMPLE_PROMPT"] = sample_prompt prompt = st.text_area("Prompt", value=st.session_state["SAMPLE_PROMPT"]) if prompt is None or prompt == "": @@ -354,13 +351,9 @@ def ideate_ui(): nodes = None edges = None - if run_openai == False: - # Load vetted response to save on hitting openai for the same thing - response = [["Sharks", "eat", "big fish"], ["Big fish", "eat", "small fish"], ["Small fish", "eat", "bugs"]] - else: - # Send completion request to openAI - full_prompt = triples_prompt(prompt) - response = generate_openai_response(full_prompt) + # Send completion request to openAI + full_prompt = triples_prompt(prompt) + response = generate_openai_response(full_prompt) # Convert response to agraph nodes and edges try: @@ -376,8 +369,8 @@ def ideate_ui(): # Display data st.write('Graph Viewer') - agraph(nodes=nodes, - edges=edges, + agraph(nodes=nodes, + edges=edges, config=config) # For displaying JSON schema. This can be quite long though @@ -399,4 +392,4 @@ def ideate_ui(): st.session_state["ARROWS_URI"] = uri with b2: if st.button("Push to Generator"): - st.session_state["ARROWS_DICT"] = arrows_dict \ No newline at end of file + st.session_state["ARROWS_DICT"] = arrows_dict diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3a2ba48 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,71 @@ +altair==5.2.0 ; python_version >= "3.11" and python_version < "4.0" +annotated-types==0.6.0 ; python_version >= "3.11" and python_version < "4.0" +anyio==4.2.0 ; python_version >= "3.11" and python_version < "4.0" +attrs==23.1.0 ; python_version >= "3.11" and python_version < "4.0" +blinker==1.7.0 ; python_version >= "3.11" and python_version < "4.0" +cachetools==5.3.2 ; python_version >= "3.11" and python_version < "4.0" +certifi==2023.11.17 ; python_version >= "3.11" and python_version < "4.0" +charset-normalizer==3.3.2 ; python_version >= "3.11" and python_version < "4.0" +click==8.1.7 ; python_version >= "3.11" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Windows" +distro==1.9.0 ; python_version >= "3.11" and python_version < "4.0" +faker==19.13.0 ; python_version >= "3.11" and python_version < "4.0" +gitdb==4.0.11 ; python_version >= "3.11" and python_version < "4.0" +gitpython==3.1.40 ; python_version >= "3.11" and python_version < "4.0" +graph-data-generator==0.4.1 ; python_version >= "3.11" and python_version < "4.0" +h11==0.14.0 ; python_version >= "3.11" and python_version < "4.0" +httpcore==1.0.2 ; python_version >= "3.11" and python_version < "4.0" +httpx==0.26.0 ; python_version >= "3.11" and python_version < "4.0" +idna==3.6 ; python_version >= "3.11" and python_version < "4.0" +importlib-metadata==6.11.0 ; python_version >= "3.11" and python_version < "4.0" +isodate==0.6.1 ; python_version >= "3.11" and python_version < "4.0" +jinja2==3.1.2 ; python_version >= "3.11" and python_version < "4.0" +jsonschema-specifications==2023.12.1 ; python_version >= "3.11" and python_version < "4.0" +jsonschema==4.20.0 ; python_version >= "3.11" and python_version < "4.0" +lorem-text==2.1 ; python_version >= "3.11" and python_version < "4.0" +markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "4.0" +markupsafe==2.1.3 ; python_version >= "3.11" and python_version < "4.0" +mdurl==0.1.2 ; python_version >= "3.11" and python_version < "4.0" +neo4j-uploader==0.4.1 ; python_version >= "3.11" and python_version < "4.0" +neo4j==5.16.0 ; python_version >= "3.11" and python_version < "4.0" +networkx==3.2.1 ; python_version >= "3.11" and python_version < "4.0" +numpy==1.26.2 ; python_version >= "3.11" and python_version < "4.0" +openai==1.6.1 ; python_version >= "3.11" and python_version < "4.0" +opencv-python==4.8.1.78 ; python_version >= "3.11" and python_version < "4.0" +packaging==23.2 ; python_version >= "3.11" and python_version < "4.0" +pandas==2.1.4 ; python_version >= "3.11" and python_version < "4.0" +pasteboard==0.3.3 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Darwin" +pillow==10.1.0 ; python_version >= "3.11" and python_version < "4.0" +protobuf==4.25.1 ; python_version >= "3.11" and python_version < "4.0" +pyarrow==14.0.2 ; python_version >= "3.11" and python_version < "4.0" +pyclip==0.7.0 ; python_version >= "3.11" and python_version < "4.0" +pydantic-core==2.14.6 ; python_version >= "3.11" and python_version < "4.0" +pydantic==2.5.3 ; python_version >= "3.11" and python_version < "4.0" +pydeck==0.8.0 ; python_version >= "3.11" and python_version < "4.0" +pygments==2.17.2 ; python_version >= "3.11" and python_version < "4.0" +pyparsing==3.1.1 ; python_version >= "3.11" and python_version < "4.0" +python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "4.0" +pytz==2023.3.post1 ; python_version >= "3.11" and python_version < "4.0" +pywin32==306 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Windows" +rdflib==7.0.0 ; python_version >= "3.11" and python_version < "4.0" +referencing==0.32.0 ; python_version >= "3.11" and python_version < "4.0" +requests==2.31.0 ; python_version >= "3.11" and python_version < "4.0" +rich==13.7.0 ; python_version >= "3.11" and python_version < "4.0" +rpds-py==0.16.2 ; python_version >= "3.11" and python_version < "4.0" +six==1.16.0 ; python_version >= "3.11" and python_version < "4.0" +smmap==5.0.1 ; python_version >= "3.11" and python_version < "4.0" +sniffio==1.3.0 ; python_version >= "3.11" and python_version < "4.0" +streamlit-agraph==0.0.45 ; python_version >= "3.11" and python_version < "4.0" +streamlit==1.29.0 ; python_version >= "3.11" and python_version < "4.0" +tenacity==8.2.3 ; python_version >= "3.11" and python_version < "4.0" +toml==0.10.2 ; python_version >= "3.11" and python_version < "4.0" +toolz==0.12.0 ; python_version >= "3.11" and python_version < "4.0" +tornado==6.4 ; python_version >= "3.11" and python_version < "4.0" +tqdm==4.66.1 ; python_version >= "3.11" and python_version < "4.0" +typing-extensions==4.9.0 ; python_version >= "3.11" and python_version < "4.0" +tzdata==2023.4 ; python_version >= "3.11" and python_version < "4.0" +tzlocal==5.2 ; python_version >= "3.11" and python_version < "4.0" +urllib3==2.1.0 ; python_version >= "3.11" and python_version < "4.0" +validators==0.22.0 ; python_version >= "3.11" and python_version < "4.0" +watchdog==3.0.0 ; python_version >= "3.11" and python_version < "4.0" and platform_system != "Darwin" +zipp==3.17.0 ; python_version >= "3.11" and python_version < "4.0"