fix(frontend): 岗位会商沙箱在 write_file 流式阶段实时打开并刷新
content 先于 path 到达时用临时文件名开沙箱,流式快照改走外部 store 避免整页重渲染卡顿。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
e939dc4548
commit
69e455274a
5 files changed
+480
-117
No files matched your search
@@ -121,8 +121,9 @@ function findScrollableArtifactElement(root: HTMLElement | null) {
|
||||
|
||||
export function ArtifactFileDetail({
|
||||
className,
|
||||
filepath: filepathFromProps,
|
||||
threadId,
|
||||
filepath: filepathFromProps,
|
||||
threadId,
|
||||
mimeType = null,
|
||||
onClose,
|
||||
allowVirtualMarkdownEdit = false,
|
||||
disableDesensitize = false,
|
||||
@@ -131,6 +132,8 @@ export function ArtifactFileDetail({
|
||||
className?: string;
|
||||
filepath: string;
|
||||
threadId: string;
|
||||
/** Server/tool supplied type wins over path guessing when available. */
|
||||
mimeType?: string | null;
|
||||
/** 覆盖默认关闭行为(仅 setOpen(false));圆桌 Step3 文件预览用于退出沙箱模式。 */
|
||||
onClose?: () => void;
|
||||
/**
|
||||
@@ -150,16 +153,38 @@ export function ArtifactFileDetail({
|
||||
const { t } = useI18n();
|
||||
const { artifacts, setOpen, select } = useArtifacts();
|
||||
const { isMock, thread, publicShareCode } = useThread();
|
||||
const isWriteFileVirtual = useMemo(() => {
|
||||
return filepathFromProps.startsWith("write-file:");
|
||||
}, [filepathFromProps]);
|
||||
const filepath = useMemo(() => {
|
||||
if (isWriteFileVirtual) {
|
||||
const url = new URL(filepathFromProps);
|
||||
return decodeURIComponent(url.pathname);
|
||||
}
|
||||
return filepathFromProps;
|
||||
}, [filepathFromProps, isWriteFileVirtual]);
|
||||
const isWriteFileVirtual = useMemo(() => {
|
||||
return filepathFromProps.startsWith("write-file:");
|
||||
}, [filepathFromProps]);
|
||||
// A streamed tool call can be opened before the path has been normalized by
|
||||
// the artifacts API. Keep the format supplied by the writer so Markdown is
|
||||
// rendered as Markdown instead of falling back to a TEXT source preview.
|
||||
const writeFileLanguageHint = useMemo(() => {
|
||||
if (!isWriteFileVirtual) return null;
|
||||
try {
|
||||
const language = new URL(filepathFromProps).searchParams.get("language");
|
||||
return language === "markdown" ? language : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [filepathFromProps, isWriteFileVirtual]);
|
||||
const mimeLanguageHint = useMemo(() => {
|
||||
const normalized = mimeType?.toLowerCase().trim() ?? "";
|
||||
if (normalized.includes("markdown")) return "markdown";
|
||||
if (normalized.includes("html")) return "html";
|
||||
if (normalized.includes("mermaid")) return "mermaid";
|
||||
if (normalized.startsWith("text/")) return "text";
|
||||
return null;
|
||||
}, [mimeType]);
|
||||
const filepath = useMemo(() => {
|
||||
if (isWriteFileVirtual) {
|
||||
const url = new URL(filepathFromProps);
|
||||
return decodeURIComponent(url.pathname).trim();
|
||||
}
|
||||
// Tool arguments may contain harmless surrounding whitespace. Passing it
|
||||
// into extension detection turns `report.md ` into a generic TEXT file.
|
||||
return filepathFromProps.trim();
|
||||
}, [filepathFromProps, isWriteFileVirtual]);
|
||||
const isSkillFile = useMemo(() => {
|
||||
return filepath.endsWith(".skill");
|
||||
}, [filepath]);
|
||||
@@ -186,17 +211,22 @@ export function ArtifactFileDetail({
|
||||
const useWriteFileStreaming = useMemo(() => {
|
||||
return isWriteFileVirtual && !isImageFile(filepath) && !editVirtualMarkdown;
|
||||
}, [filepath, isWriteFileVirtual, editVirtualMarkdown]);
|
||||
const { isCodeFile, language } = useMemo(() => {
|
||||
if (useWriteFileStreaming) {
|
||||
let nextLanguage = checkCodeFile(filepath).language;
|
||||
nextLanguage ??= "text";
|
||||
return { isCodeFile: true, language: nextLanguage };
|
||||
const { isCodeFile, language } = useMemo(() => {
|
||||
if (useWriteFileStreaming) {
|
||||
let nextLanguage =
|
||||
mimeLanguageHint ?? writeFileLanguageHint ?? checkCodeFile(filepath).language;
|
||||
nextLanguage ??= "text";
|
||||
return { isCodeFile: true, language: nextLanguage };
|
||||
}
|
||||
if (isSkillFile) {
|
||||
return { isCodeFile: true, language: "markdown" };
|
||||
}
|
||||
return checkCodeFile(filepath);
|
||||
}, [filepath, useWriteFileStreaming, isSkillFile]);
|
||||
const detected = checkCodeFile(filepath);
|
||||
if (mimeLanguageHint) {
|
||||
return { isCodeFile: true as const, language: mimeLanguageHint };
|
||||
}
|
||||
return detected;
|
||||
}, [filepath, useWriteFileStreaming, isSkillFile, mimeLanguageHint, writeFileLanguageHint]);
|
||||
const isSupportPreview = useMemo(() => {
|
||||
return (
|
||||
language === "html" ||
|
||||
@@ -209,7 +239,8 @@ export function ArtifactFileDetail({
|
||||
// editVirtualMarkdown:用解码后的真实路径走 artifacts API(而非 write-file 虚拟
|
||||
// URL),这样保存后 useUpdateArtifactContent 写入的查询缓存才能命中,编辑内容
|
||||
// 不会被旧的内存 stub 值覆盖回退。
|
||||
filepath: editVirtualMarkdown ? filepath : filepathFromProps,
|
||||
filepath:
|
||||
editVirtualMarkdown || !isWriteFileVirtual ? filepath : filepathFromProps,
|
||||
enabled: isCodeFile && !useWriteFileStreaming,
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user