diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8ad74f78 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..40a9732e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Godot 4+ specific ignores +.godot/ +.build/ +.test/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..b1471de2 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +

🐶 Godog 🐶

+

AI Godot 桌宠

+ +简体中文 / [English](README_EN.md) + +## 👋 介绍 + +🚀 🚀 🚀 一款由大语言模型驱动、Godot 制作的 AI 桌宠,旨在提供一个全能的、丰富的桌面AI宠物。你可以将其作为基础,构建你自己独特的桌宠形象和角色行为 + +- 使用Godot:轻量(内存占用90mb)、开发简单、提供动画、控件UI 等框架 +- 兼容大量的大语言模型 +- 有预制角色功能,多种形象 +- 支持多模态 +- 在此基础上,方便构建自己的 AI 桌宠 + +![](/img/example.png) + +## 📙 使用手册 + +### ⬇️ 安装 + +下载地址:查看 `release` + +### 🔑 使用 +1. 点击 `编辑预设`,填写名称,API Key,点击 **保存(修改)** +2. 点击 `存储预设`,选中刚刚添加的预设 +3. 开始对话 + +> [!Caution] +> 当前仅支持 OpenAI 的视觉能力(VISION),建议使用 **gpt-4o** 模型 +> 你可以使用其他模型,但不支持多模态 + +### ⌨️ 快捷键 +- **鼠标右键** 角色区域弹出对话框 +- 当焦点在角色窗口上,按 **ESC** 关闭软件 + +## 🚦开发计划 + +- [ ] 添加多语言的支持 +- [ ] 添加更多个性化角色的动画,机制,事件 +- [ ] 添加更多模型支持 +- [ ] 添加对话框对 markdown 语法渲染的支持 +- [ ] 添加语音、文件上传等多模态的能力 +- [ ] 添加更多的预设 +- [ ] 添加更多角色主题设置,方便导入 + +## 🐶 开发自己的桌宠 + +基于 Godot 的轻便和易学,你可以很容易构建独属于你自己的桌面宠物形象。 + +### 🐾 步骤 + +1. 准备一套角色帧动: 你可以查看[itch.ion](https://itch.io/game-assets)等,选择一套自己喜欢的下载,**注意版权,仅自己用不用太注意**。 +2. Godot!启动![下载](https://godotengine.org/download/windows/) +3. 查看并学习官方文档:不需要害怕,Godot很简单,查看[Animation](https://docs.godotengine.org/en/4.2/tutorials/animation/index.html)。 +4. 开发(continue...) + + +5. [导出](https://docs.godotengine.org/zh-cn/4.x/tutorials/export/index.html) + +### 🏗️ 项目架构 + +```css +- root(Window) + - Globals(Node) + - App(Node) + - Canvas(Node2D) - 角色部分 + - Grapic(Node2D) - 显示区域 和 角色管理 + - Dialogue(Control) - 对话显示 + - Send(Window) - 发送消息 + - TabContainer(TabContainer) + - 对话栏(Control) + - 存储预设(Control) + - 编辑预设(Control) + - Model(PanelContainer) + - Parament(PanelContainer) +``` + +### 🔊 注意 + +> [!Caution] +> 1. 开发使用的godot版本号为 4.2.2 stable,Godot4 应该都是可以的(未测试) +> 2. 导入角色素材记得合适的缩放,同时不忘了调整 显示区域(ClickPolygon) 的大小 +> 3. Godot右上角 选择**兼容模式** + +## 🤝 贡献 + + +项目目前处于开发阶段,还存在许多 Bug,我正着手修复。 +欢迎任何贡献,即使细微。 +添加角色素材等等 + +## ©️ 版权和授权 + +你可以使用项目中的任何代码片段,但不能直接将完整项目打包上架市场。 +同时,需要注意项目中使用到的一些资产的版权信息。 + + +## ❤️ 鸣谢 + +多亏了如下开源的项目和精彩绝伦的资产,才有了该项目: + +- [ChatGPT-stream-for-Godot-4](https://github.com/oceanbuilders/ChatGPT-stream-for-Godot-4) - stream输出的支持 +- [godot-click-through-transparent-window](https://github.com/atadenizoktay/godot-click-through-transparent-window) - 透明窗口 +- [tech-dungeon-roguelite](https://trevor-pupkin.itch.io/tech-dungeon-roguelite) - 角色资产 +- [tiny-swords](https://pixelfrog-assets.itch.io/tiny-swords) - 角色资产 +- [dino-characters](https://arks.itch.io/dino-characters) - 角色资产 diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 00000000..7a4f0fce --- /dev/null +++ b/README_EN.md @@ -0,0 +1,104 @@ +

🐶 Godog 🐶

+

AI Godot Desktop Pet

+ +[简体中文](README.md) / English + +## 👋 Introduction + +🚀 🚀 🚀 A desktop AI pet powered by a large language model and made with Godot. This project aims to provide a versatile and rich desktop AI pet. You can use it as a foundation to create your own unique pet image and character behavior. + +- Using Godot: lightweight (90MB memory usage), simple development, provides animation, control UI framework, etc. +- Compatible with many large language models +- Pre-made character feature with multiple appearances +- Supports multi-modal capabilities +- Easy to build your own AI desktop pet on this basis + +![](/img/example.png) + +## 📙 User Manual + +### ⬇️ Installation + +Download: Check `release` + +### 🔑 Usage +1. Click `Edit Preset`, fill in the name and API Key, and click **Save (Modify)** +2. Click `Store Preset` and select the preset you just added +3. Start the conversation + +> [!Caution] +> Currently, only OpenAI's vision capability (VISION) is supported. It is recommended to use the **gpt-4o** model. +> You can use other models, but multi-modal capabilities are not supported. + +### ⌨️ Shortcuts +- **Right-click** on the character area to pop up the dialog box +- When the focus is on the character window, press **ESC** to close the software + +## 🚦 Development Plan + +- [ ] Add support for multiple languages +- [ ] Add more personalized character animations, mechanisms, events +- [ ] Add support for more models +- [ ] Add support for Markdown syntax rendering in the dialog box +- [ ] Add multi-modal capabilities such as voice, file upload, etc. +- [ ] Add more presets +- [ ] Add more character theme settings for easy import + +## 🐶 Developing Your Own Desktop Pet + +With Godot's lightweight and easy-to-learn nature, you can easily create your own unique desktop pet image. + +### 🐾 Steps + +1. Prepare a set of character frames: You can check [itch.io](https://itch.io/game-assets), choose a set you like and download it, **be mindful of copyright if not for personal use**. +2. Godot! Start! [Download](https://godotengine.org/download/windows/) +3. Review and learn the official documentation: Don’t be afraid, Godot is simple. Check out [Animation](https://docs.godotengine.org/en/4.2/tutorials/animation/index.html). +4. Development (continue...) + +5. [Export](https://docs.godotengine.org/en/4.x/tutorials/export/index.html) + +### 🏗️ Project Structure + +```css +- root(Window) + - Globals(Node) + - App(Node) + - Canvas(Node2D) - Character part + - Graphic(Node2D) - Display area and character management + - Dialogue(Control) - Dialog display + - Send(Window) - Send message + - TabContainer(TabContainer) + - Dialog tab(Control) + - Store preset(Control) + - Edit preset(Control) + - Model(PanelContainer) + - Parameters(PanelContainer) +``` + +### 🔊 Notes + +> [!Caution] +> 1. The Godot version used for development is 4.2.2 stable, Godot4 should work as well (not tested) +> 2. Remember to scale the imported character assets appropriately and adjust the display area (ClickPolygon) size +> 3. In the upper right corner of Godot, select **Compatibility Mode** + +## 🤝 Contributions + +The project is currently under development and has many bugs that I am working to fix. +Any contributions are welcome, even small ones. +Adding character assets, etc. + +## ©️ License + +You can use any code snippet from the project, but you cannot directly package and sell the complete project. +Additionally, pay attention to the copyright information of some assets used in the project. + +## ❤️ Acknowledgements + +Thanks to the following open-source projects and amazing assets, without which this project would not exist: + +- [ChatGPT-stream-for-Godot-4](https://github.com/oceanbuilders/ChatGPT-stream-for-Godot-4) - Support for stream output +- [godot-click-through-transparent-window](https://github.com/atadenizoktay/godot-click-through-transparent-window) - Transparent window +- [tech-dungeon-roguelite](https://trevor-pupkin.itch.io/tech-dungeon-roguelite) - Character assets +- [tiny-swords](https://pixelfrog-assets.itch.io/tiny-swords) - Character assets +- [dino-characters](https://arks.itch.io/dino-characters) - Character assets diff --git a/addons/HTTPSSEClient/HTTPSSEClient_modified.gd b/addons/HTTPSSEClient/HTTPSSEClient_modified.gd new file mode 100644 index 00000000..9612a936 --- /dev/null +++ b/addons/HTTPSSEClient/HTTPSSEClient_modified.gd @@ -0,0 +1,144 @@ +class_name HTTPSSEClient +extends Node + +signal new_sse_event +signal connected +signal connection_error(error) + + +var httpclient = HTTPClient.new() +var is_connected = false + +var domain +var url_after_domain +var headers : PackedStringArray +var body: String +var port +var told_to_connect = false +var connection_in_progress = false +var request_in_progress = false +var is_requested = false +var response_body = PackedByteArray() + +var ai_status_message + +func connect_to_host(domain : String, url_after_domain : String, headers: PackedStringArray, body: String, ai_message: ChatMessageAI, port : int = -1): + self.domain = domain + self.url_after_domain = url_after_domain + self.port = port + self.headers = headers + self.body = body + told_to_connect = true + ai_status_message = ai_message + +func close_connection(): + httpclient.close() + is_connected = false + told_to_connect = false + connection_in_progress = false + request_in_progress = false + is_requested = false + +func attempt_to_connect(): + var err = httpclient.connect_to_host(domain, port) + if err == OK: + emit_signal("connected") + is_connected = true + else: + emit_signal("connection_error", str(err)) + +func attempt_to_request(httpclient_status): + if httpclient_status == HTTPClient.STATUS_CONNECTING or httpclient_status == HTTPClient.STATUS_RESOLVING: + return + + if httpclient_status == HTTPClient.STATUS_CONNECTED: + var err = httpclient.request(HTTPClient.METHOD_POST, url_after_domain, headers, body) + if err == OK: + is_requested = true + +func _process(delta): + if !told_to_connect: + return + + if !is_connected: + if !connection_in_progress: + attempt_to_connect() + connection_in_progress = true + return + + httpclient.poll() + var httpclient_status = httpclient.get_status() + if !is_requested: + if !request_in_progress: + attempt_to_request(httpclient_status) + return + + var httpclient_has_response = httpclient.has_response() + + if httpclient_has_response or httpclient_status == HTTPClient.STATUS_BODY: + var response_headers = httpclient.get_response_headers_as_dictionary() + + httpclient.poll() + var chunk = httpclient.read_response_body_chunk() + if(chunk.size() == 0): + return + else: + # each chunk sometimes can contain more than one delta of data. + response_body = chunk + + var string_body = response_body.get_string_from_utf8() + if string_body: + var partial_reply = get_open_ai_events_data(string_body) + emit_signal("new_sse_event", partial_reply, ai_status_message) + + +# Since each chunk sometimes can contain more than one delta of data this +# function will iterate through each "data" block found and extract its +# content concatenating it in an array. +func get_open_ai_events_data(string_body: String) -> Array: + var results = [] + string_body = string_body.strip_edges() # Remove leading and trailing whitespaces, including new lines + var data_entries = string_body.split("\n\n") # Split the body into individual data entries + + for entry in data_entries: + if entry == "data: [DONE]": + results.append("[DONE]") + continue + + entry = entry.replace("data: ", "") + print("Processing entry: ", entry) # Debugging line + var parsed_data = JSON.parse_string(entry) + if parsed_data == null: + printerr("Error: Received data is not a valid JSON object") + results.append("[ERROR]") + else: + if "choices" in parsed_data and parsed_data["choices"].size() > 0: + var choices = parsed_data["choices"][0] + if "delta" in choices: + var delta = choices["delta"] + if "content" in delta: + results.append(delta["content"]) + elif delta.is_empty(): + results.append("[EMPTY DELTA]") + else: + printerr("Error: 'content' field not found in the 'delta'") + results.append("[ERROR]") + else: + printerr("Error: 'delta' field not found in the first choice") + results.append("[ERROR]") + else: + printerr("Error: 'choices' field not found in the received data") + results.append("[ERROR]") + + return results + + +func _exit_tree(): + if httpclient: + httpclient.close() + +func _notification(what): + if what == NOTIFICATION_WM_CLOSE_REQUEST: + if httpclient: + httpclient.close() + get_tree().quit() diff --git a/addons/HTTPSSEClient/HTTPSSEClient_original.txt b/addons/HTTPSSEClient/HTTPSSEClient_original.txt new file mode 100644 index 00000000..f4783cc4 --- /dev/null +++ b/addons/HTTPSSEClient/HTTPSSEClient_original.txt @@ -0,0 +1,119 @@ +extends Node + +signal new_sse_event(headers, event, data) +signal connected +signal connection_error(error) + +const event_tag = "event:" +const data_tag = "data:" +const continue_internal = "continue_internal" + +var httpclient = HTTPClient.new() +var is_connected = false + +var domain +var url_after_domain +var port +var use_ssl +var verify_host +var told_to_connect = false +var connection_in_progress = false +var request_in_progress = false +var is_requested = false +var response_body = PoolByteArray() + +func connect_to_host(domain : String, url_after_domain : String, port : int = -1, use_ssl : bool = false, verify_host : bool = true): + self.domain = domain + self.url_after_domain = url_after_domain + self.port = port + self.use_ssl = use_ssl + self.verify_host = verify_host + told_to_connect = true + +func attempt_to_connect(): + var err = httpclient.connect_to_host(domain, port, use_ssl, verify_host) + if err == OK: + emit_signal("connected") + is_connected = true + else: + emit_signal("connection_error", str(err)) + +func attempt_to_request(httpclient_status): + if httpclient_status == HTTPClient.STATUS_CONNECTING or httpclient_status == HTTPClient.STATUS_RESOLVING: + return + + if httpclient_status == HTTPClient.STATUS_CONNECTED: + var err = httpclient.request(HTTPClient.METHOD_POST, url_after_domain, ["Accept: text/event-stream"]) + if err == OK: + is_requested = true + +func _process(delta): + if !told_to_connect: + return + + if !is_connected: + if !connection_in_progress: + attempt_to_connect() + connection_in_progress = true + return + + httpclient.poll() + var httpclient_status = httpclient.get_status() + if !is_requested: + if !request_in_progress: + attempt_to_request(httpclient_status) + return + + var httpclient_has_response = httpclient.has_response() + + if httpclient_has_response or httpclient_status == HTTPClient.STATUS_BODY: + var headers = httpclient.get_response_headers_as_dictionary() + + httpclient.poll() + var chunk = httpclient.read_response_body_chunk() + if(chunk.size() == 0): + return + else: + response_body = response_body + chunk + + var body = response_body.get_string_from_utf8() + if body: + var event_data = get_event_data(body) + if event_data.event != "keep-alive" and event_data.event != continue_internal: + var result = JSON.parse(event_data.data).result + if response_body.size() > 0 and result: # stop here if the value doesn't parse + response_body.resize(0) + emit_signal("new_sse_event", headers, event_data.event, result) + else: + if event_data.event != continue_internal: + response_body.resize(0) + +func get_event_data(body : String) -> Dictionary: + var result = {} + var event_idx = body.find(event_tag) + if event_idx == -1: + result["event"] = continue_internal + return result + assert(event_idx != -1) + var data_idx = body.find(data_tag, event_idx + event_tag.length()) + assert(data_idx != -1) + var event = body.substr(event_idx, data_idx) + event = event.replace(event_tag, "").strip_edges() + assert(event) + assert(event.length() > 0) + result["event"] = event + var data = body.right(data_idx + data_tag.length()).strip_edges() + assert(data) + assert(data.length() > 0) + result["data"] = data + return result + +func _exit_tree(): + if httpclient: + httpclient.close() + +func _notification(what): + if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST: + if httpclient: + httpclient.close() + get_tree().quit() diff --git a/addons/HTTPSSEClient/LICENSE.md b/addons/HTTPSSEClient/LICENSE.md new file mode 100644 index 00000000..9354ed87 --- /dev/null +++ b/addons/HTTPSSEClient/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Kyle Szklenski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/HTTPSSEClient/httpsseclient_plugin.gd b/addons/HTTPSSEClient/httpsseclient_plugin.gd new file mode 100644 index 00000000..585377cd --- /dev/null +++ b/addons/HTTPSSEClient/httpsseclient_plugin.gd @@ -0,0 +1,10 @@ +@tool +extends EditorPlugin + +func _enter_tree(): + add_custom_type("HTTPSSEClient", "Node", preload("res://addons/HTTPSSEClient/HTTPSSEClient_modified.gd"), preload("res://addons/HTTPSSEClient/icon.png")) + pass + +func _exit_tree(): + remove_custom_type("HTTPSSEClient") + pass diff --git a/addons/HTTPSSEClient/icon.png b/addons/HTTPSSEClient/icon.png new file mode 100644 index 00000000..597d001a Binary files /dev/null and b/addons/HTTPSSEClient/icon.png differ diff --git a/addons/HTTPSSEClient/icon.png.import b/addons/HTTPSSEClient/icon.png.import new file mode 100644 index 00000000..232e8917 --- /dev/null +++ b/addons/HTTPSSEClient/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://doh7ebyllxgs2" +path="res://.godot/imported/icon.png-2ea94e924d0ead3d0aefe50938b80d4c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/HTTPSSEClient/icon.png" +dest_files=["res://.godot/imported/icon.png-2ea94e924d0ead3d0aefe50938b80d4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/HTTPSSEClient/plugin.cfg b/addons/HTTPSSEClient/plugin.cfg new file mode 100644 index 00000000..28538254 --- /dev/null +++ b/addons/HTTPSSEClient/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="HTTPSSEClient" +description="An HTTPClient-based implementation that supports server-sent events, effectively enabling push notifications in Godot, using GDScript." +author="Kyle Szklenski" +version="1.0" +script="httpsseclient_plugin.gd" diff --git a/app.gd b/app.gd new file mode 100644 index 00000000..ae401bce --- /dev/null +++ b/app.gd @@ -0,0 +1,172 @@ +extends Node + +# model request setting +var api_key :String = "" +var model :String = "" +var host :String = "https://api.openai.com" +var path :String = "/v1/chat/completions" + +# model paramenter setting +var max_tokens :int = 3000 +var history_count :int = 5 +var temperature :float = 0.5 +var stream :bool = true +var top_p :float +var system_message :Dictionary + +var chat = [] +var request_url :String = host+path +var headers = ["Authorization: Bearer " + api_key, "Content-Type: application/json"] + +@onready var canvas: Node2D = $Canvas +@onready var chat_message_ai: ChatMessageAI = find_child("ChatMessageAI") + +var http_request : HTTPRequest +var httpsse_client: HTTPSSEClient + +func _ready(): + Globals.send_button_press.connect(_on_Btn_send) + Globals.update_request.connect(_on_Btn_update_preset) + Globals.change_canvas.connect(_on_change_canvas) + + + if stream: + httpsse_client = HTTPSSEClient.new() + add_child(httpsse_client) + httpsse_client.new_sse_event.connect(_on_new_sse_event) + else: + http_request = HTTPRequest.new() + add_child(http_request) + http_request.request_completed.connect(_on_request_completed) + + system_message = {"role": "system", "content": "简短回复"} + + load_preset() + +var old_node_name := "Canvas" +func _on_change_canvas(path:String): + var CANVANS := load(path) + var new_canvas = CANVANS.instantiate() + + var app_node = get_node("/root/App") + + app_node.get_node(str(old_node_name)).free() + app_node.add_child(new_canvas) + canvas = new_canvas + + chat_message_ai = get_node("/root/App/"+ new_canvas.name +"/Dialogue/PanelContainer/MarginContainer/ChatMessageAI") + + old_node_name = str(new_canvas.name) + +func load_preset(): + if !Globals.current_preset.is_empty(): + preset_set(Globals.current_preset) + + + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("exit"): + get_tree().quit() + get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST) + + +func _on_Btn_send(content:Array): + if api_key == "" or model == "" or host == "": + $Send.title = "请先配置好“请求”参数" + return + $Send.title = "SendBox" + if stream: + chat_message_ai.clear() + chat_with_stream(content) + else: + chat_without_stream(content) + +func _on_Btn_update_preset(preset :Dictionary): + preset_set(preset) + +# 复制请求的参数 +func preset_set(preset :Dictionary): + host = preset["api"]['host'] + path = preset['api']['path'] + api_key = preset["api"]['key'] + model = preset["api"]["model"] + + stream = preset['parameters']['stream'] + temperature = preset['parameters']['temperature'] + top_p = preset['parameters']['topp'] + history_count = preset['parameters']['historycount'] + max_tokens = preset['parameters']['maxtokens'] + system_message = {"role": "system", "content": preset['parameters']['systemprompt'] } + + request_url = host + path + headers = ["Authorization: Bearer " + api_key, "Content-Type: application/json"] + +## 发送请求 +func chat_with_stream(content): + var messages = [system_message] + var last_N_messages = chat.slice(-history_count, chat.size(), 1) + messages.append_array(last_N_messages) + + var new_message = {"role": "user", "content": content} + messages.append(new_message) + + var request_body = JSON.stringify({ + "messages": messages, + "stream": true, + "temperature": temperature, + "model": model, + "top_p": top_p + }) + httpsse_client.connect_to_host(host, path, headers, request_body, chat_message_ai, 443) + +func chat_without_stream(content): + var messages = [system_message] + var last_N_messages = chat.slice(-history_count, chat.size(), 1) + messages.append_array(last_N_messages) + + var new_message = {"role": "user", "content": content} + messages.append(new_message) + + var request_body = JSON.stringify({ + "messages": messages, + "temperature": temperature, + "model": model + }) + var err := http_request.request(request_url, headers, HTTPClient.METHOD_POST, request_body) + + if err != OK: + print("Error initiating HTTP request: ", err) + +## 返回请求 +func _on_request_completed(_result, _response_code, _headers, body): + var json = JSON.new() + var body_string = body.get_string_from_utf8() + var _Error = json.parse(body_string) + var response = json.get_data() + var message = response["choices"][0]["message"]["content"] + chat_msg_add(message) + +func _on_new_sse_event(partial_reply: Array, _ai_status_message: ChatMessageAI): + for msg in partial_reply: + if msg == '[DONE]': + # reply is over + httpsse_client.close_connection() + chat.append({"role": "assistant", "content":chat_message_ai.text}) + Globals.is_busy = false +# hide diague + canvas.start_hide_dialogue() + elif msg == "[EMPTY DELTA]": + continue + elif msg == "[ERROR]": + pass + else: + chat_msg_add(msg) + +func chat_msg_add(msg:String): + if stream: + chat_message_ai.add_text(msg) + else: + chat_message_ai.text = msg + + + diff --git a/app.tscn b/app.tscn new file mode 100644 index 00000000..0f610dd9 --- /dev/null +++ b/app.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=4 format=3 uid="uid://c2s5ftqtdj82m"] + +[ext_resource type="Script" path="res://app.gd" id="1_8qkjp"] +[ext_resource type="PackedScene" uid="uid://dkmf1gpprqkdb" path="res://themes/Base_Canvas/canvas.tscn" id="2_g0u3y"] +[ext_resource type="PackedScene" uid="uid://dsohs7cemnsss" path="res://send/send.tscn" id="6_h0gyl"] + +[node name="App" type="Node"] +script = ExtResource("1_8qkjp") + +[node name="Send" parent="." instance=ExtResource("6_h0gyl")] +position = Vector2i(756, 455) +size = Vector2i(650, 400) + +[node name="Canvas" parent="." instance=ExtResource("2_g0u3y")] diff --git a/build/windows/desktop_pet_godot.console.exe b/build/windows/desktop_pet_godot.console.exe new file mode 100644 index 00000000..2e389039 Binary files /dev/null and b/build/windows/desktop_pet_godot.console.exe differ diff --git a/build/windows/desktop_pet_godot.exe b/build/windows/desktop_pet_godot.exe new file mode 100644 index 00000000..3f2776df Binary files /dev/null and b/build/windows/desktop_pet_godot.exe differ diff --git a/build/windows/desktop_pet_godot.pck b/build/windows/desktop_pet_godot.pck new file mode 100644 index 00000000..2ed61cd8 Binary files /dev/null and b/build/windows/desktop_pet_godot.pck differ diff --git a/classes/Globals.gd b/classes/Globals.gd new file mode 100644 index 00000000..b3b39ccb --- /dev/null +++ b/classes/Globals.gd @@ -0,0 +1,53 @@ +extends Node + +# 按下发送按钮时候,发送消息给gpt +signal send_button_press(content :Array) + +# 更新请求接口 +signal update_request(presets :Dictionary) + +signal add_new_preset_button(preset :Dictionary) + +signal change_canvas(path :String) + +const SAVE_PATH := "user://presets.json" + +const CURRENT_PRESET_SAVE_PATH = "user://current_presets.json" + +# 预设 +var presets := {} + +var current_preset :Dictionary = {} + +# judge whether had sent request +var is_busy = false + +# before start , load all presets to presets from loacl +func _ready() -> void: + #get_tree().auto_accept_quit = true + current_preset = json_read(CURRENT_PRESET_SAVE_PATH) + presets = json_read(SAVE_PATH) + +func json_store_file(dic :Dictionary,path :String): + var json := JSON.stringify(dic) + var file := FileAccess.open(path,FileAccess.WRITE) + file.store_string(json) + file.close() + +func json_read(path:String) -> Dictionary: + var file = FileAccess.open(path,FileAccess.READ) + if file == null: + # not this file.json + return {} + var content := JSON.parse_string(file.get_as_text()) as Dictionary + file.close() + return content + +func _notification(what: int) -> void: + if what == NOTIFICATION_WM_CLOSE_REQUEST: + print('json_store_file') + json_store_file(current_preset,CURRENT_PRESET_SAVE_PATH) + json_store_file(presets,SAVE_PATH) + get_tree().quit() + + diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 00000000..e51d1f64 --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,265 @@ +[preset.0] + +name="Android" +platform="Android" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +gradle_build/use_gradle_build=false +gradle_build/export_format=0 +gradle_build/min_sdk="" +gradle_build/target_sdk="" +architectures/armeabi-v7a=false +architectures/arm64-v8a=true +architectures/x86=false +architectures/x86_64=false +version/code=1 +version/name="" +package/unique_name="com.example.$genname" +package/name="" +package/signed=true +package/app_category=2 +package/retain_data_on_uninstall=false +package/exclude_from_recents=false +package/show_in_android_tv=false +package/show_in_app_library=true +package/show_as_launcher_app=false +launcher_icons/main_192x192="" +launcher_icons/adaptive_foreground_432x432="" +launcher_icons/adaptive_background_432x432="" +graphics/opengl_debug=false +xr_features/xr_mode=0 +screen/immersive_mode=true +screen/support_small=true +screen/support_normal=true +screen/support_large=true +screen/support_xlarge=true +user_data_backup/allow=false +command_line/extra_args="" +apk_expansion/enable=false +apk_expansion/SALT="" +apk_expansion/public_key="" +permissions/custom_permissions=PackedStringArray() +permissions/access_checkin_properties=false +permissions/access_coarse_location=false +permissions/access_fine_location=false +permissions/access_location_extra_commands=false +permissions/access_mock_location=false +permissions/access_network_state=false +permissions/access_surface_flinger=false +permissions/access_wifi_state=false +permissions/account_manager=false +permissions/add_voicemail=false +permissions/authenticate_accounts=false +permissions/battery_stats=false +permissions/bind_accessibility_service=false +permissions/bind_appwidget=false +permissions/bind_device_admin=false +permissions/bind_input_method=false +permissions/bind_nfc_service=false +permissions/bind_notification_listener_service=false +permissions/bind_print_service=false +permissions/bind_remoteviews=false +permissions/bind_text_service=false +permissions/bind_vpn_service=false +permissions/bind_wallpaper=false +permissions/bluetooth=false +permissions/bluetooth_admin=false +permissions/bluetooth_privileged=false +permissions/brick=false +permissions/broadcast_package_removed=false +permissions/broadcast_sms=false +permissions/broadcast_sticky=false +permissions/broadcast_wap_push=false +permissions/call_phone=false +permissions/call_privileged=false +permissions/camera=false +permissions/capture_audio_output=false +permissions/capture_secure_video_output=false +permissions/capture_video_output=false +permissions/change_component_enabled_state=false +permissions/change_configuration=false +permissions/change_network_state=false +permissions/change_wifi_multicast_state=false +permissions/change_wifi_state=false +permissions/clear_app_cache=false +permissions/clear_app_user_data=false +permissions/control_location_updates=false +permissions/delete_cache_files=false +permissions/delete_packages=false +permissions/device_power=false +permissions/diagnostic=false +permissions/disable_keyguard=false +permissions/dump=false +permissions/expand_status_bar=false +permissions/factory_test=false +permissions/flashlight=false +permissions/force_back=false +permissions/get_accounts=false +permissions/get_package_size=false +permissions/get_tasks=false +permissions/get_top_activity_info=false +permissions/global_search=false +permissions/hardware_test=false +permissions/inject_events=false +permissions/install_location_provider=false +permissions/install_packages=false +permissions/install_shortcut=false +permissions/internal_system_window=false +permissions/internet=false +permissions/kill_background_processes=false +permissions/location_hardware=false +permissions/manage_accounts=false +permissions/manage_app_tokens=false +permissions/manage_documents=false +permissions/manage_external_storage=false +permissions/master_clear=false +permissions/media_content_control=false +permissions/modify_audio_settings=false +permissions/modify_phone_state=false +permissions/mount_format_filesystems=false +permissions/mount_unmount_filesystems=false +permissions/nfc=false +permissions/persistent_activity=false +permissions/post_notifications=false +permissions/process_outgoing_calls=false +permissions/read_calendar=false +permissions/read_call_log=false +permissions/read_contacts=false +permissions/read_external_storage=false +permissions/read_frame_buffer=false +permissions/read_history_bookmarks=false +permissions/read_input_state=false +permissions/read_logs=false +permissions/read_phone_state=false +permissions/read_profile=false +permissions/read_sms=false +permissions/read_social_stream=false +permissions/read_sync_settings=false +permissions/read_sync_stats=false +permissions/read_user_dictionary=false +permissions/reboot=false +permissions/receive_boot_completed=false +permissions/receive_mms=false +permissions/receive_sms=false +permissions/receive_wap_push=false +permissions/record_audio=false +permissions/reorder_tasks=false +permissions/restart_packages=false +permissions/send_respond_via_message=false +permissions/send_sms=false +permissions/set_activity_watcher=false +permissions/set_alarm=false +permissions/set_always_finish=false +permissions/set_animation_scale=false +permissions/set_debug_app=false +permissions/set_orientation=false +permissions/set_pointer_speed=false +permissions/set_preferred_applications=false +permissions/set_process_limit=false +permissions/set_time=false +permissions/set_time_zone=false +permissions/set_wallpaper=false +permissions/set_wallpaper_hints=false +permissions/signal_persistent_processes=false +permissions/status_bar=false +permissions/subscribed_feeds_read=false +permissions/subscribed_feeds_write=false +permissions/system_alert_window=false +permissions/transmit_ir=false +permissions/uninstall_shortcut=false +permissions/update_device_stats=false +permissions/use_credentials=false +permissions/use_sip=false +permissions/vibrate=false +permissions/wake_lock=false +permissions/write_apn_settings=false +permissions/write_calendar=false +permissions/write_call_log=false +permissions/write_contacts=false +permissions/write_external_storage=false +permissions/write_gservices=false +permissions/write_history_bookmarks=false +permissions/write_profile=false +permissions/write_secure_settings=false +permissions/write_settings=false +permissions/write_sms=false +permissions/write_social_stream=false +permissions/write_sync_settings=false +permissions/write_user_dictionary=false + +[preset.1] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/windows/desktop_pet_godot.exe" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false + +[preset.1.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/bptc=true +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="" +application/product_version="" +application/company_name="" +application/product_name="" +application/file_description="" +application/copyright="" +application/trademarks="" +application/export_angle=0 +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' +$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' +$trigger = New-ScheduledTaskTrigger -Once -At 00:00 +$settings = New-ScheduledTaskSettingsSet +$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings +Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true +Start-ScheduledTask -TaskName godot_remote_debug +while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" +ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue +Remove-Item -Recurse -Force '{temp_dir}'" diff --git a/icon.svg b/icon.svg new file mode 100644 index 00000000..b370ceb7 --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 00000000..b40d57d0 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cou3j3fpt3c5v" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/img/Warrior_Blue.png b/img/Warrior_Blue.png new file mode 100644 index 00000000..a40e9c20 Binary files /dev/null and b/img/Warrior_Blue.png differ diff --git a/img/Warrior_Blue.png.import b/img/Warrior_Blue.png.import new file mode 100644 index 00000000..3b7f5c73 --- /dev/null +++ b/img/Warrior_Blue.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://duyugi5bh5yy1" +path="res://.godot/imported/Warrior_Blue.png-10a99b2c93cbc047c02f6a2038fefcf1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://img/Warrior_Blue.png" +dest_files=["res://.godot/imported/Warrior_Blue.png-10a99b2c93cbc047c02f6a2038fefcf1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/img/example.png b/img/example.png new file mode 100644 index 00000000..384cfc05 Binary files /dev/null and b/img/example.png differ diff --git a/img/example.png.import b/img/example.png.import new file mode 100644 index 00000000..57d1642a --- /dev/null +++ b/img/example.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwx3m3jwg4ome" +path="res://.godot/imported/example.png-ca19a06c7cff21c4e05c173364314286.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://img/example.png" +dest_files=["res://.godot/imported/example.png-ca19a06c7cff21c4e05c173364314286.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/img/exmaple.png.import b/img/exmaple.png.import new file mode 100644 index 00000000..ee12bb58 --- /dev/null +++ b/img/exmaple.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dkq1r2d34a51s" +path="res://.godot/imported/exmaple.png-9371d64d8e54e0be42007139f4f9a7bf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://img/exmaple.png" +dest_files=["res://.godot/imported/exmaple.png-9371d64d8e54e0be42007139f4f9a7bf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/project.godot b/project.godot new file mode 100644 index 00000000..b804deda --- /dev/null +++ b/project.godot @@ -0,0 +1,54 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Desktop-Pet-Godot" +config/description="🚀 🚀 🚀 一款由大语言模型驱动、Godot 制作的 AI 桌宠,旨在提供一个全能的、丰富的桌面AI宠物。" +run/main_scene="res://app.tscn" +config/features=PackedStringArray("4.2", "Forward Plus") +run/low_processor_mode=true +boot_splash/bg_color=Color(0.141176, 0.141176, 0.141176, 0) +boot_splash/image="res://themes/Base_Canvas/assets/UI/transparent.png" +config/icon="res://icon.svg" + +[autoload] + +Globals="*res://classes/Globals.gd" + +[display] + +window/size/viewport_width=1 +window/size/viewport_height=1 +window/size/borderless=true +window/size/always_on_top=true +window/size/transparent=true +window/subwindows/embed_subwindows=false +window/per_pixel_transparency/allowed=true + +[editor_plugins] + +enabled=PackedStringArray("res://addons/HTTPSSEClient/plugin.cfg") + +[input] + +exit={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} + +[rendering] + +textures/canvas_textures/default_texture_filter=0 +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" +environment/defaults/default_clear_color=Color(0, 0, 0, 1) +viewport/transparent_background=true diff --git a/send/Send.gd b/send/Send.gd new file mode 100644 index 00000000..636d4dc4 --- /dev/null +++ b/send/Send.gd @@ -0,0 +1,5 @@ +extends Window + +# 关闭发送窗口 +func _on_close_requested() -> void: + queue_free() diff --git a/send/chat_box/chat_msg_edit.gd b/send/chat_box/chat_msg_edit.gd new file mode 100644 index 00000000..f97945c6 --- /dev/null +++ b/send/chat_box/chat_msg_edit.gd @@ -0,0 +1,76 @@ +extends TextEdit + +var content : Array[Dictionary] = [ + #{ + #"type": "text", + #"text": "" + #}, + #{ + #"type": "image_url", + #"image_url": { + #"url": "data:image/png;base64,{base64_image}" + #} + #} +] + +var base64_image : String = "" + +func _gui_input(event: InputEvent) -> void: + if event is InputEventKey: + if event.pressed and event.keycode == KEY_ENTER: + if event.shift_pressed: + insert_text_at_caret("\n") + else: + if not Globals.is_busy: + _send_content() + +func _send_content(): + var text_section : Dictionary = { + "type": "text", + "text": text + } + content.append(text_section) + + if base64_image != "": + var image_section : Dictionary = { + "type": "image_url", + "image_url": { + "url": "data:image/png;base64,{%s}" % [base64_image] + } + } + base64_image = "" + content.append(image_section) + + Globals.send_button_press.emit(content) + Globals.is_busy = true + + content.clear() + await get_tree().create_timer(0.2).timeout + text = "" + print("prompt text = ",text) + texture_queue_free() + +func _on_button_pressed() -> void: + if not Globals.is_busy: + _send_content() + +func _paste(_caret_index: int) -> void: + if DisplayServer.clipboard_has_image(): + var image : Image = DisplayServer.clipboard_get_image() + #$"../Sprite2D".texture = ImageTexture.create_from_image(image) + base64_image = Marshalls.raw_to_base64(image.save_png_to_buffer()) + #print("data:image/png;base64,{%s}" % [base64_image]) + print("image add") + add_texture(image) + +func add_texture(image:Image): + var texture_rect : TextureRect = TextureRect.new() + texture_rect.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL + texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + texture_rect.texture = ImageTexture.create_from_image(image) + %ToolBar.add_child(texture_rect) + +func texture_queue_free(): + for child in %ToolBar.get_children(): + child.queue_free() + diff --git a/send/edit_preset/preset.gd b/send/edit_preset/preset.gd new file mode 100644 index 00000000..c1c3b871 --- /dev/null +++ b/send/edit_preset/preset.gd @@ -0,0 +1,65 @@ +extends Control + +@onready var model_grid: GridContainer = %ModelGrid +@onready var parament_grid: GridContainer = %ParamentGrid +@onready var type: OptionButton = $MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid/Type +@onready var stream: CheckButton = $MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid/Stream + +var preset : Dictionary = { + "name": "robot", + "api": { + "type": "OpenAI", + "host": "https://api.openai.com", + "path": "/v1/chat/completions", + "key": "", + "model": "gpt-4o" + }, + "parameters": { + "stream": true, + "temperature": 1.0, + "topp": 0.3, + "historycount": 5, + "maxtokens": 3000, + "systemprompt": "" + }, +} + +func _ready() -> void: + stream.button_pressed = true + +func store_preset_dic(): + for child in model_grid.get_children(): + if child is LineEdit: + if child.name.to_lower() != "name": + preset["api"][child.name.to_lower()] = child.text + + for child in parament_grid.get_children(): + if child is TextEdit: + preset["parameters"][child.name.to_lower()] = child.text + + if child is LineEdit: + var little_name := child.name.to_lower() + if little_name == "stream": + continue + match little_name: + "historycount": preset["parameters"][little_name] = child.text.to_int() + "topp": preset["parameters"][little_name] = child.text.to_float() + "temperature": preset["parameters"][little_name] = child.text.to_float() + "maxtokens" : preset["parameters"][little_name] = child.text.to_int() + + +func _on_type_item_selected(index: int) -> void: + preset["api"]["type"] = type.get_item_text(index) + +func _on_name_text_changed(new_text: String) -> void: + preset["name"] = new_text + +func _on_stream_pressed() -> void: + preset["parameters"]["stream"] = stream.button_pressed + +func _on_button_pressed() -> void: + store_preset_dic() + print(preset) + # modify the exist preset or add new preset + Globals.presets[preset["name"]] = preset + Globals.add_new_preset_button.emit(preset) diff --git a/send/edit_preset/preset.tscn b/send/edit_preset/preset.tscn new file mode 100644 index 00000000..6cf6c966 --- /dev/null +++ b/send/edit_preset/preset.tscn @@ -0,0 +1,246 @@ +[gd_scene load_steps=3 format=3 uid="uid://h2ghtx4klqip"] + +[ext_resource type="Script" path="res://send/edit_preset/preset.gd" id="1_8tp2p"] +[ext_resource type="Theme" uid="uid://7wp65kjcrv2s" path="res://themes/Base_Canvas/main.tres" id="2_nacpe"] + +[node name="编辑预设" type="Control"] +visible = false +layout_mode = 3 +anchors_preset = 0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_8tp2p") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 31.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 5 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Model" type="PanelContainer" parent="MarginContainer/ScrollContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/ScrollContainer/VBoxContainer/Model"] +layout_mode = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_right = 20 + +[node name="ModelGrid" type="GridContainer" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme = ExtResource("2_nacpe") +columns = 2 + +[node name="NameLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +text = "名称" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Name" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 + +[node name="TypeLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "API形式" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Type" type="OptionButton" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +item_count = 4 +selected = 0 +popup/item_0/text = "OpenAI" +popup/item_0/id = 4 +popup/item_1/text = "DeepSeek" +popup/item_1/id = 0 +popup/item_2/text = "Qwen" +popup/item_2/id = 1 +popup/item_3/text = "Yi" +popup/item_3/id = 2 + +[node name="HostLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "host" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Host" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "https://api.openai.com" + +[node name="PathLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "Path" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Path" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "/v1/chat/completions" + +[node name="KeyLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "API Key" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Key" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ModelLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "模型名称" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Model" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "gpt-4o" + +[node name="Parament" type="PanelContainer" parent="MarginContainer/ScrollContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament"] +layout_mode = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_right = 20 + +[node name="ParamentGrid" type="GridContainer" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +columns = 2 + +[node name="StreamLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "stream流式输出" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Stream" type="CheckButton" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +button_pressed = true + +[node name="TemperatureLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "temperature" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Temperature" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "1.0" + +[node name="TopPLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "top_p" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TopP" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "0.3" + +[node name="HistoryCountLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "附带历史消息条数" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HistoryCount" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "5" + +[node name="MaxTokensLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "max_tokens" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="MaxTokens" type="LineEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "3000" + +[node name="SystemPromptLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_vertical = 3 +text = "系统提示词" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="SystemPrompt" type="TextEdit" parent="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid"] +layout_mode = 2 +size_flags_vertical = 3 +text = "you are a helpful helper" + +[node name="Button" type="Button" parent="MarginContainer/ScrollContainer/VBoxContainer"] +layout_mode = 2 +text = "保存(修改)" + +[connection signal="text_changed" from="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid/Name" to="." method="_on_name_text_changed"] +[connection signal="item_selected" from="MarginContainer/ScrollContainer/VBoxContainer/Model/MarginContainer/ModelGrid/Type" to="." method="_on_type_item_selected"] +[connection signal="pressed" from="MarginContainer/ScrollContainer/VBoxContainer/Parament/MarginContainer/ParamentGrid/Stream" to="." method="_on_stream_pressed"] +[connection signal="pressed" from="MarginContainer/ScrollContainer/VBoxContainer/Button" to="." method="_on_button_pressed"] diff --git a/send/send.tscn b/send/send.tscn new file mode 100644 index 00000000..b62c11bb --- /dev/null +++ b/send/send.tscn @@ -0,0 +1,113 @@ +[gd_scene load_steps=6 format=3 uid="uid://dsohs7cemnsss"] + +[ext_resource type="Script" path="res://send/send.gd" id="1_875b0"] +[ext_resource type="Script" path="res://send/chat_box/chat_msg_edit.gd" id="2_l2itr"] +[ext_resource type="Script" path="res://send/store_preset/store_preset.gd" id="3_hthny"] +[ext_resource type="PackedScene" uid="uid://h2ghtx4klqip" path="res://send/edit_preset/preset.tscn" id="5_0uube"] +[ext_resource type="PackedScene" uid="uid://c3qe413dfh71i" path="res://send/themes/themes.tscn" id="5_1e2g1"] + +[node name="Send" type="Window"] +title = "SendBox" +position = Vector2i(806, 303) +size = Vector2i(600, 500) +always_on_top = true +script = ExtResource("1_875b0") + +[node name="TabContainer" type="TabContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +current_tab = 3 + +[node name="对话栏" type="Control" parent="TabContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="TabContainer/对话栏"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="PanelContainer" type="PanelContainer" parent="TabContainer/对话栏/MarginContainer"] +layout_mode = 2 + +[node name="BoxContainer" type="VBoxContainer" parent="TabContainer/对话栏/MarginContainer/PanelContainer"] +layout_mode = 2 + +[node name="ToolBar" type="HBoxContainer" parent="TabContainer/对话栏/MarginContainer/PanelContainer/BoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="SendBar" type="HBoxContainer" parent="TabContainer/对话栏/MarginContainer/PanelContainer/BoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 3.0 + +[node name="ChatMsgEdit" type="TextEdit" parent="TabContainer/对话栏/MarginContainer/PanelContainer/BoxContainer/SendBar"] +unique_name_in_owner = true +custom_minimum_size = Vector2(20, 0) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 5.0 +script = ExtResource("2_l2itr") + +[node name="Button" type="Button" parent="TabContainer/对话栏/MarginContainer/PanelContainer/BoxContainer/SendBar"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Send" + +[node name="存储预设" type="Control" parent="TabContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +script = ExtResource("3_hthny") + +[node name="PanelContainer" type="PanelContainer" parent="TabContainer/存储预设"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="TabContainer/存储预设/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/存储预设/PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="PresetsContainer" type="GridContainer" parent="TabContainer/存储预设/PanelContainer/MarginContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/h_separation = 5 +theme_override_constants/v_separation = 5 +columns = 4 + +[node name="编辑预设" parent="TabContainer" instance=ExtResource("5_0uube")] +layout_mode = 2 + +[node name="主题" parent="TabContainer" instance=ExtResource("5_1e2g1")] +layout_mode = 2 + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="TabContainer/对话栏/MarginContainer/PanelContainer/BoxContainer/SendBar/Button" to="TabContainer/对话栏/MarginContainer/PanelContainer/BoxContainer/SendBar/ChatMsgEdit" method="_on_button_pressed"] diff --git a/send/store_preset/store_preset.gd b/send/store_preset/store_preset.gd new file mode 100644 index 00000000..e3feef7e --- /dev/null +++ b/send/store_preset/store_preset.gd @@ -0,0 +1,21 @@ +extends Control + +@onready var presets_container: GridContainer = %PresetsContainer + +func _ready() -> void: + Globals.add_new_preset_button.connect(_change_type_load_presets) + _load_presets(Globals.presets) + +func _change_type_load_presets(preset :Dictionary): + _load_presets({preset['name']:preset}) + +func _load_presets(presets :Dictionary): + for preset in presets.values(): + var button := Button.new() + button.text = preset['name'] + button.pressed.connect(_select_presets.bind(preset)) + presets_container.add_child(button) + +func _select_presets(preset :Dictionary): + Globals.current_preset = preset + Globals.update_request.emit(preset) diff --git a/send/themes/themes.gd b/send/themes/themes.gd new file mode 100644 index 00000000..f9eec19c --- /dev/null +++ b/send/themes/themes.gd @@ -0,0 +1,27 @@ +extends Control + + +@onready var grid_container: GridContainer = %GridContainer + +var themes_scenes :Array[String] = [ + "res://themes/Tiny_Swords/tiny_swords_canvas.tscn", + "res://themes/Base_Canvas/canvas.tscn" +] + + +func _ready() -> void: + _change_theme() + + +func _change_theme(): + + for i in themes_scenes: + var button := Button.new() + var arr := i.split('/') + button.text = arr[arr.size()-1].left(-5) + button.pressed.connect(_select_presets.bind(i)) + grid_container.add_child(button) + +func _select_presets(path :String): + print(path) + Globals.change_canvas.emit(path) diff --git a/send/themes/themes.tscn b/send/themes/themes.tscn new file mode 100644 index 00000000..8d0213f6 --- /dev/null +++ b/send/themes/themes.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=2 format=3 uid="uid://c3qe413dfh71i"] + +[ext_resource type="Script" path="res://send/themes/themes.gd" id="1_5dral"] + +[node name="主题" type="Control"] +layout_mode = 3 +anchors_preset = 0 +script = ExtResource("1_5dral") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/MarginContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 diff --git a/test/test.gd b/test/test.gd new file mode 100644 index 00000000..0e2c1e7f --- /dev/null +++ b/test/test.gd @@ -0,0 +1,10 @@ +extends Node + +var dic := {} + + +func _ready() -> void: + dic['你好'] = 1 + print(dic) + dic['他该'] = 2 + print(dic) diff --git a/test/test.tscn b/test/test.tscn new file mode 100644 index 00000000..9d4688bb --- /dev/null +++ b/test/test.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://c72cr8byw6apu"] + +[ext_resource type="Script" path="res://test/test.gd" id="1_gwm6m"] + +[node name="test" type="Node"] +script = ExtResource("1_gwm6m") diff --git a/themes/Base_Canvas/assets/UI/transparent.png b/themes/Base_Canvas/assets/UI/transparent.png new file mode 100644 index 00000000..9fb90424 Binary files /dev/null and b/themes/Base_Canvas/assets/UI/transparent.png differ diff --git a/themes/Base_Canvas/assets/UI/transparent.png.import b/themes/Base_Canvas/assets/UI/transparent.png.import new file mode 100644 index 00000000..37395e96 --- /dev/null +++ b/themes/Base_Canvas/assets/UI/transparent.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr56habqc6luy" +path="res://.godot/imported/transparent.png-5964354e41a4505fc07e8f893f7241b0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://themes/Base_Canvas/assets/UI/transparent.png" +dest_files=["res://.godot/imported/transparent.png-5964354e41a4505fc07e8f893f7241b0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/themes/Base_Canvas/assets/UI/ui x3.png b/themes/Base_Canvas/assets/UI/ui x3.png new file mode 100644 index 00000000..7e22b95c Binary files /dev/null and b/themes/Base_Canvas/assets/UI/ui x3.png differ diff --git a/themes/Base_Canvas/assets/UI/ui x3.png.import b/themes/Base_Canvas/assets/UI/ui x3.png.import new file mode 100644 index 00000000..3a8121c6 --- /dev/null +++ b/themes/Base_Canvas/assets/UI/ui x3.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qqko8bggda1n" +path="res://.godot/imported/ui x3.png-900857131c350d516cfe304bee790169.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://themes/Base_Canvas/assets/UI/ui x3.png" +dest_files=["res://.godot/imported/ui x3.png-900857131c350d516cfe304bee790169.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/themes/Base_Canvas/assets/characters/players grey x3.png b/themes/Base_Canvas/assets/characters/players grey x3.png new file mode 100644 index 00000000..129d16de Binary files /dev/null and b/themes/Base_Canvas/assets/characters/players grey x3.png differ diff --git a/themes/Base_Canvas/assets/characters/players grey x3.png.import b/themes/Base_Canvas/assets/characters/players grey x3.png.import new file mode 100644 index 00000000..ec91b47c --- /dev/null +++ b/themes/Base_Canvas/assets/characters/players grey x3.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5m8pylbtdb68" +path="res://.godot/imported/players grey x3.png-3d0b6eacec035a3edf719061425850fa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://themes/Base_Canvas/assets/characters/players grey x3.png" +dest_files=["res://.godot/imported/players grey x3.png-3d0b6eacec035a3edf719061425850fa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/themes/Base_Canvas/assets/dialogue/dialgue_box1.png b/themes/Base_Canvas/assets/dialogue/dialgue_box1.png new file mode 100644 index 00000000..0d851f1a Binary files /dev/null and b/themes/Base_Canvas/assets/dialogue/dialgue_box1.png differ diff --git a/themes/Base_Canvas/assets/dialogue/dialgue_box1.png.import b/themes/Base_Canvas/assets/dialogue/dialgue_box1.png.import new file mode 100644 index 00000000..d79f9e3b --- /dev/null +++ b/themes/Base_Canvas/assets/dialogue/dialgue_box1.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c36m0gk0xqi3o" +path="res://.godot/imported/dialgue_box1.png-73e82f1850be0e7279a98f9066cf1ba2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://themes/Base_Canvas/assets/dialogue/dialgue_box1.png" +dest_files=["res://.godot/imported/dialgue_box1.png-73e82f1850be0e7279a98f9066cf1ba2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf b/themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf new file mode 100644 index 00000000..db9ed7e1 Binary files /dev/null and b/themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf differ diff --git a/themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf.import b/themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf.import new file mode 100644 index 00000000..08f57e4e --- /dev/null +++ b/themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bytigbjqllywe" +path="res://.godot/imported/LXGWNeoXiHei.ttf-50e88f51ed75df471439ecdcffa5ca99.fontdata" + +[deps] + +source_file="res://themes/Base_Canvas/assets/fonts/LXGWNeoXiHei.ttf" +dest_files=["res://.godot/imported/LXGWNeoXiHei.ttf-50e88f51ed75df471439ecdcffa5ca99.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf b/themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf new file mode 100644 index 00000000..c297dc69 Binary files /dev/null and b/themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf differ diff --git a/themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf.import b/themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf.import new file mode 100644 index 00000000..fd676f3c --- /dev/null +++ b/themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://7ydt53m2btct" +path="res://.godot/imported/SmileySans-Oblique.ttf-90f5cf1d5f4301e882afa9b7b3390de7.fontdata" + +[deps] + +source_file="res://themes/Base_Canvas/assets/fonts/SmileySans-Oblique.ttf" +dest_files=["res://.godot/imported/SmileySans-Oblique.ttf-90f5cf1d5f4301e882afa9b7b3390de7.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf b/themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf new file mode 100644 index 00000000..3cb93369 Binary files /dev/null and b/themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf differ diff --git a/themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf.import b/themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf.import new file mode 100644 index 00000000..91a18439 --- /dev/null +++ b/themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://dp8cmm8sp60uy" +path="res://.godot/imported/SourceHanSansSC-Normal.otf-19b222bed4e3c810a40738b1e303883e.fontdata" + +[deps] + +source_file="res://themes/Base_Canvas/assets/fonts/SourceHanSansSC-Normal.otf" +dest_files=["res://.godot/imported/SourceHanSansSC-Normal.otf-19b222bed4e3c810a40738b1e303883e.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/themes/Base_Canvas/base_canvas.md b/themes/Base_Canvas/base_canvas.md new file mode 100644 index 00000000..e69de29b diff --git a/themes/Base_Canvas/canvas.gd b/themes/Base_Canvas/canvas.gd new file mode 100644 index 00000000..ee8eed48 --- /dev/null +++ b/themes/Base_Canvas/canvas.gd @@ -0,0 +1,28 @@ +extends Node2D + +@onready var dialogue_timer: Timer = $Dialogue_Timer +@onready var dialogue_node: Control = %Dialogue + + +func _ready() -> void: + _initialize_window() + dialogue_timer.timeout.connect(_on_dialogue_timer) + +func _initialize_window() -> void: + var window: Window = get_window() + window.size = Vector2i(DisplayServer.screen_get_size() + Vector2i(1, 1)) + window.position = DisplayServer.screen_get_position() + + +func set_dialogue_label(msg:String): + %Dialogue.set_dialogue_label(msg) + + +# 20s内没有任何操作,自动隐藏对话节点 ; 有操作就重新记时 +func start_hide_dialogue(): + dialogue_node.show() + + dialogue_timer.start(20) + +func _on_dialogue_timer(): + dialogue_node.hide() diff --git a/themes/Base_Canvas/canvas.tscn b/themes/Base_Canvas/canvas.tscn new file mode 100644 index 00000000..d33f69c8 --- /dev/null +++ b/themes/Base_Canvas/canvas.tscn @@ -0,0 +1,120 @@ +[gd_scene load_steps=12 format=3 uid="uid://dkmf1gpprqkdb"] + +[ext_resource type="Script" path="res://themes/Base_Canvas/canvas.gd" id="1_s7786"] +[ext_resource type="Script" path="res://themes/Base_Canvas/grapic/grapic.gd" id="2_a65q2"] +[ext_resource type="Texture2D" uid="uid://5m8pylbtdb68" path="res://themes/Base_Canvas/assets/characters/players grey x3.png" id="3_s54au"] +[ext_resource type="Theme" uid="uid://7wp65kjcrv2s" path="res://themes/Base_Canvas/main.tres" id="4_j53t2"] +[ext_resource type="Script" path="res://themes/Base_Canvas/dialogue/dialogue.gd" id="4_lp7mw"] +[ext_resource type="Texture2D" uid="uid://c36m0gk0xqi3o" path="res://themes/Base_Canvas/assets/dialogue/dialgue_box1.png" id="6_be7al"] +[ext_resource type="Script" path="res://themes/Base_Canvas/dialogue/chat_message_ai.gd" id="7_4g7g5"] + +[sub_resource type="Animation" id="Animation_fqk1a"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [73] +} + +[sub_resource type="Animation" id="Animation_8lixs"] +resource_name = "idle" +loop_mode = 1 +step = 0.2 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1), +"update": 1, +"values": [72, 73, 74, 75, 76] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_tibt3"] +_data = { +"RESET": SubResource("Animation_fqk1a"), +"idle": SubResource("Animation_8lixs") +} + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_o4c6w"] +texture = ExtResource("6_be7al") + +[node name="Canvas" type="Node2D"] +position = Vector2(2373, 152) +script = ExtResource("1_s7786") +metadata/_edit_lock_ = true +metadata/_edit_group_ = true + +[node name="Grapic" type="Node2D" parent="."] +scale = Vector2(-2, 2) +script = ExtResource("2_a65q2") + +[node name="Pet" type="Sprite2D" parent="Grapic"] +position = Vector2(-5, 8) +texture = ExtResource("3_s54au") +hframes = 8 +vframes = 13 +frame = 73 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="Grapic/Pet"] +libraries = { +"": SubResource("AnimationLibrary_tibt3") +} +autoplay = "idle" + +[node name="Area2D" type="Area2D" parent="Grapic"] +position = Vector2(2, 9) + +[node name="ClickPolygon" type="CollisionPolygon2D" parent="Grapic/Area2D"] +unique_name_in_owner = true +self_modulate = Color(1, 1, 1, 0.105882) +polygon = PackedVector2Array(274, 64, -64, 64, -64, -64, 275, -64) +disabled = true + +[node name="Dialogue_Timer" type="Timer" parent="."] +wait_time = 20.0 +one_shot = true +autostart = true + +[node name="Dialogue" type="Control" parent="."] +unique_name_in_owner = true +layout_mode = 3 +anchors_preset = 0 +offset_left = -541.0 +offset_top = -149.0 +offset_right = -73.0 +offset_bottom = 158.0 +theme = ExtResource("4_j53t2") +script = ExtResource("4_lp7mw") + +[node name="PanelContainer" type="PanelContainer" parent="Dialogue"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxTexture_o4c6w") + +[node name="MarginContainer" type="MarginContainer" parent="Dialogue/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 30 +theme_override_constants/margin_top = 120 +theme_override_constants/margin_right = 30 +theme_override_constants/margin_bottom = 40 + +[node name="ChatMessageAI" type="RichTextLabel" parent="Dialogue/PanelContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +bbcode_enabled = true +script = ExtResource("7_4g7g5") diff --git a/themes/Base_Canvas/dialogue/chat_message_ai.gd b/themes/Base_Canvas/dialogue/chat_message_ai.gd new file mode 100644 index 00000000..df5a9323 --- /dev/null +++ b/themes/Base_Canvas/dialogue/chat_message_ai.gd @@ -0,0 +1,4 @@ +class_name ChatMessageAI +extends RichTextLabel + + diff --git a/themes/Base_Canvas/dialogue/dialogue.gd b/themes/Base_Canvas/dialogue/dialogue.gd new file mode 100644 index 00000000..05693ced --- /dev/null +++ b/themes/Base_Canvas/dialogue/dialogue.gd @@ -0,0 +1,5 @@ +extends Control + + +func set_dialogue_label(msg:String): + %ChatMessageAI.text = msg diff --git a/themes/Base_Canvas/grapic/grapic.gd b/themes/Base_Canvas/grapic/grapic.gd new file mode 100644 index 00000000..1fede286 --- /dev/null +++ b/themes/Base_Canvas/grapic/grapic.gd @@ -0,0 +1,34 @@ +extends Node2D +var dragging:bool #拖拽状态 +var v2_mouse:Vector2i #鼠标的偏差 + +@onready var _ClickPolygon: CollisionPolygon2D = $"%ClickPolygon" + +func _physics_process(_delta: float) -> void: + _update_click_polygon() + +func _update_click_polygon() -> void: + var click_polygon: PackedVector2Array = _ClickPolygon.polygon + for vec_i in range(click_polygon.size()): + click_polygon[vec_i] = to_global(click_polygon[vec_i]) + get_window().mouse_passthrough_polygon = click_polygon + + +#拖拽窗口 +func _input(event): + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: + dragging = true + v2_mouse =get_global_mouse_position() + else: + dragging = false + + if event is InputEventMouseMotion and dragging: + #窗口位置=鼠标位置 - 鼠标的偏差值 + DisplayServer.window_set_position(DisplayServer.mouse_get_position()-v2_mouse) + + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed: + var SEND := load("res://send/send.tscn") + var send = SEND.instantiate() + get_node('/root/App').add_child(send) diff --git a/themes/Base_Canvas/main.tres b/themes/Base_Canvas/main.tres new file mode 100644 index 00000000..a22426b8 --- /dev/null +++ b/themes/Base_Canvas/main.tres @@ -0,0 +1,10 @@ +[gd_resource type="Theme" load_steps=3 format=3 uid="uid://7wp65kjcrv2s"] + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_lj7mo"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ddhj5"] + +[resource] +RichTextLabel/colors/default_color = Color(1, 0.996094, 0.996094, 1) +RichTextLabel/styles/normal = SubResource("StyleBoxTexture_lj7mo") +VScrollBar/styles/scroll = SubResource("StyleBoxEmpty_ddhj5") diff --git a/themes/Tiny_Swords/assets/characters/Warrior_Blue.png b/themes/Tiny_Swords/assets/characters/Warrior_Blue.png new file mode 100644 index 00000000..10481456 Binary files /dev/null and b/themes/Tiny_Swords/assets/characters/Warrior_Blue.png differ diff --git a/themes/Tiny_Swords/assets/characters/Warrior_Blue.png.import b/themes/Tiny_Swords/assets/characters/Warrior_Blue.png.import new file mode 100644 index 00000000..4347814b --- /dev/null +++ b/themes/Tiny_Swords/assets/characters/Warrior_Blue.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2fc18wxmmf6e" +path="res://.godot/imported/Warrior_Blue.png-f2aaf8f74a8b3cbda3ffe2fc9ad0323f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://themes/Tiny_Swords/assets/characters/Warrior_Blue.png" +dest_files=["res://.godot/imported/Warrior_Blue.png-f2aaf8f74a8b3cbda3ffe2fc9ad0323f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png b/themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png new file mode 100644 index 00000000..e9076f82 Binary files /dev/null and b/themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png differ diff --git a/themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png.import b/themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png.import new file mode 100644 index 00000000..0a1a0c9f --- /dev/null +++ b/themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckkx6v5ckrfpd" +path="res://.godot/imported/Banner_Vertical.png-698e4220a9a76f2ba26cce4ea2b2b212.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png" +dest_files=["res://.godot/imported/Banner_Vertical.png-698e4220a9a76f2ba26cce4ea2b2b212.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf b/themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf new file mode 100644 index 00000000..db9ed7e1 Binary files /dev/null and b/themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf differ diff --git a/themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf.import b/themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf.import new file mode 100644 index 00000000..41516316 --- /dev/null +++ b/themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://4ox4ebvuv7qk" +path="res://.godot/imported/LXGWNeoXiHei.ttf-b4e9cda6f06c822cd3af1dfb9f213e79.fontdata" + +[deps] + +source_file="res://themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf" +dest_files=["res://.godot/imported/LXGWNeoXiHei.ttf-b4e9cda6f06c822cd3af1dfb9f213e79.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf b/themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf new file mode 100644 index 00000000..c297dc69 Binary files /dev/null and b/themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf differ diff --git a/themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf.import b/themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf.import new file mode 100644 index 00000000..af7df832 --- /dev/null +++ b/themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://kmj5xlo1evt2" +path="res://.godot/imported/SmileySans-Oblique.ttf-b7baff69bf94db9c1d21f32f77fb1cca.fontdata" + +[deps] + +source_file="res://themes/Tiny_Swords/assets/fonts/SmileySans-Oblique.ttf" +dest_files=["res://.godot/imported/SmileySans-Oblique.ttf-b7baff69bf94db9c1d21f32f77fb1cca.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf b/themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf new file mode 100644 index 00000000..3cb93369 Binary files /dev/null and b/themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf differ diff --git a/themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf.import b/themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf.import new file mode 100644 index 00000000..86790306 --- /dev/null +++ b/themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://c7rot5p106nta" +path="res://.godot/imported/SourceHanSansSC-Normal.otf-b18de7fefbe9ec0a4c694354c59a4f1c.fontdata" + +[deps] + +source_file="res://themes/Tiny_Swords/assets/fonts/SourceHanSansSC-Normal.otf" +dest_files=["res://.godot/imported/SourceHanSansSC-Normal.otf-b18de7fefbe9ec0a4c694354c59a4f1c.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/themes/Tiny_Swords/dialogue/dialogue.gd b/themes/Tiny_Swords/dialogue/dialogue.gd new file mode 100644 index 00000000..05693ced --- /dev/null +++ b/themes/Tiny_Swords/dialogue/dialogue.gd @@ -0,0 +1,5 @@ +extends Control + + +func set_dialogue_label(msg:String): + %ChatMessageAI.text = msg diff --git a/themes/Tiny_Swords/grapic/grapic.gd b/themes/Tiny_Swords/grapic/grapic.gd new file mode 100644 index 00000000..43b09a0d --- /dev/null +++ b/themes/Tiny_Swords/grapic/grapic.gd @@ -0,0 +1,36 @@ +extends Node2D +var dragging:bool #拖拽状态 +var v2_mouse:Vector2i #鼠标的偏差 + +@onready var _ClickPolygon: CollisionPolygon2D = $"%ClickPolygon" +const SEND = preload("res://send/send.tscn") + +func _physics_process(_delta: float) -> void: + _update_click_polygon() + +func _update_click_polygon() -> void: + var click_polygon: PackedVector2Array = _ClickPolygon.polygon + for vec_i in range(click_polygon.size()): + click_polygon[vec_i] = to_global(click_polygon[vec_i]) + get_window().mouse_passthrough_polygon = click_polygon + + +#拖拽窗口 +func _input(event): + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: + dragging = true + v2_mouse =get_global_mouse_position() + else: + dragging = false + + if event is InputEventMouseMotion and dragging: + #窗口位置=鼠标位置 - 鼠标的偏差值 + DisplayServer.window_set_position(DisplayServer.mouse_get_position()-v2_mouse) + + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed: + var send = SEND.instantiate() + get_node('/root/App').add_child(send) + + diff --git a/themes/Tiny_Swords/tiny_swords.md b/themes/Tiny_Swords/tiny_swords.md new file mode 100644 index 00000000..e69de29b diff --git a/themes/Tiny_Swords/tiny_swords_canvas.gd b/themes/Tiny_Swords/tiny_swords_canvas.gd new file mode 100644 index 00000000..554c534c --- /dev/null +++ b/themes/Tiny_Swords/tiny_swords_canvas.gd @@ -0,0 +1,46 @@ +extends Node2D + +@onready var state_change_timer : Timer = $StateChange +@onready var animation_player: AnimationPlayer = $Grapic/Pet/AnimationPlayer +@onready var dialogue_timer: Timer = $Dialogue_Timer +@onready var dialogue_node: Control = %Dialogue + + + +var animation_list := [] + +func _ready() -> void: + _initialize_window() + animation_list = animation_player.get_animation_list() + animation_list.remove_at(animation_list.find('idle')) + animation_list.remove_at(animation_list.find('RESET')) + state_change_timer.start() + state_change_timer.timeout.connect(_on_state_change) + + dialogue_timer.timeout.connect(_on_dialogue_timer) + +func _initialize_window() -> void: + var window: Window = get_window() + window.size = Vector2i(DisplayServer.screen_get_size() + Vector2i(1, 1)) + window.position = DisplayServer.screen_get_position() + + +func set_dialogue_label(msg:String): + %Dialogue.set_dialogue_label(msg) + +func _on_state_change(): + var idx = randi_range(0,animation_list.size()-1) + animation_player.play(animation_list[idx]) + + await animation_player.animation_finished + animation_player.play('idle') + state_change_timer.start(randf_range(30,120)) + +# 20s内没有任何操作,自动隐藏对话节点 ; 有操作就重新记时 +func start_hide_dialogue(): + dialogue_node.show() + + dialogue_timer.start(20) + +func _on_dialogue_timer(): + dialogue_node.hide() diff --git a/themes/Tiny_Swords/tiny_swords_canvas.tres b/themes/Tiny_Swords/tiny_swords_canvas.tres new file mode 100644 index 00000000..ab9fa507 --- /dev/null +++ b/themes/Tiny_Swords/tiny_swords_canvas.tres @@ -0,0 +1,13 @@ +[gd_resource type="Theme" load_steps=4 format=3 uid="uid://bn50jp1pavmmx"] + +[ext_resource type="FontFile" uid="uid://4ox4ebvuv7qk" path="res://themes/Tiny_Swords/assets/fonts/LXGWNeoXiHei.ttf" id="1_h3w4b"] + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_lj7mo"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ddhj5"] + +[resource] +default_font = ExtResource("1_h3w4b") +RichTextLabel/colors/default_color = Color(1, 0.996094, 0.996094, 1) +RichTextLabel/styles/normal = SubResource("StyleBoxTexture_lj7mo") +VScrollBar/styles/scroll = SubResource("StyleBoxEmpty_ddhj5") diff --git a/themes/Tiny_Swords/tiny_swords_canvas.tscn b/themes/Tiny_Swords/tiny_swords_canvas.tscn new file mode 100644 index 00000000..2f6a5f18 --- /dev/null +++ b/themes/Tiny_Swords/tiny_swords_canvas.tscn @@ -0,0 +1,224 @@ +[gd_scene load_steps=17 format=3 uid="uid://u4f1it53avo1"] + +[ext_resource type="Script" path="res://themes/Tiny_Swords/tiny_swords_canvas.gd" id="1_k04u4"] +[ext_resource type="Script" path="res://themes/Tiny_Swords/grapic/grapic.gd" id="2_ff0a0"] +[ext_resource type="Texture2D" uid="uid://d2fc18wxmmf6e" path="res://themes/Tiny_Swords/assets/characters/Warrior_Blue.png" id="3_mrsu2"] +[ext_resource type="Theme" uid="uid://bn50jp1pavmmx" path="res://themes/Tiny_Swords/tiny_swords_canvas.tres" id="4_0s2uf"] +[ext_resource type="Script" path="res://themes/Tiny_Swords/dialogue/dialogue.gd" id="4_8v2p0"] +[ext_resource type="Texture2D" uid="uid://ckkx6v5ckrfpd" path="res://themes/Tiny_Swords/assets/dialogue/Banner_Vertical.png" id="5_wpqw3"] +[ext_resource type="Script" path="res://themes/Base_Canvas/dialogue/chat_message_ai.gd" id="7_0038y"] + +[sub_resource type="Animation" id="Animation_75tyo"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [0] +} + +[sub_resource type="Animation" id="Animation_4elpe"] +resource_name = "attack_down" +length = 0.9 +step = 0.15 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3, 0.45, 0.6, 0.75), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [12, 13, 14, 15, 16, 17] +} + +[sub_resource type="Animation" id="Animation_aknv2"] +resource_name = "attack_left" +length = 0.9 +step = 0.15 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3, 0.45, 0.6, 0.75), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [24, 25, 26, 27, 28, 29] +} + +[sub_resource type="Animation" id="Animation_rhkkn"] +resource_name = "attack_right" +length = 0.9 +step = 0.15 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3, 0.45, 0.6, 0.75), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [30, 31, 32, 33, 34, 35] +} + +[sub_resource type="Animation" id="Animation_3unuo"] +resource_name = "attack_up" +length = 0.9 +step = 0.15 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3, 0.45, 0.6, 0.75), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [18, 19, 20, 21, 22, 23] +} + +[sub_resource type="Animation" id="Animation_kb6fg"] +resource_name = "idle" +length = 0.9 +loop_mode = 1 +step = 0.15 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3, 0.45, 0.6, 0.75), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [0, 1, 2, 3, 4, 5] +} + +[sub_resource type="Animation" id="Animation_8umtp"] +resource_name = "run" +length = 0.9 +step = 0.15 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.15, 0.3, 0.45, 0.6, 0.75), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [6, 7, 8, 9, 10, 11] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_5hx07"] +_data = { +"RESET": SubResource("Animation_75tyo"), +"attack_down": SubResource("Animation_4elpe"), +"attack_left": SubResource("Animation_aknv2"), +"attack_right": SubResource("Animation_rhkkn"), +"attack_up": SubResource("Animation_3unuo"), +"idle": SubResource("Animation_kb6fg"), +"run": SubResource("Animation_8umtp") +} + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_o4c6w"] +texture = ExtResource("5_wpqw3") +region_rect = Rect2(34.6328, 30.5003, 120.784, 132.116) + +[node name="TinySwordsCanvas" type="Node2D"] +position = Vector2(2373, 152) +script = ExtResource("1_k04u4") +metadata/_edit_lock_ = true +metadata/_edit_group_ = true + +[node name="Grapic" type="Node2D" parent="."] +scale = Vector2(-2, 2) +script = ExtResource("2_ff0a0") + +[node name="Pet" type="Sprite2D" parent="Grapic"] +position = Vector2(-1, 33.5) +texture = ExtResource("3_mrsu2") +hframes = 6 +vframes = 8 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="Grapic/Pet"] +libraries = { +"": SubResource("AnimationLibrary_5hx07") +} +autoplay = "idle" +next/attack_up = &"RESET" + +[node name="Area2D" type="Area2D" parent="Grapic"] +position = Vector2(2, 9) + +[node name="ClickPolygon" type="CollisionPolygon2D" parent="Grapic/Area2D"] +unique_name_in_owner = true +self_modulate = Color(1, 1, 1, 0.105882) +polygon = PackedVector2Array(276, 91, -65, 91, -64, -82.5, 274.5, -83.5) +disabled = true + +[node name="StateChange" type="Timer" parent="."] +wait_time = 30.0 + +[node name="Dialogue_Timer" type="Timer" parent="."] +wait_time = 20.0 +one_shot = true +autostart = true + +[node name="Dialogue" type="Control" parent="."] +unique_name_in_owner = true +layout_mode = 3 +anchors_preset = 0 +offset_left = -514.0 +offset_top = -214.0 +offset_right = -145.0 +offset_bottom = 227.0 +scale = Vector2(0.8, 0.8) +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme = ExtResource("4_0s2uf") +script = ExtResource("4_8v2p0") + +[node name="PanelContainer" type="PanelContainer" parent="Dialogue"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 56.0 +offset_top = 84.0 +offset_right = 100.0 +offset_bottom = 70.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxTexture_o4c6w") + +[node name="MarginContainer" type="MarginContainer" parent="Dialogue/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 64 +theme_override_constants/margin_top = 47 +theme_override_constants/margin_right = 47 +theme_override_constants/margin_bottom = 80 + +[node name="ChatMessageAI" type="RichTextLabel" parent="Dialogue/PanelContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_colors/default_color = Color(0, 0, 0, 1) +theme_override_font_sizes/normal_font_size = 25 +bbcode_enabled = true +script = ExtResource("7_0038y")