mirror of
https://github.com/langgenius/webapp-conversation.git
synced 2025-12-23 15:56:41 +08:00
feat: migrate ESLint to v9 flat config
- Replace .eslintrc.json with eslint.config.mjs - Simplify configuration using @antfu/eslint-config - Add necessary ESLint plugin dependencies - Disable overly strict style rules - Set package.json type to module for ESM support - Fix ESLint disable comment format 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -2,7 +2,7 @@ import type { FC } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import style from './style.module.css'
|
||||
|
||||
export type AppIconProps = {
|
||||
export interface AppIconProps {
|
||||
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
|
||||
rounded?: boolean
|
||||
icon?: string
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { forwardRef, useEffect, useRef } from 'react'
|
||||
import cn from 'classnames'
|
||||
|
||||
type IProps = {
|
||||
interface IProps {
|
||||
placeholder?: string
|
||||
value: string
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
@ -36,19 +36,16 @@ const AutoHeightTextarea = forwardRef(
|
||||
let hasFocus = false
|
||||
const runId = setInterval(() => {
|
||||
hasFocus = doFocus()
|
||||
if (hasFocus)
|
||||
clearInterval(runId)
|
||||
if (hasFocus) { clearInterval(runId) }
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus)
|
||||
focus()
|
||||
if (autoFocus) { focus() }
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (controlFocus)
|
||||
focus()
|
||||
if (controlFocus) { focus() }
|
||||
}, [controlFocus])
|
||||
|
||||
return (
|
||||
|
||||
@ -2,7 +2,7 @@ import type { FC, MouseEventHandler } from 'react'
|
||||
import React from 'react'
|
||||
import Spinner from '@/app/components/base/spinner'
|
||||
|
||||
export type IButtonProps = {
|
||||
export interface IButtonProps {
|
||||
type?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type FileFromLinkOrLocalProps = {
|
||||
interface FileFromLinkOrLocalProps {
|
||||
showFromLink?: boolean
|
||||
showFromLocal?: boolean
|
||||
trigger: (open: boolean) => React.ReactNode
|
||||
@ -38,8 +38,7 @@ const FileFromLinkOrLocal = ({
|
||||
const disabled = !!fileConfig.number_limits && files.length >= fileConfig.number_limits
|
||||
|
||||
const handleSaveUrl = () => {
|
||||
if (!url)
|
||||
return
|
||||
if (!url) { return }
|
||||
|
||||
if (!FILE_URL_REGEX.test(url)) {
|
||||
setShowError(true)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type FileImageRenderProps = {
|
||||
interface FileImageRenderProps {
|
||||
imageUrl: string
|
||||
className?: string
|
||||
alt?: string
|
||||
|
||||
@ -4,7 +4,7 @@ import type { FileUpload } from './types'
|
||||
import { FILE_EXTS } from './constants'
|
||||
import { SupportUploadFileTypes } from './types'
|
||||
|
||||
type FileInputProps = {
|
||||
interface FileInputProps {
|
||||
fileConfig: FileUpload
|
||||
}
|
||||
const FileInput = ({
|
||||
@ -18,8 +18,7 @@ const FileInput = ({
|
||||
if (targetFiles) {
|
||||
if (fileConfig.number_limits) {
|
||||
for (let i = 0; i < targetFiles.length; i++) {
|
||||
if (i + 1 + files.length <= fileConfig.number_limits)
|
||||
handleLocalFileUpload(targetFiles[i])
|
||||
if (i + 1 + files.length <= fileConfig.number_limits) { handleLocalFileUpload(targetFiles[i]) }
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@ -24,7 +24,7 @@ import cn from '@/utils/classnames'
|
||||
import ReplayLine from '@/app/components/base/icons/other/ReplayLine'
|
||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||
|
||||
type FileInAttachmentItemProps = {
|
||||
interface FileInAttachmentItemProps {
|
||||
file: FileEntity
|
||||
showDeleteAction?: boolean
|
||||
showDownloadAction?: boolean
|
||||
|
||||
@ -67,7 +67,7 @@ const FILE_TYPE_ICON_MAP = {
|
||||
color: 'text-[#00B2EA]',
|
||||
},
|
||||
}
|
||||
type FileTypeIconProps = {
|
||||
interface FileTypeIconProps {
|
||||
type: FileAppearanceType
|
||||
size?: 'sm' | 'lg' | 'md'
|
||||
className?: string
|
||||
|
||||
@ -148,8 +148,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
const newFiles = produce(files, (draft) => {
|
||||
const index = draft.findIndex(file => file.id === newFile.id)
|
||||
|
||||
if (index > -1)
|
||||
draft[index] = newFile
|
||||
if (index > -1) { draft[index] = newFile }
|
||||
})
|
||||
setFiles(newFiles)
|
||||
}, [fileStore])
|
||||
@ -198,10 +197,8 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
const files = fileStore.getState().files
|
||||
const file = files.find(file => file.id === fileId)
|
||||
|
||||
if (file && file.progress < 80 && file.progress >= 0)
|
||||
handleUpdateFile({ ...file, progress: file.progress + 20 })
|
||||
else
|
||||
clearTimeout(timer)
|
||||
if (file && file.progress < 80 && file.progress >= 0) { handleUpdateFile({ ...file, progress: file.progress + 20 }) }
|
||||
else { clearTimeout(timer) }
|
||||
}, 200)
|
||||
}, [fileStore, handleUpdateFile])
|
||||
const handleLoadFileFromLink = useCallback((url: string) => {
|
||||
@ -235,10 +232,8 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
|
||||
handleRemoveFile(uploadingFile.id)
|
||||
}
|
||||
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
|
||||
handleRemoveFile(uploadingFile.id)
|
||||
else
|
||||
handleUpdateFile(newFile)
|
||||
if (!checkSizeLimit(newFile.supportFileType, newFile.size)) { handleRemoveFile(uploadingFile.id) }
|
||||
else { handleUpdateFile(newFile) }
|
||||
}).catch(() => {
|
||||
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
|
||||
handleRemoveFile(uploadingFile.id)
|
||||
@ -263,8 +258,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
}
|
||||
const allowedFileTypes = fileConfig.allowed_file_types
|
||||
const fileType = getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom))
|
||||
if (!checkSizeLimit(fileType, file.size))
|
||||
return
|
||||
if (!checkSizeLimit(fileType, file.size)) { return }
|
||||
|
||||
const reader = new FileReader()
|
||||
const isImage = file.type.startsWith('image')
|
||||
@ -344,8 +338,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
|
||||
const file = e.dataTransfer.files[0]
|
||||
|
||||
if (file)
|
||||
handleLocalFileUpload(file)
|
||||
if (file) { handleLocalFileUpload(file) }
|
||||
}, [handleLocalFileUpload])
|
||||
|
||||
return {
|
||||
|
||||
@ -19,12 +19,12 @@ import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
type Option = {
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
icon: JSX.Element
|
||||
}
|
||||
type FileUploaderInAttachmentProps = {
|
||||
interface FileUploaderInAttachmentProps {
|
||||
fileConfig: FileUpload
|
||||
}
|
||||
const FileUploaderInAttachment = ({
|
||||
@ -71,8 +71,7 @@ const FileUploaderInAttachment = ({
|
||||
return (open: boolean) => renderButton(option, open)
|
||||
}, [renderButton])
|
||||
const renderOption = useCallback((option: Option) => {
|
||||
if (option.value === TransferMethod.local_file && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.local_file))
|
||||
return renderButton(option)
|
||||
if (option.value === TransferMethod.local_file && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.local_file)) { return renderButton(option) }
|
||||
|
||||
if (option.value === TransferMethod.remote_url && fileConfig?.allowed_file_upload_methods?.includes(TransferMethod.remote_url)) {
|
||||
return (
|
||||
@ -109,7 +108,7 @@ const FileUploaderInAttachment = ({
|
||||
)
|
||||
}
|
||||
|
||||
type FileUploaderInAttachmentWrapperProps = {
|
||||
interface FileUploaderInAttachmentWrapperProps {
|
||||
value?: FileEntity[]
|
||||
onChange: (files: FileEntity[]) => void
|
||||
fileConfig: FileUpload
|
||||
|
||||
@ -11,7 +11,7 @@ import type {
|
||||
FileEntity,
|
||||
} from './types'
|
||||
|
||||
type Shape = {
|
||||
interface Shape {
|
||||
files: FileEntity[]
|
||||
setFiles: (files: FileEntity[]) => void
|
||||
}
|
||||
@ -34,8 +34,7 @@ export const FileContext = createContext<FileStore | null>(null)
|
||||
|
||||
export function useStore<T>(selector: (state: Shape) => T): T {
|
||||
const store = useContext(FileContext)
|
||||
if (!store)
|
||||
throw new Error('Missing FileContext.Provider in the tree')
|
||||
if (!store) { throw new Error('Missing FileContext.Provider in the tree') }
|
||||
|
||||
return useZustandStore(store, selector)
|
||||
}
|
||||
@ -44,7 +43,7 @@ export const useFileStore = () => {
|
||||
return useContext(FileContext)!
|
||||
}
|
||||
|
||||
type FileProviderProps = {
|
||||
interface FileProviderProps {
|
||||
children: React.ReactNode
|
||||
value?: FileEntity[]
|
||||
onChange?: (files: FileEntity[]) => void
|
||||
@ -56,8 +55,7 @@ export const FileContextProvider = ({
|
||||
}: FileProviderProps) => {
|
||||
const storeRef = useRef<FileStore | undefined>(undefined)
|
||||
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createFileStore(value, onChange)
|
||||
if (!storeRef.current) { storeRef.current = createFileStore(value, onChange) }
|
||||
|
||||
return (
|
||||
<FileContext.Provider value={storeRef.current}>
|
||||
|
||||
@ -17,7 +17,7 @@ export enum FileAppearanceTypeEnum {
|
||||
|
||||
export type FileAppearanceType = keyof typeof FileAppearanceTypeEnum
|
||||
|
||||
export type FileEntity = {
|
||||
export interface FileEntity {
|
||||
id: string
|
||||
name: string
|
||||
size: number
|
||||
@ -32,7 +32,7 @@ export type FileEntity = {
|
||||
isRemote?: boolean
|
||||
}
|
||||
|
||||
export type EnabledOrDisabled = {
|
||||
export interface EnabledOrDisabled {
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export enum Resolution {
|
||||
high = 'high',
|
||||
}
|
||||
|
||||
export type FileUploadConfigResponse = {
|
||||
export interface FileUploadConfigResponse {
|
||||
batch_count_limit: number
|
||||
image_file_size_limit?: number | string // default is 10MB
|
||||
file_size_limit: number // default is 15MB
|
||||
@ -71,7 +71,7 @@ export enum SupportUploadFileTypes {
|
||||
custom = 'custom',
|
||||
}
|
||||
|
||||
export type FileResponse = {
|
||||
export interface FileResponse {
|
||||
related_id: string
|
||||
extension: string
|
||||
filename: string
|
||||
|
||||
@ -5,7 +5,7 @@ import { FILE_EXTS } from './constants'
|
||||
import { upload } from '@/service/base'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
type FileUploadParams = {
|
||||
interface FileUploadParams {
|
||||
file: File
|
||||
onProgressCallback: (progress: number) => void
|
||||
onSuccessCallback: (res: { id: string }) => void
|
||||
@ -42,21 +42,17 @@ export const fileUpload: FileUpload = ({
|
||||
|
||||
export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
|
||||
let extension = ''
|
||||
if (fileMimetype)
|
||||
extension = mime.getExtension(fileMimetype) || ''
|
||||
if (fileMimetype) { extension = mime.getExtension(fileMimetype) || '' }
|
||||
|
||||
if (fileName && !extension) {
|
||||
const fileNamePair = fileName.split('.')
|
||||
const fileNamePairLength = fileNamePair.length
|
||||
|
||||
if (fileNamePairLength > 1)
|
||||
extension = fileNamePair[fileNamePairLength - 1]
|
||||
else
|
||||
extension = ''
|
||||
if (fileNamePairLength > 1) { extension = fileNamePair[fileNamePairLength - 1] }
|
||||
else { extension = '' }
|
||||
}
|
||||
|
||||
if (isRemote)
|
||||
extension = ''
|
||||
if (isRemote) { extension = '' }
|
||||
|
||||
return extension
|
||||
}
|
||||
@ -64,50 +60,37 @@ export const getFileExtension = (fileName: string, fileMimetype: string, isRemot
|
||||
export const getFileAppearanceType = (fileName: string, fileMimetype: string) => {
|
||||
const extension = getFileExtension(fileName, fileMimetype)
|
||||
|
||||
if (extension === 'gif')
|
||||
return FileAppearanceTypeEnum.gif
|
||||
if (extension === 'gif') { return FileAppearanceTypeEnum.gif }
|
||||
|
||||
if (FILE_EXTS.image.includes(extension.toUpperCase()))
|
||||
return FileAppearanceTypeEnum.image
|
||||
if (FILE_EXTS.image.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.image }
|
||||
|
||||
if (FILE_EXTS.video.includes(extension.toUpperCase()))
|
||||
return FileAppearanceTypeEnum.video
|
||||
if (FILE_EXTS.video.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.video }
|
||||
|
||||
if (FILE_EXTS.audio.includes(extension.toUpperCase()))
|
||||
return FileAppearanceTypeEnum.audio
|
||||
if (FILE_EXTS.audio.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.audio }
|
||||
|
||||
if (extension === 'html')
|
||||
return FileAppearanceTypeEnum.code
|
||||
if (extension === 'html') { return FileAppearanceTypeEnum.code }
|
||||
|
||||
if (extension === 'pdf')
|
||||
return FileAppearanceTypeEnum.pdf
|
||||
if (extension === 'pdf') { return FileAppearanceTypeEnum.pdf }
|
||||
|
||||
if (extension === 'md' || extension === 'markdown' || extension === 'mdx')
|
||||
return FileAppearanceTypeEnum.markdown
|
||||
if (extension === 'md' || extension === 'markdown' || extension === 'mdx') { return FileAppearanceTypeEnum.markdown }
|
||||
|
||||
if (extension === 'xlsx' || extension === 'xls')
|
||||
return FileAppearanceTypeEnum.excel
|
||||
if (extension === 'xlsx' || extension === 'xls') { return FileAppearanceTypeEnum.excel }
|
||||
|
||||
if (extension === 'docx' || extension === 'doc')
|
||||
return FileAppearanceTypeEnum.word
|
||||
if (extension === 'docx' || extension === 'doc') { return FileAppearanceTypeEnum.word }
|
||||
|
||||
if (extension === 'pptx' || extension === 'ppt')
|
||||
return FileAppearanceTypeEnum.ppt
|
||||
if (extension === 'pptx' || extension === 'ppt') { return FileAppearanceTypeEnum.ppt }
|
||||
|
||||
if (FILE_EXTS.document.includes(extension.toUpperCase()))
|
||||
return FileAppearanceTypeEnum.document
|
||||
if (FILE_EXTS.document.includes(extension.toUpperCase())) { return FileAppearanceTypeEnum.document }
|
||||
|
||||
return FileAppearanceTypeEnum.custom
|
||||
}
|
||||
|
||||
export const getSupportFileType = (fileName: string, fileMimetype: string, isCustom?: boolean) => {
|
||||
if (isCustom)
|
||||
return SupportUploadFileTypes.custom
|
||||
if (isCustom) { return SupportUploadFileTypes.custom }
|
||||
|
||||
const extension = getFileExtension(fileName, fileMimetype)
|
||||
for (const key in FILE_EXTS) {
|
||||
if ((FILE_EXTS[key]).includes(extension.toUpperCase()))
|
||||
return key
|
||||
if ((FILE_EXTS[key]).includes(extension.toUpperCase())) { return key }
|
||||
}
|
||||
|
||||
return ''
|
||||
@ -144,8 +127,7 @@ export const getFileNameFromUrl = (url: string) => {
|
||||
}
|
||||
|
||||
export const getSupportFileExtensionList = (allowFileTypes: string[], allowFileExtensions: string[]) => {
|
||||
if (allowFileTypes.includes(SupportUploadFileTypes.custom))
|
||||
return allowFileExtensions.map(item => item.slice(1).toUpperCase())
|
||||
if (allowFileTypes.includes(SupportUploadFileTypes.custom)) { return allowFileExtensions.map(item => item.slice(1).toUpperCase()) }
|
||||
|
||||
return allowFileTypes.map(type => FILE_EXTS[type]).flat()
|
||||
}
|
||||
@ -174,11 +156,9 @@ export const getFilesInLogs = (rawData: any) => {
|
||||
}
|
||||
|
||||
export const fileIsUploaded = (file: FileEntity) => {
|
||||
if (file.uploadedId)
|
||||
return true
|
||||
if (file.uploadedId) { return true }
|
||||
|
||||
if (file.transferMethod === TransferMethod.remote_url && file.progress === 100)
|
||||
return true
|
||||
if (file.transferMethod === TransferMethod.remote_url && file.progress === 100) { return true }
|
||||
}
|
||||
|
||||
export const downloadFile = (url: string, filename: string) => {
|
||||
|
||||
@ -2,12 +2,12 @@ import { forwardRef } from 'react'
|
||||
import { generate } from './utils'
|
||||
import type { AbstractNode } from './utils'
|
||||
|
||||
export type IconData = {
|
||||
export interface IconData {
|
||||
name: string
|
||||
icon: AbstractNode
|
||||
}
|
||||
|
||||
export type IconBaseProps = {
|
||||
export interface IconBaseProps {
|
||||
data: IconData
|
||||
className?: string
|
||||
onClick?: React.MouseEventHandler<SVGElement>
|
||||
|
||||
@ -11,7 +11,7 @@ const Icon = (
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
export type AbstractNode = {
|
||||
export interface AbstractNode {
|
||||
name: string
|
||||
attributes: {
|
||||
[key: string]: string
|
||||
@ -8,7 +8,7 @@ export type AbstractNode = {
|
||||
children?: AbstractNode[]
|
||||
}
|
||||
|
||||
export type Attrs = {
|
||||
export interface Attrs {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import cn from 'classnames'
|
||||
import s from './style.module.css'
|
||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
srcs: string[]
|
||||
}
|
||||
|
||||
@ -65,9 +65,9 @@ export const ImageGalleryTest = () => {
|
||||
const imgGallerySrcs = (() => {
|
||||
const srcs = []
|
||||
for (let i = 0; i < 6; i++)
|
||||
// srcs.push('https://placekitten.com/640/360')
|
||||
// srcs.push('https://placekitten.com/360/640')
|
||||
srcs.push('https://placekitten.com/360/360')
|
||||
// srcs.push('https://placekitten.com/640/360')
|
||||
// srcs.push('https://placekitten.com/360/640')
|
||||
{ srcs.push('https://placekitten.com/360/360') }
|
||||
|
||||
return srcs
|
||||
})()
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
import Upload03 from '@/app/components/base/icons/line/upload-03'
|
||||
import type { ImageFile, VisionSettings } from '@/types/app'
|
||||
|
||||
type UploadOnlyFromLocalProps = {
|
||||
interface UploadOnlyFromLocalProps {
|
||||
onUpload: (imageFile: ImageFile) => void
|
||||
disabled?: boolean
|
||||
limit?: number
|
||||
@ -39,7 +39,7 @@ const UploadOnlyFromLocal: FC<UploadOnlyFromLocalProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
type UploaderButtonProps = {
|
||||
interface UploaderButtonProps {
|
||||
methods: VisionSettings['transfer_methods']
|
||||
onUpload: (imageFile: ImageFile) => void
|
||||
disabled?: boolean
|
||||
@ -62,8 +62,7 @@ const UploaderButton: FC<UploaderButtonProps> = ({
|
||||
}
|
||||
|
||||
const handleToggle = () => {
|
||||
if (disabled)
|
||||
return
|
||||
if (disabled) { return }
|
||||
|
||||
setOpen(v => !v)
|
||||
}
|
||||
@ -115,7 +114,7 @@ const UploaderButton: FC<UploaderButtonProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
type ChatImageUploaderProps = {
|
||||
interface ChatImageUploaderProps {
|
||||
settings: VisionSettings
|
||||
onUpload: (imageFile: ImageFile) => void
|
||||
disabled?: boolean
|
||||
|
||||
@ -5,7 +5,7 @@ import Button from '@/app/components/base/button'
|
||||
import type { ImageFile } from '@/types/app'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
type ImageLinkInputProps = {
|
||||
interface ImageLinkInputProps {
|
||||
onUpload: (imageFile: ImageFile) => void
|
||||
}
|
||||
const regex = /^(https?|ftp):\/\//
|
||||
|
||||
@ -10,7 +10,7 @@ import type { ImageFile } from '@/types/app'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||
|
||||
type ImageListProps = {
|
||||
interface ImageListProps {
|
||||
list: ImageFile[]
|
||||
readonly?: boolean
|
||||
onRemove?: (imageFileId: string) => void
|
||||
@ -31,12 +31,10 @@ const ImageList: FC<ImageListProps> = ({
|
||||
const [imagePreviewUrl, setImagePreviewUrl] = useState('')
|
||||
|
||||
const handleImageLinkLoadSuccess = (item: ImageFile) => {
|
||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadSuccess && item.progress !== -1)
|
||||
onImageLinkLoadSuccess(item._id)
|
||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadSuccess && item.progress !== -1) { onImageLinkLoadSuccess(item._id) }
|
||||
}
|
||||
const handleImageLinkLoadError = (item: ImageFile) => {
|
||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadError)
|
||||
onImageLinkLoadError(item._id)
|
||||
if (item.type === TransferMethod.remote_url && onImageLinkLoadError) { onImageLinkLoadError(item._id) }
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -2,7 +2,7 @@ import type { FC } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import XClose from '@/app/components/base/icons/line/x-close'
|
||||
|
||||
type ImagePreviewProps = {
|
||||
interface ImagePreviewProps {
|
||||
url: string
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import type { ImageFile } from '@/types/app'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
type UploaderProps = {
|
||||
interface UploaderProps {
|
||||
children: (hovering: boolean) => JSX.Element
|
||||
onUpload: (imageFile: ImageFile) => void
|
||||
limit?: number
|
||||
@ -28,8 +28,7 @@ const Uploader: FC<UploaderProps> = ({
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
|
||||
if (!file)
|
||||
return
|
||||
if (!file) { return }
|
||||
|
||||
if (limit && file.size > limit * 1024 * 1024) {
|
||||
notify({ type: 'error', message: t('common.imageUploader.uploadFromComputerLimit', { size: limit }) })
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { upload } from '@/service/base'
|
||||
|
||||
type ImageUploadParams = {
|
||||
interface ImageUploadParams {
|
||||
file: File
|
||||
onProgressCallback: (progress: number) => void
|
||||
onSuccessCallback: (res: { id: string }) => void
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react'
|
||||
|
||||
import './style.css'
|
||||
|
||||
type ILoadingProps = {
|
||||
interface ILoadingProps {
|
||||
type?: 'area' | 'app'
|
||||
}
|
||||
const Loading = (
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
|
||||
import type { OffsetOptions, Placement } from '@floating-ui/react'
|
||||
|
||||
type PortalToFollowElemOptions = {
|
||||
interface PortalToFollowElemOptions {
|
||||
/*
|
||||
* top, bottom, left, right
|
||||
* start, end. Default is middle
|
||||
@ -85,8 +85,7 @@ const PortalToFollowElemContext = React.createContext<ContextType>(null)
|
||||
export function usePortalToFollowElemContext() {
|
||||
const context = React.useContext(PortalToFollowElemContext)
|
||||
|
||||
if (context == null)
|
||||
throw new Error('PortalToFollowElem components must be wrapped in <PortalToFollowElem />')
|
||||
if (context == null) { throw new Error('PortalToFollowElem components must be wrapped in <PortalToFollowElem />') }
|
||||
|
||||
return context
|
||||
}
|
||||
@ -106,7 +105,7 @@ export function PortalToFollowElem({
|
||||
}
|
||||
|
||||
export const PortalToFollowElemTrigger = React.forwardRef<
|
||||
HTMLElement,
|
||||
HTMLElement,
|
||||
React.HTMLProps<HTMLElement> & { asChild?: boolean }
|
||||
>(({ children, asChild = false, ...props }, propRef) => {
|
||||
const context = usePortalToFollowElemContext()
|
||||
@ -141,14 +140,13 @@ React.HTMLProps<HTMLElement> & { asChild?: boolean }
|
||||
PortalToFollowElemTrigger.displayName = 'PortalToFollowElemTrigger'
|
||||
|
||||
export const PortalToFollowElemContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLProps<HTMLDivElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLProps<HTMLDivElement>
|
||||
>(({ style, ...props }, propRef) => {
|
||||
const context = usePortalToFollowElemContext()
|
||||
const ref = useMergeRefs([context.refs.setFloating, propRef])
|
||||
|
||||
if (!context.open)
|
||||
return null
|
||||
if (!context.open) { return null }
|
||||
|
||||
return (
|
||||
<FloatingPortal>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
type ProgressBarProps = {
|
||||
interface ProgressBarProps {
|
||||
percent: number
|
||||
}
|
||||
const ProgressBar = ({
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { memo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ProgressCircleProps = {
|
||||
interface ProgressCircleProps {
|
||||
className?: string
|
||||
percentage?: number
|
||||
size?: number
|
||||
|
||||
@ -15,12 +15,12 @@ const defaultItems = [
|
||||
{ value: 7, name: 'option7' },
|
||||
]
|
||||
|
||||
export type Item = {
|
||||
export interface Item {
|
||||
value: number | string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type ISelectProps = {
|
||||
export interface ISelectProps {
|
||||
className?: string
|
||||
items?: Item[]
|
||||
defaultValue?: number | string
|
||||
@ -45,8 +45,7 @@ const Select: FC<ISelectProps> = ({
|
||||
useEffect(() => {
|
||||
let defaultSelect = null
|
||||
const existed = items.find((item: Item) => item.value === defaultValue)
|
||||
if (existed)
|
||||
defaultSelect = existed
|
||||
if (existed) { defaultSelect = existed }
|
||||
|
||||
setSelectedItem(defaultSelect)
|
||||
}, [defaultValue])
|
||||
@ -77,23 +76,20 @@ const Select: FC<ISelectProps> = ({
|
||||
? <Combobox.Input
|
||||
className={`w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-not-allowed`}
|
||||
onChange={(event) => {
|
||||
if (!disabled)
|
||||
setQuery(event.target.value)
|
||||
if (!disabled) { setQuery(event.target.value) }
|
||||
}}
|
||||
displayValue={(item: Item) => item?.name}
|
||||
/>
|
||||
: <Combobox.Button onClick={
|
||||
() => {
|
||||
if (!disabled)
|
||||
setOpen(!open)
|
||||
if (!disabled) { setOpen(!open) }
|
||||
}
|
||||
} className={`flex items-center h-9 w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200`}>
|
||||
{selectedItem?.name}
|
||||
</Combobox.Button>}
|
||||
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none group-hover:bg-gray-200" onClick={
|
||||
() => {
|
||||
if (!disabled)
|
||||
setOpen(!open)
|
||||
if (!disabled) { setOpen(!open) }
|
||||
}
|
||||
}>
|
||||
{open ? <ChevronUpIcon className="h-5 w-5" /> : <ChevronDownIcon className="h-5 w-5" />}
|
||||
@ -147,8 +143,7 @@ const SimpleSelect: FC<ISelectProps> = ({
|
||||
useEffect(() => {
|
||||
let defaultSelect = null
|
||||
const existed = items.find((item: Item) => item.value === defaultValue)
|
||||
if (existed)
|
||||
defaultSelect = existed
|
||||
if (existed) { defaultSelect = existed }
|
||||
|
||||
setSelectedItem(defaultSelect)
|
||||
}, [defaultValue])
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
className?: string
|
||||
children?: React.ReactNode | string
|
||||
|
||||
18
app/components/base/streamdown-markdown.tsx
Normal file
18
app/components/base/streamdown-markdown.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
import { Streamdown } from 'streamdown'
|
||||
import 'katex/dist/katex.min.css'
|
||||
|
||||
interface StreamdownMarkdownProps {
|
||||
content: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function StreamdownMarkdown({ content, className = '' }: StreamdownMarkdownProps) {
|
||||
return (
|
||||
<div className={`markdown-body streamdown-markdown ${className}`}>
|
||||
<Streamdown>{content}</Streamdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StreamdownMarkdown
|
||||
@ -11,14 +11,14 @@ import {
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
|
||||
export type IToastProps = {
|
||||
export interface IToastProps {
|
||||
type?: 'success' | 'error' | 'warning' | 'info'
|
||||
duration?: number
|
||||
message: string
|
||||
children?: ReactNode
|
||||
onClose?: () => void
|
||||
}
|
||||
type IToastContext = {
|
||||
interface IToastContext {
|
||||
notify: (props: IToastProps) => void
|
||||
}
|
||||
const defaultDuring = 3000
|
||||
@ -33,8 +33,7 @@ const Toast = ({
|
||||
children,
|
||||
}: IToastProps) => {
|
||||
// sometimes message is react node array. Not handle it.
|
||||
if (typeof message !== 'string')
|
||||
return null
|
||||
if (typeof message !== 'string') { return null }
|
||||
|
||||
return <div className={classNames(
|
||||
'fixed rounded-md p-4 my-4 mx-8 z-50',
|
||||
@ -124,8 +123,7 @@ Toast.notify = ({
|
||||
root.render(<Toast type={type} message={message} duration={duration} />)
|
||||
document.body.appendChild(holder)
|
||||
setTimeout(() => {
|
||||
if (holder)
|
||||
holder.remove()
|
||||
if (holder) { holder.remove() }
|
||||
}, duration || defaultDuring)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
export type TooltipProps = {
|
||||
export interface TooltipProps {
|
||||
position?: 'top' | 'right' | 'bottom' | 'left'
|
||||
triggerMethod?: 'hover' | 'click'
|
||||
popupContent: React.ReactNode
|
||||
@ -13,7 +13,7 @@ const arrow = (
|
||||
<svg className="absolute text-white h-2 w-full left-0 top-full" x="0px" y="0px" viewBox="0 0 255 255"><polygon className="fill-current" points="0,0 127.5,127.5 255,0"></polygon></svg>
|
||||
)
|
||||
|
||||
const Tooltip: FC< TooltipProps> = ({
|
||||
const Tooltip: FC<TooltipProps> = ({
|
||||
position = 'top',
|
||||
triggerMethod = 'hover',
|
||||
popupContent,
|
||||
|
||||
@ -5,7 +5,7 @@ import React from 'react'
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip' // fixed version to 5.8.3 https://github.com/ReactTooltip/react-tooltip/issues/972
|
||||
import 'react-tooltip/dist/react-tooltip.css'
|
||||
|
||||
type TooltipProps = {
|
||||
interface TooltipProps {
|
||||
selector: string
|
||||
content?: string
|
||||
htmlContent?: React.ReactNode
|
||||
|
||||
Reference in New Issue
Block a user