Skip to content

Commit 045ff09

Browse files
author
Aki
committed
make stream class
1 parent 1600e16 commit 045ff09

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

gemini_stream_client.gd

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
class_name GeminiStreamClient extends HTTPClient
2+
3+
var _api_key
4+
5+
signal receive_text(text)
6+
signal receive_finished
7+
8+
var _thread = Thread.new()
9+
func _init(api_key):
10+
_api_key = api_key
11+
_thread.start(_loop_process)
12+
print("thread started")
13+
14+
var path_text_regex = RegEx.new()
15+
func request_text(prompt):
16+
17+
var url = "https://generativelanguage.googleapis.com/v1/models/gemini-pro:streamGenerateContent?key=%s"%_api_key
18+
19+
var body = JSON.new().stringify({
20+
"contents":[
21+
{ "parts":[{
22+
"text": prompt
23+
}]
24+
}
25+
]
26+
})
27+
var fields = {
28+
"contents":[
29+
{ "parts":[{
30+
"text": prompt
31+
}]
32+
}
33+
]
34+
}
35+
36+
print("send-content:"+str(body))
37+
38+
var status = self.get_status()
39+
if status!=Status.STATUS_CONNECTED:
40+
var error0 = self.connect_to_host("https://generativelanguage.googleapis.com",-1) # Connect to the host on port 80
41+
if error0 != OK:
42+
return error0
43+
44+
while self.get_status() == HTTPClient.STATUS_CONNECTING or self.get_status() == HTTPClient.STATUS_RESOLVING:
45+
self.poll()
46+
if not OS.has_feature("web"):
47+
OS.delay_msec(30)
48+
else:
49+
# TODO test
50+
await Engine.get_singleton("SceneTree").create_timer(0.0).wait_one_frame()
51+
else:
52+
print("status:%s"%get_status_text(status))
53+
assert(self.get_status() == HTTPClient.STATUS_CONNECTED)
54+
55+
56+
var query_string = self.query_string_from_dict(fields)
57+
58+
var error = self.request(HTTPClient.METHOD_POST,"/v1/models/gemini-pro:streamGenerateContent?key=%s"%_api_key, ["User-Agent: Pirulo/1.0 (Godot)","Accept: */*","Content-Type: application/json"], body)
59+
60+
return error
61+
62+
var receiving = false
63+
var last_status = 0
64+
func _loop_process():
65+
while _thread:
66+
_process()
67+
OS.delay_msec(30)
68+
69+
func _process():
70+
var status = self.get_status()
71+
if status!=last_status:
72+
print("status:%s"%get_status_text(status))
73+
last_status = status
74+
75+
path_text_regex.compile(r'"parts":\s*\[\s*{\s*"text":\s*"((?:[^"\\]|\\.)*)"\s*}\s*\]')
76+
77+
#print("status:%s"%http_client.get_status())
78+
if self.get_status() == HTTPClient.STATUS_REQUESTING:
79+
self.poll()
80+
81+
elif self.get_status() == HTTPClient.STATUS_BODY:
82+
#print("body")
83+
receiving = true
84+
var response_body = self.read_response_body_chunk()
85+
if response_body.size() > 0:
86+
# Process the response body
87+
var texts = response_body.get_string_from_utf8()
88+
#print(texts)
89+
var result = path_text_regex.search(texts)
90+
#print(result)
91+
if result:
92+
call_deferred("emit_signal","receive_text",result.get_string(1))
93+
else:
94+
# possible show some remaining.
95+
print("somehow no text:%s"%texts)
96+
97+
elif receiving and self.get_status() == HTTPClient.STATUS_CONNECTED:
98+
call_deferred("emit_signal","receive_finished")
99+
receiving = false
100+
OS.delay_msec(30)
101+
102+
func dispose():
103+
_thread.free()
104+
105+
func get_status_text(status):
106+
match status:
107+
Status.STATUS_DISCONNECTED:
108+
return "Disconnected from the server."
109+
Status.STATUS_RESOLVING:
110+
return "Currently resolving the hostname for the given URL into an IP."
111+
Status.STATUS_CANT_RESOLVE:
112+
return "DNS failure: Can't resolve the hostname for the given URL."
113+
Status.STATUS_CONNECTING:
114+
return "Currently connecting to server."
115+
Status.STATUS_CANT_CONNECT:
116+
return "Can't connect to the server."
117+
Status.STATUS_CONNECTED:
118+
return "Connection established."
119+
Status.STATUS_REQUESTING:
120+
return "Currently sending request."
121+
Status.STATUS_BODY:
122+
return "HTTP body received."
123+
Status.STATUS_CONNECTION_ERROR:
124+
return "Error in HTTP connection."
125+
Status.STATUS_TLS_HANDSHAKE_ERROR:
126+
return "Error in TLS handshake."
127+
_:
128+
return "Unknown status."

send_text_stream2.gd

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
extends Control
2+
3+
4+
#see https://ai.google.dev/tutorials/rest_quickstart
5+
6+
var api_key = ""
7+
var gemini_stream_client
8+
var response_edit
9+
func _ready():
10+
var settings = JSON.parse_string(FileAccess.get_file_as_string("res://settings.json"))
11+
api_key = settings.api_key
12+
gemini_stream_client = GeminiStreamClient.new(api_key)
13+
gemini_stream_client.receive_text.connect(_on_receive_text)
14+
gemini_stream_client.receive_finished.connect(_on_receive_finished)
15+
16+
response_edit = find_child("ResponseEdit")
17+
func _on_request_finished():
18+
find_child("SendButton").disabled = false
19+
20+
func _on_send_button_pressed():
21+
var input = find_child("InputEdit").text
22+
_request_text(input)
23+
24+
func _request_text(prompt):
25+
find_child("ResponseEdit").text = ""
26+
find_child("SendButton").disabled = true
27+
var error = gemini_stream_client.request_text(prompt)
28+
29+
if error != OK:
30+
push_error("Some error happened:%s"%error)
31+
32+
func _on_receive_text(text):
33+
text = text.replace("\\n\\n","\n") #somehow return double
34+
text = text.replace("\\n", "\n") #
35+
text = text.replace("\\r", "\r") #
36+
text = text.replace("\\t", "\t") #
37+
text = text.replace("\\\\", "\\") #
38+
text = text.replace("\\\"", "\"") #
39+
text = text.replace("\\\'", "\'") #
40+
41+
response_edit.text = response_edit.text + text
42+
43+
func _on_receive_finished():
44+
find_child("SendButton").disabled = false

send_text_stream2.tscn

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
[gd_scene load_steps=3 format=3 uid="uid://vo5pro0gjv7u"]
2+
3+
[ext_resource type="Script" path="res://send_text_stream2.gd" id="1_eirlv"]
4+
5+
[sub_resource type="LabelSettings" id="LabelSettings_6qyb2"]
6+
font_color = Color(1, 0, 0, 1)
7+
8+
[node name="Control" type="Control"]
9+
layout_mode = 3
10+
anchors_preset = 15
11+
anchor_right = 1.0
12+
anchor_bottom = 1.0
13+
grow_horizontal = 2
14+
grow_vertical = 2
15+
script = ExtResource("1_eirlv")
16+
17+
[node name="MarginContainer" type="MarginContainer" parent="."]
18+
layout_mode = 1
19+
anchors_preset = 15
20+
anchor_right = 1.0
21+
anchor_bottom = 1.0
22+
grow_horizontal = 2
23+
grow_vertical = 2
24+
theme_override_constants/margin_left = 8
25+
theme_override_constants/margin_top = 8
26+
theme_override_constants/margin_right = 8
27+
theme_override_constants/margin_bottom = 8
28+
29+
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
30+
layout_mode = 2
31+
32+
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
33+
layout_mode = 2
34+
text = "User"
35+
36+
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
37+
layout_mode = 2
38+
39+
[node name="SendButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
40+
layout_mode = 2
41+
text = "Send"
42+
43+
[node name="InputEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"]
44+
custom_minimum_size = Vector2(250, 0)
45+
layout_mode = 2
46+
size_flags_horizontal = 3
47+
text = "Hello how are you.return long text"
48+
49+
[node name="Label2" type="Label" parent="MarginContainer/VBoxContainer"]
50+
layout_mode = 2
51+
text = "Model"
52+
53+
[node name="FinishedLabel" type="Label" parent="MarginContainer/VBoxContainer"]
54+
visible = false
55+
layout_mode = 2
56+
label_settings = SubResource("LabelSettings_6qyb2")
57+
58+
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
59+
layout_mode = 2
60+
61+
[node name="ResponseEdit" type="TextEdit" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
62+
custom_minimum_size = Vector2(250, 280)
63+
layout_mode = 2
64+
size_flags_horizontal = 3
65+
editable = false
66+
wrap_mode = 1
67+
68+
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/SendButton" to="." method="_on_send_button_pressed"]

0 commit comments

Comments
 (0)