├── .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 | --------------------------------------------------------------------------------