mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
chore(web): new lint setup (#30020)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
@ -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'
|
||||
|
||||
|
||||
@ -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$/, '')} />
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -12,7 +12,8 @@ function PreCode(props: { children: any }) {
|
||||
<pre ref={ref}>
|
||||
<span
|
||||
className="copy-code-button"
|
||||
></span>
|
||||
>
|
||||
</span>
|
||||
{props.children}
|
||||
</pre>
|
||||
)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user