feat: Enhance image preview functionality in WikiBrowser component

- Updated the `picture-preview.vue` component to conditionally render images based on the `reviewUrl` prop, improving performance and usability.
- Integrated image preview functionality into the `WikiBrowser` component, allowing users to view images in a modal when clicked.
- Added references for `drawerBodyRef` and `readerBodyRef` to facilitate image loading and hydration, ensuring images are displayed correctly.
- Enhanced the `prompts_wiki.go` file to include new image handling rules for summaries, ensuring images are contextually relevant in generated content.

These changes improve the user experience by providing a seamless image viewing option within the WikiBrowser interface.
This commit is contained in:
wizardchen
2026-04-08 21:58:15 +08:00
parent 98e2464831
commit 233c398d7a
3 changed files with 89 additions and 15 deletions

View File

@@ -8,11 +8,8 @@ const close = () => {
}
</script>
<template>
<t-image-viewer :visible="reviewImg" closeOnOverlay closeOnEscKeydown @close="close"
:images="[{
mainImage: reviewUrl,
download: false
}]">
<t-image-viewer :visible="reviewImg" closeOnOverlay closeOnEscKeydown @close="close"
:images="reviewUrl ? [reviewUrl] : []">
</t-image-viewer>
</template>
<style scoped lang="less"></style>

View File

@@ -70,6 +70,7 @@
:attach="false"
:show-overlay="false"
:close-btn="true"
destroy-on-close
class="wiki-graph-drawer"
>
<template v-if="graphDrawerPage">
@@ -79,7 +80,7 @@
</t-tag>
<span class="wiki-reader-meta-text">{{ $t('knowledgeEditor.wikiBrowser.version', { ver: graphDrawerPage.version }) }}</span>
</div>
<div class="wiki-reader-body" v-html="graphDrawerContent" @click="handleGraphDrawerClick"></div>
<div ref="drawerBodyRef" class="wiki-reader-body" v-html="graphDrawerContent" @click="handleGraphDrawerClick"></div>
</template>
</t-drawer>
</div>
@@ -206,7 +207,7 @@
</div>
<!-- Content -->
<div class="wiki-reader-body" v-html="renderedContent" @click="handleContentClick"></div>
<div ref="readerBodyRef" class="wiki-reader-body" v-html="renderedContent" @click="handleContentClick"></div>
<!-- Source refs -->
<div v-if="parsedSourceRefs.length" class="wiki-reader-sources">
@@ -239,6 +240,11 @@
</div>
</div>
</template>
<!-- Image Preview -->
<Teleport to="body">
<picturePreview v-if="imagePreviewVisible" :reviewImg="imagePreviewVisible" :reviewUrl="imagePreviewUrl" @closePreImg="closeImagePreview" />
</Teleport>
</div>
</template>
@@ -246,6 +252,8 @@
import { ref, computed, onMounted, watch, nextTick, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { marked } from 'marked'
import { hydrateProtectedFileImages } from '@/utils/security'
import picturePreview from '@/components/picture-preview.vue'
import {
listWikiPages,
getWikiPage,
@@ -274,6 +282,8 @@ const graphData = ref<WikiGraphData | null>(null)
const searchQuery = ref('')
const graphSearchValue = ref('')
const graphRef = ref<HTMLElement | null>(null)
const readerBodyRef = ref<HTMLElement | null>(null)
const drawerBodyRef = ref<HTMLElement | null>(null)
const loading = ref(false)
const graphLoading = ref(false)
const graphReady = ref(false)
@@ -340,6 +350,21 @@ const graphDrawerContent = computed(() => {
return renderMarkdown(graphDrawerPage.value.content)
})
const imagePreviewVisible = ref(false)
const imagePreviewUrl = ref('')
function closeImagePreview() {
imagePreviewVisible.value = false
imagePreviewUrl.value = ''
}
watch(graphDrawerContent, async () => {
await nextTick()
if (drawerBodyRef.value) {
await hydrateProtectedFileImages(drawerBodyRef.value)
}
})
function renderMarkdown(content: string): string {
// Pre-process wiki links [[slug|name]] to custom HTML tags
let preprocessed = content.replace(/\[\[([^\]]+)\]\]/g, (_, inner: string) => {
@@ -369,6 +394,12 @@ function handleGraphDrawerClick(e: MouseEvent) {
e.preventDefault()
const slug = target.getAttribute('data-slug')
if (slug) handleGraphSearchSelect(slug)
} else if (target.tagName.toLowerCase() === 'img') {
e.preventDefault()
imagePreviewUrl.value = target.getAttribute('src') || ''
if (imagePreviewUrl.value) {
imagePreviewVisible.value = true
}
}
}
@@ -402,12 +433,25 @@ const renderedContent = computed(() => {
return renderMarkdown(selectedPage.value.content)
})
watch(renderedContent, async () => {
await nextTick()
if (readerBodyRef.value) {
await hydrateProtectedFileImages(readerBodyRef.value)
}
})
function handleContentClick(e: MouseEvent) {
const target = e.target as HTMLElement
if (target.classList.contains('wiki-content-link')) {
e.preventDefault()
const slug = target.getAttribute('data-slug')
if (slug) navigateToSlug(slug)
} else if (target.tagName.toLowerCase() === 'img') {
e.preventDefault()
imagePreviewUrl.value = target.getAttribute('src') || ''
if (imagePreviewUrl.value) {
imagePreviewVisible.value = true
}
}
}
@@ -1234,7 +1278,15 @@ watch(searchQuery, (val) => {
})
watch(() => props.view, (v) => {
if (v === 'graph') loadGraph()
if (v === 'graph') {
loadGraph()
} else if (v === 'browser') {
nextTick(async () => {
if (readerBodyRef.value && renderedContent.value) {
await hydrateProtectedFileImages(readerBodyRef.value)
}
})
}
})
onMounted(() => {
@@ -1563,6 +1615,29 @@ onMounted(() => {
}
}
:deep(p:has(img)) {
text-align: center;
color: var(--td-text-color-secondary);
font-size: 13px;
margin-top: 16px;
margin-bottom: 24px;
img {
max-width: 100%;
max-height: 400px;
object-fit: contain;
border-radius: 6px;
display: block;
margin: 0 auto 8px;
cursor: zoom-in;
transition: opacity 0.2s;
&:hover {
opacity: 0.9;
}
}
}
:deep(.wiki-content-link) {
color: var(--td-brand-color);
text-decoration: none;

View File

@@ -26,9 +26,10 @@ const WikiSummaryPrompt = `You are a wiki editor. Given the following document c
3. Include the key facts, arguments, and conclusions.
4. Use proper heading hierarchy (## for sections, ### for subsections).
5. **Wiki-link rule**: The available_wiki_pages list above maps slugs to display names (format: "[[slug]] = display name"). Whenever you mention a name that matches a listed entry, you MUST write it as [[slug|display name]] (e.g. [[entity/zhong-guo|中国]]), NOT as bold (**name**) or bare [[slug]]. Use the EXACT slugs provided — do NOT invent new slugs.
6. At the end, include a "## Key Takeaways" section with bullet points.
7. Write in {{.Language}}.
8. Keep the summary concise but thorough (500-1500 words depending on document length).
6. **Image rule**: If the document contains <images> tags with <image> elements, you SHOULD include the relevant images in your summary using the Markdown syntax: ![caption](url). Place the images where they are contextually relevant to the text.
7. At the end, include a "## Key Takeaways" section with bullet points.
8. Write in {{.Language}}.
9. Keep the summary concise but thorough (500-1500 words depending on document length).
</instructions>
Output the SUMMARY line first, then the Markdown content. Do not include any other preamble.`
@@ -51,7 +52,7 @@ const WikiKnowledgeExtractPrompt = `You are a knowledge extraction system. Analy
<instructions>
Return a JSON object with two arrays: "entities" and "concepts".
**IMPORTANT: Write ALL names, descriptions, and details in {{.Language}}.**
**IMPORTANT: Write ALL names, descriptions, and details in {{.Language}}**.
### Slug Continuity Rules
If previous slugs are provided above, you MUST follow these rules:
@@ -65,7 +66,7 @@ Each entity should have:
- "name": The entity name in {{.Language}} (human-readable)
- "slug": URL-friendly slug, format "entity/<lowercase-hyphenated-name>" (use romanized/pinyin form for non-Latin names). **Reuse previous slug if the entity was extracted before.**
- "description": **Index listing summary** — one sentence, 15-40 words, in {{.Language}}. Describes WHAT this entity IS and its role in the document. Must be self-contained (understandable without reading the full page). This will be displayed in the wiki index.
- "details": A 2-5 sentence summary in {{.Language}} of key facts from the document
- "details": A 2-5 sentence summary in {{.Language}} of key facts from the document. **Image rule**: If the document contains relevant <image> elements in an <images> tag, include them in the details using Markdown syntax: ![caption](url).
Only include entities that are substantively discussed (mentioned at least twice or described in detail). Do NOT include generic terms.
@@ -74,7 +75,7 @@ Each concept should have:
- "name": The concept name in {{.Language}} (human-readable)
- "slug": URL-friendly slug, format "concept/<lowercase-hyphenated-name>" (use romanized/pinyin form for non-Latin names). **Reuse previous slug if the concept was extracted before.**
- "description": **Index listing summary** — one sentence, 15-40 words, in {{.Language}}. Defines WHAT this concept IS. Must be self-contained (understandable without reading the full page). This will be displayed in the wiki index.
- "details": A 2-5 sentence explanation in {{.Language}} as discussed in the document
- "details": A 2-5 sentence explanation in {{.Language}} as discussed in the document. **Image rule**: If the document contains relevant <image> elements in an <images> tag, include them in the details using Markdown syntax: ![caption](url).
Only include concepts that are substantively discussed. Skip trivial or overly generic concepts.
@@ -127,7 +128,8 @@ const WikiPageUpdatePrompt = `You are a wiki editor tasked with updating an exis
6. Preserve any existing [[slug|name]] wiki-link references in the content. Do NOT invent new wiki-link slugs.
7. Maintain the existing page structure and formatting style.
8. Add a source reference to the new document at the bottom.
9. Write in {{.Language}}.
9. **Image rule**: If the new document contains <images> tags with <image> elements, you SHOULD include the relevant images in your updated page using the Markdown syntax: ![caption](url). Place the images where they are contextually relevant to the text.
10. Write in {{.Language}}.
</instructions>
Output the SUMMARY line first, then the updated Markdown content. Do not include any other preamble.`