From 0be6fe972435e8e646ffe83517b7396bef4eb50d Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 12 Nov 2023 03:09:56 -0800 Subject: [PATCH] Swap AI completions to be behind a dedicated config option and add docs on it --- README.md | 15 +++++++++++ backend/web/landing/www/img/aidemo.png | Bin 0 -> 37532 bytes client/client_test.go | 12 ++++++++- client/cmd/configGet.go | 12 +++++++++ client/cmd/configSet.go | 19 ++++++++++++++ client/cmd/install.go | 10 ++++++-- client/hctx/hctx.go | 2 ++ client/lib/goldens/TestTui-AiQuery-Disabled | 27 ++++++++++++++++++++ client/tui/tui.go | 2 +- 9 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 backend/web/landing/www/img/aidemo.png create mode 100644 client/lib/goldens/TestTui-AiQuery-Disabled diff --git a/README.md b/README.md index 4a430b2..4afcba3 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,19 @@ To update `hishtory` to the latest version, just run `hishtory update` to secure ### Advanced Features +
+AI Shell Assistance +If you are ever trying to figure out a bash command and searching your history isn't working, you can query ChatGPT by prefixing your query with `?`. For example, press `Control+R` and then type in `? list all files larger than 1MB`: + +![demo showing ChatGPT suggesting the right command](https://raw.githubusercontent.com/ddworken/hishtory/master/backend/web/landing/www/img/aidemo.png) + +If you would like to: +* Disable this, you can run `hishtory config-set ai-completion false` +* Run this with your own OpenAI API key (thereby ensuring that your queries do not pass through the centrally hosted hiSHtory server), you can run `export OPENAI_API_KEY='...'` + +
+ +
TUI key bindings The TUI (opened via `Control+R`) supports a number of key bindings: @@ -74,6 +87,8 @@ The TUI (opened via `Control+R`) supports a number of key bindings: | Shift + Left/Right | Scroll the table left/right | | Control+K | Delete the selected command | +Press `Control+H` to view a help page documenting these. +
diff --git a/backend/web/landing/www/img/aidemo.png b/backend/web/landing/www/img/aidemo.png new file mode 100644 index 0000000000000000000000000000000000000000..5c18a8b11d62b63487044d134b76db41037c4241 GIT binary patch literal 37532 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 bPCB7bXEMNa#-Q^U_;>fVhB8|5@w5K}Mo26I literal 0 HcmV?d00001 diff --git a/client/client_test.go b/client/client_test.go index 21c92b7..ec6c935 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1934,7 +1934,7 @@ func testTui_ai(t *testing.T) { require.NoError(t, err) // Test running an AI query - tester.RunInteractiveShell(t, `hishtory config-set beta-mode true`) + require.Equal(t, "true", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get ai-completion`))) out := captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{ {Keys: "hishtory SPACE tquery ENTER"}, // ExtraDelay since AI queries are debounced and thus slower @@ -1942,6 +1942,16 @@ func testTui_ai(t *testing.T) { }) out = stripTuiCommandPrefix(t, out) testutils.CompareGoldens(t, out, "TestTui-AiQuery") + + // Test that when it is disabled, no AI queries are run + tester.RunInteractiveShell(t, `hishtory config-set ai-completion false`) + out = captureTerminalOutput(t, tester, []string{ + "hishtory SPACE tquery ENTER", + "'?myQuery'", + }) + out = stripTuiCommandPrefix(t, out) + testutils.CompareGoldens(t, out, "TestTui-AiQuery-Disabled") + } func testControlR(t *testing.T, tester shellTester, shellName string, onlineStatus OnlineStatus) { diff --git a/client/cmd/configGet.go b/client/cmd/configGet.go index af47030..c3d47ae 100644 --- a/client/cmd/configGet.go +++ b/client/cmd/configGet.go @@ -50,6 +50,17 @@ var getFilterDuplicateCommandsCmd = &cobra.Command{ }, } +var getEnableAiCompletion = &cobra.Command{ + Use: "ai-completion", + Short: "Enable AI completion for searches starting with '?'", + Long: "Note that AI completion requests are sent to the shared hiSHtory backend and then to OpenAI. Requests are not logged, but still be careful not to put anything sensitive in queries.", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Println(config.AiCompletion) + }, +} + var getBetaModeCmd = &cobra.Command{ Use: "beta-mode", Short: "Enable beta-mode to opt-in to unreleased features", @@ -109,4 +120,5 @@ func init() { configGetCmd.AddCommand(getCustomColumnsCmd) configGetCmd.AddCommand(getBetaModeCmd) configGetCmd.AddCommand(getHighlightMatchesCmd) + configGetCmd.AddCommand(getEnableAiCompletion) } diff --git a/client/cmd/configSet.go b/client/cmd/configSet.go index 85bb0fe..ab6ca06 100644 --- a/client/cmd/configSet.go +++ b/client/cmd/configSet.go @@ -70,6 +70,24 @@ var setBetaModeCommand = &cobra.Command{ }, } +var setEnableAiCompletionCmd = &cobra.Command{ + Use: "ai-completion", + Short: "Enable AI completion for searches starting with '?'", + Long: "Note that AI completion requests are sent to the shared hiSHtory backend and then to OpenAI. Requests are not logged, but still be careful not to put anything sensitive in queries.", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + ValidArgs: []string{"true", "false"}, + Run: func(cmd *cobra.Command, args []string) { + val := args[0] + if val != "true" && val != "false" { + log.Fatalf("Unexpected config value %s, must be one of: true, false", val) + } + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.AiCompletion = (val == "true") + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + var setHighlightMatchesCmd = &cobra.Command{ Use: "highlight-matches", Short: "Enable highlight-matches to enable highlighting of matches in the search results", @@ -119,4 +137,5 @@ func init() { configSetCmd.AddCommand(setTimestampFormatCmd) configSetCmd.AddCommand(setBetaModeCommand) configSetCmd.AddCommand(setHighlightMatchesCmd) + configSetCmd.AddCommand(setEnableAiCompletionCmd) } diff --git a/client/cmd/install.go b/client/cmd/install.go index 2b3b71b..e0a3f01 100644 --- a/client/cmd/install.go +++ b/client/cmd/install.go @@ -198,8 +198,8 @@ func handleDbUpgrades(ctx context.Context) error { // Handles people running `hishtory update` from an old version of hishtory that // doesn't support certain config options that we now default to true. This ensures -// that upgrades get them enabled by default, but if someone has it explicitly disabled, -// we keep it that way. +// that we can customize the behavior for upgrades while still respecting the option +// if someone has it explicitly set. func handleUpgradedFeatures() error { configContents, err := hctx.GetConfigContents() if err != nil { @@ -218,6 +218,10 @@ func handleUpgradedFeatures() error { // highlighting is not yet configured, so enable it config.HighlightMatches = true } + if !strings.Contains(string(configContents), "ai_completion") { + // AI completion is not yet configured, disable it for upgrades since this is a new feature + config.AiCompletion = false + } return hctx.SetConfig(&config) } @@ -562,6 +566,8 @@ func setup(userSecret string, isOffline bool) error { config.IsEnabled = true config.DeviceId = uuid.Must(uuid.NewRandom()).String() config.ControlRSearchEnabled = true + // TODO: Set config.HighlightMatches = true here, so that we enable highlighting by default + config.AiCompletion = true config.IsOffline = isOffline err := hctx.SetConfig(&config) if err != nil { diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index b74598a..2b55ade 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -197,6 +197,8 @@ type ClientConfig struct { BetaMode bool `json:"beta_mode"` // Whether to highlight matches in search results HighlightMatches bool `json:"highlight_matches"` + // Whether to enable AI completion + AiCompletion bool `json:"ai_completion"` } type CustomColumnDefinition struct { diff --git a/client/lib/goldens/TestTui-AiQuery-Disabled b/client/lib/goldens/TestTui-AiQuery-Disabled new file mode 100644 index 0000000..f9476c7 --- /dev/null +++ b/client/lib/goldens/TestTui-AiQuery-Disabled @@ -0,0 +1,27 @@ +Search Query: > ?myQuery + +┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Hostname CWD Timestamp Runtime Exit Code Command │ +│────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +hiSHtory: Search your shell history • ctrl+h help \ No newline at end of file diff --git a/client/tui/tui.go b/client/tui/tui.go index 6029412..7c09f49 100644 --- a/client/tui/tui.go +++ b/client/tui/tui.go @@ -471,7 +471,7 @@ func getRowsFromAiSuggestions(ctx context.Context, columnNames []string, query s func getRows(ctx context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) { db := hctx.GetDb(ctx) config := hctx.GetConf(ctx) - if config.BetaMode && strings.HasPrefix(query, "?") && len(query) > 1 { + if config.AiCompletion && !config.IsOffline && strings.HasPrefix(query, "?") && len(query) > 1 { return getRowsFromAiSuggestions(ctx, columnNames, query) } searchResults, err := lib.Search(ctx, db, query, numEntries)