mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add Cmd/Ctrl+Enter send setting (#25062)
Adds an Agents General setting to require Cmd/Ctrl+Enter before sending
chat messages. When enabled, plain Enter inserts a newline in agent chat
inputs while the send button remains available.
The preference is now persisted server-side through
`/api/v2/users/{user}/preferences`, alongside the existing user
preference settings, and is applied to both the create-agent input and
existing chat composer. Storybook and API coverage verify the setting,
keyboard behavior, validation, and persistence.
<details>
<summary>Coder Agents notes</summary>
Generated by Coder Agents from a Slack request. Dogfooded with
agent-browser against the Storybook settings and chat input stories.
</details>
This commit is contained in:
+48
-2
@@ -1257,10 +1257,20 @@ func (api *API) userPreferenceSettings(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
agentChatSendShortcut, err := api.Database.GetUserAgentChatSendShortcut(ctx, user.ID)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Error reading user preference settings.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UserPreferenceSettings{
|
||||
TaskNotificationAlertDismissed: taskAlertDismissed,
|
||||
ThinkingDisplayMode: sanitizeThinkingDisplayMode(thinkingMode),
|
||||
CodeDiffDisplayMode: sanitizeAgentDisplayMode(codeDiffMode),
|
||||
AgentChatSendShortcut: sanitizeAgentChatSendShortcut(agentChatSendShortcut),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1305,6 +1315,16 @@ func (api *API) putUserPreferenceSettings(rw http.ResponseWriter, r *http.Reques
|
||||
})
|
||||
return
|
||||
}
|
||||
if params.AgentChatSendShortcut != "" &&
|
||||
!slices.Contains(codersdk.ValidAgentChatSendShortcuts, params.AgentChatSendShortcut) {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid agent chat send shortcut.",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{Field: "agent_chat_send_shortcut", Detail: agentChatSendShortcutValidationDetail},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
var settings codersdk.UserPreferenceSettings
|
||||
err := api.Database.InTx(func(tx database.Store) error {
|
||||
var err error
|
||||
@@ -1356,6 +1376,23 @@ func (api *API) putUserPreferenceSettings(rw http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
settings.CodeDiffDisplayMode = sanitizeAgentDisplayMode(stored)
|
||||
}
|
||||
|
||||
if params.AgentChatSendShortcut != "" {
|
||||
updated, err := tx.UpdateUserAgentChatSendShortcut(ctx, database.UpdateUserAgentChatSendShortcutParams{
|
||||
UserID: user.ID,
|
||||
AgentChatSendShortcut: string(params.AgentChatSendShortcut),
|
||||
})
|
||||
if err != nil {
|
||||
return newUserPreferenceSettingsAPIError("Internal error updating agent chat send shortcut.", err)
|
||||
}
|
||||
settings.AgentChatSendShortcut = sanitizeAgentChatSendShortcut(updated)
|
||||
} else {
|
||||
stored, err := tx.GetUserAgentChatSendShortcut(ctx, user.ID)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return newUserPreferenceSettingsAPIError("Error reading agent chat send shortcut.", err)
|
||||
}
|
||||
settings.AgentChatSendShortcut = sanitizeAgentChatSendShortcut(stored)
|
||||
}
|
||||
return nil
|
||||
}, database.DefaultTXOptions().WithID("user_preference_settings"))
|
||||
if err != nil {
|
||||
@@ -1400,8 +1437,9 @@ func (e userPreferenceSettingsAPIError) Unwrap() error {
|
||||
}
|
||||
|
||||
const (
|
||||
thinkingDisplayModeValidationDetail = "must be one of: auto, preview, always_expanded, always_collapsed"
|
||||
agentDisplayModeValidationDetail = "must be one of: auto, always_expanded, always_collapsed"
|
||||
thinkingDisplayModeValidationDetail = "must be one of: auto, preview, always_expanded, always_collapsed"
|
||||
agentDisplayModeValidationDetail = "must be one of: auto, always_expanded, always_collapsed"
|
||||
agentChatSendShortcutValidationDetail = "must be one of: enter, modifier_enter"
|
||||
)
|
||||
|
||||
func sanitizeThinkingDisplayMode(raw string) codersdk.ThinkingDisplayMode {
|
||||
@@ -1420,6 +1458,14 @@ func sanitizeAgentDisplayMode(raw string) codersdk.AgentDisplayMode {
|
||||
return codersdk.AgentDisplayModeAuto
|
||||
}
|
||||
|
||||
func sanitizeAgentChatSendShortcut(raw string) codersdk.AgentChatSendShortcut {
|
||||
shortcut := codersdk.AgentChatSendShortcut(raw)
|
||||
if slices.Contains(codersdk.ValidAgentChatSendShortcuts, shortcut) {
|
||||
return shortcut
|
||||
}
|
||||
return codersdk.AgentChatSendShortcutEnter
|
||||
}
|
||||
|
||||
func isValidFontName(font codersdk.TerminalFontName) bool {
|
||||
return slices.Contains(codersdk.TerminalFontNames, font)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user