diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .golangci.yml | 32 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | bot.go | 72 | ||||
-rw-r--r-- | main.go | 1 | ||||
-rw-r--r-- | session.go | 5 | ||||
-rw-r--r-- | storage/storage.go | 1 | ||||
-rw-r--r-- | tools.go | 9 | ||||
-rw-r--r-- | tui.go | 45 |
9 files changed, 109 insertions, 59 deletions
@@ -4,3 +4,4 @@ testlog elefant history/ *.db +config.toml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..66732bf --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,32 @@ +run: + timeout: 1m + concurrency: 2 + tests: false + +linters: + enable-all: false + disable-all: true + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused + - prealloc + presets: + - performance + +linters-settings: + funlen: + lines: 80 + statements: 50 + lll: + line-length: 80 + +issues: + exclude: + # Display all issues + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..41d7962 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +lint: ## Run linters. Use make install-linters first. + golangci-lint run -c .golangci.yml ./... @@ -53,15 +53,15 @@ func formMsg(chatBody *models.ChatBody, newMsg, role string) io.Reader { } // func sendMsgToLLM(body io.Reader) (*models.LLMRespChunk, error) { -func sendMsgToLLM(body io.Reader) (any, error) { +func sendMsgToLLM(body io.Reader) { + // nolint resp, err := httpClient.Post(cfg.APIURL, "application/json", body) if err != nil { logger.Error("llamacpp api", "error", err) - return nil, err + return } defer resp.Body.Close() - llmResp := []models.LLMRespChunk{} - // chunkChan <- cfg.AssistantIcon + // llmResp := []models.LLMRespChunk{} reader := bufio.NewReader(resp.Body) counter := 0 for { @@ -90,9 +90,9 @@ func sendMsgToLLM(body io.Reader) (any, error) { if err := json.Unmarshal(line, &llmchunk); err != nil { logger.Error("failed to decode", "error", err, "line", string(line)) streamDone <- true - return nil, err + return } - llmResp = append(llmResp, llmchunk) + // llmResp = append(llmResp, llmchunk) // logger.Info("streamview", "chunk", llmchunk) // if llmchunk.Choices[len(llmchunk.Choices)-1].FinishReason != "chat.completion.chunk" { if llmchunk.Choices[len(llmchunk.Choices)-1].FinishReason == "stop" { @@ -105,7 +105,6 @@ func sendMsgToLLM(body io.Reader) (any, error) { answerText := strings.ReplaceAll(llmchunk.Choices[0].Delta.Content, "\n\n", "\n") chunkChan <- answerText } - return llmResp, nil } func chatRound(userMsg, role string, tv *tview.TextView) { @@ -117,8 +116,8 @@ func chatRound(userMsg, role string, tv *tview.TextView) { } go sendMsgToLLM(reader) if userMsg != "" { // no need to write assistant icon since we continue old message - fmt.Fprintf(tv, fmt.Sprintf("(%d) ", len(chatBody.Messages))) - fmt.Fprintf(tv, cfg.AssistantIcon) + fmt.Fprintf(tv, "(%d) ", len(chatBody.Messages)) + fmt.Fprint(tv, cfg.AssistantIcon) } respText := strings.Builder{} out: @@ -126,7 +125,7 @@ out: select { case chunk := <-chunkChan: // fmt.Printf(chunk) - fmt.Fprintf(tv, chunk) + fmt.Fprint(tv, chunk) respText.WriteString(chunk) tv.ScrollToEnd() case <-streamDone: @@ -163,7 +162,7 @@ func findCall(msg string, tv *tview.TextView) { // call a func f, ok := fnMap[fc.Name] if !ok { - m := fmt.Sprintf("%s is not implemented", fc.Name) + m := fc.Name + "%s is not implemented" chatRound(m, cfg.ToolRole, tv) return } @@ -188,30 +187,30 @@ func chatToText(showSys bool) string { return strings.Join(s, "") } -func textToMsg(rawMsg string) models.MessagesStory { - msg := models.MessagesStory{} - // system and tool? - if strings.HasPrefix(rawMsg, cfg.AssistantIcon) { - msg.Role = cfg.AssistantRole - msg.Content = strings.TrimPrefix(rawMsg, cfg.AssistantIcon) - return msg - } - if strings.HasPrefix(rawMsg, cfg.UserIcon) { - msg.Role = cfg.UserRole - msg.Content = strings.TrimPrefix(rawMsg, cfg.UserIcon) - return msg - } - return msg -} +// func textToMsg(rawMsg string) models.MessagesStory { +// msg := models.MessagesStory{} +// // system and tool? +// if strings.HasPrefix(rawMsg, cfg.AssistantIcon) { +// msg.Role = cfg.AssistantRole +// msg.Content = strings.TrimPrefix(rawMsg, cfg.AssistantIcon) +// return msg +// } +// if strings.HasPrefix(rawMsg, cfg.UserIcon) { +// msg.Role = cfg.UserRole +// msg.Content = strings.TrimPrefix(rawMsg, cfg.UserIcon) +// return msg +// } +// return msg +// } -func textSliceToChat(chat []string) []models.MessagesStory { - resp := make([]models.MessagesStory, len(chat)) - for i, rawMsg := range chat { - msg := textToMsg(rawMsg) - resp[i] = msg - } - return resp -} +// func textSliceToChat(chat []string) []models.MessagesStory { +// resp := make([]models.MessagesStory, len(chat)) +// for i, rawMsg := range chat { +// msg := textToMsg(rawMsg) +// resp[i] = msg +// } +// return resp +// } func init() { cfg = config.LoadConfigOrDefault("config.example.toml") @@ -233,7 +232,10 @@ func init() { store = storage.NewProviderSQL("test.db", logger) // https://github.com/coreydaley/ggerganov-llama.cpp/blob/master/examples/server/README.md // load all chats in memory - loadHistoryChats() + if _, err := loadHistoryChats(); err != nil { + logger.Error("failed to load chat", "error", err) + return + } lastChat := loadOldChatOrGetNew() logger.Info("loaded history") chatBody = &models.ChatBody{ @@ -9,7 +9,6 @@ import ( var ( botRespMode = false editMode = false - botMsg = "no" selectedIndex = int(-1) indexLine = "F12 to show keys help; bot resp mode: %v; current chat: %s" focusSwitcher = map[tview.Primitive]tview.Primitive{} @@ -3,6 +3,7 @@ package main import ( "elefant/models" "encoding/json" + "errors" "fmt" "os/exec" "strings" @@ -19,7 +20,7 @@ func historyToSJSON(msgs []models.MessagesStory) (string, error) { return "", err } if data == nil { - return "", fmt.Errorf("nil data") + return "", errors.New("nil data") } return string(data), nil } @@ -61,7 +62,7 @@ func loadHistoryChats() ([]string, error) { func loadHistoryChat(chatName string) ([]models.MessagesStory, error) { chat, ok := chatMap[chatName] if !ok { - err := fmt.Errorf("failed to read chat") + err := errors.New("failed to read chat") logger.Error("failed to read chat", "name", chatName) return nil, err } diff --git a/storage/storage.go b/storage/storage.go index c863799..66640fd 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -55,6 +55,7 @@ func (p ProviderSQL) UpsertChat(chat *models.Chat) (*models.Chat, error) { if err != nil { return nil, err } + defer stmt.Close() // Execute the query and scan the result into a new chat object var resp models.Chat err = stmt.Get(&resp, chat) @@ -73,8 +73,11 @@ func memorise(args ...string) []byte { Mind: args[1], UpdatedAt: time.Now(), } - store.Memorise(memory) - msg := fmt.Sprintf("info saved under the topic: %s", args[0]) + if _, err := store.Memorise(memory); err != nil { + logger.Error("failed to save memory", "err", err, "memoory", memory) + return []byte("failed to save info") + } + msg := "info saved under the topic:" + args[0] return []byte(msg) } @@ -104,7 +107,7 @@ func recallTopics(args ...string) []byte { return []byte(joinedS) } -func fullMemoryLoad() {} +// func fullMemoryLoad() {} type fnSig func(...string) []byte @@ -11,15 +11,15 @@ import ( ) var ( - app *tview.Application - pages *tview.Pages - textArea *tview.TextArea - editArea *tview.TextArea - textView *tview.TextView - position *tview.TextView - helpView *tview.TextView - flex *tview.Flex - chatActModal *tview.Modal + app *tview.Application + pages *tview.Pages + textArea *tview.TextArea + editArea *tview.TextArea + textView *tview.TextView + position *tview.TextView + helpView *tview.TextView + flex *tview.Flex + // chatActModal *tview.Modal sysModal *tview.Modal indexPickWindow *tview.InputField renameWindow *tview.InputField @@ -145,7 +145,9 @@ func init() { if event.Key() == tcell.KeyEscape && editMode { editedMsg := editArea.GetText() if editedMsg == "" { - notifyUser("edit", "no edit provided") + if err := notifyUser("edit", "no edit provided"); err != nil { + logger.Error("failed to send notification", "error", err) + } pages.RemovePage("editArea") editMode = false return nil @@ -165,7 +167,6 @@ func init() { SetAcceptanceFunc(tview.InputFieldInteger). SetDoneFunc(func(key tcell.Key) { pages.RemovePage("getIndex") - return }) indexPickWindow.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { si := indexPickWindow.GetText() @@ -183,9 +184,13 @@ func init() { editArea.SetText(m.Content, true) } if !editMode && event.Key() == tcell.KeyEnter { - copyToClipboard(m.Content) + if err := copyToClipboard(m.Content); err != nil { + logger.Error("failed to copy to clipboard", "error", err) + } notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:30]) - notifyUser("copied", notification) + if err := notifyUser("copied", notification); err != nil { + logger.Error("failed to send notification", "error", err) + } } return event }) @@ -196,7 +201,6 @@ func init() { SetAcceptanceFunc(tview.InputFieldMaxLength(100)). SetDoneFunc(func(key tcell.Key) { pages.RemovePage("renameW") - return }) renameWindow.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyEnter { @@ -214,14 +218,15 @@ func init() { logger.Error("failed to upsert chat", "error", err, "chat", currentChat) } notification := fmt.Sprintf("renamed chat to '%s'", activeChatName) - notifyUser("renamed", notification) + if err := notifyUser("renamed", notification); err != nil { + logger.Error("failed to send notification", "error", err) + } } return event }) // helpView = tview.NewTextView().SetDynamicColors(true).SetText(helpText).SetDoneFunc(func(key tcell.Key) { pages.RemovePage("helpView") - return }) helpView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { @@ -281,9 +286,13 @@ func init() { // copy msg to clipboard editMode = false m := chatBody.Messages[len(chatBody.Messages)-1] - copyToClipboard(m.Content) + if err := copyToClipboard(m.Content); err != nil { + logger.Error("failed to copy to clipboard", "error", err) + } notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:30]) - notifyUser("copied", notification) + if err := notifyUser("copied", notification); err != nil { + logger.Error("failed to send notification", "error", err) + } return nil } if event.Key() == tcell.KeyF8 { |