fix(frontend-workspace): 修复引用滚动与产物路径解析

This commit is contained in:
肖应宇 2026-04-24 17:04:51 +08:00 committed by MT-Fire
parent d8226b834c
commit 74813ff61d
3 changed files with 57 additions and 23 deletions

View File

@ -16,7 +16,7 @@ function ScrollArea({
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport

View File

@ -430,7 +430,7 @@ export function ArtifactFileDetail({
type="single"
variant={null}
size="default"
className="h-[28px] bg-ws-surface-base"
className="bg-ws-surface-base h-[28px]"
value={viewMode}
onValueChange={(value) => {
if (value) {
@ -734,7 +734,7 @@ export function ArtifactFileDetail({
/>
)}
{isCodeFile && viewMode === "code" && (
<div className="mb-0 mb-[207px] min-h-full rounded-b-[10px] bg-ws-surface-base p-0">
<div className="bg-ws-surface-base mb-0 mb-[207px] min-h-full rounded-b-[10px] p-0">
<CodeEditor
className="size-full resize-none rounded-none border-none py-[20px]"
value={displayContent ?? ""}
@ -917,7 +917,7 @@ export function ArtifactFilePreview({
if (language === "markdown") {
return (
<div
className={cn("mb-[207px] w-full bg-ws-surface-base p-[20px]")}
className={cn("bg-ws-surface-base mb-[207px] w-full p-[20px]")}
style={{ "--zoom-scale": zoomScale } as CSSProperties}
>
<Streamdown
@ -974,7 +974,7 @@ function PreviewIframe({
{...props}
/>
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-surface-base/85">
<div className="bg-ws-surface-base/85 absolute inset-0 z-10 flex items-center justify-center">
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
</div>
)}
@ -1089,8 +1089,13 @@ function ArtifactPdfPreview({
if (error) {
return (
<div className={cn("relative overflow-auto bg-ws-surface-subtle p-4", className)}>
<div className="mx-auto grid max-w-xl gap-3 rounded-md border border-ws-line-default bg-ws-surface-base p-5 text-center">
<div
className={cn(
"bg-ws-surface-subtle relative overflow-auto p-4",
className,
)}
>
<div className="border-ws-line-default bg-ws-surface-base mx-auto grid max-w-xl gap-3 rounded-md border p-5 text-center">
<p className="text-sm font-medium break-all">{fileName}</p>
<p className="text-muted-foreground text-sm">{error}</p>
<a
@ -1107,15 +1112,20 @@ function ArtifactPdfPreview({
}
return (
<div className={cn("relative overflow-auto bg-ws-surface-subtle p-4", className)}>
<div className="mb-3 text-center text-xs text-ws-text-muted">
<div
className={cn(
"bg-ws-surface-subtle relative overflow-auto p-4",
className,
)}
>
<div className="text-ws-text-muted mb-3 text-center text-xs">
{pageCount > 0
? t.artifactPreview.pageCountLabel(fileName, pageCount)
: fileName}
</div>
<div ref={containerRef} />
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-surface-base/70">
<div className="bg-ws-surface-base/70 absolute inset-0 z-10 flex items-center justify-center">
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
</div>
)}
@ -1313,7 +1323,12 @@ function ArtifactOfficePreview({
}, [canRenderPptx, t.artifactPreview.pptxDownloadHint]);
return (
<div className={cn("relative h-full overflow-hidden bg-ws-surface-base", className)}>
<div
className={cn(
"bg-ws-surface-base relative h-full overflow-hidden",
className,
)}
>
{canRenderXlsx && sheetNames.length > 0 && (
<div className="border-border flex items-center gap-1 overflow-x-auto border-b p-2">
{sheetNames.map((sheetName) => (
@ -1357,7 +1372,7 @@ function ArtifactOfficePreview({
/>
)}
{isLoading && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-ws-surface-base/85">
<div className="bg-ws-surface-base/85 absolute inset-0 z-10 flex items-center justify-center">
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
</div>
)}
@ -1376,7 +1391,7 @@ function ArtifactPreviewFallback({
}) {
const { t } = useI18n();
return (
<div className="absolute inset-0 z-20 grid place-content-center bg-ws-surface-base p-6 text-center">
<div className="bg-ws-surface-base absolute inset-0 z-20 grid place-content-center p-6 text-center">
<p className="text-foreground mb-2 text-sm font-medium">{fileName}</p>
<p className="text-muted-foreground mb-3 text-xs">{message}</p>
<a
@ -1400,9 +1415,23 @@ function rewriteArtifactImagePaths(
return content;
}
const encodeVirtualPath = (path: string) =>
path
.split("/")
.map((segment) => {
try {
return encodeURIComponent(decodeURIComponent(segment));
} catch {
return encodeURIComponent(segment);
}
})
.join("/");
const toArtifactUrl = (rawPath: string) => {
const normalizedPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
return resolveArtifactURL(normalizedPath, threadId);
const trimmedPath = rawPath.trim();
const normalizedPath = trimmedPath.startsWith("/")
? trimmedPath
: `/${trimmedPath}`;
return resolveArtifactURL(encodeVirtualPath(normalizedPath), threadId);
};
const toArtifactUrlFromRelative = (rawPath: string) => {
const trimmed = rawPath.trim();
@ -1416,17 +1445,17 @@ function rewriteArtifactImagePaths(
const absolutePath = new URL(trimmed, `file://${baseDir}`).pathname;
if (!absolutePath.startsWith("/mnt/user-data/")) return null;
return resolveArtifactURL(absolutePath, threadId);
return resolveArtifactURL(encodeVirtualPath(absolutePath), threadId);
};
const markdownRewritten = content.replace(
/!\[([^\]]*)\]\(\s*(\/?mnt\/user-data\/outputs\/[^)\s]+)\s*\)/g,
/!\[([^\]]*)\]\(\s*(\/?mnt\/user-data\/(?:outputs|uploads)\/[^)]+?)\s*\)/g,
(_full, alt, rawPath) => {
return `![${alt}](${toArtifactUrl(rawPath)})`;
},
);
const markdownRelativeRewritten = markdownRewritten.replace(
/!\[([^\]]*)\]\(\s*([^) \t]+)\s*\)/g,
/!\[([^\]]*)\]\(\s*([^)]+?)\s*\)/g,
(_full, alt, rawPath) => {
const absoluteUrl = toArtifactUrlFromRelative(rawPath);
if (!absoluteUrl) {
@ -1437,7 +1466,7 @@ function rewriteArtifactImagePaths(
);
const shorthandMarkdownRewritten = markdownRelativeRewritten.replace(
/!(?!\[)([^\n()]+?)\s*[(]\s*(\/?mnt\/user-data\/outputs\/[^)\s]+)\s*[)]/g,
/!(?!\[)([^\n()]+?)\s*[(]\s*(\/?mnt\/user-data\/(?:outputs|uploads)\/[^)]+?)\s*[)]/g,
(_full, alt, rawPath) => {
return `![${String(alt).trim()}](${toArtifactUrl(rawPath)})`;
},
@ -1446,7 +1475,7 @@ function rewriteArtifactImagePaths(
return shorthandMarkdownRewritten.replace(
/(<img\b[^>]*\bsrc\s*=\s*)(["'])([^"']+)\2/gi,
(_full, prefix, quote, rawPath) => {
if (/^\/?mnt\/user-data\/outputs\//.test(rawPath)) {
if (/^\/?mnt\/user-data\/(?:outputs|uploads)\//.test(rawPath)) {
return `${prefix}${quote}${toArtifactUrl(rawPath)}${quote}`;
}
const absoluteUrl = toArtifactUrlFromRelative(rawPath);
@ -1736,7 +1765,12 @@ export const ArtifactZoomSelector = ({
viewBox="0 0 16 16"
fill="none"
>
<circle cx="7.55558" cy="7.55534" r="6.16667" stroke="currentColor" />
<circle
cx="7.55558"
cy="7.55534"
r="6.16667"
stroke="currentColor"
/>
<path
d="M13.8688 15.4646C14.064 15.6598 14.3806 15.6598 14.5759 15.4646C14.7711 15.2693 14.7711 14.9527 14.5759 14.7574L14.2223 15.111L13.8688 15.4646ZM14.2223 15.111L14.5759 14.7574L11.9092 12.0908L11.5557 12.4443L11.2021 12.7979L13.8688 15.4646L14.2223 15.111Z"
fill="currentColor"

View File

@ -893,8 +893,8 @@ export function InputBox({
{t.inputBox.addReference}
</DropdownMenuLabel>
<DropdownMenuSeparator className="mx-0 mt-[20px] mb-0" />
<DropdownMenuGroup className="flex max-h-[480px] flex-col gap-[10px] px-0 pt-[20px]">
<ScrollArea className="h-[480px]" data-state="hidden">
<DropdownMenuGroup className="flex min-h-0 flex-col gap-[10px] px-0">
<ScrollArea className="h-[320px] pt-[20px]" hideScrollbar={false}>
{filteredMentionCandidates.map((candidate, index) => {
const detail = [candidate.typeLabel, candidate.pathTail]
.filter(Boolean)