Feat: add character card support
This commit is contained in:
@@ -18,12 +18,16 @@
|
|||||||
- sqlite for the bot memory; +
|
- sqlite for the bot memory; +
|
||||||
- rename current chat; +
|
- rename current chat; +
|
||||||
- help page with all key bindings; +
|
- help page with all key bindings; +
|
||||||
- change temp, min-p and other params from tui;
|
|
||||||
- default config file (api url, path to sysprompts, path to log, limits, etc); +
|
- default config file (api url, path to sysprompts, path to log, limits, etc); +
|
||||||
|
- change temp, min-p and other params from tui;
|
||||||
- fullscreen textarea option (bothersome to implement);
|
- fullscreen textarea option (bothersome to implement);
|
||||||
- consider adding use /completion of llamacpp, since openai endpoint clearly has template|format issues;
|
- consider adding use /completion of llamacpp, since openai endpoint clearly has template|format issues;
|
||||||
- export whole chat into a json file;
|
- export whole chat into a json file;
|
||||||
- directoty with sys prompts;
|
- directoty with sys prompts (charcards png & json);
|
||||||
|
- separate messages that are stored and chat and send to the bot, i.e. option to omit tool calls (there might be a point where they are no longer needed in ctx);
|
||||||
|
- colourschemes, colours or markdown of quotes and styles;
|
||||||
|
- RAG support|implementation;
|
||||||
|
- change card-chat pair with one binding;
|
||||||
|
|
||||||
### FIX:
|
### FIX:
|
||||||
- bot responding (or haninging) blocks everything; +
|
- bot responding (or haninging) blocks everything; +
|
||||||
@@ -38,3 +42,4 @@
|
|||||||
- lets say we have two (or more) agents with the same name across multiple chats. These agents go and ask db for topics they memoriesed. Now they can access topics that aren't meant for them. (so memory should have an option: shareble; that indicates if that memory can be shared across chats);
|
- lets say we have two (or more) agents with the same name across multiple chats. These agents go and ask db for topics they memoriesed. Now they can access topics that aren't meant for them. (so memory should have an option: shareble; that indicates if that memory can be shared across chats);
|
||||||
- if option to show sys msg enabled: it show display new tool responses;
|
- if option to show sys msg enabled: it show display new tool responses;
|
||||||
- when bot generation ended with err: need a way to switch back to the bot_resp_false mode;
|
- when bot generation ended with err: need a way to switch back to the bot_resp_false mode;
|
||||||
|
- no selection focus on modal sys buttons after opening it a second time;
|
||||||
|
|||||||
31
bot.go
31
bot.go
@@ -28,7 +28,7 @@ var (
|
|||||||
chatBody *models.ChatBody
|
chatBody *models.ChatBody
|
||||||
store storage.FullRepo
|
store storage.FullRepo
|
||||||
defaultFirstMsg = "Hello! What can I do for you?"
|
defaultFirstMsg = "Hello! What can I do for you?"
|
||||||
defaultStarter = []models.MessagesStory{}
|
defaultStarter = []models.RoleMsg{}
|
||||||
defaultStarterBytes = []byte{}
|
defaultStarterBytes = []byte{}
|
||||||
interruptResp = false
|
interruptResp = false
|
||||||
)
|
)
|
||||||
@@ -37,7 +37,7 @@ var (
|
|||||||
|
|
||||||
func formMsg(chatBody *models.ChatBody, newMsg, role string) io.Reader {
|
func formMsg(chatBody *models.ChatBody, newMsg, role string) io.Reader {
|
||||||
if newMsg != "" { // otherwise let the bot continue
|
if newMsg != "" { // otherwise let the bot continue
|
||||||
newMsg := models.MessagesStory{Role: role, Content: newMsg}
|
newMsg := models.RoleMsg{Role: role, Content: newMsg}
|
||||||
chatBody.Messages = append(chatBody.Messages, newMsg)
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(chatBody)
|
data, err := json.Marshal(chatBody)
|
||||||
@@ -128,7 +128,7 @@ out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
botRespMode = false
|
botRespMode = false
|
||||||
chatBody.Messages = append(chatBody.Messages, models.MessagesStory{
|
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{
|
||||||
Role: cfg.AssistantRole, Content: respText.String(),
|
Role: cfg.AssistantRole, Content: respText.String(),
|
||||||
})
|
})
|
||||||
// bot msg is done;
|
// bot msg is done;
|
||||||
@@ -182,8 +182,17 @@ func chatToText(showSys bool) string {
|
|||||||
return strings.Join(s, "")
|
return strings.Join(s, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// func textToMsg(rawMsg string) models.MessagesStory {
|
func applyCharCard(cc *models.CharCard) {
|
||||||
// msg := models.MessagesStory{}
|
cfg.AssistantRole = cc.Role
|
||||||
|
newChat := []models.RoleMsg{
|
||||||
|
{Role: "system", Content: cc.SysPrompt},
|
||||||
|
{Role: cfg.AssistantRole, Content: cc.FirstMsg},
|
||||||
|
}
|
||||||
|
chatBody.Messages = newChat
|
||||||
|
}
|
||||||
|
|
||||||
|
// func textToMsg(rawMsg string) models.RoleMsg {
|
||||||
|
// msg := models.RoleMsg{}
|
||||||
// // system and tool?
|
// // system and tool?
|
||||||
// if strings.HasPrefix(rawMsg, cfg.AssistantIcon) {
|
// if strings.HasPrefix(rawMsg, cfg.AssistantIcon) {
|
||||||
// msg.Role = cfg.AssistantRole
|
// msg.Role = cfg.AssistantRole
|
||||||
@@ -198,8 +207,8 @@ func chatToText(showSys bool) string {
|
|||||||
// return msg
|
// return msg
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// func textSliceToChat(chat []string) []models.MessagesStory {
|
// func textSliceToChat(chat []string) []models.RoleMsg {
|
||||||
// resp := make([]models.MessagesStory, len(chat))
|
// resp := make([]models.RoleMsg, len(chat))
|
||||||
// for i, rawMsg := range chat {
|
// for i, rawMsg := range chat {
|
||||||
// msg := textToMsg(rawMsg)
|
// msg := textToMsg(rawMsg)
|
||||||
// resp[i] = msg
|
// resp[i] = msg
|
||||||
@@ -209,8 +218,8 @@ func chatToText(showSys bool) string {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cfg = config.LoadConfigOrDefault("config.example.toml")
|
cfg = config.LoadConfigOrDefault("config.example.toml")
|
||||||
defaultStarter = []models.MessagesStory{
|
defaultStarter = []models.RoleMsg{
|
||||||
{Role: "system", Content: systemMsg},
|
{Role: "system", Content: basicSysMsg},
|
||||||
{Role: cfg.AssistantRole, Content: defaultFirstMsg},
|
{Role: cfg.AssistantRole, Content: defaultFirstMsg},
|
||||||
}
|
}
|
||||||
file, err := os.OpenFile(cfg.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
file, err := os.OpenFile(cfg.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
@@ -223,6 +232,10 @@ func init() {
|
|||||||
logger.Error("failed to marshal defaultStarter", "error", err)
|
logger.Error("failed to marshal defaultStarter", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// load cards
|
||||||
|
basicCard.Role = cfg.AssistantRole
|
||||||
|
toolCard.Role = cfg.AssistantRole
|
||||||
|
//
|
||||||
logger = slog.New(slog.NewTextHandler(file, nil))
|
logger = slog.New(slog.NewTextHandler(file, nil))
|
||||||
store = storage.NewProviderSQL("test.db", logger)
|
store = storage.NewProviderSQL("test.db", logger)
|
||||||
// https://github.com/coreydaley/ggerganov-llama.cpp/blob/master/examples/server/README.md
|
// https://github.com/coreydaley/ggerganov-llama.cpp/blob/master/examples/server/README.md
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ AssistantRole = "assistant"
|
|||||||
AssistantIcon = "<🤖>: "
|
AssistantIcon = "<🤖>: "
|
||||||
UserIcon = "<user>: "
|
UserIcon = "<user>: "
|
||||||
ToolIcon = "<>>: "
|
ToolIcon = "<>>: "
|
||||||
|
SysDir = "sysprompts"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type Config struct {
|
|||||||
UserIcon string `toml:"UserIcon"`
|
UserIcon string `toml:"UserIcon"`
|
||||||
ToolIcon string `toml:"ToolIcon"`
|
ToolIcon string `toml:"ToolIcon"`
|
||||||
ChunkLimit uint32 `toml:"ChunkLimit"`
|
ChunkLimit uint32 `toml:"ChunkLimit"`
|
||||||
|
SysDir string `toml:"SysDir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfigOrDefault(fn string) *Config {
|
func LoadConfigOrDefault(fn string) *Config {
|
||||||
@@ -34,6 +35,7 @@ func LoadConfigOrDefault(fn string) *Config {
|
|||||||
config.ToolRole = "tool"
|
config.ToolRole = "tool"
|
||||||
config.AssistantRole = "assistant"
|
config.AssistantRole = "assistant"
|
||||||
config.ChunkLimit = 8192
|
config.ChunkLimit = 8192
|
||||||
|
config.SysDir = "sysprompts"
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|||||||
41
models/card.go
Normal file
41
models/card.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// https://github.com/malfoyslastname/character-card-spec-v2/blob/main/spec_v2.md
|
||||||
|
// what a bloat; trim to Role->Msg pair and first msg
|
||||||
|
type CharCardSpec struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Personality string `json:"personality"`
|
||||||
|
FirstMes string `json:"first_mes"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Chat string `json:"chat"`
|
||||||
|
MesExample string `json:"mes_example"`
|
||||||
|
Scenario string `json:"scenario"`
|
||||||
|
CreateDate string `json:"create_date"`
|
||||||
|
Talkativeness string `json:"talkativeness"`
|
||||||
|
Fav bool `json:"fav"`
|
||||||
|
Creatorcomment string `json:"creatorcomment"`
|
||||||
|
Spec string `json:"spec"`
|
||||||
|
SpecVersion string `json:"spec_version"`
|
||||||
|
Tags []any `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CharCardSpec) Simplify(userName, fpath string) *CharCard {
|
||||||
|
fm := strings.ReplaceAll(strings.ReplaceAll(c.FirstMes, "{{char}}", c.Name), "{{user}}", userName)
|
||||||
|
sysPr := strings.ReplaceAll(strings.ReplaceAll(c.Description, "{{char}}", c.Name), "{{user}}", userName)
|
||||||
|
return &CharCard{
|
||||||
|
SysPrompt: sysPr,
|
||||||
|
FirstMsg: fm,
|
||||||
|
Role: c.Name,
|
||||||
|
FilePath: fpath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CharCard struct {
|
||||||
|
SysPrompt string
|
||||||
|
FirstMsg string
|
||||||
|
Role string
|
||||||
|
FilePath string
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ import (
|
|||||||
type Chat struct {
|
type Chat struct {
|
||||||
ID uint32 `db:"id" json:"id"`
|
ID uint32 `db:"id" json:"id"`
|
||||||
Name string `db:"name" json:"name"`
|
Name string `db:"name" json:"name"`
|
||||||
Msgs string `db:"msgs" json:"msgs"` // []MessagesStory to string json
|
Msgs string `db:"msgs" json:"msgs"` // []RoleMsg to string json
|
||||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chat) ToHistory() ([]MessagesStory, error) {
|
func (c Chat) ToHistory() ([]RoleMsg, error) {
|
||||||
resp := []MessagesStory{}
|
resp := []RoleMsg{}
|
||||||
if err := json.Unmarshal([]byte(c.Msgs), &resp); err != nil {
|
if err := json.Unmarshal([]byte(c.Msgs), &resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// type FuncCall struct {
|
|
||||||
// XMLName xml.Name `xml:"tool_call"`
|
|
||||||
// Name string `xml:"name"`
|
|
||||||
// Args []string `xml:"args"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
type FuncCall struct {
|
type FuncCall struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Args []string `json:"args"`
|
Args []string `json:"args"`
|
||||||
@@ -56,12 +50,12 @@ type LLMRespChunk struct {
|
|||||||
} `json:"usage"`
|
} `json:"usage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessagesStory struct {
|
type RoleMsg struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MessagesStory) ToText(i int) string {
|
func (m RoleMsg) ToText(i int) string {
|
||||||
icon := ""
|
icon := ""
|
||||||
switch m.Role {
|
switch m.Role {
|
||||||
case "assistant":
|
case "assistant":
|
||||||
@@ -72,6 +66,8 @@ func (m MessagesStory) ToText(i int) string {
|
|||||||
icon = fmt.Sprintf("(%d) <system>: ", i)
|
icon = fmt.Sprintf("(%d) <system>: ", i)
|
||||||
case "tool":
|
case "tool":
|
||||||
icon = fmt.Sprintf("(%d) <tool>: ", i)
|
icon = fmt.Sprintf("(%d) <tool>: ", i)
|
||||||
|
default:
|
||||||
|
icon = fmt.Sprintf("(%d) <%s>: ", i, m.Role)
|
||||||
}
|
}
|
||||||
textMsg := fmt.Sprintf("%s%s\n", icon, m.Content)
|
textMsg := fmt.Sprintf("%s%s\n", icon, m.Content)
|
||||||
return strings.ReplaceAll(textMsg, "\n\n", "\n")
|
return strings.ReplaceAll(textMsg, "\n\n", "\n")
|
||||||
@@ -80,12 +76,12 @@ func (m MessagesStory) ToText(i int) string {
|
|||||||
type ChatBody struct {
|
type ChatBody struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Stream bool `json:"stream"`
|
Stream bool `json:"stream"`
|
||||||
Messages []MessagesStory `json:"messages"`
|
Messages []RoleMsg `json:"messages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatToolsBody struct {
|
type ChatToolsBody struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Messages []MessagesStory `json:"messages"`
|
Messages []RoleMsg `json:"messages"`
|
||||||
Tools []struct {
|
Tools []struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Function struct {
|
Function struct {
|
||||||
|
|||||||
107
pngmeta/metareader.go
Normal file
107
pngmeta/metareader.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package pngmeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"elefant/models"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
embType = "tEXt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PngEmbed struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c PngEmbed) GetDecodedValue() (*models.CharCardSpec, error) {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(c.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
card := &models.CharCardSpec{}
|
||||||
|
if err := json.Unmarshal(data, &card); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return card, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractChar(fname string) (*PngEmbed, error) {
|
||||||
|
data, err := os.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
pr, err := NewPNGStepReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
step, err := pr.Next()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if step.Type() != embType {
|
||||||
|
if _, err := io.Copy(io.Discard, step); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf, err := io.ReadAll(step)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dataInstep := string(buf)
|
||||||
|
values := strings.Split(dataInstep, "\x00")
|
||||||
|
if len(values) == 2 {
|
||||||
|
return &PngEmbed{Key: values[0], Value: values[1]}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := step.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to find embedded char in png: " + fname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadCard(fname, uname string) (*models.CharCard, error) {
|
||||||
|
pe, err := extractChar(fname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
charSpec, err := pe.GetDecodedValue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return charSpec.Simplify(uname, fname), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDirCards(dirname, uname string) ([]*models.CharCard, error) {
|
||||||
|
files, err := os.ReadDir(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := []*models.CharCard{}
|
||||||
|
for _, f := range files {
|
||||||
|
if !strings.HasSuffix(f.Name(), ".png") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fpath := path.Join(dirname, f.Name())
|
||||||
|
cc, err := ReadCard(fpath, uname)
|
||||||
|
if err != nil {
|
||||||
|
// log err
|
||||||
|
return nil, err
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
resp = append(resp, cc)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
33
pngmeta/metareader_test.go
Normal file
33
pngmeta/metareader_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package pngmeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadMeta(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Filename string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Filename: "../sysprompts/default_Seraphina.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Filename: "../sysprompts/llama.png",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tc := range cases {
|
||||||
|
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
|
||||||
|
// Call the readMeta function
|
||||||
|
pembed, err := extractChar(tc.Filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, but got %v", err)
|
||||||
|
}
|
||||||
|
v, err := pembed.GetDecodedValue()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, but got %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v\n", v.Simplify("Adam"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
77
pngmeta/partsreader.go
Normal file
77
pngmeta/partsreader.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package pngmeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCRC32Mismatch = errors.New("crc32 mismatch")
|
||||||
|
ErrNotPNG = errors.New("not png")
|
||||||
|
ErrBadLength = errors.New("bad length")
|
||||||
|
)
|
||||||
|
|
||||||
|
const header = "\x89PNG\r\n\x1a\n"
|
||||||
|
|
||||||
|
type PngChunk struct {
|
||||||
|
typ string
|
||||||
|
length int32
|
||||||
|
r io.Reader
|
||||||
|
realR io.Reader
|
||||||
|
checksummer hash.Hash32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PngChunk) Read(p []byte) (int, error) {
|
||||||
|
return io.TeeReader(c.r, c.checksummer).Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PngChunk) Close() error {
|
||||||
|
var crc32 uint32
|
||||||
|
if err := binary.Read(c.realR, binary.BigEndian, &crc32); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if crc32 != c.checksummer.Sum32() {
|
||||||
|
return ErrCRC32Mismatch
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PngChunk) Type() string {
|
||||||
|
return c.typ
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPNGStepReader(r io.Reader) (*Reader, error) {
|
||||||
|
expectedHeader := make([]byte, len(header))
|
||||||
|
if _, err := io.ReadFull(r, expectedHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if string(expectedHeader) != header {
|
||||||
|
return nil, ErrNotPNG
|
||||||
|
}
|
||||||
|
return &Reader{r}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Next() (*PngChunk, error) {
|
||||||
|
var length int32
|
||||||
|
if err := binary.Read(r.r, binary.BigEndian, &length); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if length < 0 {
|
||||||
|
return nil, ErrBadLength
|
||||||
|
}
|
||||||
|
var rawTyp [4]byte
|
||||||
|
if _, err := io.ReadFull(r.r, rawTyp[:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
typ := string(rawTyp[:])
|
||||||
|
checksummer := crc32.NewIEEE()
|
||||||
|
checksummer.Write([]byte(typ))
|
||||||
|
return &PngChunk{typ, length, io.LimitReader(r.r, int64(length)), r.r, checksummer}, nil
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ var (
|
|||||||
chatMap = make(map[string]*models.Chat)
|
chatMap = make(map[string]*models.Chat)
|
||||||
)
|
)
|
||||||
|
|
||||||
func historyToSJSON(msgs []models.MessagesStory) (string, error) {
|
func historyToSJSON(msgs []models.RoleMsg) (string, error) {
|
||||||
data, err := json.Marshal(msgs)
|
data, err := json.Marshal(msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -25,7 +25,7 @@ func historyToSJSON(msgs []models.MessagesStory) (string, error) {
|
|||||||
return string(data), nil
|
return string(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStorageChat(name string, msgs []models.MessagesStory) error {
|
func updateStorageChat(name string, msgs []models.RoleMsg) error {
|
||||||
var err error
|
var err error
|
||||||
chat, ok := chatMap[name]
|
chat, ok := chatMap[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -59,7 +59,7 @@ func loadHistoryChats() ([]string, error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadHistoryChat(chatName string) ([]models.MessagesStory, error) {
|
func loadHistoryChat(chatName string) ([]models.RoleMsg, error) {
|
||||||
chat, ok := chatMap[chatName]
|
chat, ok := chatMap[chatName]
|
||||||
if !ok {
|
if !ok {
|
||||||
err := errors.New("failed to read chat")
|
err := errors.New("failed to read chat")
|
||||||
@@ -70,7 +70,7 @@ func loadHistoryChat(chatName string) ([]models.MessagesStory, error) {
|
|||||||
return chat.ToHistory()
|
return chat.ToHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadOldChatOrGetNew() []models.MessagesStory {
|
func loadOldChatOrGetNew() []models.RoleMsg {
|
||||||
newChat := &models.Chat{
|
newChat := &models.Chat{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
|||||||
BIN
sysprompts/default_Seraphina.png
Normal file
BIN
sysprompts/default_Seraphina.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 539 KiB |
BIN
sysprompts/llama.png
Normal file
BIN
sysprompts/llama.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 606 KiB |
18
tools.go
18
tools.go
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// TODO: form that message based on existing funcs
|
// TODO: form that message based on existing funcs
|
||||||
basicSysMsg = `Large Language Model that helps user with any of his requests.`
|
|
||||||
toolCallRE = regexp.MustCompile(`__tool_call__\s*([\s\S]*?)__tool_call__`)
|
toolCallRE = regexp.MustCompile(`__tool_call__\s*([\s\S]*?)__tool_call__`)
|
||||||
|
basicSysMsg = `Large Language Model that helps user with any of his requests.`
|
||||||
toolSysMsg = `You're a helpful assistant.
|
toolSysMsg = `You're a helpful assistant.
|
||||||
# Tools
|
# Tools
|
||||||
You can do functions call if needed.
|
You can do functions call if needed.
|
||||||
@@ -47,8 +47,20 @@ Tool call is addressed to the tool agent, avoid sending more info than tool call
|
|||||||
When done right, tool call will be delivered to the tool agent. tool agent will respond with the results of the call.
|
When done right, tool call will be delivered to the tool agent. tool agent will respond with the results of the call.
|
||||||
After that you are free to respond to the user.
|
After that you are free to respond to the user.
|
||||||
`
|
`
|
||||||
systemMsg = toolSysMsg
|
basicCard = &models.CharCard{
|
||||||
sysMap = map[string]string{"basic_sys": basicSysMsg, "tool_sys": toolSysMsg}
|
SysPrompt: basicSysMsg,
|
||||||
|
FirstMsg: defaultFirstMsg,
|
||||||
|
Role: "",
|
||||||
|
FilePath: "",
|
||||||
|
}
|
||||||
|
toolCard = &models.CharCard{
|
||||||
|
SysPrompt: toolSysMsg,
|
||||||
|
FirstMsg: defaultFirstMsg,
|
||||||
|
Role: "",
|
||||||
|
FilePath: "",
|
||||||
|
}
|
||||||
|
// sysMap = map[string]string{"basic_sys": basicSysMsg, "tool_sys": toolSysMsg}
|
||||||
|
sysMap = map[string]*models.CharCard{"basic_sys": basicCard, "tool_sys": toolCard}
|
||||||
sysLabels = []string{"cancel", "basic_sys", "tool_sys"}
|
sysLabels = []string{"cancel", "basic_sys", "tool_sys"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
28
tui.go
28
tui.go
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"elefant/models"
|
"elefant/models"
|
||||||
|
"elefant/pngmeta"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -119,22 +120,27 @@ func init() {
|
|||||||
})
|
})
|
||||||
sysModal = tview.NewModal().
|
sysModal = tview.NewModal().
|
||||||
SetText("Switch sys msg:").
|
SetText("Switch sys msg:").
|
||||||
AddButtons(sysLabels).
|
|
||||||
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||||
switch buttonLabel {
|
switch buttonLabel {
|
||||||
case "cancel":
|
case "cancel":
|
||||||
pages.RemovePage("sys")
|
pages.RemovePage("sys")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
sysMsg, ok := sysMap[buttonLabel]
|
cc, ok := sysMap[buttonLabel]
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Warn("no such sys msg", "name", buttonLabel)
|
logger.Warn("no such sys msg", "name", buttonLabel)
|
||||||
pages.RemovePage("sys")
|
pages.RemovePage("sys")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chatBody.Messages[0].Content = sysMsg
|
// to replace it old role in text
|
||||||
|
// oldRole := chatBody.Messages[0].Role
|
||||||
|
// replace every role with char
|
||||||
|
// chatBody.Messages[0].Content = cc.SysPrompt
|
||||||
|
// chatBody.Messages[1].Content = cc.FirstMsg
|
||||||
|
applyCharCard(cc)
|
||||||
// replace textview
|
// replace textview
|
||||||
textView.SetText(chatToText(cfg.ShowSys))
|
textView.SetText(chatToText(cfg.ShowSys))
|
||||||
|
sysModal.ClearButtons()
|
||||||
pages.RemovePage("sys")
|
pages.RemovePage("sys")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -312,6 +318,22 @@ func init() {
|
|||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlS {
|
if event.Key() == tcell.KeyCtrlS {
|
||||||
// switch sys prompt
|
// switch sys prompt
|
||||||
|
cards, err := pngmeta.ReadDirCards(cfg.SysDir, cfg.UserRole)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to read sys dir", "error", err)
|
||||||
|
if err := notifyUser("error", "failed to read: "+cfg.SysDir); err != nil {
|
||||||
|
logger.Debug("failed to notify user", "error", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
labels := []string{}
|
||||||
|
labels = append(labels, sysLabels...)
|
||||||
|
for _, cc := range cards {
|
||||||
|
labels = append(labels, cc.Role)
|
||||||
|
sysMap[cc.Role] = cc
|
||||||
|
}
|
||||||
|
sysModal.AddButtons(labels)
|
||||||
|
// load all chars
|
||||||
pages.AddPage("sys", sysModal, true, true)
|
pages.AddPage("sys", sysModal, true, true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user