mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
refactor(web): share new RAG setup controls
This commit is contained in:
parent
fbde89eeb9
commit
655e596106
@ -44,8 +44,9 @@ import {
|
||||
sourceProviderOptionForDraft,
|
||||
} from './source-provider-options'
|
||||
import {
|
||||
SourceProviderIcon,
|
||||
SourceProviderNotInstalledCard,
|
||||
SourceProviderRadioGroup,
|
||||
SourceProviderSelector,
|
||||
SourceTypeSelector,
|
||||
} from './source-setup-fields'
|
||||
import { WebsiteCrawlPreview } from './website-crawl-preview'
|
||||
@ -207,64 +208,6 @@ function getSupportedAuthKinds(provider: Provider, credentialId?: string) {
|
||||
return supported
|
||||
}
|
||||
|
||||
function ProviderSelector({
|
||||
disabled = false,
|
||||
onMoreProviders,
|
||||
options,
|
||||
providerKey,
|
||||
onChange,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
onMoreProviders: () => void
|
||||
options: SourceProviderOption[]
|
||||
providerKey: string
|
||||
onChange: (providerKey: string) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
|
||||
return (
|
||||
<Fieldset disabled={disabled}>
|
||||
<div className="mb-1.5 flex items-center justify-between gap-3">
|
||||
<FieldsetLegend className="py-0 system-xs-medium">
|
||||
{t(($) => $['newKnowledge.providerLabel'])}
|
||||
</FieldsetLegend>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost-accent"
|
||||
size="small"
|
||||
className="gap-0.5 px-2.75"
|
||||
disabled={disabled}
|
||||
onClick={onMoreProviders}
|
||||
>
|
||||
{t(($) => $['newKnowledge.moreProviders'])}
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<SourceProviderRadioGroup
|
||||
value={providerKey}
|
||||
disabled={disabled}
|
||||
layout="wrap"
|
||||
options={options.map((option) => ({
|
||||
icon: <WebsiteProviderIcon option={option} />,
|
||||
label: option.label,
|
||||
value: option.key,
|
||||
}))}
|
||||
size="small"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
function WebsiteProviderIcon({ option }: { option: SourceProviderOption }) {
|
||||
const icon = option.installed
|
||||
? (option.datasource.identity.icon ?? option.plugin.declaration.identity.icon)
|
||||
: undefined
|
||||
if (typeof icon === 'string' && icon)
|
||||
return <img aria-hidden alt="" className="size-4 shrink-0 object-contain" src={icon} />
|
||||
return <span aria-hidden className={`${option.fallbackIcon} size-4 shrink-0`} />
|
||||
}
|
||||
|
||||
function ProviderFieldControl({
|
||||
field,
|
||||
setValues,
|
||||
@ -680,7 +623,15 @@ function UnconfiguredProvider({
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-2.5 rounded-xl bg-background-section p-4">
|
||||
<span className="flex size-9 items-center justify-center rounded-lg border border-divider-subtle bg-background-default">
|
||||
<WebsiteProviderIcon option={providerOption} />
|
||||
<SourceProviderIcon
|
||||
fallbackIcon={providerOption.fallbackIcon}
|
||||
icon={
|
||||
providerOption.installed
|
||||
? (providerOption.datasource.identity.icon ??
|
||||
providerOption.plugin.declaration.identity.icon)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<h3 className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.providerNotConfigured'], {
|
||||
@ -1311,7 +1262,7 @@ export function AddSourcePage({
|
||||
/>
|
||||
{sourceDraft.sourceType === 'websiteCrawl' ? (
|
||||
<>
|
||||
<ProviderSelector
|
||||
<SourceProviderSelector
|
||||
disabled={websiteSetupLocked}
|
||||
options={websiteProviderOptions}
|
||||
providerKey={websiteProviderOption?.key ?? ''}
|
||||
@ -1351,7 +1302,7 @@ export function AddSourcePage({
|
||||
</div>
|
||||
) : websiteProviderOption && !websiteProviderOption.installed ? (
|
||||
<SourceProviderNotInstalledCard
|
||||
icon={<WebsiteProviderIcon option={websiteProviderOption} />}
|
||||
icon={<SourceProviderIcon fallbackIcon={websiteProviderOption.fallbackIcon} />}
|
||||
provider={websiteProviderOption.label}
|
||||
onInstall={() =>
|
||||
globalThis.open(
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const retrievalModes = ['fast', 'deep', 'research'] as const
|
||||
|
||||
export type RetrievalMode = (typeof retrievalModes)[number]
|
||||
|
||||
export function RetrievalModeSegmentedControl({
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
appearance = 'default',
|
||||
disabled = false,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
'aria-label'?: string
|
||||
'aria-labelledby'?: string
|
||||
appearance?: 'composer' | 'default'
|
||||
disabled?: boolean
|
||||
value: RetrievalMode
|
||||
onChange: (value: RetrievalMode) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
|
||||
return (
|
||||
<SegmentedControl<RetrievalMode>
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
className={cn(
|
||||
appearance === 'composer' && 'flex min-w-46.5 gap-0.5 bg-background-section-burn',
|
||||
)}
|
||||
value={[value]}
|
||||
onValueChange={(values) => {
|
||||
const nextValue = values[0]
|
||||
if (nextValue) onChange(nextValue)
|
||||
}}
|
||||
>
|
||||
{retrievalModes.map((mode) => (
|
||||
<SegmentedControlItem<RetrievalMode>
|
||||
key={mode}
|
||||
value={mode}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
appearance === 'composer' &&
|
||||
'grow border-0 px-2.5 py-1.25 system-sm-regular text-text-tertiary capitalize data-pressed:bg-components-panel-bg data-pressed:font-medium data-pressed:text-text-primary',
|
||||
)}
|
||||
>
|
||||
{t(($) => $[`newKnowledge.settings.retrievalMode.${mode}`])}
|
||||
</SegmentedControlItem>
|
||||
))}
|
||||
</SegmentedControl>
|
||||
)
|
||||
}
|
||||
@ -15,11 +15,9 @@ import type {
|
||||
DataSourceAuth,
|
||||
DataSourceCredential,
|
||||
} from '@/app/components/header/account-setting/data-source-page-new/types'
|
||||
import type { DataSourceItem } from '@/app/components/workflow/block-selector/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -46,8 +44,9 @@ import {
|
||||
import {
|
||||
SourceConnectionRequiredCard,
|
||||
SourceNameField,
|
||||
SourceProviderIcon,
|
||||
SourceProviderNotInstalledCard,
|
||||
SourceProviderRadioGroup,
|
||||
SourceProviderSelector,
|
||||
SourceSyncPolicyField,
|
||||
} from './source-setup-fields'
|
||||
|
||||
@ -142,11 +141,9 @@ function datasourceProviderForOption(option?: SourceProviderOption) {
|
||||
: undefined
|
||||
}
|
||||
|
||||
type ProviderBrandIconValue = DataSourceItem['declaration']['identity']['icon']
|
||||
|
||||
function datasourceProviderIcon(
|
||||
datasourceProvider: ReturnType<typeof datasourceProviderForOption>,
|
||||
): ProviderBrandIconValue | undefined {
|
||||
) {
|
||||
return datasourceProvider?.plugin.declaration.identity.icon
|
||||
}
|
||||
|
||||
@ -315,83 +312,6 @@ function requestStatus(error: unknown) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function ProviderSelector({
|
||||
options,
|
||||
providerKey,
|
||||
onChange,
|
||||
onMoreProviders,
|
||||
}: {
|
||||
options: SourceProviderOption[]
|
||||
providerKey: string
|
||||
onChange: (providerKey: string) => void
|
||||
onMoreProviders: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
return (
|
||||
<Fieldset>
|
||||
<div className="mb-2 flex items-center justify-between gap-3">
|
||||
<FieldsetLegend className="py-0 system-xs-medium">
|
||||
{t(($) => $['newKnowledge.providerLabel'])}
|
||||
</FieldsetLegend>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost-accent"
|
||||
size="small"
|
||||
className="h-6 gap-0.5 px-0"
|
||||
onClick={onMoreProviders}
|
||||
>
|
||||
{t(($) => $['newKnowledge.moreProviders'])}
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<SourceProviderRadioGroup
|
||||
value={providerKey}
|
||||
layout="wrap"
|
||||
options={options.map((option) => ({
|
||||
icon: (
|
||||
<ProviderBrandIcon
|
||||
fallbackIcon={option.fallbackIcon}
|
||||
icon={
|
||||
option.installed
|
||||
? (option.datasource.identity.icon ?? option.plugin.declaration.identity.icon)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
label: option.label,
|
||||
value: option.key,
|
||||
}))}
|
||||
size="small"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
function ProviderBrandIcon({
|
||||
fallbackIcon,
|
||||
icon,
|
||||
}: {
|
||||
fallbackIcon: string
|
||||
icon?: ProviderBrandIconValue
|
||||
}) {
|
||||
if (typeof icon === 'string' && icon)
|
||||
return <img aria-hidden alt="" className="size-4 shrink-0 object-contain" src={icon} />
|
||||
|
||||
if (icon && typeof icon !== 'string')
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
className="flex size-4 shrink-0 items-center justify-center overflow-hidden rounded text-2xs"
|
||||
style={{ backgroundColor: icon.background }}
|
||||
>
|
||||
{icon.content}
|
||||
</span>
|
||||
)
|
||||
|
||||
return <span aria-hidden className={`${fallbackIcon} size-4 shrink-0`} />
|
||||
}
|
||||
|
||||
function OAuthConnectionCard({
|
||||
draft,
|
||||
icon,
|
||||
@ -399,7 +319,7 @@ function OAuthConnectionCard({
|
||||
onConnect,
|
||||
}: {
|
||||
draft: ConnectedSourceDraft
|
||||
icon?: ProviderBrandIconValue
|
||||
icon?: ReturnType<typeof datasourceProviderIcon>
|
||||
providerOption: InstalledSourceProviderOption
|
||||
onConnect: () => void
|
||||
}) {
|
||||
@ -414,7 +334,7 @@ function OAuthConnectionCard({
|
||||
provider: draft.provider,
|
||||
})
|
||||
}
|
||||
icon={<ProviderBrandIcon fallbackIcon={providerOption.fallbackIcon} icon={icon} />}
|
||||
icon={<SourceProviderIcon fallbackIcon={providerOption.fallbackIcon} icon={icon} />}
|
||||
title={
|
||||
draft.provider === 'Notion'
|
||||
? t(($) => $['newKnowledge.notionNotConnected'])
|
||||
@ -1824,7 +1744,8 @@ export function ConnectedSourceSetup({
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<ProviderSelector
|
||||
<SourceProviderSelector
|
||||
appearance="embedded"
|
||||
options={providerOptions}
|
||||
providerKey={providerOption?.key ?? ''}
|
||||
onChange={selectProvider}
|
||||
@ -1860,7 +1781,7 @@ export function ConnectedSourceSetup({
|
||||
</div>
|
||||
) : providerOption && !providerOption.installed ? (
|
||||
<SourceProviderNotInstalledCard
|
||||
icon={<ProviderBrandIcon fallbackIcon={providerOption.fallbackIcon} />}
|
||||
icon={<SourceProviderIcon fallbackIcon={providerOption.fallbackIcon} />}
|
||||
provider={providerOption.label}
|
||||
onInstall={() =>
|
||||
globalThis.open(
|
||||
|
||||
@ -24,7 +24,6 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
@ -40,6 +39,7 @@ import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { KnowledgeSettingsMembers } from './components/knowledge-settings-members'
|
||||
import { KnowledgeSpaceIcon } from './components/knowledge-space-icon'
|
||||
import { RetrievalModeSegmentedControl } from './components/retrieval-mode-segmented-control'
|
||||
import { isKnowledgeModelSetupReady, KNOWLEDGE_NAME_MAX_LENGTH } from './constants'
|
||||
import { newKnowledgeListPath } from './routes'
|
||||
|
||||
@ -1024,30 +1024,22 @@ export function KnowledgeSettingsForm({
|
||||
>
|
||||
{t(($) => $['newKnowledge.settings.retrievalDepth'])}
|
||||
</label>
|
||||
<SegmentedControl
|
||||
<RetrievalModeSegmentedControl
|
||||
aria-labelledby="knowledge-retrieval-depth-label"
|
||||
value={[retrievalMode]}
|
||||
onValueChange={(values) => {
|
||||
const mode = values[0]
|
||||
if (mode === 'fast' || mode === 'deep' || mode === 'research') {
|
||||
const nextScoreThresholdEnabled =
|
||||
mode !== 'research' && !rerankEnabled ? false : scoreThresholdEnabled
|
||||
setRetrievalMode(mode)
|
||||
setScoreThresholdEnabled(nextScoreThresholdEnabled)
|
||||
void performSettingsSave({
|
||||
...currentSettingsDraft,
|
||||
retrievalMode: mode,
|
||||
scoreThresholdEnabled: nextScoreThresholdEnabled,
|
||||
})
|
||||
}
|
||||
disabled={retrievalFieldsDisabled}
|
||||
value={retrievalMode}
|
||||
onChange={(mode) => {
|
||||
const nextScoreThresholdEnabled =
|
||||
mode !== 'research' && !rerankEnabled ? false : scoreThresholdEnabled
|
||||
setRetrievalMode(mode)
|
||||
setScoreThresholdEnabled(nextScoreThresholdEnabled)
|
||||
void performSettingsSave({
|
||||
...currentSettingsDraft,
|
||||
retrievalMode: mode,
|
||||
scoreThresholdEnabled: nextScoreThresholdEnabled,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{(['fast', 'deep', 'research'] as const).map((mode) => (
|
||||
<SegmentedControlItem key={mode} value={mode} disabled={retrievalFieldsDisabled}>
|
||||
{t(($) => $[`newKnowledge.settings.retrievalMode.${mode}`])}
|
||||
</SegmentedControlItem>
|
||||
))}
|
||||
</SegmentedControl>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 sm:flex-row">
|
||||
|
||||
@ -26,6 +26,7 @@ import { Markdown } from '@/app/components/base/markdown'
|
||||
import { Link as MarkdownLink } from '@/app/components/base/markdown-blocks'
|
||||
import Link from '@/next/link'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { RetrievalModeSegmentedControl } from './components/retrieval-mode-segmented-control'
|
||||
import {
|
||||
extractRetrievalEvidence,
|
||||
extractStreamError,
|
||||
@ -1589,34 +1590,19 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
|
||||
}}
|
||||
/>
|
||||
<div className="flex min-h-13 items-center justify-between gap-3 p-2.5">
|
||||
<div
|
||||
role="group"
|
||||
<RetrievalModeSegmentedControl
|
||||
aria-label={t(($) => $['newKnowledge.settings.retrievalModeLabel'])}
|
||||
className="flex min-w-46.5 gap-0.5 rounded-lg bg-background-section-burn p-0.5"
|
||||
>
|
||||
{(['fast', 'deep', 'research'] as const).map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
disabled={selectedResearchActive || localRun?.status === 'running'}
|
||||
aria-pressed={mode === item}
|
||||
className={cn(
|
||||
'grow rounded-md px-2.5 py-1.25 system-sm-regular text-text-tertiary capitalize outline-hidden hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed',
|
||||
mode === item &&
|
||||
'bg-components-panel-bg font-medium text-text-primary shadow-xs',
|
||||
)}
|
||||
onClick={() =>
|
||||
setComposerDraft({
|
||||
mode: item,
|
||||
query,
|
||||
...(selectedHistoryKey ? { selectionKey: selectedHistoryKey } : {}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{t(($) => $[`newKnowledge.settings.retrievalMode.${item}`])}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
appearance="composer"
|
||||
disabled={selectedResearchActive || localRun?.status === 'running'}
|
||||
value={mode}
|
||||
onChange={(nextMode) =>
|
||||
setComposerDraft({
|
||||
mode: nextMode,
|
||||
query,
|
||||
...(selectedHistoryKey ? { selectionKey: selectedHistoryKey } : {}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="px-3.25"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { NewKnowledgeSourceDraft, NewKnowledgeSourceType } from './routes'
|
||||
import type { InstalledSourceProviderOption, SourceProviderOption } from './source-provider-options'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
@ -134,6 +135,99 @@ export function SourceProviderRadioGroup<T extends string>({
|
||||
)
|
||||
}
|
||||
|
||||
type SourceProviderIconValue =
|
||||
| InstalledSourceProviderOption['datasource']['identity']['icon']
|
||||
| InstalledSourceProviderOption['plugin']['declaration']['identity']['icon']
|
||||
|
||||
export function SourceProviderIcon({
|
||||
fallbackIcon,
|
||||
icon,
|
||||
}: {
|
||||
fallbackIcon: string
|
||||
icon?: SourceProviderIconValue
|
||||
}) {
|
||||
if (typeof icon === 'string' && icon)
|
||||
return <img aria-hidden alt="" className="size-4 shrink-0 object-contain" src={icon} />
|
||||
|
||||
if (icon && typeof icon !== 'string')
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
className="flex size-4 shrink-0 items-center justify-center overflow-hidden rounded text-2xs"
|
||||
style={{ backgroundColor: icon.background }}
|
||||
>
|
||||
{icon.content}
|
||||
</span>
|
||||
)
|
||||
|
||||
return <span aria-hidden className={`${fallbackIcon} size-4 shrink-0`} />
|
||||
}
|
||||
|
||||
export function SourceProviderSelector({
|
||||
appearance = 'page',
|
||||
disabled = false,
|
||||
onMoreProviders,
|
||||
options,
|
||||
providerKey,
|
||||
onChange,
|
||||
}: {
|
||||
appearance?: 'embedded' | 'page'
|
||||
disabled?: boolean
|
||||
onMoreProviders: () => void
|
||||
options: SourceProviderOption[]
|
||||
providerKey: string
|
||||
onChange: (providerKey: string) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
|
||||
return (
|
||||
<Fieldset disabled={disabled}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between gap-3',
|
||||
appearance === 'embedded' ? 'mb-2' : 'mb-1.5',
|
||||
)}
|
||||
>
|
||||
<FieldsetLegend className="py-0 system-xs-medium">
|
||||
{t(($) => $['newKnowledge.providerLabel'])}
|
||||
</FieldsetLegend>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost-accent"
|
||||
size="small"
|
||||
className={cn('gap-0.5', appearance === 'embedded' ? 'h-6 px-0' : 'px-2.75')}
|
||||
disabled={disabled}
|
||||
onClick={onMoreProviders}
|
||||
>
|
||||
{t(($) => $['newKnowledge.moreProviders'])}
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<SourceProviderRadioGroup
|
||||
value={providerKey}
|
||||
disabled={disabled}
|
||||
layout="wrap"
|
||||
options={options.map((option) => ({
|
||||
icon: (
|
||||
<SourceProviderIcon
|
||||
fallbackIcon={option.fallbackIcon}
|
||||
icon={
|
||||
option.installed
|
||||
? (option.datasource.identity.icon ?? option.plugin.declaration.identity.icon)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
label: option.label,
|
||||
value: option.key,
|
||||
}))}
|
||||
size="small"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
export function SourceNameField({
|
||||
className,
|
||||
disabled = false,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user