summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md1
-rw-r--r--bot.go145
-rw-r--r--main.go10
-rw-r--r--models/models.go21
5 files changed, 148 insertions, 30 deletions
diff --git a/.gitignore b/.gitignore
index 92828cc..a149479 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*.json
testlog
elefant
+history/
diff --git a/README.md b/README.md
index 27ccedf..167f8df 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
### TODO:
- scrolling chat history; (somewhat works out of box);
- log errors to file;
+- give serial id to each msg in chat to track it;
- regen last message;
- delete last message
- edit message? (including from bot);
diff --git a/bot.go b/bot.go
index 9564e3f..09d3e62 100644
--- a/bot.go
+++ b/bot.go
@@ -21,17 +21,23 @@ var httpClient = http.Client{
}
var (
- logger *slog.Logger
- APIURL = "http://localhost:8080/v1/chat/completions"
- DB = map[string]map[string]any{}
- userRole = "user"
- assistantRole = "assistant"
- toolRole = "tool"
- assistantIcon = "<🤖>: "
- chunkChan = make(chan string, 10)
- streamDone = make(chan bool, 1)
- chatBody *models.ChatBody
- systemMsg = `You're a helpful assistant.
+ logger *slog.Logger
+ APIURL = "http://localhost:8080/v1/chat/completions"
+ DB = map[string]map[string]any{}
+ userRole = "user"
+ assistantRole = "assistant"
+ toolRole = "tool"
+ assistantIcon = "<🤖>: "
+ userIcon = "<user>: "
+ chunkChan = make(chan string, 10)
+ streamDone = make(chan bool, 1)
+ chatBody *models.ChatBody
+ defaultFirstMsg = "Hello! What can I do for you?"
+ defaultStarter = []models.MessagesStory{
+ {Role: "system", Content: systemMsg},
+ {Role: assistantRole, Content: defaultFirstMsg},
+ }
+ systemMsg = `You're a helpful assistant.
# Tools
You can do functions call if needed.
Your current tools:
@@ -213,30 +219,111 @@ func findCall(msg string, tv *tview.TextView) {
chatRound(toolMsg, toolRole, tv)
// return func result to the llm
}
+
+func findLatestChat() string {
+ dir := "./history/"
+ files, err := os.ReadDir(dir)
+ if err != nil {
+ logger.Error("failed to readdir", "error", err)
+ panic(err)
+ }
+ var (
+ latestF string
+ newestTime int64
+ )
+ logger.Info("filelist", "list", files)
+ for _, f := range files {
+ fi, err := os.Stat(dir + f.Name())
+ if err != nil {
+ logger.Error("failed to get stat", "error", err, "name", f.Name())
+ panic(err)
+ }
+ currTime := fi.ModTime().Unix()
+ if currTime > newestTime {
+ newestTime = currTime
+ latestF = f.Name()
+ }
+ }
+ return latestF
+}
+
+func readHistoryChat(fn string) ([]models.MessagesStory, error) {
+ content, err := os.ReadFile(fn)
+ if err != nil {
+ logger.Error("failed to read file", "error", err, "name", fn)
+ return nil, err
+ }
+ resp := []models.MessagesStory{}
+ if err := json.Unmarshal(content, &resp); err != nil {
+ logger.Error("failed to unmarshal", "error", err, "name", fn)
+ return nil, err
+ }
+ return resp, nil
+}
+
+func loadOldChatOrGetNew(fns ...string) []models.MessagesStory {
+ // find last chat
+ fn := findLatestChat()
+ if len(fns) > 0 {
+ fn = fns[0]
+ }
+ logger.Info("reading history from file", "filename", fn)
+ history, err := readHistoryChat(fn)
+ if err != nil {
+ logger.Warn("faield to load history chat", "error", err)
+ return defaultStarter
+ }
+ return history
+}
+
+func chatToText() []string {
+ resp := make([]string, len(chatBody.Messages))
+ for i, msg := range chatBody.Messages {
+ resp[i] = msg.ToText()
+ }
+ return resp
+}
+
+func textToChat(chat []string) []models.MessagesStory {
+ resp := make([]models.MessagesStory, len(chat))
+ for i, rawMsg := range chat {
+ // trim icon
+ var (
+ role string
+ msg string
+ )
+ // system and tool?
+ if strings.HasPrefix(rawMsg, assistantIcon) {
+ role = assistantRole
+ msg = strings.TrimPrefix(rawMsg, assistantIcon)
+ goto messagebuild
+ }
+ if strings.HasPrefix(rawMsg, userIcon) {
+ role = assistantRole
+ msg = strings.TrimPrefix(rawMsg, userIcon)
+ goto messagebuild
+ }
+ messagebuild:
+ resp[i].Role = role
+ resp[i].Content = msg
+ }
+ return resp
+}
+
func init() {
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
- defer file.Close()
- logger = slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{}))
+ // defer file.Close()
+ logger = slog.New(slog.NewTextHandler(file, nil))
logger.Info("test msg")
- firstMsg := "Hello! What can I do for you?"
- // fm, err := fillTempl("chatml", chatml)
- // if err != nil {
- // panic(err)
- // }
// https://github.com/coreydaley/ggerganov-llama.cpp/blob/master/examples/server/README.md
+ lastChat := loadOldChatOrGetNew()
+ logger.Info("loaded history", "chat", lastChat)
chatBody = &models.ChatBody{
- Model: "modl_name",
- Stream: true,
- Messages: []models.MessagesStory{
- {Role: "system", Content: systemMsg},
- {Role: assistantRole, Content: firstMsg},
- },
- }
- // fmt.Printf("<🤖>: Hello! How can I help?")
- // for {
- // chatLoop()
- // }
+ Model: "modl_name",
+ Stream: true,
+ Messages: lastChat,
+ }
}
diff --git a/main.go b/main.go
index 7d3bb56..472945f 100644
--- a/main.go
+++ b/main.go
@@ -2,6 +2,7 @@ package main
import (
"fmt"
+ "strings"
"unicode"
"github.com/gdamore/tcell/v2"
@@ -53,7 +54,14 @@ func main() {
}
textArea.SetMovedFunc(updateStatusLine)
updateStatusLine()
- textView.SetText("<🤖>: Hello! What can I do for you?")
+ history := chatToText()
+ chatHistory := strings.Builder{}
+ for _, m := range history {
+ chatHistory.WriteString(m)
+ // textView.SetText(m)
+ }
+ textView.SetText(chatHistory.String())
+ // textView.SetText("<🤖>: Hello! What can I do for you?")
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if botRespMode {
// do nothing while bot typing
diff --git a/models/models.go b/models/models.go
index dd1dace..30ba548 100644
--- a/models/models.go
+++ b/models/models.go
@@ -1,5 +1,10 @@
package models
+import (
+ "fmt"
+ "strings"
+)
+
// type FuncCall struct {
// XMLName xml.Name `xml:"tool_call"`
// Name string `xml:"name"`
@@ -56,6 +61,22 @@ type MessagesStory struct {
Content string `json:"content"`
}
+func (m MessagesStory) ToText() string {
+ icon := ""
+ switch m.Role {
+ case "assistant":
+ icon = "<🤖>: "
+ case "user":
+ icon = "<user>: "
+ case "system":
+ icon = "<system>: "
+ case "tool":
+ icon = "<tool>: "
+ }
+ textMsg := fmt.Sprintf("%s%s\n", icon, m.Content)
+ return strings.ReplaceAll(textMsg, "\n\n", "\n")
+}
+
type ChatBody struct {
Model string `json:"model"`
Stream bool `json:"stream"`