Skip to content

Commit e57e949

Browse files
committed
Update readme and add example
1 parent 2562606 commit e57e949

File tree

7 files changed

+151
-87
lines changed

7 files changed

+151
-87
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Miscellaneous
2+
.vscode/
23
*.class
34
*.log
45
*.pyc

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
## 0.0.7
2+
3+
* Update Pubspec and readme
4+
15
## 0.0.6
26

3-
* Handle parsing case where stream response has multiple chunks in byte stream
7+
* Handle parsing case where stream response has multiple chunks of `data:` in byte stream
48

59
## 0.0.5
610

example/main.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:chatgpt_client/chatgpt_client.dart';
2+
3+
void main() async {
4+
final client = ChatGPTClient(apiKey: "API_KEY");
5+
final prompt = "what is observable object?";
6+
7+
/// Standard Response
8+
print("Standard Response");
9+
try {
10+
final text = await client.sendMessage(prompt);
11+
print(text);
12+
} catch (exception) {
13+
print(exception.toString());
14+
}
15+
16+
/// Stream Response
17+
print("Stream Response");
18+
try {
19+
var text = "";
20+
final stream = client.sendMessageStream(prompt);
21+
await for (final textChunk in stream) {
22+
text += textChunk;
23+
print(textChunk);
24+
}
25+
print(text);
26+
} catch (exception) {
27+
print(exception.toString());
28+
}
29+
30+
client.clearHistoryList();
31+
}

lib/src/chatgptclient.dart

Lines changed: 80 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,28 @@ import 'dart:convert';
22
import 'package:http/http.dart' as http;
33
import 'package:chatgpt_client/src/message.dart';
44

5+
/// A class to interact with OpenAI ChatGPT Completions API
6+
/// Support various models such as gpt-3.5-turbo, gpt-4, etc
57
class ChatGPTClient {
8+
9+
/// OpenAI ChatGPT Completions API Endpoint URL
610
final url = Uri.https("api.openai.com", "/v1/chat/completions");
11+
12+
/// OpenAI API Key which you can get from https://openai.com/api
713
final String apiKey;
14+
15+
/// GPT Model (gpt-3.5-turbo, gpt-4, etc) default to gpt-3.5-turbo
816
final String model;
17+
18+
/// System prompt, default to "You're a helpful assistant"
919
final String systemPrompt;
20+
21+
/// Temperature, default to 0.5
1022
final double temperature;
1123

1224
List<Message> _historyList = List.empty(growable: true);
1325

26+
/// Initializer, API key is required
1427
ChatGPTClient(
1528
{required this.apiKey,
1629
this.model = "gpt-3.5-turbo",
@@ -24,7 +37,7 @@ class ChatGPTClient {
2437
};
2538
}
2639

27-
String _getBody(String text, bool stream) {
40+
String _getBody(String text, bool stream) {
2841
final body = {
2942
"model": model,
3043
"temperature": temperature,
@@ -35,14 +48,17 @@ class ChatGPTClient {
3548
}
3649

3750
List<Message> _generateMessages(String prompt) {
38-
var messages = [_getSystemMessage()] + _historyList + [Message(content: prompt, role: "user")];
39-
final messagesContentCount = messages.map((e) => e.content.length)
40-
.reduce((value, element) => value + element);
51+
var messages = [_getSystemMessage()] +
52+
_historyList +
53+
[Message(content: prompt, role: "user")];
54+
final messagesContentCount = messages
55+
.map((e) => e.content.length)
56+
.reduce((value, element) => value + element);
4157
if (messagesContentCount > (4000 * 4)) {
42-
_historyList.removeAt(0);
43-
messages = _generateMessages(prompt);
44-
}
45-
return messages;
58+
_historyList.removeAt(0);
59+
messages = _generateMessages(prompt);
60+
}
61+
return messages;
4662
}
4763

4864
void _appendToHistoryList(String userText, String responseText) {
@@ -56,77 +72,80 @@ class ChatGPTClient {
5672
return Message(content: systemPrompt, role: "system");
5773
}
5874

75+
/// Send message to ChatGPT to a prompt asynchronously
5976
Future<String> sendMessage(String text) async {
60-
final response = await http.Client().post(url,
61-
headers: _getHeaders(), body: _getBody(text, false));
62-
63-
dynamic decodedResponse;
64-
if (response.contentLength != null) {
65-
decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;
66-
}
77+
final response = await http.Client()
78+
.post(url, headers: _getHeaders(), body: _getBody(text, false));
6779

68-
final statusCode = response.statusCode;
69-
if (!(statusCode >= 200 && statusCode < 300)) {
70-
if (decodedResponse != null) {
71-
final errorMessage = decodedResponse["error"]["message"] as String;
72-
throw Exception("($statusCode) $errorMessage");
73-
}
74-
throw Exception("($statusCode) Bad response ${response.reasonPhrase ?? ""}");
80+
dynamic decodedResponse;
81+
if (response.contentLength != null) {
82+
decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;
83+
}
84+
85+
final statusCode = response.statusCode;
86+
if (!(statusCode >= 200 && statusCode < 300)) {
87+
if (decodedResponse != null) {
88+
final errorMessage = decodedResponse["error"]["message"] as String;
89+
throw Exception("($statusCode) $errorMessage");
7590
}
76-
77-
final choices = decodedResponse["choices"] as List;
78-
final choice = choices[0] as Map;
79-
final content = choice["message"]["content"] as String;
80-
_appendToHistoryList(text, content);
81-
return content;
91+
throw Exception(
92+
"($statusCode) Bad response ${response.reasonPhrase ?? ""}");
93+
}
94+
95+
final choices = decodedResponse["choices"] as List;
96+
final choice = choices[0] as Map;
97+
final content = choice["message"]["content"] as String;
98+
_appendToHistoryList(text, content);
99+
return content;
82100
}
83101

102+
// Send Message to ChatGPT and receives the streamed response in chunk
84103
Stream<String> sendMessageStream(String text) async* {
85-
final request = http.Request("POST", url)
86-
..headers.addAll(_getHeaders());
87-
request.body = _getBody(text, true);
88-
89-
final responseStream = await request.send();
90-
final statusCode = responseStream.statusCode;
91-
final byteStream = responseStream.stream;
92-
93-
if (!(statusCode >= 200 && statusCode < 300)) {
94-
var error = "";
95-
await for (final byte in byteStream) {
96-
final decoded = utf8.decode(byte).trim();
97-
final map = jsonDecode(decoded) as Map;
98-
final errorMessage = map["error"]["message"] as String;
99-
error += errorMessage;
100-
}
101-
throw Exception("($statusCode) ${error.isEmpty ? "Bad Response" : error}");
102-
}
104+
final request = http.Request("POST", url)..headers.addAll(_getHeaders());
105+
request.body = _getBody(text, true);
106+
107+
final responseStream = await request.send();
108+
final statusCode = responseStream.statusCode;
109+
final byteStream = responseStream.stream;
103110

104-
var responseText = "";
111+
if (!(statusCode >= 200 && statusCode < 300)) {
112+
var error = "";
105113
await for (final byte in byteStream) {
106-
var decoded = utf8.decode(byte);
107-
final strings = decoded.split("data: ");
108-
for (final string in strings) {
109-
final trimmedString = string.trim();
110-
if (trimmedString.isNotEmpty && !trimmedString.endsWith("[DONE]")) {
111-
final map = jsonDecode(trimmedString) as Map;
112-
final choices = map["choices"] as List;
113-
final delta = choices[0]["delta"] as Map;
114-
if (delta["content"] != null) {
115-
final content = delta["content"] as String;
116-
responseText += content;
117-
yield content;
118-
}
114+
final decoded = utf8.decode(byte).trim();
115+
final map = jsonDecode(decoded) as Map;
116+
final errorMessage = map["error"]["message"] as String;
117+
error += errorMessage;
118+
}
119+
throw Exception(
120+
"($statusCode) ${error.isEmpty ? "Bad Response" : error}");
121+
}
122+
123+
var responseText = "";
124+
await for (final byte in byteStream) {
125+
var decoded = utf8.decode(byte);
126+
final strings = decoded.split("data: ");
127+
for (final string in strings) {
128+
final trimmedString = string.trim();
129+
if (trimmedString.isNotEmpty && !trimmedString.endsWith("[DONE]")) {
130+
final map = jsonDecode(trimmedString) as Map;
131+
final choices = map["choices"] as List;
132+
final delta = choices[0]["delta"] as Map;
133+
if (delta["content"] != null) {
134+
final content = delta["content"] as String;
135+
responseText += content;
136+
yield content;
119137
}
120138
}
139+
}
121140
}
122141

123142
if (responseText.isNotEmpty) {
124143
_appendToHistoryList(text, responseText);
125144
}
126145
}
127146

147+
/// Clear history list array
128148
void clearHistoryList() {
129149
_historyList = List.empty(growable: true);
130150
}
131151
}
132-

lib/src/message.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
/// ChatGPT message
12
class Message {
3+
4+
/// Content of message
25
final String content;
6+
7+
/// Role of message (system, user, assistant)
38
final String role;
49

10+
/// Initializer
511
Message({required this.content, required this.role});
612

13+
/// Convert instance to dictionary
714
Map<String, String> toMap() {
815
return {"role": role, "content": content};
916
}

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: chatgpt_client
2-
description: ChatGPT API Client for Dart
3-
version: 0.0.6
2+
description: Access OpenAI ChatGPT Public API using Dart Language. Supports native Dart project and all Flutter target platforms (iOS, Android, Windows, Linux, Web) It provides streamed and normal response when asking ChatGPT, the client also stores the converstation list history so it can be used on new prompt to make it aware of previous conversation context.
3+
version: 0.0.7
44
homepage: https://github.com/alfianlosari/chatgpt_api_dart
55

66
environment:

test/chatgpt_client_test.dart

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
import 'package:chatgpt_client/chatgpt_client.dart';
22

33
void main() async {
4-
final client = ChatGPTClient(apiKey: "API_KEY");
5-
final prompt = "What is ChatGPT?";
6-
/// Standard Response
7-
print("Standard Response");
8-
try {
9-
final text = await client.sendMessage(prompt);
10-
print(text);
11-
} catch (exception) {
12-
print(exception.toString());
13-
}
4+
final client = ChatGPTClient(
5+
apiKey: "API_KEY");
6+
final prompt = "what is observable object?";
7+
8+
/// Standard Response
9+
print("Standard Response");
10+
try {
11+
final text = await client.sendMessage(prompt);
12+
print(text);
13+
} catch (exception) {
14+
print(exception.toString());
15+
}
1416

15-
/// Stream Response
16-
print("Stream Response");
17-
try {
18-
var text = "";
19-
final stream = client.sendMessageStream(prompt);
20-
await for (final textChunk in stream) {
21-
text += textChunk;
22-
print(textChunk);
23-
}
24-
print(text);
25-
} catch (exception) {
26-
print(exception.toString());
17+
/// Stream Response
18+
print("Stream Response");
19+
try {
20+
var text = "";
21+
final stream = client.sendMessageStream(prompt);
22+
await for (final textChunk in stream) {
23+
text += textChunk;
24+
print(textChunk);
2725
}
26+
print(text);
27+
} catch (exception) {
28+
print(exception.toString());
29+
}
2830

29-
client.clearHistoryList();
31+
client.clearHistoryList();
3032
}

0 commit comments

Comments
 (0)