Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
gcslaoli committed Apr 13, 2023
0 parents commit bd6cdb8
Show file tree
Hide file tree
Showing 17 changed files with 898 additions and 0 deletions.
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
MIT License

Copyright (c) Miguel Piedrafita

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.
170 changes: 170 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# chatgpt-go

ChatGPT SDK 是用 Golang 编写的,可以用于快速集成 ChatGPT 的聊天机器人功能。

## 快速开始

1. 安装 ChatGPT-Go SDK。

```shell
go get -u github.com/xyhelper/chatgpt-go
```

2. 在独立的对话中进行聊天。

```go
package main

import (
"log"
"time"

chatgpt "github.com/xyhelper/chatgpt-go"
)

func main() {
token := `random token`

cli := chatgpt.NewClient(
chatgpt.WithDebug(true), // 开启调试模式
chatgpt.WithTimeout(120*time.Second), // 设置超时时间为120秒
chatgpt.WithAccessToken(token), // 设置token
chatgpt.WithBaseURI("https://freechat.lidong.xin"), // 设置base uri
)

// chat in independent conversation
message := "你好"
text, err := cli.GetChatText(message)
if err != nil {
log.Fatalf("get chat text failed: %v", err)
}

log.Printf("q: %s, a: %s\n", message, text.Content)
}

```

3. 在连续的对话中进行聊天。

```go
package main

import (
"log"
"time"

chatgpt "github.com/xyhelper/chatgpt-go"
)

func main() {
// new chatgpt client
token := `random token`

cli := chatgpt.NewClient(
chatgpt.WithDebug(true),
chatgpt.WithTimeout(60*time.Second),
chatgpt.WithAccessToken(token),
chatgpt.WithBaseURI("https://freechat.lidong.xin"),
)

// chat in continuous conversation

// first message
message := "对我说你好"
text, err := cli.GetChatText(message)
if err != nil {
log.Fatalf("get chat text failed: %v", err)
}

log.Printf("q: %s, a: %s\n", message, text.Content)

// continue conversation with new message
conversationID := text.ConversationID
parentMessage := text.MessageID
newMessage := "再说一次"

newText, err := cli.GetChatText(newMessage, conversationID, parentMessage)
if err != nil {
log.Fatalf("get chat text failed: %v", err)
}

log.Printf("q: %s, a: %s\n", newMessage, newText.Content)
}
```

> 如果你想要在当前对话之外开始一个新的对话,你不需要重置客户端。只需在`GetChatText`方法中移除`conversationID``parentMessage`参数,即可获得一个新的文本回复,从而开始一个新的对话。
4. 使用流(stream)获取聊天内容。

```go
package main

import (
"log"
"time"

chatgpt "github.com/xyhelper/chatgpt-go"
)

func main() {
// new chatgpt client
token := `random token`

cli := chatgpt.NewClient(
chatgpt.WithDebug(true),
chatgpt.WithTimeout(120*time.Second),
chatgpt.WithAccessToken(token),
chatgpt.WithBaseURI("https://freechat.xyhelper.cn"),
)

message := "你好"
stream, err := cli.GetChatStream(message)
if err != nil {
log.Fatalf("get chat stream failed: %v\n", err)
}

var answer string
for text := range stream.Stream {
log.Printf("stream text: %s\n", text.Content)

answer = text.Content
}

if stream.Err != nil {
log.Fatalf("stream closed with error: %v\n", stream.Err)
}

log.Printf("q: %s, a: %s\n", message, answer)
}

```

## DEMO

cli/chatgpt-go 是一个使用 ChatGPT-Go SDK 的示例程序。
可以使用以下命令运行它:

```shell
cd cli/chatgpt-go
go run main.go
```

也可以使用 go install 命令安装它:

```shell
go install github.com/xyhelper/chatgpt-go/cli/chatgpt-go@latest
```

然后运行:

```shell
chatgpt-go
```

## 作品演示

[https://xyhelper.cn](https;//xyhelper.cn)

## 友情链接

- [CoolAdmin](https://cool-js.com) - 一个项目,用 COOL 就够了。AI 编码、物联网、函数云开发等更加智能和全面的功能,让应用开发紧跟时代的步伐,cool 绝不落后!!!
195 changes: 195 additions & 0 deletions chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package chatgpt

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"

"github.com/google/uuid"
"github.com/launchdarkly/eventsource"
"github.com/tidwall/gjson"
)

// ChatText chat reply with text format
type ChatText struct {
data string // event data
ConversationID string // conversation context id
MessageID string // current message id, can used as next chat's parent_message_id
Content string // text content
}

// ChatStream chat reply with sream
type ChatStream struct {
Stream chan *ChatText // chat text stream
Err error // error message
}

// ChatText format to string
func (c *ChatText) String() string {
return c.data
}

// GetChatText will return text message
func (c *Client) GetChatText(message string, args ...string) (*ChatText, error) {
resp, err := c.sendMessage(message, args...)
if err != nil {
return nil, fmt.Errorf("send message failed: %v", err)
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response body failed: %v", err)
}

arr := strings.Split(string(body), "\n\n")

const TEXT_ARR_MIN_LEN = 3
const TEXT_STR_MIN_LEN = 6

l := len(arr)
if l < TEXT_ARR_MIN_LEN {
return nil, fmt.Errorf("invalid reply message: %s", body)
}

str := arr[l-TEXT_ARR_MIN_LEN]
if len(str) < TEXT_STR_MIN_LEN {
return nil, fmt.Errorf("invalid reply message: %s", body)
}

text := str[TEXT_STR_MIN_LEN:]

return c.parseChatText(text)
}

// GetChatStream will return text stream
func (c *Client) GetChatStream(message string, args ...string) (*ChatStream, error) {
resp, err := c.sendMessage(message, args...)
if err != nil {
return nil, fmt.Errorf("send message failed: %v", err)
}

chatStream := &ChatStream{
Stream: make(chan *ChatText),
Err: nil,
}

decoder := eventsource.NewDecoder(resp.Body)

go func() {
defer resp.Body.Close()
defer close(chatStream.Stream)

for {
event, err := decoder.Decode()
if err != nil {
chatStream.Err = fmt.Errorf("decode data failed: %v", err)
return
}

text := event.Data()
if text == "" || text == EOF_TEXT {
// read data finished, success return
return
}

chatText, err := c.parseChatText(text)
if err != nil {
continue
}

chatStream.Stream <- chatText
}
}()

return chatStream, nil
}

// parseChatText will return a ChatText struct from ChatText json
func (c *Client) parseChatText(text string) (*ChatText, error) {
if text == "" || text == EOF_TEXT {
return nil, fmt.Errorf("invalid chat text: %s", text)
}

res := gjson.Parse(text)

conversationID := res.Get("conversation_id").String()
messageID := res.Get("message.id").String()
content := res.Get("message.content.parts.0").String()

if conversationID == "" || messageID == "" {
return nil, fmt.Errorf("invalid chat text")
}

return &ChatText{
data: text,
ConversationID: conversationID,
MessageID: messageID,
Content: content,
}, nil
}

// sendMessage will send message to ChatGPT server
func (c *Client) sendMessage(message string, args ...string) (*http.Response, error) {
accessToken, err := c.getAccessToken()
if err != nil {
return nil, fmt.Errorf("get accessToken failed: %v", err)
}

var messageID string
var conversationID string
var parentMessageID string

messageID = uuid.NewString()
if len(args) > 0 {
conversationID = args[0]
}
if len(args) > 1 {
parentMessageID = args[1]
}
if parentMessageID == "" {
parentMessageID = uuid.NewString()
}

params := MixMap{
"action": "next",
"model": c.opts.Model,
"parent_message_id": parentMessageID,
"messages": []MixMap{
{
"role": "user",
"id": messageID,
"content": MixMap{
"content_type": "text",
"parts": []string{message},
},
},
},
}

if conversationID != "" {
params["conversation_id"] = conversationID
}

data, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("marshal request body failed: %v", err)
}

req, err := http.NewRequest(http.MethodPost, c.baseURI+"/backend-api/conversation", bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("new request failed: %v", err)
}

bearerToken := fmt.Sprintf("Bearer %s", accessToken)
req.Header.Set("Authorization", bearerToken)
req.Header.Set("Accept", "text/event-stream")
req.Header.Set("Content-Type", "application/json")

resp, err := c.doRequest(req)

return resp, err
}
Loading

0 comments on commit bd6cdb8

Please sign in to comment.