Skip to content

Commit 9377da2

Browse files
committed
🤖 fix: unify chat input and commands flow
1 parent 085f22c commit 9377da2

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";
@@ -458,8 +455,38 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
458455
}
459456

460457
const messageText = input.trim();
458+
const parsed = parseCommand(messageText);
459+
460+
if (parsed) {
461+
const context: SlashCommandContext = {
462+
variant,
463+
workspaceId: variant === "workspace" ? props.workspaceId : undefined,
464+
sendMessageOptions,
465+
setInput,
466+
setIsSending,
467+
setToast,
468+
setVimEnabled,
469+
setPreferredModel,
470+
onProviderConfig: props.onProviderConfig,
471+
onModelChange: props.onModelChange,
472+
onTruncateHistory: variant === "workspace" ? props.onTruncateHistory : undefined,
473+
onCancelEdit: variant === "workspace" ? props.onCancelEdit : undefined,
474+
editMessageId: editingMessage?.id,
475+
resetInputHeight: () => {
476+
if (inputRef.current) {
477+
inputRef.current.style.height = "36px";
478+
}
479+
},
480+
};
481+
482+
const result = await processSlashCommand(parsed, context);
483+
484+
if (!result.clearInput) {
485+
setInput(messageText); // Restore input on failure
486+
}
487+
return;
488+
}
461489

462-
// Route to creation handler for creation variant
463490
if (variant === "creation") {
464491
// Creation variant: simple message send + workspace creation
465492
setIsSending(true);
@@ -474,193 +501,10 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
474501
return;
475502
}
476503

477-
// Workspace variant: full command handling + message send
504+
// Workspace variant: regular message send
478505
if (variant !== "workspace") return; // Type guard
479506

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

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)