feat: complete public wiki and embedded previews

This commit is contained in:
吉鑫升 committed 2026-08-24 20:40:44 +08:00
1 parent bc49df4561
commit 30943159bf
18 files changed
+926 -48

No files matched your search

@@ -1,7 +1,12 @@
"use client";
import { useMemo } from "react";
import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react";
import type {
AnchorHTMLAttributes,
HTMLAttributes,
MouseEvent as ReactMouseEvent,
ReactNode,
} from "react";
import {
MessageResponse,
@@ -34,6 +39,12 @@ export type MarkdownContentProps = {
className?: string;
remarkPlugins?: MessageResponseProps["remarkPlugins"];
components?: MessageResponseProps["components"];
linkClassName?: (href: string, label: string) => string | undefined;
onLinkClick?: (
event: ReactMouseEvent<HTMLAnchorElement>,
href: string,
label: string,
) => void;
};
/** Renders markdown content. */
@@ -43,6 +54,8 @@ export function MarkdownContent({
className,
remarkPlugins = streamdownPlugins.remarkPlugins,
components: componentsFromProps,
linkClassName,
onLinkClick,
}: MarkdownContentProps) {
const anchorResolver = useMemo(() => createMarkdownAnchorResolver(content), [content]);
const components = useMemo(() => {
@@ -93,6 +106,7 @@ export function MarkdownContent({
);
}
const finalHref = resolvedHref ?? props.href;
const label = extractTextFromChildren(props.children);
return (
<a
{...rest}
@@ -100,6 +114,7 @@ export function MarkdownContent({
className={cn(
"text-primary decoration-primary/30 hover:decoration-primary/60 underline underline-offset-2 transition-colors",
className,
linkClassName?.(finalHref || "", label),
)}
target={target ?? (external ? "_blank" : undefined)}
rel={rel ?? (external ? "noopener noreferrer" : undefined)}
@@ -110,6 +125,7 @@ export function MarkdownContent({
event.preventDefault();
scrollToInPageAnchor(event.currentTarget, finalHref);
}
onLinkClick?.(event, finalHref || "", label);
rest.onClick?.(event);
}}
/>
@@ -117,7 +133,7 @@ export function MarkdownContent({
},
...componentsFromProps,
};
}, [anchorResolver, componentsFromProps]);
}, [anchorResolver, componentsFromProps, linkClassName, onLinkClick]);
if (!content) return null;
@@ -5,6 +5,38 @@ export type WikiPageUrlBuilder = (
wikiSlug: string,
) => string;
export type WikiPageLinkTarget = {
slug: string;
title: string;
aliases?: string[];
page_type?: string;
summary?: string;
category_path?: string[];
};
export type WikiIndexGroup = {
type: string;
total: number;
items: WikiPageLinkTarget[];
};
type WikiIndexCategoryNode = {
label: string;
pages: WikiPageLinkTarget[];
children: Map<string, WikiIndexCategoryNode>;
};
const WIKI_INDEX_TYPE_LABELS: Record<string, string> = {
summary: "摘要",
entity: "实体",
concept: "概念",
overview: "概览",
directory: "目录",
article: "文章",
};
const WIKI_INDEX_TYPE_ORDER = ["summary", "entity", "concept"];
export function stripWikiFrontmatter(value: string) {
return value.replace(/^\s*---\s*\n[\s\S]*?\n---\s*(?:\n|$)/, "").trim();
}
@@ -38,18 +70,192 @@ export function wikiLinkMarkdown(
.replace(/\[\[([^\]]+)\]\]/g, (_match, slug) => link(slug));
}
function normalizeWikiLinkLabel(value: string): string {
return value
.replace(/\\([\\`*_[\]{}()#+\-.!])/g, "$1")
.replace(/[*_`~]/g, "")
.replace(/\s+/g, " ")
.trim()
.toLocaleLowerCase("zh-CN");
}
function isLegacyWikiPlaceholderUrl(value: string): boolean {
return /\/platform\/knowledge-bases\/[^/?#)]+/i.test(value);
}
function wikiIndexTypeRank(pageType: string): number {
const index = WIKI_INDEX_TYPE_ORDER.indexOf(pageType);
return index >= 0 ? index : WIKI_INDEX_TYPE_ORDER.length;
}
function wikiIndexPageLine(
page: WikiPageLinkTarget,
knowledgeBaseId: string,
buildPageUrl: WikiPageUrlBuilder,
): string {
const label = (page.title || page.slug).replace(/\]/g, "\\]");
const summary = (page.summary || "").replace(/\s+/g, " ").trim();
return `[${label}](${buildPageUrl(knowledgeBaseId, page.slug)})${summary ? ` — ${summary}` : ""}`;
}
function renderWikiIndexCategory(
node: WikiIndexCategoryNode,
knowledgeBaseId: string,
buildPageUrl: WikiPageUrlBuilder,
): string[] {
const lines = node.label ? [`**${node.label}**`] : [];
lines.push(...node.pages.map((page) => wikiIndexPageLine(page, knowledgeBaseId, buildPageUrl)));
const children = Array.from(node.children.values()).sort((left, right) =>
left.label.localeCompare(right.label, "zh-Hans-CN"),
);
for (const child of children) {
lines.push(...renderWikiIndexCategory(child, knowledgeBaseId, buildPageUrl));
}
return lines;
}
/** Build the category catalogue that WeKnora renders dynamically for its index page. */
export function buildWikiIndexCatalogMarkdown(
pages: WikiPageLinkTarget[],
knowledgeBaseId: string,
buildPageUrl: WikiPageUrlBuilder = buildWeKnoraWikiPageUrl,
indexGroups: WikiIndexGroup[] = [],
): string {
if (indexGroups.length > 0) {
const sections: string[] = [];
for (const group of indexGroups) {
if (group.items.length === 0) continue;
const seenCategories = new Set<string>();
const lines: string[] = [];
for (const page of group.items) {
const categories = (page.category_path || []).map((item) => item.trim()).filter(Boolean);
for (let depth = 0; depth < categories.length; depth += 1) {
const path = categories.slice(0, depth + 1).join("/");
if (seenCategories.has(path)) continue;
seenCategories.add(path);
lines.push(`${" ".repeat(depth)}**${categories[depth]}**`);
}
lines.push(
`${" ".repeat(categories.length)}${wikiIndexPageLine(page, knowledgeBaseId, buildPageUrl)}`,
);
}
const label = WIKI_INDEX_TYPE_LABELS[group.type] || group.type;
sections.push(`## ${label} (${group.total})`, lines.join("\n"));
}
return sections.join("\n\n");
}
const groups = new Map<string, WikiPageLinkTarget[]>();
for (const page of pages) {
const pageType = (page.page_type || "article").trim().toLowerCase();
const slugName = page.slug.split("/").filter(Boolean).at(-1)?.toLowerCase();
if (pageType === "index" || ["index", "home", "readme"].includes(slugName || "")) continue;
const group = groups.get(pageType) || [];
group.push(page);
groups.set(pageType, group);
}
const pageTypes = Array.from(groups.keys()).sort((left, right) => {
const rankDifference = wikiIndexTypeRank(left) - wikiIndexTypeRank(right);
if (rankDifference !== 0) return rankDifference;
return (WIKI_INDEX_TYPE_LABELS[left] || left).localeCompare(
WIKI_INDEX_TYPE_LABELS[right] || right,
"zh-Hans-CN",
);
});
const sections: string[] = [];
for (const pageType of pageTypes) {
const groupPages = groups.get(pageType) || [];
const root: WikiIndexCategoryNode = { label: "", pages: [], children: new Map() };
for (const page of groupPages) {
let node = root;
for (const category of page.category_path || []) {
const label = category.trim();
if (!label) continue;
let child = node.children.get(label);
if (!child) {
child = { label, pages: [], children: new Map() };
node.children.set(label, child);
}
node = child;
}
node.pages.push(page);
}
const label = WIKI_INDEX_TYPE_LABELS[pageType] || pageType;
sections.push(
`## ${label} (${groupPages.length})`,
...renderWikiIndexCategory(root, knowledgeBaseId, buildPageUrl),
);
}
return sections.join("\n\n");
}
/**
* WeKnora-generated index pages may contain Markdown links whose href only
* points at the knowledge-base Wiki shell. Resolve those placeholders by the
* visible link label so the native/public reader can open the exact page.
*/
export function resolveWikiIndexPageLinks(
markdown: string,
knowledgeBaseId: string,
pages: WikiPageLinkTarget[],
buildPageUrl: WikiPageUrlBuilder = buildWeKnoraWikiPageUrl,
): string {
const pagesByLabel = new Map<string, WikiPageLinkTarget>();
for (const page of pages) {
const labels = [page.title, page.slug, page.slug.split("/").filter(Boolean).at(-1), ...(page.aliases || [])];
for (const label of labels) {
const normalized = normalizeWikiLinkLabel(label || "");
if (normalized && !pagesByLabel.has(normalized)) pagesByLabel.set(normalized, page);
}
}
return markdown.replace(
/(?<!!)\[([^\]\n]+)\]\(([^)\n]+)\)/g,
(original, rawLabel: string, rawUrl: string) => {
if (!isLegacyWikiPlaceholderUrl(rawUrl)) return original;
const normalizedLabel = normalizeWikiLinkLabel(rawLabel);
const withoutSummarySuffix = normalizedLabel.replace(/\s+-\s+summary$/i, "");
const target = pagesByLabel.get(normalizedLabel) || pagesByLabel.get(withoutSummarySuffix);
if (!target) return original;
return `[${rawLabel}](${buildPageUrl(knowledgeBaseId, target.slug)})`;
},
);
}
export function renderableWikiMarkdown(
title: string,
content: string,
knowledgeBaseId: string,
buildPageUrl?: WikiPageUrlBuilder,
pages: WikiPageLinkTarget[] = [],
indexGroups: WikiIndexGroup[] = [],
) {
const lines = content.trim().split("\n");
if (lines[0]?.replace(/^#+\s*/, "").trim() === title.trim()) {
lines[0] = `# ${title}`;
}
const baseMarkdown = exportableWikiMarkdown(title, "", lines.join("\n"));
const isIndex = pages.some((page) => {
const slugName = page.slug.split("/").filter(Boolean).at(-1)?.toLowerCase();
return page.title === title && (
page.page_type?.toLowerCase() === "index" ||
["index", "home", "readme"].includes(slugName || "")
);
});
const existingMarkdownLinkCount = (baseMarkdown.match(/(?<!!)\[[^\]\n]+\]\([^)\n]+\)/g) || []).length;
const catalogue = isIndex && existingMarkdownLinkCount === 0
? buildWikiIndexCatalogMarkdown(pages, knowledgeBaseId, buildPageUrl, indexGroups)
: "";
const markdown = resolveWikiIndexPageLinks(
[baseMarkdown, catalogue].filter(Boolean).join("\n\n"),
knowledgeBaseId,
pages,
buildPageUrl,
);
return wikiLinkMarkdown(
exportableWikiMarkdown(title, "", lines.join("\n")),
markdown,
knowledgeBaseId,
buildPageUrl,
);
+70 -3
View File
@@ -13,6 +13,34 @@ export type WikiTree = {
parentKeysBySlug: Map<string, string[]>;
};
export type BuildWikiTreeOptions = {
preferredRootPageSlug?: string;
};
export function isWikiIndexPage(page: LlmWikiWikiPage): boolean {
const pageType = (page.page_type || "").trim().toLowerCase();
const title = (page.title || "").trim().toLowerCase();
const slug = (page.slug || "").trim().replace(/^\/+|\/+$/g, "");
const slugName = slug.split("/").pop()?.toLowerCase() || "";
return (
pageType === "index" ||
title === "索引" ||
title === "index" ||
["index", "home", "readme"].includes(slugName)
);
}
export function selectDefaultWikiPage(
pages: LlmWikiWikiPage[],
): LlmWikiWikiPage | undefined {
return (
pages.find(isWikiIndexPage) ||
pages.find((page) => page.page_type?.toLowerCase() === "overview") ||
pages.find((page) => !page.parent_slug && (page.depth ?? 0) === 0) ||
pages[0]
);
}
function pageDirectoryParts(page: LlmWikiWikiPage): string[] {
const wikiPath = (page.wiki_path || "").split("/").map((item) => item.trim()).filter(Boolean);
if (wikiPath.length > 2) return wikiPath.slice(1, -1);
@@ -62,20 +90,49 @@ function attachNode(parent: WikiTreeNode | null, roots: WikiTreeNode[], node: Wi
if (!siblings.some((item) => item.key === node.key)) siblings.push(node);
}
function sortWikiNodes(nodes: WikiTreeNode[]) {
function sortWikiNodes(
nodes: WikiTreeNode[],
preferredRootPageSlug?: string,
isRoot = false,
) {
nodes.sort((left, right) => {
if (isRoot && preferredRootPageSlug) {
const leftPreferred = left.page?.slug === preferredRootPageSlug;
const rightPreferred = right.page?.slug === preferredRootPageSlug;
if (leftPreferred !== rightPreferred) return leftPreferred ? -1 : 1;
}
if (!!left.children.length !== !!right.children.length) return left.children.length ? -1 : 1;
return left.label.localeCompare(right.label, "zh-Hans-CN");
});
nodes.forEach((node) => sortWikiNodes(node.children));
}
function detachPageNode(nodes: WikiTreeNode[], slug: string): WikiTreeNode | null {
const index = nodes.findIndex((node) => node.page?.slug === slug);
if (index >= 0) return nodes.splice(index, 1)[0] || null;
for (const node of nodes) {
const detached = detachPageNode(node.children, slug);
if (detached) return detached;
}
return null;
}
function pruneEmptyDirectories(nodes: WikiTreeNode[]): WikiTreeNode[] {
return nodes.filter((node) => {
node.children = pruneEmptyDirectories(node.children);
return node.page !== null || node.children.length > 0;
});
}
function countPages(node: WikiTreeNode): number {
node.count = (node.page ? 1 : 0) + node.children.reduce((sum, child) => sum + countPages(child), 0);
return node.count;
}
export function buildWikiTree(pages: LlmWikiWikiPage[]): WikiTree {
export function buildWikiTree(
pages: LlmWikiWikiPage[],
options: BuildWikiTreeOptions = {},
): WikiTree {
const roots: WikiTreeNode[] = [];
const pageNodes = new Map<string, WikiTreeNode>();
const directoryNodes = new Map<string, WikiTreeNode>();
@@ -140,7 +197,17 @@ export function buildWikiTree(pages: LlmWikiWikiPage[]): WikiTree {
parentKeysBySlug.set(page.slug, ancestorKeys);
}
const preferredSlug = options.preferredRootPageSlug?.trim();
if (preferredSlug) {
const preferredNode = detachPageNode(roots, preferredSlug);
if (preferredNode) {
roots.splice(0, roots.length, ...pruneEmptyDirectories(roots));
roots.unshift(preferredNode);
parentKeysBySlug.set(preferredSlug, []);
}
}
roots.forEach(countPages);
sortWikiNodes(roots);
sortWikiNodes(roots, preferredSlug, true);
return { roots, parentKeysBySlug };
}
@@ -144,6 +144,27 @@ export interface LlmWikiWikiPageList {
total_pages: number;
}
export interface LlmWikiWikiIndexItem {
slug: string;
title: string;
summary: string;
category_path: string[];
wiki_path: string;
depth: number;
}
export interface LlmWikiWikiIndexGroup {
type: string;
total: number;
items: LlmWikiWikiIndexItem[];
}
export interface LlmWikiWikiIndex {
intro: string;
version: number;
groups: LlmWikiWikiIndexGroup[];
}
export interface LlmWikiWikiIndexStatus {
knowledge_base_id: string;
state: "not_synced" | "idle" | "running" | "partial" | "failed" | "rebuilding" | "rebuild_required";
@@ -710,6 +731,17 @@ export async function getPublicLlmWikiWikiPage(
);
}
export async function getPublicLlmWikiWikiIndex(
knowledgeBaseId: string,
): Promise<LlmWikiWikiIndex> {
return readJson(
await apiFetch(
`${PUBLIC_API()}/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/wiki/index`,
),
"读取公开 Wiki 索引",
);
}
export async function createLlmWikiConversationDeposit(input: {
thread_id: string;
question: string;
@@ -939,6 +971,18 @@ export function usePublicLlmWikiWikiPage(
});
}
export function usePublicLlmWikiWikiIndex(
knowledgeBaseId: string | null,
enabled = true,
) {
return useQuery({
queryKey: ["public-llmwiki", "wiki-index", knowledgeBaseId],
queryFn: () => getPublicLlmWikiWikiIndex(knowledgeBaseId!),
enabled: enabled && !!knowledgeBaseId,
staleTime: 60_000,
});
}
export function useLlmWikiWikiIndexStatus(
knowledgeBaseId: string | null,
enabled = true,
@@ -273,7 +273,6 @@ export function WeKnoraKnowledgeDetailPage() {
title={`${details.data.name || "知识库"} 详情`}
src={frameSrc}
className="h-full min-h-0 w-full flex-1 border-0 bg-background"
sandbox="allow-same-origin allow-scripts allow-forms allow-downloads allow-popups allow-popups-to-escape-sandbox"
referrerPolicy="no-referrer"
onLoad={() => postThemeToFrame(frameRef.current)}
/>
@@ -21,12 +21,15 @@ import {
} from "@/components/ui/sheet";
import { apiFetch } from "@/core/api/fetch-client";
import { getBackendBaseURL } from "@/core/config";
import type { WeKnoraSourceTarget } from "@/core/llmwiki/source-links";
import {
buildWeKnoraWikiPagePath,
parseWeKnoraPreviewUrl,
type WeKnoraSourceTarget,
} from "@/core/llmwiki/source-links";
import { SpreadsheetPreview } from "@/components/workspace/artifacts/spreadsheet-preview";
import { MarkdownContent } from "@/components/workspace/messages/markdown-content";
import { cn } from "@/lib/utils";
import { reasoningPlugins } from "@/core/streamdown";
import { buildWeKnoraWikiPagePath } from "@/core/llmwiki/source-links";
import {
exportableWikiMarkdown,
renderableWikiMarkdown,
@@ -81,6 +84,14 @@ type CachedSourceDetail = {
const sourceDetailCache = new Map<string, CachedSourceDetail>();
function buildDrawerWikiPageUrl(knowledgeBaseId: string, wikiSlug: string) {
const params = new URLSearchParams({
knowledge_base_id: knowledgeBaseId,
wiki_slug: wikiSlug,
});
return `/weknora-source-preview?${params.toString()}`;
}
async function getAvailableWikiPage(
knowledgeBaseId: string,
wikiSlug: string,
@@ -539,6 +550,7 @@ export function WeKnoraSourceDrawer({
const [downloading, setDownloading] = useState(false);
const [wikiDownload, setWikiDownload] = useState<"md" | "word" | null>(null);
const [resolvedDocumentId, setResolvedDocumentId] = useState("");
const wikiNavigationRequestRef = useRef(0);
const targetKey = useMemo(() => {
if (!target) return "";
@@ -690,7 +702,12 @@ export function WeKnoraSourceDrawer({
const summary = cleanText(wikiPage?.summary || document?.description);
const wikiContent = stripWikiFrontmatter(cleanText(wikiPage?.content));
const wikiDisplayContent = wikiPage && target
? renderableWikiMarkdown(resolvedTitle, wikiContent, target.knowledgeBaseId)
? renderableWikiMarkdown(
resolvedTitle,
wikiContent,
target.knowledgeBaseId,
buildDrawerWikiPageUrl,
)
: "";
const wikiMarkdown = wikiPage
? wikiLinkMarkdown(
@@ -703,6 +720,32 @@ export function WeKnoraSourceDrawer({
? buildWeKnoraWikiPagePath(target.knowledgeBaseId, resolvedWikiSlug)
: "";
const openWikiPageInDrawer = async (linkedTarget: WeKnoraSourceTarget) => {
if (!linkedTarget.wikiSlug) return;
const requestId = ++wikiNavigationRequestRef.current;
setLoading(true);
setError(null);
try {
const page = await getAvailableWikiPage(
linkedTarget.knowledgeBaseId,
linkedTarget.wikiSlug,
);
if (requestId === wikiNavigationRequestRef.current) {
setWikiPage(page);
setDocument(null);
setResolvedDocumentId("");
setPreview({ kind: "none" });
setPreviewError("");
}
} catch (error) {
if (requestId === wikiNavigationRequestRef.current) {
setError(error instanceof Error ? error.message : "无法打开 Wiki 页面");
}
} finally {
if (requestId === wikiNavigationRequestRef.current) setLoading(false);
}
};
const downloadOriginal = async () => {
if (!target || !resolvedDocumentId) return;
setDownloading(true);
@@ -925,6 +968,17 @@ export function WeKnoraSourceDrawer({
isLoading={false}
remarkPlugins={reasoningPlugins.remarkPlugins}
rehypePlugins={reasoningPlugins.rehypePlugins}
linkClassName={(href) =>
parseWeKnoraPreviewUrl(href)?.wikiSlug
? "wiki-content-link font-medium text-[#be2323] decoration-[#be2323]/55 decoration-dashed hover:decoration-solid"
: undefined
}
onLinkClick={(event, href) => {
const linkedTarget = parseWeKnoraPreviewUrl(href);
if (!linkedTarget?.wikiSlug) return;
event.preventDefault();
void openWikiPageInDrawer(linkedTarget);
}}
/>
</div>
</section>
@@ -25,6 +25,8 @@ import {
import {
buildWikiTree,
filterWikiPagesWithAncestors,
isWikiIndexPage,
selectDefaultWikiPage,
type WikiTreeNode,
} from "@/core/llmwiki/wiki-tree";
import { usePublicBackground } from "@/core/embed/background";
@@ -43,6 +45,7 @@ import {
useLlmWikiWikiPage,
useLlmWikiWikiPages,
usePublicLlmWikiKnowledgeBase,
usePublicLlmWikiWikiIndex,
usePublicLlmWikiWikiPage,
usePublicLlmWikiWikiPages,
} from "@/strategy-components/api/llmwiki";
@@ -92,13 +95,13 @@ function downloadBlob(blob: Blob, fileName: string) {
window.setTimeout(() => URL.revokeObjectURL(url), 30_000);
}
function selectLandingPage(pages: LlmWikiWikiPage[]): LlmWikiWikiPage | undefined {
return (
pages.find((page) => page.page_type?.toLowerCase() === "overview") ||
pages.find((page) => /^(?:index|home|readme)$/i.test(page.slug)) ||
pages.find((page) => !page.parent_slug && (page.depth ?? 0) === 0) ||
pages[0]
);
function publicWikiTargetFromHref(href: string, knowledgeBaseId: string) {
const routePrefix = `/#${buildPublicKnowledgeWikiPath(knowledgeBaseId)}`;
if (!href.startsWith(`${routePrefix}?`)) return null;
const route = href.slice(2);
const queryIndex = route.indexOf("?");
const slug = new URLSearchParams(route.slice(queryIndex + 1)).get("wikiSlug")?.trim();
return slug ? { route, slug } : null;
}
export function WeKnoraWikiPageView({
@@ -137,7 +140,11 @@ export function WeKnoraWikiPageView({
);
const pages = publicEmbed ? publicPages : privatePages;
const pagesList = useMemo(() => pages.data?.pages || [], [pages.data?.pages]);
const landingPage = useMemo(() => selectLandingPage(pagesList), [pagesList]);
const indexPage = useMemo(
() => (publicEmbed ? pagesList.find(isWikiIndexPage) : undefined),
[pagesList, publicEmbed],
);
const landingPage = useMemo(() => selectDefaultWikiPage(pagesList), [pagesList]);
const wikiSlug = requestedWikiSlug || (publicEmbed ? landingPage?.slug || "" : "");
const privatePage = useLlmWikiWikiPage(
noteId || null,
@@ -150,6 +157,10 @@ export function WeKnoraWikiPageView({
!!noteId && !!wikiSlug && publicEmbed,
);
const page = publicEmbed ? publicPage : privatePage;
const publicWikiIndex = usePublicLlmWikiWikiIndex(
noteId || null,
!!noteId && publicEmbed && !!indexPage && wikiSlug === indexPage.slug,
);
const [downloading, setDownloading] = useState<"md" | "word" | null>(null);
const [directoryQuery, setDirectoryQuery] = useState("");
const [collapsedDirectories, setCollapsedDirectories] = useState<Set<string>>(() => new Set());
@@ -159,7 +170,12 @@ export function WeKnoraWikiPageView({
() => filterWikiPagesWithAncestors(pagesList, directoryQuery),
[directoryQuery, pagesList],
);
const wikiTree = useMemo(() => buildWikiTree(filteredPages), [filteredPages]);
const wikiTree = useMemo(
() => buildWikiTree(filteredPages, {
preferredRootPageSlug: publicEmbed ? indexPage?.slug : undefined,
}),
[filteredPages, indexPage?.slug, publicEmbed],
);
useEffect(() => {
if (!publicEmbed || requestedWikiSlug || !wikiSlug) return;
@@ -182,8 +198,16 @@ export function WeKnoraWikiPageView({
}, [wikiSlug, wikiTree.parentKeysBySlug]);
const title = pageData?.title || wikiSlug || "Wiki 页面";
const summary = cleanText(pageData?.summary);
const content = stripWikiFrontmatter(cleanText(pageData?.content));
const displayTitle = publicEmbed && pageData && isWikiIndexPage(pageData)
? "索引"
: title;
const currentPageIsIndex = !!pageData && isWikiIndexPage(pageData);
const summary = publicEmbed && currentPageIsIndex ? "" : cleanText(pageData?.summary);
const content = stripWikiFrontmatter(cleanText(
publicEmbed && currentPageIsIndex && publicWikiIndex.data?.intro
? publicWikiIndex.data.intro
: pageData?.content,
));
const displayMarkdown = pageData
? renderableWikiMarkdown(
title,
@@ -196,6 +220,8 @@ export function WeKnoraWikiPageView({
return `/#${buildPublicKnowledgeWikiPath(knowledgeBaseId)}?${next.toString()}`;
}
: undefined,
publicEmbed ? pagesList : [],
publicEmbed ? publicWikiIndex.data?.groups || [] : [],
)
: "";
const exportMarkdown = pageData
@@ -239,7 +265,11 @@ export function WeKnoraWikiPageView({
const hasChildren = node.children.length > 0;
const expanded = directoryQuery.trim() ? true : !collapsedDirectories.has(node.key);
const active = node.page?.slug === wikiSlug;
const label = node.page ? node.label : directoryLabel(node.label);
const label = node.page
? publicEmbed && isWikiIndexPage(node.page)
? "索引"
: node.label
: directoryLabel(node.label);
return (
<div key={node.key}>
<div
@@ -372,7 +402,7 @@ export function WeKnoraWikiPageView({
<h1 className="truncate text-sm font-semibold text-foreground">
{knowledgeBase.data?.name || "知识库 Wiki"}
</h1>
<p className="mt-0.5 truncate text-xs text-muted-foreground">{title}</p>
<p className="mt-0.5 truncate text-xs text-muted-foreground">{displayTitle}</p>
</div>
<Badge variant="outline" className="shrink-0 border-emerald-200 text-emerald-700 dark:border-emerald-800 dark:text-emerald-300">
公开 Wiki
@@ -467,6 +497,22 @@ export function WeKnoraWikiPageView({
isLoading={false}
remarkPlugins={reasoningPlugins.remarkPlugins}
rehypePlugins={reasoningPlugins.rehypePlugins}
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"
: undefined
}
onLinkClick={(event, href) => {
if (!publicEmbed) return;
const target = publicWikiTargetFromHref(href, noteId);
if (!target) return;
event.preventDefault();
navigate(target.route, {
state: {
wikiPage: pagesList.find((item) => item.slug === target.slug),
},
});
}}
/>
</section>
</article>