diff --git a/rag/svr/sync_data_source.py b/rag/svr/sync_data_source.py index 525a8bb10..4349b6f55 100644 --- a/rag/svr/sync_data_source.py +++ b/rag/svr/sync_data_source.py @@ -157,9 +157,23 @@ class Confluence(SyncBase): from common.data_source.config import DocumentSource from common.data_source.interfaces import StaticCredentialsProvider - space = (self.conf.get("space") or "").strip() - page_id = (self.conf.get("page_id") or "").strip() - index_recursively = bool(self.conf.get("index_recursively", False)) + index_mode = (self.conf.get("index_mode") or "everything").lower() + if index_mode not in {"everything", "space", "page"}: + index_mode = "everything" + + space = "" + page_id = "" + + index_recursively = False + if index_mode == "space": + space = (self.conf.get("space") or "").strip() + if not space: + raise ValueError("Space Key is required when indexing a specific Confluence space.") + elif index_mode == "page": + page_id = (self.conf.get("page_id") or "").strip() + if not page_id: + raise ValueError("Page ID is required when indexing a specific Confluence page.") + index_recursively = bool(self.conf.get("index_recursively", False)) self.connector = ConfluenceConnector( wiki_base=self.conf["wiki_base"], diff --git a/web/src/pages/user-setting/data-source/component/confluence-token-field.tsx b/web/src/pages/user-setting/data-source/component/confluence-token-field.tsx new file mode 100644 index 000000000..5fe50b931 --- /dev/null +++ b/web/src/pages/user-setting/data-source/component/confluence-token-field.tsx @@ -0,0 +1,191 @@ +import { useEffect, useMemo } from 'react'; +import { ControllerRenderProps, useFormContext } from 'react-hook-form'; + +import { Checkbox } from '@/components/ui/checkbox'; +import { Input } from '@/components/ui/input'; +import { cn } from '@/lib/utils'; + +/* ---------------- Token Field ---------------- */ + +export type ConfluenceTokenFieldProps = ControllerRenderProps & { + fieldType: 'username' | 'token'; + placeholder?: string; + disabled?: boolean; +}; + +const ConfluenceTokenField = ({ + fieldType, + value, + onChange, + placeholder, + disabled, + ...rest +}: ConfluenceTokenFieldProps) => { + return ( +
+ onChange(e.target.value)} + placeholder={ + placeholder || + (fieldType === 'token' + ? 'Enter your Confluence access token' + : 'Confluence username or email') + } + disabled={disabled} + {...rest} + /> +
+ ); +}; + +/* ---------------- Indexing Mode Field ---------------- */ + +type ConfluenceIndexingMode = 'everything' | 'space' | 'page'; + +export type ConfluenceIndexingModeFieldProps = ControllerRenderProps; + +export const ConfluenceIndexingModeField = ( + fieldProps: ConfluenceIndexingModeFieldProps, +) => { + const { value, onChange, disabled } = fieldProps; + const { watch, setValue } = useFormContext(); + + const mode = useMemo( + () => (value as ConfluenceIndexingMode) || 'everything', + [value], + ); + + const spaceValue = watch('config.space'); + const pageIdValue = watch('config.page_id'); + const indexRecursively = watch('config.index_recursively'); + + useEffect(() => { + if (!value) onChange('everything'); + }, [value, onChange]); + + const handleModeChange = (nextMode?: string) => { + const normalized = (nextMode || 'everything') as ConfluenceIndexingMode; + onChange(normalized); + + if (normalized === 'everything') { + setValue('config.space', '', { shouldDirty: true, shouldTouch: true }); + setValue('config.page_id', '', { shouldDirty: true, shouldTouch: true }); + setValue('config.index_recursively', false, { + shouldDirty: true, + shouldTouch: true, + }); + } else if (normalized === 'space') { + setValue('config.page_id', '', { shouldDirty: true, shouldTouch: true }); + setValue('config.index_recursively', false, { + shouldDirty: true, + shouldTouch: true, + }); + } else if (normalized === 'page') { + setValue('config.space', '', { shouldDirty: true, shouldTouch: true }); + } + }; + + return ( +
+
+ {INDEX_MODE_OPTIONS.map((option) => { + const isActive = option.value === mode; + return ( + + ); + })} +
+ + {mode === 'everything' && ( +

+ This connector will index all pages the provided credentials have + access to. +

+ )} + + {mode === 'space' && ( +
+
+ Space Key +
+ + setValue('config.space', e.target.value, { + shouldDirty: true, + shouldTouch: true, + }) + } + placeholder="e.g. KB" + disabled={disabled} + /> +

+ The Confluence space key to index. +

+
+ )} + + {mode === 'page' && ( +
+
Page ID
+ + setValue('config.page_id', e.target.value, { + shouldDirty: true, + shouldTouch: true, + }) + } + placeholder="e.g. 123456" + disabled={disabled} + /> +

+ The Confluence page ID to index. +

+ +
+ + setValue('config.index_recursively', Boolean(checked), { + shouldDirty: true, + shouldTouch: true, + }) + } + disabled={disabled} + /> + + Index child pages recursively + +
+
+ )} +
+ ); +}; + +const INDEX_MODE_OPTIONS = [ + { label: 'Everything', value: 'everything' }, + { label: 'Space', value: 'space' }, + { label: 'Page', value: 'page' }, +]; + +export default ConfluenceTokenField; diff --git a/web/src/pages/user-setting/data-source/contant.tsx b/web/src/pages/user-setting/data-source/contant.tsx index 55f94072d..34ced0ae2 100644 --- a/web/src/pages/user-setting/data-source/contant.tsx +++ b/web/src/pages/user-setting/data-source/contant.tsx @@ -1,9 +1,9 @@ import { FormFieldType } from '@/components/dynamic-form'; import SvgIcon from '@/components/svg-icon'; import { t } from 'i18next'; +import { ConfluenceIndexingModeField } from './component/confluence-token-field'; import GmailTokenField from './component/gmail-token-field'; import GoogleDriveTokenField from './component/google-drive-token-field'; - export enum DataSourceKey { CONFLUENCE = 'confluence', S3 = 's3', @@ -230,23 +230,35 @@ export const DataSourceFormFields = { required: false, tooltip: t('setting.confluenceIsCloudTip'), }, + { + label: 'Index Method', + name: 'config.index_mode', + type: FormFieldType.Text, // keep as text so RHF registers it + required: false, + horizontal: true, + labelClassName: 'self-start pt-4', + render: (fieldProps) => , + }, { label: 'Space Key', name: 'config.space', type: FormFieldType.Text, required: false, + hidden: true, }, { label: 'Page ID', name: 'config.page_id', type: FormFieldType.Text, required: false, + hidden: true, }, { label: 'Index Recursively', name: 'config.index_recursively', type: FormFieldType.Checkbox, required: false, + hidden: true, }, ], [DataSourceKey.GOOGLE_DRIVE]: [