代码修改

This commit is contained in:
fengxiangdebaozi committed 2026-06-05 18:58:28 +08:00
1 parent 9bbd04d6cf
commit b0fdeab995
15 files changed
+467 -90

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
.sidebar-contnet {
position: fixed;
position: absolute;
left: 0;
top: 4rem;
z-index: 40;
@@ -48,11 +48,17 @@ import { useI18n } from "@/core/i18n/hooks";
import { downloadMarkdownAsDocx } from "@/core/scheduled-tasks/markdown-docx";
import { installSkill } from "@/core/skills/api";
import { streamdownPlugins } from "@/core/streamdown";
import {
checkCodeFile,
getFileName,
isImageFile,
} from "@/core/utils/files";
import {
checkCodeFile,
getFileName,
isImageFile,
} from "@/core/utils/files";
import {
downloadMermaidSvgFile,
isMermaidFile,
openMermaidSvgInNewTab,
renderMermaidToSvg,
} from "@/core/utils/mermaid";
import { getBackendBaseURL } from "@/core/config";
import { resolveArtifactURL } from "@/core/artifacts/utils";
import { env } from "@/env";
@@ -129,10 +135,14 @@ export function ArtifactFileDetail({
return { isCodeFile: true, language: "markdown" };
}
return checkCodeFile(filepath);
}, [filepath, useWriteFileStreaming, isSkillFile]);
const isSupportPreview = useMemo(() => {
return language === "html" || language === "markdown";
}, [language]);
}, [filepath, useWriteFileStreaming, isSkillFile]);
const isSupportPreview = useMemo(() => {
return (
language === "html" ||
language === "markdown" ||
language === "mermaid"
);
}, [language]);
const { content, isApplyingEdit } = useArtifactContent({
threadId,
filepath: filepathFromProps,
@@ -150,11 +160,12 @@ export function ArtifactFileDetail({
const displayContent = canEditArtifact ? draftContent : (content ?? "");
const isHtmlStreaming = language === "html" && thread.isLoading;
const hasMultipleArtifacts = (artifacts?.length ?? 0) > 1;
const fileTypeLabel = useMemo(() => {
if (isImageFile(filepath)) return "IMG";
if (language === "html") return "HTML";
if (language === "markdown") return "MD";
if (isSkillFile) return "SKILL";
const fileTypeLabel = useMemo(() => {
if (isImageFile(filepath)) return "IMG";
if (isMermaidFile(filepath) || language === "mermaid") return "MMD";
if (language === "html") return "HTML";
if (language === "markdown") return "MD";
if (isSkillFile) return "SKILL";
if (language) return language.toUpperCase();
return "FILE";
}, [filepath, isSkillFile, language]);
@@ -423,9 +434,16 @@ export function ArtifactFileDetail({
toast.error("文件内容为空,暂时无法打开");
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
if (language === "mermaid") {
void openMermaidSvgInNewTab(source).catch((error) => {
console.error("Failed to open mermaid artifact:", error);
toast.error(error instanceof Error ? error.message : "Failed to open artifact");
});
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
: language === "markdown"
? "text/markdown;charset=utf-8"
: "text/plain;charset=utf-8";
@@ -435,15 +453,22 @@ export function ArtifactFileDetail({
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
}, [displayContent, language]);
const handleDownloadWriteFile = useCallback(() => {
const source = displayContent ?? "";
const handleDownloadWriteFile = useCallback(() => {
const source = displayContent ?? "";
if (!source.trim()) {
toast.error("文件内容为空,暂时无法下载");
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
if (language === "mermaid") {
void downloadMermaidSvgFile(source, filepath).catch((error) => {
console.error("Failed to download mermaid artifact:", error);
toast.error(error instanceof Error ? error.message : "Download failed");
});
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
: language === "markdown"
? "text/markdown;charset=utf-8"
: "text/plain;charset=utf-8";
@@ -562,14 +587,27 @@ export function ArtifactFileDetail({
icon={SquareArrowOutUpRightIcon}
label={t.common.openInNewWindow}
tooltip={t.common.openInNewWindow}
onClick={async () => {
if (useWriteFileStreaming) {
handleOpenWriteFileInNewTab();
return;
}
try {
await openArtifactInNewTab({
filepath,
onClick={async () => {
if (useWriteFileStreaming) {
handleOpenWriteFileInNewTab();
return;
}
if (language === "mermaid") {
try {
await openMermaidSvgInNewTab(displayContent ?? "");
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Failed to open artifact",
);
}
return;
}
try {
await openArtifactInNewTab({
filepath,
threadId,
isMock,
publicShareCode,
@@ -614,14 +652,27 @@ export function ArtifactFileDetail({
icon={DownloadIcon}
label={t.common.download}
tooltip={t.common.download}
onClick={async () => {
if (useWriteFileStreaming) {
handleDownloadWriteFile();
return;
}
try {
await downloadArtifactFile({
filepath,
onClick={async () => {
if (useWriteFileStreaming) {
handleDownloadWriteFile();
return;
}
if (language === "mermaid") {
try {
await downloadMermaidSvgFile(displayContent ?? "", filepath);
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Download failed",
);
}
return;
}
try {
await downloadArtifactFile({
filepath,
threadId,
isMock,
publicShareCode,
@@ -666,13 +717,15 @@ export function ArtifactFileDetail({
<ArrowDownIcon className="size-4" />
滑动到底部并继续自动下滑
</button>
)}
{isSupportPreview &&
viewMode === "preview" &&
(language === "markdown" || language === "html") && (
<ArtifactFilePreview
content={desensitize(displayContent ?? "")}
language={language ?? "text"}
)}
{isSupportPreview &&
viewMode === "preview" &&
(language === "markdown" ||
language === "html" ||
language === "mermaid") && (
<ArtifactFilePreview
content={desensitize(displayContent ?? "")}
language={language ?? "text"}
threadId={threadId}
publicShareCode={publicShareCode}
animated={showPreviewSwitchPulse}
@@ -768,13 +821,15 @@ export function ArtifactFilePreview({
publicShareCode?: string;
animated?: boolean;
activeCursor?: boolean;
}) {
const [htmlPreviewUrl, setHtmlPreviewUrl] = useState<string>();
}) {
const [htmlPreviewUrl, setHtmlPreviewUrl] = useState<string>();
const [mermaidSvg, setMermaidSvg] = useState<string | null>(null);
const [mermaidError, setMermaidError] = useState<string | null>(null);
useEffect(() => {
if (language !== "html") {
setHtmlPreviewUrl(undefined);
return;
useEffect(() => {
if (language !== "html") {
setHtmlPreviewUrl(undefined);
return;
}
// 必须带 charset=utf-8:blob: URL 的 Content-Type 不含 charset 时,iframe 会按
@@ -785,9 +840,40 @@ export function ArtifactFilePreview({
setHtmlPreviewUrl(url);
return () => {
URL.revokeObjectURL(url);
};
}, [content, language]);
URL.revokeObjectURL(url);
};
}, [content, language]);
useEffect(() => {
if (language !== "mermaid") {
setMermaidSvg(null);
setMermaidError(null);
return;
}
let cancelled = false;
setMermaidSvg(null);
setMermaidError(null);
void renderMermaidToSvg(content ?? "")
.then((svg) => {
if (!cancelled) {
setMermaidSvg(svg);
}
})
.catch((error) => {
console.error("Failed to render mermaid preview:", error);
if (!cancelled) {
setMermaidError(
error instanceof Error ? error.message : "Mermaid 渲染失败",
);
}
});
return () => {
cancelled = true;
};
}, [content, language]);
if (language === "markdown") {
return (
@@ -821,9 +907,9 @@ export function ArtifactFilePreview({
);
}
if (language === "html") {
return (
<iframe
if (language === "html") {
return (
<iframe
className={cn(
"size-full bg-white",
animated && "animate-[sandbox-preview-enter_320ms_ease-out]",
@@ -832,8 +918,41 @@ export function ArtifactFilePreview({
sandbox="allow-scripts allow-forms"
src={htmlPreviewUrl}
/>
);
}
return null;
}
);
}
if (language === "mermaid") {
return (
<div
data-artifact-preview-scroll
className={cn(
"size-full overflow-auto bg-[#fcfdfc] px-5 py-4",
animated && "animate-[sandbox-preview-enter_320ms_ease-out]",
)}
>
{mermaidSvg ? (
<div
className="mx-auto flex min-h-full items-start justify-center rounded-2xl border border-slate-200 bg-white p-4 shadow-sm"
dangerouslySetInnerHTML={{ __html: mermaidSvg }}
/>
) : mermaidError ? (
<div className="space-y-3">
<div className="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
{mermaidError}
</div>
<pre className="whitespace-pre-wrap break-words rounded-xl bg-slate-100 p-4 text-xs text-slate-700">
{content ?? ""}
</pre>
</div>
) : (
<div className="text-muted-foreground flex size-full items-center justify-center gap-2 text-sm">
<LoaderIcon className="size-5 animate-spin" />
正在生成图表预览
</div>
)}
</div>
);
}
return null;
}