-
Notifications
You must be signed in to change notification settings - Fork 241
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
26ead66
commit 4a0a9a4
Showing
7 changed files
with
173 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package ai | ||
|
||
var Usage = []string{"how"} | ||
|
||
func GetDescription() string { | ||
return "Ask questions about JFrog CLI commands and their usage." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package ai | ||
|
||
import ( | ||
"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/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" | ||
"strings" | ||
) | ||
|
||
type ApiCommand string | ||
|
||
const ( | ||
cliAiApiPath = "https://cli-ai.jfrog.info/" | ||
questionApi ApiCommand = "ask" | ||
feedbackApi ApiCommand = "feedback" | ||
) | ||
|
||
type questionBody struct { | ||
Question string `json:"question"` | ||
} | ||
|
||
type feedbackBody struct { | ||
questionBody | ||
LlmAnswer string `json:"llm_answer"` | ||
IsAccurate bool `json:"is_accurate"` | ||
ExpectedAnswer string `json:"expected_answer"` | ||
} | ||
|
||
func HowCmd(c *cli.Context) error { | ||
if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { | ||
return err | ||
} | ||
if c.NArg() < 1 { | ||
return cliutils.WrongNumberOfArgumentsHandler(c) | ||
} | ||
|
||
args := cliutils.ExtractCommand(c) | ||
question := questionBody{Question: fmt.Sprintf("How %s", strings.Join(args, " "))} | ||
llmAnswer, err := askQuestion(question) | ||
if err != nil { | ||
return err | ||
} | ||
log.Output("AI generated JFrog CLI command:") | ||
err = coreutils.PrintTable("", "", coreutils.PrintTitle(llmAnswer), false) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
feedback := feedbackBody{questionBody: question, LlmAnswer: llmAnswer} | ||
feedback.getUserFeedback() | ||
if err = sendFeedback(feedback); err != nil { | ||
return err | ||
} | ||
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) | ||
if !fb.IsAccurate { | ||
ioutils.ScanFromConsole("Please provide the exact command you expected (Example: 'jf rt u ...')", &fb.ExpectedAnswer, "") | ||
} | ||
} | ||
|
||
func askQuestion(question questionBody) (response string, err error) { | ||
return sendRequestToCliAiServer(question, questionApi) | ||
} | ||
|
||
func sendFeedback(feedback feedbackBody) (err error) { | ||
_, err = sendRequestToCliAiServer(feedback, feedbackApi) | ||
return | ||
} | ||
|
||
func sendRequestToCliAiServer(content interface{}, apiCommand ApiCommand) (response string, err error) { | ||
contentBytes, err := json.Marshal(content) | ||
if errorutils.CheckError(err) != nil { | ||
return | ||
} | ||
client, err := httpclient.ClientBuilder().Build() | ||
if errorutils.CheckError(err) != nil { | ||
return | ||
} | ||
req, err := http.NewRequest(http.MethodPost, cliAiApiPath+string(apiCommand), bytes.NewBuffer(contentBytes)) | ||
if errorutils.CheckError(err) != nil { | ||
return | ||
} | ||
req.Header.Set("Content-Type", "application/json") | ||
log.Debug(fmt.Sprintf("Sending HTTP %s request to: %s", req.Method, req.URL)) | ||
resp, err := client.GetClient().Do(req) | ||
if errorutils.CheckError(err) != nil { | ||
return | ||
} | ||
if resp == nil { | ||
err = errorutils.CheckErrorf("received empty response from server") | ||
return | ||
} | ||
if err = errorutils.CheckResponseStatus(resp, http.StatusOK); err != nil { | ||
if resp.StatusCode == http.StatusInternalServerError { | ||
err = errorutils.CheckErrorf("AI model Endpoint is not available.\n" + err.Error()) | ||
} else if resp.StatusCode == http.StatusNotFound { | ||
err = errorutils.CheckErrorf("CLI-AI app server is no available. Note that the this command is supported while inside JFrog's internal network only.\n" + err.Error()) | ||
} | ||
return | ||
} | ||
if apiCommand == questionApi { | ||
defer func() { | ||
if resp.Body != nil { | ||
err = errors.Join(err, errorutils.CheckError(resp.Body.Close())) | ||
} | ||
}() | ||
var body []byte | ||
body, err = io.ReadAll(resp.Body) | ||
if errorutils.CheckError(err) != nil { | ||
return | ||
} | ||
response = string(body) | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters