Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jihe520 committed Jun 6, 2024
0 parents commit 4e49b5f
Show file tree
Hide file tree
Showing 73 changed files with 2,774 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Godot 4+ specific ignores
.godot/
.build/
.test/
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<h1 align="center">🐶 Godog 🐶</h1>
<h2 align="center">AI Godot 桌宠</h2>

简体中文 / [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) - 角色资产
104 changes: 104 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<h1 align="center">🐶 Godog 🐶</h1>
<h2 align="center">AI Godot Desktop Pet</h2>

[简体中文](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
144 changes: 144 additions & 0 deletions addons/HTTPSSEClient/HTTPSSEClient_modified.gd
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit 4e49b5f

Please sign in to comment.