Skip to content

Commit 9e6f1d9

Browse files
committed
fix: unify toast rendering for slash commands in both variants
- Toast was only rendered for workspace variant, so slash command toasts (e.g., /providers help) were invisible in creation mode - Now renders a single ChatInputToast that shows either: - Shared toast from slash commands (priority) - Creation-specific toast as fallback - Command suggestions were already unified, this completes parity
1 parent d2a2079 commit 9e6f1d9

File tree

2 files changed

+58
-26
lines changed

2 files changed

+58
-26
lines changed

src/browser/components/ChatInput/index.tsx

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ import { cn } from "@/common/lib/utils";
6363
import { CreationControls } from "./CreationControls";
6464
import { useCreationWorkspace } from "./useCreationWorkspace";
6565

66+
const LEADING_COMMAND_NOISE = /^(?:\s|\u200B|\u200C|\u200D|\u200E|\u200F|\uFEFF)+/;
67+
68+
function normalizeSlashCommandInput(value: string): string {
69+
if (!value) {
70+
return value;
71+
}
72+
return value.replace(LEADING_COMMAND_NOISE, "");
73+
}
6674
type TokenCountReader = () => number;
6775

6876
function createTokenCountResource(promise: Promise<number>): TokenCountReader {
@@ -301,9 +309,10 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
301309

302310
// Watch input for slash commands
303311
useEffect(() => {
304-
const suggestions = getSlashCommandSuggestions(input, { providerNames });
312+
const normalizedSlashSource = normalizeSlashCommandInput(input);
313+
const suggestions = getSlashCommandSuggestions(normalizedSlashSource, { providerNames });
305314
setCommandSuggestions(suggestions);
306-
setShowCommandSuggestions(suggestions.length > 0);
315+
setShowCommandSuggestions(normalizedSlashSource.startsWith("/") && suggestions.length > 0);
307316
}, [input, providerNames]);
308317

309318
// Load provider names for suggestions
@@ -463,8 +472,11 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
463472
return;
464473
}
465474

466-
const messageText = input.trim();
467-
const parsed = parseCommand(messageText);
475+
const rawInputValue = input;
476+
const messageText = rawInputValue.trim();
477+
const normalizedCommandInput = normalizeSlashCommandInput(messageText);
478+
const isSlashCommand = normalizedCommandInput.startsWith("/");
479+
const parsed = isSlashCommand ? parseCommand(normalizedCommandInput) : null;
468480

469481
if (parsed) {
470482
const context: SlashCommandContext = {
@@ -491,11 +503,20 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
491503
const result = await processSlashCommand(parsed, context);
492504

493505
if (!result.clearInput) {
494-
setInput(messageText); // Restore input on failure
506+
setInput(rawInputValue); // Restore exact input on failure
495507
}
496508
return;
497509
}
498510

511+
if (isSlashCommand) {
512+
setToast({
513+
id: Date.now().toString(),
514+
type: "error",
515+
message: `Unknown command: ${normalizedCommandInput.split(/\s+/)[0] ?? ""}`,
516+
});
517+
return;
518+
}
519+
499520
// Handle standard message sending based on variant
500521
if (variant === "creation") {
501522
setIsSending(true);
@@ -511,7 +532,6 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
511532
}
512533

513534
// Workspace variant: regular message send
514-
if (variant !== "workspace") return;
515535

516536
try {
517537
// Regular message - send directly via API
@@ -555,18 +575,18 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
555575
let muxMetadata: MuxFrontendMetadata | undefined;
556576
let compactionOptions = {};
557577

558-
if (editingMessage && messageText.startsWith("/")) {
559-
const parsed = parseCommand(messageText);
560-
if (parsed?.type === "compact") {
578+
if (editingMessage && normalizedCommandInput.startsWith("/")) {
579+
const parsedEditingCommand = parseCommand(normalizedCommandInput);
580+
if (parsedEditingCommand?.type === "compact") {
561581
const {
562582
messageText: regeneratedText,
563583
metadata,
564584
sendOptions,
565585
} = prepareCompactionMessage({
566586
workspaceId: props.workspaceId,
567-
maxOutputTokens: parsed.maxOutputTokens,
568-
continueMessage: parsed.continueMessage,
569-
model: parsed.model,
587+
maxOutputTokens: parsedEditingCommand.maxOutputTokens,
588+
continueMessage: parsedEditingCommand.continueMessage,
589+
model: parsedEditingCommand.model,
570590
sendMessageOptions,
571591
});
572592
actualMessageText = regeneratedText;
@@ -602,7 +622,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
602622
// Show error using enhanced toast
603623
setToast(createErrorToast(result.error));
604624
// Restore input and images on error so user can try again
605-
setInput(messageText);
625+
setInput(rawInputValue);
606626
setImageAttachments(previousImageAttachments);
607627
} else {
608628
// Track telemetry for successful message send
@@ -623,7 +643,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
623643
raw: error instanceof Error ? error.message : "Failed to send message",
624644
})
625645
);
626-
setInput(messageText);
646+
setInput(rawInputValue);
627647
setImageAttachments(previousImageAttachments);
628648
} finally {
629649
setIsSending(false);
@@ -749,18 +769,16 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
749769
data-component="ChatInputSection"
750770
>
751771
<div className="mx-auto w-full max-w-4xl">
752-
{/* Creation toast */}
753-
{variant === "creation" && (
754-
<ChatInputToast
755-
toast={creationState.toast}
756-
onDismiss={() => creationState.setToast(null)}
757-
/>
758-
)}
759-
760-
{/* Workspace toast */}
761-
{variant === "workspace" && (
762-
<ChatInputToast toast={toast} onDismiss={handleToastDismiss} />
763-
)}
772+
{/* Toast - show shared toast (slash commands) or variant-specific toast */}
773+
<ChatInputToast
774+
toast={toast ?? (variant === "creation" ? creationState.toast : null)}
775+
onDismiss={() => {
776+
handleToastDismiss();
777+
if (variant === "creation") {
778+
creationState.setToast(null);
779+
}
780+
}}
781+
/>
764782

765783
{/* Command suggestions - available in both variants */}
766784
<CommandSuggestions

tests/manual_parse_check.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { parseCommand } from "../src/browser/utils/slashCommands/parser";
2+
3+
try {
4+
const result = parseCommand("/providers");
5+
console.log("Parsing /providers:", JSON.stringify(result, null, 2));
6+
7+
const result2 = parseCommand("/providers set anthropic apiKey 123");
8+
console.log("Parsing /providers set:", JSON.stringify(result2, null, 2));
9+
10+
const result4 = parseCommand("/providers ");
11+
console.log("Parsing /providers (space):", JSON.stringify(result4, null, 2));
12+
} catch (e) {
13+
console.error("Error:", e);
14+
}

0 commit comments

Comments
 (0)