Feat: read from chat files

This commit is contained in:
Grail Finder
2024-11-16 06:41:52 +03:00
parent 3cbad31a16
commit 84b06c0da5
5 changed files with 147 additions and 29 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
*.json
testlog
elefant
history/

View File

@@ -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);

117
bot.go
View File

@@ -28,9 +28,15 @@ var (
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.
@@ -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},
},
Messages: lastChat,
}
// fmt.Printf("<🤖>: Hello! How can I help?")
// for {
// chatLoop()
// }
}

10
main.go
View File

@@ -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

View File

@@ -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"`