mirror of https://github.com/langgenius/dify.git
feat: add service connection panel and translations for service connection messages
This commit is contained in:
parent
5e6053b367
commit
c8807d3f89
|
|
@ -24,6 +24,10 @@ import cn from '@/utils/classnames'
|
|||
import type { FileEntity } from '../../file-uploader/types'
|
||||
import { formatBooleanInputs } from '@/utils/model-config'
|
||||
import Avatar from '../../avatar'
|
||||
import ServiceConnectionPanel from '@/app/components/base/service-connection-panel'
|
||||
import type { AuthType, ServiceConnectionItem as ServiceConnectionItemType } from '@/app/components/base/service-connection-panel'
|
||||
import { Notion } from '@/app/components/base/icons/src/public/common'
|
||||
import { Google } from '@/app/components/base/icons/src/public/plugins'
|
||||
|
||||
const ChatWrapper = () => {
|
||||
const {
|
||||
|
|
@ -167,6 +171,53 @@ const ChatWrapper = () => {
|
|||
|
||||
const [collapsed, setCollapsed] = useState(!!currentConversationId)
|
||||
|
||||
// Demo: Service connection state
|
||||
const [serviceConnections, setServiceConnections] = useState<ServiceConnectionItemType[]>([
|
||||
{
|
||||
id: 'notion',
|
||||
name: 'Notion Page Search',
|
||||
icon: <Notion className="h-6 w-6" />,
|
||||
authType: 'oauth',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'gmail',
|
||||
name: 'Gmail Tools',
|
||||
icon: <img src="https://www.gstatic.com/images/branding/product/1x/gmail_2020q4_32dp.png" alt="Gmail" className="h-6 w-6" />,
|
||||
authType: 'oauth',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'youtube',
|
||||
name: 'YouTube Data Upload',
|
||||
icon: <img src="https://www.youtube.com/s/desktop/f506bd45/img/favicon_32x32.png" alt="YouTube" className="h-6 w-6" />,
|
||||
authType: 'oauth',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'google-serp',
|
||||
name: 'Google SerpApi Search',
|
||||
icon: <Google className="h-6 w-6" />,
|
||||
authType: 'api_key',
|
||||
status: 'pending',
|
||||
},
|
||||
])
|
||||
|
||||
const [showServiceConnection, setShowServiceConnection] = useState(true)
|
||||
|
||||
const handleServiceConnect = useCallback((serviceId: string, _authType: AuthType) => {
|
||||
// Demo: 模拟连接成功
|
||||
setServiceConnections(prev => prev.map(service =>
|
||||
service.id === serviceId
|
||||
? { ...service, status: 'connected' as const }
|
||||
: service,
|
||||
))
|
||||
}, [])
|
||||
|
||||
const handleServiceContinue = useCallback(() => {
|
||||
setShowServiceConnection(false)
|
||||
}, [])
|
||||
|
||||
const chatNode = useMemo(() => {
|
||||
if (allInputsHidden || !inputsForms.length)
|
||||
return null
|
||||
|
|
@ -253,6 +304,23 @@ const ChatWrapper = () => {
|
|||
/>
|
||||
: null
|
||||
|
||||
// 如果需要显示服务连接面板,则显示面板而非聊天界面
|
||||
if (showServiceConnection) {
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex h-full items-center justify-center overflow-auto bg-chatbot-bg',
|
||||
isMobile && 'px-4 py-8',
|
||||
)}>
|
||||
<ServiceConnectionPanel
|
||||
services={serviceConnections}
|
||||
onConnect={handleServiceConnect}
|
||||
onContinue={handleServiceContinue}
|
||||
className={cn(isMobile && 'max-w-full')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='h-full overflow-hidden bg-chatbot-bg'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { RiArrowRightLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ServiceItem from './service-item'
|
||||
import type { ServiceConnectionPanelProps } from './types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const ServiceConnectionPanel: FC<ServiceConnectionPanelProps> = ({
|
||||
title,
|
||||
description,
|
||||
services,
|
||||
onConnect,
|
||||
onContinue,
|
||||
continueDisabled,
|
||||
continueText,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const allConnected = useMemo(() => {
|
||||
return services.every(service => service.status === 'connected')
|
||||
}, [services])
|
||||
|
||||
const displayTitle = title || t('share.serviceConnection.title')
|
||||
const displayDescription = description || t('share.serviceConnection.description', { count: services.length })
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex w-full max-w-[600px] flex-col items-center',
|
||||
className,
|
||||
)}>
|
||||
<div className="mb-6 text-center">
|
||||
<h2 className="system-xl-semibold mb-1 text-text-primary">
|
||||
{displayTitle}
|
||||
</h2>
|
||||
<p className="system-sm-regular text-text-tertiary">
|
||||
{displayDescription}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-2">
|
||||
{services.map(service => (
|
||||
<ServiceItem
|
||||
key={service.id}
|
||||
service={service}
|
||||
onConnect={onConnect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{onContinue && (
|
||||
<div className="mt-6 flex w-full justify-end">
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={continueDisabled ?? !allConnected}
|
||||
onClick={onContinue}
|
||||
>
|
||||
{continueText || t('share.serviceConnection.continue')}
|
||||
<RiArrowRightLine className="ml-1 h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ServiceConnectionPanel)
|
||||
|
||||
export { default as ServiceItem } from './service-item'
|
||||
export type {
|
||||
ServiceConnectionPanelProps,
|
||||
ServiceConnectionItem,
|
||||
AuthType,
|
||||
ServiceConnectionStatus,
|
||||
} from './types'
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { AuthType, ServiceConnectionItem } from './types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ServiceItemProps = {
|
||||
service: ServiceConnectionItem
|
||||
onConnect: (serviceId: string, authType: AuthType) => void
|
||||
}
|
||||
|
||||
const ServiceItem: FC<ServiceItemProps> = ({
|
||||
service,
|
||||
onConnect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleConnect = () => {
|
||||
onConnect(service.id, service.authType)
|
||||
}
|
||||
|
||||
const getButtonText = () => {
|
||||
if (service.status === 'connected')
|
||||
return t('share.serviceConnection.connected')
|
||||
|
||||
if (service.authType === 'api_key')
|
||||
return t('share.serviceConnection.addApiKey')
|
||||
|
||||
return t('share.serviceConnection.connect')
|
||||
}
|
||||
|
||||
const isConnected = service.status === 'connected'
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex items-center justify-between gap-3 rounded-xl border border-components-panel-border-subtle bg-components-panel-bg px-4 py-3',
|
||||
'hover:border-components-panel-border hover:shadow-xs',
|
||||
'transition-all duration-200',
|
||||
)}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center">
|
||||
{service.icon}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="system-sm-medium text-text-secondary">
|
||||
{service.name}
|
||||
</span>
|
||||
{service.description && (
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{service.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant={isConnected ? 'secondary' : 'secondary-accent'}
|
||||
size="small"
|
||||
onClick={handleConnect}
|
||||
disabled={isConnected}
|
||||
>
|
||||
{!isConnected && <RiAddLine className="mr-0.5 h-3.5 w-3.5" />}
|
||||
{getButtonText()}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ServiceItem)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import type { ReactNode } from 'react'
|
||||
|
||||
export type AuthType = 'oauth' | 'api_key'
|
||||
|
||||
export type ServiceConnectionStatus = 'pending' | 'connected' | 'error'
|
||||
|
||||
export type ServiceConnectionItem = {
|
||||
id: string
|
||||
name: string
|
||||
icon: ReactNode
|
||||
authType: AuthType
|
||||
status: ServiceConnectionStatus
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type ServiceConnectionPanelProps = {
|
||||
title?: string
|
||||
description?: string
|
||||
services: ServiceConnectionItem[]
|
||||
onConnect: (serviceId: string, authType: AuthType) => void
|
||||
onContinue?: () => void
|
||||
continueDisabled?: boolean
|
||||
continueText?: string
|
||||
className?: string
|
||||
}
|
||||
|
|
@ -81,6 +81,14 @@ const translation = {
|
|||
login: {
|
||||
backToHome: 'Back to Home',
|
||||
},
|
||||
serviceConnection: {
|
||||
title: 'Connect the required services to start',
|
||||
description: 'You need to configure {{count}} connections before you can use this app',
|
||||
connect: 'Connect',
|
||||
addApiKey: 'Add API Key',
|
||||
connected: 'Connected',
|
||||
continue: 'Continue',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ const translation = {
|
|||
login: {
|
||||
backToHome: 'ホームに戻る',
|
||||
},
|
||||
serviceConnection: {
|
||||
title: 'サービスを接続して開始',
|
||||
description: 'このアプリを使用するには {{count}} 件の接続を設定する必要があります',
|
||||
connect: '接続',
|
||||
addApiKey: 'API Key を追加',
|
||||
connected: '接続済み',
|
||||
continue: '続行',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ const translation = {
|
|||
login: {
|
||||
backToHome: '返回首页',
|
||||
},
|
||||
serviceConnection: {
|
||||
title: '连接所需服务以开始',
|
||||
description: '您需要配置 {{count}} 个连接才能使用此应用',
|
||||
connect: '连接',
|
||||
addApiKey: '添加 API Key',
|
||||
connected: '已连接',
|
||||
continue: '继续',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ const translation = {
|
|||
login: {
|
||||
backToHome: '返回首頁',
|
||||
},
|
||||
serviceConnection: {
|
||||
title: '連接所需服務以開始',
|
||||
description: '您需要配置 {{count}} 個連接才能使用此應用',
|
||||
connect: '連接',
|
||||
addApiKey: '新增 API Key',
|
||||
connected: '已連接',
|
||||
continue: '繼續',
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
|
|
|||
Loading…
Reference in New Issue