Skip to content

Commit

Permalink
Improve the how command UI (#2559)
Browse files Browse the repository at this point in the history
  • Loading branch information
sverdlov93 committed May 31, 2024
1 parent f490b15 commit ced8359
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 36 deletions.
2 changes: 1 addition & 1 deletion docs/general/ai/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package ai
var Usage = []string{"how"}

func GetDescription() string {
return "Ask questions about JFrog CLI commands and their usage."
return "This AI-based interface converts your natural language inputs into fully functional JFrog CLI commands. This is an interactive command that accepts no arguments."
}
90 changes: 55 additions & 35 deletions general/ai/cli.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
package ai

import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-core/v2/utils/ioutils"
"github.com/jfrog/jfrog-cli/utils/cliutils"
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/jfrog/jfrog-client-go/http/httpclient"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"github.com/urfave/cli"
"io"
"net/http"
"os"
"strings"
)

type ApiCommand string

const (
cliAiApiPath = "https://cli-ai.jfrog.info/"
questionApi ApiCommand = "ask"
feedbackApi ApiCommand = "feedback"
cliAiApiPath = "https://cli-ai-app.jfrog.info/"
apiPrefix = "api/"
questionApi ApiCommand = apiPrefix + "ask"
feedbackApi ApiCommand = apiPrefix + "feedback"
)

type questionBody struct {
type QuestionBody struct {
Question string `json:"question"`
}

type feedbackBody struct {
questionBody
type FeedbackBody struct {
QuestionBody
LlmAnswer string `json:"llm_answer"`
IsAccurate bool `json:"is_accurate"`
ExpectedAnswer string `json:"expected_answer"`
Expand All @@ -40,47 +43,63 @@ func HowCmd(c *cli.Context) error {
if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil {
return err
}
if c.NArg() < 1 {
if c.NArg() > 0 {
return cliutils.WrongNumberOfArgumentsHandler(c)
}
log.Output(coreutils.PrintTitle("This AI-based interface converts your natural language inputs into fully functional JFrog CLI commands.\n" +
"NOTE: This is a beta version and it supports mostly Artifactory and Xray commands.\n"))

args := cliutils.ExtractCommand(c)
question := questionBody{Question: fmt.Sprintf("How %s", strings.Join(args, " "))}
llmAnswer, err := askQuestion(question)
if err != nil {
return err
}
if strings.ToLower(llmAnswer) == "i dont know" {
log.Output("The current version of the AI model does not support this type of command yet.")
return nil
}
log.Output("AI generated JFrog CLI command:")
err = coreutils.PrintTable("", "", coreutils.PrintTitle(llmAnswer), false)
if err != nil {
return err
}
for {
var question string
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("🐸 Your request: ")
for {
// Ask the user for a question
scanner.Scan()
question = strings.TrimSpace(scanner.Text())
if question != "" {
// If the user entered a question, break the loop
break
}
}
questionBody := QuestionBody{Question: question}
llmAnswer, err := askQuestion(questionBody)
if err != nil {
return err
}

feedback := feedbackBody{questionBody: question, LlmAnswer: llmAnswer}
feedback.getUserFeedback()
if err = sendFeedback(feedback); err != nil {
return err
log.Output("🤖 Generated command: " + coreutils.PrintLink(llmAnswer) + "\n")
feedback := FeedbackBody{QuestionBody: questionBody, LlmAnswer: llmAnswer}
feedback.getUserFeedback()
if err = sendFeedback(feedback); err != nil {
return err
}
log.Output("\n" + coreutils.PrintComment("-------------------") + "\n")
}
log.Output("Thank you for your feedback!")
return nil
}

func (fb *feedbackBody) getUserFeedback() {
fb.IsAccurate = coreutils.AskYesNo(coreutils.PrintLink("Is the provided command accurate?"), true)
func (fb *FeedbackBody) getUserFeedback() {
fb.IsAccurate = coreutils.AskYesNo("Is the provided command accurate?", true)
if !fb.IsAccurate {
ioutils.ScanFromConsole("Please provide the exact command you expected (Example: 'jf rt u ...')", &fb.ExpectedAnswer, "")
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Please provide the exact command you expected (Example: 'jf rt u ...'): ")
for {
scanner.Scan()
expectedAnswer := strings.TrimSpace(scanner.Text())
if expectedAnswer != "" {
// If the user entered an expected answer, break and return
fb.ExpectedAnswer = expectedAnswer
return
}
}
}
}

func askQuestion(question questionBody) (response string, err error) {
func askQuestion(question QuestionBody) (response string, err error) {
return sendRequestToCliAiServer(question, questionApi)
}

func sendFeedback(feedback feedbackBody) (err error) {
func sendFeedback(feedback FeedbackBody) (err error) {
_, err = sendRequestToCliAiServer(feedback, feedbackApi)
return
}
Expand Down Expand Up @@ -123,7 +142,8 @@ func sendRequestToCliAiServer(content interface{}, apiCommand ApiCommand) (respo
}
}()
var body []byte
body, err = io.ReadAll(resp.Body)
// Limit size of response body to 10MB
body, err = io.ReadAll(io.LimitReader(resp.Body, 10*utils.SizeMiB))
if errorutils.CheckError(err) != nil {
return
}
Expand Down

0 comments on commit ced8359

Please sign in to comment.