fix: restore embedded wiki images and optimize vector sync
This commit is contained in:
1 parent
30943159bf
commit
0c5e2e8cb0
23 files changed
+609
-136
No files matched your search
@@ -37,6 +37,34 @@ const WIKI_INDEX_TYPE_LABELS: Record<string, string> = {
|
||||
|
||||
const WIKI_INDEX_TYPE_ORDER = ["summary", "entity", "concept"];
|
||||
|
||||
export const WIKI_PROTECTED_IMAGE_MARKER = "/__llmwiki-protected-image__";
|
||||
|
||||
const PROTECTED_WIKI_IMAGE_SOURCE =
|
||||
/(?:storage:\/\/[0-9A-Za-z_-]+\/)?(?:resource|local|minio|cos|tos|s3|oss|ks3|obs):\/\/[^\s"'<>\])]+/gi;
|
||||
|
||||
/**
|
||||
* Browsers cannot load WeKnora's protected resource:// and object-storage
|
||||
* schemes directly. Replace them with a safe marker before Markdown
|
||||
* sanitization; the image component resolves that marker through DeerFlow.
|
||||
*/
|
||||
export function rewriteProtectedWikiImageSources(markdown: string): string {
|
||||
return markdown.replace(
|
||||
PROTECTED_WIKI_IMAGE_SOURCE,
|
||||
(filePath) => `${WIKI_PROTECTED_IMAGE_MARKER}?file_path=${encodeURIComponent(filePath)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function parseProtectedWikiImageSource(source: string | undefined): string | null {
|
||||
if (!source) return null;
|
||||
try {
|
||||
const url = new URL(source, window.location.origin);
|
||||
if (url.pathname !== WIKI_PROTECTED_IMAGE_MARKER) return null;
|
||||
return url.searchParams.get("file_path")?.trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function stripWikiFrontmatter(value: string) {
|
||||
return value.replace(/^\s*---\s*\n[\s\S]*?\n---\s*(?:\n|$)/, "").trim();
|
||||
}
|
||||
|
||||
@@ -444,6 +444,23 @@ export async function getLlmWikiDocumentPreview(
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
export async function getLlmWikiProtectedFile(
|
||||
knowledgeBaseId: string,
|
||||
filePath: string,
|
||||
publicAccess = false,
|
||||
): Promise<Blob> {
|
||||
const base = publicAccess ? PUBLIC_API() : API();
|
||||
const params = new URLSearchParams({ file_path: filePath });
|
||||
const response = await apiFetch(
|
||||
`${base}/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/files?${params.toString()}`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
await readJson<void>(response, "读取 Wiki 图片");
|
||||
throw new Error("读取 Wiki 图片失败");
|
||||
}
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
export async function getLlmWikiGraph(knowledgeBaseId: string): Promise<LlmWikiGraph> {
|
||||
return readJson(
|
||||
await apiFetch(`${API()}/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/graph?limit=200`),
|
||||
|
||||
+20
-2
@@ -33,6 +33,7 @@ import { reasoningPlugins } from "@/core/streamdown";
|
||||
import {
|
||||
exportableWikiMarkdown,
|
||||
renderableWikiMarkdown,
|
||||
rewriteProtectedWikiImageSources,
|
||||
safeWikiFileName,
|
||||
stripWikiFrontmatter,
|
||||
wikiLinkMarkdown,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
type LlmWikiDocument,
|
||||
type LlmWikiWikiPage,
|
||||
} from "@/strategy-components/api/llmwiki";
|
||||
import { WikiProtectedImage } from "./WikiProtectedImage";
|
||||
|
||||
type SourceContextResponse = {
|
||||
success?: boolean;
|
||||
@@ -926,10 +928,18 @@ export function WeKnoraSourceDrawer({
|
||||
</div>
|
||||
<MarkdownContent
|
||||
className="max-w-none"
|
||||
content={preview.text}
|
||||
content={rewriteProtectedWikiImageSources(preview.text)}
|
||||
isLoading={false}
|
||||
remarkPlugins={reasoningPlugins.remarkPlugins}
|
||||
rehypePlugins={reasoningPlugins.rehypePlugins}
|
||||
components={{
|
||||
img: (props) => (
|
||||
<WikiProtectedImage
|
||||
{...props}
|
||||
knowledgeBaseId={target?.knowledgeBaseId || ""}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</article>
|
||||
</section>
|
||||
@@ -964,10 +974,18 @@ export function WeKnoraSourceDrawer({
|
||||
<div className="rounded-lg border bg-background p-5">
|
||||
<MarkdownContent
|
||||
className="max-w-none"
|
||||
content={wikiDisplayContent}
|
||||
content={rewriteProtectedWikiImageSources(wikiDisplayContent)}
|
||||
isLoading={false}
|
||||
remarkPlugins={reasoningPlugins.remarkPlugins}
|
||||
rehypePlugins={reasoningPlugins.rehypePlugins}
|
||||
components={{
|
||||
img: (props) => (
|
||||
<WikiProtectedImage
|
||||
{...props}
|
||||
knowledgeBaseId={target?.knowledgeBaseId || ""}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
linkClassName={(href) =>
|
||||
parseWeKnoraPreviewUrl(href)?.wikiSlug
|
||||
? "wiki-content-link font-medium text-[#be2323] decoration-[#be2323]/55 decoration-dashed hover:decoration-solid"
|
||||
|
||||
+13
-2
@@ -35,6 +35,7 @@ import { markdownToDocxBlob } from "@/core/scheduled-tasks/markdown-docx";
|
||||
import {
|
||||
exportableWikiMarkdown,
|
||||
renderableWikiMarkdown,
|
||||
rewriteProtectedWikiImageSources,
|
||||
safeWikiFileName,
|
||||
stripWikiFrontmatter,
|
||||
wikiLinkMarkdown,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
usePublicLlmWikiWikiPage,
|
||||
usePublicLlmWikiWikiPages,
|
||||
} from "@/strategy-components/api/llmwiki";
|
||||
import { WikiProtectedImage } from "./WikiProtectedImage";
|
||||
|
||||
const LIST_PATH = "/page/workspace/knowledge";
|
||||
|
||||
@@ -209,7 +211,7 @@ export function WeKnoraWikiPageView({
|
||||
: pageData?.content,
|
||||
));
|
||||
const displayMarkdown = pageData
|
||||
? renderableWikiMarkdown(
|
||||
? rewriteProtectedWikiImageSources(renderableWikiMarkdown(
|
||||
title,
|
||||
content,
|
||||
noteId,
|
||||
@@ -222,7 +224,7 @@ export function WeKnoraWikiPageView({
|
||||
: undefined,
|
||||
publicEmbed ? pagesList : [],
|
||||
publicEmbed ? publicWikiIndex.data?.groups || [] : [],
|
||||
)
|
||||
))
|
||||
: "";
|
||||
const exportMarkdown = pageData
|
||||
? wikiLinkMarkdown(exportableWikiMarkdown(title, summary, content), noteId)
|
||||
@@ -497,6 +499,15 @@ export function WeKnoraWikiPageView({
|
||||
isLoading={false}
|
||||
remarkPlugins={reasoningPlugins.remarkPlugins}
|
||||
rehypePlugins={reasoningPlugins.rehypePlugins}
|
||||
components={{
|
||||
img: (props) => (
|
||||
<WikiProtectedImage
|
||||
{...props}
|
||||
knowledgeBaseId={noteId}
|
||||
publicAccess={publicEmbed}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
linkClassName={(href) =>
|
||||
publicEmbed && publicWikiTargetFromHref(href, noteId)
|
||||
? "wiki-content-link border-b border-dashed !border-[var(--primary)] font-medium !text-primary !no-underline hover:border-solid focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { ImageOff, Loader2 } from "lucide-react";
|
||||
import { type ImgHTMLAttributes, useEffect, useState } from "react";
|
||||
|
||||
import { parseProtectedWikiImageSource } from "@/core/llmwiki/wiki-page-markdown";
|
||||
import { getLlmWikiProtectedFile } from "@/strategy-components/api/llmwiki";
|
||||
|
||||
type WikiProtectedImageProps = ImgHTMLAttributes<HTMLImageElement> & {
|
||||
knowledgeBaseId: string;
|
||||
publicAccess?: boolean;
|
||||
};
|
||||
|
||||
export function WikiProtectedImage({
|
||||
knowledgeBaseId,
|
||||
publicAccess = false,
|
||||
src,
|
||||
alt,
|
||||
className,
|
||||
...props
|
||||
}: WikiProtectedImageProps) {
|
||||
const filePath = parseProtectedWikiImageSource(src);
|
||||
const [objectUrl, setObjectUrl] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!filePath || !knowledgeBaseId) {
|
||||
setObjectUrl("");
|
||||
setError("");
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
let nextObjectUrl = "";
|
||||
setObjectUrl("");
|
||||
setError("");
|
||||
void getLlmWikiProtectedFile(knowledgeBaseId, filePath, publicAccess)
|
||||
.then((blob) => {
|
||||
if (cancelled) return;
|
||||
nextObjectUrl = URL.createObjectURL(blob);
|
||||
setObjectUrl(nextObjectUrl);
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
if (!cancelled) {
|
||||
setError(reason instanceof Error ? reason.message : "Wiki 图片加载失败");
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (nextObjectUrl) URL.revokeObjectURL(nextObjectUrl);
|
||||
};
|
||||
}, [filePath, knowledgeBaseId, publicAccess]);
|
||||
|
||||
if (!filePath) {
|
||||
return <img {...props} src={src} alt={alt} className={className} />;
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<span
|
||||
className="my-3 flex min-h-24 items-center justify-center gap-2 rounded-lg border border-dashed bg-muted/40 px-4 text-sm text-muted-foreground"
|
||||
data-wiki-image-error={error}
|
||||
role="img"
|
||||
aria-label={alt || "Wiki 图片加载失败"}
|
||||
>
|
||||
<ImageOff className="size-5" />
|
||||
{alt || "Wiki 图片加载失败"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (!objectUrl) {
|
||||
return (
|
||||
<span className="my-3 flex min-h-24 items-center justify-center rounded-lg bg-muted/30 text-muted-foreground">
|
||||
<Loader2 className="size-5 animate-spin" aria-label="正在加载 Wiki 图片" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return <img {...props} src={objectUrl} alt={alt} className={className} />;
|
||||
}
|
||||
Reference in new issue
Block a user