Skip to content

Commit 2b5d4fc

Browse files
committed
🤖 fix: unify chat input and commands flow
1 parent 0d923bf commit 2b5d4fc

File tree

5 files changed

+367
-279
lines changed

5 files changed

+367
-279
lines changed

src/browser/App.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { buildCoreSources, type BuildSourcesParams } from "./utils/commands/sour
2828

2929
import type { ThinkingLevel } from "@/common/types/thinking";
3030
import { CUSTOM_EVENTS } from "@/common/constants/events";
31-
import { isWorkspaceForkSwitchEvent } from "./utils/workspaceFork";
31+
import { isWorkspaceForkSwitchEvent } from "./utils/chatCommands";
3232
import { getThinkingLevelKey } from "@/common/constants/storage";
3333
import type { BranchListResult } from "@/common/types/ipc";
3434
import { useTelemetry } from "./hooks/useTelemetry";
@@ -517,9 +517,21 @@ function AppInner() {
517517
);
518518
}, [projects, setSelectedWorkspace, setWorkspaceMetadata]);
519519

520+
const handleProviderConfig = useCallback(
521+
async (provider: string, keyPath: string[], value: string) => {
522+
const result = await window.api.providers.setProviderConfig(provider, keyPath, value);
523+
if (!result.success) {
524+
throw new Error(result.error);
525+
}
526+
},
527+
[]
528+
);
529+
520530
return (
521531
<>
522532
<div className="bg-bg-dark mobile-layout flex h-screen overflow-hidden">
533+
534+
523535
<LeftSidebar
524536
lastReadTimestamps={lastReadTimestamps}
525537
onToggleUnread={onToggleUnread}
@@ -561,6 +573,7 @@ function AppInner() {
561573
variant="creation"
562574
projectPath={projectPath}
563575
projectName={projectName}
576+
onProviderConfig={handleProviderConfig}
564577
onReady={handleCreationChatReady}
565578
onWorkspaceCreated={(metadata) => {
566579
// Add to workspace metadata map

src/browser/components/ChatInput/index.tsx

Lines changed: 35 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import React, {
1111
import { CommandSuggestions, COMMAND_SUGGESTION_KEYS } from "../CommandSuggestions";
1212
import type { Toast } from "../ChatInputToast";
1313
import { ChatInputToast } from "../ChatInputToast";
14-
import { createCommandToast, createErrorToast } from "../ChatInputToasts";
14+
import { createErrorToast } from "../ChatInputToasts";
1515
import { parseCommand } from "@/browser/utils/slashCommands/parser";
1616
import { usePersistedState, updatePersistedState } from "@/browser/hooks/usePersistedState";
1717
import { useMode } from "@/browser/contexts/ModeContext";
@@ -26,11 +26,9 @@ import {
2626
getPendingScopeId,
2727
} from "@/common/constants/storage";
2828
import {
29-
handleNewCommand,
30-
handleCompactCommand,
31-
forkWorkspace,
3229
prepareCompactionMessage,
33-
type CommandHandlerContext,
30+
processSlashCommand,
31+
type SlashCommandContext,
3432
} from "@/browser/utils/chatCommands";
3533
import { CUSTOM_EVENTS } from "@/common/constants/events";
3634
import {
@@ -59,7 +57,6 @@ import {
5957
import type { ThinkingLevel } from "@/common/types/thinking";
6058
import type { MuxFrontendMetadata } from "@/common/types/message";
6159
import { useTelemetry } from "@/browser/hooks/useTelemetry";
62-
import { setTelemetryEnabled } from "@/common/telemetry";
6360
import { getTokenCountPromise } from "@/browser/utils/tokenizer/rendererClient";
6461
import { CreationCenterContent } from "./CreationCenterContent";
6562
import { cn } from "@/common/lib/utils";
@@ -467,8 +464,38 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
467464
}
468465

469466
const messageText = input.trim();
467+
const parsed = parseCommand(messageText);
468+
469+
if (parsed) {
470+
const context: SlashCommandContext = {
471+
variant,
472+
workspaceId: variant === "workspace" ? props.workspaceId : undefined,
473+
sendMessageOptions,
474+
setInput,
475+
setIsSending,
476+
setToast,
477+
setVimEnabled,
478+
setPreferredModel,
479+
onProviderConfig: props.onProviderConfig,
480+
onModelChange: props.onModelChange,
481+
onTruncateHistory: variant === "workspace" ? props.onTruncateHistory : undefined,
482+
onCancelEdit: variant === "workspace" ? props.onCancelEdit : undefined,
483+
editMessageId: editingMessage?.id,
484+
resetInputHeight: () => {
485+
if (inputRef.current) {
486+
inputRef.current.style.height = "36px";
487+
}
488+
},
489+
};
490+
491+
const result = await processSlashCommand(parsed, context);
492+
493+
if (!result.clearInput) {
494+
setInput(messageText); // Restore input on failure
495+
}
496+
return;
497+
}
470498

471-
// Route to creation handler for creation variant
472499
if (variant === "creation") {
473500
// Creation variant: simple message send + workspace creation
474501
setIsSending(true);
@@ -483,193 +510,10 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
483510
return;
484511
}
485512

486-
// Workspace variant: full command handling + message send
513+
// Workspace variant: regular message send
487514
if (variant !== "workspace") return; // Type guard
488515

489516
try {
490-
// Parse command
491-
const parsed = parseCommand(messageText);
492-
493-
if (parsed) {
494-
// Handle /clear command
495-
if (parsed.type === "clear") {
496-
setInput("");
497-
if (inputRef.current) {
498-
inputRef.current.style.height = "36px";
499-
}
500-
await props.onTruncateHistory(1.0);
501-
setToast({
502-
id: Date.now().toString(),
503-
type: "success",
504-
message: "Chat history cleared",
505-
});
506-
return;
507-
}
508-
509-
// Handle /truncate command
510-
if (parsed.type === "truncate") {
511-
setInput("");
512-
if (inputRef.current) {
513-
inputRef.current.style.height = "36px";
514-
}
515-
await props.onTruncateHistory(parsed.percentage);
516-
setToast({
517-
id: Date.now().toString(),
518-
type: "success",
519-
message: `Chat history truncated by ${Math.round(parsed.percentage * 100)}%`,
520-
});
521-
return;
522-
}
523-
524-
// Handle /providers set command
525-
if (parsed.type === "providers-set" && props.onProviderConfig) {
526-
setIsSending(true);
527-
setInput(""); // Clear input immediately
528-
529-
try {
530-
await props.onProviderConfig(parsed.provider, parsed.keyPath, parsed.value);
531-
// Success - show toast
532-
setToast({
533-
id: Date.now().toString(),
534-
type: "success",
535-
message: `Provider ${parsed.provider} updated`,
536-
});
537-
} catch (error) {
538-
console.error("Failed to update provider config:", error);
539-
setToast({
540-
id: Date.now().toString(),
541-
type: "error",
542-
message: error instanceof Error ? error.message : "Failed to update provider",
543-
});
544-
setInput(messageText); // Restore input on error
545-
} finally {
546-
setIsSending(false);
547-
}
548-
return;
549-
}
550-
551-
// Handle /model command
552-
if (parsed.type === "model-set") {
553-
setInput(""); // Clear input immediately
554-
setPreferredModel(parsed.modelString);
555-
props.onModelChange?.(parsed.modelString);
556-
setToast({
557-
id: Date.now().toString(),
558-
type: "success",
559-
message: `Model changed to ${parsed.modelString}`,
560-
});
561-
return;
562-
}
563-
564-
// Handle /vim command
565-
if (parsed.type === "vim-toggle") {
566-
setInput(""); // Clear input immediately
567-
setVimEnabled((prev) => !prev);
568-
return;
569-
}
570-
571-
// Handle /telemetry command
572-
if (parsed.type === "telemetry-set") {
573-
setInput(""); // Clear input immediately
574-
setTelemetryEnabled(parsed.enabled);
575-
setToast({
576-
id: Date.now().toString(),
577-
type: "success",
578-
message: `Telemetry ${parsed.enabled ? "enabled" : "disabled"}`,
579-
});
580-
return;
581-
}
582-
583-
// Handle /compact command
584-
if (parsed.type === "compact") {
585-
const context: CommandHandlerContext = {
586-
workspaceId: props.workspaceId,
587-
sendMessageOptions,
588-
editMessageId: editingMessage?.id,
589-
setInput,
590-
setIsSending,
591-
setToast,
592-
onCancelEdit: props.onCancelEdit,
593-
};
594-
595-
const result = await handleCompactCommand(parsed, context);
596-
if (!result.clearInput) {
597-
setInput(messageText); // Restore input on error
598-
}
599-
return;
600-
}
601-
602-
// Handle /fork command
603-
if (parsed.type === "fork") {
604-
setInput(""); // Clear input immediately
605-
setIsSending(true);
606-
607-
try {
608-
const forkResult = await forkWorkspace({
609-
sourceWorkspaceId: props.workspaceId,
610-
newName: parsed.newName,
611-
startMessage: parsed.startMessage,
612-
sendMessageOptions,
613-
});
614-
615-
if (!forkResult.success) {
616-
const errorMsg = forkResult.error ?? "Failed to fork workspace";
617-
console.error("Failed to fork workspace:", errorMsg);
618-
setToast({
619-
id: Date.now().toString(),
620-
type: "error",
621-
title: "Fork Failed",
622-
message: errorMsg,
623-
});
624-
setInput(messageText); // Restore input on error
625-
} else {
626-
setToast({
627-
id: Date.now().toString(),
628-
type: "success",
629-
message: `Forked to workspace "${parsed.newName}"`,
630-
});
631-
}
632-
} catch (error) {
633-
const errorMsg = error instanceof Error ? error.message : "Failed to fork workspace";
634-
console.error("Fork error:", error);
635-
setToast({
636-
id: Date.now().toString(),
637-
type: "error",
638-
title: "Fork Failed",
639-
message: errorMsg,
640-
});
641-
setInput(messageText); // Restore input on error
642-
}
643-
644-
setIsSending(false);
645-
return;
646-
}
647-
648-
// Handle /new command
649-
if (parsed.type === "new") {
650-
const context: CommandHandlerContext = {
651-
workspaceId: props.workspaceId,
652-
sendMessageOptions,
653-
setInput,
654-
setIsSending,
655-
setToast,
656-
};
657-
658-
const result = await handleNewCommand(parsed, context);
659-
if (!result.clearInput) {
660-
setInput(messageText); // Restore input on error
661-
}
662-
return;
663-
}
664-
665-
// Handle all other commands - show display toast
666-
const commandToast = createCommandToast(parsed);
667-
if (commandToast) {
668-
setToast(commandToast);
669-
return;
670-
}
671-
}
672-
673517
// Regular message - send directly via API
674518
setIsSending(true);
675519

src/browser/components/ChatInput/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export interface ChatInputCreationVariant {
3131
projectPath: string;
3232
projectName: string;
3333
onWorkspaceCreated: (metadata: FrontendWorkspaceMetadata) => void;
34+
onProviderConfig?: (provider: string, keyPath: string[], value: string) => Promise<void>;
35+
onModelChange?: (model: string) => void;
3436
onCancel?: () => void;
3537
disabled?: boolean;
3638
onReady?: (api: ChatInputAPI) => void;

0 commit comments

Comments
 (0)