chore(web): new lint setup (#30020)

Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
Stephen Zhou
2025-12-23 16:58:55 +08:00
committed by GitHub
parent 9701a2994b
commit f2842da397
3356 changed files with 85046 additions and 81278 deletions

View File

@ -1,7 +1,8 @@
import { useChatContext } from '@/app/components/base/chat/chat/context'
import Button from '@/app/components/base/button'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import { cn } from '@/utils/classnames'
import { isValidUrl } from './utils'
const MarkdownButton = ({ node }: any) => {
const { onSend } = useChatContext()
const variant = node.properties.dataVariant
@ -9,22 +10,24 @@ const MarkdownButton = ({ node }: any) => {
const link = node.properties.dataLink
const size = node.properties.dataSize
return <Button
variant={variant}
size={size}
className={cn('!h-auto min-h-8 select-none whitespace-normal !px-3')}
onClick={() => {
if (link && isValidUrl(link)) {
window.open(link, '_blank')
return
}
if(!message)
return
onSend?.(message)
}}
>
<span className='text-[13px]'>{node.children[0]?.value || ''}</span>
</Button>
return (
<Button
variant={variant}
size={size}
className={cn('!h-auto min-h-8 select-none whitespace-normal !px-3')}
onClick={() => {
if (link && isValidUrl(link)) {
window.open(link, '_blank')
return
}
if (!message)
return
onSend?.(message)
}}
>
<span className="text-[13px]">{node.children[0]?.value || ''}</span>
</Button>
)
}
MarkdownButton.displayName = 'MarkdownButton'

View File

@ -1,5 +1,6 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactEcharts from 'echarts-for-react'
import dynamic from 'next/dynamic'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import SyntaxHighlighter from 'react-syntax-highlighter'
import {
atelierHeathDark,
@ -7,13 +8,12 @@ import {
} from 'react-syntax-highlighter/dist/esm/styles/hljs'
import ActionButton from '@/app/components/base/action-button'
import CopyIcon from '@/app/components/base/copy-icon'
import SVGBtn from '@/app/components/base/svg'
import { Theme } from '@/types/app'
import useTheme from '@/hooks/use-theme'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
import MarkdownMusic from '@/app/components/base/markdown-blocks/music'
import ErrorBoundary from '@/app/components/base/markdown/error-boundary'
import dynamic from 'next/dynamic'
import SVGBtn from '@/app/components/base/svg'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
const Flowchart = dynamic(() => import('@/app/components/base/mermaid'), { ssr: false })
@ -66,13 +66,13 @@ const getCorrectCapitalizationLanguageName = (language: string) => {
// Define ECharts event parameter types
type EChartsEventParams = {
type: string;
seriesIndex?: number;
dataIndex?: number;
name?: string;
value?: any;
currentIndex?: number; // Added for timeline events
[key: string]: any;
type: string
seriesIndex?: number
dataIndex?: number
name?: string
value?: any
currentIndex?: number // Added for timeline events
[key: string]: any
}
const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any) => {
@ -144,7 +144,8 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
// Handle container resize for echarts
useEffect(() => {
if (language !== 'echarts' || !chartInstanceRef.current) return
if (language !== 'echarts' || !chartInstanceRef.current)
return
const handleResize = () => {
if (chartInstanceRef.current)
@ -163,7 +164,8 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
// Process chart data when content changes
useEffect(() => {
// Only process echarts content
if (language !== 'echarts') return
if (language !== 'echarts')
return
// Reset state when new content is detected
if (!contentRef.current) {
@ -174,11 +176,13 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
const newContent = String(children).replace(/\n$/, '')
// Skip if content hasn't changed
if (contentRef.current === newContent) return
if (contentRef.current === newContent)
return
contentRef.current = newContent
const trimmedContent = newContent.trim()
if (!trimmedContent) return
if (!trimmedContent)
return
// Detect if this is historical data (already complete)
// Historical data typically comes as a complete code block with complete JSON
@ -224,14 +228,14 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
// Check more conditions for streaming data
const isIncomplete
= trimmedContent.length < 5
|| (trimmedContent.startsWith('{')
&& (!trimmedContent.endsWith('}')
|| trimmedContent.split('{').length !== trimmedContent.split('}').length))
|| (trimmedContent.startsWith('[')
&& (!trimmedContent.endsWith(']')
|| trimmedContent.split('[').length !== trimmedContent.split('}').length))
|| (trimmedContent.split('"').length % 2 !== 1)
|| (trimmedContent.includes('{"') && !trimmedContent.includes('"}'))
|| (trimmedContent.startsWith('{')
&& (!trimmedContent.endsWith('}')
|| trimmedContent.split('{').length !== trimmedContent.split('}').length))
|| (trimmedContent.startsWith('[')
&& (!trimmedContent.endsWith(']')
|| trimmedContent.split('[').length !== trimmedContent.split('}').length))
|| (trimmedContent.split('"').length % 2 !== 1)
|| (trimmedContent.includes('{"') && !trimmedContent.includes('"}'))
// Only try to parse streaming data if it looks complete and hasn't been processed
if (!isIncomplete && !processedRef.current) {
@ -288,12 +292,14 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
borderBottomRightRadius: '10px',
backgroundColor: isDarkMode ? 'var(--color-components-input-bg-normal)' : 'transparent',
color: 'var(--color-text-secondary)',
}}>
}}
>
<div style={{
marginBottom: '12px',
width: '24px',
height: '24px',
}}>
}}
>
{/* Rotating spinner that works in both light and dark modes */}
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ animation: 'spin 1.5s linear infinite' }}>
<style>
@ -311,7 +317,10 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
<div style={{
fontFamily: 'var(--font-family)',
fontSize: '14px',
}}>Chart loading...</div>
}}
>
Chart loading...
</div>
</div>
)
}
@ -330,7 +339,8 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
borderBottomLeftRadius: '10px',
borderBottomRightRadius: '10px',
transition: 'background-color 0.3s ease',
}}>
}}
>
<ErrorBoundary>
<ReactEcharts
ref={(e) => {
@ -369,7 +379,8 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
borderBottomLeftRadius: '10px',
borderBottomRightRadius: '10px',
transition: 'background-color 0.3s ease',
}}>
}}
>
<ErrorBoundary>
<ReactEcharts
ref={echartsRef}
@ -423,10 +434,10 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
return <code {...props} className={className}>{children}</code>
return (
<div className='relative'>
<div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'>
<div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div>
<div className='flex items-center gap-1'>
<div className="relative">
<div className="flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3">
<div className="system-xs-semibold-uppercase text-text-secondary">{languageShowName}</div>
<div className="flex items-center gap-1">
{language === 'svg' && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
<ActionButton>
<CopyIcon content={String(children).replace(/\n$/, '')} />

View File

@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import Checkbox from '@/app/components/base/checkbox'
import DatePicker from '@/app/components/base/date-and-time-picker/date-picker'
import TimePicker from '@/app/components/base/date-and-time-picker/time-picker'
import Checkbox from '@/app/components/base/checkbox'
import Select from '@/app/components/base/select'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import { formatDateForOutput } from '@/app/components/base/date-and-time-picker/utils/dayjs'
import Input from '@/app/components/base/input'
import Select from '@/app/components/base/select'
import Textarea from '@/app/components/base/textarea'
enum DATA_FORMAT {
TEXT = 'text',
@ -56,7 +56,7 @@ const MarkdownForm = ({ node }: any) => {
let value = formValues[child.properties.name]
if (child.tagName === SUPPORTED_TAGS.INPUT
&& (child.properties.type === SUPPORTED_TYPES.DATE || child.properties.type === SUPPORTED_TYPES.DATETIME)) {
&& (child.properties.type === SUPPORTED_TYPES.DATE || child.properties.type === SUPPORTED_TYPES.DATETIME)) {
if (value && typeof value.format === 'function') {
// Format date output consistently
const includeTime = child.properties.type === SUPPORTED_TYPES.DATETIME
@ -88,7 +88,7 @@ const MarkdownForm = ({ node }: any) => {
return (
<form
autoComplete="off"
className='flex flex-col self-stretch'
className="flex flex-col self-stretch"
onSubmit={(e: any) => {
e.preventDefault()
e.stopPropagation()
@ -150,7 +150,7 @@ const MarkdownForm = ({ node }: any) => {
}
if (child.properties.type === SUPPORTED_TYPES.CHECKBOX) {
return (
<div className='mt-2 flex h-6 items-center space-x-2' key={index}>
<div className="mt-2 flex h-6 items-center space-x-2" key={index}>
<Checkbox
key={index}
checked={formValues[child.properties.name]}
@ -249,16 +249,21 @@ const MarkdownForm = ({ node }: any) => {
<Button
variant={variant}
size={size}
className='mt-4'
className="mt-4"
key={index}
onClick={onSubmit}
>
<span className='text-[13px]'>{child.children[0]?.value || ''}</span>
<span className="text-[13px]">{child.children[0]?.value || ''}</span>
</Button>
)
}
return <p key={index}>Unsupported tag: {child.tagName}</p>
return (
<p key={index}>
Unsupported tag:
{child.tagName}
</p>
)
})}
</form>
)

View File

@ -4,17 +4,17 @@
*/
export { default as AudioBlock } from './audio-block'
export { default as CodeBlock } from './code-block'
export * from './plugin-img'
export * from './plugin-paragraph'
export { default as Img } from './img'
export { default as Paragraph } from './paragraph'
export { default as Link } from './link'
export { default as PreCode } from './pre-code'
export { default as ScriptBlock } from './script-block'
export { default as VideoBlock } from './video-block'
// Assuming these are also standalone components in this directory intended for Markdown rendering
export { default as MarkdownButton } from './button'
export { default as CodeBlock } from './code-block'
export { default as MarkdownForm } from './form'
export { default as Img } from './img'
export { default as Link } from './link'
export { default as Paragraph } from './paragraph'
export * from './plugin-img'
export * from './plugin-paragraph'
export { default as PreCode } from './pre-code'
export { default as ScriptBlock } from './script-block'
export { default as ThinkBlock } from './think-block'
export { default as VideoBlock } from './video-block'

View File

@ -17,7 +17,7 @@ const Link = ({ node, children, ...props }: any) => {
}
else {
const href = props.href || node.properties?.href
if (href && /^#[a-zA-Z0-9_-]+$/.test(href.toString())) {
if (href && /^#[\w-]+$/.test(href.toString())) {
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault()
// scroll to target element if exists within the answer container

View File

@ -1,3 +1,4 @@
import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper'
/**
* @fileoverview Img component for rendering <img> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
@ -5,9 +6,8 @@
*/
import React, { useEffect, useMemo, useState } from 'react'
import ImageGallery from '@/app/components/base/image-gallery'
import { getMarkdownImageURL } from './utils'
import { usePluginReadmeAsset } from '@/service/use-plugins'
import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper'
import { getMarkdownImageURL } from './utils'
type ImgProps = {
src: string

View File

@ -1,3 +1,5 @@
import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper'
import React, { useEffect, useMemo, useState } from 'react'
/**
* @fileoverview Paragraph component for rendering <p> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
@ -5,8 +7,6 @@
*/
import ImageGallery from '@/app/components/base/image-gallery'
import { usePluginReadmeAsset } from '@/service/use-plugins'
import React, { useEffect, useMemo, useState } from 'react'
import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper'
import { getMarkdownImageURL } from './utils'
type PluginParagraphProps = {

View File

@ -12,7 +12,8 @@ function PreCode(props: { children: any }) {
<pre ref={ref}>
<span
className="copy-code-button"
></span>
>
</span>
{props.children}
</pre>
)

View File

@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/nextjs'
import { useState } from 'react'
import ThinkBlock from './think-block'
import { ChatContextProvider } from '@/app/components/base/chat/chat/context'
import ThinkBlock from './think-block'
const THOUGHT_TEXT = `
Gather docs from knowledge base.

View File

@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useChatContext } from '../chat/chat/context'
import { cn } from '@/utils/classnames'
import { useChatContext } from '../chat/chat/context'
const hasEndThink = (children: any): boolean => {
if (typeof children === 'string')
@ -44,7 +44,8 @@ const useThinkTimer = (children: any) => {
const timerRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
if (isComplete) return
if (isComplete)
return
timerRef.current = setInterval(() => {
setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10)

View File

@ -2,7 +2,8 @@ import { ALLOW_UNSAFE_DATA_SCHEME, MARKETPLACE_API_PREFIX } from '@/config'
export const isValidUrl = (url: string): boolean => {
const validPrefixes = ['http:', 'https:', '//', 'mailto:']
if (ALLOW_UNSAFE_DATA_SCHEME) validPrefixes.push('data:')
if (ALLOW_UNSAFE_DATA_SCHEME)
validPrefixes.push('data:')
return validPrefixes.some(prefix => url.startsWith(prefix))
}