feat: introduce trigger functionality (#27644)

Signed-off-by: lyzno1 <yuanyouhuilyz@gmail.com>
Co-authored-by: Stream <Stream_2@qq.com>
Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: zhsama <torvalds@linux.do>
Co-authored-by: Harry <xh001x@hotmail.com>
Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com>
Co-authored-by: yessenia <yessenia.contact@gmail.com>
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: WTW0313 <twwu@dify.ai>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Yeuoly
2025-11-12 17:59:37 +08:00
committed by GitHub
parent ca7794305b
commit b76e17b25d
785 changed files with 41186 additions and 3725 deletions

View File

@@ -20,7 +20,7 @@ const Badge = ({
return (
<div
className={cn(
'relative inline-flex h-5 items-center rounded-[5px] border border-divider-deep px-[5px] leading-3 text-text-tertiary',
'relative inline-flex h-5 items-center whitespace-nowrap rounded-[5px] border border-divider-deep px-[5px] leading-3 text-text-tertiary',
uppercase ? 'system-2xs-medium-uppercase' : 'system-xs-medium',
className,
)}

View File

@@ -0,0 +1,203 @@
'use client'
import Badge from '@/app/components/base/badge'
import Checkbox from '@/app/components/base/checkbox'
import SearchInput from '@/app/components/base/search-input'
import SearchMenu from '@/assets/search-menu.svg'
import cn from '@/utils/classnames'
import Image from 'next/image'
import type { FC } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../button'
export type CheckboxListOption = {
label: string
value: string
disabled?: boolean
}
export type CheckboxListProps = {
title?: string
label?: string
description?: string
options: CheckboxListOption[]
value?: string[]
onChange?: (value: string[]) => void
disabled?: boolean
containerClassName?: string
showSelectAll?: boolean
showCount?: boolean
showSearch?: boolean
maxHeight?: string | number
}
const CheckboxList: FC<CheckboxListProps> = ({
title = '',
label,
description,
options,
value = [],
onChange,
disabled = false,
containerClassName,
showSelectAll = true,
showCount = true,
showSearch = true,
maxHeight,
}) => {
const { t } = useTranslation()
const [searchQuery, setSearchQuery] = useState('')
const filteredOptions = useMemo(() => {
if (!searchQuery?.trim())
return options
const query = searchQuery.toLowerCase()
return options.filter(option =>
option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query),
)
}, [options, searchQuery])
const selectedCount = value.length
const isAllSelected = useMemo(() => {
const selectableOptions = options.filter(option => !option.disabled)
return selectableOptions.length > 0 && selectableOptions.every(option => value.includes(option.value))
}, [options, value])
const isIndeterminate = useMemo(() => {
const selectableOptions = options.filter(option => !option.disabled)
const selectedCount = selectableOptions.filter(option => value.includes(option.value)).length
return selectedCount > 0 && selectedCount < selectableOptions.length
}, [options, value])
const handleSelectAll = useCallback(() => {
if (disabled)
return
if (isAllSelected) {
// Deselect all
onChange?.([])
}
else {
// Select all non-disabled options
const allValues = options
.filter(option => !option.disabled)
.map(option => option.value)
onChange?.(allValues)
}
}, [isAllSelected, options, onChange, disabled])
const handleToggleOption = useCallback((optionValue: string) => {
if (disabled)
return
const newValue = value.includes(optionValue)
? value.filter(v => v !== optionValue)
: [...value, optionValue]
onChange?.(newValue)
}, [value, onChange, disabled])
return (
<div className={cn('flex w-full flex-col gap-1', containerClassName)}>
{label && (
<div className='system-sm-medium text-text-secondary'>
{label}
</div>
)}
{description && (
<div className='body-xs-regular text-text-tertiary'>
{description}
</div>
)}
<div className='rounded-lg border border-components-panel-border bg-components-panel-bg'>
{(showSelectAll || title || showSearch) && (
<div className='relative flex items-center gap-2 border-b border-divider-subtle px-3 py-2'>
{!searchQuery && showSelectAll && (
<Checkbox
checked={isAllSelected}
indeterminate={isIndeterminate}
onCheck={handleSelectAll}
disabled={disabled}
/>
)}
{!searchQuery ? <div className='flex min-w-0 flex-1 items-center gap-1'>
{title && (
<span className='system-xs-semibold-uppercase truncate leading-5 text-text-secondary'>
{title}
</span>
)}
{showCount && selectedCount > 0 && (
<Badge uppercase>
{t('common.operation.selectCount', { count: selectedCount })}
</Badge>
)}
</div> : <div className='system-sm-medium-uppercase flex-1 leading-6 text-text-secondary'>{
filteredOptions.length > 0
? t('common.operation.searchCount', { count: filteredOptions.length, content: title })
: t('common.operation.noSearchCount', { content: title })}</div>}
{showSearch && (
<SearchInput
value={searchQuery}
onChange={setSearchQuery}
placeholder={t('common.placeholder.search')}
className='w-40'
/>
)}
</div>
)}
<div
className='p-1'
style={maxHeight ? { maxHeight, overflowY: 'auto' } : {}}
>
{!filteredOptions.length ? (
<div className='px-3 py-6 text-center text-sm text-text-tertiary'>
{searchQuery ? <div className='flex flex-col items-center justify-center gap-2'>
<Image alt='search menu' src={SearchMenu} width={32} />
<span className='system-sm-regular text-text-secondary'>{t('common.operation.noSearchResults', { content: title })}</span>
<Button variant='secondary-accent' size='small' onClick={() => setSearchQuery('')}>{t('common.operation.resetKeywords')}</Button>
</div> : t('common.noData')}
</div>
) : (
filteredOptions.map((option) => {
const selected = value.includes(option.value)
return (
<div
key={option.value}
className={cn(
'flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 transition-colors hover:bg-state-base-hover',
option.disabled && 'cursor-not-allowed opacity-50',
)}
onClick={() => {
if (!option.disabled && !disabled)
handleToggleOption(option.value)
}}
>
<Checkbox
checked={selected}
onCheck={() => {
if (!option.disabled && !disabled)
handleToggleOption(option.value)
}}
disabled={option.disabled || disabled}
/>
<div
className='system-sm-medium flex-1 truncate text-text-secondary'
title={option.label}
>
{option.label}
</div>
</div>
)
})
)}
</div>
</div>
</div>
)
}
export default CheckboxList

View File

@@ -30,7 +30,7 @@ const Checkbox = ({
<div
id={id}
className={cn(
'flex h-4 w-4 cursor-pointer items-center justify-center rounded-[4px] shadow-xs shadow-shadow-shadow-3',
'flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center rounded-[4px] shadow-xs shadow-shadow-shadow-3',
checkClassName,
disabled && disabledClassName,
className,

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import Button from '../button'
import Tooltip from '../tooltip'
export type IConfirm = {
className?: string
@@ -37,7 +38,9 @@ function Confirm({
}: IConfirm) {
const { t } = useTranslation()
const dialogRef = useRef<HTMLDivElement>(null)
const titleRef = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(isShow)
const [isTitleTruncated, setIsTitleTruncated] = useState(false)
const confirmTxt = confirmText || `${t('common.operation.confirm')}`
const cancelTxt = cancelText || `${t('common.operation.cancel')}`
@@ -80,6 +83,13 @@ function Confirm({
}
}, [isShow])
useEffect(() => {
if (titleRef.current) {
const isOverflowing = titleRef.current.scrollWidth > titleRef.current.clientWidth
setIsTitleTruncated(isOverflowing)
}
}, [title, isVisible])
if (!isVisible)
return null
@@ -92,8 +102,18 @@ function Confirm({
<div ref={dialogRef} className={'relative w-full max-w-[480px] overflow-hidden'}>
<div className='shadows-shadow-lg flex max-w-full flex-col items-start rounded-2xl border-[0.5px] border-solid border-components-panel-border bg-components-panel-bg'>
<div className='flex flex-col items-start gap-2 self-stretch pb-4 pl-6 pr-6 pt-6'>
<div className='title-2xl-semi-bold text-text-primary'>{title}</div>
<div className='system-md-regular w-full text-text-tertiary'>{content}</div>
<Tooltip
popupContent={title}
disabled={!isTitleTruncated}
portalContentClassName='!z-[10000001]'
asChild={false}
triggerClassName='w-full'
>
<div ref={titleRef} className='title-2xl-semi-bold w-full truncate text-text-primary'>
{title}
</div>
</Tooltip>
<div className='system-md-regular w-full whitespace-pre-wrap break-words text-text-tertiary'>{content}</div>
</div>
<div className='flex items-start justify-end gap-2 self-stretch p-6'>
{showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}

View File

@@ -10,26 +10,25 @@ const Footer: FC<TimePickerFooterProps> = ({
const { t } = useTranslation()
return (
<div className='flex items-center justify-end border-t-[0.5px] border-divider-regular p-2'>
<div className='flex items-center gap-x-1'>
{/* Now */}
<button
type='button'
className='system-xs-medium flex items-center justify-center px-1.5 py-1 text-components-button-secondary-accent-text'
onClick={handleSelectCurrentTime}
>
<span className='px-[3px]'>{t('time.operation.now')}</span>
</button>
{/* Confirm Button */}
<Button
variant='primary'
size='small'
className='w-16 px-1.5 py-1'
onClick={handleConfirm.bind(null)}
>
{t('time.operation.ok')}
</Button>
</div>
<div className='flex items-center justify-between border-t-[0.5px] border-divider-regular p-2'>
{/* Now Button */}
<Button
variant='secondary-accent'
size='small'
className='mr-1 flex-1'
onClick={handleSelectCurrentTime}
>
{t('time.operation.now')}
</Button>
{/* Confirm Button */}
<Button
variant='primary'
size='small'
className='ml-1 flex-1'
onClick={handleConfirm.bind(null)}
>
{t('time.operation.ok')}
</Button>
</div>
)
}

View File

@@ -29,6 +29,15 @@ jest.mock('@/app/components/base/portal-to-follow-elem', () => ({
jest.mock('./options', () => () => <div data-testid="time-options" />)
jest.mock('./header', () => () => <div data-testid="time-header" />)
jest.mock('@/app/components/base/timezone-label', () => {
return function MockTimezoneLabel({ timezone, inline, className }: { timezone: string, inline?: boolean, className?: string }) {
return (
<span data-testid="timezone-label" data-timezone={timezone} data-inline={inline} className={className}>
UTC+8
</span>
)
}
})
describe('TimePicker', () => {
const baseProps: Pick<TimePickerProps, 'onChange' | 'onClear' | 'value'> = {
@@ -94,4 +103,86 @@ describe('TimePicker', () => {
expect(isDayjsObject(emitted)).toBe(true)
expect(emitted?.utcOffset()).toBe(dayjs().tz('America/New_York').utcOffset())
})
describe('Timezone Label Integration', () => {
test('should not display timezone label by default', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Asia/Shanghai"
/>,
)
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
})
test('should not display timezone label when showTimezone is false', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Asia/Shanghai"
showTimezone={false}
/>,
)
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
})
test('should display timezone label when showTimezone is true', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Asia/Shanghai"
showTimezone={true}
/>,
)
const timezoneLabel = screen.getByTestId('timezone-label')
expect(timezoneLabel).toBeInTheDocument()
expect(timezoneLabel).toHaveAttribute('data-timezone', 'Asia/Shanghai')
})
test('should pass inline prop to timezone label', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="America/New_York"
showTimezone={true}
/>,
)
const timezoneLabel = screen.getByTestId('timezone-label')
expect(timezoneLabel).toHaveAttribute('data-inline', 'true')
})
test('should not display timezone label when showTimezone is true but timezone is not provided', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
showTimezone={true}
/>,
)
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
})
test('should apply shrink-0 and text-xs classes to timezone label', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Europe/London"
showTimezone={true}
/>,
)
const timezoneLabel = screen.getByTestId('timezone-label')
expect(timezoneLabel).toHaveClass('shrink-0', 'text-xs')
})
})
})

View File

@@ -19,6 +19,7 @@ import Header from './header'
import { useTranslation } from 'react-i18next'
import { RiCloseCircleFill, RiTimeLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import TimezoneLabel from '@/app/components/base/timezone-label'
const to24Hour = (hour12: string, period: Period) => {
const normalized = Number.parseInt(hour12, 10) % 12
@@ -35,6 +36,10 @@ const TimePicker = ({
title,
minuteFilter,
popupClassName,
notClearable = false,
triggerFullWidth = false,
showTimezone = false,
placement = 'bottom-start',
}: TimePickerProps) => {
const { t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
@@ -189,7 +194,7 @@ const TimePicker = ({
const inputElem = (
<input
className='system-xs-regular flex-1 cursor-pointer appearance-none truncate bg-transparent p-1
className='system-xs-regular flex-1 cursor-pointer select-none appearance-none truncate bg-transparent p-1
text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder'
readOnly
value={isOpen ? '' : displayValue}
@@ -200,28 +205,34 @@ const TimePicker = ({
<PortalToFollowElem
open={isOpen}
onOpenChange={setIsOpen}
placement='bottom-end'
placement={placement}
>
<PortalToFollowElemTrigger>
<PortalToFollowElemTrigger className={triggerFullWidth ? '!block w-full' : undefined}>
{renderTrigger ? (renderTrigger({
inputElem,
onClick: handleClickTrigger,
isOpen,
})) : (
<div
className='group flex w-[252px] cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt'
className={cn(
'group flex cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt',
triggerFullWidth ? 'w-full min-w-0' : 'w-[252px]',
)}
onClick={handleClickTrigger}
>
{inputElem}
{showTimezone && timezone && (
<TimezoneLabel timezone={timezone} inline className='shrink-0 select-none text-xs' />
)}
<RiTimeLine className={cn(
'h-4 w-4 shrink-0 text-text-quaternary',
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',
(displayValue || (isOpen && selectedTime)) && 'group-hover:hidden',
(displayValue || (isOpen && selectedTime)) && !notClearable && 'group-hover:hidden',
)} />
<RiCloseCircleFill
className={cn(
'hidden h-4 w-4 shrink-0 text-text-quaternary',
(displayValue || (isOpen && selectedTime)) && 'hover:text-text-secondary group-hover:inline-block',
(displayValue || (isOpen && selectedTime)) && !notClearable && 'hover:text-text-secondary group-hover:inline-block',
)}
role='button'
aria-label={t('common.operation.clear')}

View File

@@ -1,4 +1,5 @@
import type { Dayjs } from 'dayjs'
import type { Placement } from '@floating-ui/react'
export enum ViewType {
date = 'date',
@@ -65,6 +66,10 @@ export type TimePickerProps = {
title?: string
minuteFilter?: (minutes: string[]) => string[]
popupClassName?: string
notClearable?: boolean
triggerFullWidth?: boolean
showTimezone?: boolean
placement?: Placement
}
export type TimePickerFooterProps = {

View File

@@ -1,5 +1,6 @@
import dayjs from './dayjs'
import {
convertTimezoneToOffsetStr,
getDateWithTimezone,
isDayjsObject,
toDayjs,
@@ -65,3 +66,50 @@ describe('dayjs utilities', () => {
expect(result?.minute()).toBe(0)
})
})
describe('convertTimezoneToOffsetStr', () => {
test('should return default UTC+0 for undefined timezone', () => {
expect(convertTimezoneToOffsetStr(undefined)).toBe('UTC+0')
})
test('should return default UTC+0 for invalid timezone', () => {
expect(convertTimezoneToOffsetStr('Invalid/Timezone')).toBe('UTC+0')
})
test('should handle whole hour positive offsets without leading zeros', () => {
expect(convertTimezoneToOffsetStr('Asia/Shanghai')).toBe('UTC+8')
expect(convertTimezoneToOffsetStr('Pacific/Auckland')).toBe('UTC+12')
expect(convertTimezoneToOffsetStr('Pacific/Apia')).toBe('UTC+13')
})
test('should handle whole hour negative offsets without leading zeros', () => {
expect(convertTimezoneToOffsetStr('Pacific/Niue')).toBe('UTC-11')
expect(convertTimezoneToOffsetStr('Pacific/Honolulu')).toBe('UTC-10')
expect(convertTimezoneToOffsetStr('America/New_York')).toBe('UTC-5')
})
test('should handle zero offset', () => {
expect(convertTimezoneToOffsetStr('Europe/London')).toBe('UTC+0')
expect(convertTimezoneToOffsetStr('UTC')).toBe('UTC+0')
})
test('should handle half-hour offsets (30 minutes)', () => {
// India Standard Time: UTC+5:30
expect(convertTimezoneToOffsetStr('Asia/Kolkata')).toBe('UTC+5:30')
// Australian Central Time: UTC+9:30
expect(convertTimezoneToOffsetStr('Australia/Adelaide')).toBe('UTC+9:30')
expect(convertTimezoneToOffsetStr('Australia/Darwin')).toBe('UTC+9:30')
})
test('should handle 45-minute offsets', () => {
// Chatham Time: UTC+12:45
expect(convertTimezoneToOffsetStr('Pacific/Chatham')).toBe('UTC+12:45')
})
test('should preserve leading zeros in minute part for non-zero minutes', () => {
// Ensure +05:30 is displayed as "UTC+5:30", not "UTC+5:3"
const result = convertTimezoneToOffsetStr('Asia/Kolkata')
expect(result).toMatch(/UTC[+-]\d+:30/)
expect(result).not.toMatch(/UTC[+-]\d+:3[^0]/)
})
})

View File

@@ -107,7 +107,18 @@ export const convertTimezoneToOffsetStr = (timezone?: string) => {
const tzItem = tz.find(item => item.value === timezone)
if (!tzItem)
return DEFAULT_OFFSET_STR
return `UTC${tzItem.name.charAt(0)}${tzItem.name.charAt(2)}`
// Extract offset from name format like "-11:00 Niue Time" or "+05:30 India Time"
// Name format is always "{offset}:{minutes} {timezone name}"
const offsetMatch = tzItem.name.match(/^([+-]?\d{1,2}):(\d{2})/)
if (!offsetMatch)
return DEFAULT_OFFSET_STR
// Parse hours and minutes separately
const hours = Number.parseInt(offsetMatch[1], 10)
const minutes = Number.parseInt(offsetMatch[2], 10)
const sign = hours >= 0 ? '+' : ''
// If minutes are non-zero, include them in the output (e.g., "UTC+5:30")
// Otherwise, only show hours (e.g., "UTC+8")
return minutes !== 0 ? `UTC${sign}${hours}:${offsetMatch[2]}` : `UTC${sign}${hours}`
}
export const isDayjsObject = (value: unknown): value is Dayjs => dayjs.isDayjs(value)

View File

@@ -29,7 +29,7 @@ export type DividerProps = {
const Divider: FC<DividerProps> = ({ type, bgStyle, className = '', style }) => {
return (
<div className={classNames(dividerVariants({ type, bgStyle }), className)} style={style}></div>
<div className={classNames(dividerVariants({ type, bgStyle }), 'shrink-0', className)} style={style}></div>
)
}

View File

@@ -10,6 +10,7 @@ export type IDrawerProps = {
description?: string
dialogClassName?: string
dialogBackdropClassName?: string
containerClassName?: string
panelClassName?: string
children: React.ReactNode
footer?: React.ReactNode
@@ -22,6 +23,7 @@ export type IDrawerProps = {
onCancel?: () => void
onOk?: () => void
unmount?: boolean
noOverlay?: boolean
}
export default function Drawer({
@@ -29,6 +31,7 @@ export default function Drawer({
description = '',
dialogClassName = '',
dialogBackdropClassName = '',
containerClassName = '',
panelClassName = '',
children,
footer,
@@ -41,6 +44,7 @@ export default function Drawer({
onCancel,
onOk,
unmount = false,
noOverlay = false,
}: IDrawerProps) {
const { t } = useTranslation()
return (
@@ -53,15 +57,15 @@ export default function Drawer({
}}
className={cn('fixed inset-0 z-[30] overflow-y-auto', dialogClassName)}
>
<div className={cn('flex h-screen w-screen justify-end', positionCenter && '!justify-center')}>
<div className={cn('flex h-screen w-screen justify-end', positionCenter && '!justify-center', containerClassName)}>
{/* mask */}
<DialogBackdrop
{!noOverlay && <DialogBackdrop
className={cn('fixed inset-0 z-[40]', mask && 'bg-black/30', dialogBackdropClassName)}
onClick={() => {
if (!clickOutsideNotOpen)
onClose()
}}
/>
/>}
<div className={cn('relative z-[50] flex w-full max-w-sm flex-col justify-between overflow-hidden bg-components-panel-bg p-6 text-left align-middle shadow-xl', panelClassName)}>
<>
<div className='flex justify-between'>

View File

@@ -0,0 +1,30 @@
import cn from '@/utils/classnames'
import { RiLock2Fill } from '@remixicon/react'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
type Props = {
className?: string
frontTextKey?: string
backTextKey?: string
}
export const EncryptedBottom = (props: Props) => {
const { t } = useTranslation()
const { frontTextKey, backTextKey, className } = props
return (
<div className={cn('system-xs-regular flex items-center justify-center rounded-b-2xl border-t-[0.5px] border-divider-subtle bg-background-soft px-2 py-3 text-text-tertiary', className)}>
<RiLock2Fill className='mx-1 h-3 w-3 text-text-quaternary' />
{t(frontTextKey || 'common.provider.encrypted.front')}
<Link
className='mx-1 text-text-accent'
target='_blank' rel='noopener noreferrer'
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
>
PKCS1_OAEP
</Link>
{t(backTextKey || 'common.provider.encrypted.back')}
</div>
)
}

View File

@@ -0,0 +1,273 @@
'use client'
import type { ErrorInfo, ReactNode } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { RiAlertLine, RiBugLine } from '@remixicon/react'
import Button from '@/app/components/base/button'
import cn from '@/utils/classnames'
type ErrorBoundaryState = {
hasError: boolean
error: Error | null
errorInfo: ErrorInfo | null
errorCount: number
}
type ErrorBoundaryProps = {
children: ReactNode
fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode)
onError?: (error: Error, errorInfo: ErrorInfo) => void
onReset?: () => void
showDetails?: boolean
className?: string
resetKeys?: Array<string | number>
resetOnPropsChange?: boolean
isolate?: boolean
enableRecovery?: boolean
customTitle?: string
customMessage?: string
}
// Internal class component for error catching
class ErrorBoundaryInner extends React.Component<
ErrorBoundaryProps & {
resetErrorBoundary: () => void
onResetKeysChange: (prevResetKeys?: Array<string | number>) => void
},
ErrorBoundaryState
> {
constructor(props: any) {
super(props)
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorCount: 0,
}
}
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return {
hasError: true,
error,
}
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
if (process.env.NODE_ENV === 'development') {
console.error('ErrorBoundary caught an error:', error)
console.error('Error Info:', errorInfo)
}
this.setState(prevState => ({
errorInfo,
errorCount: prevState.errorCount + 1,
}))
if (this.props.onError)
this.props.onError(error, errorInfo)
}
componentDidUpdate(prevProps: any) {
const { resetKeys, resetOnPropsChange } = this.props
const { hasError } = this.state
if (hasError && prevProps.resetKeys !== resetKeys) {
if (resetKeys?.some((key, idx) => key !== prevProps.resetKeys?.[idx]))
this.props.resetErrorBoundary()
}
if (hasError && resetOnPropsChange && prevProps.children !== this.props.children)
this.props.resetErrorBoundary()
if (prevProps.resetKeys !== resetKeys)
this.props.onResetKeysChange(prevProps.resetKeys)
}
render() {
const { hasError, error, errorInfo, errorCount } = this.state
const {
fallback,
children,
showDetails = false,
className,
isolate = true,
enableRecovery = true,
customTitle,
customMessage,
resetErrorBoundary,
} = this.props
if (hasError && error) {
if (fallback) {
if (typeof fallback === 'function')
return fallback(error, resetErrorBoundary)
return fallback
}
return (
<div
className={cn(
'border-state-critical-border bg-state-critical-hover-alt flex flex-col items-center justify-center rounded-lg border p-8',
isolate && 'min-h-[200px]',
className,
)}
>
<div className='mb-4 flex items-center gap-2'>
<RiAlertLine className='text-state-critical-solid h-8 w-8' />
<h2 className='text-xl font-semibold text-text-primary'>
{customTitle || 'Something went wrong'}
</h2>
</div>
<p className='mb-6 text-center text-text-secondary'>
{customMessage || 'An unexpected error occurred while rendering this component.'}
</p>
{showDetails && errorInfo && (
<details className='mb-6 w-full max-w-2xl'>
<summary className='mb-2 cursor-pointer text-sm font-medium text-text-tertiary hover:text-text-secondary'>
<span className='inline-flex items-center gap-1'>
<RiBugLine className='h-4 w-4' />
Error Details (Development Only)
</span>
</summary>
<div className='rounded-lg bg-gray-100 p-4'>
<div className='mb-2'>
<span className='font-mono text-xs font-semibold text-gray-600'>Error:</span>
<pre className='mt-1 overflow-auto whitespace-pre-wrap font-mono text-xs text-gray-800'>
{error.toString()}
</pre>
</div>
{errorInfo && (
<div>
<span className='font-mono text-xs font-semibold text-gray-600'>Component Stack:</span>
<pre className='mt-1 max-h-40 overflow-auto whitespace-pre-wrap font-mono text-xs text-gray-700'>
{errorInfo.componentStack}
</pre>
</div>
)}
{errorCount > 1 && (
<div className='mt-2 text-xs text-gray-600'>
This error has occurred {errorCount} times
</div>
)}
</div>
</details>
)}
{enableRecovery && (
<div className='flex gap-3'>
<Button
variant='primary'
size='small'
onClick={resetErrorBoundary}
>
Try Again
</Button>
<Button
variant='secondary'
size='small'
onClick={() => window.location.reload()}
>
Reload Page
</Button>
</div>
)}
</div>
)
}
return children
}
}
// Main functional component wrapper
const ErrorBoundary: React.FC<ErrorBoundaryProps> = (props) => {
const [errorBoundaryKey, setErrorBoundaryKey] = useState(0)
const resetKeysRef = useRef(props.resetKeys)
const prevResetKeysRef = useRef<Array<string | number> | undefined>(undefined)
const resetErrorBoundary = useCallback(() => {
setErrorBoundaryKey(prev => prev + 1)
props.onReset?.()
}, [props])
const onResetKeysChange = useCallback((prevResetKeys?: Array<string | number>) => {
prevResetKeysRef.current = prevResetKeys
}, [])
useEffect(() => {
if (prevResetKeysRef.current !== props.resetKeys)
resetKeysRef.current = props.resetKeys
}, [props.resetKeys])
return (
<ErrorBoundaryInner
{...props}
key={errorBoundaryKey}
resetErrorBoundary={resetErrorBoundary}
onResetKeysChange={onResetKeysChange}
/>
)
}
// Hook for imperative error handling
export function useErrorHandler() {
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
if (error)
throw error
}, [error])
return setError
}
// Hook for catching async errors
export function useAsyncError() {
const [, setError] = useState()
return useCallback(
(error: Error) => {
setError(() => {
throw error
})
},
[setError],
)
}
// HOC for wrapping components with error boundary
export function withErrorBoundary<P extends object>(
Component: React.ComponentType<P>,
errorBoundaryProps?: Omit<ErrorBoundaryProps, 'children'>,
): React.ComponentType<P> {
const WrappedComponent = (props: P) => (
<ErrorBoundary {...errorBoundaryProps}>
<Component {...props} />
</ErrorBoundary>
)
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})`
return WrappedComponent
}
// Simple error fallback component
export const ErrorFallback: React.FC<{
error: Error
resetErrorBoundary: () => void
}> = ({ error, resetErrorBoundary }) => {
return (
<div className='flex min-h-[200px] flex-col items-center justify-center rounded-lg border border-red-200 bg-red-50 p-8'>
<h2 className='mb-2 text-lg font-semibold text-red-800'>Oops! Something went wrong</h2>
<p className='mb-4 text-center text-red-600'>{error.message}</p>
<Button onClick={resetErrorBoundary} size='small'>
Try again
</Button>
</div>
)
}
export default ErrorBoundary

View File

@@ -26,6 +26,7 @@ import { CustomConfigurationStatusEnum } from '@/app/components/header/account-s
import cn from '@/utils/classnames'
import { noop } from 'lodash-es'
import { useDocLink } from '@/context/i18n'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
const systemTypes = ['openai_moderation', 'keywords', 'api']
@@ -55,7 +56,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
const { setShowAccountSettingModal } = useModalContext()
const handleOpenSettingsModal = () => {
setShowAccountSettingModal({
payload: 'provider',
payload: ACCOUNT_SETTING_TAB.PROVIDER,
onCancelCallback: () => {
mutate()
},

View File

@@ -1,20 +1,71 @@
import CheckboxList from '@/app/components/base/checkbox-list'
import type { FieldState, FormSchema, TypeWithI18N } from '@/app/components/base/form/types'
import { FormItemValidateStatusEnum, FormTypeEnum } from '@/app/components/base/form/types'
import Input from '@/app/components/base/input'
import Radio from '@/app/components/base/radio'
import RadioE from '@/app/components/base/radio/ui'
import PureSelect from '@/app/components/base/select/pure'
import Tooltip from '@/app/components/base/tooltip'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import { useTriggerPluginDynamicOptions } from '@/service/use-triggers'
import cn from '@/utils/classnames'
import { RiExternalLinkLine } from '@remixicon/react'
import type { AnyFieldApi } from '@tanstack/react-form'
import { useStore } from '@tanstack/react-form'
import {
isValidElement,
memo,
useCallback,
useMemo,
} from 'react'
import { RiExternalLinkLine } from '@remixicon/react'
import type { AnyFieldApi } from '@tanstack/react-form'
import { useStore } from '@tanstack/react-form'
import cn from '@/utils/classnames'
import Input from '@/app/components/base/input'
import PureSelect from '@/app/components/base/select/pure'
import type { FormSchema } from '@/app/components/base/form/types'
import { FormTypeEnum } from '@/app/components/base/form/types'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import Radio from '@/app/components/base/radio'
import RadioE from '@/app/components/base/radio/ui'
import { useTranslation } from 'react-i18next'
const getExtraProps = (type: FormTypeEnum) => {
switch (type) {
case FormTypeEnum.secretInput:
return { type: 'password', autoComplete: 'new-password' }
case FormTypeEnum.textNumber:
return { type: 'number' }
default:
return { type: 'text' }
}
}
const getTranslatedContent = ({ content, render }: {
content: React.ReactNode | string | null | undefined | TypeWithI18N<string> | Record<string, string>
render: (content: TypeWithI18N<string> | Record<string, string>) => string
}): string => {
if (isValidElement(content) || typeof content === 'string')
return content as string
if (typeof content === 'object' && content !== null)
return render(content as TypeWithI18N<string>)
return ''
}
const VALIDATE_STATUS_STYLE_MAP: Record<FormItemValidateStatusEnum, { componentClassName: string, textClassName: string, infoFieldName: string }> = {
[FormItemValidateStatusEnum.Error]: {
componentClassName: 'border-components-input-border-destructive focus:border-components-input-border-destructive',
textClassName: 'text-text-destructive',
infoFieldName: 'errors',
},
[FormItemValidateStatusEnum.Warning]: {
componentClassName: 'border-components-input-border-warning focus:border-components-input-border-warning',
textClassName: 'text-text-warning',
infoFieldName: 'warnings',
},
[FormItemValidateStatusEnum.Success]: {
componentClassName: '',
textClassName: '',
infoFieldName: '',
},
[FormItemValidateStatusEnum.Validating]: {
componentClassName: '',
textClassName: '',
infoFieldName: '',
},
}
export type BaseFieldProps = {
fieldClassName?: string
@@ -25,7 +76,9 @@ export type BaseFieldProps = {
field: AnyFieldApi
disabled?: boolean
onChange?: (field: string, value: any) => void
fieldState?: FieldState
}
const BaseField = ({
fieldClassName,
labelClassName,
@@ -35,204 +88,259 @@ const BaseField = ({
field,
disabled: propsDisabled,
onChange,
fieldState,
}: BaseFieldProps) => {
const renderI18nObject = useRenderI18nObject()
const { t } = useTranslation()
const {
name,
label,
required,
placeholder,
options,
labelClassName: formLabelClassName,
disabled: formSchemaDisabled,
type: formItemType,
dynamicSelectParams,
multiple = false,
tooltip,
showCopy,
description,
url,
help,
} = formSchema
const disabled = propsDisabled || formSchemaDisabled
const memorizedLabel = useMemo(() => {
if (isValidElement(label))
return label
const [translatedLabel, translatedPlaceholder, translatedTooltip, translatedDescription, translatedHelp] = useMemo(() => {
const results = [
label,
placeholder,
tooltip,
description,
help,
].map(v => getTranslatedContent({ content: v, render: renderI18nObject }))
if (!results[1]) results[1] = t('common.placeholder.input')
return results
}, [label, placeholder, tooltip, description, help, renderI18nObject])
if (typeof label === 'string')
return label
const watchedVariables = useMemo(() => {
const variables = new Set<string>()
if (typeof label === 'object' && label !== null)
return renderI18nObject(label as Record<string, string>)
}, [label, renderI18nObject])
const memorizedPlaceholder = useMemo(() => {
if (typeof placeholder === 'string')
return placeholder
for (const option of options || []) {
for (const condition of option.show_on || [])
variables.add(condition.variable)
}
if (typeof placeholder === 'object' && placeholder !== null)
return renderI18nObject(placeholder as Record<string, string>)
}, [placeholder, renderI18nObject])
const optionValues = useStore(field.form.store, (s) => {
return Array.from(variables)
}, [options])
const watchedValues = useStore(field.form.store, (s) => {
const result: Record<string, any> = {}
options?.forEach((option) => {
if (option.show_on?.length) {
option.show_on.forEach((condition) => {
result[condition.variable] = s.values[condition.variable]
})
}
})
for (const variable of watchedVariables)
result[variable] = s.values[variable]
return result
})
const memorizedOptions = useMemo(() => {
return options?.filter((option) => {
if (!option.show_on || option.show_on.length === 0)
if (!option.show_on?.length)
return true
return option.show_on.every((condition) => {
const conditionValue = optionValues[condition.variable]
return conditionValue === condition.value
return watchedValues[condition.variable] === condition.value
})
}).map((option) => {
return {
label: typeof option.label === 'string' ? option.label : renderI18nObject(option.label),
label: getTranslatedContent({ content: option.label, render: renderI18nObject }),
value: option.value,
}
}) || []
}, [options, renderI18nObject, optionValues])
}, [options, renderI18nObject, watchedValues])
const value = useStore(field.form.store, s => s.values[field.name])
const { data: dynamicOptionsData, isLoading: isDynamicOptionsLoading, error: dynamicOptionsError } = useTriggerPluginDynamicOptions(
dynamicSelectParams || {
plugin_id: '',
provider: '',
action: '',
parameter: '',
credential_id: '',
},
formItemType === FormTypeEnum.dynamicSelect,
)
const dynamicOptions = useMemo(() => {
if (!dynamicOptionsData?.options)
return []
return dynamicOptionsData.options.map(option => ({
label: getTranslatedContent({ content: option.label, render: renderI18nObject }),
value: option.value,
}))
}, [dynamicOptionsData, renderI18nObject])
const handleChange = useCallback((value: any) => {
field.handleChange(value)
onChange?.(field.name, value)
}, [field, onChange])
return (
<div className={cn(fieldClassName)}>
<div className={cn(labelClassName, formLabelClassName)}>
{memorizedLabel}
{
required && !isValidElement(label) && (
<span className='ml-1 text-text-destructive-secondary'>*</span>
)
}
</div>
<div className={cn(inputContainerClassName)}>
{
formSchema.type === FormTypeEnum.textInput && (
<Input
id={field.name}
name={field.name}
className={cn(inputClassName)}
value={value || ''}
onChange={(e) => {
handleChange(e.target.value)
}}
onBlur={field.handleBlur}
disabled={disabled}
placeholder={memorizedPlaceholder}
<>
<div className={cn(fieldClassName)}>
<div className={cn(labelClassName, formLabelClassName)}>
{translatedLabel}
{
required && !isValidElement(label) && (
<span className='ml-1 text-text-destructive-secondary'>*</span>
)
}
{tooltip && (
<Tooltip
popupContent={<div className='w-[200px]'>{translatedTooltip}</div>}
triggerClassName='ml-0.5 w-4 h-4'
/>
)
}
{
formSchema.type === FormTypeEnum.secretInput && (
<Input
id={field.name}
name={field.name}
type='password'
className={cn(inputClassName)}
value={value || ''}
onChange={e => handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
placeholder={memorizedPlaceholder}
autoComplete={'new-password'}
/>
)
}
{
formSchema.type === FormTypeEnum.textNumber && (
<Input
id={field.name}
name={field.name}
type='number'
className={cn(inputClassName)}
value={value || ''}
onChange={e => handleChange(e.target.value)}
onBlur={field.handleBlur}
disabled={disabled}
placeholder={memorizedPlaceholder}
/>
)
}
{
formSchema.type === FormTypeEnum.select && (
<PureSelect
value={value}
onChange={v => handleChange(v)}
disabled={disabled}
placeholder={memorizedPlaceholder}
options={memorizedOptions}
triggerPopupSameWidth
popupProps={{
className: 'max-h-[320px] overflow-y-auto',
}}
/>
)
}
{
formSchema.type === FormTypeEnum.radio && (
)}
</div>
<div className={cn(inputContainerClassName)}>
{
[FormTypeEnum.textInput, FormTypeEnum.secretInput, FormTypeEnum.textNumber].includes(formItemType) && (
<Input
id={field.name}
name={field.name}
className={cn(inputClassName, VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus as FormItemValidateStatusEnum]?.componentClassName)}
value={value || ''}
onChange={(e) => {
handleChange(e.target.value)
}}
onBlur={field.handleBlur}
disabled={disabled}
placeholder={translatedPlaceholder}
{...getExtraProps(formItemType)}
showCopyIcon={showCopy}
/>
)
}
{
formItemType === FormTypeEnum.select && !multiple && (
<PureSelect
value={value}
onChange={v => handleChange(v)}
disabled={disabled}
placeholder={translatedPlaceholder}
options={memorizedOptions}
triggerPopupSameWidth
popupProps={{
className: 'max-h-[320px] overflow-y-auto',
}}
/>
)
}
{
formItemType === FormTypeEnum.checkbox /* && multiple */ && (
<CheckboxList
title={name}
value={value}
onChange={v => field.handleChange(v)}
options={memorizedOptions}
maxHeight='200px'
/>
)
}
{
formItemType === FormTypeEnum.dynamicSelect && (
<PureSelect
options={dynamicOptions}
value={value}
onChange={field.handleChange}
disabled={disabled || isDynamicOptionsLoading}
placeholder={
isDynamicOptionsLoading
? t('common.dynamicSelect.loading')
: translatedPlaceholder
}
{...(dynamicOptionsError ? { popupProps: { title: t('common.dynamicSelect.error'), titleClassName: 'text-text-destructive-secondary' } }
: (!dynamicOptions.length ? { popupProps: { title: t('common.dynamicSelect.noData') } } : {}))}
triggerPopupSameWidth
multiple={multiple}
/>
)
}
{
formItemType === FormTypeEnum.radio && (
<div className={cn(
memorizedOptions.length < 3 ? 'flex items-center space-x-2' : 'space-y-2',
)}>
{
memorizedOptions.map(option => (
<div
key={option.value}
className={cn(
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center gap-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
disabled && 'cursor-not-allowed opacity-50',
inputClassName,
)}
onClick={() => !disabled && handleChange(option.value)}
>
{
formSchema.showRadioUI && (
<RadioE
className='mr-2'
isChecked={value === option.value}
/>
)
}
{option.label}
</div>
))
}
</div>
)
}
{
formItemType === FormTypeEnum.boolean && (
<Radio.Group
className='flex w-fit items-center'
value={value}
onChange={v => field.handleChange(v)}
>
<Radio value={true} className='!mr-1'>True</Radio>
<Radio value={false}>False</Radio>
</Radio.Group>
)
}
{fieldState?.validateStatus && [FormItemValidateStatusEnum.Error, FormItemValidateStatusEnum.Warning].includes(fieldState?.validateStatus) && (
<div className={cn(
memorizedOptions.length < 3 ? 'flex items-center space-x-2' : 'space-y-2',
'system-xs-regular mt-1 px-0 py-[2px]',
VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus].textClassName,
)}>
{
memorizedOptions.map(option => (
<div
key={option.value}
className={cn(
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
disabled && 'cursor-not-allowed opacity-50',
inputClassName,
)}
onClick={() => !disabled && handleChange(option.value)}
>
{
formSchema.showRadioUI && (
<RadioE
className='mr-2'
isChecked={value === option.value}
/>
)
}
{option.label}
</div>
))
}
{fieldState?.[VALIDATE_STATUS_STYLE_MAP[fieldState?.validateStatus].infoFieldName as keyof FieldState]}
</div>
)
}
{
formSchema.type === FormTypeEnum.boolean && (
<Radio.Group
className='flex w-fit items-center'
value={value}
onChange={v => field.handleChange(v)}
>
<Radio value={true} className='!mr-1'>True</Radio>
<Radio value={false}>False</Radio>
</Radio.Group>
)
}
{
formSchema.url && (
<a
className='system-xs-regular mt-4 flex items-center text-text-accent'
href={formSchema?.url}
target='_blank'
>
<span className='break-all'>
{renderI18nObject(formSchema?.help as any)}
</span>
{
<RiExternalLinkLine className='ml-1 h-3 w-3' />
}
</a>
)
}
)}
</div>
</div>
</div>
{description && (
<div className='system-xs-regular mt-4 text-text-tertiary'>
{translatedDescription}
</div>
)}
{
url && (
<a
className='system-xs-regular mt-4 flex items-center text-text-accent'
href={url}
target='_blank'
>
<span className='break-all'>
{translatedHelp}
</span>
<RiExternalLinkLine className='ml-1 h-3 w-3 shrink-0' />
</a>
)
}
</>
)
}

View File

@@ -3,6 +3,7 @@ import {
useCallback,
useImperativeHandle,
useMemo,
useState,
} from 'react'
import type {
AnyFieldApi,
@@ -12,9 +13,12 @@ import {
useForm,
useStore,
} from '@tanstack/react-form'
import type {
FormRef,
FormSchema,
import {
type FieldState,
FormItemValidateStatusEnum,
type FormRef,
type FormSchema,
type SetFieldsParam,
} from '@/app/components/base/form/types'
import {
BaseField,
@@ -36,6 +40,8 @@ export type BaseFormProps = {
disabled?: boolean
formFromProps?: AnyFormApi
onChange?: (field: string, value: any) => void
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void
preventDefaultSubmit?: boolean
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
const BaseForm = ({
@@ -50,6 +56,8 @@ const BaseForm = ({
disabled,
formFromProps,
onChange,
onSubmit,
preventDefaultSubmit = false,
}: BaseFormProps) => {
const initialDefaultValues = useMemo(() => {
if (defaultValues)
@@ -68,6 +76,8 @@ const BaseForm = ({
const { getFormValues } = useGetFormValues(form, formSchemas)
const { getValidators } = useGetValidators()
const [fieldStates, setFieldStates] = useState<Record<string, FieldState>>({})
const showOnValues = useStore(form.store, (s: any) => {
const result: Record<string, any> = {}
formSchemas.forEach((schema) => {
@@ -81,6 +91,34 @@ const BaseForm = ({
return result
})
const setFields = useCallback((fields: SetFieldsParam[]) => {
const newFieldStates: Record<string, FieldState> = { ...fieldStates }
for (const field of fields) {
const { name, value, errors, warnings, validateStatus, help } = field
if (value !== undefined)
form.setFieldValue(name, value)
let finalValidateStatus = validateStatus
if (!finalValidateStatus) {
if (errors && errors.length > 0)
finalValidateStatus = FormItemValidateStatusEnum.Error
else if (warnings && warnings.length > 0)
finalValidateStatus = FormItemValidateStatusEnum.Warning
}
newFieldStates[name] = {
validateStatus: finalValidateStatus,
help,
errors,
warnings,
}
}
setFieldStates(newFieldStates)
}, [form, fieldStates])
useImperativeHandle(ref, () => {
return {
getForm() {
@@ -89,8 +127,9 @@ const BaseForm = ({
getFormValues: (option) => {
return getFormValues(option)
},
setFields,
}
}, [form, getFormValues])
}, [form, getFormValues, setFields])
const renderField = useCallback((field: AnyFieldApi) => {
const formSchema = formSchemas?.find(schema => schema.name === field.name)
@@ -100,18 +139,19 @@ const BaseForm = ({
<BaseField
field={field}
formSchema={formSchema}
fieldClassName={fieldClassName}
labelClassName={labelClassName}
fieldClassName={fieldClassName ?? formSchema.fieldClassName}
labelClassName={labelClassName ?? formSchema.labelClassName}
inputContainerClassName={inputContainerClassName}
inputClassName={inputClassName}
disabled={disabled}
onChange={onChange}
fieldState={fieldStates[field.name]}
/>
)
}
return null
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled, onChange])
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled, onChange, fieldStates])
const renderFieldWrapper = useCallback((formSchema: FormSchema) => {
const validators = getValidators(formSchema)
@@ -142,9 +182,18 @@ const BaseForm = ({
if (!formSchemas?.length)
return null
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
if (preventDefaultSubmit) {
e.preventDefault()
e.stopPropagation()
}
onSubmit?.(e)
}
return (
<form
className={cn(formClassName)}
onSubmit={handleSubmit}
>
{formSchemas.map(renderFieldWrapper)}
</form>

View File

@@ -11,7 +11,9 @@ type SelectFieldProps = {
options: Option[]
onChange?: (value: string) => void
className?: string
} & Omit<PureSelectProps, 'options' | 'value' | 'onChange'>
} & Omit<PureSelectProps, 'options' | 'value' | 'onChange' | 'multiple'> & {
multiple?: false
}
const SelectField = ({
label,

View File

@@ -1,5 +1,5 @@
import type { ChangeEvent } from 'react'
import { useState } from 'react'
import { useCallback, useState } from 'react'
import { RiEditLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import SegmentedControl from '@/app/components/base/segmented-control'
@@ -33,9 +33,9 @@ const VariableOrConstantInputField = ({
},
]
const handleVariableOrConstantChange = (value: string) => {
const handleVariableOrConstantChange = useCallback((value: string) => {
setVariableType(value)
}
}, [setVariableType])
const handleVariableValueChange = () => {
console.log('Variable value changed')

View File

@@ -12,7 +12,7 @@ export const useGetFormValues = (form: AnyFormApi, formSchemas: FormSchema[]) =>
const getFormValues = useCallback((
{
needCheckValidatedValues,
needCheckValidatedValues = true,
needTransformWhenSecretFieldIsPristine,
}: GetValuesOptions,
) => {
@@ -20,7 +20,7 @@ export const useGetFormValues = (form: AnyFormApi, formSchemas: FormSchema[]) =>
if (!needCheckValidatedValues) {
return {
values,
isCheckValidated: false,
isCheckValidated: true,
}
}

View File

@@ -102,14 +102,14 @@ const FormPlayground = () => {
options={{
...demoFormOpts,
validators: {
onSubmit: ({ value }) => {
const result = UserSchema.safeParse(value as typeof demoFormOpts.defaultValues)
onSubmit: ({ value: formValue }) => {
const result = UserSchema.safeParse(formValue as typeof demoFormOpts.defaultValues)
if (!result.success)
return result.error.issues[0].message
return undefined
},
},
onSubmit: ({ value }) => {
onSubmit: () => {
setStatus('Successfully saved profile.')
},
}}

View File

@@ -6,6 +6,7 @@ import type {
AnyFormApi,
FieldValidators,
} from '@tanstack/react-form'
import type { Locale } from '@/i18n-config'
export type TypeWithI18N<T = string> = {
en_US: T
@@ -36,7 +37,7 @@ export enum FormTypeEnum {
}
export type FormOption = {
label: TypeWithI18N | string
label: string | TypeWithI18N | Record<Locale, string>
value: string
show_on?: FormShowOnObject[]
icon?: string
@@ -44,23 +45,41 @@ export type FormOption = {
export type AnyValidators = FieldValidators<any, any, any, any, any, any, any, any, any, any, any, any>
export enum FormItemValidateStatusEnum {
Success = 'success',
Warning = 'warning',
Error = 'error',
Validating = 'validating',
}
export type FormSchema = {
type: FormTypeEnum
name: string
label: string | ReactNode | TypeWithI18N
label: string | ReactNode | TypeWithI18N | Record<Locale, string>
required: boolean
multiple?: boolean
default?: any
tooltip?: string | TypeWithI18N
description?: string | TypeWithI18N | Record<Locale, string>
tooltip?: string | TypeWithI18N | Record<Locale, string>
show_on?: FormShowOnObject[]
url?: string
scope?: string
help?: string | TypeWithI18N
placeholder?: string | TypeWithI18N
help?: string | TypeWithI18N | Record<Locale, string>
placeholder?: string | TypeWithI18N | Record<Locale, string>
options?: FormOption[]
labelClassName?: string
fieldClassName?: string
validators?: AnyValidators
showRadioUI?: boolean
disabled?: boolean
showCopy?: boolean
dynamicSelectParams?: {
plugin_id: string
provider: string
action: string
parameter: string
credential_id: string
}
}
export type FormValues = Record<string, any>
@@ -69,11 +88,25 @@ export type GetValuesOptions = {
needTransformWhenSecretFieldIsPristine?: boolean
needCheckValidatedValues?: boolean
}
export type FieldState = {
validateStatus?: FormItemValidateStatusEnum
help?: string | ReactNode
errors?: string[]
warnings?: string[]
}
export type SetFieldsParam = {
name: string
value?: any
} & FieldState
export type FormRefObject = {
getForm: () => AnyFormApi
getFormValues: (obj: GetValuesOptions) => {
values: Record<string, any>
isCheckValidated: boolean
}
setFields: (fields: SetFieldsParam[]) => void
}
export type FormRef = ForwardedRef<FormRefObject>

View File

@@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.43295 1.50009L11.1961 9.7501C11.3342 9.98925 11.2523 10.295 11.0131 10.4331C10.9371 10.477 10.8509 10.5001 10.7631 10.5001H1.23682C0.960676 10.5001 0.736816 10.2762 0.736816 10.0001C0.736816 9.9123 0.759921 9.8261 0.803806 9.7501L5.56695 1.50009C5.705 1.26094 6.0108 1.179 6.24995 1.31707C6.32595 1.36096 6.3891 1.42408 6.43295 1.50009ZM5.49995 8.0001V9.0001H6.49995V8.0001H5.49995ZM5.49995 4.50008V7.0001H6.49995V4.50008H5.49995Z" fill="#F79009"/>
</svg>

After

Width:  |  Height:  |  Size: 564 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.43341 6.41661L6.30441 3.2876L7.12936 2.46265L11.6666 6.99994L7.12936 11.5372L6.30441 10.7122L9.43341 7.58327H2.33331V6.41661H9.43341Z" fill="#155AEF"/>
</svg>

After

Width:  |  Height:  |  Size: 267 B

View File

@@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.1499 6.35213L7.25146 6.38208L14.2248 9.03898L14.3172 9.08195C14.7224 9.30788 14.778 9.87906 14.424 10.179L14.342 10.2389L11.8172 11.817L10.2391 14.3417C9.96271 14.7839 9.32424 14.751 9.08219 14.317L9.03923 14.2245L6.38232 7.25122C6.18829 6.74188 6.64437 6.24196 7.1499 6.35213ZM9.81201 12.5084L10.7671 10.981L10.8114 10.9185C10.8589 10.8589 10.9163 10.8075 10.9813 10.7668L12.5086 9.81177L8.15251 8.15226L9.81201 12.5084Z" fill="#676F83"/>
<path d="M5.2124 10.3977L3.56266 12.0474L2.61995 11.1047L4.26969 9.455L5.2124 10.3977Z" fill="#676F83"/>
<path d="M3.66683 7.99992H1.3335V6.66659H3.66683V7.99992Z" fill="#676F83"/>
<path d="M5.2124 4.2688L4.26969 5.21151L2.61995 3.56177L3.56266 2.61906L5.2124 4.2688Z" fill="#676F83"/>
<path d="M12.0477 3.56177L10.3979 5.21151L9.45524 4.2688L11.105 2.61906L12.0477 3.56177Z" fill="#676F83"/>
<path d="M8.00016 3.66659H6.66683V1.33325H8.00016V3.66659Z" fill="#676F83"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00001 12.7761L12.1381 8.63804L11.1953 7.69524L8.00001 10.8905L4.80475 7.69524L3.86194 8.63804L8.00001 12.7761ZM8.00001 9.00951L12.1381 4.87146L11.1953 3.92865L8.00001 7.12391L4.80475 3.92865L3.86194 4.87146L8.00001 9.00951Z" fill="#676F83"/>
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.02888 6.23572C5.08558 6.23572 4.56458 7.33027 5.15943 8.06239L7.13069 10.4885C7.57898 11.0403 8.42124 11.0403 8.86962 10.4885L10.8408 8.06239C11.4357 7.33027 10.9147 6.23572 9.97134 6.23572H6.02888Z" fill="#101828" fill-opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3.22388L3.86194 7.36193L4.80475 8.30473L8 5.10949L11.1953 8.30473L12.1381 7.36193L8 3.22388ZM8 6.99046L3.86194 11.1285L4.80475 12.0713L8 8.87606L11.1953 12.0713L12.1381 11.1285L8 6.99046Z" fill="#676F83"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.92578 11.0094C5.92578 10.0174 5.12163 9.21256 4.12956 9.21256C3.13752 9.2126 2.33333 10.0174 2.33333 11.0094C2.33349 12.0014 3.13762 12.8056 4.12956 12.8057C5.12153 12.8057 5.92562 12.0014 5.92578 11.0094ZM13.6667 11.0094C13.6667 10.0174 12.8625 9.2126 11.8704 9.21256C10.8784 9.21256 10.0742 10.0174 10.0742 11.0094C10.0744 12.0014 10.8785 12.8057 11.8704 12.8057C12.8624 12.8056 13.6665 12.0014 13.6667 11.0094ZM9.79622 4.32389C9.79619 3.33186 8.99205 2.52767 8 2.52767C7.00796 2.52767 6.20382 3.33186 6.20378 4.32389C6.20378 5.31596 7.00793 6.12012 8 6.12012C8.99207 6.12012 9.79622 5.31596 9.79622 4.32389ZM11.1296 4.32389C11.1296 5.82351 10.0748 7.07628 8.66667 7.38184V7.9196L9.74284 8.71387C10.3012 8.19607 11.0489 7.87923 11.8704 7.87923C13.5989 7.87927 15 9.28101 15 11.0094C14.9998 12.7377 13.5988 14.139 11.8704 14.139C10.1421 14.139 8.74104 12.7378 8.74089 11.0094C8.74089 10.5837 8.82585 10.1776 8.97982 9.80762L8 9.08366L7.01953 9.80762C7.17356 10.1777 7.25911 10.5836 7.25911 11.0094C7.25896 12.7378 5.85791 14.139 4.12956 14.139C2.40124 14.139 1.00016 12.7377 1 11.0094C1 9.28101 2.40114 7.87927 4.12956 7.87923C4.95094 7.87923 5.69819 8.19627 6.25651 8.71387L7.33333 7.9196V7.38184C5.92523 7.07628 4.87044 5.82351 4.87044 4.32389C4.87048 2.59548 6.27158 1.19434 8 1.19434C9.72843 1.19434 11.1295 2.59548 11.1296 4.32389Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.58325 1.75L7.58314 5.98908L11.2549 3.86982L11.8382 4.88018L8.16705 6.99942L11.8382 9.11983L11.2549 10.1302L7.58314 8.01033L7.58325 12.25H6.41659L6.41647 8.01033L2.74495 10.1302L2.16162 9.11983L5.83254 7L2.16162 4.88018L2.74495 3.86982L6.41647 5.98908L6.41659 1.75H7.58325Z" fill="#354052"/>
</svg>

After

Width:  |  Height:  |  Size: 406 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.24984 0.583252V1.74992H8.74984V0.583252H9.9165V1.74992H12.2498C12.572 1.74992 12.8332 2.01109 12.8332 2.33325V11.6666C12.8332 11.9888 12.572 12.2499 12.2498 12.2499H1.74984C1.42767 12.2499 1.1665 11.9888 1.1665 11.6666V2.33325C1.1665 2.01109 1.42767 1.74992 1.74984 1.74992H4.08317V0.583252H5.24984ZM11.6665 5.83325H2.33317V11.0833H11.6665V5.83325ZM8.77055 6.49592L9.5955 7.32093L6.70817 10.2083L4.64578 8.14588L5.47073 7.32093L6.70817 8.55835L8.77055 6.49592ZM4.08317 2.91659H2.33317V4.66659H11.6665V2.91659H9.9165V3.49992H8.74984V2.91659H5.24984V3.49992H4.08317V2.91659Z" fill="#354052"/>
</svg>

After

Width:  |  Height:  |  Size: 706 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3333 9.33337C11.7015 9.33337 11.9999 9.63193 12 10V11.0573L12.8047 11.862L12.8503 11.9128C13.0638 12.1746 13.0487 12.5607 12.8047 12.8047C12.5606 13.0488 12.1746 13.0639 11.9128 12.8503L11.862 12.8047L10.862 11.8047C10.7371 11.6798 10.6667 11.5101 10.6667 11.3334V10C10.6668 9.63193 10.9652 9.33337 11.3333 9.33337Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.3333 7.33337C13.5425 7.33337 15.3333 9.12424 15.3333 11.3334C15.3333 13.5425 13.5425 15.3334 11.3333 15.3334C9.12419 15.3334 7.33333 13.5425 7.33333 11.3334C7.33333 9.12424 9.12419 7.33337 11.3333 7.33337ZM11.3333 8.66671C9.86057 8.66671 8.66667 9.86061 8.66667 11.3334C8.66667 12.8061 9.86057 14 11.3333 14C12.8061 14 14 12.8061 14 11.3334C14 9.86061 12.8061 8.66671 11.3333 8.66671Z" fill="white"/>
<path d="M10.6667 1.33337C11.0349 1.33337 11.3333 1.63185 11.3333 2.00004V2.66671H12.6667C13.4031 2.66671 14 3.26367 14 4.00004V5.66671C14 6.0349 13.7015 6.33337 13.3333 6.33337C12.9651 6.33337 12.6667 6.0349 12.6667 5.66671V4.00004H3.33333V12.6667H5.66667C6.03486 12.6667 6.33333 12.9652 6.33333 13.3334C6.33333 13.7016 6.03486 14 5.66667 14H3.33333C2.59697 14 2 13.4031 2 12.6667V4.00004C2 3.26366 2.59696 2.66671 3.33333 2.66671H4.66667V2.00004C4.66667 1.63185 4.96514 1.33337 5.33333 1.33337C5.70152 1.33337 6 1.63185 6 2.00004V2.66671H10V2.00004C10 1.63185 10.2985 1.33337 10.6667 1.33337Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,8 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.34698 6.42505C5.10275 5.79268 5.67045 5.17005 6.29816 5.30916L6.42446 5.34758L13.0846 7.92049L13.1999 7.97518C13.7051 8.26089 13.7647 8.9802 13.3118 9.34432L13.207 9.41659L10.8196 10.8202L9.416 13.2076C9.08465 13.7711 8.28069 13.742 7.97459 13.2004L7.9199 13.0852L5.34698 6.42505ZM8.791 11.6392L9.73631 10.0325L9.73696 10.0318L9.7962 9.94458C9.86055 9.86164 9.94031 9.79125 10.0312 9.73755L10.0319 9.7369L11.6387 8.79159L6.99738 6.99797L8.791 11.6392Z" fill="white"/>
<path d="M2.79751 8.9257C3.05781 8.66539 3.47985 8.66547 3.74021 8.9257C4.00057 9.18604 4.00056 9.60805 3.74021 9.86841L3.03318 10.5754C2.77283 10.8356 2.35078 10.8357 2.09047 10.5754C1.83032 10.3151 1.83033 9.89305 2.09047 9.63273L2.79751 8.9257Z" fill="white"/>
<path d="M1.99998 5.66659C2.36817 5.66659 2.66665 5.96506 2.66665 6.33325C2.66665 6.70144 2.36817 6.99992 1.99998 6.99992H0.99998C0.63179 6.99992 0.333313 6.70144 0.333313 6.33325C0.333313 5.96506 0.63179 5.66659 0.99998 5.66659H1.99998Z" fill="white"/>
<path d="M9.63279 2.09106C9.8931 1.83077 10.3151 1.83086 10.5755 2.09106C10.8358 2.35142 10.8359 2.77343 10.5755 3.03377L9.86847 3.7408C9.6081 4.00098 9.18605 4.0011 8.92576 3.7408C8.66559 3.4805 8.66562 3.05841 8.92576 2.7981L9.63279 2.09106Z" fill="white"/>
<path d="M2.09113 2.09041C2.33521 1.84649 2.72126 1.83132 2.98305 2.04484L3.03383 2.09041L3.74087 2.79744L3.78644 2.84823C3.9999 3.11002 3.98476 3.49609 3.74087 3.74015C3.49682 3.9842 3.11079 3.9992 2.84894 3.78573L2.79816 3.74015L2.09113 3.03312L2.04555 2.98234C1.83199 2.72049 1.84705 2.33449 2.09113 2.09041Z" fill="white"/>
<path d="M6.33331 0.333252C6.7015 0.333252 6.99998 0.631729 6.99998 0.999919V1.99992C6.99998 2.36811 6.7015 2.66659 6.33331 2.66659C5.96512 2.66659 5.66665 2.36811 5.66665 1.99992V0.999919C5.66665 0.631729 5.96512 0.333252 6.33331 0.333252Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.91246 9.42618C5.77036 9.66084 5.70006 9.85191 5.81358 10.1502C6.12696 10.9742 5.68488 11.776 4.85394 11.9937C4.07033 12.199 3.30686 11.684 3.15138 10.8451C3.01362 10.1025 3.58988 9.37451 4.40859 9.25851C4.45305 9.25211 4.49808 9.24938 4.55563 9.24591C4.58692 9.24404 4.62192 9.24191 4.66252 9.23884L5.90792 7.15051C5.12463 6.37166 4.65841 5.46114 4.7616 4.33295C4.83455 3.53543 5.14813 2.84626 5.72135 2.28138C6.81916 1.19968 8.49403 1.02449 9.78663 1.85479C11.0282 2.65232 11.5967 4.20582 11.112 5.53545L9.97403 5.22671C10.1263 4.48748 10.0137 3.82362 9.5151 3.25494C9.1857 2.87947 8.76303 2.68267 8.28236 2.61015C7.31883 2.46458 6.37278 3.08364 6.09207 4.02937C5.77342 5.10275 6.25566 5.97954 7.5735 6.64023C7.0207 7.56944 6.47235 8.50124 5.91246 9.42618ZM9.18916 5.51562C9.5877 6.2187 9.99236 6.93244 10.3934 7.63958C12.4206 7.01244 13.9491 8.13458 14.4974 9.33604C15.1597 10.7873 14.707 12.5062 13.4062 13.4016C12.0711 14.3207 10.3827 14.1636 9.19976 12.983L10.1279 12.2063C11.2962 12.963 12.3181 12.9274 13.0767 12.0314C13.7236 11.2669 13.7096 10.1271 13.0439 9.37871C12.2757 8.51511 11.2467 8.48878 10.0029 9.31784C9.48696 8.40251 8.96196 7.49424 8.46236 6.57234C8.2939 6.2616 8.10783 6.08135 7.72816 6.01558C7.09403 5.90564 6.68463 5.36109 6.66007 4.75099C6.63593 4.14763 6.99136 3.60224 7.54696 3.38974C8.0973 3.17924 8.74316 3.34916 9.11336 3.81707C9.4159 4.19938 9.51203 4.62966 9.35283 5.10116C9.32283 5.19018 9.28689 5.27727 9.2475 5.37261C9.22869 5.418 9.20916 5.46538 9.18916 5.51562ZM7.7013 11.2634H10.1417C10.1757 11.3087 10.2075 11.3536 10.2386 11.3973C10.3034 11.4887 10.3649 11.5755 10.4367 11.6526C10.9536 12.2052 11.8263 12.2326 12.3788 11.7197C12.9514 11.1881 12.9773 10.2951 12.4362 9.74011C11.9068 9.19704 11.0019 9.14518 10.5103 9.72018C10.2117 10.0696 9.9057 10.1107 9.50936 10.1045C8.49423 10.0888 7.47843 10.0994 6.46346 10.0994C6.52934 11.5273 5.98953 12.417 4.9189 12.6283C3.87051 12.8352 2.90496 12.3003 2.56502 11.3243C2.17891 10.2153 2.65641 9.32838 4.0361 8.62444C3.93228 8.24838 3.8274 7.86778 3.72357 7.49071C2.21981 7.81844 1.09162 9.27738 1.20809 10.9187C1.31097 12.3676 2.47975 13.6544 3.90909 13.8849C4.68542 14.0102 5.41485 13.88 6.09157 13.4962C6.96216 13.0022 7.46736 12.2254 7.7013 11.2634Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/baichuan-text-cn.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './BaichuanTextCn.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'BaichuanTextCn'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/minimax.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './Minimax.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'Minimax'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/minimax-text.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './MinimaxText.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'MinimaxText'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/tongyi.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './Tongyi.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'Tongyi'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/tongyi-text.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './TongyiText.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'TongyiText'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/tongyi-text-cn.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './TongyiTextCn.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'TongyiTextCn'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/wxyy.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './Wxyy.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'Wxyy'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/wxyy-text.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './WxyyText.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'WxyyText'
export default Icon

View File

@@ -0,0 +1,5 @@
.wrapper {
display: inline-flex;
background: url(~@/app/components/base/icons/assets/image/llm/wxyy-text-cn.png) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import cn from '@/utils/classnames'
import s from './WxyyTextCn.module.css'
const Icon = (
{
ref,
className,
...restProps
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> & {
ref?: React.RefObject<HTMLSpanElement>;
},
) => <span className={cn(s.wrapper, className)} {...restProps} ref={ref} />
Icon.displayName = 'WxyyTextCn'
export default Icon

View File

@@ -0,0 +1,9 @@
export { default as BaichuanTextCn } from './BaichuanTextCn'
export { default as MinimaxText } from './MinimaxText'
export { default as Minimax } from './Minimax'
export { default as TongyiTextCn } from './TongyiTextCn'
export { default as TongyiText } from './TongyiText'
export { default as Tongyi } from './Tongyi'
export { default as WxyyTextCn } from './WxyyTextCn'
export { default as WxyyText } from './WxyyText'
export { default as Wxyy } from './Wxyy'

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Checked.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Checked'
export default Icon

View File

@@ -0,0 +1 @@
export { default as Checked } from './Checked'

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Google.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Google'
export default Icon

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './WebReader.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'WebReader'
export default Icon

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Wikipedia.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Wikipedia'
export default Icon

View File

@@ -0,0 +1,7 @@
export { default as Google } from './Google'
export { default as PartnerDark } from './PartnerDark'
export { default as PartnerLight } from './PartnerLight'
export { default as VerifiedDark } from './VerifiedDark'
export { default as VerifiedLight } from './VerifiedLight'
export { default as WebReader } from './WebReader'
export { default as Wikipedia } from './Wikipedia'

View File

@@ -18,4 +18,3 @@ const Icon = (
Icon.displayName = 'DataSet'
export default Icon

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Loading.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Loading'
export default Icon

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Search.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Search'
export default Icon

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './ThoughtList.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'ThoughtList'
export default Icon

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './WebReader.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'WebReader'
export default Icon

View File

@@ -1,2 +1,5 @@
export { default as DataSet } from './DataSet'
export { default as Loading } from './Loading'
export { default as Search } from './Search'
export { default as ThoughtList } from './ThoughtList'
export { default as WebReader } from './WebReader'

View File

@@ -8,10 +8,10 @@ export { default as LangsmithIconBig } from './LangsmithIconBig'
export { default as LangsmithIcon } from './LangsmithIcon'
export { default as OpikIconBig } from './OpikIconBig'
export { default as OpikIcon } from './OpikIcon'
export { default as PhoenixIconBig } from './PhoenixIconBig'
export { default as PhoenixIcon } from './PhoenixIcon'
export { default as TencentIconBig } from './TencentIconBig'
export { default as TencentIcon } from './TencentIcon'
export { default as PhoenixIconBig } from './PhoenixIconBig'
export { default as PhoenixIcon } from './PhoenixIcon'
export { default as TracingIcon } from './TracingIcon'
export { default as WeaveIconBig } from './WeaveIconBig'
export { default as WeaveIcon } from './WeaveIcon'

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -11,7 +11,7 @@ const Icon = (
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />

View File

@@ -0,0 +1,26 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.43295 1.50009L11.1961 9.7501C11.3342 9.98925 11.2523 10.295 11.0131 10.4331C10.9371 10.477 10.8509 10.5001 10.7631 10.5001H1.23682C0.960676 10.5001 0.736816 10.2762 0.736816 10.0001C0.736816 9.9123 0.759921 9.8261 0.803806 9.7501L5.56695 1.50009C5.705 1.26094 6.0108 1.179 6.24995 1.31707C6.32595 1.36096 6.3891 1.42408 6.43295 1.50009ZM5.49995 8.0001V9.0001H6.49995V8.0001H5.49995ZM5.49995 4.50008V7.0001H6.49995V4.50008H5.49995Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "Warning"
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Warning.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Warning'
export default Icon

View File

@@ -1,3 +1,4 @@
export { default as AlertTriangle } from './AlertTriangle'
export { default as ThumbsDown } from './ThumbsDown'
export { default as ThumbsUp } from './ThumbsUp'
export { default as Warning } from './Warning'

View File

@@ -0,0 +1,26 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "14",
"viewBox": "0 0 14 14",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M9.43341 6.41661L6.30441 3.2876L7.12936 2.46265L11.6666 6.99994L7.12936 11.5372L6.30441 10.7122L9.43341 7.58327H2.33331V6.41661H9.43341Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "IconR"
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './IconR.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'IconR'
export default Icon

View File

@@ -1,3 +1,4 @@
export { default as IconR } from './IconR'
export { default as ArrowNarrowLeft } from './ArrowNarrowLeft'
export { default as ArrowUpRight } from './ArrowUpRight'
export { default as ChevronDownDouble } from './ChevronDownDouble'

View File

@@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "14",
"viewBox": "0 0 14 14",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "ai-text"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector",
"d": "M2.33301 10.5H4.08301M2.33301 7H5.24967M2.33301 3.5H11.6663M9.91634 5.83333L10.7913 7.875L12.833 8.75L10.7913 9.625L9.91634 11.6667L9.04134 9.625L6.99967 8.75L9.04134 7.875L9.91634 5.83333Z",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "AiText"
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './AiText.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'AiText'
export default Icon

Some files were not shown because too many files have changed in this diff Show More