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 9c0404b..2e4c029 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ yarn.lock # pmpm pnpm-lock.yaml + +# mcp +.serena \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..578eab2 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm eslint-fix 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..7262c77 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() } } } diff --git a/app/components/chat/loading-anim/index.tsx b/app/components/chat/loading-anim/index.tsx index 09f8a54..885be61 100644 --- a/app/components/chat/loading-anim/index.tsx +++ b/app/components/chat/loading-anim/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React from 'react' import s from './style.module.css' -export type ILoaidingAnimProps = { +export interface ILoaidingAnimProps { type: 'text' | 'avatar' } diff --git a/app/components/chat/thought/index.tsx b/app/components/chat/thought/index.tsx index 6a893b1..64cc89c 100644 --- a/app/components/chat/thought/index.tsx +++ b/app/components/chat/thought/index.tsx @@ -5,7 +5,7 @@ import type { ThoughtItem, ToolInfoInThought } from '../type' import Tool from './tool' import type { Emoji } from '@/types/tools' -export type IThoughtProps = { +export interface IThoughtProps { thought: ThoughtItem allToolIcons: Record isFinished: boolean @@ -29,8 +29,7 @@ const Thought: FC = ({ }) => { const [toolNames, isValueArray]: [string[], boolean] = (() => { try { - if (Array.isArray(JSON.parse(thought.tool))) - return [JSON.parse(thought.tool), true] + if (Array.isArray(JSON.parse(thought.tool))) { return [JSON.parse(thought.tool), true] } } catch (e) { } diff --git a/app/components/chat/thought/panel.tsx b/app/components/chat/thought/panel.tsx index 2b0f1f9..b1dc5cd 100644 --- a/app/components/chat/thought/panel.tsx +++ b/app/components/chat/thought/panel.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -type Props = { +interface Props { isRequest: boolean toolName: string content: string diff --git a/app/components/chat/thought/tool.tsx b/app/components/chat/thought/tool.tsx index d52728f..618ea5d 100644 --- a/app/components/chat/thought/tool.tsx +++ b/app/components/chat/thought/tool.tsx @@ -13,17 +13,15 @@ import DataSetIcon from '@/app/components/base/icons/public/data-set' import type { Emoji } from '@/types/tools' import AppIcon from '@/app/components/base/app-icon' -type Props = { +interface Props { payload: ToolInfoInThought allToolIcons?: Record } const getIcon = (toolName: string, allToolIcons: Record) => { - if (toolName.startsWith('dataset-')) - return + if (toolName.startsWith('dataset-')) { return } const icon = allToolIcons[toolName] - if (!icon) - return null + if (!icon) { return null } return ( typeof icon === 'string' ? ( @@ -87,12 +85,14 @@ const Tool: FC = ({ + content={input} + /> {output && ( + content={output as string} + /> )}
)} diff --git a/app/components/chat/type.ts b/app/components/chat/type.ts index 7bbc72b..537d439 100644 --- a/app/components/chat/type.ts +++ b/app/components/chat/type.ts @@ -1,6 +1,6 @@ import type { VisionFile } from '@/types/app' -export type LogAnnotation = { +export interface LogAnnotation { content: string account: { id: string @@ -10,7 +10,7 @@ export type LogAnnotation = { created_at: number } -export type Annotation = { +export interface Annotation { id: string authorName: string logAnnotation?: LogAnnotation @@ -20,13 +20,13 @@ export type Annotation = { export const MessageRatings = ['like', 'dislike', null] as const export type MessageRating = typeof MessageRatings[number] -export type MessageMore = { +export interface MessageMore { time: string tokens: number latency: number | string } -export type Feedbacktype = { +export interface Feedbacktype { rating: MessageRating content?: string | null } @@ -36,14 +36,14 @@ export type SubmitAnnotationFunc = (messageId: string, content: string) => Promi export type DisplayScene = 'web' | 'console' -export type ToolInfoInThought = { +export interface ToolInfoInThought { name: string input: string output: string isFinished: boolean } -export type ThoughtItem = { +export interface ThoughtItem { id: string tool: string // plugin or dataset. May has multi. thought: string @@ -55,7 +55,7 @@ export type ThoughtItem = { message_files?: VisionFile[] } -export type CitationItem = { +export interface CitationItem { content: string data_source_type: string dataset_name: string @@ -70,7 +70,7 @@ export type CitationItem = { word_count: number } -export type IChatItem = { +export interface IChatItem { id: string content: string citation?: CitationItem[] @@ -98,12 +98,12 @@ export type IChatItem = { useCurrentUserAvatar?: boolean isOpeningStatement?: boolean suggestedQuestions?: string[] - log?: { role: string; text: string }[] + log?: { role: string, text: string }[] agent_thoughts?: ThoughtItem[] message_files?: VisionFile[] } -export type MessageEnd = { +export interface MessageEnd { id: string metadata: { retriever_resources?: CitationItem[] @@ -117,14 +117,14 @@ export type MessageEnd = { } } -export type MessageReplace = { +export interface MessageReplace { id: string task_id: string answer: string conversation_id: string } -export type AnnotationReply = { +export interface AnnotationReply { id: string task_id: string answer: string diff --git a/app/components/header.tsx b/app/components/header.tsx index 2513696..18b7dd7 100644 --- a/app/components/header.tsx +++ b/app/components/header.tsx @@ -5,7 +5,7 @@ import { PencilSquareIcon, } from '@heroicons/react/24/solid' import AppIcon from '@/app/components/base/app-icon' -export type IHeaderProps = { +export interface IHeaderProps { title: string isMobile?: boolean onShowSideBar?: () => void @@ -35,9 +35,7 @@ const Header: FC = ({
{isMobile ? ( -
onCreateNewChat?.()} - > +
onCreateNewChat?.()} >
) :
} diff --git a/app/components/index.tsx b/app/components/index.tsx index 788a01b..76b7700 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ 'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' @@ -24,7 +23,7 @@ import { API_KEY, APP_ID, APP_INFO, isShowPrompt, promptTemplate } from '@/confi import type { Annotation as AnnotationType } from '@/types/log' import { addFileInfos, sortAgentSorts } from '@/utils/tools' -export type IMainProps = { +export interface IMainProps { params: any } @@ -52,8 +51,7 @@ const Main: FC = () => { const [fileConfig, setFileConfig] = useState() useEffect(() => { - if (APP_INFO?.title) - document.title = `${APP_INFO.title} - Powered by Dify` + if (APP_INFO?.title) { document.title = `${APP_INFO.title} - Powered by Dify` } }, [APP_INFO?.title]) // onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576 @@ -95,8 +93,7 @@ const Main: FC = () => { setChatList(generateNewChatListWithOpenStatement('', inputs)) } const hasSetInputs = (() => { - if (!isNewConversation) - return true + if (!isNewConversation) { return true } return isChatStarted })() @@ -106,8 +103,7 @@ const Main: FC = () => { const suggestedQuestions = currConversationInfo?.suggested_questions || [] const handleConversationSwitch = () => { - if (!inited) - return + if (!inited) { return } // update inputs of current conversation let notSyncToStateIntroduction = '' @@ -155,8 +151,7 @@ const Main: FC = () => { }) } - if (isNewConversation && isChatStarted) - setChatList(generateNewChatListWithOpenStatement()) + if (isNewConversation && isChatStarted) { setChatList(generateNewChatListWithOpenStatement()) } } useEffect(handleConversationSwitch, [currConversationId, inited]) @@ -180,15 +175,13 @@ const Main: FC = () => { const chatListDomRef = useRef(null) useEffect(() => { // scroll to bottom - if (chatListDomRef.current) - chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight + if (chatListDomRef.current) { chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight } }, [chatList, currConversationId]) // user can not edit inputs if user had send message const canEditInputs = !chatList.some(item => item.isAnswer === false) && isNewConversation const createNewChat = () => { // if new chat is already exist, do not create new chat - if (conversationList.some(item => item.id === '-1')) - return + if (conversationList.some(item => item.id === '-1')) { return } setConversationList(produce(conversationList, (draft) => { draft.unshift({ @@ -205,8 +198,7 @@ const Main: FC = () => { const generateNewChatListWithOpenStatement = (introduction?: string, inputs?: Record | null) => { let calculatedIntroduction = introduction || conversationIntroduction || '' const calculatedPromptVariables = inputs || currInputs || null - if (calculatedIntroduction && calculatedPromptVariables) - calculatedIntroduction = replaceVarWithValues(calculatedIntroduction, promptConfig?.prompt_variables || [], calculatedPromptVariables) + if (calculatedIntroduction && calculatedPromptVariables) { calculatedIntroduction = replaceVarWithValues(calculatedIntroduction, promptConfig?.prompt_variables || [], calculatedPromptVariables) } const openStatement = { id: `${Date.now()}`, @@ -214,10 +206,9 @@ const Main: FC = () => { isAnswer: true, feedbackDisabled: true, isOpeningStatement: isShowPrompt, - suggestedQuestions: suggestedQuestions, + suggestedQuestions, } - if (calculatedIntroduction) - return [openStatement] + if (calculatedIntroduction) { return [openStatement] } return [] } @@ -232,7 +223,7 @@ const Main: FC = () => { try { const [conversationData, appParams] = await Promise.all([fetchConversations(), fetchAppParams()]) // handle current conversation id - const { data: conversations, error } = conversationData as { data: ConversationItem[]; error: string } + const { data: conversations, error } = conversationData as { data: ConversationItem[], error: string } if (error) { Toast.notify({ type: 'error', message: error }) throw new Error(error) @@ -248,13 +239,13 @@ const Main: FC = () => { setNewConversationInfo({ name: t('app.chat.newChatDefaultName'), introduction, - suggested_questions + suggested_questions, }) if (isNotNewConversation) { setExistConversationInfo({ name: currentConversation.name || t('app.chat.newChatDefaultName'), introduction, - suggested_questions + suggested_questions, }) } const prompt_variables = userInputsFormToPromptVariables(user_input_form) @@ -278,8 +269,7 @@ const Main: FC = () => { }) setConversationList(conversations as ConversationItem[]) - if (isNotNewConversation) - setCurrConversationId(_conversationId, APP_ID, false) + if (isNotNewConversation) { setCurrConversationId(_conversationId, APP_ID, false) } setInited(true) } @@ -303,11 +293,9 @@ const Main: FC = () => { } const checkCanSend = () => { - if (currConversationId !== '-1') - return true + if (currConversationId !== '-1') { return true } - if (!currInputs || !promptConfig?.prompt_variables) - return true + if (!currInputs || !promptConfig?.prompt_variables) { return true } const inputLens = Object.values(currInputs).length const promptVariablesLens = promptConfig.prompt_variables.length @@ -342,11 +330,11 @@ const Main: FC = () => { const newListWithAnswer = produce( getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), (draft) => { - if (!draft.find(item => item.id === questionId)) - draft.push({ ...questionItem }) + if (!draft.find(item => item.id === questionId)) { draft.push({ ...questionItem }) } draft.push({ ...responseItem }) - }) + }, + ) setChatList(newListWithAnswer) } @@ -368,14 +356,11 @@ const Main: FC = () => { if (currInputs) { Object.keys(currInputs).forEach((key) => { const value = currInputs[key] - if (value.supportFileType) - toServerInputs[key] = transformToServerFile(value) + if (value.supportFileType) { toServerInputs[key] = transformToServerFile(value) } - else if (value[0]?.supportFileType) - toServerInputs[key] = value.map((item: any) => transformToServerFile(item)) + else if (value[0]?.supportFileType) { toServerInputs[key] = value.map((item: any) => transformToServerFile(item)) } - else - toServerInputs[key] = value + else { toServerInputs[key] = value } }) } @@ -442,16 +427,14 @@ const Main: FC = () => { } else { const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1] - if (lastThought) - lastThought.thought = lastThought.thought + message // need immer setAutoFreeze + if (lastThought) { lastThought.thought = lastThought.thought + message } // need immer setAutoFreeze } if (messageId && !hasSetResponseId) { responseItem.id = messageId hasSetResponseId = true } - if (isFirstMessage && newConversationId) - tempNewConversationId = newConversationId + if (isFirstMessage && newConversationId) { tempNewConversationId = newConversationId } setMessageTaskId(taskId) // has switched to other conversation @@ -467,8 +450,7 @@ const Main: FC = () => { }) }, async onCompleted(hasError?: boolean) { - if (hasError) - return + if (hasError) { return } if (getConversationIdChangeBecauseOfNew()) { const { data: allConversations }: any = await fetchConversations() @@ -487,8 +469,7 @@ const Main: FC = () => { }, onFile(file) { const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1] - if (lastThought) - lastThought.message_files = [...(lastThought as any).message_files, { ...file }] + if (lastThought) { lastThought.message_files = [...(lastThought as any).message_files, { ...file }] } updateCurrentQA({ responseItem, @@ -543,13 +524,13 @@ const Main: FC = () => { const newListWithAnswer = produce( getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), (draft) => { - if (!draft.find(item => item.id === questionId)) - draft.push({ ...questionItem }) + if (!draft.find(item => item.id === questionId)) { draft.push({ ...questionItem }) } draft.push({ ...responseItem, }) - }) + }, + ) setChatList(newListWithAnswer) return } @@ -558,11 +539,11 @@ const Main: FC = () => { const newListWithAnswer = produce( getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), (draft) => { - if (!draft.find(item => item.id === questionId)) - draft.push({ ...questionItem }) + if (!draft.find(item => item.id === questionId)) { draft.push({ ...questionItem }) } draft.push({ ...responseItem }) - }) + }, + ) setChatList(newListWithAnswer) }, onMessageReplace: (messageReplace) => { @@ -571,8 +552,7 @@ const Main: FC = () => { (draft) => { const current = draft.find(item => item.id === messageReplace.id) - if (current) - current.content = messageReplace.answer + if (current) { current.content = messageReplace.answer } }, )) }, @@ -648,8 +628,7 @@ const Main: FC = () => { } const renderSidebar = () => { - if (!APP_ID || !APP_INFO || !promptConfig) - return null + if (!APP_ID || !APP_INFO || !promptConfig) { return null } return ( = () => { ) } - if (appUnavailable) - return + if (appUnavailable) { return } - if (!APP_ID || !APP_INFO || !promptConfig) - return + if (!APP_ID || !APP_INFO || !promptConfig) { return } return (
@@ -678,10 +655,7 @@ const Main: FC = () => { {/* sidebar */} {!isMobile && renderSidebar()} {isMobile && isShowSidebar && ( -
+
e.stopPropagation()}> {renderSidebar()}
diff --git a/app/components/sidebar/card.tsx b/app/components/sidebar/card.tsx index 5803cc3..458c5ed 100644 --- a/app/components/sidebar/card.tsx +++ b/app/components/sidebar/card.tsx @@ -2,7 +2,7 @@ import React from 'react' import { useTranslation } from 'react-i18next' import s from './card.module.css' -type PropType = { +interface PropType { children: React.ReactNode text?: string } diff --git a/app/components/sidebar/index.tsx b/app/components/sidebar/index.tsx index 5a40553..e10baf5 100644 --- a/app/components/sidebar/index.tsx +++ b/app/components/sidebar/index.tsx @@ -16,7 +16,7 @@ function classNames(...classes: any[]) { const MAX_CONVERSATION_LENTH = 20 -export type ISidebarProps = { +export interface ISidebarProps { copyRight: string currentId: string onCurrentIdChange: (id: string) => void @@ -38,7 +38,8 @@ const Sidebar: FC = ({
diff --git a/app/components/value-panel/index.tsx b/app/components/value-panel/index.tsx index 1a38bd9..8df142c 100644 --- a/app/components/value-panel/index.tsx +++ b/app/components/value-panel/index.tsx @@ -7,7 +7,7 @@ import s from './style.module.css' import { StarIcon } from '@/app/components//welcome/massive-component' import Button from '@/app/components/base/button' -export type ITemplateVarPanelProps = { +export interface ITemplateVarPanelProps { className?: string header: ReactNode children?: ReactNode | null @@ -38,7 +38,7 @@ const TemplateVarPanel: FC = ({ ) } -export const PanelTitle: FC<{ title: string; className?: string }> = ({ +export const PanelTitle: FC<{ title: string, className?: string }> = ({ title, className, }) => { @@ -50,7 +50,7 @@ export const PanelTitle: FC<{ title: string; className?: string }> = ({ ) } -export const VarOpBtnGroup: FC<{ className?: string; onConfirm: () => void; onCancel: () => void }> = ({ +export const VarOpBtnGroup: FC<{ className?: string, onConfirm: () => void, onCancel: () => void }> = ({ className, onConfirm, onCancel, diff --git a/app/components/welcome/index.tsx b/app/components/welcome/index.tsx index b795261..f55d22e 100644 --- a/app/components/welcome/index.tsx +++ b/app/components/welcome/index.tsx @@ -14,7 +14,7 @@ import { DEFAULT_VALUE_MAX_LEN } from '@/config' // regex to match the {{}} and replace it with a span const regex = /\{\{([^}]+)\}\}/g -export type IWelcomeProps = { +export interface IWelcomeProps { conversationName: string hasSetInputs: boolean isPublicVersion: boolean @@ -42,8 +42,7 @@ const Welcome: FC = ({ const hasVar = promptConfig.prompt_variables.length > 0 const [isFold, setIsFold] = useState(true) const [inputs, setInputs] = useState>((() => { - if (hasSetInputs) - return savedInputs + if (hasSetInputs) { return savedInputs } const res: Record = {} if (promptConfig) { @@ -69,8 +68,7 @@ const Welcome: FC = ({ }, [savedInputs]) const highLightPromoptTemplate = (() => { - if (!promptConfig) - return '' + if (!promptConfig) { return '' } const res = promptConfig.prompt_template.replace(regex, (match, p1) => { return `${inputs?.[p1] ? inputs?.[p1] : match}` }) @@ -189,8 +187,7 @@ const Welcome: FC = ({ } const handleChat = () => { - if (!canChat()) - return + if (!canChat()) { return } onStartChat(inputs) } @@ -251,8 +248,7 @@ const Welcome: FC = ({ return ( { - if (!canChat()) - return + if (!canChat()) { return } onInputsChange(inputs) setIsFold(true) @@ -309,8 +305,7 @@ const Welcome: FC = ({ } const renderHasSetInputsPrivate = () => { - if (!canEditInputs || !hasVar) - return null + if (!canEditInputs || !hasVar) { return null } return ( = ({ } const renderHasSetInputs = () => { - if ((!isPublicVersion && !canEditInputs) || !hasVar) - return null + if ((!isPublicVersion && !canEditInputs) || !hasVar) { return null } return (
= ({ {t('app.chat.privacyPolicyMiddle')} + target='_blank' + >{t('app.chat.privacyPolicyMiddle')} {t('app.chat.privacyPolicyRight')}
:
diff --git a/app/components/welcome/massive-component.tsx b/app/components/welcome/massive-component.tsx index 41240bd..d3f5486 100644 --- a/app/components/welcome/massive-component.tsx +++ b/app/components/welcome/massive-component.tsx @@ -37,7 +37,7 @@ export const StarIcon = () => ( ) -export const ChatBtn: FC<{ onClick: () => void; className?: string }> = ({ +export const ChatBtn: FC<{ onClick: () => void, className?: string }> = ({ className, onClick, }) => { @@ -46,7 +46,8 @@ export const ChatBtn: FC<{ onClick: () => void; className?: string }> = ({