diff --git a/functions/texttostreet.py b/functions/texttostreet.py deleted file mode 100644 index 7ca1732..0000000 --- a/functions/texttostreet.py +++ /dev/null @@ -1,135 +0,0 @@ -import openai -import json -import os -import dotenv - -config = dotenv.dotenv_values(".env") -openai.api_key = config['OPENAI_API_KEY'] - -#openai.api_key = os.getenv("OPENAI_API_KEY") - -ASSIST_TEXT_FILENAME = 'prompt_bot.txt' - -def load_prompt(): - with open(ASSIST_TEXT_FILENAME, "r") as file: - return file.read() - - -def get_street(name, data): - """Return streetmix JSON with street description""" - # here will be valid JSON checking - print(name) - print(data) - return json.dumps({"name": name, "data": data}) - - -def get_streetmix_json(user_message): - # Step 1: send the conversation and available functions to GPT - assistant_description = load_prompt() - messages = [ - {"role": "system", "content": assistant_description}, - {"role": "system", "content": "Only use the functions you have been provided with."}, - {"role": "user", "content": user_message} - ] - functions = [ - { - "name": "get_street", - "description": "get 3d street (url with 3dstreet) by its description", - "parameters": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "name of created street", - "default": "default street" - }, - "data": { - "type": "object", - "properties": { - "rightBuildingVariant": { - "type": "string", - "description": "A string to determine which building variant to create for the right side of the street", - "enum": ["grass","narrow","residental","fence","parking-lot","waterfront","wide"], - "default": "grass" - }, - "leftBuildingVariant": { - "type": "string", - "description": "A string to determine which building variant to create for the left side of the street", - "enum": ["grass", "narrow", "residental", "fence", "parking-lot", "waterfront", "wide"], - "default": "grass" - }, - "street": { - "type": "object", - "properties": { - "width": { - "type": "number", - "description": "street length in meters", - "default": 150 - }, - "segments": { - "type": "array", - "description": "list of segments of a cross-section perspective of the 3D scene, each with a width in imperial feet units, a type in string format, and a variantString that applies modifications to the segment type", - "items": { - "type": "object", - "properties": { - "width": { - "type": "number", - "description": "segment width in imperial feet units", - "default": 9 - }, - "variantString": { - "type": "string", - "description": "Variant of segment. It's depend upon which segment type is selected. variantString values are separated by a pipe character (literally '|'). Most drive lane segments have an 'inbound' or 'outbound' value as the first variant." - }, - "type": { - "type": "string", - "description": "street segment type", - "enum": ["sidewalk","streetcar","bus-lane","drive-lane","light-rail","streetcar","turn-lane","divider","temporary","stencils","food-truck","flex-zone","sidewalk-wayfinding","sidewalk-bench","sidewalk-bike-rack","magic-carpet","outdoor-dining","parklet","bikeshare","utilities","sidewalk-tree","sidewalk-lamp","transit-shelter","parking-lane"], - "default": "sidewalk" - }, - "elevation": { - "type": "number", - "description": "elevation level for segment. 1 is default for all type of sidewalks and buildings, 0 is default for roads, lanes, parking, etc", - "enum": [0, 1], - "default": 1 - } - } - } - } - } - } - } - }, - }, - "required": ["data"], - }, - } - ] - response = openai.ChatCompletion.create( - model="gpt-3.5-turbo-0613", - messages=messages, - functions=functions, - function_call="auto", - # auto is default. "auto" means the model can pick between an end-user or calling a function - ) - response_message = response["choices"][0]["message"] - - # Step 2: check if GPT wanted to call a function - if response_message.get("function_call"): - # Step 3: call the function - # Note: the JSON response may not always be valid; be sure to handle errors - available_functions = { - "get_street": get_street, - } - function_name = response_message["function_call"]["name"] - fuction_to_call = available_functions[function_name] - function_args = json.loads(response_message["function_call"]["arguments"]) - print(function_args) - function_response = fuction_to_call( - name=function_args.get("name"), - data=function_args.get("data"), - ) - - return function_response # return JSON from get_street - - return response_message # in case if GPT not called a function diff --git a/functions/prompt_bot.txt b/prompt_bot.txt similarity index 93% rename from functions/prompt_bot.txt rename to prompt_bot.txt index 8af2b59..4859867 100644 --- a/functions/prompt_bot.txt +++ b/prompt_bot.txt @@ -103,5 +103,3 @@ For example, if a user says "show me a street with trains, sidewalks, trees and ``` The output of this JSON representing a street's cross-section is extruded 150 meters, and additional street props and/or vehicle and pedestrian models are placed along each segment's extruded plane to generate a low fidelity realtime rendering of a 3D street scene that is navigable or editable by the user making the original request. - -A final segment should exist with a `width` of 0, `type` "suggestion", and variantString to consist of a few sentences in plain language to suggest safer street treatments including but not limited to adding protected concrete barriers or bollards to protect vulnerable road users such as pedestrians vs. motor vehicles of curb weight great than 1,000 lbs. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index da9cae6..133cd9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ discord.py==2.0.1 python-dotenv==0.20.0 openai -firebase_functions +flask \ No newline at end of file diff --git a/simple-server.py b/simple-server.py new file mode 100644 index 0000000..1cfe5f9 --- /dev/null +++ b/simple-server.py @@ -0,0 +1,33 @@ +from flask import Flask, render_template, request, redirect +from texttostreet import get_streetmix_json +import urllib.parse + +app = Flask(__name__) + +@app.route('/', methods=['GET', 'POST']) +def form(): + return render_template('form.html') + +@app.route('/querytext', methods=['GET', 'POST']) +def querytext(): + user_query = request.form['query'] + if user_query: + streetmix_json = get_streetmix_json(user_query) + #return render_template('localhost:7001/index-bot.html', street_json=streetmix_json) + localhost_url = 'http://localhost:7001/index-bot.html' + street_url = "{localhost}#streetmix-json:{streetmix_json}".format( + localhost=localhost_url, + streetmix_json=urllib.parse.quote(streetmix_json) + ) + return redirect(street_url) + +@app.route('/json_street', methods=['GET', 'POST']) +def json_street(): + user_query = request.form['query'] + if user_query: + streetmix_json = get_streetmix_json(user_query) + return streetmix_json + + +if __name__ == "__main__": + app.run() \ No newline at end of file diff --git a/streetmix.json b/streetmix.json new file mode 100644 index 0000000..a0c15d3 --- /dev/null +++ b/streetmix.json @@ -0,0 +1,159 @@ +{ + "properties": { + "name": { + "type": "string", + "description": "name of created street", + "default": "default street" + }, + "data": { + "type": "object", + "properties": { + "street": { + "type": "object", + "properties": { + "rightBuildingVariant": { + "type": "string", + "description": "A string to determine which building variant to create for the right side of the street. Can use only provided variants from enum array", + "enum": [ + "grass", + "narrow", + "residental", + "fence", + "parking-lot", + "waterfront", + "wide" + ], + "default": "grass" + }, + "leftBuildingVariant": { + "type": "string", + "description": "A string to determine which building variant to create for the left side of the street. Can use only provided variants from enum array", + "enum": [ + "grass", + "narrow", + "residental", + "fence", + "parking-lot", + "waterfront", + "wide" + ], + "default": "grass" + }, + "width": { + "type": "number", + "description": "street width in imperial feet units. This is the sum of the width values of all segments" + }, + "segments": { + "type": "array", + "description": "list of segments of a cross-section perspective of the 3D scene, each with a width in imperial feet units, a type in string format, and a variantString that applies modifications to the segment type", + "items": { + "type": "object", + "properties": { + "width": { + "type": "number", + "description": "segment width in imperial feet units. Here is minimal width for each type of segment: {'sidewalk-tree': 4, 'sidewalk-bike-rack': 5, 'sidewalk-bench': 4, 'sidewalk-wayfinding': 4, 'sidewalk-lamp': 4, 'bus-lane': 12, 'drive-lane': 10, 'light-rail': 12, 'streetcar': 12, 'turn-lane': 10, 'divider': 2, 'stencils': 12, 'food-truck': 10, 'flex-zone': 7, 'parking-lane': 7 for inbound and outbound variantString and 14 for other variants}", + "default": 9 + }, + "variantString": { + "type": "string", + "oneOf": [ + { + "items": { + "enum": ["inbound|car","outbound|car"], + "description": "variants for drive-lane segments" + } + }, + { + "items": { + "enum": ["empty", "sparse", "normal", "dense"], + "description": "variants for sidewalks segments. Default is 'normal'" + } + }, + { + "items": { + "enum": ["right|modern","both|modern", "left|modern", "right|traditional", "both|traditional", "left|traditional", "right|pride", "both|pride", "left|pride"], + "description": "variants for sidewalk-lamp segments. Default is 'both|traditional'" + } + }, + { + "items": { + "enum": ["left", "right"], + "description": "variants for utilities segments. Default is 'normal'" + } + }, + { + "items": { + "enum": ["left", "right", "left-right-straight", "shared", "both", "left-straight", "right-straight", "straight"], + "description": "variants for turn-lane segments" + } + }, + { + "items": { + "enum": ["big", "palm-tree"], + "description": "variants for sidewalk-tree segments" + } + }, + { + "items": { + "enum": ["sideways|right", "sideways|left", "inbound|right", "inbound|left", "outbound|left", "outbound|right", "angled-front-left|left", "angled-front-right|left", "angled-rear-left|left", "angled-rear-right|left", "angled-front-left|right", "angled-front-right|right", "angled-rear-left|right", "angled-rear-right|right"], + "description": "variants for parking-lane segments" + } + } + ], + "description": "Variant of segment. It's required propery. List of possible variants depend upon which segment type is selected. variantString values are separated with their subvariants by a pipe character (literally '|'). Drive-lane segments possible variants: 'inbound|car' (default variant) or 'outbound|car'. Most of drive-lane segments have a car subvariant by default. Sidewalk segments possible variants: empty/sparse/normal/dense variants, depends on pedestrian count, default is normal. Sidewalk could have a lamps (sidewalk-lamp type of segment), variants: right, left, both; and subvariants (through |): modern, traditional, pride. For example and by default 'right|modern'. Turn lane (turn-lane segment type) variants: left, right, left-right-straight, shared, both, left-straight, right-straight, straight. Sidewalk tree variants: palm-tree, big. Parking-lane variants: sideways (subvariants right,left), inbound or outbound (subvariants right,left), angled-front-left (angled front left), angled-front-right (angled front right), angled-rear-left (angled rear left), angled-rear-right (angled rear right)." + }, + "type": { + "type": "string", + "description": "street segment type. Using default width for each segment from streetmix API. Use default variant for each type of segment", + "enum": [ + "sidewalk", + "bus-lane", + "drive-lane", + "light-rail", + "streetcar", + "turn-lane", + "divider", + "temporary", + "stencils", + "food-truck", + "flex-zone", + "sidewalk-wayfinding", + "sidewalk-bench", + "sidewalk-bike-rack", + "magic-carpet", + "outdoor-dining", + "parklet", + "bikeshare", + "utilities", + "sidewalk-tree", + "sidewalk-lamp", + "transit-shelter", + "parking-lane" + ], + "default": "sidewalk" + }, + "elevation": { + "type": "number", + "description": "elevation level for segment. 1 is default for all type of sidewalks and buildings, 0 is default for roads, lanes, parking, etc", + "enum": [ + 0, + 1 + ], + "default": 1 + } + }, + "required": [ + "variantString", "type", "width" + ] + } + } + } + } + } + } + }, + "required": [ + "data" + ], + "type": "object" +} \ No newline at end of file diff --git a/templates/form.html b/templates/form.html new file mode 100644 index 0000000..96521b9 --- /dev/null +++ b/templates/form.html @@ -0,0 +1,43 @@ + + + + + Python form submission example + + + +
+
+ + +
+
+ +
+
+ + \ No newline at end of file diff --git a/texttostreet.py b/texttostreet.py new file mode 100644 index 0000000..9dd54e4 --- /dev/null +++ b/texttostreet.py @@ -0,0 +1,79 @@ +import openai +import json +import os +from dotenv import dotenv_values + +config = dotenv_values(".env") +openai.api_key = config["OPENAI_API_KEY"] + +#openai.api_key = os.getenv("OPENAI_API_KEY") + +ASSIST_TEXT_FILENAME = 'prompt_bot.txt' +STREETMIX_SCHEMA_FILENAME = 'streetmix.json' + + +def load_prompt(): + with open(ASSIST_TEXT_FILENAME, "r") as file: + return file.read() + + +def load_streetmix_schema(): + with open(STREETMIX_SCHEMA_FILENAME, "r") as file: + return json.loads(file.read()) + + +def get_street(name, title, data): + """Return streetmix JSON with street description""" + # here will be valid JSON checking + print(name) + print(data) + return json.dumps({"name": title, "data": data}) + + +def get_streetmix_json(user_message): + # Step 1: send the conversation and available functions to GPT + assistant_description = load_prompt() + sreetmix_schema = load_streetmix_schema() + messages = [ + {"role": "system", "content": assistant_description}, + {"role": "system", "content": "Only use the functions you have been provided with. Use default values for properties if they are not provided. Only output valid JSON"}, + {"role": "user", "content": user_message} + ] + functions = [ + { + "name": "get_street", + "description": "get 3d street (url with 3dstreet) by its description", + "parameters": sreetmix_schema + } + ] + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo-0613", + messages=messages, + functions=functions, + function_call="auto", + # auto is default. "auto" means the model can pick between an end-user or calling a function + ) + response_message = response["choices"][0]["message"] + + # Step 2: check if GPT wanted to call a function + if response_message.get("function_call"): + # Step 3: call the function + # Note: the JSON response may not always be valid; be sure to handle errors + available_functions = { + "get_street": get_street, + } + function_name = response_message["function_call"]["name"] + fuction_to_call = available_functions[function_name] + function_args = json.loads(response_message["function_call"]["arguments"]) + print(function_args) + function_response = fuction_to_call( + name=function_args.get("name"), + title=user_message, + data=function_args.get("data"), + ) + + return function_response # return JSON from get_street + + return response_message # in case if GPT not called a function + +# get_streetmix_json("create a street with sidewalks and palm trees, red bicicle roads, bus station, train and car roads. with grass in one side and buildings in another") \ No newline at end of file