Swap AI completions to be behind a dedicated config option and add docs on it

This commit is contained in:
David Dworken 2023-11-12 03:09:56 -08:00
parent aeadaf4e0d
commit 0be6fe9724
No known key found for this signature in database
9 changed files with 95 additions and 4 deletions

View File

@ -62,6 +62,19 @@ To update `hishtory` to the latest version, just run `hishtory update` to secure
### Advanced Features ### Advanced Features
<details>
<summary>AI Shell Assistance</summary>
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='...'`
</details>
<details> <details>
<summary>TUI key bindings</summary> <summary>TUI key bindings</summary>
The TUI (opened via `Control+R`) supports a number of 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 | | Shift + Left/Right | Scroll the table left/right |
| Control+K | Delete the selected command | | Control+K | Delete the selected command |
Press `Control+H` to view a help page documenting these.
</details> </details>
<details> <details>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1934,7 +1934,7 @@ func testTui_ai(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Test running an AI query // 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{ out := captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
{Keys: "hishtory SPACE tquery ENTER"}, {Keys: "hishtory SPACE tquery ENTER"},
// ExtraDelay since AI queries are debounced and thus slower // ExtraDelay since AI queries are debounced and thus slower
@ -1942,6 +1942,16 @@ func testTui_ai(t *testing.T) {
}) })
out = stripTuiCommandPrefix(t, out) out = stripTuiCommandPrefix(t, out)
testutils.CompareGoldens(t, out, "TestTui-AiQuery") 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) { func testControlR(t *testing.T, tester shellTester, shellName string, onlineStatus OnlineStatus) {

View File

@ -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{ var getBetaModeCmd = &cobra.Command{
Use: "beta-mode", Use: "beta-mode",
Short: "Enable beta-mode to opt-in to unreleased features", Short: "Enable beta-mode to opt-in to unreleased features",
@ -109,4 +120,5 @@ func init() {
configGetCmd.AddCommand(getCustomColumnsCmd) configGetCmd.AddCommand(getCustomColumnsCmd)
configGetCmd.AddCommand(getBetaModeCmd) configGetCmd.AddCommand(getBetaModeCmd)
configGetCmd.AddCommand(getHighlightMatchesCmd) configGetCmd.AddCommand(getHighlightMatchesCmd)
configGetCmd.AddCommand(getEnableAiCompletion)
} }

View File

@ -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{ var setHighlightMatchesCmd = &cobra.Command{
Use: "highlight-matches", Use: "highlight-matches",
Short: "Enable highlight-matches to enable highlighting of matches in the search results", Short: "Enable highlight-matches to enable highlighting of matches in the search results",
@ -119,4 +137,5 @@ func init() {
configSetCmd.AddCommand(setTimestampFormatCmd) configSetCmd.AddCommand(setTimestampFormatCmd)
configSetCmd.AddCommand(setBetaModeCommand) configSetCmd.AddCommand(setBetaModeCommand)
configSetCmd.AddCommand(setHighlightMatchesCmd) configSetCmd.AddCommand(setHighlightMatchesCmd)
configSetCmd.AddCommand(setEnableAiCompletionCmd)
} }

View File

@ -198,8 +198,8 @@ func handleDbUpgrades(ctx context.Context) error {
// Handles people running `hishtory update` from an old version of hishtory that // 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 // 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, // that we can customize the behavior for upgrades while still respecting the option
// we keep it that way. // if someone has it explicitly set.
func handleUpgradedFeatures() error { func handleUpgradedFeatures() error {
configContents, err := hctx.GetConfigContents() configContents, err := hctx.GetConfigContents()
if err != nil { if err != nil {
@ -218,6 +218,10 @@ func handleUpgradedFeatures() error {
// highlighting is not yet configured, so enable it // highlighting is not yet configured, so enable it
config.HighlightMatches = true 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) return hctx.SetConfig(&config)
} }
@ -562,6 +566,8 @@ func setup(userSecret string, isOffline bool) error {
config.IsEnabled = true config.IsEnabled = true
config.DeviceId = uuid.Must(uuid.NewRandom()).String() config.DeviceId = uuid.Must(uuid.NewRandom()).String()
config.ControlRSearchEnabled = true config.ControlRSearchEnabled = true
// TODO: Set config.HighlightMatches = true here, so that we enable highlighting by default
config.AiCompletion = true
config.IsOffline = isOffline config.IsOffline = isOffline
err := hctx.SetConfig(&config) err := hctx.SetConfig(&config)
if err != nil { if err != nil {

View File

@ -197,6 +197,8 @@ type ClientConfig struct {
BetaMode bool `json:"beta_mode"` BetaMode bool `json:"beta_mode"`
// Whether to highlight matches in search results // Whether to highlight matches in search results
HighlightMatches bool `json:"highlight_matches"` HighlightMatches bool `json:"highlight_matches"`
// Whether to enable AI completion
AiCompletion bool `json:"ai_completion"`
} }
type CustomColumnDefinition struct { type CustomColumnDefinition struct {

View File

@ -0,0 +1,27 @@
Search Query: > ?myQuery
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hostname CWD Timestamp Runtime Exit Code Command │
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
hiSHtory: Search your shell history • ctrl+h help

View File

@ -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) { func getRows(ctx context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) {
db := hctx.GetDb(ctx) db := hctx.GetDb(ctx)
config := hctx.GetConf(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) return getRowsFromAiSuggestions(ctx, columnNames, query)
} }
searchResults, err := lib.Search(ctx, db, query, numEntries) searchResults, err := lib.Search(ctx, db, query, numEntries)