Extract prompt input modes

This commit is contained in:
Federico Jaramillo Martinez
2026-05-07 14:16:22 +02:00
parent 35718dc284
commit 437478f0fc
4 changed files with 27 additions and 15 deletions
+4 -10
View File
@@ -1,6 +1,7 @@
import { LitElement, html, type PropertyValues } from "lit"; import { LitElement, html, type PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators.js"; import { customElement, property, query, state } from "lit/decorators.js";
import { api, type FileSuggestion, type SlashCommand } from "../api"; import { api, type FileSuggestion, type SlashCommand } from "../api";
import { inputModeForDraft } from "../inputModes";
import { promptEditorStyles, type CompletionItem } from "./shared"; import { promptEditorStyles, type CompletionItem } from "./shared";
import "./AutocompleteMenu"; import "./AutocompleteMenu";
@@ -31,7 +32,8 @@ export class PromptEditor extends LitElement {
} }
render() { render() {
const shellMode = this.isShellMode(); const inputMode = inputModeForDraft(this.draft);
const shellMode = inputMode.kind === "shell";
return html` return html`
<footer class=${shellMode ? "shell-mode" : ""}> <footer class=${shellMode ? "shell-mode" : ""}>
<div class="editor-wrap"> <div class="editor-wrap">
@@ -42,7 +44,7 @@ export class PromptEditor extends LitElement {
@keydown=${(event: KeyboardEvent) => this.handleKeyDown(event)} @keydown=${(event: KeyboardEvent) => this.handleKeyDown(event)}
placeholder="Message pi... Use / for commands, @ for files" placeholder="Message pi... Use / for commands, @ for files"
></textarea> ></textarea>
${shellMode ? html`<div class="mode-hint">Shell command${this.isShellExcludedFromContext() ? " · excluded from context" : ""}</div>` : null} ${shellMode ? html`<div class="mode-hint">Shell command${inputMode.excludeFromContext ? " · excluded from context" : ""}</div>` : null}
<autocomplete-menu .items=${this.completions} .selectedIndex=${this.selectedIndex} .onPick=${(item: CompletionItem) => this.pick(item)}></autocomplete-menu> <autocomplete-menu .items=${this.completions} .selectedIndex=${this.selectedIndex} .onPick=${(item: CompletionItem) => this.pick(item)}></autocomplete-menu>
</div> </div>
<button ?disabled=${this.disabled} @click=${this.send}>Send</button> <button ?disabled=${this.disabled} @click=${this.send}>Send</button>
@@ -62,14 +64,6 @@ export class PromptEditor extends LitElement {
textarea.style.height = `${textarea.scrollHeight}px`; textarea.style.height = `${textarea.scrollHeight}px`;
} }
private isShellMode(): boolean {
return this.draft.trimStart().startsWith("!");
}
private isShellExcludedFromContext(): boolean {
return this.draft.trimStart().startsWith("!!");
}
private updateDraft(value: string) { private updateDraft(value: string) {
this.draft = value; this.draft = value;
if (this.sessionId) saveDraft(this.sessionId, this.draft); if (this.sessionId) saveDraft(this.sessionId, this.draft);
@@ -1,7 +1,7 @@
import { api, type CommandResult, type SessionActivity, type SessionInfo, type SessionStatus } from "../api"; import { api, type CommandResult, type SessionActivity, type SessionInfo, type SessionStatus } from "../api";
import { normalizeMessages, textMessage } from "../chatMessages"; import { normalizeMessages, textMessage } from "../chatMessages";
import { applyTranscriptEvent } from "../chatTranscript"; import { applyTranscriptEvent } from "../chatTranscript";
import { isShellInput } from "../shellMessages"; import { isShellInput } from "../inputModes";
import { GlobalSessionSocket, SessionSocket, type SessionUiEvent } from "../sessionSocket"; import { GlobalSessionSocket, SessionSocket, type SessionUiEvent } from "../sessionSocket";
import type { GetState, SetState, UpdateUrl } from "./types"; import type { GetState, SetState, UpdateUrl } from "./types";
+22
View File
@@ -0,0 +1,22 @@
export type InputMode =
| { kind: "normal" }
| { kind: "command" }
| { kind: "file" }
| { kind: "shell"; excludeFromContext: boolean };
export function inputModeForDraft(draft: string): InputMode {
const trimmed = draft.trimStart();
if (trimmed.startsWith("!")) return { kind: "shell", excludeFromContext: trimmed.startsWith("!!") };
if (currentToken(draft).startsWith("/")) return { kind: "command" };
if (draft.endsWith("@ ") || currentToken(draft).startsWith("@")) return { kind: "file" };
return { kind: "normal" };
}
export function isShellInput(text: string): boolean {
return inputModeForDraft(text).kind === "shell";
}
function currentToken(draft: string): string {
const tokenStart = Math.max(draft.lastIndexOf(" "), draft.lastIndexOf("\n")) + 1;
return draft.slice(tokenStart);
}
-4
View File
@@ -2,10 +2,6 @@ import { textMessage } from "./chatMessages";
import type { ChatLine } from "./components/shared"; import type { ChatLine } from "./components/shared";
import type { SessionUiEvent } from "./sessionSocket"; import type { SessionUiEvent } from "./sessionSocket";
export function isShellInput(text: string): boolean {
return text.trim().startsWith("!");
}
export function shellStartMessage(command: string, excludeFromContext?: boolean): ChatLine { export function shellStartMessage(command: string, excludeFromContext?: boolean): ChatLine {
return textMessage("bash", `$ ${command}${excludeFromContext ? "\n\nexcluded from context" : ""}`); return textMessage("bash", `$ ${command}${excludeFromContext ? "\n\nexcluded from context" : ""}`);
} }