├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md └── addons └── OpenAI ├── ExecuteWindow.gd ├── ExecuteWindow.tscn ├── OpenAI.gd ├── dock.gd ├── dock.tscn └── plugin.cfg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jakub Buriánek 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # godot-openai 2 | Prototype to implement OpenAI to Godot 4. Inspired by https://github.com/keijiro/AICommand 3 | 4 | To use this plugin, please 5 | 1) Create api key at https://platform.openai.com/account/api-keys (create free account) 6 | 2) Install this plugin 7 | 3) Go to project settings and create new setting `open_ai/api_key` and put your api key there 8 | 4) ... 9 | 5) Profit 10 | 11 | ## Note 12 | This is very much a prototype/demo to see, if such integration is possible. 13 | -------------------------------------------------------------------------------- /addons/OpenAI/ExecuteWindow.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends PopupPanel 3 | 4 | signal execute_code(code: String) 5 | 6 | func offerCode(code: String): 7 | %CodeEdit.text = code 8 | 9 | func _on_execute_pressed(): 10 | execute_code.emit(%CodeEdit.text) 11 | queue_free() 12 | 13 | func _on_cancel_pressed(): 14 | queue_free() 15 | 16 | -------------------------------------------------------------------------------- /addons/OpenAI/ExecuteWindow.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://wmrneiumv7vc"] 2 | 3 | [ext_resource type="Script" path="res://addons/OpenAI/ExecuteWindow.gd" id="1_ls01p"] 4 | 5 | [sub_resource type="CodeHighlighter" id="CodeHighlighter_a777b"] 6 | number_color = Color(0.576471, 0.815686, 0.72549, 1) 7 | symbol_color = Color(0.231373, 0.447059, 0.501961, 1) 8 | function_color = Color(0.341176, 0.698039, 1, 1) 9 | member_variable_color = Color(0.52549, 0.623529, 0.713726, 1) 10 | keyword_colors = { 11 | "@tool": Color(0.462745, 0.352941, 0.27451, 1), 12 | "extends": Color(0.466667, 0.254902, 0.301961, 1) 13 | } 14 | 15 | [sub_resource type="InputEventKey" id="InputEventKey_eb4ry"] 16 | device = -1 17 | ctrl_pressed = true 18 | keycode = 4194309 19 | 20 | [sub_resource type="Shortcut" id="Shortcut_153sv"] 21 | events = [SubResource("InputEventKey_eb4ry")] 22 | 23 | [node name="Panel" type="PopupPanel"] 24 | initial_position = 2 25 | title = "OpenAI Response" 26 | size = Vector2i(1280, 720) 27 | visible = true 28 | unresizable = false 29 | borderless = false 30 | script = ExtResource("1_ls01p") 31 | 32 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 33 | offset_left = 4.0 34 | offset_top = 4.0 35 | offset_right = 1276.0 36 | offset_bottom = 716.0 37 | 38 | [node name="Response" type="Label" parent="VBoxContainer"] 39 | layout_mode = 2 40 | text = "Response from OpenAI. This will execute code in \"callMe\" function" 41 | 42 | [node name="CodeEdit" type="CodeEdit" parent="VBoxContainer"] 43 | unique_name_in_owner = true 44 | layout_mode = 2 45 | size_flags_vertical = 3 46 | syntax_highlighter = SubResource("CodeHighlighter_a777b") 47 | 48 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] 49 | layout_mode = 2 50 | alignment = 1 51 | 52 | [node name="Execute" type="Button" parent="VBoxContainer/HBoxContainer"] 53 | layout_mode = 2 54 | shortcut = SubResource("Shortcut_153sv") 55 | text = "Execute" 56 | 57 | [node name="Cancel" type="Button" parent="VBoxContainer/HBoxContainer"] 58 | layout_mode = 2 59 | text = "Cancel" 60 | 61 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/Execute" to="." method="_on_execute_pressed"] 62 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/Cancel" to="." method="_on_cancel_pressed"] 63 | -------------------------------------------------------------------------------- /addons/OpenAI/OpenAI.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | var dock 5 | 6 | func _enter_tree(): 7 | dock = load("res://addons/OpenAI/dock.tscn").instantiate() 8 | add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, dock) 9 | dock.find_child("Reload").connect("pressed", self.reload) 10 | 11 | func _exit_tree(): 12 | remove_control_from_docks(dock) 13 | dock.queue_free() 14 | 15 | func reload(): 16 | print("Reloading OpenAI") 17 | _exit_tree() 18 | _enter_tree() 19 | -------------------------------------------------------------------------------- /addons/OpenAI/dock.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | const apiKeyName = "open_ai/api_key" 5 | var lastCode = "# No response yet. Please submit your query first" 6 | 7 | func _ready(): 8 | if !ProjectSettings.has_setting(apiKeyName): 9 | ProjectSettings.set_initial_value(apiKeyName, '') 10 | 11 | func constructAIQuery() -> String: 12 | var input = %Query.text 13 | var query = """Write a Godot Engine script. 14 | - It extends Node 15 | - Expect Godot 4 16 | - Use GDScript version 2 17 | - Put all your code into callMe method 18 | - I only need the script body. Don’t add any explanation. 19 | - If you are doing operation on nodes, access them via "get_tree().get_edited_scene_root()" 20 | - If you are doing operation on nodes, set their owner to "get_tree().get_edited_scene_root()" 21 | The task is as follows: 22 | %s""" % input 23 | return query 24 | 25 | func _on_submit_pressed(): 26 | var query = constructAIQuery() 27 | sendQuery(query) 28 | 29 | func sendQuery(query: String): 30 | print("OpenAI: Querying AI") 31 | var http_request = HTTPRequest.new() 32 | add_child(http_request) 33 | http_request.request_completed.connect(self._http_request_completed) 34 | #http_request.request_completed.connect(func (x1,x2,x3,x4): http_request.queue_free()) 35 | 36 | var body = JSON.new().stringify({"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": query}]}) 37 | var error = http_request.request("https://api.openai.com/v1/chat/completions", ["Authorization: Bearer %s" % ProjectSettings.get_setting(apiKeyName), "Content-Type: application/json"], HTTPClient.METHOD_POST, body) 38 | if error != OK: 39 | push_error("An error occurred in the HTTP request.") 40 | 41 | func _http_request_completed(result, response_code, headers, body): 42 | print("OpenAI: Parsing reply") 43 | var json = JSON.new() 44 | json.parse(body.get_string_from_utf8()) 45 | var response = json.get_data() 46 | 47 | var code = response.choices[0].message.content 48 | var source = "@tool\n%s" % code 49 | lastCode = source 50 | if %ExecuteImmediately.button_pressed: 51 | executeCode(source) 52 | else: 53 | showCodePopup(source) 54 | 55 | func showCodePopup(code: String) -> void: 56 | var popup = load("res://addons/OpenAI/ExecuteWindow.tscn").instantiate() 57 | add_child(popup) 58 | popup.offerCode(code) 59 | popup.execute_code.connect(self.executeCode) 60 | popup.popup_centered() 61 | 62 | func executeCode(code: String) -> void: 63 | lastCode = code 64 | var exec = GDScript.new() 65 | exec.source_code = code 66 | exec.reload() 67 | print("OpenAI: Executing code") 68 | print_verbose(code) 69 | %Executer.set_script(exec) 70 | if "callMe" in %Executer: 71 | %Executer.callMe() 72 | print("OpenAI: Done") 73 | 74 | 75 | func _on_open_last_pressed(): 76 | showCodePopup(lastCode) 77 | -------------------------------------------------------------------------------- /addons/OpenAI/dock.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bovneiifihlvs"] 2 | 3 | [ext_resource type="Script" path="res://addons/OpenAI/dock.gd" id="1_ugsn2"] 4 | 5 | [sub_resource type="Theme" id="Theme_fjagg"] 6 | 7 | [node name="OpenAI" type="Control"] 8 | layout_mode = 3 9 | anchors_preset = 15 10 | anchor_right = 1.0 11 | anchor_bottom = 1.0 12 | grow_horizontal = 2 13 | grow_vertical = 2 14 | script = ExtResource("1_ugsn2") 15 | 16 | [node name="ScrollContainer" type="ScrollContainer" parent="."] 17 | custom_minimum_size = Vector2(100, 100) 18 | layout_mode = 1 19 | anchors_preset = 15 20 | anchor_right = 1.0 21 | anchor_bottom = 1.0 22 | grow_horizontal = 2 23 | grow_vertical = 2 24 | follow_focus = true 25 | horizontal_scroll_mode = 0 26 | vertical_scroll_mode = 2 27 | 28 | [node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"] 29 | layout_mode = 2 30 | size_flags_horizontal = 3 31 | size_flags_vertical = 3 32 | 33 | [node name="Query" type="TextEdit" parent="ScrollContainer/VBoxContainer"] 34 | unique_name_in_owner = true 35 | layout_mode = 2 36 | size_flags_horizontal = 3 37 | size_flags_vertical = 3 38 | theme = SubResource("Theme_fjagg") 39 | text = "Print \"Hello World\"" 40 | placeholder_text = "Enter query" 41 | wrap_mode = 1 42 | highlight_current_line = true 43 | draw_control_chars = true 44 | draw_tabs = true 45 | 46 | [node name="Submit" type="Button" parent="ScrollContainer/VBoxContainer"] 47 | layout_mode = 2 48 | text = "Submit" 49 | 50 | [node name="ExecuteImmediately" type="CheckButton" parent="ScrollContainer/VBoxContainer"] 51 | unique_name_in_owner = true 52 | layout_mode = 2 53 | text = "Run immediately" 54 | 55 | [node name="OpenLast" type="Button" parent="ScrollContainer/VBoxContainer"] 56 | layout_mode = 2 57 | text = "Open last" 58 | 59 | [node name="Reload" type="Button" parent="ScrollContainer/VBoxContainer"] 60 | layout_mode = 2 61 | text = "Reload plugin" 62 | 63 | [node name="Executer" type="Node" parent="."] 64 | unique_name_in_owner = true 65 | 66 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/Submit" to="." method="_on_submit_pressed"] 67 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/OpenLast" to="." method="_on_open_last_pressed"] 68 | -------------------------------------------------------------------------------- /addons/OpenAI/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="OpenAI" 4 | description="" 5 | author="Buri" 6 | version="" 7 | script="OpenAI.gd" 8 | --------------------------------------------------------------------------------