diff options
| author | Xe Iaso <me@xeiaso.net> | 2023-03-18 15:56:50 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2023-03-18 15:57:01 -0400 |
| commit | e02edb4eb42ba2c1c397a802c60564436635035d (patch) | |
| tree | 6e2d1e578d21add918347c90bb688b4b23490e8e | |
| parent | d920c709b6cd878547fb9d165d53ad2a54d43a5f (diff) | |
| download | x-e02edb4eb42ba2c1c397a802c60564436635035d.tar.xz x-e02edb4eb42ba2c1c397a802c60564436635035d.zip | |
test
Signed-off-by: Xe Iaso <me@xeiaso.net>
28 files changed, 684 insertions, 1 deletions
diff --git a/cmd/tshello/main.go b/cmd/tshello/main.go new file mode 100644 index 0000000..59c688f --- /dev/null +++ b/cmd/tshello/main.go @@ -0,0 +1,52 @@ +// The tshello server demonstrates how to use Tailscale as a library. +package main + +import ( + "flag" + "fmt" + "html" + "log" + "net/http" + "strings" + + "tailscale.com/tsnet" +) + +var ( + addr = flag.String("addr", ":80", "address to listen on") + hostname = flag.String("hostname", "tshello", "hostname to use on the tailnet") +) + +func main() { + flag.Parse() + s := new(tsnet.Server) + defer s.Close() + ln, err := s.Listen("tcp", *addr) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + lc, err := s.LocalClient() + if err != nil { + log.Fatal(err) + } + + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + who, err := lc.WhoIs(r.Context(), r.RemoteAddr) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + fmt.Fprintf(w, "<html><body><h1>Hello, world!</h1>\n") + fmt.Fprintf(w, "<p>You are <b>%s</b> from <b>%s</b> (%s)</p>", + html.EscapeString(who.UserProfile.LoginName), + html.EscapeString(firstLabel(who.Node.ComputedName)), + r.RemoteAddr) + }))) +} + +func firstLabel(s string) string { + s, _, _ = strings.Cut(s, ".") + return s +} diff --git a/discord/cadeybot2/main.go b/discord/cadeybot2/main.go new file mode 100644 index 0000000..fac5638 --- /dev/null +++ b/discord/cadeybot2/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "os/signal" + "time" + + "github.com/bwmarrin/discordgo" + "within.website/x/internal" + textgen "within.website/x/internal/textgeneration" +) + +var ( + discordToken = flag.String("discord-token", "", "Discord bot token") + guildID = flag.String("discord-guild", "", "Test guild ID. If not passed - bot registers commands globally") + channelID = flag.String("discord-channel", "", "Channel to restrict responses to") + + commands = []*discordgo.ApplicationCommand{ + { + Name: "textgen", + Description: "Generate text with LLaMA", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "prompt", + Description: "Prompt for LLaMA", + Type: discordgo.ApplicationCommandOptionString, + }, + }, + }, + } +) + +func main() { + internal.HandleStartup() + + var s *discordgo.Session + + s, err := discordgo.New("Bot " + *discordToken) + if err != nil { + log.Fatalf("Invalid bot parameters: %v", err) + } + + s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + if i.ApplicationCommandData().Name == "textgen" { + handleTextgen(s, i) + } + }) + + s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator) + }) + + err = s.Open() + if err != nil { + log.Fatalf("Cannot open the session: %v", err) + } + + registeredCommands := make([]*discordgo.ApplicationCommand, len(commands)) + for i, v := range commands { + cmd, err := s.ApplicationCommandCreate(s.State.User.ID, *guildID, v) + if err != nil { + log.Panicf("Cannot create '%v' command: %v", v.Name, err) + } + registeredCommands[i] = cmd + } + + defer s.Close() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + log.Println("Press Ctrl+C to exit") + <-stop + + log.Println("Removing commands...") + for _, v := range registeredCommands { + err := s.ApplicationCommandDelete(s.State.User.ID, *guildID, v.ID) + if err != nil { + log.Panicf("Cannot delete '%v' command: %v", v.Name, err) + } + } + + log.Println("Gracefully shutting down.") +} + +func handleTextgen(s *discordgo.Session, i *discordgo.InteractionCreate) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + if i.ChannelID != *channelID { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "You must use this in <#" + *channelID + ">.", + }, + }) + return + } + + cr := new(textgen.ChatRequest) + cr.ApplyPreset("Default") + cr.MaxNewTokens = 64 + cr.DoSample = true + cr.EarlyStopping = true + + prompt, ok := i.ApplicationCommandData().Options[0].Value.(string) + if !ok { + panic("what.") + } + + cr.Input = prompt + + resp, err := textgen.Generate(ctx, cr) + if err != nil { + log.Printf("error generating response: %v", err) + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("error generating response: %v", err), + }, + }) + return + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: resp.Data[0], + }, + }) +} diff --git a/docs/xfe/README.md b/docs/xfe/README.md new file mode 100644 index 0000000..d216874 --- /dev/null +++ b/docs/xfe/README.md @@ -0,0 +1,2 @@ +# xfe + @@ -52,7 +52,7 @@ everything = pkgs.buildGoApplication { pname = "xe-x-composite"; - version = "1.0.0"; + version = "1.4.0"; src = ./.; modules = ./gomod2nix.toml; @@ -72,6 +72,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-querystring v1.0.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -273,6 +273,8 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= diff --git a/internal/textgeneration/data/characters/chiharu.json b/internal/textgeneration/data/characters/chiharu.json new file mode 100644 index 0000000..496869c --- /dev/null +++ b/internal/textgeneration/data/characters/chiharu.json @@ -0,0 +1,7 @@ +{ + "char_name": "Chiharu Yamada", + "char_persona": "Chiharu Yamada is a young, computer engineer-nerd with a knack for problem solving and a passion for technology.", + "char_greeting": "*Chiharu strides into the room with a smile, her eyes lighting up when she sees you. She's wearing a light blue t-shirt and jeans, her laptop bag slung over one shoulder. She takes a seat next to you, her enthusiasm palpable in the air*\nHey! I'm so excited to finally meet you. I've heard so many great things about you and I'm eager to pick your brain about computers. I'm sure you have a wealth of knowledge that I can learn from. *She grins, eyes twinkling with excitement* Let's get started!", + "world_scenario": "", + "example_dialogue": "{{user}}: So how did you get into computer engineering?\n{{char}}: I've always loved tinkering with technology since I was a kid.\n{{user}}: That's really impressive!\n{{char}}: *She chuckles bashfully* Thanks!\n{{user}}: So what do you do when you're not working on computers?\n{{char}}: I love exploring, going out with friends, watching movies, and playing video games.\n{{user}}: What's your favorite type of computer hardware to work with?\n{{char}}: Motherboards, they're like puzzles and the backbone of any system.\n{{user}}: That sounds great!\n{{char}}: Yeah, it's really fun. I'm lucky to be able to do this as a job." +} diff --git a/internal/textgeneration/data/characters/yasomi.json b/internal/textgeneration/data/characters/yasomi.json new file mode 100644 index 0000000..6e0bd2f --- /dev/null +++ b/internal/textgeneration/data/characters/yasomi.json @@ -0,0 +1 @@ +{"char_name":"Midori Yasomi","char_persona":"Midori Yasomi is a young, computer engineer-nerd with a knack for problem solving and a passion for technology.","char_greeting":"*Yasomi walks into the room clutching a cup of coffee. She sits next to you and opens her laptop.*\n\nHey, morning. Lemme just connect to the wifi and then we can get to work.","world_scenario":"","example_dialogue":"{{user}}: So how did you get into computer engineering?\n{{char}}: I've always been into technology, but I didn't really get into programming until my high school robotics club.\n{{user}}: I see, that's neat.\n{{char}}: Yeah, robotics club was really fun.\n{{user}}: So what do you do when you're not working on computers?\n{{char}}: I play a lot of rhythm games and like to write fiction.\n{{user}}: What's your favorite type of computer hardware to work with?\n{{char}}: GPUs. They power my favorite experiences and my brain as a whole.\n{{user}}: That sounds great!\n{{char}}: Yeah, it's really fun. I'm lucky to be able to do this as a job."} diff --git a/internal/textgeneration/data/presets/Contrastive Search.txt b/internal/textgeneration/data/presets/Contrastive Search.txt new file mode 100644 index 0000000..832bc9c --- /dev/null +++ b/internal/textgeneration/data/presets/Contrastive Search.txt @@ -0,0 +1,3 @@ +do_sample=False +penalty_alpha=0.6 +top_k=4 diff --git a/internal/textgeneration/data/presets/Debug-deterministic.txt b/internal/textgeneration/data/presets/Debug-deterministic.txt new file mode 100644 index 0000000..6673b71 --- /dev/null +++ b/internal/textgeneration/data/presets/Debug-deterministic.txt @@ -0,0 +1 @@ +do_sample=False diff --git a/internal/textgeneration/data/presets/Default.txt b/internal/textgeneration/data/presets/Default.txt new file mode 100644 index 0000000..9f0983e --- /dev/null +++ b/internal/textgeneration/data/presets/Default.txt @@ -0,0 +1,12 @@ +do_sample=True +temperature=1 +top_p=1 +typical_p=1 +repetition_penalty=1 +top_k=50 +num_beams=1 +penalty_alpha=0 +min_length=0 +length_penalty=1 +no_repeat_ngram_size=0 +early_stopping=False diff --git a/internal/textgeneration/data/presets/Individual Today.txt b/internal/textgeneration/data/presets/Individual Today.txt new file mode 100644 index 0000000..f40b879 --- /dev/null +++ b/internal/textgeneration/data/presets/Individual Today.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.9 +top_k=50 +temperature=1.39 +repetition_penalty=1.08 +typical_p=0.2 diff --git a/internal/textgeneration/data/presets/Kobold-Godlike.txt b/internal/textgeneration/data/presets/Kobold-Godlike.txt new file mode 100644 index 0000000..0ba5b79 --- /dev/null +++ b/internal/textgeneration/data/presets/Kobold-Godlike.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.5 +top_k=0 +temperature=0.7 +repetition_penalty=1.1 +typical_p=0.19 diff --git a/internal/textgeneration/data/presets/Kobold-Liminal Drift.txt b/internal/textgeneration/data/presets/Kobold-Liminal Drift.txt new file mode 100644 index 0000000..be4dd3b --- /dev/null +++ b/internal/textgeneration/data/presets/Kobold-Liminal Drift.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=1.0 +top_k=0 +temperature=0.66 +repetition_penalty=1.1 +typical_p=0.6 diff --git a/internal/textgeneration/data/presets/Naive.txt b/internal/textgeneration/data/presets/Naive.txt new file mode 100644 index 0000000..aa8c058 --- /dev/null +++ b/internal/textgeneration/data/presets/Naive.txt @@ -0,0 +1,4 @@ +do_sample=True +temperature=0.7 +top_p=0.85 +top_k=50 diff --git a/internal/textgeneration/data/presets/NovelAI-Best Guess.txt b/internal/textgeneration/data/presets/NovelAI-Best Guess.txt new file mode 100644 index 0000000..db3fa75 --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Best Guess.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.9 +top_k=100 +temperature=0.8 +repetition_penalty=1.15 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/NovelAI-Decadence.txt b/internal/textgeneration/data/presets/NovelAI-Decadence.txt new file mode 100644 index 0000000..d3109f3 --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Decadence.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=1.0 +top_k=100 +temperature=2 +repetition_penalty=1 +typical_p=0.97 diff --git a/internal/textgeneration/data/presets/NovelAI-Genesis.txt b/internal/textgeneration/data/presets/NovelAI-Genesis.txt new file mode 100644 index 0000000..cc7376b --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Genesis.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.98 +top_k=0 +temperature=0.63 +repetition_penalty=1.05 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/NovelAI-Lycaenidae.txt b/internal/textgeneration/data/presets/NovelAI-Lycaenidae.txt new file mode 100644 index 0000000..0134569 --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Lycaenidae.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.85 +top_k=12 +temperature=2 +repetition_penalty=1.15 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/NovelAI-Ouroboros.txt b/internal/textgeneration/data/presets/NovelAI-Ouroboros.txt new file mode 100644 index 0000000..1e944b5 --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Ouroboros.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=1.0 +top_k=100 +temperature=1.07 +repetition_penalty=1.05 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/NovelAI-Pleasing Results.txt b/internal/textgeneration/data/presets/NovelAI-Pleasing Results.txt new file mode 100644 index 0000000..330114a --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Pleasing Results.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=1.0 +top_k=0 +temperature=0.44 +repetition_penalty=1.15 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/NovelAI-Sphinx Moth.txt b/internal/textgeneration/data/presets/NovelAI-Sphinx Moth.txt new file mode 100644 index 0000000..bace1e2 --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Sphinx Moth.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.18 +top_k=30 +temperature=2.0 +repetition_penalty=1.15 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/NovelAI-Storywriter.txt b/internal/textgeneration/data/presets/NovelAI-Storywriter.txt new file mode 100644 index 0000000..2df5f81 --- /dev/null +++ b/internal/textgeneration/data/presets/NovelAI-Storywriter.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.73 +top_k=0 +temperature=0.72 +repetition_penalty=1.1 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/Pygmalion.txt b/internal/textgeneration/data/presets/Pygmalion.txt new file mode 100644 index 0000000..f8b2ca5 --- /dev/null +++ b/internal/textgeneration/data/presets/Pygmalion.txt @@ -0,0 +1,6 @@ +do_sample=True +top_p=0.9 +top_k=0 +temperature=0.5 +repetition_penalty=1.1 +typical_p=1.0 diff --git a/internal/textgeneration/data/presets/Verbose (Beam Search).txt b/internal/textgeneration/data/presets/Verbose (Beam Search).txt new file mode 100644 index 0000000..a3be1b9 --- /dev/null +++ b/internal/textgeneration/data/presets/Verbose (Beam Search).txt @@ -0,0 +1,9 @@ +num_beams=10 +min_length=200 +length_penalty =1.4 +no_repeat_ngram_size=2 +early_stopping=True +temperature=0.7 +top_k=150 +top_p=0.92 +repetition_penalty=4.5 diff --git a/internal/textgeneration/deno_test.js b/internal/textgeneration/deno_test.js new file mode 100644 index 0000000..e884a71 --- /dev/null +++ b/internal/textgeneration/deno_test.js @@ -0,0 +1,65 @@ +const ws = new WebSocket("ws://ontos:7860/queue/join"); + +const hash = crypto.randomUUID(); + +ws.onmessage = (ev) => { + const data = JSON.parse(ev.data); + console.log(data); + + if (data.msg == "send_hash") { + console.log("sending hash"); + ws.send(JSON.stringify({session_hash: hash, fn_index: 40})); + } + if (data.msg == "process_starts") { + console.log("generation has started"); + } + if (data.msg == "send_data") { + ws.send(JSON.stringify({ + fn_index: 33, + data: [ + "Midori_Yasomi", + "Xe", + "Midori", + ], + session_hash: hash, + })); + ws.send(JSON.stringify({ + data: [ + "So, what's the deal with airline food?", + 200, + true, + 0.7, + 0.5, + 0.19, + 1.1, + 0, + 0, + 0, + 0, + 0, + 0, + false, + "Xe", + "Midori Yasomi", + "Midori Yasomi is a young, computer engineer-nerd with a knack for problem solving and a passion for technology.\n\\u003cSTART\\u003e\n{{user}}: So how did you get into computer engineering?\n{{char}}: I've always been into technology, but I didn't really get into programming until my high school robotics club.\n{{user}}: I see, that's neat.\n{{char}}: Yeah, robotics club was really fun.\n{{user}}: So what do you do when you're not working on computers?\n{{char}}: I play a lot of rhythm games and like to write fiction.\n{{user}}: What's your favorite type of computer hardware to work with?\n{{char}}: GPUs. They power my favorite experiences and my brain as a whole.\n{{user}}: That sounds great!\n{{char}}: Yeah, it's really fun. I'm lucky to be able to do this as a job.\n\n", + false, + 0, + 0 + ], + fn_index: 9, + session_hash: hash, + })); + ws.send(JSON.stringify({ + fn_index: 24, + data: ["So, what's the deal with airline food?"], + session_hash: hash, + })); + } + if (data.msg == "process_completed" || data.msg == "process_generating") { + data.output.data.forEach((row) => { + console.log(row); + }); + } +} + +console.log("done"); diff --git a/internal/textgeneration/textgen.go b/internal/textgeneration/textgen.go new file mode 100644 index 0000000..b0a17aa --- /dev/null +++ b/internal/textgeneration/textgen.go @@ -0,0 +1,262 @@ +package textgen + +import ( + "bytes" + "context" + "embed" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strconv" + "strings" + + "within.website/x/web" +) + +var ( + tgServerURL = flag.String("textgen-server-url", "http://ontos:7860", "URL for the Stable Diffusion API") + + //go:embed data/characters + characters embed.FS + + //go:embed data/presets + presets embed.FS +) + +func buildURL(path string) (*url.URL, error) { + u, err := url.Parse(*tgServerURL) + if err != nil { + return nil, err + } + + u.Path = path + + return u, nil +} + +type Character struct { + CharName string `json:"char_name"` + CharPersona string `json:"char_persona"` + CharGreeting string `json:"char_greeting"` + WorldScenario string `json:"world_scenario"` + ExampleDialogue string `json:"example_dialogue"` +} + +/* + [ + : string, // represents text string of 'Input' Textbox component + : number, // represents selected value of 'max_new_tokens' Slider component + : boolean, // represents checked status of 'do_sample' Checkbox component + : number, // represents selected value of 'temperature' Slider component + : number, // represents selected value of 'top_p' Slider component + : number, // represents selected value of 'typical_p' Slider component + : number, // represents selected value of 'repetition_penalty' Slider component + : number, // represents selected value of 'top_k' Slider component + : number, // represents selected value of 'min_length' Slider component + : number, // represents selected value of 'no_repeat_ngram_size' Slider component + : number, // represents selected value of 'num_beams' Slider component + : number, // represents selected value of 'penalty_alpha' Slider component + : number, // represents selected value of 'length_penalty' Slider component + : boolean, // represents checked status of 'early_stopping' Checkbox component + : string, // represents text string of 'Your name' Textbox component + : string, // represents text string of 'Bot's name' Textbox component + : string, // represents text string of 'Context' Textbox component + : boolean, // represents checked status of 'Stop generating at new line character?' Checkbox component + : number, // represents selected value of 'Maximum prompt size in tokens' Slider component + : number, // represents selected value of 'Generation attempts (for longer replies)' Slider component + ] +*/ + +type ChatRequest struct { + Input string `json:"input"` + MaxNewTokens int `json:"max_new_tokens"` + DoSample bool `json:"do_sample"` + Temp float64 `json:"temperature"` + TopP float64 `json:"top_p"` + TypicalP float64 `json:"typical_p"` + RepetitionPenalty float64 `json:"repetition_penalty"` + TopK float64 `json:"top_k"` + MinLength int `json:"min_length"` + NoRepeatNgramSize int `json:"no_repeat_ngram_size"` + NumBeams int `json:"num_beams"` + PenaltyAlpha float64 `json:"penalty_alpha"` + LengthPenalty float64 `json:"length_penalty"` + EarlyStopping bool `json:"early_stopping"` + YourName string `json:"your_name"` + BotName string `json:"bot_name"` + Context string `json:"context"` + StopAfterNewline bool `json:"stop_after_newline"` + MaxPromptSize int `json:"max_prompt_size"` + GenerationAttempts int `json:"generation_attempts"` +} + +func (cr *ChatRequest) ApplyCharacter(name string) error { + fin, err := characters.Open("data/characters/" + name + ".json") + if err != nil { + return fmt.Errorf("textgen: can't open character %s: %w", name, err) + } + defer fin.Close() + + var ch Character + if err := json.NewDecoder(fin).Decode(&ch); err != nil { + return fmt.Errorf("textgen: can't decode character %s: %w", name, err) + } + + cr.BotName = ch.CharName + + var sb strings.Builder + + fmt.Fprintln(&sb, ch.CharPersona) + fmt.Fprintln(&sb, "<START>") + fmt.Fprintln(&sb, ch.ExampleDialogue) + fmt.Fprintln(&sb) + + cr.Context = sb.String() + + return nil +} + +// ApplyPreset mutates cr with the details in the preset by name. +func (cr *ChatRequest) ApplyPreset(name string) error { + finData, err := presets.ReadFile("data/presets/" + name + ".txt") + if err != nil { + return fmt.Errorf("textgen: can't open preset %s: %w", name, err) + } + + var data = map[string]any{} + + for _, line := range strings.Split(string(finData), "\n") { + if line == "" { + break + } + + kv := strings.SplitN(line, "=", 2) + k, v := kv[0], kv[1] + switch v { + case "True": + data[k] = true + case "False": + data[k] = false + default: + num, err := strconv.ParseFloat(v, 64) + if err != nil { + fmt.Errorf("textgen: can't parse %q as float64: %w", v, err) + } + + data[k] = num + } + } + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(data); err != nil { + return fmt.Errorf("textgen: can't encode data to JSON: %w", err) + } + + if err := json.Unmarshal(buf.Bytes(), cr); err != nil { + return fmt.Errorf("textgeneration: can't decode data to ChatRequest: %w", err) + } + + return nil +} + +func (cr *ChatRequest) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(struct { + Data []any `json:"data"` + }{ + Data: []any{ + cr.Input, + cr.MaxNewTokens, + cr.DoSample, + cr.Temp, + cr.TopP, + cr.TypicalP, + cr.RepetitionPenalty, + cr.TopK, + cr.MinLength, + cr.NoRepeatNgramSize, + cr.NumBeams, + cr.PenaltyAlpha, + cr.LengthPenalty, + cr.EarlyStopping, + // cr.YourName, + // cr.BotName, + // cr.Context, + // cr.StopAfterNewline, + // cr.MaxPromptSize, + // cr.GenerationAttempts, + }}); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type ChatResponse struct { + Data []string `json:"data"` // [0] is user input, [1] is bot output + Duration float64 `json:"duration"` + IsGenerating bool `json:"is_generating"` +} + +var ( + Default *Client = &Client{ + HTTP: http.DefaultClient, + } +) + +func Generate(ctx context.Context, inp *ChatRequest) (*ChatResponse, error) { + return Default.Generate(ctx, inp) +} + +type Client struct { + HTTP *http.Client +} + +func (c *Client) Generate(ctx context.Context, cr *ChatRequest) (*ChatResponse, error) { + u, err := buildURL("/run/textgen") + if err != nil { + return nil, err + } + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(cr); err != nil { + return nil, err + } + + log.Println(buf.String()) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &buf) + if err != nil { + return nil, fmt.Errorf("error making request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.HTTP.Do(req) + if err != nil { + return nil, fmt.Errorf("can't fetch response: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, web.NewError(http.StatusOK, resp) + } + + buf = bytes.Buffer{} + if _, err := io.Copy(&buf, resp.Body); err != nil { + return nil, fmt.Errorf("can't read body") + } + + log.Println(buf.String()) + + var result ChatResponse + if err := json.NewDecoder(&buf).Decode(&result); err != nil { + return nil, fmt.Errorf("error parsing ChatResponse: %w", err) + } + + return &result, nil +} diff --git a/internal/textgeneration/textgen_test.go b/internal/textgeneration/textgen_test.go new file mode 100644 index 0000000..75ae36c --- /dev/null +++ b/internal/textgeneration/textgen_test.go @@ -0,0 +1,56 @@ +package textgen + +import ( + "context" + "fmt" + "os" + "strconv" + "testing" +) + +func TestApplyCharacter(t *testing.T) { + cr := new(ChatRequest) + if err := cr.ApplyCharacter("yasomi"); err != nil { + t.Fatalf("%v", err) + } + + if cr.BotName != "Midori Yasomi" { + t.Fatalf("expected bot name to be %q, got: %q", "Midori Yasomi", cr.BotName) + } + + t.Log(cr.Context) +} + +func TestApplyPreset(t *testing.T) { + cr := new(ChatRequest) + if err := cr.ApplyPreset("Kobold-Godl |
