Skip to content

Commit

Permalink
Merge pull request #85 from whatwewant/feat/support-image-generation
Browse files Browse the repository at this point in the history
feat: support image generation (closes #86)
  • Loading branch information
whatwewant authored Nov 27, 2023
2 parents 4f9bd2d + cd06d2f commit f2eb47b
Show file tree
Hide file tree
Showing 15 changed files with 910 additions and 590 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@

## 功能支持

### 核心
* [x] 支持文本生成
* [x] 支持图片生成

### 扩展
* [x] 支持长对话,自动联系上下文
* [x] 支持私人对话
* [x] 支持群聊
Expand Down
76 changes: 76 additions & 0 deletions commands/custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package commands

import (
"net/http"

"github.com/go-zoox/chatbot-feishu"
chatgpt "github.com/go-zoox/chatgpt-client"
"github.com/go-zoox/chatgpt-for-chatbot-feishu/config"
"github.com/go-zoox/core-utils/fmt"
"github.com/go-zoox/feishu"
feishuEvent "github.com/go-zoox/feishu/event"
"github.com/go-zoox/fetch"
"github.com/go-zoox/logger"
)

func CreateCustomCommand(
feishuClient feishu.Client,
chatgptClient chatgpt.Client,
cfg *config.Config,
) *chatbot.Command {
return &chatbot.Command{
ArgsLength: 1,
Handler: func(args []string, request *feishuEvent.EventRequest, reply chatbot.MessageReply) error {
if len(args) != 1 {
return fmt.Errorf("invalid args: %v", args)
}

question := args[0]
logger.Debugf("[custom command: %s, service: %s] question: %s", cfg.CustomCommand, cfg.CustomCommandService, question)

response, err := fetch.Post(cfg.CustomCommandService, &fetch.Config{
Headers: fetch.Headers{
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": fmt.Sprintf("go-zoox_fetch/%s chatgpt-for-chatbot-feishu/%s", fetch.Version, cfg.Version),
},
Body: map[string]interface{}{
"question": args[0],
},
})
if err != nil {
logger.Errorf("failed to request from custom command service(%s)(1): %v", cfg.CustomCommandService, err)
if err2 := replyText(reply, fmt.Sprintf("failed to interact with command service(err: %v)", err)); err2 != nil {
return fmt.Errorf("failed to reply: %v", err)
}

return nil
}

if response.Status != http.StatusOK {
logger.Errorf("failed to request from custom command service(%s)(2): %d", cfg.CustomCommandService, response.Status)
if err := replyText(reply, fmt.Sprintf("failed to interact with command service (status: %d, response: %s)", response.Status, response.String())); err != nil {
return fmt.Errorf("failed to reply: %v", err)
}

return nil
}

answer := response.Get("answer").String()
if answer == "" {
logger.Error("failed to request from custom command service(%s): empty answer (response: %s)", cfg.CustomCommandService, response.String())
if err := replyText(reply, fmt.Sprintf("no answer found, unexpected response from custom command service(response: %s)", response.String())); err != nil {
return fmt.Errorf("failed to reply: %v", err)
}

return nil
}

if err := replyText(reply, answer); err != nil {
return fmt.Errorf("failed to reply: %v", err)
}

return nil
},
}
}
68 changes: 68 additions & 0 deletions commands/draw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package commands

import (
"github.com/go-zoox/chatbot-feishu"
chatgpt "github.com/go-zoox/chatgpt-client"
"github.com/go-zoox/core-utils/fmt"
"github.com/go-zoox/core-utils/strings"
"github.com/go-zoox/feishu"
feishuEvent "github.com/go-zoox/feishu/event"
feishuImage "github.com/go-zoox/feishu/image"
"github.com/go-zoox/fetch"
"github.com/go-zoox/fs"
"github.com/go-zoox/logger"
openaiclient "github.com/go-zoox/openai-client"
)

func CreateDrawCommand(
feishuClient feishu.Client,
chatgptClient chatgpt.Client,
) *chatbot.Command {
return &chatbot.Command{
Handler: func(args []string, request *feishuEvent.EventRequest, reply chatbot.MessageReply) error {
prompt := strings.Join(args, " ")
if prompt == "" {
return replyText(reply, fmt.Sprintf("prompt is required (args: %s)", strings.Join(args, " ")))
}

logger.Infof("[draw]: %v", prompt)

logger.Infof("[draw]: request image generation ...")
response, err := chatgptClient.ImageGeneration(&openaiclient.ImageGenerationRequest{
Prompt: prompt,
})
if err != nil {
return replyText(reply, fmt.Sprintf("failed to request image generation: %v", err))
}

for _, image := range response.Data {
tmpFilePath := fs.TmpFilePath()

logger.Infof("[draw] download image from chatgpt: %v", image.URL)
_, err := fetch.Download(image.URL, tmpFilePath, &fetch.Config{})
if err != nil {
return replyText(reply, fmt.Sprintf("failed to download image: %v", err))
}

tmpFile, err := fs.Open(tmpFilePath)
if err != nil {
return replyText(reply, fmt.Sprintf("failed to open image: %v", err))
}

logger.Infof("[draw] upload image to feishu ...")
response, err := feishuClient.Image().Upload(&feishuImage.UploadRequest{
ImageType: "message",
Image: tmpFile,
})
if err != nil {
return replyText(reply, fmt.Sprintf("failed to upload image: %v", err))
}

logger.Infof("[draw] reply image to feishu: %v", response.ImageKey)
replyImage(reply, response.ImageKey)
}

return nil
},
}
}
166 changes: 166 additions & 0 deletions commands/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package commands

import (
"time"

"github.com/go-zoox/core-utils/regexp"

"github.com/go-zoox/chatbot-feishu"
chatgpt "github.com/go-zoox/chatgpt-client"
"github.com/go-zoox/chatgpt-for-chatbot-feishu/config"
"github.com/go-zoox/core-utils/fmt"
"github.com/go-zoox/core-utils/strings"
"github.com/go-zoox/feishu"
feishuEvent "github.com/go-zoox/feishu/event"
mc "github.com/go-zoox/feishu/message/content"
"github.com/go-zoox/logger"
"github.com/go-zoox/retry"
)

func CreateMessageCommand(
feishuClient feishu.Client,
chatgptClient chatgpt.Client,
cfg *config.Config,
) *chatbot.Command {
return &chatbot.Command{
Handler: func(args []string, request *feishuEvent.EventRequest, reply func(content string, msgType ...string) error) (err error) {
text := strings.Join(args, " ")

// fmt.PrintJSON(request)
if cfg.BotInfo == nil {
logger.Infof("Trying to get bot info ...")
cfg.BotInfo, err = feishuClient.Bot().GetBotInfo()
if err != nil {
return fmt.Errorf("failed to get bot info: %v", err)
}
}

user, err := getUser(feishuClient, request, cfg)
if err != nil {
return fmt.Errorf("failed to get user: %v", err)
}

textMessage := strings.TrimSpace(text)
if textMessage == "" {
return nil
}

var question string
// group chat
if request.IsGroupChat() {
// @
if ok := regexp.Match("^@_user_1", textMessage); ok {
for _, metion := range request.Event.Message.Mentions {
if metion.Key == "@_user_1" && metion.ID.OpenID == cfg.BotInfo.OpenID {
question = textMessage[len("@_user_1"):]
question = strings.TrimSpace(question)
break
}
}
} else if ok := regexp.Match("^/chatgpt\\s+", textMessage); ok {
// command: /chatgpt
question = textMessage[len("/chatgpt "):]
}
} else if request.IsP2pChat() {
question = textMessage
}

question = strings.TrimSpace(question)
if question == "" {
logger.Infof("ignore empty question message")
return nil
}

// @TODO 离线服务
if !cfg.IsInService {
return replyText(reply, cfg.OfflineMessage)
}

go func() {
logger.Debugf("%s 问 ChatGPT:%s", user.User.Name, question)

var err error

conversation, err := chatgptClient.GetOrCreateConversation(request.ChatID(), &chatgpt.ConversationConfig{
MaxMessages: 50,
Model: cfg.OpenAIModel,
})
if err != nil {
logger.Errorf("failed to get or create conversation by ChatID %s", request.ChatID())
return
}

if err := conversation.IsQuestionAsked(request.Event.Message.MessageID); err != nil {
logger.Warnf("duplicated event(id: %s): %v", request.Event.Message.MessageID, err)
return
}

var answer []byte
err = retry.Retry(func() error {

answer, err = conversation.Ask([]byte(question), &chatgpt.ConversationAskConfig{
ID: request.Event.Message.MessageID,
User: user.User.Name,
})
if err != nil {
logger.Errorf("failed to request answer: %v", err)
return fmt.Errorf("failed to request answer: %v", err)
}

return nil
}, 5, 3*time.Second)
if err != nil {
logger.Errorf("failed to get answer: %v", err)
msgType, content, err := mc.
NewContent().
Text(&mc.ContentTypeText{
Text: "ChatGPT 繁忙,请稍后重试",
}).
Build()
if err != nil {
logger.Errorf("failed to build content: %v", err)
return
}
if err := reply(string(content), msgType); err != nil {
return
}
return
}

logger.Debugf("ChatGPT 答 %s:%s", user.User.Name, answer)

responseMessage := string(answer)
// if request.IsGroupChat() {
// responseMessage = fmt.Sprintf("%s\n-------------\n%s", question, answer)
// }

msgType, content, err := mc.
NewContent().
Post(&mc.ContentTypePost{
ZhCN: &mc.ContentTypePostBody{
Content: [][]mc.ContentTypePostBodyItem{
{
{
Tag: "text",
UnEscape: true,
Text: responseMessage,
},
},
},
},
}).
Build()
if err != nil {
logger.Errorf("failed to build content: %v", err)
return
}
if err := reply(string(content), msgType); err != nil {
logger.Errorf("failed to reply: %v", err)
return
}
}()

return nil
},
}
}
60 changes: 60 additions & 0 deletions commands/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package commands

import (
"github.com/go-zoox/chatbot-feishu"
chatgpt "github.com/go-zoox/chatgpt-client"
"github.com/go-zoox/chatgpt-for-chatbot-feishu/config"
"github.com/go-zoox/core-utils/fmt"
"github.com/go-zoox/core-utils/strings"
"github.com/go-zoox/feishu"
feishuEvent "github.com/go-zoox/feishu/event"
)

func CreateModelCommand(
feishuClient feishu.Client,
chatgptClient chatgpt.Client,
cfg *config.Config,
) *chatbot.Command {
return &chatbot.Command{
ArgsLength: 1,
Handler: func(args []string, request *feishuEvent.EventRequest, reply func(content string, msgType ...string) error) error {
if err := isAllowToDo(feishuClient, cfg, request, "model"); err != nil {
return err
}

if len(args) == 0 || args[0] == "" {
currentModel, err := chatgptClient.GetConversationModel(request.ChatID(), &chatgpt.ConversationConfig{
MaxMessages: 50,
Model: cfg.OpenAIModel,
})
if err != nil {
return fmt.Errorf("failed to get model by conversation(%s)", request.ChatID())
}

if err := replyText(reply, fmt.Sprintf("当前模型:%s", currentModel)); err != nil {
return fmt.Errorf("failed to reply: %v", err)
}

return nil
}

model := args[0]
if model == "" {
return fmt.Errorf("model name is required (args: %s)", strings.Join(args, " "))
}

if err := chatgptClient.ChangeConversationModel(request.ChatID(), model, &chatgpt.ConversationConfig{
MaxMessages: 50,
Model: cfg.OpenAIModel,
}); err != nil {
return fmt.Errorf("failed to set model(%s) for conversation(%s)", model, request.ChatID())
}

if err := replyText(reply, fmt.Sprintf("succeed to set model: %s", model)); err != nil {
return fmt.Errorf("failed to reply: %v", err)
}

return nil
},
}
}
Loading

0 comments on commit f2eb47b

Please sign in to comment.