Просмотр исходного кода

feat(app): add composer add menu with draft-preserving commands (#35711)

usrnk1 1 месяц назад
Родитель
Сommit
3cd9ee5a73

+ 110 - 20
packages/app/src/components/prompt-input.tsx

@@ -520,7 +520,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
 
   const setMode = (mode: "normal" | "shell") => {
   const setMode = (mode: "normal" | "shell") => {
     setStore("mode", mode)
     setStore("mode", mode)
-    setStore("popover", null)
+    setStore({ popover: null, slashMenu: false, slashMenuQuery: "" })
     requestAnimationFrame(() => editorRef?.focus())
     requestAnimationFrame(() => editorRef?.focus())
   }
   }
 
 
@@ -554,7 +554,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     },
     },
   ])
   ])
 
 
-  const closePopover = () => setStore("popover", null)
+  const closePopover = () => setStore({ popover: null, slashMenu: false, slashMenuQuery: "" })
 
 
   const resetHistoryNavigation = (force = false) => {
   const resetHistoryNavigation = (force = false) => {
     if (!force && (store.historyIndex < 0 || store.applyingHistory)) return
     if (!force && (store.historyIndex < 0 || store.applyingHistory)) return
@@ -800,17 +800,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
 
   const handleSlashSelect = (cmd: SlashCommand | undefined) => {
   const handleSlashSelect = (cmd: SlashCommand | undefined) => {
     if (!cmd) return
     if (!cmd) return
+    const menu = store.slashMenu
     closePopover()
     closePopover()
     const images = imageAttachments()
     const images = imageAttachments()
 
 
     if (cmd.type === "custom") {
     if (cmd.type === "custom") {
       const text = `/${cmd.trigger} `
       const text = `/${cmd.trigger} `
+      if (menu) {
+        editorRef.focus()
+        setCursorPosition(editorRef, 0)
+        addPart({ type: "text", content: text, start: 0, end: text.length })
+        focusEditorEnd()
+        return
+      }
       setEditorText(text)
       setEditorText(text)
       prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length)
       prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length)
       focusEditorEnd()
       focusEditorEnd()
       return
       return
     }
     }
 
 
+    if (menu) {
+      command.trigger(cmd.id, "slash")
+      return
+    }
+
     clearEditor()
     clearEditor()
     prompt.set([...DEFAULT_PROMPT, ...images], 0)
     prompt.set([...DEFAULT_PROMPT, ...images], 0)
     command.trigger(cmd.id, "slash")
     command.trigger(cmd.id, "slash")
@@ -1072,10 +1085,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
 
       if (atMatch) {
       if (atMatch) {
         atOnInput(atMatch[1])
         atOnInput(atMatch[1])
-        setStore("popover", "at")
+        setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" })
       } else if (slashMatch) {
       } else if (slashMatch) {
         slashOnInput(slashMatch[1])
         slashOnInput(slashMatch[1])
-        setStore("popover", "slash")
+        setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" })
       } else {
       } else {
         closePopover()
         closePopover()
       }
       }
@@ -1171,6 +1184,28 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     return true
     return true
   }
   }
 
 
+  const openCommands = () => {
+    const populated = prompt.dirty() || commentCount() > 0
+    requestAnimationFrame(() => {
+      if (!populated) {
+        if (!addPart({ type: "text", content: "/", start: 0, end: 0 })) return
+        slashOnInput("")
+        setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" })
+        return
+      }
+      slashOnInput("")
+      setStore({ popover: "slash", slashMenu: true, slashMenuQuery: "" })
+    })
+  }
+
+  const openContext = () => {
+    requestAnimationFrame(() => {
+      if (!addPart({ type: "text", content: "@", start: 0, end: 0 })) return
+      atOnInput("")
+      setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" })
+    })
+  }
+
   const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
   const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
     history.add(prompt, mode, mode === "shell" ? [] : historyComments())
     history.add(prompt, mode, mode === "shell" ? [] : historyComments())
   }
   }
@@ -1199,7 +1234,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         }
         }
 
 
         setStore("mode", "normal")
         setStore("mode", "normal")
-        setStore("popover", null)
+        closePopover()
         setStore("historyIndex", -1)
         setStore("historyIndex", -1)
         setStore("savedPrompt", null)
         setStore("savedPrompt", null)
         prompt.set(edit.prompt, promptLength(edit.prompt))
         prompt.set(edit.prompt, promptLength(edit.prompt))
@@ -1286,7 +1321,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         resetHistoryNavigation(true)
         resetHistoryNavigation(true)
       },
       },
       setMode: (mode) => setStore("mode", mode),
       setMode: (mode) => setStore("mode", mode),
-      setPopover: (popover) => setStore("popover", popover),
+      setPopover: (popover) => {
+        if (!popover) return closePopover()
+        setStore({ popover, slashMenu: false, slashMenuQuery: "" })
+      },
       newSessionWorktree: () => props.newSessionWorktree,
       newSessionWorktree: () => props.newSessionWorktree,
       onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
       onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
       shouldQueue: props.shouldQueue,
       shouldQueue: props.shouldQueue,
@@ -1325,7 +1363,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       const cursorPosition = getCursorPosition(editorRef)
       const cursorPosition = getCursorPosition(editorRef)
       if (cursorPosition === 0) {
       if (cursorPosition === 0) {
         setStore("mode", "shell")
         setStore("mode", "shell")
-        setStore("popover", null)
+        closePopover()
         event.preventDefault()
         event.preventDefault()
         return
         return
       }
       }
@@ -1460,6 +1498,29 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     }
     }
   }
   }
 
 
+  const handleSlashMenuKeyDown = (event: KeyboardEvent) => {
+    if (event.key === "Escape") {
+      closePopover()
+      requestAnimationFrame(() => editorRef.focus())
+      event.preventDefault()
+      return
+    }
+
+    if (event.key === "Tab") {
+      selectPopoverActive()
+      event.preventDefault()
+      return
+    }
+
+    const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
+    const nav = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter"
+    const ctrlNav = ctrl && (event.key === "n" || event.key === "p")
+    if (!nav && !ctrlNav) return
+    slashOnKeyDown(event)
+    if (event.key === "ArrowUp" || event.key === "ArrowDown" || ctrlNav) scrollSlashActiveIntoView()
+    event.preventDefault()
+  }
+
   const agentsLoading = () => props.controls.agents.loading
   const agentsLoading = () => props.controls.agents.loading
   const agentsShouldFadeIn = createMemo<boolean>((prev) => prev ?? agentsLoading())
   const agentsShouldFadeIn = createMemo<boolean>((prev) => prev ?? agentsLoading())
   const providersLoading = () => props.controls.model.loading
   const providersLoading = () => props.controls.model.loading
@@ -1527,6 +1588,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         slashActive={slashActive() ?? undefined}
         slashActive={slashActive() ?? undefined}
         setSlashActive={setSlashActive}
         setSlashActive={setSlashActive}
         onSlashSelect={handleSlashSelect}
         onSlashSelect={handleSlashSelect}
+        slashMenu={store.slashMenu}
+        slashMenuQuery={store.slashMenuQuery}
+        onSlashMenuInput={(value) => {
+          setStore("slashMenuQuery", value)
+          slashOnInput(value)
+        }}
+        onSlashMenuKeyDown={handleSlashMenuKeyDown}
         commandKeybind={command.keybind}
         commandKeybind={command.keybind}
         commandKeybindParts={command.keybindParts}
         commandKeybindParts={command.keybindParts}
         newLayoutDesigns={props.controls.newLayoutDesigns}
         newLayoutDesigns={props.controls.newLayoutDesigns}
@@ -1625,23 +1693,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     placement="top"
                     placement="top"
                     value={
                     value={
                       <>
                       <>
-                        {language.t("prompt.action.attachFile")}
+                        {language.t("prompt.menu.addImagesAndFiles")}
                         <KeybindV2 keys={command.keybindParts("file.attach")} variant="neutral" />
                         <KeybindV2 keys={command.keybindParts("file.attach")} variant="neutral" />
                       </>
                       </>
                     }
                     }
                   >
                   >
-                    <IconButton
-                      data-action="prompt-attach"
-                      type="button"
-                      icon="plus"
-                      variant="ghost"
-                      class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted"
-                      style={buttons()}
-                      onClick={pick}
-                      disabled={store.mode !== "normal"}
-                      tabIndex={store.mode === "normal" ? undefined : -1}
-                      aria-label={language.t("prompt.action.attachFile")}
-                    />
+                    <MenuV2 gutter={6} modal={false} placement="top-start">
+                      <MenuV2.Trigger
+                        as={IconButton}
+                        data-action="prompt-attach"
+                        type="button"
+                        icon="plus"
+                        variant="ghost"
+                        class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted"
+                        style={buttons()}
+                        disabled={store.mode !== "normal"}
+                        tabIndex={store.mode === "normal" ? undefined : -1}
+                        aria-label={language.t("prompt.menu.addImagesAndFiles")}
+                      />
+                      <MenuV2.Portal>
+                        <MenuV2.Content
+                          class="[&_[data-slot=menu-v2-item-shortcut]]:w-5 [&_[data-slot=menu-v2-item-shortcut]]:justify-center"
+                          style={{ "min-width": "180px" }}
+                        >
+                          <MenuV2.Item onSelect={pick} shortcut={command.keybind("file.attach")}>
+                            {language.t("prompt.menu.imagesAndFiles")}
+                          </MenuV2.Item>
+                          <MenuV2.Separator />
+                          <MenuV2.Item onSelect={openCommands} shortcut="/">
+                            {language.t("prompt.menu.commands")}
+                          </MenuV2.Item>
+                          <MenuV2.Item onSelect={openContext} shortcut="@">
+                            {language.t("prompt.menu.context")}
+                          </MenuV2.Item>
+                          <MenuV2.Item onSelect={() => setMode("shell")} shortcut="!">
+                            {language.t("prompt.menu.shellCommand")}
+                          </MenuV2.Item>
+                        </MenuV2.Content>
+                      </MenuV2.Portal>
+                    </MenuV2>
                   </TooltipV2>
                   </TooltipV2>
                   <Show when={showAgentControl()}>
                   <Show when={showAgentControl()}>
                     <ComposerAgentControl state={agentControlState()} />
                     <ComposerAgentControl state={agentControlState()} />

+ 18 - 0
packages/app/src/components/prompt-input/slash-popover.tsx

@@ -41,6 +41,10 @@ type PromptPopoverProps = {
   slashActive?: string
   slashActive?: string
   setSlashActive: (id: string) => void
   setSlashActive: (id: string) => void
   onSlashSelect: (item: SlashCommand) => void
   onSlashSelect: (item: SlashCommand) => void
+  slashMenu: boolean
+  slashMenuQuery: string
+  onSlashMenuInput: (value: string) => void
+  onSlashMenuKeyDown: (event: KeyboardEvent) => void
   commandKeybind: (id: string) => string | undefined
   commandKeybind: (id: string) => string | undefined
   commandKeybindParts: (id: string) => string[]
   commandKeybindParts: (id: string) => string[]
   newLayoutDesigns: boolean
   newLayoutDesigns: boolean
@@ -254,6 +258,20 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
             </Show>
             </Show>
           </Match>
           </Match>
           <Match when={props.popover === "slash"}>
           <Match when={props.popover === "slash"}>
+            <Show when={props.slashMenu}>
+              <div class="px-2 py-1">
+                <input
+                  ref={(el) => requestAnimationFrame(() => el.focus())}
+                  value={props.slashMenuQuery}
+                  onInput={(event) => props.onSlashMenuInput(event.currentTarget.value)}
+                  onKeyDown={props.onSlashMenuKeyDown}
+                  onMouseDown={(event) => event.stopPropagation()}
+                  aria-label={props.t("prompt.menu.commands")}
+                  placeholder="/"
+                  class="w-full bg-transparent outline-none text-[13px] leading-5 text-v2-text-text-base placeholder:text-v2-text-text-faint"
+                />
+              </div>
+            </Show>
             <Show
             <Show
               when={props.slashFlat.length > 0}
               when={props.slashFlat.length > 0}
               fallback={
               fallback={

+ 6 - 0
packages/app/src/components/prompt-input/transient-state.ts

@@ -4,6 +4,8 @@ import type { PromptHistoryEntry } from "./history"
 
 
 export type PromptInputTransientState = {
 export type PromptInputTransientState = {
   popover: "at" | "slash" | null
   popover: "at" | "slash" | null
+  slashMenu: boolean
+  slashMenuQuery: string
   historyIndex: number
   historyIndex: number
   savedPrompt: PromptHistoryEntry | null
   savedPrompt: PromptHistoryEntry | null
   placeholder: number
   placeholder: number
@@ -16,6 +18,8 @@ export type PromptInputTransientState = {
 function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTransientState>) {
 function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTransientState>) {
   setStore({
   setStore({
     popover: null,
     popover: null,
+    slashMenu: false,
+    slashMenuQuery: "",
     historyIndex: -1,
     historyIndex: -1,
     savedPrompt: null,
     savedPrompt: null,
     draggingType: null,
     draggingType: null,
@@ -28,6 +32,8 @@ function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTr
 export function createPromptInputTransientState(identity: Accessor<unknown>, placeholder: number) {
 export function createPromptInputTransientState(identity: Accessor<unknown>, placeholder: number) {
   const [store, setStore] = createStore<PromptInputTransientState>({
   const [store, setStore] = createStore<PromptInputTransientState>({
     popover: null,
     popover: null,
+    slashMenu: false,
+    slashMenuQuery: "",
     historyIndex: -1,
     historyIndex: -1,
     savedPrompt: null,
     savedPrompt: null,
     placeholder,
     placeholder,

+ 5 - 0
packages/app/src/i18n/ar.ts

@@ -267,6 +267,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "إزالة الملف النشط من السياق",
   "prompt.context.removeActiveFile": "إزالة الملف النشط من السياق",
   "prompt.context.removeFile": "إزالة الملف من السياق",
   "prompt.context.removeFile": "إزالة الملف من السياق",
   "prompt.action.attachFile": "إرفاق ملف",
   "prompt.action.attachFile": "إرفاق ملف",
+  "prompt.menu.addImagesAndFiles": "إضافة ملفات والمزيد",
+  "prompt.menu.imagesAndFiles": "الصور والملفات",
+  "prompt.menu.commands": "الأوامر",
+  "prompt.menu.context": "السياق",
+  "prompt.menu.shellCommand": "أمر shell",
   "prompt.attachment.remove": "إزالة المرفق",
   "prompt.attachment.remove": "إزالة المرفق",
   "prompt.action.send": "إرسال",
   "prompt.action.send": "إرسال",
   "prompt.action.stop": "توقف",
   "prompt.action.stop": "توقف",

+ 5 - 0
packages/app/src/i18n/br.ts

@@ -267,6 +267,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Remover arquivo ativo do contexto",
   "prompt.context.removeActiveFile": "Remover arquivo ativo do contexto",
   "prompt.context.removeFile": "Remover arquivo do contexto",
   "prompt.context.removeFile": "Remover arquivo do contexto",
   "prompt.action.attachFile": "Anexar arquivo",
   "prompt.action.attachFile": "Anexar arquivo",
+  "prompt.menu.addImagesAndFiles": "Adicionar arquivos e mais",
+  "prompt.menu.imagesAndFiles": "Imagens e arquivos",
+  "prompt.menu.commands": "Comandos",
+  "prompt.menu.context": "Contexto",
+  "prompt.menu.shellCommand": "Comando shell",
   "prompt.attachment.remove": "Remover anexo",
   "prompt.attachment.remove": "Remover anexo",
   "prompt.action.send": "Enviar",
   "prompt.action.send": "Enviar",
   "prompt.action.stop": "Parar",
   "prompt.action.stop": "Parar",

+ 5 - 0
packages/app/src/i18n/bs.ts

@@ -287,6 +287,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta",
   "prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta",
   "prompt.context.removeFile": "Ukloni datoteku iz konteksta",
   "prompt.context.removeFile": "Ukloni datoteku iz konteksta",
   "prompt.action.attachFile": "Priloži datoteku",
   "prompt.action.attachFile": "Priloži datoteku",
+  "prompt.menu.addImagesAndFiles": "Dodaj datoteke i više",
+  "prompt.menu.imagesAndFiles": "Slike i datoteke",
+  "prompt.menu.commands": "Komande",
+  "prompt.menu.context": "Kontekst",
+  "prompt.menu.shellCommand": "Shell naredba",
   "prompt.attachment.remove": "Ukloni prilog",
   "prompt.attachment.remove": "Ukloni prilog",
   "prompt.action.send": "Pošalji",
   "prompt.action.send": "Pošalji",
   "prompt.action.stop": "Zaustavi",
   "prompt.action.stop": "Zaustavi",

+ 5 - 0
packages/app/src/i18n/da.ts

@@ -285,6 +285,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
   "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
   "prompt.context.removeFile": "Fjern fil fra kontekst",
   "prompt.context.removeFile": "Fjern fil fra kontekst",
   "prompt.action.attachFile": "Vedhæft fil",
   "prompt.action.attachFile": "Vedhæft fil",
+  "prompt.menu.addImagesAndFiles": "Tilføj filer og mere",
+  "prompt.menu.imagesAndFiles": "Billeder og filer",
+  "prompt.menu.commands": "Kommandoer",
+  "prompt.menu.context": "Kontekst",
+  "prompt.menu.shellCommand": "Shell-kommando",
   "prompt.attachment.remove": "Fjern vedhæftning",
   "prompt.attachment.remove": "Fjern vedhæftning",
   "prompt.action.send": "Send",
   "prompt.action.send": "Send",
   "prompt.action.stop": "Stop",
   "prompt.action.stop": "Stop",

+ 5 - 0
packages/app/src/i18n/de.ts

@@ -272,6 +272,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen",
   "prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen",
   "prompt.context.removeFile": "Datei aus dem Kontext entfernen",
   "prompt.context.removeFile": "Datei aus dem Kontext entfernen",
   "prompt.action.attachFile": "Datei anhängen",
   "prompt.action.attachFile": "Datei anhängen",
+  "prompt.menu.addImagesAndFiles": "Dateien und mehr hinzufügen",
+  "prompt.menu.imagesAndFiles": "Bilder und Dateien",
+  "prompt.menu.commands": "Befehle",
+  "prompt.menu.context": "Kontext",
+  "prompt.menu.shellCommand": "Shell-Befehl",
   "prompt.attachment.remove": "Anhang entfernen",
   "prompt.attachment.remove": "Anhang entfernen",
   "prompt.action.send": "Senden",
   "prompt.action.send": "Senden",
   "prompt.action.stop": "Stopp",
   "prompt.action.stop": "Stopp",

+ 5 - 0
packages/app/src/i18n/en.ts

@@ -287,6 +287,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Remove active file from context",
   "prompt.context.removeActiveFile": "Remove active file from context",
   "prompt.context.removeFile": "Remove file from context",
   "prompt.context.removeFile": "Remove file from context",
   "prompt.action.attachFile": "Add files",
   "prompt.action.attachFile": "Add files",
+  "prompt.menu.addImagesAndFiles": "Add files and more",
+  "prompt.menu.imagesAndFiles": "Images and files",
+  "prompt.menu.commands": "Commands",
+  "prompt.menu.context": "Context",
+  "prompt.menu.shellCommand": "Shell command",
   "prompt.attachment.remove": "Remove attachment",
   "prompt.attachment.remove": "Remove attachment",
   "prompt.action.send": "Send",
   "prompt.action.send": "Send",
   "prompt.action.stop": "Stop",
   "prompt.action.stop": "Stop",

+ 5 - 0
packages/app/src/i18n/es.ts

@@ -286,6 +286,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Eliminar archivo activo del contexto",
   "prompt.context.removeActiveFile": "Eliminar archivo activo del contexto",
   "prompt.context.removeFile": "Eliminar archivo del contexto",
   "prompt.context.removeFile": "Eliminar archivo del contexto",
   "prompt.action.attachFile": "Adjuntar archivo",
   "prompt.action.attachFile": "Adjuntar archivo",
+  "prompt.menu.addImagesAndFiles": "Añadir archivos y más",
+  "prompt.menu.imagesAndFiles": "Imágenes y archivos",
+  "prompt.menu.commands": "Comandos",
+  "prompt.menu.context": "Contexto",
+  "prompt.menu.shellCommand": "Comando de shell",
   "prompt.attachment.remove": "Eliminar adjunto",
   "prompt.attachment.remove": "Eliminar adjunto",
   "prompt.action.send": "Enviar",
   "prompt.action.send": "Enviar",
   "prompt.action.stop": "Detener",
   "prompt.action.stop": "Detener",

+ 5 - 0
packages/app/src/i18n/fr.ts

@@ -267,6 +267,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Retirer le fichier actif du contexte",
   "prompt.context.removeActiveFile": "Retirer le fichier actif du contexte",
   "prompt.context.removeFile": "Retirer le fichier du contexte",
   "prompt.context.removeFile": "Retirer le fichier du contexte",
   "prompt.action.attachFile": "Joindre un fichier",
   "prompt.action.attachFile": "Joindre un fichier",
+  "prompt.menu.addImagesAndFiles": "Ajouter des fichiers et plus encore",
+  "prompt.menu.imagesAndFiles": "Images et fichiers",
+  "prompt.menu.commands": "Commandes",
+  "prompt.menu.context": "Contexte",
+  "prompt.menu.shellCommand": "Commande shell",
   "prompt.attachment.remove": "Supprimer la pièce jointe",
   "prompt.attachment.remove": "Supprimer la pièce jointe",
   "prompt.action.send": "Envoyer",
   "prompt.action.send": "Envoyer",
   "prompt.action.stop": "Arrêter",
   "prompt.action.stop": "Arrêter",

+ 5 - 0
packages/app/src/i18n/ja.ts

@@ -266,6 +266,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除",
   "prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除",
   "prompt.context.removeFile": "コンテキストからファイルを削除",
   "prompt.context.removeFile": "コンテキストからファイルを削除",
   "prompt.action.attachFile": "ファイルを添付",
   "prompt.action.attachFile": "ファイルを添付",
+  "prompt.menu.addImagesAndFiles": "ファイルなどを追加",
+  "prompt.menu.imagesAndFiles": "画像とファイル",
+  "prompt.menu.commands": "コマンド",
+  "prompt.menu.context": "コンテキスト",
+  "prompt.menu.shellCommand": "シェルコマンド",
   "prompt.attachment.remove": "添付ファイルを削除",
   "prompt.attachment.remove": "添付ファイルを削除",
   "prompt.action.send": "送信",
   "prompt.action.send": "送信",
   "prompt.action.stop": "停止",
   "prompt.action.stop": "停止",

+ 5 - 0
packages/app/src/i18n/ko.ts

@@ -254,6 +254,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거",
   "prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거",
   "prompt.context.removeFile": "컨텍스트에서 파일 제거",
   "prompt.context.removeFile": "컨텍스트에서 파일 제거",
   "prompt.action.attachFile": "파일 첨부",
   "prompt.action.attachFile": "파일 첨부",
+  "prompt.menu.addImagesAndFiles": "파일 및 기타 항목 추가",
+  "prompt.menu.imagesAndFiles": "이미지 및 파일",
+  "prompt.menu.commands": "명령어",
+  "prompt.menu.context": "컨텍스트",
+  "prompt.menu.shellCommand": "셸 명령",
   "prompt.attachment.remove": "첨부 파일 제거",
   "prompt.attachment.remove": "첨부 파일 제거",
   "prompt.action.send": "전송",
   "prompt.action.send": "전송",
   "prompt.action.stop": "중지",
   "prompt.action.stop": "중지",

+ 5 - 0
packages/app/src/i18n/no.ts

@@ -277,6 +277,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
   "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
   "prompt.context.removeFile": "Fjern fil fra kontekst",
   "prompt.context.removeFile": "Fjern fil fra kontekst",
   "prompt.action.attachFile": "Legg ved fil",
   "prompt.action.attachFile": "Legg ved fil",
+  "prompt.menu.addImagesAndFiles": "Legg til filer og mer",
+  "prompt.menu.imagesAndFiles": "Bilder og filer",
+  "prompt.menu.commands": "Kommandoer",
+  "prompt.menu.context": "Kontekst",
+  "prompt.menu.shellCommand": "Shell-kommando",
   "prompt.attachment.remove": "Fjern vedlegg",
   "prompt.attachment.remove": "Fjern vedlegg",
   "prompt.action.send": "Send",
   "prompt.action.send": "Send",
   "prompt.action.stop": "Stopp",
   "prompt.action.stop": "Stopp",

+ 5 - 0
packages/app/src/i18n/pl.ts

@@ -268,6 +268,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu",
   "prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu",
   "prompt.context.removeFile": "Usuń plik z kontekstu",
   "prompt.context.removeFile": "Usuń plik z kontekstu",
   "prompt.action.attachFile": "Załącz plik",
   "prompt.action.attachFile": "Załącz plik",
+  "prompt.menu.addImagesAndFiles": "Dodaj pliki i inne elementy",
+  "prompt.menu.imagesAndFiles": "Obrazy i pliki",
+  "prompt.menu.commands": "Polecenia",
+  "prompt.menu.context": "Kontekst",
+  "prompt.menu.shellCommand": "Polecenie powłoki",
   "prompt.attachment.remove": "Usuń załącznik",
   "prompt.attachment.remove": "Usuń załącznik",
   "prompt.action.send": "Wyślij",
   "prompt.action.send": "Wyślij",
   "prompt.action.stop": "Zatrzymaj",
   "prompt.action.stop": "Zatrzymaj",

+ 5 - 0
packages/app/src/i18n/ru.ts

@@ -286,6 +286,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Удалить активный файл из контекста",
   "prompt.context.removeActiveFile": "Удалить активный файл из контекста",
   "prompt.context.removeFile": "Удалить файл из контекста",
   "prompt.context.removeFile": "Удалить файл из контекста",
   "prompt.action.attachFile": "Прикрепить файл",
   "prompt.action.attachFile": "Прикрепить файл",
+  "prompt.menu.addImagesAndFiles": "Добавить файлы и другое",
+  "prompt.menu.imagesAndFiles": "Изображения и файлы",
+  "prompt.menu.commands": "Команды",
+  "prompt.menu.context": "Контекст",
+  "prompt.menu.shellCommand": "Команда оболочки",
   "prompt.attachment.remove": "Удалить вложение",
   "prompt.attachment.remove": "Удалить вложение",
   "prompt.action.send": "Отправить",
   "prompt.action.send": "Отправить",
   "prompt.action.stop": "Остановить",
   "prompt.action.stop": "Остановить",

+ 5 - 0
packages/app/src/i18n/th.ts

@@ -286,6 +286,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท",
   "prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท",
   "prompt.context.removeFile": "เอาไฟล์ออกจากบริบท",
   "prompt.context.removeFile": "เอาไฟล์ออกจากบริบท",
   "prompt.action.attachFile": "แนบไฟล์",
   "prompt.action.attachFile": "แนบไฟล์",
+  "prompt.menu.addImagesAndFiles": "เพิ่มไฟล์และอื่น ๆ",
+  "prompt.menu.imagesAndFiles": "รูปภาพและไฟล์",
+  "prompt.menu.commands": "คำสั่ง",
+  "prompt.menu.context": "บริบท",
+  "prompt.menu.shellCommand": "คำสั่งเชลล์",
   "prompt.attachment.remove": "เอาไฟล์แนบออก",
   "prompt.attachment.remove": "เอาไฟล์แนบออก",
   "prompt.action.send": "ส่ง",
   "prompt.action.send": "ส่ง",
   "prompt.action.stop": "หยุด",
   "prompt.action.stop": "หยุด",

+ 5 - 0
packages/app/src/i18n/tr.ts

@@ -291,6 +291,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar",
   "prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar",
   "prompt.context.removeFile": "Dosyayı bağlamdan çıkar",
   "prompt.context.removeFile": "Dosyayı bağlamdan çıkar",
   "prompt.action.attachFile": "Dosya ekle",
   "prompt.action.attachFile": "Dosya ekle",
+  "prompt.menu.addImagesAndFiles": "Dosya ve daha fazlasını ekle",
+  "prompt.menu.imagesAndFiles": "Görseller ve dosyalar",
+  "prompt.menu.commands": "Komutlar",
+  "prompt.menu.context": "Bağlam",
+  "prompt.menu.shellCommand": "Kabuk komutu",
   "prompt.attachment.remove": "Eki kaldır",
   "prompt.attachment.remove": "Eki kaldır",
   "prompt.action.send": "Gönder",
   "prompt.action.send": "Gönder",
   "prompt.action.stop": "Durdur",
   "prompt.action.stop": "Durdur",

+ 5 - 0
packages/app/src/i18n/uk.ts

@@ -288,6 +288,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "Видалити активний файл з контексту",
   "prompt.context.removeActiveFile": "Видалити активний файл з контексту",
   "prompt.context.removeFile": "Видалити файл з контексту",
   "prompt.context.removeFile": "Видалити файл з контексту",
   "prompt.action.attachFile": "Додати файли",
   "prompt.action.attachFile": "Додати файли",
+  "prompt.menu.addImagesAndFiles": "Додати файли та інше",
+  "prompt.menu.imagesAndFiles": "Зображення та файли",
+  "prompt.menu.commands": "Команди",
+  "prompt.menu.context": "Контекст",
+  "prompt.menu.shellCommand": "Команда оболонки",
   "prompt.attachment.remove": "Видалити вкладення",
   "prompt.attachment.remove": "Видалити вкладення",
   "prompt.action.send": "Надіслати",
   "prompt.action.send": "Надіслати",
   "prompt.action.stop": "Зупинити",
   "prompt.action.stop": "Зупинити",

+ 5 - 0
packages/app/src/i18n/zh.ts

@@ -306,6 +306,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "从上下文移除活动文件",
   "prompt.context.removeActiveFile": "从上下文移除活动文件",
   "prompt.context.removeFile": "从上下文移除文件",
   "prompt.context.removeFile": "从上下文移除文件",
   "prompt.action.attachFile": "附加文件",
   "prompt.action.attachFile": "附加文件",
+  "prompt.menu.addImagesAndFiles": "添加文件及更多内容",
+  "prompt.menu.imagesAndFiles": "图片和文件",
+  "prompt.menu.commands": "命令",
+  "prompt.menu.context": "上下文",
+  "prompt.menu.shellCommand": "shell 命令",
   "prompt.attachment.remove": "移除附件",
   "prompt.attachment.remove": "移除附件",
   "prompt.action.send": "发送",
   "prompt.action.send": "发送",
   "prompt.action.stop": "停止",
   "prompt.action.stop": "停止",

+ 5 - 0
packages/app/src/i18n/zht.ts

@@ -286,6 +286,11 @@ export const dict = {
   "prompt.context.removeActiveFile": "從上下文移除目前檔案",
   "prompt.context.removeActiveFile": "從上下文移除目前檔案",
   "prompt.context.removeFile": "從上下文移除檔案",
   "prompt.context.removeFile": "從上下文移除檔案",
   "prompt.action.attachFile": "附加檔案",
   "prompt.action.attachFile": "附加檔案",
+  "prompt.menu.addImagesAndFiles": "新增檔案及更多內容",
+  "prompt.menu.imagesAndFiles": "圖片和檔案",
+  "prompt.menu.commands": "命令",
+  "prompt.menu.context": "上下文",
+  "prompt.menu.shellCommand": "shell 命令",
   "prompt.attachment.remove": "移除附件",
   "prompt.attachment.remove": "移除附件",
   "prompt.action.send": "傳送",
   "prompt.action.send": "傳送",
   "prompt.action.stop": "停止",
   "prompt.action.stop": "停止",

+ 4 - 0
packages/app/test-browser/prompt-transient-state.test.ts

@@ -8,6 +8,8 @@ test("resets transient prompt input state when the prompt session changes", () =
     const [state, setState] = createPromptInputTransientState(identity, 3)
     const [state, setState] = createPromptInputTransientState(identity, 3)
     setState({
     setState({
       popover: "slash",
       popover: "slash",
+      slashMenu: true,
+      slashMenuQuery: "compact",
       historyIndex: 2,
       historyIndex: 2,
       savedPrompt: {
       savedPrompt: {
         prompt: [{ type: "text", content: "draft-A", start: 0, end: 7 }],
         prompt: [{ type: "text", content: "draft-A", start: 0, end: 7 }],
@@ -23,6 +25,8 @@ test("resets transient prompt input state when the prompt session changes", () =
 
 
     expect(state).toMatchObject({
     expect(state).toMatchObject({
       popover: null,
       popover: null,
+      slashMenu: false,
+      slashMenuQuery: "",
       historyIndex: -1,
       historyIndex: -1,
       savedPrompt: null,
       savedPrompt: null,
       placeholder: 3,
       placeholder: 3,