Skip to content

Commit b2c62e0

Browse files
committed
Merge cloud branch
2 parents 72b38d2 + d66f503 commit b2c62e0

18 files changed

+987
-71
lines changed

Pipfile

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ streamlit-player = "*"
1515
requests = "*"
1616
pandas = "*"
1717
random-address = "*"
18+
openai = "*"
19+
numpy = "*"
1820

1921
[dev-packages]
2022
mock-generators = {editable = true, path = "."}

Pipfile.lock

+375-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mock_generators/app.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import streamlit as st
22
from constants import *
3+
from tabs.ideate_tab import ideate_tab
34
from tabs.importing_tab import import_tab
45
from tabs.design_tab import design_tab
56
from tabs.data_importer import data_importer_tab
67
from tabs.tutorial import tutorial_tab
8+
from tabs.getting_help import get_help_tab
9+
710
from config import setup_logging, preload_state, load_generators_to_streamlit
811

912
# SETUP
@@ -22,18 +25,24 @@
2225

2326
# 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.
2427

25-
t0, t1, t2, t5 = st.tabs([
26-
"⓪ Tutorial",
27-
"① Design",
28-
"② Generate",
29-
"③ Data Importer"
28+
t0, t1, t2, t3, t4, t5 = st.tabs([
29+
"⓪ Getting Started",
30+
"① Ideate",
31+
"② Design",
32+
"③ Generate",
33+
"④ Data Importer",
34+
"Ⓘ Info"
3035
])
3136

3237
with t0:
3338
tutorial_tab()
3439
with t1:
35-
design_tab()
40+
ideate_tab()
3641
with t2:
42+
design_tab()
43+
with t3:
3744
import_tab()
45+
with t4:
46+
data_importer_tab()
3847
with t5:
39-
data_importer_tab()
48+
get_help_tab()

mock_generators/generators/address_usa.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
from random_address import real_random_address_by_state
1+
from random_address import real_random_address
22
import random
33

4+
def generate(args: list[any]):
5+
# Generate a dictionary with valid random address information
6+
# states = [
7+
# "AL", "AR", "CA", "CO", "CT", "DC", "FL", "GA", "HI", "KY", "MA" "MD", "TN", "TX", "OK", "VT"
8+
# ]
9+
# state_code = random.choice(states)
10+
# return real_random_address_by_state(state_code)
11+
return real_random_address()
12+
413
def generate(args: list[any]):
514
# Generate a dictionary with valid random address information
615
states = [
+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# For converting data from various forms to other forms
2+
from streamlit_agraph import agraph, Node, Edge, Config
3+
import logging
4+
import json
5+
6+
def random_coordinates_for(
7+
number: int):
8+
import numpy as np
9+
10+
# Generate random coordinates
11+
min_range = 0
12+
max_width = number * 300
13+
x_values = np.random.uniform(min_range, max_width, number)
14+
y_values = np.random.uniform(min_range, max_width, number)
15+
16+
# Create list of (x, y) coordinates
17+
coordinates = list(zip(x_values, y_values))
18+
return coordinates
19+
20+
21+
def agraph_data_from_response(response: str)->tuple[any, any, any]:
22+
# Returns agraph compatible nodes, edges, and config
23+
logging.debug(f'Response: {response}')
24+
# Response will be a list of 3 item tuples
25+
try:
26+
answers = json.loads(response)
27+
except Exception as e:
28+
logging.error(e)
29+
return None, None, None
30+
if isinstance(answers, list) == False:
31+
logging.error(f'Response could not be converted to list, got {type(answers)} instead.')
32+
return None, None, None
33+
nodes = []
34+
# node_labels = []
35+
edges = []
36+
for idx, item in enumerate(answers):
37+
if item is None or len(item) != 3:
38+
continue
39+
n1_label = item[0]
40+
r = item[1]
41+
n2_label = item[2]
42+
43+
# We only want to add agraph nodes with the same label once in our return
44+
45+
# Gross but works
46+
add_n1 = True
47+
for node in nodes:
48+
if node.label == n1_label:
49+
add_n1 = False
50+
if add_n1:
51+
nodes.append(Node(id=n1_label, label=n1_label))
52+
53+
add_n2 = True
54+
for node in nodes:
55+
if node.label == n2_label:
56+
add_n2 = False
57+
if add_n2:
58+
nodes.append(Node(id=n2_label, label=n2_label))
59+
60+
# agraph requires source and target ids to use what we consider labels
61+
edge = Edge(source=n1_label, target=n2_label, label=r)
62+
edges.append(edge)
63+
config = Config(
64+
width=800,
65+
height=800,
66+
backgroundColor="#000000",
67+
directed=True)
68+
return (nodes, edges, config)
69+
70+
71+
def convert_agraph_nodes_to_arrows_nodes(
72+
agraph_nodes: list
73+
)-> list[dict]:
74+
# Convert agraph nodes to arrows nodes
75+
arrows_nodes = []
76+
77+
# Generate random coordinates to init new arrows nodes with - since we can't extract the location data from agraph
78+
coordinates = random_coordinates_for(len(agraph_nodes))
79+
80+
for nidx, node in enumerate(agraph_nodes):
81+
new_node = convert_agraph_node_to_arrows_node(
82+
nidx, node, coordinates[nidx][0], coordinates[nidx][1])
83+
arrows_nodes.append(new_node)
84+
return arrows_nodes
85+
86+
def convert_agraph_node_to_arrows_node(
87+
idx,
88+
node,
89+
x,
90+
y):
91+
# Convert agraph node to arrows node
92+
arrows_node = {
93+
"id": f'n{idx+1}',
94+
"caption": node.label,
95+
"position": {
96+
"x": x,
97+
"y": y,
98+
},
99+
"labels":[],
100+
"style": {},
101+
"properties": {}
102+
}
103+
return arrows_node
104+
105+
def convert_agraph_edge_to_arrows_relationship(
106+
idx,
107+
edge,
108+
arrows_nodes: list):
109+
# Example: {'source': 'People', 'from': 'People', 'to': 'Cars', 'color': '#F7A7A6', 'label': 'DRIVE'}
110+
source_node_label = edge.source
111+
target_node_label = edge.to
112+
source_node_id = None
113+
target_node_id = None
114+
115+
for node in arrows_nodes:
116+
if node['caption'] == source_node_label:
117+
source_node_id = node['id']
118+
if node['caption'] == target_node_label:
119+
target_node_id = node['id']
120+
121+
if source_node_id is None or target_node_id is None:
122+
node_info = [node.__dict__ for node in arrows_nodes]
123+
logging.error(f'Could not find source or target node for edge {edge.__dict__} from nodes: {node_info}')
124+
return None
125+
edge_type = edge.label
126+
arrows_relationship = {
127+
"id": f'n{idx+1}',
128+
"fromId": source_node_id,
129+
"toId": target_node_id,
130+
"type": edge_type,
131+
"properties": {},
132+
"style": {}
133+
}
134+
return arrows_relationship
135+
136+
def convert_agraph_to_arrows(agraph_nodes, agraph_edges):
137+
arrows_nodes = convert_agraph_nodes_to_arrows_nodes(agraph_nodes)
138+
139+
arrows_relationships = []
140+
for eidx, edge in enumerate(agraph_edges):
141+
new_relationship = convert_agraph_edge_to_arrows_relationship(eidx, edge, arrows_nodes=arrows_nodes)
142+
arrows_relationships.append(new_relationship)
143+
arrows_json = {
144+
"nodes": arrows_nodes,
145+
"relationships": arrows_relationships,
146+
"style": {
147+
"font-family": "sans-serif",
148+
"background-color": "#ffffff",
149+
"background-image": "",
150+
"background-size": "100%",
151+
"node-color": "#ffffff",
152+
"border-width": 4,
153+
"border-color": "#000000",
154+
"radius": 50,
155+
"node-padding": 5,
156+
"node-margin": 2,
157+
"outside-position": "auto",
158+
"node-icon-image": "",
159+
"node-background-image": "",
160+
"icon-position": "inside",
161+
"icon-size": 64,
162+
"caption-position": "inside",
163+
"caption-max-width": 200,
164+
"caption-color": "#000000",
165+
"caption-font-size": 50,
166+
"caption-font-weight": "normal",
167+
"label-position": "inside",
168+
"label-display": "pill",
169+
"label-color": "#000000",
170+
"label-background-color": "#ffffff",
171+
"label-border-color": "#000000",
172+
"label-border-width": 4,
173+
"label-font-size": 40,
174+
"label-padding": 5,
175+
"label-margin": 4,
176+
"directionality": "directed",
177+
"detail-position": "inline",
178+
"detail-orientation": "parallel",
179+
"arrow-width": 5,
180+
"arrow-color": "#000000",
181+
"margin-start": 5,
182+
"margin-end": 5,
183+
"margin-peer": 20,
184+
"attachment-start": "normal",
185+
"attachment-end": "normal",
186+
"relationship-icon-image": "",
187+
"type-color": "#000000",
188+
"type-background-color": "#ffffff",
189+
"type-border-color": "#000000",
190+
"type-border-width": 0,
191+
"type-font-size": 16,
192+
"type-padding": 5,
193+
"property-position": "outside",
194+
"property-alignment": "colon",
195+
"property-color": "#000000",
196+
"property-font-size": 16,
197+
"property-font-weight": "normal"
198+
}
199+
}
200+
return arrows_json
201+

mock_generators/logic/generate_values.py

+54-21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from models.generator import Generator, GeneratorType
33
import logging
44
import json
5+
import re
56

67
# ORIGINAL GENERATOR ASSIGNMENT
78
def actual_generator_for_raw_property(
@@ -94,11 +95,39 @@ def is_int(value: str) -> bool:
9495

9596
def is_float(value: str) -> bool:
9697
try:
97-
f = float(value)
98+
_ = float(value)
9899
return True
99100
except ValueError:
100101
return False
101102

103+
def find_longest_float_precision(float_list):
104+
max_precision = 0
105+
for num in float_list:
106+
num_str = str(num)
107+
if '.' in num_str:
108+
decimal_part = num_str.split('.')[1]
109+
precision = len(decimal_part)
110+
max_precision = max(max_precision, precision)
111+
return max_precision
112+
113+
def extract_numbers(string):
114+
# Use regex to find all number patterns in the string
115+
number_list = re.findall(r"-?\d+(?:\.\d+)?", string)
116+
117+
# Convert the extracted strings to appropriate number types
118+
number_list = [int(num) if num.isdigit() else float(num) for num in number_list]
119+
120+
return number_list
121+
122+
def numbers_list_from(string):
123+
# numbers = []
124+
# ranges = string.split('-')
125+
# for r in ranges:
126+
# numbers.extend(extract_numbers(r))
127+
# return numbers
128+
options = re.split(r'(?<=[^-])-', string)
129+
return options
130+
102131
def literal_generator_from_value(
103132
value: str,
104133
generators: list[Generator]
@@ -134,9 +163,10 @@ def literal_generator_from_value(
134163
# Original specificaion took stringified JSON objects to notate generator and args to use. We're going to convert matching literal values to appropriate generators
135164

136165
# Default is to use the literal generator
137-
result = {
138-
"string": [value]
139-
}
166+
result = None
167+
# result = {
168+
# "string": [value]
169+
# }
140170

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

148-
if is_float(value):
178+
if result is None and is_float(value):
149179
f = float(value)
150180
result = {
151181
"float": [f]
152182
}
153183

154184
# NOTE: Not currently handling complex literals
155185

156-
# Check if value is a range of ints or floats
157-
r = value.split("-")
158-
if len(r) == 2:
159-
# Single dash in string, possibly a literal range
160-
values = [r[0], r[1]]
161-
if all_ints(values):
162-
result = {
163-
"int_range": [int(r[0]), int(r[1])]
164-
}
165-
elif some_floats(values):
166-
# Float range function expects 3 args - this one seems more sensible than other functions
167-
result = {
168-
"float_range": [float(r[0]), float(r[1]), 2]
169-
}
170-
186+
# Check if value is a range of positive ints or floats
187+
if result is None:
188+
numbers = numbers_list_from(value)
189+
if len(numbers) == 2:
190+
# Check for correctly formatted int or float range string
191+
precision = find_longest_float_precision(numbers)
192+
if precision == 0:
193+
result = {
194+
"int_range": [int(numbers[0]), int(numbers[1])]
195+
}
196+
else:
197+
result = {
198+
"float_range": [float(numbers[0]), float(numbers[1]), precision]
199+
}
171200

172201
# Check for literal list of ints, floats, or strings
173-
if value.startswith('[') and value.endswith(']'):
202+
if result is None and value.startswith('[') and value.endswith(']'):
174203
values = value[1:-1].split(',')
175204
# 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
176205
# because certain generators could take multiple args from different text fields
@@ -193,6 +222,10 @@ def literal_generator_from_value(
193222
"string_from_list": values
194223
}
195224

225+
if result is None:
226+
result = {
227+
"string": [value]
228+
}
196229
# Package and return from legacy process
197230
actual_string = json.dumps(result)
198231
return actual_generator_for_raw_property(actual_string, generators)
22.4 KB
Loading
Loading
Loading

0 commit comments

Comments
 (0)