fix: ²¹Æë Step3 ɳÏä²Ù×÷²¢Îȶ¨д×÷³ÎÇåÁ÷³Ì

Ϊ roundtable µÚÈý²½ɳÏ䲹»ØÐ´°¿ڴò¿ªÓëÏÂÔزÙ×÷£¬²¢ÐÞÕýд×÷ÅäÖóÎÇåµÄÌύÁ÷³ÌÓëĬÈÏËزÄÀ´Դ£¬±ÜÃâÓû§Ôڶ࿨Ƭ³¡¾°ÏÂÌáǰÌύ»ò½øÈ벻·ûºÏԤÆڵļìË÷·¾¶¡£
This commit is contained in:
fengxiangdebaozi committed 2026-06-03 17:36:52 +08:00
1 parent a81d8adc90
commit 56114562cb
6 files changed
+134 -69

No files matched your search

@@ -400,6 +400,47 @@ export function ArtifactFileDetail({
}
}, [displayContent, filepath]);
const handleOpenWriteFileInNewTab = useCallback(() => {
const source = displayContent ?? "";
if (!source.trim()) {
toast.error("文件内容为空,暂时无法打开");
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
: language === "markdown"
? "text/markdown;charset=utf-8"
: "text/plain;charset=utf-8";
const blob = new Blob([source], { type: blobType });
const url = URL.createObjectURL(blob);
window.open(url, "_blank", "noopener,noreferrer");
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
}, [displayContent, language]);
const handleDownloadWriteFile = useCallback(() => {
const source = displayContent ?? "";
if (!source.trim()) {
toast.error("文件内容为空,暂时无法下载");
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
: language === "markdown"
? "text/markdown;charset=utf-8"
: "text/plain;charset=utf-8";
const blob = new Blob([source], { type: blobType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = getFileName(filepath);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, [displayContent, filepath, language]);
return (
<Artifact className={cn(className)}>
<ArtifactHeader className="grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 px-4 py-3">
@@ -500,30 +541,32 @@ export function ArtifactFileDetail({
/>
</Tooltip>
)}
{!isWriteFile && (
<ArtifactAction
icon={SquareArrowOutUpRightIcon}
label={t.common.openInNewWindow}
tooltip={t.common.openInNewWindow}
onClick={async () => {
try {
await openArtifactInNewTab({
filepath,
threadId,
isMock,
publicShareCode,
});
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Failed to open artifact",
);
}
}}
/>
)}
<ArtifactAction
icon={SquareArrowOutUpRightIcon}
label={t.common.openInNewWindow}
tooltip={t.common.openInNewWindow}
onClick={async () => {
if (isWriteFile) {
handleOpenWriteFileInNewTab();
return;
}
try {
await openArtifactInNewTab({
filepath,
threadId,
isMock,
publicShareCode,
});
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Failed to open artifact",
);
}
}}
/>
{isCodeFile && (
<ArtifactAction
icon={CopyIcon}
@@ -550,30 +593,32 @@ export function ArtifactFileDetail({
onClick={handleDownloadAsWord}
/>
)}
{!isWriteFile && (
<ArtifactAction
icon={DownloadIcon}
label={t.common.download}
tooltip={t.common.download}
onClick={async () => {
try {
await downloadArtifactFile({
filepath,
threadId,
isMock,
publicShareCode,
});
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Download failed",
);
}
}}
/>
)}
<ArtifactAction
icon={DownloadIcon}
label={t.common.download}
tooltip={t.common.download}
onClick={async () => {
if (isWriteFile) {
handleDownloadWriteFile();
return;
}
try {
await downloadArtifactFile({
filepath,
threadId,
isMock,
publicShareCode,
});
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Download failed",
);
}
}}
/>
<ArtifactAction
className="hover:bg-background"
icon={XIcon}
@@ -892,9 +892,14 @@ function ClarificationCard({
return;
}
if (!allowMultiple) {
const next = { selectedIds: [optionId], custom: "" };
// 单选:仅当「只有这一张待答澄清卡」时才点击即发送。多张澄清卡同时出现时,
// 点击只记录本卡选择,待最后一张的「全部发送」统一提交——否则点了第一张就立刻
// 提交整批,后面的卡片再也无法选择(见 bug:选了第一个,第二个不让选)。
const next = { selectedIds: [optionId], custom: selection.custom };
onSelectionChange(next);
void onSubmitAll(next);
if (!multiPending) {
void onSubmitAll(next);
}
return;
}
const next = selection.selectedIds.includes(optionId)
@@ -934,7 +939,11 @@ function ClarificationCard({
{options.length > 0 ? (
<div className="mt-4 grid gap-2">
<div className="text-muted-foreground text-xs">
{allowMultiple ? "可多选(再次点击取消选择)" : "请选择一项(点击即发送)"}
{allowMultiple
? "可多选(再次点击取消选择)"
: multiPending
? "请选择一项"
: "请选择一项(点击即发送)"}
</div>
{options.map((option) => {
const optionId = option.id || option.label;
@@ -943,7 +952,7 @@ function ClarificationCard({
<Button
key={optionId}
type="button"
variant={isSelected && allowMultiple ? "default" : "outline"}
variant={isSelected && (allowMultiple || multiPending) ? "default" : "outline"}
className="h-auto justify-start whitespace-normal px-3 py-2 text-left"
disabled={disabled}
onClick={() => handleOptionClick(optionId)}
@@ -963,7 +972,7 @@ function ClarificationCard({
</span>
) : null}
</span>
{isSelected && allowMultiple ? (
{isSelected && (allowMultiple || multiPending) ? (
<CheckIcon className="ml-2 h-4 w-4 shrink-0" />
) : null}
</Button>
@@ -988,10 +997,10 @@ function ClarificationCard({
</div>
) : null}
{isPending && (allowMultiple || payload.allow_custom !== false) ? (
{isPending && (allowMultiple || multiPending || payload.allow_custom !== false) ? (
isLastPending ? (
<div className="mt-3 flex items-center justify-end gap-2">
{multiPending && allowMultiple ? (
{multiPending ? (
<span className="text-muted-foreground text-xs">
将一起提交以上 {pendingCount} 个问题的回答
</span>
@@ -1003,10 +1012,10 @@ function ClarificationCard({
onClick={() => void onSubmitAll()}
>
<SendIcon className="h-4 w-4" />
{submitting ? "发送中…" : multiPending && allowMultiple ? "全部发送" : "发送回答"}
{submitting ? "发送中…" : multiPending ? "全部发送" : "发送回答"}
</Button>
</div>
) : allowMultiple ? (
) : allowMultiple || multiPending ? (
<div className="text-muted-foreground mt-3 text-right text-xs">
选择后等待最后一题统一提交
</div>
@@ -77,16 +77,24 @@ export function WritingSetupChat() {
setThreadPrepared(true);
}, [threadId, threadPrepared]);
// 生成中点提交按钮 = 暂停:取消当前 SSE 流(与主聊天页一致)。
const handleChatSubmit = useCallback(
async (text: string) => {
if (thread.isLoading) {
void thread.stop();
return;
}
const trimmed = text.trim();
if (!trimmed || thread.isLoading) return;
if (!trimmed) return;
await ensureThreadExists();
await sendMessage(threadId, { text: trimmed, files: [] });
},
[ensureThreadExists, sendMessage, thread.isLoading, threadId],
[thread, ensureThreadExists, sendMessage, threadId],
);
// 提交按钮状态:生成中显示「停止」方块,可点击暂停对话
const submitStatus = thread.error ? 'error' : thread.isLoading ? 'streaming' : 'ready';
const handleApprove = useCallback(
async (payload: SetupWritingApprovalPayload) => {
applyProposal(payload);
@@ -150,10 +158,9 @@ export function WritingSetupChat() {
<PromptInput onSubmit={({ text }) => void handleChatSubmit(text)}>
<PromptInputTextarea
placeholder="例如:帮我写一篇 2000 字、面向行业专家的新能源汽车出口分析"
disabled={thread.isLoading}
/>
<PromptInputFooter className="justify-end">
<PromptInputSubmit disabled={thread.isLoading} />
<PromptInputSubmit status={submitStatus} />
</PromptInputFooter>
</PromptInput>
</div>