feat: migrate ESLint to v9 flat config

- Replace .eslintrc.json with eslint.config.mjs
- Simplify configuration using @antfu/eslint-config
- Add necessary ESLint plugin dependencies
- Disable overly strict style rules
- Set package.json type to module for ESM support
- Fix ESLint disable comment format

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
lyzno1
2025-09-10 22:21:17 +08:00
parent 2b1882a5e3
commit 05dcfcf0ca
85 changed files with 464 additions and 502 deletions

View File

@ -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 }) => (
<div
className={`relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800 ${className ?? ''}`}
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
onClick={onClick && onClick}
>
{innerContent}
</div>
)
function OperationBtn({ innerContent, onClick, className }: { innerContent: React.ReactNode, onClick?: () => void, className?: string }) {
return (
<div
className={`relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800 ${className ?? ''}`}
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
onClick={onClick && onClick}
>
{innerContent}
</div>
)
}
const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => (
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -33,29 +35,35 @@ const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => (
)
const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => {
return isLike ? <HandThumbUpIcon className='w-4 h-4' /> : <HandThumbDownIcon className='w-4 h-4' />
return isLike ? <HandThumbUpIcon className="w-4 h-4" /> : <HandThumbDownIcon className="w-4 h-4" />
}
const EditIcon: FC<{ className?: string }> = ({ className }) => {
return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M14 11.9998L13.3332 12.7292C12.9796 13.1159 12.5001 13.3332 12.0001 13.3332C11.5001 13.3332 11.0205 13.1159 10.6669 12.7292C10.3128 12.3432 9.83332 12.1265 9.33345 12.1265C8.83359 12.1265 8.35409 12.3432 7.99998 12.7292M2 13.3332H3.11636C3.44248 13.3332 3.60554 13.3332 3.75899 13.2963C3.89504 13.2637 4.0251 13.2098 4.1444 13.1367C4.27895 13.0542 4.39425 12.9389 4.62486 12.7083L13 4.33316C13.5523 3.78087 13.5523 2.88544 13 2.33316C12.4477 1.78087 11.5523 1.78087 11 2.33316L2.62484 10.7083C2.39424 10.9389 2.27894 11.0542 2.19648 11.1888C2.12338 11.3081 2.0695 11.4381 2.03684 11.5742C2 11.7276 2 11.8907 2 12.2168V13.3332Z" stroke="#6B7280" strokeLinecap="round" strokeLinejoin="round" />
</svg>
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M14 11.9998L13.3332 12.7292C12.9796 13.1159 12.5001 13.3332 12.0001 13.3332C11.5001 13.3332 11.0205 13.1159 10.6669 12.7292C10.3128 12.3432 9.83332 12.1265 9.33345 12.1265C8.83359 12.1265 8.35409 12.3432 7.99998 12.7292M2 13.3332H3.11636C3.44248 13.3332 3.60554 13.3332 3.75899 13.2963C3.89504 13.2637 4.0251 13.2098 4.1444 13.1367C4.27895 13.0542 4.39425 12.9389 4.62486 12.7083L13 4.33316C13.5523 3.78087 13.5523 2.88544 13 2.33316C12.4477 1.78087 11.5523 1.78087 11 2.33316L2.62484 10.7083C2.39424 10.9389 2.27894 11.0542 2.19648 11.1888C2.12338 11.3081 2.0695 11.4381 2.03684 11.5742C2 11.7276 2 11.8907 2 12.2168V13.3332Z" stroke="#6B7280" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}
export const EditIconSolid: FC<{ className?: string }> = ({ className }) => {
return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path fillRule="evenodd" clip-rule="evenodd" d="M10.8374 8.63108C11.0412 8.81739 11.0554 9.13366 10.8691 9.33747L10.369 9.88449C10.0142 10.2725 9.52293 10.5001 9.00011 10.5001C8.47746 10.5001 7.98634 10.2727 7.63157 9.8849C7.45561 9.69325 7.22747 9.59515 7.00014 9.59515C6.77271 9.59515 6.54446 9.69335 6.36846 9.88517C6.18177 10.0886 5.86548 10.1023 5.66201 9.91556C5.45853 9.72888 5.44493 9.41259 5.63161 9.20911C5.98678 8.82201 6.47777 8.59515 7.00014 8.59515C7.52251 8.59515 8.0135 8.82201 8.36867 9.20911L8.36924 9.20974C8.54486 9.4018 8.77291 9.50012 9.00011 9.50012C9.2273 9.50012 9.45533 9.40182 9.63095 9.20979L10.131 8.66276C10.3173 8.45895 10.6336 8.44476 10.8374 8.63108Z" fill="#6B7280" />
<path fillRule="evenodd" clip-rule="evenodd" d="M7.89651 1.39656C8.50599 0.787085 9.49414 0.787084 10.1036 1.39656C10.7131 2.00604 10.7131 2.99419 10.1036 3.60367L3.82225 9.88504C3.81235 9.89494 3.80254 9.90476 3.79281 9.91451C3.64909 10.0585 3.52237 10.1855 3.3696 10.2791C3.23539 10.3613 3.08907 10.4219 2.93602 10.4587C2.7618 10.5005 2.58242 10.5003 2.37897 10.5001C2.3652 10.5001 2.35132 10.5001 2.33732 10.5001H1.50005C1.22391 10.5001 1.00005 10.2763 1.00005 10.0001V9.16286C1.00005 9.14886 1.00004 9.13497 1.00003 9.1212C0.999836 8.91776 0.999669 8.73838 1.0415 8.56416C1.07824 8.4111 1.13885 8.26479 1.22109 8.13058C1.31471 7.97781 1.44166 7.85109 1.58566 7.70736C1.5954 7.69764 1.60523 7.68783 1.61513 7.67793L7.89651 1.39656Z" fill="#6B7280" />
</svg>
return (
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path fillRule="evenodd" clip-rule="evenodd" d="M10.8374 8.63108C11.0412 8.81739 11.0554 9.13366 10.8691 9.33747L10.369 9.88449C10.0142 10.2725 9.52293 10.5001 9.00011 10.5001C8.47746 10.5001 7.98634 10.2727 7.63157 9.8849C7.45561 9.69325 7.22747 9.59515 7.00014 9.59515C6.77271 9.59515 6.54446 9.69335 6.36846 9.88517C6.18177 10.0886 5.86548 10.1023 5.66201 9.91556C5.45853 9.72888 5.44493 9.41259 5.63161 9.20911C5.98678 8.82201 6.47777 8.59515 7.00014 8.59515C7.52251 8.59515 8.0135 8.82201 8.36867 9.20911L8.36924 9.20974C8.54486 9.4018 8.77291 9.50012 9.00011 9.50012C9.2273 9.50012 9.45533 9.40182 9.63095 9.20979L10.131 8.66276C10.3173 8.45895 10.6336 8.44476 10.8374 8.63108Z" fill="#6B7280" />
<path fillRule="evenodd" clip-rule="evenodd" d="M7.89651 1.39656C8.50599 0.787085 9.49414 0.787084 10.1036 1.39656C10.7131 2.00604 10.7131 2.99419 10.1036 3.60367L3.82225 9.88504C3.81235 9.89494 3.80254 9.90476 3.79281 9.91451C3.64909 10.0585 3.52237 10.1855 3.3696 10.2791C3.23539 10.3613 3.08907 10.4219 2.93602 10.4587C2.7618 10.5005 2.58242 10.5003 2.37897 10.5001C2.3652 10.5001 2.35132 10.5001 2.33732 10.5001H1.50005C1.22391 10.5001 1.00005 10.2763 1.00005 10.0001V9.16286C1.00005 9.14886 1.00004 9.13497 1.00003 9.1212C0.999836 8.91776 0.999669 8.73838 1.0415 8.56416C1.07824 8.4111 1.13885 8.26479 1.22109 8.13058C1.31471 7.97781 1.44166 7.85109 1.58566 7.70736C1.5954 7.69764 1.60523 7.68783 1.61513 7.67793L7.89651 1.39656Z" fill="#6B7280" />
</svg>
)
}
const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => {
return <div className={'rounded-lg h-6 w-6 flex items-center justify-center hover:bg-gray-100'}>
{children}
</div>
return (
<div className="rounded-lg h-6 w-6 flex items-center justify-center hover:bg-gray-100">
{children}
</div>
)
}
type IAnswerProps = {
interface IAnswerProps {
item: ChatItem
feedbackDisabled: boolean
onFeedback?: FeedbackFunc
@ -79,15 +87,14 @@ const Answer: FC<IAnswerProps> = ({
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<IAnswerProps> = ({
content={isLike ? '取消赞同' : '取消反对'}
>
<div
className={'relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800'}
className="relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-gray-500 hover:text-gray-800"
style={{ boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.05)' }}
onClick={async () => {
await onFeedback?.(id, { rating: null })
@ -120,14 +127,16 @@ const Answer: FC<IAnswerProps> = ({
const userOperation = () => {
return feedback?.rating
? null
: <div className='flex gap-1'>
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.like') as string}>
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={true} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'like' }) })}
</Tooltip>
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.dislike') as string}>
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={false} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'dislike' }) })}
</Tooltip>
</div>
: (
<div className="flex gap-1">
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.like') as string}>
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={true} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'like' }) })}
</Tooltip>
<Tooltip selector={`user-feedback-${randomString(16)}`} content={t('common.operation.dislike') as string}>
{OperationBtn({ innerContent: <IconWrapper><RatingIcon isLike={false} /></IconWrapper>, onClick: () => onFeedback?.(id, { rating: 'dislike' }) })}
</Tooltip>
</div>
)
}
return (
@ -138,8 +147,7 @@ const Answer: FC<IAnswerProps> = ({
}
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<IAnswerProps> = ({
{agent_thoughts?.map((item, index) => (
<div key={index}>
{item.thought && (
<Markdown content={item.thought} />
<StreamdownMarkdown content={item.thought} />
)}
{/* {item.tool} */}
{/* perhaps not use tool */}
@ -170,13 +178,14 @@ const Answer: FC<IAnswerProps> = ({
return (
<div key={id}>
<div className='flex items-start'>
<div className="flex items-start">
<div className={`${s.answerIcon} w-10 h-10 shrink-0`}>
{isResponding
&& <div className={s.typeingIcon}>
<LoadingAnim type='avatar' />
</div>
}
&& (
<div className={s.typeingIcon}>
<LoadingAnim type="avatar" />
</div>
)}
</div>
<div className={`${s.answerWrap}`}>
<div className={`${s.answer} relative text-sm text-gray-900`}>
@ -186,28 +195,28 @@ const Answer: FC<IAnswerProps> = ({
)}
{(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content))
? (
<div className='flex items-center justify-center w-6 h-5'>
<LoadingAnim type='text' />
<div className="flex items-center justify-center w-6 h-5">
<LoadingAnim type="text" />
</div>
)
: (isAgentMode
? agentModeAnswer
: (
<Markdown content={content} />
<StreamdownMarkdown content={content} />
))}
{suggestedQuestions.length > 0 && (
<div className='mt-3'>
<div className='flex gap-1 mt-1 flex-wrap'>
<div className="mt-3">
<div className="flex gap-1 mt-1 flex-wrap">
{suggestedQuestions.map((suggestion, index) => (
<div key={index} className='flex items-center gap-1'>
<Button className='text-sm' type='link' onClick={() => suggestionClick(suggestion)}>{suggestion}</Button>
<div key={index} className="flex items-center gap-1">
<Button className="text-sm" type="link" onClick={() => suggestionClick(suggestion)}>{suggestion}</Button>
</div>
))}
</div>
</div>
)}
</div>
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
<div className="absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1">
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
{/* User feedback must be displayed */}
{!feedbackDisabled && renderFeedbackRating(feedback?.rating)}

View File

@ -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<IChatProps> = ({
const [attachmentFiles, setAttachmentFiles] = React.useState<FileEntity[]>([])
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<IChatProps> = ({
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() }
}
}

View File

@ -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'
}

View File

@ -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<string, string | Emoji>
isFinished: boolean
@ -29,8 +29,7 @@ const Thought: FC<IThoughtProps> = ({
}) => {
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) {
}

View File

@ -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

View File

@ -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<string, string | Emoji>
}
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
if (toolName.startsWith('dataset-'))
return <DataSetIcon className='shrink-0'></DataSetIcon>
if (toolName.startsWith('dataset-')) { return <DataSetIcon className='shrink-0'></DataSetIcon> }
const icon = allToolIcons[toolName]
if (!icon)
return null
if (!icon) { return null }
return (
typeof icon === 'string'
? (
@ -87,12 +85,14 @@ const Tool: FC<Props> = ({
<Panel
isRequest={true}
toolName={toolName}
content={input} />
content={input}
/>
{output && (
<Panel
isRequest={false}
toolName={toolName}
content={output as string} />
content={output as string}
/>
)}
</div>
)}

View File

@ -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