diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 631cb2b..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "extends": [ - "@antfu", - "plugin:react-hooks/recommended" - ], - "rules": { - "@typescript-eslint/consistent-type-definitions": [ - "error", - "type" - ], - "no-console": "off", - "indent": "off", - "@typescript-eslint/indent": [ - "error", - 2, - { - "SwitchCase": 1, - "flatTernaryExpressions": false, - "ignoredNodes": [ - "PropertyDefinition[decorators]", - "TSUnionType", - "FunctionExpression[params]:has(Identifier[decorators])" - ] - } - ], - "react-hooks/exhaustive-deps": "warn" - } -} diff --git a/.gitignore b/.gitignore index 8de2014..ba3a7b4 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ yarn.lock # mcp .serena +# pmpm +pnpm-lock.yaml diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..cb2c84d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged diff --git a/app/api/chat-messages/route.ts b/app/api/chat-messages/route.ts index 849be1f..d96e525 100644 --- a/app/api/chat-messages/route.ts +++ b/app/api/chat-messages/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { client, getInfo } from '@/app/api/utils/common' export async function POST(request: NextRequest) { diff --git a/app/api/conversations/[conversationId]/name/route.ts b/app/api/conversations/[conversationId]/name/route.ts index d9dd327..1411123 100644 --- a/app/api/conversations/[conversationId]/name/route.ts +++ b/app/api/conversations/[conversationId]/name/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { client, getInfo } from '@/app/api/utils/common' diff --git a/app/api/conversations/route.ts b/app/api/conversations/route.ts index 642da1b..f5be086 100644 --- a/app/api/conversations/route.ts +++ b/app/api/conversations/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { client, getInfo, setSession } from '@/app/api/utils/common' diff --git a/app/api/file-upload/route.ts b/app/api/file-upload/route.ts index fa09906..217967b 100644 --- a/app/api/file-upload/route.ts +++ b/app/api/file-upload/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { client, getInfo } from '@/app/api/utils/common' export async function POST(request: NextRequest) { diff --git a/app/api/messages/[messageId]/feedbacks/route.ts b/app/api/messages/[messageId]/feedbacks/route.ts index c701e15..4d90fe6 100644 --- a/app/api/messages/[messageId]/feedbacks/route.ts +++ b/app/api/messages/[messageId]/feedbacks/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { client, getInfo } from '@/app/api/utils/common' diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts index 0edafe7..9db4d53 100644 --- a/app/api/messages/route.ts +++ b/app/api/messages/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { client, getInfo, setSession } from '@/app/api/utils/common' diff --git a/app/api/parameters/route.ts b/app/api/parameters/route.ts index f133da8..8574274 100644 --- a/app/api/parameters/route.ts +++ b/app/api/parameters/route.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { client, getInfo, setSession } from '@/app/api/utils/common' diff --git a/app/api/utils/common.ts b/app/api/utils/common.ts index 109ee4b..b9209e4 100644 --- a/app/api/utils/common.ts +++ b/app/api/utils/common.ts @@ -1,4 +1,4 @@ -import { type NextRequest } from 'next/server' +import type { NextRequest } from 'next/server' import { ChatClient } from 'dify-client' import { v4 } from 'uuid' import { API_KEY, API_URL, APP_ID } from '@/config' diff --git a/app/components/app-unavailable.tsx b/app/components/app-unavailable.tsx index 495c8ce..59f970c 100644 --- a/app/components/app-unavailable.tsx +++ b/app/components/app-unavailable.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -type IAppUnavailableProps = { +interface IAppUnavailableProps { isUnknownReason: boolean errMessage?: string } @@ -14,8 +14,7 @@ const AppUnavailable: FC = ({ }) => { const { t } = useTranslation() let message = errMessage - if (!errMessage) - message = (isUnknownReason ? t('app.common.appUnkonwError') : t('app.common.appUnavailable')) as string + if (!errMessage) { message = (isUnknownReason ? t('app.common.appUnkonwError') : t('app.common.appUnavailable')) as string } return (
diff --git a/app/components/base/app-icon/index.tsx b/app/components/base/app-icon/index.tsx index 48e1608..e8a82a9 100644 --- a/app/components/base/app-icon/index.tsx +++ b/app/components/base/app-icon/index.tsx @@ -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 diff --git a/app/components/base/auto-height-textarea/index.tsx b/app/components/base/auto-height-textarea/index.tsx index ee015fb..6c33b62 100644 --- a/app/components/base/auto-height-textarea/index.tsx +++ b/app/components/base/auto-height-textarea/index.tsx @@ -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) => 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 ( diff --git a/app/components/base/button/index.tsx b/app/components/base/button/index.tsx index 518a849..2e49397 100644 --- a/app/components/base/button/index.tsx +++ b/app/components/base/button/index.tsx @@ -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 diff --git a/app/components/base/file-uploader-in-attachment/file-from-link-or-local/index.tsx b/app/components/base/file-uploader-in-attachment/file-from-link-or-local/index.tsx index 5da52a6..498d09e 100644 --- a/app/components/base/file-uploader-in-attachment/file-from-link-or-local/index.tsx +++ b/app/components/base/file-uploader-in-attachment/file-from-link-or-local/index.tsx @@ -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) diff --git a/app/components/base/file-uploader-in-attachment/file-image-render.tsx b/app/components/base/file-uploader-in-attachment/file-image-render.tsx index d613505..96150cf 100644 --- a/app/components/base/file-uploader-in-attachment/file-image-render.tsx +++ b/app/components/base/file-uploader-in-attachment/file-image-render.tsx @@ -1,6 +1,6 @@ import cn from '@/utils/classnames' -type FileImageRenderProps = { +interface FileImageRenderProps { imageUrl: string className?: string alt?: string diff --git a/app/components/base/file-uploader-in-attachment/file-input.tsx b/app/components/base/file-uploader-in-attachment/file-input.tsx index d4cca19..53be5a1 100644 --- a/app/components/base/file-uploader-in-attachment/file-input.tsx +++ b/app/components/base/file-uploader-in-attachment/file-input.tsx @@ -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 { diff --git a/app/components/base/file-uploader-in-attachment/file-item.tsx b/app/components/base/file-uploader-in-attachment/file-item.tsx index 4517251..d679d4e 100644 --- a/app/components/base/file-uploader-in-attachment/file-item.tsx +++ b/app/components/base/file-uploader-in-attachment/file-item.tsx @@ -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 diff --git a/app/components/base/file-uploader-in-attachment/file-type-icon.tsx b/app/components/base/file-uploader-in-attachment/file-type-icon.tsx index 08d0131..22c0337 100644 --- a/app/components/base/file-uploader-in-attachment/file-type-icon.tsx +++ b/app/components/base/file-uploader-in-attachment/file-type-icon.tsx @@ -67,7 +67,7 @@ const FILE_TYPE_ICON_MAP = { color: 'text-[#00B2EA]', }, } -type FileTypeIconProps = { +interface FileTypeIconProps { type: FileAppearanceType size?: 'sm' | 'lg' | 'md' className?: string diff --git a/app/components/base/file-uploader-in-attachment/hooks.ts b/app/components/base/file-uploader-in-attachment/hooks.ts index bd49b52..3983e03 100644 --- a/app/components/base/file-uploader-in-attachment/hooks.ts +++ b/app/components/base/file-uploader-in-attachment/hooks.ts @@ -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 { diff --git a/app/components/base/file-uploader-in-attachment/index.tsx b/app/components/base/file-uploader-in-attachment/index.tsx index 697f65f..92c79b3 100644 --- a/app/components/base/file-uploader-in-attachment/index.tsx +++ b/app/components/base/file-uploader-in-attachment/index.tsx @@ -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 diff --git a/app/components/base/file-uploader-in-attachment/store.tsx b/app/components/base/file-uploader-in-attachment/store.tsx index cddfdf6..3b7d80d 100644 --- a/app/components/base/file-uploader-in-attachment/store.tsx +++ b/app/components/base/file-uploader-in-attachment/store.tsx @@ -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(null) export function useStore(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(undefined) - if (!storeRef.current) - storeRef.current = createFileStore(value, onChange) + if (!storeRef.current) { storeRef.current = createFileStore(value, onChange) } return ( diff --git a/app/components/base/file-uploader-in-attachment/types.ts b/app/components/base/file-uploader-in-attachment/types.ts index 384355e..a0fa99c 100644 --- a/app/components/base/file-uploader-in-attachment/types.ts +++ b/app/components/base/file-uploader-in-attachment/types.ts @@ -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 diff --git a/app/components/base/file-uploader-in-attachment/utils.ts b/app/components/base/file-uploader-in-attachment/utils.ts index b63532e..3a0a22c 100644 --- a/app/components/base/file-uploader-in-attachment/utils.ts +++ b/app/components/base/file-uploader-in-attachment/utils.ts @@ -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) => { diff --git a/app/components/base/icons/IconBase.tsx b/app/components/base/icons/IconBase.tsx index 994cd98..452196d 100644 --- a/app/components/base/icons/IconBase.tsx +++ b/app/components/base/icons/IconBase.tsx @@ -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 diff --git a/app/components/base/icons/other/ReplayLine.tsx b/app/components/base/icons/other/ReplayLine.tsx index 29f7137..f8d1fbe 100644 --- a/app/components/base/icons/other/ReplayLine.tsx +++ b/app/components/base/icons/other/ReplayLine.tsx @@ -11,7 +11,7 @@ const Icon = ( ref, ...props }: React.SVGProps & { - ref?: React.RefObject>; + ref?: React.RefObject> }, ) => diff --git a/app/components/base/icons/utils.tsx b/app/components/base/icons/utils.tsx index 90d075f..a75f819 100644 --- a/app/components/base/icons/utils.tsx +++ b/app/components/base/icons/utils.tsx @@ -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 } diff --git a/app/components/base/image-gallery/index.tsx b/app/components/base/image-gallery/index.tsx index bd46c34..c8d32f4 100644 --- a/app/components/base/image-gallery/index.tsx +++ b/app/components/base/image-gallery/index.tsx @@ -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 })() diff --git a/app/components/base/image-uploader/chat-image-uploader.tsx b/app/components/base/image-uploader/chat-image-uploader.tsx index 863b79d..d84d89f 100644 --- a/app/components/base/image-uploader/chat-image-uploader.tsx +++ b/app/components/base/image-uploader/chat-image-uploader.tsx @@ -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 = ({ ) } -type UploaderButtonProps = { +interface UploaderButtonProps { methods: VisionSettings['transfer_methods'] onUpload: (imageFile: ImageFile) => void disabled?: boolean @@ -62,8 +62,7 @@ const UploaderButton: FC = ({ } const handleToggle = () => { - if (disabled) - return + if (disabled) { return } setOpen(v => !v) } @@ -115,7 +114,7 @@ const UploaderButton: FC = ({ ) } -type ChatImageUploaderProps = { +interface ChatImageUploaderProps { settings: VisionSettings onUpload: (imageFile: ImageFile) => void disabled?: boolean diff --git a/app/components/base/image-uploader/image-link-input.tsx b/app/components/base/image-uploader/image-link-input.tsx index 6c1435d..d7b9a50 100644 --- a/app/components/base/image-uploader/image-link-input.tsx +++ b/app/components/base/image-uploader/image-link-input.tsx @@ -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):\/\// diff --git a/app/components/base/image-uploader/image-list.tsx b/app/components/base/image-uploader/image-list.tsx index 2c0f35e..971391a 100644 --- a/app/components/base/image-uploader/image-list.tsx +++ b/app/components/base/image-uploader/image-list.tsx @@ -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 = ({ 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 ( diff --git a/app/components/base/image-uploader/image-preview.tsx b/app/components/base/image-uploader/image-preview.tsx index c188ddd..674852e 100644 --- a/app/components/base/image-uploader/image-preview.tsx +++ b/app/components/base/image-uploader/image-preview.tsx @@ -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 } diff --git a/app/components/base/image-uploader/uploader.tsx b/app/components/base/image-uploader/uploader.tsx index fe5d829..ce13296 100644 --- a/app/components/base/image-uploader/uploader.tsx +++ b/app/components/base/image-uploader/uploader.tsx @@ -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 = ({ const handleChange = (e: ChangeEvent) => { 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 }) }) diff --git a/app/components/base/image-uploader/utils.ts b/app/components/base/image-uploader/utils.ts index ff3fa92..03af5a4 100644 --- a/app/components/base/image-uploader/utils.ts +++ b/app/components/base/image-uploader/utils.ts @@ -2,7 +2,7 @@ import { upload } from '@/service/base' -type ImageUploadParams = { +interface ImageUploadParams { file: File onProgressCallback: (progress: number) => void onSuccessCallback: (res: { id: string }) => void diff --git a/app/components/base/loading/index.tsx b/app/components/base/loading/index.tsx index c6c4800..d23ad9c 100644 --- a/app/components/base/loading/index.tsx +++ b/app/components/base/loading/index.tsx @@ -2,7 +2,7 @@ import React from 'react' import './style.css' -type ILoadingProps = { +interface ILoadingProps { type?: 'area' | 'app' } const Loading = ( diff --git a/app/components/base/portal-to-follow-elem/index.tsx b/app/components/base/portal-to-follow-elem/index.tsx index c117441..e3cc114 100644 --- a/app/components/base/portal-to-follow-elem/index.tsx +++ b/app/components/base/portal-to-follow-elem/index.tsx @@ -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(null) export function usePortalToFollowElemContext() { const context = React.useContext(PortalToFollowElemContext) - if (context == null) - throw new Error('PortalToFollowElem components must be wrapped in ') + if (context == null) { throw new Error('PortalToFollowElem components must be wrapped in ') } return context } @@ -106,7 +105,7 @@ export function PortalToFollowElem({ } export const PortalToFollowElemTrigger = React.forwardRef< -HTMLElement, + HTMLElement, React.HTMLProps & { asChild?: boolean } >(({ children, asChild = false, ...props }, propRef) => { const context = usePortalToFollowElemContext() @@ -141,14 +140,13 @@ React.HTMLProps & { asChild?: boolean } PortalToFollowElemTrigger.displayName = 'PortalToFollowElemTrigger' export const PortalToFollowElemContent = React.forwardRef< -HTMLDivElement, -React.HTMLProps + HTMLDivElement, + React.HTMLProps >(({ style, ...props }, propRef) => { const context = usePortalToFollowElemContext() const ref = useMergeRefs([context.refs.setFloating, propRef]) - if (!context.open) - return null + if (!context.open) { return null } return ( diff --git a/app/components/base/progress-bar/index.tsx b/app/components/base/progress-bar/index.tsx index 759c9ea..ca15600 100644 --- a/app/components/base/progress-bar/index.tsx +++ b/app/components/base/progress-bar/index.tsx @@ -1,4 +1,4 @@ -type ProgressBarProps = { +interface ProgressBarProps { percent: number } const ProgressBar = ({ diff --git a/app/components/base/progress-bar/progress-circle.tsx b/app/components/base/progress-bar/progress-circle.tsx index b9b280e..c2a63fa 100644 --- a/app/components/base/progress-bar/progress-circle.tsx +++ b/app/components/base/progress-bar/progress-circle.tsx @@ -1,7 +1,7 @@ import { memo } from 'react' import cn from '@/utils/classnames' -type ProgressCircleProps = { +interface ProgressCircleProps { className?: string percentage?: number size?: number diff --git a/app/components/base/select/index.tsx b/app/components/base/select/index.tsx index 6f90516..c5aab21 100644 --- a/app/components/base/select/index.tsx +++ b/app/components/base/select/index.tsx @@ -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 = ({ 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 = ({ ? { - if (!disabled) - setQuery(event.target.value) + if (!disabled) { setQuery(event.target.value) } }} displayValue={(item: Item) => item?.name} /> : { - 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} } { - if (!disabled) - setOpen(!open) + if (!disabled) { setOpen(!open) } } }> {open ? : } @@ -147,8 +143,7 @@ const SimpleSelect: FC = ({ useEffect(() => { let defaultSelect = null const existed = items.find((item: Item) => item.value === defaultValue) - if (existed) - defaultSelect = existed + if (existed) { defaultSelect = existed } setSelectedItem(defaultSelect) }, [defaultValue]) diff --git a/app/components/base/spinner/index.tsx b/app/components/base/spinner/index.tsx index 53de4ed..367fdc7 100644 --- a/app/components/base/spinner/index.tsx +++ b/app/components/base/spinner/index.tsx @@ -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 diff --git a/app/components/base/streamdown-markdown.tsx b/app/components/base/streamdown-markdown.tsx new file mode 100644 index 0000000..8732418 --- /dev/null +++ b/app/components/base/streamdown-markdown.tsx @@ -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 ( +
+ {content} +
+ ) +} + +export default StreamdownMarkdown diff --git a/app/components/base/toast/index.tsx b/app/components/base/toast/index.tsx index 8032318..8fdb29f 100644 --- a/app/components/base/toast/index.tsx +++ b/app/components/base/toast/index.tsx @@ -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
) document.body.appendChild(holder) setTimeout(() => { - if (holder) - holder.remove() + if (holder) { holder.remove() } }, duration || defaultDuring) } } diff --git a/app/components/base/tooltip-plus/index.tsx b/app/components/base/tooltip-plus/index.tsx index 6fc7915..68c8ec5 100644 --- a/app/components/base/tooltip-plus/index.tsx +++ b/app/components/base/tooltip-plus/index.tsx @@ -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 = ( ) -const Tooltip: FC< TooltipProps> = ({ +const Tooltip: FC = ({ position = 'top', triggerMethod = 'hover', popupContent, diff --git a/app/components/base/tooltip/index.tsx b/app/components/base/tooltip/index.tsx index 610f17b..54fc51d 100644 --- a/app/components/base/tooltip/index.tsx +++ b/app/components/base/tooltip/index.tsx @@ -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 diff --git a/app/components/chat/answer/index.tsx b/app/components/chat/answer/index.tsx index 2bce5b9..56733a4 100644 --- a/app/components/chat/answer/index.tsx +++ b/app/components/chat/answer/index.tsx @@ -1,30 +1,32 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' -import { useTranslation } from 'react-i18next' -import LoadingAnim from '../loading-anim' import type { FeedbackFunc } from '../type' -import s from '../style.module.css' -import ImageGallery from '../../base/image-gallery' -import Thought from '../thought' -import { randomString } from '@/utils/string' import type { ChatItem, MessageRating, VisionFile } from '@/types/app' +import type { Emoji } from '@/types/tools' +import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import StreamdownMarkdown from '@/app/components/base/streamdown-markdown' import Tooltip from '@/app/components/base/tooltip' import WorkflowProcess from '@/app/components/workflow/workflow-process' -import { Markdown } from '@/app/components/base/markdown' -import Button from '@/app/components/base/button' -import type { Emoji } from '@/types/tools' +import { randomString } from '@/utils/string' +import ImageGallery from '../../base/image-gallery' +import LoadingAnim from '../loading-anim' +import s from '../style.module.css' +import Thought from '../thought' -const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => ( -
- {innerContent} -
-) +function OperationBtn({ innerContent, onClick, className }: { innerContent: React.ReactNode, onClick?: () => void, className?: string }) { + return ( +
+ {innerContent} +
+ ) +} const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => ( @@ -33,29 +35,35 @@ const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => ( ) const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => { - return isLike ? : + return isLike ? : } const EditIcon: FC<{ className?: string }> = ({ className }) => { - return - - + return ( + + + + ) } export const EditIconSolid: FC<{ className?: string }> = ({ className }) => { - return - - - + return ( + + + + + ) } const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => { - return
- {children} -
+ return ( +
+ {children} +
+ ) } -type IAnswerProps = { +interface IAnswerProps { item: ChatItem feedbackDisabled: boolean onFeedback?: FeedbackFunc @@ -79,15 +87,14 @@ const Answer: FC = ({ const { t } = useTranslation() /** - * Render feedback results (distinguish between users and administrators) - * User reviews cannot be cancelled in Console - * @param rating feedback result - * @param isUserFeedback Whether it is user's feedback - * @returns comp - */ + * Render feedback results (distinguish between users and administrators) + * User reviews cannot be cancelled in Console + * @param rating feedback result + * @param isUserFeedback Whether it is user's feedback + * @returns comp + */ const renderFeedbackRating = (rating: MessageRating | undefined) => { - if (!rating) - return null + if (!rating) { return null } const isLike = rating === 'like' const ratingIconClassname = isLike ? 'text-primary-600 bg-primary-100 hover:bg-primary-200' : 'text-red-600 bg-red-100 hover:bg-red-200' @@ -98,7 +105,7 @@ const Answer: FC = ({ content={isLike ? '取消赞同' : '取消反对'} >
{ await onFeedback?.(id, { rating: null }) @@ -120,14 +127,16 @@ const Answer: FC = ({ const userOperation = () => { return feedback?.rating ? null - :
- - {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'like' }) })} - - - {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'dislike' }) })} - -
+ : ( +
+ + {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'like' }) })} + + + {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'dislike' }) })} + +
+ ) } return ( @@ -138,8 +147,7 @@ const Answer: FC = ({ } const getImgs = (list?: VisionFile[]) => { - if (!list) - return [] + if (!list) { return [] } return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant') } @@ -148,7 +156,7 @@ const Answer: FC = ({ {agent_thoughts?.map((item, index) => (
{item.thought && ( - + )} {/* {item.tool} */} {/* perhaps not use tool */} @@ -170,13 +178,14 @@ const Answer: FC = ({ return (
-
+
{isResponding - &&
- -
- } + && ( +
+ +
+ )}
@@ -186,28 +195,28 @@ const Answer: FC = ({ )} {(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content)) ? ( -
- +
+
) : (isAgentMode ? agentModeAnswer : ( - + ))} {suggestedQuestions.length > 0 && ( -
-
+
+
{suggestedQuestions.map((suggestion, index) => ( -
- +
+
))}
)}
-
+
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()} {/* User feedback must be displayed */} {!feedbackDisabled && renderFeedbackRating(feedback?.rating)} diff --git a/app/components/chat/index.tsx b/app/components/chat/index.tsx index fc67d85..ee4fabc 100644 --- a/app/components/chat/index.tsx +++ b/app/components/chat/index.tsx @@ -19,7 +19,7 @@ import FileUploaderInAttachmentWrapper from '@/app/components/base/file-uploader import type { FileEntity, FileUpload } from '@/app/components/base/file-uploader-in-attachment/types' import { getProcessedFiles } from '@/app/components/base/file-uploader-in-attachment/utils' -export type IChatProps = { +export interface IChatProps { chatList: ChatItem[] /** * Whether to display the editing area and rating status @@ -97,8 +97,7 @@ const Chat: FC = ({ const [attachmentFiles, setAttachmentFiles] = React.useState([]) const handleSend = () => { - if (!valid() || (checkCanSend && !checkCanSend())) - return + if (!valid() || (checkCanSend && !checkCanSend())) { return } const imageFiles: VisionFile[] = files.filter(file => file.progress !== -1).map(fileItem => ({ type: 'image', transfer_method: fileItem.type, @@ -109,23 +108,20 @@ const Chat: FC = ({ const combinedFiles: VisionFile[] = [...imageFiles, ...docAndOtherFiles] onSend(queryRef.current, combinedFiles) if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) { - if (files.length) - onClear() + if (files.length) { onClear() } if (!isResponding) { setQuery('') queryRef.current = '' } } - if (!attachmentFiles.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId)) - setAttachmentFiles([]) + if (!attachmentFiles.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId)) { setAttachmentFiles([]) } } const handleKeyUp = (e: any) => { if (e.code === 'Enter') { e.preventDefault() // prevent send message when using input method enter - if (!e.shiftKey && !isUseInputMethod.current) - handleSend() + if (!e.shiftKey && !isUseInputMethod.current) { handleSend() } } } @@ -212,7 +208,7 @@ const Chat: FC = ({ }