fix(wiki): internal link navigation and source citation guidance

This commit is contained in:
matevip 2026-04-24 06:55:20 +08:00
parent 4be867a3e6
commit c50977785a
4 changed files with 22 additions and 6 deletions

View File

@ -49,7 +49,9 @@ public class WikiContextService {
} }
StringBuilder sb = new StringBuilder("<wiki-relevant>\n"); StringBuilder sb = new StringBuilder("<wiki-relevant>\n");
sb.append("[Relevant wiki pages for this query. Use wiki_read_page(slug) for full content.]\n\n"); sb.append("[Relevant wiki pages for this query. Use wiki_read_page(slug) for full content. " +
"When using information from these pages in your answer, always cite the source page title, " +
"e.g. 「来源:[[页面标题]]」or「(来源:页面标题)」.]\n\n");
int totalChars = 0; int totalChars = 0;
int maxChars = properties.getMaxContextChars(); int maxChars = properties.getMaxContextChars();

View File

@ -72,6 +72,8 @@ public class WikiTool {
@Tool(description = """ @Tool(description = """
Read a wiki page. Use maxChars to limit size (recommended: 3000-6000 for most tasks). Read a wiki page. Use maxChars to limit size (recommended: 3000-6000 for most tasks).
Use sectionHeading to read only one section by its heading text. Use sectionHeading to read only one section by its heading text.
The result includes a "sourceFiles" field listing the source documents this page was derived from.
When using content from this page in your answer, cite the page title and source files.
""") """)
public String wiki_read_page( public String wiki_read_page(
@ToolParam(description = "Agent ID") Long agentId, @ToolParam(description = "Agent ID") Long agentId,
@ -167,6 +169,8 @@ public class WikiTool {
@Tool(description = """ @Tool(description = """
Search wiki pages. Returns snippet so you can judge relevance without reading the full page. Search wiki pages. Returns snippet so you can judge relevance without reading the full page.
Default topK=5 is sufficient for most queries. Default topK=5 is sufficient for most queries.
Each result includes "slug" and "title" use wiki_read_page to get full content.
When using wiki information in your answer, always cite the source page title.
""") """)
public String wiki_search_pages( public String wiki_search_pages(
@ToolParam(description = "Agent ID") Long agentId, @ToolParam(description = "Agent ID") Long agentId,
@ -214,8 +218,9 @@ public class WikiTool {
@Tool(description = """ @Tool(description = """
Chunk-level semantic search in the wiki knowledge base. Chunk-level semantic search in the wiki knowledge base.
Returns raw text fragments closest to the query with similarity scores. Returns raw text fragments closest to the query with similarity scores and source page title.
Use when wiki_search_pages results are not specific enough. Use when wiki_search_pages results are not specific enough.
When using retrieved content in your answer, cite the source page title shown in each result.
""") """)
public String wiki_semantic_search( public String wiki_semantic_search(
@ToolParam(description = "Agent ID") Long agentId, @ToolParam(description = "Agent ID") Long agentId,

View File

@ -88,7 +88,7 @@ const markedInstance = new Marked({
// 配置 DOMPurify — 允许 Markdown + 代码块复制按钮的标签和属性 // 配置 DOMPurify — 允许 Markdown + 代码块复制按钮的标签和属性
const purifyConfig = { const purifyConfig = {
ADD_ATTR: ['target', 'rel', 'class', 'data-code', 'data-echarts-option', 'data-wiki-title', 'type', 'viewBox', 'fill', 'stroke', 'stroke-width', 'd', 'x', 'y', 'width', 'height', 'rx', 'ry', 'points'], ADD_ATTR: ['target', 'rel', 'class', 'data-code', 'data-echarts-option', 'data-wiki-title', 'data-slug', 'type', 'viewBox', 'fill', 'stroke', 'stroke-width', 'd', 'x', 'y', 'width', 'height', 'rx', 'ry', 'points'],
ADD_TAGS: ['input', 'button', 'svg', 'path', 'rect', 'polyline', 'circle', 'line', 'span'], ADD_TAGS: ['input', 'button', 'svg', 'path', 'rect', 'polyline', 'circle', 'line', 'span'],
} }

View File

@ -99,9 +99,18 @@ const enrichToast = ref('')
const renderedContent = computed(() => { const renderedContent = computed(() => {
if (!store.currentPage?.content) return '' if (!store.currentPage?.content) return ''
const content = store.currentPage.content.replace(/\[\[([^\]]+)\]\]/g, (_match, title) => { // Build a lookup map: title (normalized) slug, for resolving [[Title]] links
const slug = title.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, '').replace(/\s+/g, '-') const titleToSlug = new Map<string, string>()
return `<a class="wiki-link" data-slug="${slug}" onclick="return false">${title}</a>` for (const p of store.pages) {
if (p.title && p.slug) {
titleToSlug.set(p.title.trim().toLowerCase(), p.slug)
}
}
const content = store.currentPage.content.replace(/\[\[([^\]]+)\]\]/g, (_match, raw) => {
const title = raw.trim()
// Prefer exact title match; fall back to slug-style guess
const slug = titleToSlug.get(title.toLowerCase()) ?? title.toLowerCase().replace(/\s+/g, '-')
return `<a class="wiki-link" data-slug="${slug}">${title}</a>`
}) })
return renderMarkdown(content) return renderMarkdown(content)
}) })