mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-21 23:21:04 +08:00
Feat/configurable metadata display (#13464)
### What problem does this PR solve? Currently, RAGFlow's Search and Chat interfaces display only raw vectorized text chunks during retrieval, without contextual information about their source documents. Users cannot see document titles, page numbers, upload dates, or custom metadata fields that would help them understand and trust the retrieved results. This PR introduces an **optional metadata display feature** that enriches retrieved chunks with document-level metadata in both the Search tab and Chatbot interface. **Key improvements:** - **Search results**: Display document metadata as styled badges beneath chunk snippets - **Chat citations**: Show metadata in citation popovers and reference lists for better source context - **LLM context**: Metadata is injected into the LLM prompt to enable more accurate, citation-aware responses - **External API support**: Applications using RAGFlow's SDK retrieval endpoints (`/v1/retrieval`, `/v1/searchbots/retrieval_test`) can opt-in via request parameters - **User control**: Multi-select dropdown UI allows users to choose which metadata fields to display **Implementation approach:** - ✅ Reuses existing `DocMetadataService` infrastructure (no new database tables or indices) - ✅ Settings stored in existing JSON configuration fields (`search_config.reference_metadata`, `prompt_config.reference_metadata`) - ✅ No database migrations required - ✅ Disabled by default (fully opt-in and backward-compatible) - ✅ Dynamic metadata field selection populated from actual document metadata keys - ✅ Fixed critical bug where Python's builtin `set()` was shadowed by a route handler function **Modified endpoints (all backward-compatible):** - `POST /v1/retrieval` (Public SDK) - `POST /v1/searchbots/retrieval_test` (Searchbots) - `POST /v1/chunk/retrieval_test` (UI/Internal) - Chat completions endpoints (via `extra_body.reference_metadata` or `prompt_config`) ### Type of change - [x] New Feature (non-breaking change which adds functionality) ###Images - <img width="879" height="1275" alt="image" src="https://github.com/user-attachments/assets/95b2d731-31ae-45a1-b081-bf5893f52aeb" /> <br><br> <br><br> <img width="1532" height="362" alt="image" src="https://github.com/user-attachments/assets/9cebc65b-b7a7-459f-b25e-3b13fa9b638e" /> <br><br> <br><br> <img width="2586" height="1320" alt="image" src="https://github.com/user-attachments/assets/2153d493-d899-461f-a7a9-041391e07776" /> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Attili-sys <Attili-sys@users.noreply.github.com> Co-authored-by: Ahmad Intisar <ahmadintisar@Ahmads-MacBook-M4-Pro.local>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isRouteErrorResponse, useRouteError } from 'react-router';
|
||||
|
||||
interface FallbackComponentProps {
|
||||
error?: Error;
|
||||
@@ -7,10 +8,32 @@ interface FallbackComponentProps {
|
||||
}
|
||||
|
||||
const FallbackComponent: React.FC<FallbackComponentProps> = ({
|
||||
error,
|
||||
error: errorProp,
|
||||
reset,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const routeError = useRouteError();
|
||||
const error =
|
||||
errorProp ?? (routeError instanceof Error ? routeError : undefined);
|
||||
|
||||
let routeErrorDataStr = '';
|
||||
if (isRouteErrorResponse(routeError)) {
|
||||
if (typeof routeError.data === 'string') {
|
||||
routeErrorDataStr = routeError.data;
|
||||
} else if (routeError.data == null) {
|
||||
routeErrorDataStr = 'no body';
|
||||
} else {
|
||||
try {
|
||||
routeErrorDataStr = JSON.stringify(routeError.data);
|
||||
} catch {
|
||||
routeErrorDataStr = String(routeError.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const errorMessage = isRouteErrorResponse(routeError)
|
||||
? `${routeError.status} ${routeError.statusText}${routeErrorDataStr ? `: ${routeErrorDataStr}` : ''}`
|
||||
: (error?.toString() ?? (routeError ? String(routeError) : undefined));
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
||||
@@ -21,10 +44,10 @@ const FallbackComponent: React.FC<FallbackComponentProps> = ({
|
||||
'Sorry, an error occurred while loading the page.',
|
||||
)}
|
||||
</p>
|
||||
{error && (
|
||||
<details style={{ whiteSpace: 'pre-wrap', marginTop: '16px' }}>
|
||||
{errorMessage && (
|
||||
<details open className="mt-4 whitespace-pre-wrap">
|
||||
<summary>{t('error_boundary.details', 'Error details')}</summary>
|
||||
{error.toString()}
|
||||
{errorMessage}
|
||||
</details>
|
||||
)}
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
|
||||
@@ -40,6 +40,13 @@ import styles from './index.module.less';
|
||||
|
||||
const getChunkIndex = (match: string) => parseCitationIndex(match);
|
||||
|
||||
const formatMetadataValue = (value: unknown) => {
|
||||
if (Array.isArray(value)) return value.join(', ');
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'object') return JSON.stringify(value);
|
||||
return String(value);
|
||||
};
|
||||
|
||||
// TODO: The display of the table is inconsistent with the display previously placed in the MessageItem.
|
||||
const MarkdownContent = ({
|
||||
reference,
|
||||
@@ -174,6 +181,21 @@ const MarkdownContent = ({
|
||||
className={classNames(styles.chunkContentText)}
|
||||
dir="auto"
|
||||
></div>
|
||||
{chunkItem?.document_metadata &&
|
||||
Object.keys(chunkItem.document_metadata).length > 0 && (
|
||||
<section className="space-y-1 border border-border-default rounded p-2">
|
||||
{Object.entries(chunkItem.document_metadata).map(
|
||||
([key, value]) => (
|
||||
<div key={key} className="text-xs">
|
||||
<span className="text-text-secondary">{key}:</span>{' '}
|
||||
<span className="text-text-primary">
|
||||
{formatMetadataValue(value)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
{documentId && (
|
||||
<section className="flex gap-1">
|
||||
{fileThumbnail ? (
|
||||
|
||||
@@ -48,6 +48,7 @@ export const enum KnowledgeApiAction {
|
||||
FetchKnowledgeDetail = 'fetchKnowledgeDetail',
|
||||
FetchKnowledgeGraph = 'fetchKnowledgeGraph',
|
||||
FetchMetadata = 'fetchMetadata',
|
||||
FetchMetadataKeys = 'fetchMetadataKeys',
|
||||
FetchKnowledgeList = 'fetchKnowledgeList',
|
||||
RemoveKnowledgeGraph = 'removeKnowledgeGraph',
|
||||
}
|
||||
@@ -378,6 +379,24 @@ export function useFetchKnowledgeMetadata(kbIds: string[] = []) {
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export function useFetchKnowledgeMetadataKeys(kbIds: string[] = []) {
|
||||
const sortedKbIds = useMemo(() => [...kbIds].sort(), [kbIds]);
|
||||
const { data, isFetching: loading } = useQuery<string[]>({
|
||||
queryKey: [KnowledgeApiAction.FetchMetadataKeys, sortedKbIds],
|
||||
initialData: [],
|
||||
enabled: sortedKbIds.length > 0,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.getMetaKeys({
|
||||
kb_ids: sortedKbIds.join(','),
|
||||
});
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export const useRemoveKnowledgeGraph = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ export interface PromptConfig {
|
||||
cross_languages?: Array<string>;
|
||||
tavily_api_key?: string;
|
||||
toc_enhance?: boolean;
|
||||
reference_metadata?: {
|
||||
include?: boolean;
|
||||
fields?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface Parameter {
|
||||
@@ -126,6 +130,7 @@ export interface IReferenceChunk {
|
||||
term_similarity: number;
|
||||
positions: number[];
|
||||
doc_type?: string;
|
||||
document_metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IReference {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type Node } from '@xyflow/react';
|
||||
import { useMemo } from 'react';
|
||||
import { Node } from 'reactflow';
|
||||
import { initialDocGeneratorValues } from '../../constant';
|
||||
|
||||
export const useValues = (node?: Node) => {
|
||||
|
||||
@@ -13,16 +13,45 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { MultiSelect } from '@/components/ui/multi-select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useFetchKnowledgeMetadataKeys } from '@/hooks/use-knowledge-request';
|
||||
import { getDirAttribute } from '@/utils/text-direction';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
export default function ChatBasicSetting() {
|
||||
const { t } = useTranslate('chat');
|
||||
const form = useFormContext();
|
||||
const emptyResponseValue = form.watch('prompt_config.empty_response');
|
||||
const prologueValue = form.watch('prompt_config.prologue');
|
||||
const kbIds = (useWatch({ control: form.control, name: 'dataset_ids' }) ||
|
||||
[]) as string[];
|
||||
const metadataInclude = useWatch({
|
||||
control: form.control,
|
||||
name: 'prompt_config.reference_metadata.include',
|
||||
});
|
||||
const { data: metadataKeys } = useFetchKnowledgeMetadataKeys(kbIds);
|
||||
const metadataFieldOptions = useMemo(() => {
|
||||
return (metadataKeys || []).map((key) => ({
|
||||
label: key,
|
||||
value: key,
|
||||
}));
|
||||
}, [metadataKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentFields = form.getValues('prompt_config.reference_metadata.fields');
|
||||
if (metadataInclude && Array.isArray(currentFields) && currentFields.length > 0 && metadataKeys) {
|
||||
const validFields = currentFields.filter((field) => metadataKeys.includes(field));
|
||||
if (validFields.length !== currentFields.length) {
|
||||
form.setValue('prompt_config.reference_metadata.fields', validFields);
|
||||
}
|
||||
} else if (!metadataInclude) {
|
||||
form.setValue('prompt_config.reference_metadata.fields', undefined);
|
||||
}
|
||||
}, [kbIds, metadataKeys, metadataInclude, form]);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -83,6 +112,59 @@ export default function ChatBasicSetting() {
|
||||
<TavilyFormField></TavilyFormField>
|
||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
||||
<MetadataFilter></MetadataFilter>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'prompt_config.reference_metadata.include'}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (!value) {
|
||||
form.setValue(
|
||||
'prompt_config.reference_metadata.fields',
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel tooltip="Display document metadata (e.g., title, page number, upload date) alongside retrieved text chunks">
|
||||
Show chunk metadata
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{metadataInclude && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'prompt_config.reference_metadata.fields'}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip="Select which metadata fields to display with each chunk">
|
||||
{t('metadataKeys')}
|
||||
</FormLabel>
|
||||
<FormControl className="bg-bg-input">
|
||||
<MultiSelect
|
||||
options={metadataFieldOptions}
|
||||
onValueChange={field.onChange}
|
||||
showSelectAll={false}
|
||||
placeholder="Please select"
|
||||
maxCount={20}
|
||||
defaultValue={Array.isArray(field.value) ? field.value : []}
|
||||
value={Array.isArray(field.value) ? field.value : []}
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
onBlur={field.onBlur}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
reasoning: false,
|
||||
cross_languages: [],
|
||||
toc_enhance: false,
|
||||
reference_metadata: {
|
||||
include: false,
|
||||
fields: undefined,
|
||||
},
|
||||
},
|
||||
top_n: 8,
|
||||
similarity_threshold: 0.2,
|
||||
@@ -74,6 +78,14 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
values,
|
||||
'llm_setting.',
|
||||
);
|
||||
const referenceMetadata = nextValues?.prompt_config?.reference_metadata;
|
||||
if (
|
||||
referenceMetadata &&
|
||||
Array.isArray(referenceMetadata.fields) &&
|
||||
referenceMetadata.fields.length === 0
|
||||
) {
|
||||
referenceMetadata.fields = undefined;
|
||||
}
|
||||
|
||||
updateChat({
|
||||
chatId: id!,
|
||||
@@ -101,8 +113,20 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
const llmSettingEnabledValues = setLLMSettingEnabledValues(
|
||||
data.llm_setting,
|
||||
);
|
||||
const referenceMetadata = data?.prompt_config?.reference_metadata;
|
||||
const normalizedReferenceMetadata =
|
||||
referenceMetadata &&
|
||||
Array.isArray(referenceMetadata.fields) &&
|
||||
referenceMetadata.fields.length === 0
|
||||
? { ...referenceMetadata, fields: undefined }
|
||||
: referenceMetadata;
|
||||
|
||||
const nextData = {
|
||||
...data,
|
||||
prompt_config: {
|
||||
...data.prompt_config,
|
||||
reference_metadata: normalizedReferenceMetadata,
|
||||
},
|
||||
...llmSettingEnabledValues,
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,12 @@ export function useChatSettingSchema() {
|
||||
reasoning: z.boolean().optional(),
|
||||
cross_languages: z.array(z.string()).optional(),
|
||||
toc_enhance: z.boolean().optional(),
|
||||
reference_metadata: z
|
||||
.object({
|
||||
include: z.boolean().optional(),
|
||||
fields: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const formSchema = z.object({
|
||||
|
||||
@@ -22,9 +22,15 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { MultiSelect } from '@/components/ui/multi-select';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import {
|
||||
useFetchKnowledgeList,
|
||||
useFetchKnowledgeMetadataKeys,
|
||||
} from '@/hooks/use-knowledge-request';
|
||||
import {
|
||||
useComposeLlmOptionsByModelTypes,
|
||||
useSelectLlmOptionsByModelType,
|
||||
@@ -79,6 +85,12 @@ const SearchSettingFormSchema = z
|
||||
highlight: z.boolean(),
|
||||
keyword: z.boolean(),
|
||||
chat_settingcross_languages: z.array(z.string()),
|
||||
reference_metadata: z
|
||||
.object({
|
||||
include: z.boolean().optional(),
|
||||
fields: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
...MetadataFilterSchema,
|
||||
}),
|
||||
})
|
||||
@@ -156,6 +168,14 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
related_search: search_config?.related_search || false,
|
||||
query_mindmap: search_config?.query_mindmap || false,
|
||||
meta_data_filter: search_config?.meta_data_filter,
|
||||
reference_metadata: {
|
||||
include: search_config?.reference_metadata?.include || false,
|
||||
fields:
|
||||
search_config?.reference_metadata?.fields &&
|
||||
search_config.reference_metadata.fields.length > 0
|
||||
? search_config.reference_metadata.fields
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [data, search_config, llm_setting, formMethods, descriptionDefaultValue]);
|
||||
@@ -193,6 +213,35 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
control: formMethods.control,
|
||||
name: 'search_config.summary',
|
||||
});
|
||||
const selectedKbIds = useWatch({
|
||||
control: formMethods.control,
|
||||
name: 'search_config.kb_ids',
|
||||
});
|
||||
const referenceMetadataEnabled = useWatch({
|
||||
control: formMethods.control,
|
||||
name: 'search_config.reference_metadata.include',
|
||||
});
|
||||
const { data: metadataKeys } = useFetchKnowledgeMetadataKeys(
|
||||
selectedKbIds || [],
|
||||
);
|
||||
const metadataFieldOptions = useMemo(() => {
|
||||
return (metadataKeys || []).map((key) => ({
|
||||
label: key,
|
||||
value: key,
|
||||
}));
|
||||
}, [metadataKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentFields = formMethods.getValues('search_config.reference_metadata.fields');
|
||||
if (referenceMetadataEnabled && Array.isArray(currentFields) && currentFields.length > 0 && metadataKeys) {
|
||||
const validFields = currentFields.filter((field) => metadataKeys.includes(field));
|
||||
if (validFields.length !== currentFields.length) {
|
||||
formMethods.setValue('search_config.reference_metadata.fields', validFields);
|
||||
}
|
||||
} else if (!referenceMetadataEnabled) {
|
||||
formMethods.setValue('search_config.reference_metadata.fields', undefined);
|
||||
}
|
||||
}, [selectedKbIds, metadataKeys, referenceMetadataEnabled, formMethods]);
|
||||
|
||||
// Reset top_k to 1024 only when user actively disables rerank (from true to false)
|
||||
const prevRerankEnabled = useRef<boolean | undefined>(undefined);
|
||||
@@ -227,11 +276,22 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
frequency_penalty: llm_setting.frequency_penalty,
|
||||
presence_penalty: llm_setting.presence_penalty,
|
||||
} as IllmSettingProps;
|
||||
const referenceMetadata = other_config.reference_metadata;
|
||||
const normalizedReferenceMetadata = referenceMetadata
|
||||
? {
|
||||
...referenceMetadata,
|
||||
...(Array.isArray(referenceMetadata.fields) &&
|
||||
referenceMetadata.fields.length === 0
|
||||
? { fields: undefined }
|
||||
: {}),
|
||||
}
|
||||
: referenceMetadata;
|
||||
|
||||
await updateSearch({
|
||||
...other_formdata,
|
||||
search_config: {
|
||||
...other_config,
|
||||
reference_metadata: normalizedReferenceMetadata,
|
||||
chat_id: llm_setting.llm_id,
|
||||
vector_similarity_weight: 1 - vector_similarity_weight,
|
||||
rerank_id: use_rerank ? rerank_id : '',
|
||||
@@ -288,6 +348,61 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
required
|
||||
></KnowledgeBaseFormField>
|
||||
<MetadataFilter prefix="search_config."></MetadataFilter>
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="search_config.reference_metadata.include"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (!value) {
|
||||
formMethods.setValue(
|
||||
'search_config.reference_metadata.fields',
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel tooltip="Display document metadata (e.g., title, page number, upload date) alongside retrieved text chunks">
|
||||
Show chunk metadata
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{referenceMetadataEnabled && (
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="search_config.reference_metadata.fields"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip="Select which metadata fields to display with each chunk">
|
||||
Metadata fields
|
||||
</FormLabel>
|
||||
<FormControl className="bg-bg-input">
|
||||
<MultiSelect
|
||||
options={metadataFieldOptions}
|
||||
onValueChange={field.onChange}
|
||||
showSelectAll={false}
|
||||
placeholder="Please select"
|
||||
maxCount={20}
|
||||
defaultValue={
|
||||
Array.isArray(field.value) ? field.value : []
|
||||
}
|
||||
value={Array.isArray(field.value) ? field.value : []}
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
onBlur={field.onBlur}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<SimilaritySliderFormField
|
||||
isTooltipShown
|
||||
similarityName="search_config.similarity_threshold"
|
||||
|
||||
@@ -28,6 +28,12 @@ import MindMapSheet from './mindmap-sheet';
|
||||
import { RAGFlowLogo } from './ragflow-logo';
|
||||
import RetrievalDocuments from './retrieval-documents';
|
||||
|
||||
const formatMetadataValue = (value: unknown) => {
|
||||
if (Array.isArray(value)) return value.join(', ');
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'object') return JSON.stringify(value);
|
||||
return String(value);
|
||||
};
|
||||
export default function SearchingView({
|
||||
setIsSearching,
|
||||
searchData,
|
||||
@@ -208,6 +214,26 @@ export default function SearchingView({
|
||||
{chunk.content_with_weight}
|
||||
</HighLightMarkdown>
|
||||
</div>
|
||||
{chunk.document_metadata &&
|
||||
Object.keys(chunk.document_metadata).length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{Object.entries(chunk.document_metadata).map(
|
||||
([key, value]) => (
|
||||
<div
|
||||
key={key}
|
||||
className="text-xs border border-border-default rounded px-2 py-1"
|
||||
>
|
||||
<span className="text-text-secondary">
|
||||
{key}:
|
||||
</span>{' '}
|
||||
<span className="text-text-primary">
|
||||
{formatMetadataValue(value)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit mt-3"
|
||||
onClick={() =>
|
||||
|
||||
@@ -185,6 +185,10 @@ export interface ISearchAppDetailProps {
|
||||
method: string;
|
||||
manual: { key: string; op: string; value: string }[];
|
||||
};
|
||||
reference_metadata?: {
|
||||
include?: boolean;
|
||||
fields?: string[];
|
||||
};
|
||||
};
|
||||
tenant_id: string;
|
||||
update_time: number;
|
||||
|
||||
@@ -24,6 +24,7 @@ const {
|
||||
listTagByKnowledgeIds,
|
||||
setMeta,
|
||||
getMeta,
|
||||
getMetaKeys,
|
||||
retrievalTestShare,
|
||||
} = api;
|
||||
|
||||
@@ -81,6 +82,10 @@ const methods = {
|
||||
url: getMeta,
|
||||
method: 'get',
|
||||
},
|
||||
getMetaKeys: {
|
||||
url: getMetaKeys,
|
||||
method: 'get',
|
||||
},
|
||||
retrievalTestShare: {
|
||||
url: retrievalTestShare,
|
||||
method: 'post',
|
||||
|
||||
Reference in New Issue
Block a user