feat(frontend): 优化工作区输入框与 artifacts 展示体验
改进工作区核心交互,提升输入与结果查看的一致性和可用性。 调整 prompt 输入相关组件逻辑,优化输入行为与状态反馈 更新 workspace input-box 交互细节,改善可用性与稳定性 优化 message-group 展示逻辑,增强消息区域可读性 调整 artifact-file-detail 预览相关实现,为后续 Office 文件展示做准备 补充并更新 thread-routing e2e 用例,覆盖关键路由与交互回归场景
This commit is contained in:
parent
5a0c2f5c95
commit
cd2a41b8a6
|
|
@ -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 = <ArrowUpIcon className="size-4" />;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 "";
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ChainOfThought
|
||||
|
|
@ -87,14 +90,17 @@ export function MessageGroup({
|
|||
key="above"
|
||||
className="w-full items-start justify-start text-left"
|
||||
variant="ghost"
|
||||
onClick={() => setShowAbove(!showAbove)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setShowAbove((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<ChainOfThoughtStep
|
||||
label={
|
||||
<span className="opacity-60">
|
||||
{showAbove
|
||||
? t.toolCalls.lessSteps
|
||||
: t.toolCalls.moreSteps(aboveLastToolCallSteps.length)}
|
||||
: t.toolCalls.moreSteps(totalToolStepCount)}
|
||||
</span>
|
||||
}
|
||||
icon={
|
||||
|
|
@ -108,7 +114,7 @@ export function MessageGroup({
|
|||
></ChainOfThoughtStep>
|
||||
</Button>
|
||||
)}
|
||||
{lastToolCallStep && (
|
||||
{shouldShowToolSteps && (
|
||||
<ChainOfThoughtContent className="px-4 pb-2">
|
||||
{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);
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<ChainOfThoughtStep
|
||||
|
|
|
|||
|
|
@ -78,4 +78,25 @@ test.describe("线程路由(无 isnew)", () => {
|
|||
{ 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 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue