├── .github
├── FUNDING.yaml
└── workflows
│ ├── lint.yaml
│ └── release.yaml
├── .gitignore
├── common
├── options.go
├── utils.go
├── config.go
├── vars.go
└── const.go
├── vars.go
├── config
├── init.go
├── config_model.go
├── load.go
├── view.go
├── new.go
├── update.go
└── utils.go
├── chat
├── init.go
├── utils.go
├── chat_model.go
├── view.go
├── new.go
└── update.go
├── .goreleaser.yaml
├── util
└── utils.go
├── errors
└── errors.go
├── const.go
├── utils.go
├── style
└── vars.go
├── LICENSE
├── go.mod
├── main.go
├── README.md
└── go.sum
/.github/FUNDING.yaml:
--------------------------------------------------------------------------------
1 | github: ["dwisiswant0"]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | chatgptui
2 | main
3 | bin/
4 | dist/
--------------------------------------------------------------------------------
/common/options.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | type Options struct {
4 | Edit bool
5 | List bool
6 | Remove bool
7 | Version bool
8 | }
9 |
--------------------------------------------------------------------------------
/vars.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/charmbracelet/bubbletea"
5 |
6 | "github.com/dwisiswant0/chatgptui/common"
7 | )
8 |
9 | var (
10 | m tea.Model
11 | opt common.Options
12 | )
13 |
--------------------------------------------------------------------------------
/config/init.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/charmbracelet/bubbles/textinput"
5 | "github.com/charmbracelet/bubbletea"
6 | )
7 |
8 | func (m model) Init() tea.Cmd {
9 | return textinput.Blink
10 | }
11 |
--------------------------------------------------------------------------------
/chat/init.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "github.com/charmbracelet/bubbles/textarea"
5 | "github.com/charmbracelet/bubbletea"
6 | )
7 |
8 | func (m model) Init() tea.Cmd {
9 | return tea.Batch(textarea.Blink, tea.ClearScreen)
10 | }
11 |
--------------------------------------------------------------------------------
/common/utils.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "path/filepath"
8 | )
9 |
10 | func GetConfigPath() string {
11 | homeDir, err := os.UserHomeDir()
12 | if err != nil {
13 | log.Fatal(err)
14 | }
15 |
16 | return filepath.Join(homeDir, ".chatgptui.json")
17 | }
18 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | builds:
2 | - binary: chatgptui
3 | main: .
4 | ldflags:
5 | - -s -w
6 | goos:
7 | - linux
8 | - windows
9 | - darwin
10 | goarch:
11 | - amd64
12 | - 386
13 | - arm
14 | - arm64
15 |
16 | archives:
17 | - id: binary
18 | format: binary
--------------------------------------------------------------------------------
/config/config_model.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/charmbracelet/bubbles/textinput"
4 |
5 | type configInput struct {
6 | defaultValue string
7 | label, name string
8 | value any
9 | }
10 |
11 | type model struct {
12 | err error
13 | focusIndex int
14 | inputs []textinput.Model
15 | configs []configInput
16 | }
17 |
--------------------------------------------------------------------------------
/chat/utils.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "context"
5 | "strings"
6 | )
7 |
8 | func (m model) sendChat(prompt string) (string, error) {
9 | ctx := context.Background()
10 |
11 | m.openaiRequest.Prompt = prompt
12 |
13 | resp, err := m.openaiClient.CreateCompletion(ctx, m.openaiRequest)
14 | if err != nil {
15 | return "", err
16 | }
17 |
18 | return strings.TrimSpace(resp.Choices[0].Text), nil
19 | }
20 |
--------------------------------------------------------------------------------
/common/config.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | type Config struct {
4 | MaxLength int `json:"max_length" validate:"required,number"`
5 | Model string `json:"model" validate:"required"`
6 | OpenaiAPIKey string `json:"openai_api_key" validate:"required,startswith=sk-"`
7 | Temperature float32 `json:"temperature" validate:"required,number"`
8 | TopP float32 `json:"top_p" validate:"required,number"`
9 | }
10 |
--------------------------------------------------------------------------------
/util/utils.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/charmbracelet/bubbletea"
7 | "github.com/charmbracelet/lipgloss"
8 | )
9 |
10 | func RunProgram(model tea.Model) {
11 | p := tea.NewProgram(model)
12 | if _, err := p.Run(); err != nil {
13 | log.Fatal(err)
14 | }
15 | }
16 |
17 | func SetTermColor(s string) lipgloss.Style {
18 | return lipgloss.NewStyle().Foreground(lipgloss.Color(s))
19 | }
20 |
--------------------------------------------------------------------------------
/common/vars.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | var OpenaiModels = []string{
4 | "gpt-4-32k-0314", "gpt-4-32k", "gpt-4-0314", "gpt-4", "gpt-3.5-turbo-0301", "gpt-3.5-turbo",
5 | "text-davinci-003", "text-davinci-002", "text-curie-001", "text-babbage-001", "text-ada-001",
6 | "text-davinci-001", "davinci-instruct-beta", "davinci", "curie-instruct-beta", "curie", "ada",
7 | "babbage", "code-davinci-002", "code-cushman-001", "code-davinci-001",
8 | }
9 |
--------------------------------------------------------------------------------
/errors/errors.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import "errors"
4 |
5 | var (
6 | InvalidAPIKey = errors.New("Invalid OpenAI API key!")
7 | MaxLengthRange = errors.New("Max. length range between 1-4000!")
8 | GreaterFloatNumber = errors.New("Floating number cannot be greater than 1!")
9 | )
10 |
11 | const (
12 | InvalidModel = `"%s" is not a valid model!`
13 | InvalidFloatNumber = `"%s" is not a valid floating number!`
14 | )
15 |
--------------------------------------------------------------------------------
/common/const.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | const Version = "v1.0.0"
4 |
5 | const (
6 | HelpText = "↑/↓: Navigate • esc: Quit"
7 | HelpTextProTip = "💡 ProTip: Type '/c' to clear chat history."
8 | HelpTextReset = "ctrl+r: Reset all"
9 | HelpTextTab = "tab: Fill to default"
10 | )
11 |
12 | const (
13 | ChatPlaceholder = "Send your prompt..."
14 | ChatWelcomeMessage = `Welcome to the ChatGPTUI! 🤖
15 | Type a message and press Enter to send.`
16 | )
17 |
--------------------------------------------------------------------------------
/chat/chat_model.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "github.com/charmbracelet/bubbles/textarea"
5 | "github.com/charmbracelet/bubbles/viewport"
6 | "github.com/dwisiswant0/chatgptui/common"
7 | "github.com/sashabaranov/go-openai"
8 | )
9 |
10 | type model struct {
11 | config common.Config
12 | err error
13 | messages []string
14 | textarea textarea.Model
15 | viewport viewport.Model
16 |
17 | openaiClient *openai.Client
18 | openaiRequest openai.CompletionRequest
19 | }
20 |
--------------------------------------------------------------------------------
/const.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const (
4 | header = "\n ChatGPTUI 🤖 %s\n" +
5 | " --\n" + " ChatGPT with Textual User Interface\n" +
6 | " made with 💖 by dw1\n\n"
7 |
8 | options = "\nOptions:\n" +
9 | " -e, --edit Edit configuration\n" +
10 | " -l, --list List all supported OpenAI model\n" +
11 | " --rm Remove configuration\n" +
12 | " -V, --version Show current version\n"
13 |
14 | examples = "\nExamples:\n" +
15 | " %s\n" +
16 | " %s --edit\n"
17 | )
18 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yaml:
--------------------------------------------------------------------------------
1 | name: Lint
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 |
8 | jobs:
9 | checks:
10 | name: "Linter"
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: "Set up Go"
14 | uses: actions/setup-go@v3
15 | with:
16 | go-version: 1.19
17 |
18 | - name: "Check out code"
19 | uses: actions/checkout@v3
20 |
21 | - name: "GolangCI-Lint"
22 | uses: golangci/golangci-lint-action@v3.4.0
23 | with:
24 | version: v1.50
--------------------------------------------------------------------------------
/chat/view.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/dwisiswant0/chatgptui/common"
7 | "github.com/dwisiswant0/chatgptui/style"
8 | )
9 |
10 | func (m model) View() string {
11 | var b strings.Builder
12 |
13 | b.WriteString(m.viewport.View())
14 | b.WriteString("\n\n")
15 | b.WriteString(m.textarea.View())
16 | b.WriteString("\n\n")
17 |
18 | if m.err != nil {
19 | b.WriteString(style.Error.Render(m.err.Error()) + "\n\n")
20 | }
21 |
22 | b.WriteString(style.Help.Render(common.HelpTextProTip))
23 | b.WriteString("\n\n")
24 | b.WriteString(style.Help.Render(common.HelpText))
25 |
26 | return b.String()
27 | }
28 |
--------------------------------------------------------------------------------
/config/load.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 |
6 | "encoding/json"
7 |
8 | "github.com/go-playground/validator/v10"
9 |
10 | "github.com/dwisiswant0/chatgptui/common"
11 | )
12 |
13 | func Load(path string) (common.Config, error) {
14 | var cfg common.Config
15 |
16 | file, err := os.Open(path)
17 | if err != nil {
18 | return cfg, err
19 | }
20 | defer file.Close()
21 |
22 | decoder := json.NewDecoder(file)
23 | if err := decoder.Decode(&cfg); err != nil {
24 | return cfg, err
25 | }
26 |
27 | validate := validator.New()
28 | if err := validate.Struct(cfg); err != nil {
29 | return cfg, err
30 | }
31 |
32 | return cfg, nil
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release & Publish
2 | on:
3 | create:
4 | tags:
5 | - v*
6 |
7 | jobs:
8 | release:
9 | name: "Release binary"
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: "Check out code"
13 | uses: actions/checkout@v3
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: "Set up Go"
18 | uses: actions/setup-go@v3
19 | with:
20 | go-version: 1.19
21 |
22 | - name: "Create release on GitHub"
23 | uses: goreleaser/goreleaser-action@v4.2.0
24 | env:
25 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
26 | with:
27 | args: "release --rm-dist"
28 | version: latest
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/dwisiswant0/chatgptui/common"
8 | )
9 |
10 | func showBanner() {
11 | fmt.Fprintf(os.Stderr, header, common.Version)
12 | }
13 |
14 | func showUsage() {
15 | main := os.Args[0]
16 | fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", main)
17 | fmt.Fprint(os.Stderr, options)
18 | fmt.Fprintf(os.Stderr, examples, main, main)
19 | }
20 |
21 | func showVersion() {
22 | fmt.Fprintf(os.Stderr, "ChatGPTUI %s\n", common.Version)
23 | os.Exit(2)
24 | }
25 |
26 | func listAllModels() {
27 | for _, model := range common.OpenaiModels {
28 | fmt.Println(model)
29 | }
30 | os.Exit(0)
31 | }
32 |
33 | func removeConfig() {
34 | _ = os.Remove(common.GetConfigPath())
35 | os.Exit(0)
36 | }
37 |
--------------------------------------------------------------------------------
/style/vars.go:
--------------------------------------------------------------------------------
1 | package style
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/charmbracelet/lipgloss"
7 | "github.com/dwisiswant0/chatgptui/util"
8 | )
9 |
10 | var (
11 | Focused = util.SetTermColor("205")
12 |
13 | Cursor = Focused.Copy()
14 | Error = util.SetTermColor("11")
15 | Help = util.SetTermColor("240")
16 | Clear = lipgloss.NewStyle()
17 | Placeholder = util.SetTermColor("60")
18 | Response = util.SetTermColor("#b13434")
19 | Sender = util.SetTermColor("#1c74d4")
20 | Spinner = Focused.Copy()
21 | Viewport = lipgloss.NewStyle().
22 | BorderStyle(lipgloss.RoundedBorder()).
23 | BorderForeground(lipgloss.Color("62")).
24 | PaddingRight(2)
25 |
26 | FocusedBtn = Focused.Copy().Render("[ Save ]")
27 | BlurredBtn = fmt.Sprintf("[ %s ]", Help.Render("Save"))
28 | )
29 |
--------------------------------------------------------------------------------
/config/view.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/dwisiswant0/chatgptui/common"
8 | "github.com/dwisiswant0/chatgptui/style"
9 | )
10 |
11 | func (m model) View() string {
12 | var b strings.Builder
13 |
14 | for i := range m.inputs {
15 | b.WriteString(m.inputs[i].View())
16 | if i < len(m.inputs)-1 {
17 | b.WriteRune('\n')
18 | }
19 | }
20 |
21 | button := &style.BlurredBtn
22 | if m.focusIndex == len(m.inputs) {
23 | button = &style.FocusedBtn
24 | }
25 | fmt.Fprintf(&b, "\n\n%s\n\n", *button)
26 |
27 | if m.err != nil {
28 | b.WriteString(style.Error.Render(m.err.Error()) + "\n\n")
29 | }
30 |
31 | b.WriteString(style.Help.Render(fmt.Sprintf(
32 | " %s\n %s\n %s\n", common.HelpText,
33 | common.HelpTextTab, common.HelpTextReset,
34 | )))
35 |
36 | return b.String()
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Dwi Siswanto
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/chat/new.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "github.com/charmbracelet/bubbles/textarea"
5 | "github.com/charmbracelet/bubbles/viewport"
6 | "github.com/sashabaranov/go-openai"
7 |
8 | "github.com/dwisiswant0/chatgptui/common"
9 | "github.com/dwisiswant0/chatgptui/style"
10 | )
11 |
12 | func New(cfg common.Config) model {
13 | ta := textarea.New()
14 | ta.Placeholder = "Send your prompt..."
15 | ta.Focus()
16 |
17 | ta.Prompt = "┃ "
18 | ta.CharLimit = int(cfg.MaxLength)
19 |
20 | ta.SetHeight(3)
21 |
22 | ta.FocusedStyle.CursorLine = style.Clear
23 | ta.FocusedStyle.Placeholder = style.Placeholder
24 |
25 | ta.ShowLineNumbers = false
26 | ta.KeyMap.InsertNewline.SetEnabled(false)
27 |
28 | vp := viewport.New(78, 15)
29 | vp.SetContent(common.ChatWelcomeMessage)
30 | vp.Style = style.Viewport
31 |
32 | client := openai.NewClient(cfg.OpenaiAPIKey)
33 | req := openai.CompletionRequest{
34 | MaxTokens: cfg.MaxLength,
35 | Model: cfg.Model,
36 | Temperature: cfg.Temperature,
37 | TopP: cfg.TopP,
38 | }
39 |
40 | return model{
41 | config: cfg,
42 | err: nil,
43 | messages: []string{},
44 | textarea: ta,
45 | viewport: vp,
46 |
47 | openaiClient: client,
48 | openaiRequest: req,
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dwisiswant0/chatgptui
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/charmbracelet/bubbles v0.15.0
7 | github.com/charmbracelet/bubbletea v0.23.2
8 | github.com/charmbracelet/lipgloss v0.7.1
9 | github.com/go-playground/validator/v10 v10.12.0
10 | github.com/sashabaranov/go-openai v1.5.7
11 | )
12 |
13 | require (
14 | github.com/atotto/clipboard v0.1.4 // indirect
15 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
16 | github.com/containerd/console v1.0.3 // indirect
17 | github.com/go-playground/locales v0.14.1 // indirect
18 | github.com/go-playground/universal-translator v0.18.1 // indirect
19 | github.com/leodido/go-urn v1.2.2 // indirect
20 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
21 | github.com/mattn/go-isatty v0.0.17 // indirect
22 | github.com/mattn/go-localereader v0.0.1 // indirect
23 | github.com/mattn/go-runewidth v0.0.14 // indirect
24 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
25 | github.com/muesli/cancelreader v0.2.2 // indirect
26 | github.com/muesli/reflow v0.3.0 // indirect
27 | github.com/muesli/termenv v0.15.1 // indirect
28 | github.com/rivo/uniseg v0.2.0 // indirect
29 | golang.org/x/crypto v0.7.0 // indirect
30 | golang.org/x/sync v0.1.0 // indirect
31 | golang.org/x/sys v0.6.0 // indirect
32 | golang.org/x/term v0.6.0 // indirect
33 | golang.org/x/text v0.8.0 // indirect
34 | )
35 |
--------------------------------------------------------------------------------
/chat/update.go:
--------------------------------------------------------------------------------
1 | package chat
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/charmbracelet/bubbletea"
8 | "github.com/charmbracelet/lipgloss"
9 |
10 | "github.com/dwisiswant0/chatgptui/common"
11 | "github.com/dwisiswant0/chatgptui/style"
12 | )
13 |
14 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
15 | var (
16 | tiCmd tea.Cmd
17 | vpCmd tea.Cmd
18 | )
19 |
20 | m.textarea, tiCmd = m.textarea.Update(msg)
21 | m.viewport, vpCmd = m.viewport.Update(msg)
22 |
23 | switch msg := msg.(type) {
24 | case tea.KeyMsg:
25 | switch msg.String() {
26 | case "ctrl+c", "esc":
27 | return m, tea.Quit
28 | case "enter":
29 | val := m.textarea.Value()
30 | if val == "" {
31 | return m, nil
32 | }
33 |
34 | switch val {
35 | case "/c", "/clear":
36 | m.viewport.SetContent(common.ChatWelcomeMessage)
37 | m.messages = []string{}
38 | default:
39 | m.messages = append(m.messages, fmt.Sprintf("%s %s", style.Sender.Render("👤:"), val))
40 |
41 | res, err := m.sendChat(val)
42 | if err != nil {
43 | m.err = err
44 | return m, nil
45 | }
46 |
47 | m.messages = append(m.messages, fmt.Sprintf(
48 | "%s %s",
49 | style.Response.Render("🤖:"),
50 | lipgloss.NewStyle().Width(78-5).Render(res)),
51 | )
52 | m.viewport.SetContent(strings.Join(m.messages, "\n"))
53 | m.viewport.GotoBottom()
54 | }
55 |
56 | m.textarea.Reset()
57 | }
58 | }
59 |
60 | return m, tea.Batch(tiCmd, vpCmd)
61 | }
62 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 |
6 | "github.com/dwisiswant0/chatgptui/chat"
7 | "github.com/dwisiswant0/chatgptui/common"
8 | "github.com/dwisiswant0/chatgptui/config"
9 | "github.com/dwisiswant0/chatgptui/util"
10 | )
11 |
12 | func init() {
13 | flag.BoolVar(&opt.Edit, "e", false, "Edit configuration file")
14 | flag.BoolVar(&opt.Edit, "edit", false, "Edit configuration file")
15 |
16 | flag.BoolVar(&opt.List, "l", false, "List all supported OpenAI model")
17 | flag.BoolVar(&opt.List, "list", false, "List all supported OpenAI model")
18 |
19 | flag.BoolVar(&opt.Remove, "rm", false, "Remove configuration file")
20 |
21 | flag.BoolVar(&opt.Version, "V", false, "Show current version")
22 | flag.BoolVar(&opt.Version, "version", false, "Show current version")
23 |
24 | flag.Usage = func() {
25 | showBanner()
26 | showUsage()
27 | }
28 | flag.Parse()
29 |
30 | switch {
31 | case opt.List:
32 | listAllModels()
33 | case opt.Remove:
34 | removeConfig()
35 | case opt.Version:
36 | showVersion()
37 | }
38 |
39 | // if opt.List {
40 | // listAllModels()
41 | // }
42 |
43 | // if opt.Remove {
44 | // removeConfig()
45 | // }
46 |
47 | // if opt.Version {
48 | // showVersion()
49 | // }
50 | }
51 |
52 | func main() {
53 | cfgPath := common.GetConfigPath()
54 |
55 | cfg, err := config.Load(cfgPath)
56 | if err == nil {
57 | m = chat.New(cfg)
58 |
59 | if opt.Edit {
60 | m = config.New(cfg)
61 | }
62 | } else {
63 | m = config.New()
64 | }
65 |
66 | util.RunProgram(m)
67 | }
68 |
--------------------------------------------------------------------------------
/config/new.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/charmbracelet/bubbles/textinput"
7 |
8 | "github.com/dwisiswant0/chatgptui/common"
9 | "github.com/dwisiswant0/chatgptui/style"
10 | )
11 |
12 | func New(cfgs ...common.Config) model {
13 | var (
14 | cfg common.Config
15 | t textinput.Model
16 |
17 | isEdit bool
18 | )
19 |
20 | if len(cfgs) > 0 {
21 | isEdit = true
22 | cfg = cfgs[0]
23 | }
24 |
25 | m := model{inputs: make([]textinput.Model, 5)}
26 | m.configs = make([]configInput, len(m.inputs))
27 |
28 | for i := range m.configs {
29 | switch i {
30 | case 0:
31 | m.configs[i].label = "OpenAI API key"
32 | m.configs[i].name = "openai_api_key"
33 | case 1:
34 | m.configs[i].label = "Model"
35 | m.configs[i].name = "model"
36 | m.configs[i].defaultValue = "text-davinci-003"
37 | case 2:
38 | m.configs[i].label = "Temperature"
39 | m.configs[i].name = "temperature"
40 | m.configs[i].defaultValue = "0.7"
41 | case 3:
42 | m.configs[i].label = "Maximum length"
43 | m.configs[i].name = "max_length"
44 | m.configs[i].defaultValue = "256"
45 | case 4:
46 | m.configs[i].label = "Top P"
47 | m.configs[i].name = "top_p"
48 | m.configs[i].defaultValue = "1"
49 | }
50 | }
51 |
52 | for i := range m.inputs {
53 | t = textinput.New()
54 | t.CursorStyle = style.Cursor
55 | t.CharLimit = 64
56 |
57 | switch i {
58 | case 0:
59 | t.Focus()
60 | t.Placeholder = m.getPlaceholder(i)
61 | t.PromptStyle = style.Focused
62 | t.TextStyle = style.Focused
63 | t.EchoMode = textinput.EchoPassword
64 | t.EchoCharacter = '•'
65 |
66 | if isEdit {
67 | t.SetValue(cfg.OpenaiAPIKey)
68 | }
69 | default:
70 | t.Placeholder = m.getPlaceholder(i)
71 |
72 | if isEdit {
73 | switch m.configs[i].name {
74 | case "model":
75 | t.SetValue(cfg.Model)
76 | case "temperature":
77 | t.SetValue(fmt.Sprintf("%f", cfg.Temperature))
78 | case "max_length":
79 | t.SetValue(fmt.Sprintf("%d", cfg.MaxLength))
80 | case "top_p":
81 | t.SetValue(fmt.Sprintf("%f", cfg.TopP))
82 | }
83 | }
84 | }
85 |
86 | m.inputs[i] = t
87 | }
88 |
89 | return m
90 | }
91 |
--------------------------------------------------------------------------------
/config/update.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/charmbracelet/bubbletea"
5 |
6 | "github.com/dwisiswant0/chatgptui/chat"
7 | "github.com/dwisiswant0/chatgptui/common"
8 | "github.com/dwisiswant0/chatgptui/style"
9 | "github.com/dwisiswant0/chatgptui/util"
10 | )
11 |
12 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
13 | switch msg := msg.(type) {
14 | case tea.KeyMsg:
15 | switch msg.String() {
16 | case "ctrl+c", "esc":
17 | return m, tea.Quit
18 | case "ctrl+r":
19 | for i := 0; i <= len(m.inputs)-1; i++ {
20 | m.inputs[i].Reset()
21 | }
22 |
23 | return m, nil
24 | case "tab":
25 | if m.focusIndex < len(m.inputs) {
26 | i := m.focusIndex
27 | v := m.configs[i].defaultValue
28 | if v != "" {
29 | m.inputs[i].SetValue(v)
30 | }
31 | }
32 | case "enter", "up", "down":
33 | s := msg.String()
34 | if s == "enter" {
35 | if m.focusIndex == len(m.inputs) {
36 | if err := m.validateInputs(); err != nil {
37 | m.err = err
38 | return m, nil
39 | } else {
40 | m.err = nil
41 | }
42 |
43 | if err := m.saveConfig(); err != nil {
44 | m.err = err
45 | return m, nil
46 | } else {
47 | cfg, err := Load(common.GetConfigPath())
48 | if err != nil {
49 | m.err = err
50 | return m, nil
51 | }
52 |
53 | util.RunProgram(chat.New(cfg))
54 | m.err = nil
55 | }
56 |
57 | return m, tea.Quit
58 | }
59 |
60 | if m.inputs[m.focusIndex].Value() == "" {
61 | return m, nil
62 | }
63 |
64 | if err := m.validateInput(m.focusIndex); err != nil {
65 | m.err = err
66 | return m, nil
67 | } else {
68 | m.err = nil
69 | }
70 | }
71 |
72 | if s == "up" {
73 | m.focusIndex--
74 | } else {
75 | m.focusIndex++
76 | }
77 |
78 | if m.focusIndex > len(m.inputs) {
79 | m.focusIndex = 0
80 | } else if m.focusIndex < 0 {
81 | m.focusIndex = len(m.inputs)
82 | }
83 |
84 | cmds := make([]tea.Cmd, len(m.inputs))
85 | for i := 0; i <= len(m.inputs)-1; i++ {
86 | if i == m.focusIndex {
87 | cmds[i] = m.inputs[i].Focus()
88 | m.inputs[i].PromptStyle = style.Focused
89 | m.inputs[i].TextStyle = style.Focused
90 | continue
91 | }
92 |
93 | m.inputs[i].Blur()
94 | m.inputs[i].PromptStyle = style.Clear
95 | m.inputs[i].TextStyle = style.Clear
96 | }
97 |
98 | return m, tea.Batch(cmds...)
99 | }
100 | }
101 |
102 | cmd := m.updateInputs(msg)
103 |
104 | return m, cmd
105 | }
106 |
--------------------------------------------------------------------------------
/config/utils.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strconv"
7 | "strings"
8 |
9 | "encoding/json"
10 |
11 | "github.com/charmbracelet/bubbletea"
12 |
13 | "github.com/dwisiswant0/chatgptui/common"
14 | "github.com/dwisiswant0/chatgptui/errors"
15 | )
16 |
17 | func (m model) getPlaceholder(i int) string {
18 | if i > len(m.configs) {
19 | return ""
20 | }
21 |
22 | str := m.configs[i].label
23 | if m.configs[i].defaultValue != "" {
24 | str = fmt.Sprintf(`%s (default "%s")`, str, m.configs[i].defaultValue)
25 | }
26 |
27 | return str
28 | }
29 |
30 | func (m model) validateInput(i int) error {
31 | if i > len(m.inputs) {
32 | return nil
33 | }
34 |
35 | val := m.inputs[i].Value()
36 |
37 | switch m.configs[i].name {
38 | case "openai_api_key":
39 | if !strings.HasPrefix(val, "sk-") {
40 | return errors.InvalidAPIKey
41 | }
42 | m.configs[i].value = val
43 | case "model":
44 | for _, model := range common.OpenaiModels {
45 | if val == model {
46 | m.configs[i].value = val
47 | return nil
48 | }
49 | }
50 | return fmt.Errorf(errors.InvalidModel, val)
51 | case "max_length":
52 | length, err := strconv.Atoi(val)
53 | if err != nil {
54 | return errors.MaxLengthRange
55 | }
56 |
57 | if length < 1 || length > 4000 {
58 | return errors.MaxLengthRange
59 | }
60 |
61 | m.configs[i].value = length
62 | case "temperature", "top_p":
63 | valFloat32, err := strconv.ParseFloat(val, 32)
64 | if err != nil {
65 | return fmt.Errorf(errors.InvalidFloatNumber, val)
66 | }
67 |
68 | if valFloat32 > 1 {
69 | return errors.GreaterFloatNumber
70 | }
71 |
72 | m.configs[i].value = valFloat32
73 | }
74 |
75 | return nil
76 | }
77 |
78 | func (m model) validateInputs() error {
79 | for i := range m.inputs {
80 | err := m.validateInput(i)
81 | if err != nil {
82 | return err
83 | }
84 | }
85 |
86 | return nil
87 | }
88 |
89 | func (m model) saveConfig() error {
90 | file, err := os.Create(common.GetConfigPath())
91 | if err != nil {
92 | return err
93 | }
94 | defer file.Close()
95 |
96 | encoder := json.NewEncoder(file)
97 |
98 | cfgMap := make(map[string]any)
99 |
100 | for _, config := range m.configs {
101 | cfgMap[config.name] = config.value
102 | }
103 |
104 | if err := encoder.Encode(cfgMap); err != nil {
105 | return err
106 | }
107 |
108 | return nil
109 | }
110 |
111 | func (m *model) updateInputs(msg tea.Msg) tea.Cmd {
112 | cmds := make([]tea.Cmd, len(m.inputs))
113 |
114 | for i := range m.inputs {
115 | m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
116 | }
117 |
118 | return tea.Batch(cmds...)
119 | }
120 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ChatGPT 🤖 with Textual User Interface (TUI) mode written in Go.
4 |
5 | ChatGPTUI is an interactive tool that allows users to interact with OpenAI's GPT (Generative Pre-trained Transformer) language model in real-time, using a text-based user interface (TUI). This tool provides a convenient and intuitive way to communicate with the language model and generate human-like text responses, making it an ideal tool for anyone looking to explore the capabilities of language models or create conversational applications.
6 |
7 |
8 |
9 | ChatGPTUI is designed to be easy to use and highly customizable. The tool supports a range of configuration options, allowing users to select the language model they want to use, set the length of generated responses, and more. Additionally, ChatGPTUI provides a range of keyboard shortcuts for quick and easy navigation through the available options, making the tool more user-friendly and efficient.
10 |
11 | ## Install
12 |
13 | 1. Grab the pre-built binary from the [releases page](https://github.com/dwisiswant0/chatgptui/releases), unpack & run! Or
14 | 2. If you have [Go1.19+](https://go.dev/dl/) compiler installed & configured:
15 |
16 | ```bash
17 | go install github.com/dwisiswant0/chatgptui@latest
18 | ```
19 |
20 | ## Usage
21 |
22 | Here are all the options it supports.
23 |
24 | ```console
25 | $ chatgptui -h
26 |
27 | ChatGPTUI 🤖 v1.0.0
28 | --
29 | ChatGPT with Textual User Interface
30 | made with 💖 by dw1
31 |
32 | Usage: chatgptui [options]
33 |
34 | Options:
35 | -e, --edit Edit configuration
36 | -l, --list List all supported OpenAI model
37 | --rm Remove configuration
38 | -V, --version Show current version
39 |
40 | Examples:
41 | chatgptui
42 | chatgptui --edit
43 | ```
44 |
45 | ### Interactive
46 |
47 | The TUI mode of the ChatGPT tool can be accessed by running the **`chatgptui`** command without any additional flags. Upon executing the command, you will be prompted to provide the necessary configuration inputs that are required to use the language model.
48 |
49 | The configuration inputs may include parameters such as the OpenAI API key, model, maximum length of the generated text, etc. These settings can be adjusted to tailor the output generated by the model according to your requirements.
50 |
51 | Once you have provided the required configuration inputs, the system will direct you to the chat view model. Here, you can start generating responses by entering your prompts into the interface. The chat view model displays the conversation history, making it easier to keep track of the ongoing conversation.
52 |
53 | #### Shortcuts
54 |
55 | To improve the user experience and make ChatGPTUI more user-friendly, the tool offers a range of keyboard shortcuts that can be used in both the configuration and chat view models. These shortcuts allow you to perform various actions quickly, and make it easier to use the tool and interact with the language model.
56 |
57 | * `↑`/`↓`: Use the up and down arrow keys to navigate through the options in the view model.
58 | * `ctrl+c`/`esc`: Use these keyboard combinations to quit the ChatGPTUI.
59 | * `tab`: Use this key to fill in the default value for a field in the configuration view model.
60 |
61 | In addition to those shortcuts, ChatGPTUI also provides a convenient way to clear the chat history. Simply type `/clear` or `/c` in the text area of the chat view model to clear the chat history and start a new conversation.
62 |
63 | Those keyboard shortcuts can help you streamline your workflow & increase your productivity, and save your time & make the ChatGPTUI tool more convenient to use. They allow you to navigate through the available options quickly, fill in default values easily, and clear the chat history with just a few keystrokes, improving your overall experience with the tool!
64 |
65 | #### Configurations
66 |
67 | When you run the **`chatgptui`** command, it will immediately direct you to the chat view model, where you can start chatting with the language model. The chat view model will display the conversation history, and you can type in your prompt to generate a response from the model.
68 |
69 | If you want to change any of the configuration settings, such as the model or temperature, you can use the `-e`/`--edit` flag. This will redirect you to the configuration view model, where you can modify the necessary fields according to your needs. Once you've updated the configuration, you can return to the chat view model to continue chatting with the updated settings.
70 |
71 | ```bash
72 | chatgptui --edit
73 | ```
74 |
75 | In case you want to remove the configuration entirely, you can use the `--rm` flag. This will delete the configuration and all associated data, allowing you to start fresh with a new configuration.
76 |
77 | ```bash
78 | chatgptui --rm
79 | ```
80 |
81 | #### Models
82 |
83 | ChatGPTUI provides support for multiple OpenAI language models, each with its own unique set of features and capabilities. To view a list of all the available models that you can choose from when configuring the language model, you can use the `-l`/`--list` flag.
84 |
85 | Using the `-l` flag will display a list of all the OpenAI models that are currently supported by ChatGPTUI.
86 |
87 | ```console
88 | $ chatgptui --list
89 | gpt-4-32k-0314
90 | gpt-4-32k
91 | gpt-4-0314
92 | gpt-4
93 | gpt-3.5-turbo-0301
94 | gpt-3.5-turbo
95 | text-davinci-003
96 | text-davinci-002
97 | text-curie-001
98 | text-babbage-001
99 | text-ada-001
100 | text-davinci-001
101 | davinci-instruct-beta
102 | davinci
103 | curie-instruct-beta
104 | curie
105 | ada
106 | babbage
107 | code-davinci-002
108 | code-cushman-001
109 | code-davinci-001
110 | ```
111 |
112 | By viewing this list, you can easily select the model that best fits your needs, based on its capabilities and performance. Once you have selected the model you want to use, you can configure it according to your requirements using the configuration view model.
113 |
114 | ## Acknowledments
115 |
116 | **ChatGPTUI** built with [sashabaranov/go-openai](https://github.com/sashabaranov/go-openai), and [bubbles](https://github.com/charmbracelet/bubbles), [bubbletea](https://github.com/charmbracelet/bubbletea) & [lipgloss](https://github.com/charmbracelet/lipgloss) by [charmbracelet](https://github.com/charmbracelet).
117 |
118 | ## License
119 |
120 | Contributions are welcome!
121 |
122 | **ChatGPTUI** released under MIT. See `LICENSE` file.
123 |
124 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3 | github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
4 | github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
5 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
6 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
7 | github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI=
8 | github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74=
9 | github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
10 | github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps=
11 | github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM=
12 | github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
13 | github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
14 | github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
15 | github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
16 | github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
17 | github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
22 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
23 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
24 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
25 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
26 | github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
27 | github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
28 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
29 | github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
30 | github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
31 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
32 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
33 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
34 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
35 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
36 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
37 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
38 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
39 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
40 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
41 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
42 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
43 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
44 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
45 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
46 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
47 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
48 | github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
49 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
50 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
51 | github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
52 | github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
53 | github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8=
54 | github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
55 | github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
58 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
59 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
60 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
61 | github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
62 | github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
63 | github.com/sashabaranov/go-openai v1.5.7 h1:8DGgRG+P7yWixte5j720y6yiXgY3Hlgcd0gcpHdltfo=
64 | github.com/sashabaranov/go-openai v1.5.7/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
65 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
66 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
67 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
68 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
69 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
70 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
71 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
72 | golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
73 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
74 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
75 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
76 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
77 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
79 | golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
80 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
81 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
82 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
83 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
84 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
85 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
86 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
87 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
88 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
89 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
90 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
91 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
92 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
93 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
94 |
--------------------------------------------------------------------------------