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) {
|
if (!submitOnEnter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -1089,10 +1089,12 @@ export const PromptInputSubmit = ({
|
||||||
controller.attachments.files.length > 0
|
controller.attachments.files.length > 0
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
// 正在 streaming 时不允许发送
|
const isStreaming = status === "streaming";
|
||||||
const isStreaming = status === "streaming" || status === "submitted";
|
const isSubmitted = status === "submitted";
|
||||||
|
// Streaming 时按钮用于停止,不受输入内容是否为空限制
|
||||||
const isDisabled = disabled || !hasContent || isStreaming;
|
const isDisabled = isStreaming
|
||||||
|
? !!disabled
|
||||||
|
: disabled || !hasContent || isSubmitted;
|
||||||
|
|
||||||
let Icon = <ArrowUpIcon className="size-4" />;
|
let Icon = <ArrowUpIcon className="size-4" />;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@ export function ArtifactFileDetail({
|
||||||
return checkCodeFile(filepath);
|
return checkCodeFile(filepath);
|
||||||
}, [filepath, isWriteFile, isSkillFile]);
|
}, [filepath, isWriteFile, isSkillFile]);
|
||||||
const previewable = useMemo(() => {
|
const previewable = useMemo(() => {
|
||||||
return (language === "html" && !isWriteFile) || language === "markdown";
|
return language === "html" || language === "markdown";
|
||||||
}, [isWriteFile, language]);
|
}, [language]);
|
||||||
const artifactUrl = useMemo(() => {
|
const artifactUrl = useMemo(() => {
|
||||||
if (!threadId) {
|
if (!threadId) {
|
||||||
return "";
|
return "";
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,6 @@ export function InputBox({
|
||||||
"size-full",
|
"size-full",
|
||||||
!effectiveIsFocused && "h-[80px] py-0 leading-20",
|
!effectiveIsFocused && "h-[80px] py-0 leading-20",
|
||||||
)}
|
)}
|
||||||
submitOnEnter={false}
|
|
||||||
disabled={isInputDisabled}
|
disabled={isInputDisabled}
|
||||||
placeholder={t.inputBox.placeholder}
|
placeholder={t.inputBox.placeholder}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ export function MessageGroup({
|
||||||
return filteredSteps[filteredSteps.length - 1];
|
return filteredSteps[filteredSteps.length - 1];
|
||||||
}
|
}
|
||||||
}, [lastToolCallStep, steps]);
|
}, [lastToolCallStep, steps]);
|
||||||
|
const totalToolStepCount = aboveLastToolCallSteps.length + (lastToolCallStep ? 1 : 0);
|
||||||
|
const shouldShowToolSteps = !!lastToolCallStep &&
|
||||||
|
(showAbove || aboveLastToolCallSteps.length === 0);
|
||||||
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
||||||
return (
|
return (
|
||||||
<ChainOfThought
|
<ChainOfThought
|
||||||
|
|
@ -87,14 +90,17 @@ export function MessageGroup({
|
||||||
key="above"
|
key="above"
|
||||||
className="w-full items-start justify-start text-left"
|
className="w-full items-start justify-start text-left"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setShowAbove(!showAbove)}
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setShowAbove((prev) => !prev);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
label={
|
label={
|
||||||
<span className="opacity-60">
|
<span className="opacity-60">
|
||||||
{showAbove
|
{showAbove
|
||||||
? t.toolCalls.lessSteps
|
? t.toolCalls.lessSteps
|
||||||
: t.toolCalls.moreSteps(aboveLastToolCallSteps.length)}
|
: t.toolCalls.moreSteps(totalToolStepCount)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
|
|
@ -108,7 +114,7 @@ export function MessageGroup({
|
||||||
></ChainOfThoughtStep>
|
></ChainOfThoughtStep>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{lastToolCallStep && (
|
{shouldShowToolSteps && (
|
||||||
<ChainOfThoughtContent className="px-4 pb-2">
|
<ChainOfThoughtContent className="px-4 pb-2">
|
||||||
{showAbove &&
|
{showAbove &&
|
||||||
aboveLastToolCallSteps.map((step) =>
|
aboveLastToolCallSteps.map((step) =>
|
||||||
|
|
@ -145,7 +151,10 @@ export function MessageGroup({
|
||||||
key={lastReasoningStep.id}
|
key={lastReasoningStep.id}
|
||||||
className="w-full items-start justify-start text-left"
|
className="w-full items-start justify-start text-left"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setShowLastThinking(!showLastThinking)}
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setShowLastThinking((prev) => !prev);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
|
|
|
||||||
|
|
@ -78,4 +78,25 @@ test.describe("线程路由(无 isnew)", () => {
|
||||||
{ timeout: 30_000 },
|
{ 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