Skip to content

Commit

Permalink
feat: implement AI command (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
monotykamary authored Sep 7, 2024
1 parent 6b0086b commit ef3a2df
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 20 deletions.
14 changes: 14 additions & 0 deletions devbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.12.0/.schema/devbox.schema.json",
"packages": ["[email protected]"],
"shell": {
"init_hook": [
"echo 'Welcome to devbox!' > /dev/null"
],
"scripts": {
"test": [
"echo \"Error: no test specified\" && exit 1"
]
}
}
}
53 changes: 53 additions & 0 deletions devbox.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"lockfile_version": "1",
"packages": {
"[email protected]": {
"last_modified": "2024-08-31T10:12:23Z",
"resolved": "github:NixOS/nixpkgs/5629520edecb69630a3f4d17d3d33fc96c13f6fe#go_1_23",
"source": "devbox-search",
"version": "1.23.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/4cijk6gwv59c84h1l9yhxzsaz93f67mz-go-1.23.0",
"default": true
}
],
"store_path": "/nix/store/4cijk6gwv59c84h1l9yhxzsaz93f67mz-go-1.23.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/mhwsyzk9v43q67ic34c02sxnsnbj7qbh-go-1.23.0",
"default": true
}
],
"store_path": "/nix/store/mhwsyzk9v43q67ic34c02sxnsnbj7qbh-go-1.23.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/vbcqda38ha9gqsbwjw4q0swpwlvmnb1i-go-1.23.0",
"default": true
}
],
"store_path": "/nix/store/vbcqda38ha9gqsbwjw4q0swpwlvmnb1i-go-1.23.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/h5wkf711ql98c59n7yxa146jbjf9vrj5-go-1.23.0",
"default": true
}
],
"store_path": "/nix/store/h5wkf711ql98c59n7yxa146jbjf9vrj5-go-1.23.0"
}
}
}
}
}
2 changes: 1 addition & 1 deletion pkg/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func New(cfg *config.Config, l logger.Logger) IAdapter {
Mochi: mochi.New(cfg.Endpoint.Mochi),
OpenAI: openai.New(cfg.OpenAI.APIKey),
Tono: tono.New(cfg),
Dify: dify.New(cfg.Dify.BaseURL, cfg.Dify.SummarizerAppToken),
Dify: dify.New(cfg.Dify.BaseURL, cfg.Dify.SummarizerAppToken, cfg.Dify.ProcessAIAppToken),
},
}
}
Expand Down
72 changes: 53 additions & 19 deletions pkg/adapter/dify/dify.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import (
type Dify struct {
BaseURL string
SummarizerAppToken string
ProcessAIAppToken string
}

// New function return dify service
func New(baseURL, summarizerAppToken string) DifyAdapter {
func New(baseURL, summarizerAppToken, processAIAppToken string) *Dify {
return &Dify{
BaseURL: baseURL,
SummarizerAppToken: summarizerAppToken,
ProcessAIAppToken: processAIAppToken,
}
}

Expand Down Expand Up @@ -64,14 +66,36 @@ func (d *Dify) SummarizeArticle(url string) (content string, err error) {
return "", err
}

return d.processDifyRequest(requestBody, d.SummarizerAppToken)
}

// ProcessAIText processes any text input using the Dify API
func (d *Dify) ProcessAIText(input string) (content string, err error) {
// Define the URL and request body
requestBody, err := json.Marshal(map[string]interface{}{
"inputs": map[string]interface{}{},
"query": input,
"response_mode": "streaming",
"conversation_id": "",
"user": "fortress",
})
if err != nil {
return "", err
}

return d.processDifyRequest(requestBody, d.ProcessAIAppToken)
}

// processDifyRequest handles the common logic for making requests to the Dify API
func (d *Dify) processDifyRequest(requestBody []byte, token string) (content string, err error) {
// Create the HTTP request
req, err := http.NewRequest(http.MethodPost, d.BaseURL, bytes.NewBuffer(requestBody))
if err != nil {
return "", nil
return "", err
}

// Set the required headers
req.Header.Set("Authorization", "Bearer "+d.SummarizerAppToken)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")

client := &http.Client{
Expand All @@ -89,16 +113,38 @@ func (d *Dify) SummarizeArticle(url string) (content string, err error) {
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

// Handle streaming response
thoughts, err := d.handleStreamingResponse(resp.Body)
if err != nil {
return "", err
}

// get the last event
if len(thoughts) == 0 {
return "", fmt.Errorf("no thought found")
}

for i := len(thoughts) - 1; i >= 0; i-- {
if thoughts[i].Thought != "" {
content = thoughts[i].Thought
break
}
}

return content, nil
}

// handleStreamingResponse processes the streaming response from the Dify API
func (d *Dify) handleStreamingResponse(body io.ReadCloser) ([]AgentThought, error) {
var thoughts []AgentThought
reader := bufio.NewReader(resp.Body)
reader := bufio.NewReader(body)

for {
line, err := reader.ReadBytes('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
return nil, err
}

// Remove the "data: " prefix
Expand Down Expand Up @@ -138,17 +184,5 @@ func (d *Dify) SummarizeArticle(url string) (content string, err error) {
}
}

// get the last event
if len(thoughts) == 0 {
return "", fmt.Errorf("no thought found")
}

for i := len(thoughts) - 1; i >= 0; i-- {
if thoughts[i].Thought != "" {
content = thoughts[i].Thought
break
}
}

return content, nil
return thoughts, nil
}
1 change: 1 addition & 0 deletions pkg/adapter/dify/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package dify

type DifyAdapter interface {
SummarizeArticle(youtubeURL string) (content string, err error)
ProcessAIText(input string) (content string, err error)
}
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type ApiServer struct {
type Dify struct {
BaseURL string
SummarizerAppToken string
ProcessAIAppToken string
}
type ENV interface {
GetBool(string) bool
Expand Down Expand Up @@ -106,6 +107,7 @@ func Generate(v ENV) *Config {
Dify: Dify{
BaseURL: v.GetString("DIFY_BASE_URL"),
SummarizerAppToken: v.GetString("DIFY_SUMMARIZER_APP_TOKEN"),
ProcessAIAppToken: v.GetString("DIFY_PROCESS_AI_APP_TOKEN"),
},
}
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/discord/command/ai/ai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ai

import (
"strings"

"github.com/dwarvesf/fortress-discord/pkg/discord/service"
"github.com/dwarvesf/fortress-discord/pkg/discord/view"
"github.com/dwarvesf/fortress-discord/pkg/logger"
"github.com/dwarvesf/fortress-discord/pkg/model"
)

type AI struct {
L logger.Logger
svc service.Servicer
view view.Viewer
}

func New(l logger.Logger, svc service.Servicer, view view.Viewer) AICommander {
return &AI{
L: l,
svc: svc,
view: view,
}
}

func (a *AI) ProcessAI(message *model.DiscordMessage) error {
input := strings.TrimSpace(strings.TrimPrefix(message.RawContent, "?ai"))

if input == "" {
return a.view.Error().Raise(message, "Please provide some text to process.")
}

// Process the text using the AI service
response, err := a.svc.AI().ProcessText(input)
if err != nil {
a.L.Error(err, "failed to process AI text")
return err
}

// Send the AI response back to the user
return a.view.AI().SendResponse(message, response)
}
35 changes: 35 additions & 0 deletions pkg/discord/command/ai/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ai

import (
"github.com/dwarvesf/fortress-discord/pkg/model"
)

func (a *AI) Prefix() []string {
return []string{"ai"}
}

// Execute is where we handle logic for each command
func (a *AI) Execute(message *model.DiscordMessage) error {
// For AI command, we always process the input, regardless of the number of arguments
return a.DefaultCommand(message)
}

func (a *AI) Name() string {
return "AI Command"
}

func (a *AI) Help(message *model.DiscordMessage) error {
helpText := "The AI command processes any text input using an AI model.\n\n" +
"Usage: ?ai <your text here>\n" +
"Example: ?ai What is the capital of France?"

return a.view.Error().Raise(message, helpText)
}

func (a *AI) DefaultCommand(message *model.DiscordMessage) error {
return a.ProcessAI(message)
}

func (a *AI) PermissionCheck(message *model.DiscordMessage) (bool, []string) {
return true, []string{}
}
12 changes: 12 additions & 0 deletions pkg/discord/command/ai/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ai

import (
"github.com/dwarvesf/fortress-discord/pkg/discord/base"
"github.com/dwarvesf/fortress-discord/pkg/model"
)

type AICommander interface {
base.TextCommander

ProcessAI(message *model.DiscordMessage) error
}
2 changes: 2 additions & 0 deletions pkg/discord/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/dwarvesf/fortress-discord/pkg/discord/base"
"github.com/dwarvesf/fortress-discord/pkg/discord/command/adopt"
salary "github.com/dwarvesf/fortress-discord/pkg/discord/command/advance"
"github.com/dwarvesf/fortress-discord/pkg/discord/command/ai"
"github.com/dwarvesf/fortress-discord/pkg/discord/command/assess"
"github.com/dwarvesf/fortress-discord/pkg/discord/command/brainery"
"github.com/dwarvesf/fortress-discord/pkg/discord/command/changelog"
Expand Down Expand Up @@ -89,6 +90,7 @@ func New(cfg *config.Config, l logger.Logger, svc service.Servicer, view view.Vi
news.New(l, svc, view, cfg),
topic.New(l, svc, view, cfg),
ogif.New(l, svc, view, cfg),
ai.New(l, svc, view),
})

return cmd
Expand Down
34 changes: 34 additions & 0 deletions pkg/discord/service/ai/ai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package ai

import (
"fmt"

"github.com/dwarvesf/fortress-discord/pkg/adapter"
"github.com/dwarvesf/fortress-discord/pkg/logger"
"github.com/dwarvesf/fortress-discord/pkg/model"
)

type AI struct {
adapter adapter.IAdapter
l logger.Logger
}

func New(adapter adapter.IAdapter, l logger.Logger) AIServicer {
return &AI{
adapter: adapter,
l: l,
}
}

func (a *AI) ProcessText(input string) (*model.AIResponse, error) {
response, err := a.adapter.Dify().ProcessAIText(input)
if err != nil {
fmt.Printf("failed to process AI text. Error: %v", err)
return nil, err
}

return &model.AIResponse{
Input: input,
Response: response,
}, nil
}
9 changes: 9 additions & 0 deletions pkg/discord/service/ai/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ai

import (
"github.com/dwarvesf/fortress-discord/pkg/model"
)

type AIServicer interface {
ProcessText(input string) (*model.AIResponse, error)
}
2 changes: 2 additions & 0 deletions pkg/discord/service/interface.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"github.com/dwarvesf/fortress-discord/pkg/discord/service/ai"
"github.com/dwarvesf/fortress-discord/pkg/discord/service/brainery"
"github.com/dwarvesf/fortress-discord/pkg/discord/service/changelog"
"github.com/dwarvesf/fortress-discord/pkg/discord/service/deliverymetrics"
Expand Down Expand Up @@ -29,6 +30,7 @@ import (
)

type Servicer interface {
AI() ai.AIServicer
Brainery() brainery.Service
Changelog() changelog.ChangelogServicer
DeliveryMetrics() deliverymetrics.DeliveryMetricsServicer
Expand Down
Loading

0 comments on commit ef3a2df

Please sign in to comment.