enhance: chat with LLM

This commit is contained in:
Jacky 2024-05-02 12:49:01 +08:00
parent e84ea98be9
commit 642e21a260
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
40 changed files with 544 additions and 250 deletions

102
internal/chatbot/context.go Normal file
View file

@ -0,0 +1,102 @@
package chatbot
import (
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/sashabaranov/go-openai"
"os"
"regexp"
"strings"
)
type includeContext struct {
Paths []string
PathsMap map[string]bool
}
func IncludeContext(filename string) (includes []string) {
c := &includeContext{
Paths: make([]string, 0),
PathsMap: make(map[string]bool),
}
c.extractIncludes(filename)
return c.Paths
}
// extractIncludes extracts all include statements from the given nginx configuration file.
func (c *includeContext) extractIncludes(filename string) {
if !helper.FileExists(filename) {
logger.Error("File does not exist: ", filename)
return
}
// Read the file content
content, err := os.ReadFile(filename)
if err != nil {
logger.Error(err)
return
}
// Find all include statements
pattern := regexp.MustCompile(`(?m)^\s*include\s+([^;]+);`)
matches := pattern.FindAllStringSubmatch(string(content), -1)
for _, match := range matches {
if len(match) > 1 {
// Resolve the path of the included file
includePath := match[1]
// to avoid infinite loop
if c.PathsMap[includePath] {
continue
}
c.push(includePath)
// Recursively extract includes from the included file
c.extractIncludes(includePath)
}
}
return
}
func (c *includeContext) push(path string) {
c.Paths = append(c.Paths, path)
c.PathsMap[path] = true
}
// getConfigIncludeContext returns the context of the given filename.
func getConfigIncludeContext(filename string) (multiContent []openai.ChatMessagePart) {
multiContent = make([]openai.ChatMessagePart, 0)
if !helper.IsUnderDirectory(filename, nginx.GetConfPath()) {
return
}
includes := IncludeContext(filename)
logger.Debug(includes)
var sb strings.Builder
for _, include := range includes {
text, _ := os.ReadFile(nginx.GetConfPath(include))
if len(text) == 0 {
continue
}
sb.WriteString("The Content of ")
sb.WriteString(include)
sb.WriteString(",")
sb.WriteString(string(text))
multiContent = append(multiContent, openai.ChatMessagePart{
Type: openai.ChatMessagePartTypeText,
Text: sb.String(),
})
sb.Reset()
}
return
}

View file

@ -0,0 +1,23 @@
package chatbot
import (
"github.com/stretchr/testify/assert"
"regexp"
"testing"
)
func TestRegex(t *testing.T) {
content := `
server {
listen 80;
listen [::]:80;
server_name _;
include error_json;
}
`
pattern := regexp.MustCompile(`(?m)^\s*include\s+([^;]+);`)
matches := pattern.FindAllStringSubmatch(content, -1)
assert.Equal(t, 1, len(matches))
assert.Equal(t, "error_json", matches[0][1])
}

View file

@ -0,0 +1,22 @@
package chatbot
import (
"github.com/sashabaranov/go-openai"
)
func ChatCompletionWithContext(filename string, messages []openai.ChatCompletionMessage) []openai.ChatCompletionMessage {
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Role == openai.ChatMessageRoleUser {
// openai.ChatCompletionMessage: can't use both Content and MultiContent properties simultaneously
multiContent := getConfigIncludeContext(filename)
multiContent = append(multiContent, openai.ChatMessagePart{
Type: openai.ChatMessagePartTypeText,
Text: messages[i].Content,
})
messages[i].Content = ""
messages[i].MultiContent = multiContent
}
}
return messages
}

View file

@ -0,0 +1,26 @@
package chatbot
import (
"github.com/sashabaranov/go-openai"
"github.com/stretchr/testify/assert"
"testing"
)
func TestChatCompletionWithContext(t *testing.T) {
filename := "test"
messages := []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
},
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
}
messages = ChatCompletionWithContext(filename, messages)
assert.NotNil(t, messages[1].MultiContent)
}

View file

@ -9,7 +9,7 @@ type Config struct {
Name string `json:"name"`
Content string `json:"content,omitempty"`
ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
FilePath string `json:"file_path,omitempty"`
FilePath string `json:"filepath,omitempty"`
ModifiedAt time.Time `json:"modified_at"`
Size int64 `json:"size,omitempty"`
IsDir bool `json:"is_dir"`

View file

@ -0,0 +1,25 @@
package helper
import (
"github.com/0xJacky/Nginx-UI/internal/logger"
"path/filepath"
"strings"
)
func IsUnderDirectory(path, directory string) bool {
absPath, err := filepath.Abs(path)
if err != nil {
logger.Error(err)
return false
}
absDirectory, err := filepath.Abs(directory)
if err != nil {
logger.Error(err)
return false
}
absDirectory = filepath.Clean(absDirectory) + string(filepath.Separator)
return strings.HasPrefix(absPath, absDirectory)
}

View file

@ -0,0 +1,11 @@
package helper
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestIsUnderDirectory(t *testing.T) {
assert.Equal(t, true, IsUnderDirectory("/etc/nginx/nginx.conf", "/etc/nginx"))
assert.Equal(t, false, IsUnderDirectory("../../root/nginx.conf", "/etc/nginx"))
}