feat: support assistant frontend (#2139)

Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
This commit is contained in:
Joel
2024-01-23 19:31:56 +08:00
committed by GitHub
parent e65a2a400d
commit 7bbe12b2bd
194 changed files with 8726 additions and 1586 deletions

View File

@@ -75,7 +75,7 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({
<Modal
isShow
onClose={() => {}}
wrapperClassName='!z-30'
wrapperClassName='!z-[103]'
className='!p-8 !pb-6 !max-w-none !w-[640px]'
>
<div className='mb-2 text-xl font-semibold text-gray-900'>

View File

@@ -70,7 +70,7 @@ const ApiBasedExtensionSelector: FC<ApiBasedExtensionSelectorProps> = ({
)
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='w-[calc(100%-32px)] max-w-[576px] z-[11]'>
<PortalToFollowElemContent className='w-[calc(100%-32px)] max-w-[576px] z-[102]'>
<div className='w-full rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg z-10'>
<div className='p-1'>
<div className='flex items-center justify-between px-3 pt-2 pb-1'>

View File

@@ -8,7 +8,6 @@ import AccountPage from './account-page'
import MembersPage from './members-page'
import IntegrationsPage from './Integrations-page'
import LanguagePage from './language-page'
import PluginPage from './plugin-page'
import ApiBasedExtensionPage from './api-based-extension-page'
import DataSourcePage from './data-source-page'
import ModelProviderPage from './model-provider-page'
@@ -18,10 +17,9 @@ import CustomPage from '@/app/components/custom/custom-page'
import Modal from '@/app/components/base/modal'
import {
Database03,
PuzzlePiece01,
Webhooks,
} from '@/app/components/base/icons/src/vender/line/development'
import { Database03 as Database03Solid, PuzzlePiece01 as PuzzlePiece01Solid } from '@/app/components/base/icons/src/vender/solid/development'
import { Database03 as Database03Solid } from '@/app/components/base/icons/src/vender/solid/development'
import { User01, Users01 } from '@/app/components/base/icons/src/vender/line/users'
import { User01 as User01Solid, Users01 as Users01Solid } from '@/app/components/base/icons/src/vender/solid/users'
import { Globe01 } from '@/app/components/base/icons/src/vender/line/mapsAndTravel'
@@ -89,12 +87,6 @@ export default function AccountSetting({
icon: <Database03 className={iconClassName} />,
activeIcon: <Database03Solid className={iconClassName} />,
},
{
key: 'plugin',
name: t('common.settings.plugin'),
icon: <PuzzlePiece01 className={iconClassName} />,
activeIcon: <PuzzlePiece01Solid className={iconClassName} />,
},
{
key: 'api-based-extension',
name: t('common.settings.apiBasedExtension'),
@@ -224,9 +216,8 @@ export default function AccountSetting({
{activeMenu === 'language' && <LanguagePage />}
{activeMenu === 'provider' && <ModelProviderPage />}
{activeMenu === 'data-source' && <DataSourcePage />}
{activeMenu === 'plugin' && <PluginPage />}
{activeMenu === 'api-based-extension' && <ApiBasedExtensionPage /> }
{activeMenu === 'custom' && <CustomPage /> }
{activeMenu === 'api-based-extension' && <ApiBasedExtensionPage />}
{activeMenu === 'custom' && <CustomPage />}
</div>
</div>
</div>

View File

@@ -7,6 +7,7 @@ export type TypeWithI18N<T = string> = {
export enum FormTypeEnum {
textInput = 'text-input',
textNumber = 'number-input',
secretInput = 'secret-input',
select = 'select',
radio = 'radio',
@@ -92,10 +93,12 @@ export type CredentialFormSchemaBase = {
type: FormTypeEnum
required: boolean
default?: string
tooltip?: TypeWithI18N
show_on: FormShowOnObject[]
}
export type CredentialFormSchemaTextInput = CredentialFormSchemaBase & { max_length?: number; placeholder?: TypeWithI18N }
export type CredentialFormSchemaNumberInput = CredentialFormSchemaBase & { min?: number; max?: number; placeholder?: TypeWithI18N }
export type CredentialFormSchemaSelect = CredentialFormSchemaBase & { options: FormOption[]; placeholder?: TypeWithI18N }
export type CredentialFormSchemaRadio = CredentialFormSchemaBase & { options: FormOption[] }
export type CredentialFormSchemaSecretInput = CredentialFormSchemaBase & { placeholder?: TypeWithI18N }

View File

@@ -1,8 +1,10 @@
import { useState } from 'react'
import type { FC } from 'react'
import cn from 'classnames'
import { ValidatingTip } from '../../key-validator/ValidateStatus'
import type {
CredentialFormSchema,
CredentialFormSchemaNumberInput,
CredentialFormSchemaRadio,
CredentialFormSchemaSecretInput,
CredentialFormSchemaSelect,
@@ -13,7 +15,8 @@ import { FormTypeEnum } from '../declarations'
import { useLanguage } from '../hooks'
import Input from './Input'
import { SimpleSelect } from '@/app/components/base/select'
import Tooltip from '@/app/components/base/tooltip-plus'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
type FormProps = {
value: FormValue
onChange: (val: FormValue) => void
@@ -22,6 +25,9 @@ type FormProps = {
validatedSuccess?: boolean
showOnVariableMap: Record<string, string[]>
isEditMode: boolean
readonly?: boolean
inputClassName?: string
isShowDefaultValue?: boolean
}
const Form: FC<FormProps> = ({
@@ -32,6 +38,9 @@ const Form: FC<FormProps> = ({
validatedSuccess,
showOnVariableMap,
isEditMode,
readonly,
inputClassName,
isShowDefaultValue = false,
}) => {
const language = useLanguage()
const [changeKey, setChangeKey] = useState('')
@@ -51,7 +60,19 @@ const Form: FC<FormProps> = ({
}
const renderField = (formSchema: CredentialFormSchema) => {
if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput) {
const tooltip = formSchema.tooltip
const tooltipContent = (tooltip && (
<span className='ml-1 pt-1.5'>
<Tooltip popupContent={
// w-[100px] caused problem
<div className=''>
{tooltip[language]}
</div>
} >
<HelpCircle className='w-3 h-3 text-gray-500' />
</Tooltip>
</span>))
if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput || formSchema.type === FormTypeEnum.textNumber) {
const {
variable,
label,
@@ -63,8 +84,7 @@ const Form: FC<FormProps> = ({
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null
const disabed = isEditMode && (variable === '__model_type' || variable === '__model_name')
const disabed = readonly || (isEditMode && (variable === '__model_type' || variable === '__model_name'))
return (
<div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'>
@@ -74,14 +94,17 @@ const Form: FC<FormProps> = ({
<span className='ml-1 text-red-500'>*</span>
)
}
{tooltipContent}
</div>
<Input
className={`${disabed && 'cursor-not-allowed opacity-60'}`}
value={value[variable] as string}
className={cn(inputClassName, `${disabed && 'cursor-not-allowed opacity-60'}`)}
value={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
onChange={val => handleFormChange(variable, val)}
validated={validatedSuccess}
placeholder={placeholder?.[language]}
disabled={disabed}
type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'}
{...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})}
/>
{validating && changeKey === variable && <ValidatingTip />}
</div>
@@ -111,6 +134,7 @@ const Form: FC<FormProps> = ({
<span className='ml-1 text-red-500'>*</span>
)
}
{tooltipContent}
</div>
<div className={`grid grid-cols-${options?.length} gap-3`}>
{
@@ -160,14 +184,18 @@ const Form: FC<FormProps> = ({
<div key={variable} className='py-3'>
<div className='py-2 text-sm text-gray-900'>
{label[language]}
{
required && (
<span className='ml-1 text-red-500'>*</span>
)
}
{tooltipContent}
</div>
<SimpleSelect
defaultValue={value[variable] as string}
className={cn(inputClassName)}
disabled={readonly}
defaultValue={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
items={options.filter((option) => {
if (option.show_on.length)
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)

View File

@@ -9,6 +9,9 @@ type InputProps = {
validated?: boolean
className?: string
disabled?: boolean
type?: string
min?: number
max?: number
}
const Input: FC<InputProps> = ({
value,
@@ -18,7 +21,19 @@ const Input: FC<InputProps> = ({
validated,
className,
disabled,
type = 'text',
min,
max,
}) => {
const toLimit = (v: string) => {
if (min !== undefined && parseFloat(v) < min) {
onChange(`${min}`)
return
}
if (max !== undefined && parseFloat(v) > max)
onChange(`${max}`)
}
return (
<div className='relative'>
<input
@@ -34,9 +49,13 @@ const Input: FC<InputProps> = ({
`}
placeholder={placeholder || ''}
onChange={e => onChange(e.target.value)}
onBlur={e => toLimit(e.target.value)}
onFocus={onFocus}
value={value || ''}
disabled={disabled}
type={type}
min={min}
max={max}
/>
{
validated && (

View File

@@ -7,11 +7,11 @@ import useSWR from 'swr'
import useSWRInfinite from 'swr/infinite'
import { flatten } from 'lodash-es'
import Nav from '../nav'
import { Robot, RobotActive } from '../../base/icons/src/public/header-nav/studio'
import { fetchAppDetail, fetchAppList } from '@/service/apps'
import NewAppDialog from '@/app/(commonLayout)/apps/NewAppDialog'
import { Container } from '@/app/components/base/icons/src/vender/line/development'
import { Container as ContainerSolid } from '@/app/components/base/icons/src/vender/solid/development'
import type { AppListResponse } from '@/models/app'
import { useAppContext } from '@/context/app-context'
const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
if (!pageIndex || previousPageData.has_more)
@@ -21,6 +21,8 @@ const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
const AppNav = () => {
const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext()
const [showNewAppDialog, setShowNewAppDialog] = useState(false)
const { appId } = useParams()
const isAppDetailPage = usePathname().split('/').includes('app')
@@ -35,8 +37,8 @@ const AppNav = () => {
return (
<>
<Nav
icon={<Container className='w-4 h-4' />}
activeIcon={<ContainerSolid className='w-4 h-4' />}
icon={<Robot className='w-4 h-4' />}
activeIcon={<RobotActive className='w-4 h-4' />}
text={t('common.menus.apps')}
activeSegment={['apps', 'app']}
link='/apps'
@@ -44,7 +46,7 @@ const AppNav = () => {
navs={appItems.map(item => ({
id: item.id,
name: item.name,
link: `/app/${item.id}/overview`,
link: `/app/${item.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`,
icon: item.icon,
icon_background: item.icon_background,
}))}

View File

@@ -65,7 +65,7 @@ export default function AppSelector({ appItems, curApp }: IAppSelectorProps) {
appItems.map((app: AppDetailResponse) => (
<Menu.Item key={app.id}>
<div className={itemClassName} onClick={() =>
router.push(`/app/${app.id}/overview`)
router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
}>
<div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'>
<AppIcon size='tiny' />

View File

@@ -7,9 +7,8 @@ import useSWR from 'swr'
import useSWRInfinite from 'swr/infinite'
import { flatten } from 'lodash-es'
import Nav from '../nav'
import { Knowledge, KnowledgeActive } from '../../base/icons/src/public/header-nav/knowledge'
import { fetchDatasetDetail, fetchDatasets } from '@/service/datasets'
import { Database01 } from '@/app/components/base/icons/src/vender/line/development'
import { Database02 } from '@/app/components/base/icons/src/vender/solid/development'
import type { DataSetListResponse } from '@/models/datasets'
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
@@ -39,8 +38,8 @@ const DatasetNav = () => {
return (
<Nav
icon={<Database01 className='w-4 h-4' />}
activeIcon={<Database02 className='w-4 h-4' />}
icon={<Knowledge className='w-4 h-4' />}
activeIcon={<KnowledgeActive className='w-4 h-4' />}
text={t('common.menus.datasets')}
activeSegment='datasets'
link='/datasets'

View File

@@ -4,9 +4,7 @@ import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout'
import { Grid01 as Grid01Solid } from '@/app/components/base/icons/src/vender/solid/layout'
import { Explore, ExploreActive } from '../../base/icons/src/public/header-nav/explore'
type ExploreNavProps = {
className?: string
}
@@ -26,8 +24,8 @@ const ExploreNav = ({
)}>
{
actived
? <Grid01Solid className='mr-2 w-4 h-4' />
: <Grid01 className='mr-2 w-4 h-4' />
? <ExploreActive className='mr-2 w-4 h-4' />
: <Explore className='mr-2 w-4 h-4' />
}
{t('common.menus.explore')}
</Link>

View File

@@ -10,6 +10,7 @@ import AppNav from './app-nav'
import DatasetNav from './dataset-nav'
import EnvNav from './env-nav'
import ExploreNav from './explore-nav'
import ToolsNav from './tools-nav'
import GithubStar from './github-star'
import { WorkspaceProvider } from '@/context/workspace-context'
import { useAppContext } from '@/context/app-context'
@@ -71,6 +72,7 @@ const Header = () => {
<ExploreNav className={navClassName} />
<AppNav />
{isCurrentWorkspaceManager && <DatasetNav />}
<ToolsNav className={navClassName} />
</div>
)}
<div className='flex items-center flex-shrink-0'>
@@ -89,7 +91,7 @@ const Header = () => {
</div>
)}
<WorkspaceProvider>
<AccountDropdown isMobile={isMobile}/>
<AccountDropdown isMobile={isMobile} />
</WorkspaceProvider>
</div>
{(isMobile && isShowNavMenu) && (
@@ -97,6 +99,7 @@ const Header = () => {
<ExploreNav className={navClassName} />
<AppNav />
{isCurrentWorkspaceManager && <DatasetNav />}
<ToolsNav className={navClassName} />
</div>
)}
</div>

View File

@@ -0,0 +1,35 @@
'use client'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { Tools, ToolsActive } from '../../base/icons/src/public/header-nav/tools'
type ToolsNavProps = {
className?: string
}
const ToolsNav = ({
className,
}: ToolsNavProps) => {
const { t } = useTranslation()
const selectedSegment = useSelectedLayoutSegment()
const actived = selectedSegment === 'tools'
return (
<Link href="/tools" className={classNames(
className, 'group',
actived && 'bg-white shadow-md',
actived ? 'text-primary-600' : 'text-gray-500 hover:bg-gray-200',
)}>
{
actived
? <ToolsActive className='mr-2 w-4 h-4' />
: <Tools className='mr-2 w-4 h-4' />
}
{t('common.menus.tools')}
</Link>
)
}
export default ToolsNav