feat(roundtable): 第二/三步沙箱 md 文件支持用户编辑

复用主聊天页同款 ArtifactFileDetail 编辑能力:新增 allowVirtualMarkdownEdit
开关,圆桌沙箱里 write-file 虚拟 md 在流式结束后切回真实 artifacts API
(可加载 + PATCH 保存),由 Step2SandboxLayout 与 HighFidelityReport 启用。
主聊天页等其它调用方默认关闭、行为不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-15 12:57:18 +08:00
1 parent 8751ee7361
commit 49101ef0e8
3 files changed
+31 -5

No files matched your search

@@ -121,12 +121,19 @@ export function ArtifactFileDetail({
filepath: filepathFromProps,
threadId,
onClose,
allowVirtualMarkdownEdit = false,
}: {
className?: string;
filepath: string;
threadId: string;
/** 覆盖默认关闭行为(仅 setOpen(false));圆桌 Step3 文件预览用于退出沙箱模式。 */
onClose?: () => void;
/**
* 圆桌沙箱(Step2/Step3)用:流式结束后,把 `write-file:` 虚拟 md 预览切回真实
* artifacts API(可加载 + PATCH 保存),从而支持用户编辑(对齐主聊天页 md 可编辑)。
* 默认 false —— 主聊天页等其它调用方行为不变。
*/
allowVirtualMarkdownEdit?: boolean;
}) {
const { t } = useI18n();
const { artifacts, setOpen, select } = useArtifacts();
@@ -141,10 +148,6 @@ export function ArtifactFileDetail({
}
return filepathFromProps;
}, [filepathFromProps, isWriteFileVirtual]);
/** write_file 流式预览走 tool args 文本;图片等二进制产物始终走 artifacts API。 */
const useWriteFileStreaming = useMemo(() => {
return isWriteFileVirtual && !isImageFile(filepath);
}, [filepath, isWriteFileVirtual]);
const isSkillFile = useMemo(() => {
return filepath.endsWith(".skill");
}, [filepath]);
@@ -153,6 +156,24 @@ export function ArtifactFileDetail({
const isSpreadsheetFile = useMemo(() => {
return /\.xlsx$/i.test(filepath);
}, [filepath]);
// 圆桌沙箱:write_file 虚拟预览的 md 在流式结束(thread 非 loading)后,切回真实
// artifacts API 加载并允许编辑/保存。流式进行中仍走内存 stub 只读预览;仅
// allowVirtualMarkdownEdit 开启的调用方(Step2/Step3 沙箱)生效,主聊天页不受影响。
const editVirtualMarkdown = useMemo(() => {
return (
allowVirtualMarkdownEdit &&
isWriteFileVirtual &&
!isImageFile(filepath) &&
!isMock &&
!thread.isLoading &&
checkCodeFile(filepath).language === "markdown"
);
}, [allowVirtualMarkdownEdit, isWriteFileVirtual, filepath, isMock, thread.isLoading]);
/** write_file 流式预览走 tool args 文本;图片等二进制产物始终走 artifacts API;
* 圆桌结束后可编辑的 md(editVirtualMarkdown)亦回到真实 API(可加载 + PATCH 保存)。 */
const useWriteFileStreaming = useMemo(() => {
return isWriteFileVirtual && !isImageFile(filepath) && !editVirtualMarkdown;
}, [filepath, isWriteFileVirtual, editVirtualMarkdown]);
const { isCodeFile, language } = useMemo(() => {
if (useWriteFileStreaming) {
let nextLanguage = checkCodeFile(filepath).language;
@@ -173,7 +194,10 @@ export function ArtifactFileDetail({
}, [language]);
const { content, isApplyingEdit } = useArtifactContent({
threadId,
filepath: filepathFromProps,
// editVirtualMarkdown:用解码后的真实路径走 artifacts API(而非 write-file 虚拟
// URL),这样保存后 useUpdateArtifactContent 写入的查询缓存才能命中,编辑内容
// 不会被旧的内存 stub 值覆盖回退。
filepath: editVirtualMarkdown ? filepath : filepathFromProps,
enabled: isCodeFile && !useWriteFileStreaming,
});
@@ -1009,6 +1009,7 @@ export const HighFidelityReport: React.FC<HighFidelityReportProps> = ({
filepath={fileSandboxVirtualUrl}
threadId={fileSandboxTarget.threadId}
onClose={handleCloseFileSandbox}
allowVirtualMarkdownEdit
/>
</ThreadContext.Provider>
)}
@@ -115,6 +115,7 @@ export function Step2SandboxLayout({
filepath={selectedArtifact}
threadId={previewThreadId}
onClose={artifactsFill && onArtifactsClose ? handleArtifactsClose : undefined}
allowVirtualMarkdownEdit
/>
) : (
<div className="relative flex size-full justify-center">