mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Feat: update front end for confluence connector (#11747)
### What problem does this PR solve? Feat: update front end for confluence connector ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -157,9 +157,23 @@ class Confluence(SyncBase):
|
|||||||
from common.data_source.config import DocumentSource
|
from common.data_source.config import DocumentSource
|
||||||
from common.data_source.interfaces import StaticCredentialsProvider
|
from common.data_source.interfaces import StaticCredentialsProvider
|
||||||
|
|
||||||
space = (self.conf.get("space") or "").strip()
|
index_mode = (self.conf.get("index_mode") or "everything").lower()
|
||||||
page_id = (self.conf.get("page_id") or "").strip()
|
if index_mode not in {"everything", "space", "page"}:
|
||||||
index_recursively = bool(self.conf.get("index_recursively", False))
|
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(
|
self.connector = ConfluenceConnector(
|
||||||
wiki_base=self.conf["wiki_base"],
|
wiki_base=self.conf["wiki_base"],
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<div className="flex w-full flex-col gap-2">
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
type={fieldType === 'token' ? 'password' : 'text'}
|
||||||
|
value={value ?? ''}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
placeholder={
|
||||||
|
placeholder ||
|
||||||
|
(fieldType === 'token'
|
||||||
|
? 'Enter your Confluence access token'
|
||||||
|
: 'Confluence username or email')
|
||||||
|
}
|
||||||
|
disabled={disabled}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ---------------- 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<ConfluenceIndexingMode>(
|
||||||
|
() => (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 (
|
||||||
|
<div className="w-full rounded-lg border border-border-button bg-bg-card p-4 space-y-4">
|
||||||
|
<div className="flex items-center gap-2 text-sm font-medium text-text-secondary">
|
||||||
|
{INDEX_MODE_OPTIONS.map((option) => {
|
||||||
|
const isActive = option.value === mode;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type="button"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => handleModeChange(option.value)}
|
||||||
|
className={cn(
|
||||||
|
'flex-1 rounded-lg border px-3 py-2 transition-all',
|
||||||
|
'border-transparent bg-transparent text-text-secondary hover:border-border-button hover:bg-bg-card-secondary',
|
||||||
|
isActive &&
|
||||||
|
'border-border-button bg-background text-primary shadow-sm',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mode === 'everything' && (
|
||||||
|
<p className="text-sm text-text-secondary">
|
||||||
|
This connector will index all pages the provided credentials have
|
||||||
|
access to.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'space' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm font-semibold text-text-primary">
|
||||||
|
Space Key
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
value={spaceValue ?? ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setValue('config.space', e.target.value, {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldTouch: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="e.g. KB"
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-text-secondary">
|
||||||
|
The Confluence space key to index.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'page' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm font-semibold text-text-primary">Page ID</div>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
value={pageIdValue ?? ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setValue('config.page_id', e.target.value, {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldTouch: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="e.g. 123456"
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-text-secondary">
|
||||||
|
The Confluence page ID to index.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 pt-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={Boolean(indexRecursively)}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setValue('config.index_recursively', Boolean(checked), {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldTouch: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-text-secondary">
|
||||||
|
Index child pages recursively
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const INDEX_MODE_OPTIONS = [
|
||||||
|
{ label: 'Everything', value: 'everything' },
|
||||||
|
{ label: 'Space', value: 'space' },
|
||||||
|
{ label: 'Page', value: 'page' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default ConfluenceTokenField;
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { FormFieldType } from '@/components/dynamic-form';
|
import { FormFieldType } from '@/components/dynamic-form';
|
||||||
import SvgIcon from '@/components/svg-icon';
|
import SvgIcon from '@/components/svg-icon';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { ConfluenceIndexingModeField } from './component/confluence-token-field';
|
||||||
import GmailTokenField from './component/gmail-token-field';
|
import GmailTokenField from './component/gmail-token-field';
|
||||||
import GoogleDriveTokenField from './component/google-drive-token-field';
|
import GoogleDriveTokenField from './component/google-drive-token-field';
|
||||||
|
|
||||||
export enum DataSourceKey {
|
export enum DataSourceKey {
|
||||||
CONFLUENCE = 'confluence',
|
CONFLUENCE = 'confluence',
|
||||||
S3 = 's3',
|
S3 = 's3',
|
||||||
@ -230,23 +230,35 @@ export const DataSourceFormFields = {
|
|||||||
required: false,
|
required: false,
|
||||||
tooltip: t('setting.confluenceIsCloudTip'),
|
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) => <ConfluenceIndexingModeField {...fieldProps} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Space Key',
|
label: 'Space Key',
|
||||||
name: 'config.space',
|
name: 'config.space',
|
||||||
type: FormFieldType.Text,
|
type: FormFieldType.Text,
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Page ID',
|
label: 'Page ID',
|
||||||
name: 'config.page_id',
|
name: 'config.page_id',
|
||||||
type: FormFieldType.Text,
|
type: FormFieldType.Text,
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Index Recursively',
|
label: 'Index Recursively',
|
||||||
name: 'config.index_recursively',
|
name: 'config.index_recursively',
|
||||||
type: FormFieldType.Checkbox,
|
type: FormFieldType.Checkbox,
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[DataSourceKey.GOOGLE_DRIVE]: [
|
[DataSourceKey.GOOGLE_DRIVE]: [
|
||||||
|
|||||||
Reference in New Issue
Block a user