Add slash command prompt support

This commit is contained in:
Shantur Rathore
2026-01-08 17:41:29 +00:00
parent e9241a1b93
commit cb2966fb08
5 changed files with 264 additions and 53 deletions

View File

@@ -1,5 +1,5 @@
import { Dialog } from "@kobalte/core/dialog" import { Dialog } from "@kobalte/core/dialog"
import { Component, Show, createEffect } from "solid-js" import { Component, Show, createEffect, createSignal } from "solid-js"
import { alertDialogState, dismissAlertDialog } from "../stores/alerts" import { alertDialogState, dismissAlertDialog } from "../stores/alerts"
import type { AlertVariant, AlertDialogState } from "../stores/alerts" import type { AlertVariant, AlertDialogState } from "../stores/alerts"
@@ -27,8 +27,9 @@ const variantAccent: Record<AlertVariant, { badgeBg: string; badgeBorder: string
}, },
} }
function dismiss(confirmed: boolean, payload?: AlertDialogState | null) { function dismiss(confirmed: boolean, payload?: AlertDialogState | null, promptValue?: string) {
const current = payload ?? alertDialogState() const current = payload ?? alertDialogState()
if (current?.type === "confirm") { if (current?.type === "confirm") {
if (confirmed) { if (confirmed) {
current.onConfirm?.() current.onConfirm?.()
@@ -36,7 +37,23 @@ function dismiss(confirmed: boolean, payload?: AlertDialogState | null) {
current.onCancel?.() current.onCancel?.()
} }
current.resolve?.(confirmed) current.resolve?.(confirmed)
} else if (confirmed) { dismissAlertDialog()
return
}
if (current?.type === "prompt") {
if (confirmed) {
current.onConfirm?.()
current.resolvePrompt?.(promptValue ?? "")
} else {
current.onCancel?.()
current.resolvePrompt?.(null)
}
dismissAlertDialog()
return
}
if (confirmed) {
current?.onConfirm?.() current?.onConfirm?.()
} }
dismissAlertDialog() dismissAlertDialog()
@@ -60,9 +77,12 @@ const AlertDialog: Component = () => {
const accent = variantAccent[variant] const accent = variantAccent[variant]
const title = payload.title || accent.fallbackTitle const title = payload.title || accent.fallbackTitle
const isConfirm = payload.type === "confirm" const isConfirm = payload.type === "confirm"
const confirmLabel = payload.confirmLabel || (isConfirm ? "Confirm" : "OK") const isPrompt = payload.type === "prompt"
const confirmLabel = payload.confirmLabel || (isConfirm ? "Confirm" : isPrompt ? "Run" : "OK")
const cancelLabel = payload.cancelLabel || "Cancel" const cancelLabel = payload.cancelLabel || "Cancel"
const [inputValue, setInputValue] = createSignal(payload.inputDefaultValue ?? "")
return ( return (
<Dialog <Dialog
open open
@@ -98,27 +118,47 @@ const AlertDialog: Component = () => {
</div> </div>
</div> </div>
<div class="mt-6 flex justify-end gap-3"> <Show when={isPrompt}>
{isConfirm && ( <div class="mt-4">
<button <label class="text-xs font-medium text-muted uppercase tracking-wide">
type="button" {payload.inputLabel || "Arguments"}
class="button-secondary" </label>
onClick={() => dismiss(false, payload)} <input
> class="modal-search-input mt-2"
{cancelLabel} value={inputValue()}
</button> placeholder={payload.inputPlaceholder || ""}
)} onInput={(e) => setInputValue(e.currentTarget.value)}
<button onKeyDown={(e) => {
type="button" if (e.key === "Enter") {
class="button-primary" e.preventDefault()
ref={(el) => { dismiss(true, payload, inputValue())
primaryButtonRef = el }
}} }}
onClick={() => dismiss(true, payload)} />
> </div>
{confirmLabel} </Show>
</button>
</div> <div class="mt-6 flex justify-end gap-3">
{(isConfirm || isPrompt) && (
<button
type="button"
class="button-secondary"
onClick={() => dismiss(false, payload)}
>
{cancelLabel}
</button>
)}
<button
type="button"
class="button-primary"
ref={(el) => {
primaryButtonRef = el
}}
onClick={() => dismiss(true, payload, inputValue())}
>
{confirmLabel}
</button>
</div>
</Dialog.Content> </Dialog.Content>
</div> </div>
</Dialog.Portal> </Dialog.Portal>

View File

@@ -9,7 +9,8 @@ import type { Attachment } from "../types/attachment"
import type { Agent } from "../types/session" import type { Agent } from "../types/session"
import Kbd from "./kbd" import Kbd from "./kbd"
import { getActiveInstance } from "../stores/instances" import { getActiveInstance } from "../stores/instances"
import { agents, getSessionDraftPrompt, setSessionDraftPrompt, clearSessionDraftPrompt } from "../stores/sessions" import { agents, getSessionDraftPrompt, setSessionDraftPrompt, clearSessionDraftPrompt, executeCustomCommand } from "../stores/sessions"
import { getCommands } from "../stores/commands"
import { showAlertDialog } from "../stores/alerts" import { showAlertDialog } from "../stores/alerts"
import { getLogger } from "../lib/logger" import { getLogger } from "../lib/logger"
const log = getLogger("actions") const log = getLogger("actions")
@@ -36,6 +37,7 @@ export default function PromptInput(props: PromptInputProps) {
const [historyDraft, setHistoryDraft] = createSignal<string | null>(null) const [historyDraft, setHistoryDraft] = createSignal<string | null>(null)
const [, setIsFocused] = createSignal(false) const [, setIsFocused] = createSignal(false)
const [showPicker, setShowPicker] = createSignal(false) const [showPicker, setShowPicker] = createSignal(false)
const [pickerMode, setPickerMode] = createSignal<"mention" | "command">("mention")
const [searchQuery, setSearchQuery] = createSignal("") const [searchQuery, setSearchQuery] = createSignal("")
const [atPosition, setAtPosition] = createSignal<number | null>(null) const [atPosition, setAtPosition] = createSignal<number | null>(null)
const [isDragging, setIsDragging] = createSignal(false) const [isDragging, setIsDragging] = createSignal(false)
@@ -560,14 +562,28 @@ export default function PromptInput(props: PromptInputProps) {
const currentAttachments = attachments() const currentAttachments = attachments()
if (props.disabled || (!text && currentAttachments.length === 0)) return if (props.disabled || (!text && currentAttachments.length === 0)) return
const resolvedPrompt = resolvePastedPlaceholders(text, currentAttachments)
const isShellMode = mode() === "shell" const isShellMode = mode() === "shell"
// Slash command routing (match OpenCode TUI): only run if the command exists.
const isSlashCandidate = !isShellMode && text.startsWith("/")
const firstSpace = isSlashCandidate ? text.indexOf(" ") : -1
const commandToken = isSlashCandidate ? (firstSpace === -1 ? text : text.slice(0, firstSpace)) : ""
const commandName = isSlashCandidate ? commandToken.slice(1) : ""
const commandArgs = isSlashCandidate ? (firstSpace === -1 ? "" : text.slice(firstSpace + 1).trimStart()) : ""
const isKnownSlashCommand =
isSlashCandidate &&
commandName.length > 0 &&
getCommands(props.instanceId).some((cmd) => cmd.name === commandName)
const resolvedPrompt = isKnownSlashCommand ? text : resolvePastedPlaceholders(text, currentAttachments)
const historyEntry = resolvedPrompt
const refreshHistory = async () => { const refreshHistory = async () => {
try { try {
await addToHistory(props.instanceFolder, resolvedPrompt) await addToHistory(props.instanceFolder, historyEntry)
setHistory((prev) => { setHistory((prev) => {
const next = [resolvedPrompt, ...prev] const next = [historyEntry, ...prev]
if (next.length > HISTORY_LIMIT) { if (next.length > HISTORY_LIMIT) {
next.length = HISTORY_LIMIT next.length = HISTORY_LIMIT
} }
@@ -580,10 +596,18 @@ export default function PromptInput(props: PromptInputProps) {
} }
clearPrompt() clearPrompt()
clearAttachments(props.instanceId, props.sessionId)
setIgnoredAtPositions(new Set<number>()) // Ignore attachments for slash commands, but keep them for next prompt.
setPasteCount(0) if (!isKnownSlashCommand) {
setImageCount(0) clearAttachments(props.instanceId, props.sessionId)
setPasteCount(0)
setImageCount(0)
setIgnoredAtPositions(new Set<number>())
} else {
syncAttachmentCounters("", currentAttachments)
setIgnoredAtPositions(new Set<number>())
}
setHistoryDraft(null) setHistoryDraft(null)
try { try {
@@ -593,6 +617,8 @@ export default function PromptInput(props: PromptInputProps) {
} else { } else {
await props.onSend(resolvedPrompt, []) await props.onSend(resolvedPrompt, [])
} }
} else if (isKnownSlashCommand) {
await executeCustomCommand(props.instanceId, props.sessionId, commandName, commandArgs)
} else { } else {
await props.onSend(resolvedPrompt, currentAttachments) await props.onSend(resolvedPrompt, currentAttachments)
} }
@@ -677,11 +703,27 @@ export default function PromptInput(props: PromptInputProps) {
setHistoryDraft(null) setHistoryDraft(null)
const cursorPos = target.selectionStart const cursorPos = target.selectionStart
// Slash command picker (only when editing the command token: "/<query>")
if (value.startsWith("/") && cursorPos >= 1) {
const firstWhitespaceIndex = value.slice(1).search(/\s/)
const tokenEnd = firstWhitespaceIndex === -1 ? value.length : firstWhitespaceIndex + 1
if (cursorPos <= tokenEnd) {
setPickerMode("command")
setAtPosition(0)
setSearchQuery(value.substring(1, cursorPos))
setShowPicker(true)
return
}
}
const textBeforeCursor = value.substring(0, cursorPos) const textBeforeCursor = value.substring(0, cursorPos)
const lastAtIndex = textBeforeCursor.lastIndexOf("@") const lastAtIndex = textBeforeCursor.lastIndexOf("@")
const previousAtPosition = atPosition() const previousAtPosition = atPosition()
if (lastAtIndex === -1) { if (lastAtIndex === -1) {
setIgnoredAtPositions(new Set<number>()) setIgnoredAtPositions(new Set<number>())
} else if (previousAtPosition !== null && lastAtIndex !== previousAtPosition) { } else if (previousAtPosition !== null && lastAtIndex !== previousAtPosition) {
@@ -698,6 +740,7 @@ export default function PromptInput(props: PromptInputProps) {
if (!hasSpace && cursorPos === lastAtIndex + textAfterAt.length + 1) { if (!hasSpace && cursorPos === lastAtIndex + textAfterAt.length + 1) {
if (!ignoredAtPositions().has(lastAtIndex)) { if (!ignoredAtPositions().has(lastAtIndex)) {
setPickerMode("mention")
setAtPosition(lastAtIndex) setAtPosition(lastAtIndex)
setSearchQuery(textAfterAt) setSearchQuery(textAfterAt)
setShowPicker(true) setShowPicker(true)
@@ -716,9 +759,30 @@ export default function PromptInput(props: PromptInputProps) {
| { | {
type: "file" type: "file"
file: { path: string; relativePath?: string; isGitFile: boolean; isDirectory?: boolean } file: { path: string; relativePath?: string; isGitFile: boolean; isDirectory?: boolean }
}, }
| { type: "command"; command: { name: string; description?: string } },
) { ) {
if (item.type === "agent") { if (item.type === "command") {
const name = item.command.name
const currentPrompt = prompt()
const afterSlash = currentPrompt.slice(1)
const firstWhitespaceIndex = afterSlash.search(/\s/)
const tokenEnd = firstWhitespaceIndex === -1 ? currentPrompt.length : firstWhitespaceIndex + 1
const before = ""
const after = currentPrompt.substring(tokenEnd)
const newPrompt = before + `/${name} ` + after
setPrompt(newPrompt)
setTimeout(() => {
if (textareaRef) {
const newCursorPos = `/${name} `.length
textareaRef.setSelectionRange(newCursorPos, newCursorPos)
textareaRef.focus()
}
}, 0)
} else if (item.type === "agent") {
const agentName = item.agent.name const agentName = item.agent.name
const existingAttachments = attachments() const existingAttachments = attachments()
const alreadyAttached = existingAttachments.some( const alreadyAttached = existingAttachments.some(
@@ -822,7 +886,7 @@ export default function PromptInput(props: PromptInputProps) {
function handlePickerClose() { function handlePickerClose() {
const pos = atPosition() const pos = atPosition()
if (pos !== null) { if (pickerMode() === "mention" && pos !== null) {
setIgnoredAtPositions((prev) => new Set(prev).add(pos)) setIgnoredAtPositions((prev) => new Set(prev).add(pos))
} }
setShowPicker(false) setShowPicker(false)
@@ -981,9 +1045,11 @@ export default function PromptInput(props: PromptInputProps) {
<Show when={showPicker() && instance()}> <Show when={showPicker() && instance()}>
<UnifiedPicker <UnifiedPicker
open={showPicker()} open={showPicker()}
mode={pickerMode()}
onClose={handlePickerClose} onClose={handlePickerClose}
onSelect={handlePickerSelect} onSelect={handlePickerSelect}
agents={instanceAgents()} agents={instanceAgents()}
commands={getCommands(props.instanceId)}
instanceClient={instance()!.client} instanceClient={instance()!.client}
searchQuery={searchQuery()} searchQuery={searchQuery()}
textareaRef={textareaRef} textareaRef={textareaRef}

View File

@@ -1,5 +1,6 @@
import { Component, createSignal, createEffect, For, Show, onCleanup } from "solid-js" import { Component, createSignal, createEffect, createMemo, For, Show, onCleanup } from "solid-js"
import type { Agent } from "../types/session" import type { Agent } from "../types/session"
import type { Command as SDKCommand } from "@opencode-ai/sdk/v2"
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client" import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
import { serverApi } from "../lib/api-client" import { serverApi } from "../lib/api-client"
import { getLogger } from "../lib/logger" import { getLogger } from "../lib/logger"
@@ -67,13 +68,18 @@ function mapEntriesToFileItems(entries: { path: string; type: "file" | "director
}) })
} }
type PickerItem = { type: "agent"; agent: Agent } | { type: "file"; file: FileItem } type PickerItem =
| { type: "agent"; agent: Agent }
| { type: "file"; file: FileItem }
| { type: "command"; command: SDKCommand }
interface UnifiedPickerProps { interface UnifiedPickerProps {
open: boolean open: boolean
mode?: "mention" | "command"
onSelect: (item: PickerItem) => void onSelect: (item: PickerItem) => void
onClose: () => void onClose: () => void
agents: Agent[] agents: Agent[]
commands?: SDKCommand[]
instanceClient: OpencodeClient | null instanceClient: OpencodeClient | null
searchQuery: string searchQuery: string
textareaRef?: HTMLTextAreaElement textareaRef?: HTMLTextAreaElement
@@ -81,6 +87,8 @@ interface UnifiedPickerProps {
} }
const UnifiedPicker: Component<UnifiedPickerProps> = (props) => { const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
const mode = () => props.mode ?? "mention"
const [files, setFiles] = createSignal<FileItem[]>([]) const [files, setFiles] = createSignal<FileItem[]>([])
const [filteredAgents, setFilteredAgents] = createSignal<Agent[]>([]) const [filteredAgents, setFilteredAgents] = createSignal<Agent[]>([])
const [selectedIndex, setSelectedIndex] = createSignal(0) const [selectedIndex, setSelectedIndex] = createSignal(0)
@@ -246,6 +254,11 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
return return
} }
if (mode() !== "mention") {
// Command mode doesn't use file snapshots.
return
}
const workspaceChanged = lastWorkspaceId !== props.workspaceId const workspaceChanged = lastWorkspaceId !== props.workspaceId
const queryChanged = lastQuery !== props.searchQuery const queryChanged = lastQuery !== props.searchQuery
@@ -262,6 +275,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
createEffect(() => { createEffect(() => {
if (!props.open) return if (!props.open) return
if (mode() !== "mention") return
const query = props.searchQuery.toLowerCase() const query = props.searchQuery.toLowerCase()
const filtered = query const filtered = query
@@ -275,8 +289,25 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
setFilteredAgents(filtered) setFilteredAgents(filtered)
}) })
const filteredCommands = createMemo(() => {
if (mode() !== "command") return []
const q = props.searchQuery.trim().toLowerCase()
const source = props.commands ?? []
if (!q) return source
return source.filter((cmd) => {
const nameMatch = cmd.name.toLowerCase().includes(q)
const descMatch = (cmd.description ?? "").toLowerCase().includes(q)
return nameMatch || descMatch
})
})
const allItems = (): PickerItem[] => { const allItems = (): PickerItem[] => {
const items: PickerItem[] = [] const items: PickerItem[] = []
if (mode() === "command") {
filteredCommands().forEach((command) => items.push({ type: "command", command }))
return items
}
filteredAgents().forEach((agent) => items.push({ type: "agent", agent })) filteredAgents().forEach((agent) => items.push({ type: "agent", agent }))
files().forEach((file) => items.push({ type: "file", file })) files().forEach((file) => items.push({ type: "file", file }))
return items return items
@@ -329,9 +360,10 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
} }
}) })
const commandCount = () => filteredCommands().length
const agentCount = () => filteredAgents().length const agentCount = () => filteredAgents().length
const fileCount = () => files().length const fileCount = () => files().length
const isLoading = () => loadingState() !== "idle" const isLoading = () => mode() === "mention" && loadingState() !== "idle"
const loadingMessage = () => { const loadingMessage = () => {
if (loadingState() === "search") { if (loadingState() === "search") {
return "Searching..." return "Searching..."
@@ -351,7 +383,9 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
> >
<div class="dropdown-header"> <div class="dropdown-header">
<div class="dropdown-header-title"> <div class="dropdown-header-title">
Select Agent or File <Show when={mode() === "command"} fallback={"Select Agent or File"}>
Select Command
</Show>
<Show when={isLoading()}> <Show when={isLoading()}>
<span class="ml-2">{loadingMessage()}</span> <span class="ml-2">{loadingMessage()}</span>
</Show> </Show>
@@ -359,11 +393,41 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
</div> </div>
<div ref={scrollContainerRef} class="dropdown-content max-h-60"> <div ref={scrollContainerRef} class="dropdown-content max-h-60">
<Show when={agentCount() === 0 && fileCount() === 0}> <Show when={(mode() === "command" ? commandCount() === 0 : agentCount() === 0 && fileCount() === 0)}>
<div class="dropdown-empty">No results found</div> <div class="dropdown-empty">No results found</div>
</Show> </Show>
<Show when={agentCount() > 0}> <Show when={mode() === "command" && commandCount() > 0}>
<div class="dropdown-section-header">COMMANDS</div>
<For each={filteredCommands()}>
{(command) => {
const itemIndex = allItems().findIndex((item) => item.type === "command" && item.command.name === command.name)
return (
<div
class={`dropdown-item ${itemIndex === selectedIndex() ? "dropdown-item-highlight" : ""}`}
data-picker-selected={itemIndex === selectedIndex()}
onClick={() => handleSelect({ type: "command", command })}
>
<div class="flex items-start gap-2">
<svg class="dropdown-icon-accent h-4 w-4 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<div class="flex-1">
<div class="text-sm font-medium">/{command.name}</div>
<Show when={command.description}>
<div class="mt-0.5 text-xs" style="color: var(--text-muted)">
{(command.description ?? "").length > 80 ? (command.description ?? "").slice(0, 80) + "..." : command.description}
</div>
</Show>
</div>
</div>
</div>
)
}}
</For>
</Show>
<Show when={mode() === "mention" && agentCount() > 0}>
<div class="dropdown-section-header"> <div class="dropdown-section-header">
AGENTS AGENTS
</div> </div>
@@ -418,7 +482,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
</For> </For>
</Show> </Show>
<Show when={fileCount() > 0}> <Show when={mode() === "mention" && fileCount() > 0}>
<div class="dropdown-section-header"> <div class="dropdown-section-header">
FILES FILES
</div> </div>

View File

@@ -1,6 +1,6 @@
import type { Command } from "./commands" import type { Command } from "./commands"
import type { Command as SDKCommand } from "@opencode-ai/sdk" import type { Command as SDKCommand } from "@opencode-ai/sdk"
import { showAlertDialog } from "../stores/alerts" import { showAlertDialog, showPromptDialog } from "../stores/alerts"
import { activeSessionId, executeCustomCommand } from "../stores/sessions" import { activeSessionId, executeCustomCommand } from "../stores/sessions"
import { getLogger } from "./logger" import { getLogger } from "./logger"
@@ -11,15 +11,29 @@ export function commandRequiresArguments(template?: string): boolean {
return /\$(?:\d+|ARGUMENTS)/.test(template) return /\$(?:\d+|ARGUMENTS)/.test(template)
} }
export function promptForCommandArguments(command: SDKCommand): string | null { export async function promptForCommandArguments(command: SDKCommand): Promise<string | null> {
if (!commandRequiresArguments(command.template)) { if (!commandRequiresArguments(command.template)) {
return "" return ""
} }
const input = window.prompt(`Arguments for /${command.name}`, "")
if (input === null) { try {
return await showPromptDialog(`Arguments for /${command.name}`, {
title: "Custom command",
variant: "info",
inputLabel: "Arguments",
inputPlaceholder: "e.g. foo bar",
inputDefaultValue: "",
confirmLabel: "Run",
cancelLabel: "Cancel",
})
} catch (error) {
log.error("Failed to prompt for command arguments", error)
showAlertDialog("Failed to open arguments prompt.", {
title: "Command arguments",
variant: "error",
})
return null return null
} }
return input
} }
function formatCommandLabel(name: string): string { function formatCommandLabel(name: string): string {
@@ -43,11 +57,11 @@ export function buildCustomCommandEntries(instanceId: string, commands: SDKComma
}) })
return return
} }
const args = promptForCommandArguments(cmd)
if (args === null) {
return
}
try { try {
const args = await promptForCommandArguments(cmd)
if (args === null) {
return
}
await executeCustomCommand(instanceId, sessionId, cmd.name, args) await executeCustomCommand(instanceId, sessionId, cmd.name, args)
} catch (error) { } catch (error) {
log.error("Failed to run custom command", error) log.error("Failed to run custom command", error)

View File

@@ -3,7 +3,7 @@ import { createSignal } from "solid-js"
export type AlertVariant = "info" | "warning" | "error" export type AlertVariant = "info" | "warning" | "error"
export type AlertDialogState = { export type AlertDialogState = {
type?: "alert" | "confirm" type?: "alert" | "confirm" | "prompt"
title?: string title?: string
message: string message: string
detail?: string detail?: string
@@ -12,7 +12,17 @@ export type AlertDialogState = {
cancelLabel?: string cancelLabel?: string
onConfirm?: () => void onConfirm?: () => void
onCancel?: () => void onCancel?: () => void
// prompt-only
inputLabel?: string
inputPlaceholder?: string
inputDefaultValue?: string
// confirm-only
resolve?: (value: boolean) => void resolve?: (value: boolean) => void
// prompt-only
resolvePrompt?: (value: string | null) => void
} }
const [alertDialogState, setAlertDialogState] = createSignal<AlertDialogState | null>(null) const [alertDialogState, setAlertDialogState] = createSignal<AlertDialogState | null>(null)
@@ -39,6 +49,23 @@ export function showConfirmDialog(message: string, options?: Omit<AlertDialogSta
}) })
} }
export function showPromptDialog(
message: string,
options?: Omit<AlertDialogState, "message" | "type" | "resolve" | "resolvePrompt">,
): Promise<string | null> {
const activeElement = typeof document !== "undefined" ? (document.activeElement as HTMLElement | null) : null
activeElement?.blur()
return new Promise<string | null>((resolvePrompt) => {
setAlertDialogState({
type: "prompt",
message,
...options,
resolvePrompt,
})
})
}
export function dismissAlertDialog() { export function dismissAlertDialog() {
setAlertDialogState(null) setAlertDialogState(null)
} }