Skip to content

Commit

Permalink
Merge cloud branch
Browse files Browse the repository at this point in the history
  • Loading branch information
jalakoo committed May 25, 2023
2 parents 72b38d2 + d66f503 commit b2c62e0
Show file tree
Hide file tree
Showing 18 changed files with 987 additions and 71 deletions.
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ streamlit-player = "*"
requests = "*"
pandas = "*"
random-address = "*"
openai = "*"
numpy = "*"

[dev-packages]
mock-generators = {editable = true, path = "."}
Expand Down
385 changes: 375 additions & 10 deletions Pipfile.lock

Large diffs are not rendered by default.

23 changes: 16 additions & 7 deletions mock_generators/app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import streamlit as st
from constants import *
from tabs.ideate_tab import ideate_tab
from tabs.importing_tab import import_tab
from tabs.design_tab import design_tab
from tabs.data_importer import data_importer_tab
from tabs.tutorial import tutorial_tab
from tabs.getting_help import get_help_tab

from config import setup_logging, preload_state, load_generators_to_streamlit

# SETUP
Expand All @@ -22,18 +25,24 @@

# Streamlit runs from top-to-bottom from tabs 1 through 8. This is essentially one giant single page app. Earlier attempt to use Streamlit's multi-page app functionality resulted in an inconsistent state between pages.

t0, t1, t2, t5 = st.tabs([
"⓪ Tutorial",
"① Design",
"② Generate",
"③ Data Importer"
t0, t1, t2, t3, t4, t5 = st.tabs([
"⓪ Getting Started",
"① Ideate",
"② Design",
"③ Generate",
"④ Data Importer",
"Ⓘ Info"
])

with t0:
tutorial_tab()
with t1:
design_tab()
ideate_tab()
with t2:
design_tab()
with t3:
import_tab()
with t4:
data_importer_tab()
with t5:
data_importer_tab()
get_help_tab()
11 changes: 10 additions & 1 deletion mock_generators/generators/address_usa.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from random_address import real_random_address_by_state
from random_address import real_random_address
import random

def generate(args: list[any]):
# Generate a dictionary with valid random address information
# states = [
# "AL", "AR", "CA", "CO", "CT", "DC", "FL", "GA", "HI", "KY", "MA" "MD", "TN", "TX", "OK", "VT"
# ]
# state_code = random.choice(states)
# return real_random_address_by_state(state_code)
return real_random_address()

def generate(args: list[any]):
# Generate a dictionary with valid random address information
states = [
Expand Down
201 changes: 201 additions & 0 deletions mock_generators/logic/agraph_conversions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# For converting data from various forms to other forms
from streamlit_agraph import agraph, Node, Edge, Config
import logging
import json

def random_coordinates_for(
number: int):
import numpy as np

# Generate random coordinates
min_range = 0
max_width = number * 300
x_values = np.random.uniform(min_range, max_width, number)
y_values = np.random.uniform(min_range, max_width, number)

# Create list of (x, y) coordinates
coordinates = list(zip(x_values, y_values))
return coordinates


def agraph_data_from_response(response: str)->tuple[any, any, any]:
# Returns agraph compatible nodes, edges, and config
logging.debug(f'Response: {response}')
# Response will be a list of 3 item tuples
try:
answers = json.loads(response)
except Exception as e:
logging.error(e)
return None, None, None
if isinstance(answers, list) == False:
logging.error(f'Response could not be converted to list, got {type(answers)} instead.')
return None, None, None
nodes = []
# node_labels = []
edges = []
for idx, item in enumerate(answers):
if item is None or len(item) != 3:
continue
n1_label = item[0]
r = item[1]
n2_label = item[2]

# We only want to add agraph nodes with the same label once in our return

# Gross but works
add_n1 = True
for node in nodes:
if node.label == n1_label:
add_n1 = False
if add_n1:
nodes.append(Node(id=n1_label, label=n1_label))

add_n2 = True
for node in nodes:
if node.label == n2_label:
add_n2 = False
if add_n2:
nodes.append(Node(id=n2_label, label=n2_label))

# agraph requires source and target ids to use what we consider labels
edge = Edge(source=n1_label, target=n2_label, label=r)
edges.append(edge)
config = Config(
width=800,
height=800,
backgroundColor="#000000",
directed=True)
return (nodes, edges, config)


def convert_agraph_nodes_to_arrows_nodes(
agraph_nodes: list
)-> list[dict]:
# Convert agraph nodes to arrows nodes
arrows_nodes = []

# Generate random coordinates to init new arrows nodes with - since we can't extract the location data from agraph
coordinates = random_coordinates_for(len(agraph_nodes))

for nidx, node in enumerate(agraph_nodes):
new_node = convert_agraph_node_to_arrows_node(
nidx, node, coordinates[nidx][0], coordinates[nidx][1])
arrows_nodes.append(new_node)
return arrows_nodes

def convert_agraph_node_to_arrows_node(
idx,
node,
x,
y):
# Convert agraph node to arrows node
arrows_node = {
"id": f'n{idx+1}',
"caption": node.label,
"position": {
"x": x,
"y": y,
},
"labels":[],
"style": {},
"properties": {}
}
return arrows_node

def convert_agraph_edge_to_arrows_relationship(
idx,
edge,
arrows_nodes: list):
# Example: {'source': 'People', 'from': 'People', 'to': 'Cars', 'color': '#F7A7A6', 'label': 'DRIVE'}
source_node_label = edge.source
target_node_label = edge.to
source_node_id = None
target_node_id = None

for node in arrows_nodes:
if node['caption'] == source_node_label:
source_node_id = node['id']
if node['caption'] == target_node_label:
target_node_id = node['id']

if source_node_id is None or target_node_id is None:
node_info = [node.__dict__ for node in arrows_nodes]
logging.error(f'Could not find source or target node for edge {edge.__dict__} from nodes: {node_info}')
return None
edge_type = edge.label
arrows_relationship = {
"id": f'n{idx+1}',
"fromId": source_node_id,
"toId": target_node_id,
"type": edge_type,
"properties": {},
"style": {}
}
return arrows_relationship

def convert_agraph_to_arrows(agraph_nodes, agraph_edges):
arrows_nodes = convert_agraph_nodes_to_arrows_nodes(agraph_nodes)

arrows_relationships = []
for eidx, edge in enumerate(agraph_edges):
new_relationship = convert_agraph_edge_to_arrows_relationship(eidx, edge, arrows_nodes=arrows_nodes)
arrows_relationships.append(new_relationship)
arrows_json = {
"nodes": arrows_nodes,
"relationships": arrows_relationships,
"style": {
"font-family": "sans-serif",
"background-color": "#ffffff",
"background-image": "",
"background-size": "100%",
"node-color": "#ffffff",
"border-width": 4,
"border-color": "#000000",
"radius": 50,
"node-padding": 5,
"node-margin": 2,
"outside-position": "auto",
"node-icon-image": "",
"node-background-image": "",
"icon-position": "inside",
"icon-size": 64,
"caption-position": "inside",
"caption-max-width": 200,
"caption-color": "#000000",
"caption-font-size": 50,
"caption-font-weight": "normal",
"label-position": "inside",
"label-display": "pill",
"label-color": "#000000",
"label-background-color": "#ffffff",
"label-border-color": "#000000",
"label-border-width": 4,
"label-font-size": 40,
"label-padding": 5,
"label-margin": 4,
"directionality": "directed",
"detail-position": "inline",
"detail-orientation": "parallel",
"arrow-width": 5,
"arrow-color": "#000000",
"margin-start": 5,
"margin-end": 5,
"margin-peer": 20,
"attachment-start": "normal",
"attachment-end": "normal",
"relationship-icon-image": "",
"type-color": "#000000",
"type-background-color": "#ffffff",
"type-border-color": "#000000",
"type-border-width": 0,
"type-font-size": 16,
"type-padding": 5,
"property-position": "outside",
"property-alignment": "colon",
"property-color": "#000000",
"property-font-size": 16,
"property-font-weight": "normal"
}
}
return arrows_json

75 changes: 54 additions & 21 deletions mock_generators/logic/generate_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from models.generator import Generator, GeneratorType
import logging
import json
import re

# ORIGINAL GENERATOR ASSIGNMENT
def actual_generator_for_raw_property(
Expand Down Expand Up @@ -94,11 +95,39 @@ def is_int(value: str) -> bool:

def is_float(value: str) -> bool:
try:
f = float(value)
_ = float(value)
return True
except ValueError:
return False

def find_longest_float_precision(float_list):
max_precision = 0
for num in float_list:
num_str = str(num)
if '.' in num_str:
decimal_part = num_str.split('.')[1]
precision = len(decimal_part)
max_precision = max(max_precision, precision)
return max_precision

def extract_numbers(string):
# Use regex to find all number patterns in the string
number_list = re.findall(r"-?\d+(?:\.\d+)?", string)

# Convert the extracted strings to appropriate number types
number_list = [int(num) if num.isdigit() else float(num) for num in number_list]

return number_list

def numbers_list_from(string):
# numbers = []
# ranges = string.split('-')
# for r in ranges:
# numbers.extend(extract_numbers(r))
# return numbers
options = re.split(r'(?<=[^-])-', string)
return options

def literal_generator_from_value(
value: str,
generators: list[Generator]
Expand Down Expand Up @@ -134,9 +163,10 @@ def literal_generator_from_value(
# Original specificaion took stringified JSON objects to notate generator and args to use. We're going to convert matching literal values to appropriate generators

# Default is to use the literal generator
result = {
"string": [value]
}
result = None
# result = {
# "string": [value]
# }

# Check if value is an int or float
if is_int(value):
Expand All @@ -145,32 +175,31 @@ def literal_generator_from_value(
"int": [integer]
}

if is_float(value):
if result is None and is_float(value):
f = float(value)
result = {
"float": [f]
}

# NOTE: Not currently handling complex literals

# Check if value is a range of ints or floats
r = value.split("-")
if len(r) == 2:
# Single dash in string, possibly a literal range
values = [r[0], r[1]]
if all_ints(values):
result = {
"int_range": [int(r[0]), int(r[1])]
}
elif some_floats(values):
# Float range function expects 3 args - this one seems more sensible than other functions
result = {
"float_range": [float(r[0]), float(r[1]), 2]
}

# Check if value is a range of positive ints or floats
if result is None:
numbers = numbers_list_from(value)
if len(numbers) == 2:
# Check for correctly formatted int or float range string
precision = find_longest_float_precision(numbers)
if precision == 0:
result = {
"int_range": [int(numbers[0]), int(numbers[1])]
}
else:
result = {
"float_range": [float(numbers[0]), float(numbers[1]), precision]
}

# Check for literal list of ints, floats, or strings
if value.startswith('[') and value.endswith(']'):
if result is None and value.startswith('[') and value.endswith(']'):
values = value[1:-1].split(',')
# Generators take a strange format where the args are always a string - including # lists of other data, like ints, floats. ie ["1,2,3"] is an expected arg type
# because certain generators could take multiple args from different text fields
Expand All @@ -193,6 +222,10 @@ def literal_generator_from_value(
"string_from_list": values
}

if result is None:
result = {
"string": [value]
}
# Package and return from legacy process
actual_string = json.dumps(result)
return actual_generator_for_raw_property(actual_string, generators)
Expand Down
Binary file added mock_generators/media/sample_node_0-5-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mock_generators/media/sample_properties_0-5-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b2c62e0

Please sign in to comment.