diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 302fb9a3c7..dddd5f2526 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -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([ + { + id: 'notion', + name: 'Notion Page Search', + icon: , + authType: 'oauth', + status: 'pending', + }, + { + id: 'gmail', + name: 'Gmail Tools', + icon: Gmail, + authType: 'oauth', + status: 'pending', + }, + { + id: 'youtube', + name: 'YouTube Data Upload', + icon: YouTube, + authType: 'oauth', + status: 'pending', + }, + { + id: 'google-serp', + name: 'Google SerpApi Search', + icon: , + 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 ( +
+ +
+ ) + } + return (
= ({ + 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 ( +
+
+

+ {displayTitle} +

+

+ {displayDescription} +

+
+ +
+ {services.map(service => ( + + ))} +
+ + {onContinue && ( +
+ +
+ )} +
+ ) +} + +export default memo(ServiceConnectionPanel) + +export { default as ServiceItem } from './service-item' +export type { + ServiceConnectionPanelProps, + ServiceConnectionItem, + AuthType, + ServiceConnectionStatus, +} from './types' diff --git a/web/app/components/base/service-connection-panel/service-item.tsx b/web/app/components/base/service-connection-panel/service-item.tsx new file mode 100644 index 0000000000..81265470dc --- /dev/null +++ b/web/app/components/base/service-connection-panel/service-item.tsx @@ -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 = ({ + 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 ( +
+
+
+ {service.icon} +
+
+ + {service.name} + + {service.description && ( + + {service.description} + + )} +
+
+ +
+ ) +} + +export default memo(ServiceItem) diff --git a/web/app/components/base/service-connection-panel/types.ts b/web/app/components/base/service-connection-panel/types.ts new file mode 100644 index 0000000000..56a5bc4928 --- /dev/null +++ b/web/app/components/base/service-connection-panel/types.ts @@ -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 +} diff --git a/web/i18n/en-US/share.ts b/web/i18n/en-US/share.ts index 9139a03514..ad3e748f68 100644 --- a/web/i18n/en-US/share.ts +++ b/web/i18n/en-US/share.ts @@ -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 diff --git a/web/i18n/ja-JP/share.ts b/web/i18n/ja-JP/share.ts index 07d6ae3e45..607506ca95 100644 --- a/web/i18n/ja-JP/share.ts +++ b/web/i18n/ja-JP/share.ts @@ -77,6 +77,14 @@ const translation = { login: { backToHome: 'ホームに戻る', }, + serviceConnection: { + title: 'サービスを接続して開始', + description: 'このアプリを使用するには {{count}} 件の接続を設定する必要があります', + connect: '接続', + addApiKey: 'API Key を追加', + connected: '接続済み', + continue: '続行', + }, } export default translation diff --git a/web/i18n/zh-Hans/share.ts b/web/i18n/zh-Hans/share.ts index db67295b02..2f4c71e18f 100644 --- a/web/i18n/zh-Hans/share.ts +++ b/web/i18n/zh-Hans/share.ts @@ -77,6 +77,14 @@ const translation = { login: { backToHome: '返回首页', }, + serviceConnection: { + title: '连接所需服务以开始', + description: '您需要配置 {{count}} 个连接才能使用此应用', + connect: '连接', + addApiKey: '添加 API Key', + connected: '已连接', + continue: '继续', + }, } export default translation diff --git a/web/i18n/zh-Hant/share.ts b/web/i18n/zh-Hant/share.ts index afafbb4e35..0993e61569 100644 --- a/web/i18n/zh-Hant/share.ts +++ b/web/i18n/zh-Hant/share.ts @@ -77,6 +77,14 @@ const translation = { login: { backToHome: '返回首頁', }, + serviceConnection: { + title: '連接所需服務以開始', + description: '您需要配置 {{count}} 個連接才能使用此應用', + connect: '連接', + addApiKey: '新增 API Key', + connected: '已連接', + continue: '繼續', + }, } export default translation