feat(FN-4017): add /new command alias for task creation in quick chat

Added a `/new` slash command alias across the chat interface, with interception in ChatView, support in the QuickChatFAB, corresponding tests, and documentation in the dashboard guide.

Fusion-Task-Id: FN-4017
This commit is contained in:
Fusion
2026-05-11 10:40:25 -07:00
committed by gsxdsm
parent 867c684eec
commit f25cf6e34d
5 changed files with 93 additions and 7 deletions

View File

@@ -1244,7 +1244,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
const files = pendingAttachments.map((attachment) => attachment.file);
if ((!trimmed && files.length === 0) || !activeSession) return;
if (trimmed === "/clear") {
if (trimmed === "/clear" || trimmed === "/new") {
clearComposerState();
stopStreaming();
clearPendingMessage();

View File

@@ -1673,7 +1673,7 @@ export function QuickChatFAB({
return;
}
if (trimmed === "/clear") {
if (trimmed === "/clear" || trimmed === "/new") {
stopStreaming();
clearPendingMessage();
attachmentsToSend.forEach((attachment) => {
@@ -2382,7 +2382,7 @@ export function QuickChatFAB({
))}
{helpMessageVisible && (
<div className="quick-chat-panel-message quick-chat-panel-message--received" data-testid="quick-chat-help-message">
{renderAssistantMessageContent("Available commands:\n- `/clear` — Clear conversation and start fresh\n- `/skill:{name}` — Use a specific skill\n- `/help` — Show this help")}
{renderAssistantMessageContent("Available commands:\n- `/new` or `/clear` — Clear conversation and start fresh\n- `/skill:{name}` — Use a specific skill\n- `/help` — Show this help")}
</div>
)}
<div
@@ -2436,7 +2436,7 @@ export function QuickChatFAB({
))}
{helpMessageVisible && (
<div className="quick-chat-panel-message quick-chat-panel-message--received" data-testid="quick-chat-help-message">
{renderAssistantMessageContent("Available commands:\n- `/clear` — Clear conversation and start fresh\n- `/skill:{name}` — Use a specific skill\n- `/help` — Show this help")}
{renderAssistantMessageContent("Available commands:\n- `/new` or `/clear` — Clear conversation and start fresh\n- `/skill:{name}` — Use a specific skill\n- `/help` — Show this help")}
</div>
)}
</>

View File

@@ -1093,6 +1093,51 @@ describe("ChatView", () => {
expect(clearPendingMessage).toHaveBeenCalledTimes(1);
});
it("intercepts exact /new and starts a fresh session instead of sending message", async () => {
const sendMessage = vi.fn();
const createSession = vi.fn().mockResolvedValue({ id: "session-new", agentId: "agent-001" });
const stopStreaming = vi.fn();
const clearPendingMessage = vi.fn();
setupMockChat({
activeSession: activeSessionFixture,
messages: [],
sendMessage,
createSession,
stopStreaming,
clearPendingMessage,
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const textarea = screen.getByTestId("chat-input");
await userEvent.type(textarea, " /new {enter}");
expect(sendMessage).not.toHaveBeenCalled();
expect(createSession).toHaveBeenCalledWith({ agentId: "agent-001" });
expect(stopStreaming).toHaveBeenCalledTimes(1);
expect(clearPendingMessage).toHaveBeenCalledTimes(1);
});
it("does not intercept non-exact /new text", async () => {
const sendMessage = vi.fn();
const createSession = vi.fn();
setupMockChat({
activeSession: activeSessionFixture,
messages: [],
sendMessage,
createSession,
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const textarea = screen.getByTestId("chat-input");
await userEvent.type(textarea, "/new now{enter}");
expect(sendMessage).toHaveBeenCalledWith("/new now", []);
expect(createSession).not.toHaveBeenCalled();
});
it("does not intercept non-exact /clear text", async () => {
const sendMessage = vi.fn();
const createSession = vi.fn();

View File

@@ -231,6 +231,44 @@ describe("QuickChatFAB session-first UX", () => {
expect(mockStreamChatResponse).not.toHaveBeenCalled();
});
it("intercepts exact /new and starts a fresh session for the active target", async () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab"));
const input = await screen.findByTestId("quick-chat-input");
await waitFor(() => expect(input).not.toBeDisabled());
fireEvent.change(input, { target: { value: " /new " } });
fireEvent.click(screen.getByTestId("quick-chat-send"));
await waitFor(() => {
expect(mockCreateChatSession).toHaveBeenCalledWith(
{ agentId: "__fn_agent__", modelProvider: "openai", modelId: "gpt-4o" },
"proj-1",
);
});
expect(mockStreamChatResponse).not.toHaveBeenCalled();
});
it("does not intercept non-exact /new prompts", async () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab"));
const input = await screen.findByTestId("quick-chat-input");
await waitFor(() => expect(input).not.toBeDisabled());
fireEvent.change(input, { target: { value: "/new now" } });
fireEvent.click(screen.getByTestId("quick-chat-send"));
await waitFor(() => {
expect(mockStreamChatResponse).toHaveBeenCalledWith(
"session-model",
"/new now",
expect.any(Object),
[],
"proj-1",
);
});
});
it("does not intercept non-exact /clear prompts", async () => {
render(<QuickChatFAB addToast={vi.fn()} projectId="proj-1" />);
fireEvent.click(screen.getByTestId("quick-chat-fab"));
@@ -312,7 +350,10 @@ describe("QuickChatFAB session-first UX", () => {
fireEvent.change(input, { target: { value: "/help" } });
fireEvent.click(screen.getByTestId("quick-chat-send"));
expect(await screen.findByTestId("quick-chat-help-message")).toBeInTheDocument();
const helpMessage = await screen.findByTestId("quick-chat-help-message");
expect(helpMessage).toBeInTheDocument();
expect(helpMessage).toHaveTextContent("/new");
expect(helpMessage).toHaveTextContent("/clear");
expect(mockStreamChatResponse).not.toHaveBeenCalled();
});