diff --git a/frontend/src/components/ai-elements/prompt-input.tsx b/frontend/src/components/ai-elements/prompt-input.tsx index ddfe967e..682b804e 100644 --- a/frontend/src/components/ai-elements/prompt-input.tsx +++ b/frontend/src/components/ai-elements/prompt-input.tsx @@ -883,7 +883,7 @@ export const PromptInputTextarea = ({ if (!submitOnEnter) { return; } - if (e.shiftKey) { + if (e.shiftKey || e.ctrlKey || e.metaKey) { return; } e.preventDefault(); @@ -1089,10 +1089,12 @@ export const PromptInputSubmit = ({ controller.attachments.files.length > 0 : false; - // 正在 streaming 时不允许发送 - const isStreaming = status === "streaming" || status === "submitted"; - - const isDisabled = disabled || !hasContent || isStreaming; + const isStreaming = status === "streaming"; + const isSubmitted = status === "submitted"; + // Streaming 时按钮用于停止,不受输入内容是否为空限制 + const isDisabled = isStreaming + ? !!disabled + : disabled || !hasContent || isSubmitted; let Icon = ; diff --git a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx index db59d746..388e32cc 100644 --- a/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -95,8 +95,8 @@ export function ArtifactFileDetail({ return checkCodeFile(filepath); }, [filepath, isWriteFile, isSkillFile]); const previewable = useMemo(() => { - return (language === "html" && !isWriteFile) || language === "markdown"; - }, [isWriteFile, language]); + return language === "html" || language === "markdown"; + }, [language]); const artifactUrl = useMemo(() => { if (!threadId) { return ""; diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index df181b56..fe69aaf1 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -342,7 +342,6 @@ export function InputBox({ "size-full", !effectiveIsFocused && "h-[80px] py-0 leading-20", )} - submitOnEnter={false} disabled={isInputDisabled} placeholder={t.inputBox.placeholder} autoFocus={autoFocus} diff --git a/frontend/src/components/workspace/messages/message-group.tsx b/frontend/src/components/workspace/messages/message-group.tsx index 45c57211..53a4a101 100644 --- a/frontend/src/components/workspace/messages/message-group.tsx +++ b/frontend/src/components/workspace/messages/message-group.tsx @@ -76,6 +76,9 @@ export function MessageGroup({ return filteredSteps[filteredSteps.length - 1]; } }, [lastToolCallStep, steps]); + const totalToolStepCount = aboveLastToolCallSteps.length + (lastToolCallStep ? 1 : 0); + const shouldShowToolSteps = !!lastToolCallStep && + (showAbove || aboveLastToolCallSteps.length === 0); const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading); return ( setShowAbove(!showAbove)} + onClick={(event) => { + event.stopPropagation(); + setShowAbove((prev) => !prev); + }} > {showAbove ? t.toolCalls.lessSteps - : t.toolCalls.moreSteps(aboveLastToolCallSteps.length)} + : t.toolCalls.moreSteps(totalToolStepCount)} } icon={ @@ -108,7 +114,7 @@ export function MessageGroup({ > )} - {lastToolCallStep && ( + {shouldShowToolSteps && ( {showAbove && aboveLastToolCallSteps.map((step) => @@ -145,7 +151,10 @@ export function MessageGroup({ key={lastReasoningStep.id} className="w-full items-start justify-start text-left" variant="ghost" - onClick={() => setShowLastThinking(!showLastThinking)} + onClick={(event) => { + event.stopPropagation(); + setShowLastThinking((prev) => !prev); + }} >
{ { timeout: 30_000 }, ); }); + + test("streaming 中点击停止可中断输出", async ({ page }) => { + const threadId = uuid(); + const text = + "请逐行输出 1 到 500 的数字,并在每一行前面加上“第N行:”前缀,不要省略。"; + + await openChat(page, newChatEntry(threadId)); + await expect(page.getByTestId("welcome-suggestions")).toBeVisible(); + + await sendMessage(page, text); + + const submitButton = page.locator("button[aria-label='Submit']"); + + await expect(submitButton).toHaveText("停止", { timeout: 30_000 }); + await expect(submitButton).toBeEnabled(); + + await submitButton.click(); + + // 点击停止后应退出 streaming 态,按钮文本不再是“停止” + await expect(submitButton).toHaveText("发送", { timeout: 30_000 }); + }); });