Merge branch 'main' into feat/trigger

This commit is contained in:
lyzno1
2025-10-10 15:09:38 +08:00
124 changed files with 886 additions and 271 deletions

View File

@ -4,6 +4,7 @@ import React, { useCallback, useRef, useState } from 'react'
import type { PopupProps } from './config-popup'
import ConfigPopup from './config-popup'
import cn from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
@ -45,7 +46,7 @@ const ConfigBtn: FC<Props> = ({
offset={12}
>
<PortalToFollowElemTrigger onClick={handleTrigger}>
<div className="select-none">
<div className={cn('select-none', className)}>
{children}
</div>
</PortalToFollowElemTrigger>

View File

@ -28,7 +28,8 @@ const CSVUploader: FC<Props> = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -37,7 +38,8 @@ const CSVUploader: FC<Props> = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()

View File

@ -348,7 +348,8 @@ const AppPublisher = ({
<SuggestedAction
className='flex-1'
onClick={() => {
publishedAt && handleOpenInExplore()
if (publishedAt)
handleOpenInExplore()
}}
disabled={!publishedAt || (systemFeatures.webapp_auth.enabled && !userCanAccessApp?.result)}
icon={<RiPlanetLine className='h-4 w-4' />}

View File

@ -40,7 +40,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
return
}
else {
titleError && setTitleError(false)
if (titleError)
setTitleError(false)
}
if (releaseNotes.length > RELEASE_NOTES_MAX_LENGTH) {
@ -52,7 +53,8 @@ const VersionInfoModal: FC<VersionInfoModalProps> = ({
return
}
else {
releaseNotesError && setReleaseNotesError(false)
if (releaseNotesError)
setReleaseNotesError(false)
}
onPublish({ title, releaseNotes, id: versionInfo?.id })

View File

@ -0,0 +1,29 @@
import type { SVGProps } from 'react'
const CitationIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
{...props}
>
<path
d="M7 6h10M7 12h6M7 18h10"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 6c0-1.105.895-2 2-2h10c1.105 0 2 .895 2 2v12c0 1.105-.895 2-2 2H9l-4 3v-3H7"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
</svg>
)
export default CitationIcon

View File

@ -79,7 +79,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
try {
return JSON.stringify(JSON.parse(tempPayload.json_schema).properties, null, 2)
}
catch (_e) {
catch {
return ''
}
}, [tempPayload.json_schema])
@ -123,7 +123,7 @@ const ConfigModal: FC<IConfigModalProps> = ({
}
handlePayloadChange('json_schema')(JSON.stringify(res, null, 2))
}
catch (_e) {
catch {
return null
}
}, [handlePayloadChange])

View File

@ -480,7 +480,7 @@ const Configuration: FC = () => {
Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${Object.entries(removedDetails).map(([k, reason]) => `${k} (${reason})`).join(', ')}` })
setCompletionParams(filtered)
}
catch (e) {
catch {
Toast.notify({ type: 'error', message: t('common.error') })
setCompletionParams({})
}

View File

@ -192,7 +192,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<Button
variant="primary"
disabled={canNotRun}
onClick={() => onSend && onSend()}
onClick={() => onSend?.()}
className="w-[96px]">
<RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
{t('appDebug.inputs.run')}
@ -203,7 +203,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<Button
variant="primary"
disabled={canNotRun}
onClick={() => onSend && onSend()}
onClick={() => onSend?.()}
className="w-[96px]">
<RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
{t('appDebug.inputs.run')}

View File

@ -38,7 +38,8 @@ const Uploader: FC<Props> = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -47,7 +48,8 @@ const Uploader: FC<Props> = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()

View File

@ -107,7 +107,8 @@ const Chart: React.FC<IChartProps> = ({
const { t } = useTranslation()
const statistics = chartData.data
const statisticsLen = statistics.length
const extraDataForMarkLine = new Array(statisticsLen >= 2 ? statisticsLen - 2 : statisticsLen).fill('1')
const markLineLength = statisticsLen >= 2 ? statisticsLen - 2 : statisticsLen
const extraDataForMarkLine = Array.from({ length: markLineLength }, () => '1')
extraDataForMarkLine.push('')
extraDataForMarkLine.unshift('')

View File

@ -127,7 +127,7 @@ export default class AudioPlayer {
}
catch {
this.isLoadData = false
this.callback && this.callback('error')
this.callback?.('error')
}
}
@ -137,15 +137,14 @@ export default class AudioPlayer {
if (this.audioContext.state === 'suspended') {
this.audioContext.resume().then((_) => {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
})
}
else if (this.audio.ended) {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
}
if (this.callback)
this.callback('play')
this.callback?.('play')
}
else {
this.isLoadData = true
@ -189,24 +188,24 @@ export default class AudioPlayer {
if (this.audio.paused) {
this.audioContext.resume().then((_) => {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
})
}
else if (this.audio.ended) {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
}
else if (this.audio.played) { /* empty */ }
else {
this.audio.play()
this.callback && this.callback('play')
this.callback?.('play')
}
}
}
public pauseAudio() {
this.callback && this.callback('paused')
this.callback?.('paused')
this.audio.pause()
this.audioContext.suspend()
}

View File

@ -128,7 +128,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const localState = localStorage.getItem('webappSidebarCollapse')
return localState === 'collapsed'
}
catch (e) {
catch {
// localStorage may be disabled in private browsing mode or by security settings
// fallback to default value
return false
@ -142,7 +142,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
try {
localStorage.setItem('webappSidebarCollapse', state ? 'collapsed' : 'expanded')
}
catch (e) {
catch {
// localStorage may be disabled, continue without persisting state
}
}

View File

@ -101,10 +101,14 @@ const Answer: FC<AnswerProps> = ({
}, [])
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
if (direction === 'prev')
item.prevSibling && switchSibling?.(item.prevSibling)
else
item.nextSibling && switchSibling?.(item.nextSibling)
if (direction === 'prev') {
if (item.prevSibling)
switchSibling?.(item.prevSibling)
}
else {
if (item.nextSibling)
switchSibling?.(item.nextSibling)
}
}, [switchSibling, item.prevSibling, item.nextSibling])
return (

View File

@ -73,10 +73,14 @@ const Question: FC<QuestionProps> = ({
}, [content])
const handleSwitchSibling = useCallback((direction: 'prev' | 'next') => {
if (direction === 'prev')
item.prevSibling && switchSibling?.(item.prevSibling)
else
item.nextSibling && switchSibling?.(item.nextSibling)
if (direction === 'prev') {
if (item.prevSibling)
switchSibling?.(item.prevSibling)
}
else {
if (item.nextSibling)
switchSibling?.(item.nextSibling)
}
}, [switchSibling, item.prevSibling, item.nextSibling])
const getContentWidth = () => {

View File

@ -0,0 +1,95 @@
import React from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import TimePicker from './index'
import dayjs from '../utils/dayjs'
import { isDayjsObject } from '../utils/dayjs'
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
if (key === 'time.defaultPlaceholder') return 'Pick a time...'
if (key === 'time.operation.now') return 'Now'
if (key === 'time.operation.ok') return 'OK'
if (key === 'common.operation.clear') return 'Clear'
return key
},
}),
}))
jest.mock('@/app/components/base/portal-to-follow-elem', () => ({
PortalToFollowElem: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
PortalToFollowElemTrigger: ({ children, onClick }: { children: React.ReactNode, onClick: (e: React.MouseEvent) => void }) => (
<div onClick={onClick}>{children}</div>
),
PortalToFollowElemContent: ({ children }: { children: React.ReactNode }) => (
<div data-testid="timepicker-content">{children}</div>
),
}))
jest.mock('./options', () => () => <div data-testid="time-options" />)
jest.mock('./header', () => () => <div data-testid="time-header" />)
describe('TimePicker', () => {
const baseProps = {
onChange: jest.fn(),
onClear: jest.fn(),
}
beforeEach(() => {
jest.clearAllMocks()
})
test('renders formatted value for string input (Issue #26692 regression)', () => {
render(
<TimePicker
{...baseProps}
value="18:45"
timezone="UTC"
/>,
)
expect(screen.getByDisplayValue('06:45 PM')).toBeInTheDocument()
})
test('confirms cleared value when confirming without selection', () => {
render(
<TimePicker
{...baseProps}
value={dayjs('2024-01-01T03:30:00Z')}
timezone="UTC"
/>,
)
const input = screen.getByRole('textbox')
fireEvent.click(input)
const clearButton = screen.getByRole('button', { name: /clear/i })
fireEvent.click(clearButton)
const confirmButton = screen.getByRole('button', { name: 'OK' })
fireEvent.click(confirmButton)
expect(baseProps.onChange).toHaveBeenCalledTimes(1)
expect(baseProps.onChange).toHaveBeenCalledWith(undefined)
expect(baseProps.onClear).not.toHaveBeenCalled()
})
test('selecting current time emits timezone-aware value', () => {
const onChange = jest.fn()
render(
<TimePicker
{...baseProps}
onChange={onChange}
timezone="America/New_York"
/>,
)
const nowButton = screen.getByRole('button', { name: 'Now' })
fireEvent.click(nowButton)
expect(onChange).toHaveBeenCalledTimes(1)
const emitted = onChange.mock.calls[0][0]
expect(isDayjsObject(emitted)).toBe(true)
expect(emitted?.utcOffset()).toBe(dayjs().tz('America/New_York').utcOffset())
})
})

View File

@ -1,6 +1,13 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
import type { Period, TimePickerProps } from '../types'
import dayjs, { cloneTime, getDateWithTimezone, getHourIn12Hour } from '../utils/dayjs'
import type { Dayjs } from 'dayjs'
import { Period } from '../types'
import type { TimePickerProps } from '../types'
import dayjs, {
getDateWithTimezone,
getHourIn12Hour,
isDayjsObject,
toDayjs,
} from '../utils/dayjs'
import {
PortalToFollowElem,
PortalToFollowElemContent,
@ -13,6 +20,11 @@ import { useTranslation } from 'react-i18next'
import { RiCloseCircleFill, RiTimeLine } from '@remixicon/react'
import cn from '@/utils/classnames'
const to24Hour = (hour12: string, period: Period) => {
const normalized = Number.parseInt(hour12, 10) % 12
return period === Period.PM ? normalized + 12 : normalized
}
const TimePicker = ({
value,
timezone,
@ -29,7 +41,11 @@ const TimePicker = ({
const [isOpen, setIsOpen] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
const isInitial = useRef(true)
const [selectedTime, setSelectedTime] = useState(() => value ? getDateWithTimezone({ timezone, date: value }) : undefined)
// Initialize selectedTime
const [selectedTime, setSelectedTime] = useState(() => {
return toDayjs(value, { timezone })
})
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
@ -40,20 +56,47 @@ const TimePicker = ({
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
// Track previous values to avoid unnecessary updates
const prevValueRef = useRef(value)
const prevTimezoneRef = useRef(timezone)
useEffect(() => {
if (isInitial.current) {
isInitial.current = false
// Save initial values on first render
prevValueRef.current = value
prevTimezoneRef.current = timezone
return
}
if (value) {
const newValue = getDateWithTimezone({ date: value, timezone })
setSelectedTime(newValue)
onChange(newValue)
// Only update when timezone changes but value doesn't
const valueChanged = prevValueRef.current !== value
const timezoneChanged = prevTimezoneRef.current !== timezone
// Update reference values
prevValueRef.current = value
prevTimezoneRef.current = timezone
// Skip if neither timezone changed nor value changed
if (!timezoneChanged && !valueChanged) return
if (value !== undefined && value !== null) {
const dayjsValue = toDayjs(value, { timezone })
if (!dayjsValue) return
setSelectedTime(dayjsValue)
if (timezoneChanged && !valueChanged)
onChange(dayjsValue)
return
}
else {
setSelectedTime(prev => prev ? getDateWithTimezone({ date: prev, timezone }) : undefined)
}
}, [timezone])
setSelectedTime((prev) => {
if (!isDayjsObject(prev))
return undefined
return timezone ? getDateWithTimezone({ date: prev, timezone }) : prev
})
}, [timezone, value, onChange])
const handleClickTrigger = (e: React.MouseEvent) => {
e.stopPropagation()
@ -62,8 +105,16 @@ const TimePicker = ({
return
}
setIsOpen(true)
if (value)
setSelectedTime(value)
if (value) {
const dayjsValue = toDayjs(value, { timezone })
const needsUpdate = dayjsValue && (
!selectedTime
|| !isDayjsObject(selectedTime)
|| !dayjsValue.isSame(selectedTime, 'minute')
)
if (needsUpdate) setSelectedTime(dayjsValue)
}
}
const handleClear = (e: React.MouseEvent) => {
@ -74,42 +125,68 @@ const TimePicker = ({
}
const handleTimeSelect = (hour: string, minute: string, period: Period) => {
const newTime = cloneTime(dayjs(), dayjs(`1/1/2000 ${hour}:${minute} ${period}`))
const periodAdjustedHour = to24Hour(hour, period)
const nextMinute = Number.parseInt(minute, 10)
setSelectedTime((prev) => {
return prev ? cloneTime(prev, newTime) : newTime
const reference = isDayjsObject(prev)
? prev
: (timezone ? getDateWithTimezone({ timezone }) : dayjs()).startOf('minute')
return reference
.set('hour', periodAdjustedHour)
.set('minute', nextMinute)
.set('second', 0)
.set('millisecond', 0)
})
}
const getSafeTimeObject = useCallback(() => {
if (isDayjsObject(selectedTime))
return selectedTime
return (timezone ? getDateWithTimezone({ timezone }) : dayjs()).startOf('day')
}, [selectedTime, timezone])
const handleSelectHour = useCallback((hour: string) => {
const time = selectedTime || dayjs().startOf('day')
const time = getSafeTimeObject()
handleTimeSelect(hour, time.minute().toString().padStart(2, '0'), time.format('A') as Period)
}, [selectedTime])
}, [getSafeTimeObject])
const handleSelectMinute = useCallback((minute: string) => {
const time = selectedTime || dayjs().startOf('day')
const time = getSafeTimeObject()
handleTimeSelect(getHourIn12Hour(time).toString().padStart(2, '0'), minute, time.format('A') as Period)
}, [selectedTime])
}, [getSafeTimeObject])
const handleSelectPeriod = useCallback((period: Period) => {
const time = selectedTime || dayjs().startOf('day')
const time = getSafeTimeObject()
handleTimeSelect(getHourIn12Hour(time).toString().padStart(2, '0'), time.minute().toString().padStart(2, '0'), period)
}, [selectedTime])
}, [getSafeTimeObject])
const handleSelectCurrentTime = useCallback(() => {
const newDate = getDateWithTimezone({ timezone })
setSelectedTime(newDate)
onChange(newDate)
setIsOpen(false)
}, [onChange, timezone])
}, [timezone, onChange])
const handleConfirm = useCallback(() => {
onChange(selectedTime)
const valueToEmit = isDayjsObject(selectedTime) ? selectedTime : undefined
onChange(valueToEmit)
setIsOpen(false)
}, [onChange, selectedTime])
}, [selectedTime, onChange])
const timeFormat = 'hh:mm A'
const displayValue = value?.format(timeFormat) || ''
const placeholderDate = isOpen && selectedTime ? selectedTime.format(timeFormat) : (placeholder || t('time.defaultPlaceholder'))
const formatTimeValue = useCallback((timeValue: string | Dayjs | undefined): string => {
if (!timeValue) return ''
const dayjsValue = toDayjs(timeValue, { timezone })
return dayjsValue?.format(timeFormat) || ''
}, [timezone])
const displayValue = formatTimeValue(value)
const placeholderDate = isOpen && isDayjsObject(selectedTime)
? selectedTime.format(timeFormat)
: (placeholder || t('time.defaultPlaceholder'))
const inputElem = (
<input
@ -142,15 +219,13 @@ const TimePicker = ({
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',
(displayValue || (isOpen && selectedTime)) && !notClearable && 'group-hover:hidden',
)} />
{!notClearable && (
<RiCloseCircleFill
className={cn(
'hidden h-4 w-4 shrink-0 text-text-quaternary',
(displayValue || (isOpen && selectedTime)) && 'hover:text-text-secondary group-hover:inline-block',
)}
onClick={handleClear}
/>
)}
<RiCloseCircleFill
className={cn(
'hidden h-4 w-4 shrink-0 text-text-quaternary',
(displayValue || (isOpen && selectedTime)) && 'hover:text-text-secondary group-hover:inline-block',
)}
onClick={handleClear}
/>
</div>
)}
</PortalToFollowElemTrigger>

View File

@ -55,7 +55,7 @@ export type TriggerParams = {
onClick: (e: React.MouseEvent) => void
}
export type TimePickerProps = {
value: Dayjs | undefined
value: Dayjs | string | undefined
timezone?: string
placeholder?: string
onChange: (date: Dayjs | undefined) => void

View File

@ -0,0 +1,67 @@
import dayjs from './dayjs'
import {
getDateWithTimezone,
isDayjsObject,
toDayjs,
} from './dayjs'
describe('dayjs utilities', () => {
const timezone = 'UTC'
test('toDayjs parses time-only strings with timezone support', () => {
const result = toDayjs('18:45', { timezone })
expect(result).toBeDefined()
expect(result?.format('HH:mm')).toBe('18:45')
expect(result?.utcOffset()).toBe(getDateWithTimezone({ timezone }).utcOffset())
})
test('toDayjs parses 12-hour time strings', () => {
const tz = 'America/New_York'
const result = toDayjs('07:15 PM', { timezone: tz })
expect(result).toBeDefined()
expect(result?.format('HH:mm')).toBe('19:15')
expect(result?.utcOffset()).toBe(getDateWithTimezone({ timezone: tz }).utcOffset())
})
test('isDayjsObject detects dayjs instances', () => {
const date = dayjs()
expect(isDayjsObject(date)).toBe(true)
expect(isDayjsObject(getDateWithTimezone({ timezone }))).toBe(true)
expect(isDayjsObject('2024-01-01')).toBe(false)
expect(isDayjsObject({})).toBe(false)
})
test('toDayjs parses datetime strings in target timezone', () => {
const value = '2024-05-01 12:00:00'
const tz = 'America/New_York'
const result = toDayjs(value, { timezone: tz })
expect(result).toBeDefined()
expect(result?.hour()).toBe(12)
expect(result?.format('YYYY-MM-DD HH:mm')).toBe('2024-05-01 12:00')
})
test('toDayjs parses ISO datetime strings in target timezone', () => {
const value = '2024-05-01T14:30:00'
const tz = 'Europe/London'
const result = toDayjs(value, { timezone: tz })
expect(result).toBeDefined()
expect(result?.hour()).toBe(14)
expect(result?.minute()).toBe(30)
})
test('toDayjs handles dates without time component', () => {
const value = '2024-05-01'
const tz = 'America/Los_Angeles'
const result = toDayjs(value, { timezone: tz })
expect(result).toBeDefined()
expect(result?.format('YYYY-MM-DD')).toBe('2024-05-01')
expect(result?.hour()).toBe(0)
expect(result?.minute()).toBe(0)
})
})

View File

@ -10,6 +10,25 @@ dayjs.extend(timezone)
export default dayjs
const monthMaps: Record<string, Day[]> = {}
const DEFAULT_OFFSET_STR = 'UTC+0'
const TIME_ONLY_REGEX = /^(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?$/
const TIME_ONLY_12H_REGEX = /^(\d{1,2}):(\d{2})(?::(\d{2}))?\s?(AM|PM)$/i
const COMMON_PARSE_FORMATS = [
'YYYY-MM-DD',
'YYYY/MM/DD',
'DD-MM-YYYY',
'DD/MM/YYYY',
'MM-DD-YYYY',
'MM/DD/YYYY',
'YYYY-MM-DDTHH:mm:ss.SSSZ',
'YYYY-MM-DDTHH:mm:ssZ',
'YYYY-MM-DD HH:mm:ss',
'YYYY-MM-DDTHH:mm',
'YYYY-MM-DDTHH:mmZ',
'YYYY-MM-DDTHH:mm:ss',
'YYYY-MM-DDTHH:mm:ss.SSS',
]
export const cloneTime = (targetDate: Dayjs, sourceDate: Dayjs) => {
return targetDate.clone()
@ -76,21 +95,116 @@ export const getHourIn12Hour = (date: Dayjs) => {
return hour === 0 ? 12 : hour >= 12 ? hour - 12 : hour
}
export const getDateWithTimezone = (props: { date?: Dayjs, timezone?: string }) => {
return props.date ? dayjs.tz(props.date, props.timezone) : dayjs().tz(props.timezone)
export const getDateWithTimezone = ({ date, timezone }: { date?: Dayjs, timezone?: string }) => {
if (!timezone)
return (date ?? dayjs()).clone()
return date ? dayjs.tz(date, timezone) : dayjs().tz(timezone)
}
// Asia/Shanghai -> UTC+8
const DEFAULT_OFFSET_STR = 'UTC+0'
export const convertTimezoneToOffsetStr = (timezone?: string) => {
if (!timezone)
return DEFAULT_OFFSET_STR
const tzItem = tz.find(item => item.value === timezone)
if(!tzItem)
if (!tzItem)
return DEFAULT_OFFSET_STR
return `UTC${tzItem.name.charAt(0)}${tzItem.name.charAt(2)}`
}
export const isDayjsObject = (value: unknown): value is Dayjs => dayjs.isDayjs(value)
export type ToDayjsOptions = {
timezone?: string
format?: string
formats?: string[]
}
const warnParseFailure = (value: string) => {
if (process.env.NODE_ENV !== 'production')
console.warn('[TimePicker] Failed to parse time value', value)
}
const normalizeMillisecond = (value: string | undefined) => {
if (!value) return 0
if (value.length === 3) return Number(value)
if (value.length > 3) return Number(value.slice(0, 3))
return Number(value.padEnd(3, '0'))
}
const applyTimezone = (date: Dayjs, timezone?: string) => {
return timezone ? getDateWithTimezone({ date, timezone }) : date
}
export const toDayjs = (value: string | Dayjs | undefined, options: ToDayjsOptions = {}): Dayjs | undefined => {
if (!value)
return undefined
const { timezone: tzName, format, formats } = options
if (isDayjsObject(value))
return applyTimezone(value, tzName)
if (typeof value !== 'string')
return undefined
const trimmed = value.trim()
if (format) {
const parsedWithFormat = tzName
? dayjs.tz(trimmed, format, tzName, true)
: dayjs(trimmed, format, true)
if (parsedWithFormat.isValid())
return parsedWithFormat
}
const timeMatch = TIME_ONLY_REGEX.exec(trimmed)
if (timeMatch) {
const base = applyTimezone(dayjs(), tzName).startOf('day')
const rawHour = Number(timeMatch[1])
const minute = Number(timeMatch[2])
const second = timeMatch[3] ? Number(timeMatch[3]) : 0
const millisecond = normalizeMillisecond(timeMatch[4])
return base
.set('hour', rawHour)
.set('minute', minute)
.set('second', second)
.set('millisecond', millisecond)
}
const timeMatch12h = TIME_ONLY_12H_REGEX.exec(trimmed)
if (timeMatch12h) {
const base = applyTimezone(dayjs(), tzName).startOf('day')
let hour = Number(timeMatch12h[1]) % 12
const isPM = timeMatch12h[4]?.toUpperCase() === 'PM'
if (isPM)
hour += 12
const minute = Number(timeMatch12h[2])
const second = timeMatch12h[3] ? Number(timeMatch12h[3]) : 0
return base
.set('hour', hour)
.set('minute', minute)
.set('second', second)
.set('millisecond', 0)
}
const candidateFormats = formats ?? COMMON_PARSE_FORMATS
for (const fmt of candidateFormats) {
const parsed = tzName
? dayjs.tz(trimmed, fmt, tzName, true)
: dayjs(trimmed, fmt, true)
if (parsed.isValid())
return parsed
}
const fallbackParsed = tzName ? dayjs.tz(trimmed, tzName) : dayjs(trimmed)
if (fallbackParsed.isValid())
return fallbackParsed
warnParseFailure(value)
return undefined
}
// Parse date with multiple format support
export const parseDateWithFormat = (dateString: string, format?: string): Dayjs | null => {
if (!dateString) return null
@ -103,15 +217,7 @@ export const parseDateWithFormat = (dateString: string, format?: string): Dayjs
// Try common date formats
const formats = [
'YYYY-MM-DD', // Standard format
'YYYY/MM/DD', // Slash format
'DD-MM-YYYY', // European format
'DD/MM/YYYY', // European slash format
'MM-DD-YYYY', // US format
'MM/DD/YYYY', // US slash format
'YYYY-MM-DDTHH:mm:ss.SSSZ', // ISO format
'YYYY-MM-DDTHH:mm:ssZ', // ISO format (no milliseconds)
'YYYY-MM-DD HH:mm:ss', // Standard datetime format
...COMMON_PARSE_FORMATS,
]
for (const fmt of formats) {
@ -124,7 +230,7 @@ export const parseDateWithFormat = (dateString: string, format?: string): Dayjs
}
// Format date output with localization support
export const formatDateForOutput = (date: Dayjs, includeTime: boolean = false, locale: string = 'en-US'): string => {
export const formatDateForOutput = (date: Dayjs, includeTime: boolean = false, _locale: string = 'en-US'): string => {
if (!date || !date.isValid()) return ''
if (includeTime) {

View File

@ -47,7 +47,10 @@ export default function Drawer({
<Dialog
unmount={unmount}
open={isOpen}
onClose={() => !clickOutsideNotOpen && onClose()}
onClose={() => {
if (!clickOutsideNotOpen)
onClose()
}}
className={cn('fixed inset-0 z-[30] overflow-y-auto', dialogClassName)}
>
<div className={cn('flex h-screen w-screen justify-end', positionCenter && '!justify-center')}>
@ -55,7 +58,8 @@ export default function Drawer({
<DialogBackdrop
className={cn('fixed inset-0 z-[40]', mask && 'bg-black/30', dialogBackdropClassName)}
onClick={() => {
!clickOutsideNotOpen && onClose()
if (!clickOutsideNotOpen)
onClose()
}}
/>
<div className={cn('relative z-[50] flex w-full max-w-sm flex-col justify-between overflow-hidden bg-components-panel-bg p-6 text-left align-middle shadow-xl', panelClassName)}>
@ -80,11 +84,11 @@ export default function Drawer({
<Button
className='mr-2'
onClick={() => {
onCancel && onCancel()
onCancel?.()
}}>{t('common.operation.cancel')}</Button>
<Button
onClick={() => {
onOk && onOk()
onOk?.()
}}>{t('common.operation.save')}</Button>
</div>)}
</div>

View File

@ -45,7 +45,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
<Divider className='mb-0 mt-3' />
<div className='flex w-full items-center justify-center gap-2 p-3'>
<Button className='w-full' onClick={() => {
onClose && onClose()
onClose?.()
}}>
{t('app.iconPicker.cancel')}
</Button>
@ -54,7 +54,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
variant="primary"
className='w-full'
onClick={() => {
onSelect && onSelect(selectedEmoji, selectedBackground!)
onSelect?.(selectedEmoji, selectedBackground!)
}}>
{t('app.iconPicker.ok')}
</Button>

View File

@ -33,7 +33,10 @@ const SelectField = ({
<PureSelect
value={field.state.value}
options={options}
onChange={value => field.handleChange(value)}
onChange={(value) => {
field.handleChange(value)
onChange?.(value)
}}
{...selectProps}
/>
</div>

View File

@ -62,7 +62,7 @@ const ImageList: FC<ImageListProps> = ({
{item.progress === -1 && (
<RefreshCcw01
className="h-5 w-5 text-white"
onClick={() => onReUpload && onReUpload(item._id)}
onClick={() => onReUpload?.(item._id)}
/>
)}
</div>
@ -122,7 +122,7 @@ const ImageList: FC<ImageListProps> = ({
'rounded-2xl shadow-lg hover:bg-state-base-hover',
item.progress === -1 ? 'flex' : 'hidden group-hover:flex',
)}
onClick={() => onRemove && onRemove(item._id)}
onClick={() => onRemove?.(item._id)}
>
<RiCloseLine className="h-3 w-3 text-text-tertiary" />
</button>

View File

@ -20,7 +20,7 @@ const isBase64 = (str: string): boolean => {
try {
return btoa(atob(str)) === str
}
catch (err) {
catch {
return false
}
}

View File

@ -127,7 +127,7 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
// Store event handlers in useMemo to avoid recreating them
const echartsEvents = useMemo(() => ({
finished: (params: EChartsEventParams) => {
finished: (_params: EChartsEventParams) => {
// Limit finished event frequency to avoid infinite loops
finishedEventCountRef.current++
if (finishedEventCountRef.current > 3) {

View File

@ -60,7 +60,7 @@ export function svgToBase64(svgGraph: string): Promise<string> {
reader.readAsDataURL(blob)
})
}
catch (error) {
catch {
return Promise.resolve('')
}
}

View File

@ -10,9 +10,7 @@ const usePagination = ({
edgePageCount,
middlePagesSiblingCount,
}: IPaginationProps): IUsePagination => {
const pages = new Array(totalPages)
.fill(0)
.map((_, i) => i + 1)
const pages = React.useMemo(() => Array.from({ length: totalPages }, (_, i) => i + 1), [totalPages])
const hasPreviousPage = currentPage > 1
const hasNextPage = currentPage < totalPages

View File

@ -37,13 +37,16 @@ export default function CustomPopover({
const timeOutRef = useRef<number | null>(null)
const onMouseEnter = (isOpen: boolean) => {
timeOutRef.current && window.clearTimeout(timeOutRef.current)
!isOpen && buttonRef.current?.click()
if (timeOutRef.current != null)
window.clearTimeout(timeOutRef.current)
if (!isOpen)
buttonRef.current?.click()
}
const onMouseLeave = (isOpen: boolean) => {
timeOutRef.current = window.setTimeout(() => {
isOpen && buttonRef.current?.click()
if (isOpen)
buttonRef.current?.click()
}, timeoutDuration)
}

View File

@ -43,7 +43,7 @@ export default function LocaleSigninSelect({
className={'group flex w-full items-center rounded-lg px-3 py-2 text-sm text-text-secondary data-[active]:bg-state-base-hover'}
onClick={(evt) => {
evt.preventDefault()
onChange && onChange(item.value)
onChange?.(item.value)
}}
>
{item.name}

View File

@ -43,7 +43,7 @@ export default function Select({
className={'group flex w-full items-center rounded-lg px-3 py-2 text-sm text-text-secondary data-[active]:bg-state-base-hover'}
onClick={(evt) => {
evt.preventDefault()
onChange && onChange(item.value)
onChange?.(item.value)
}}
>
{item.name}

View File

@ -97,10 +97,13 @@ const Panel = (props: PanelProps) => {
const removeTagIDs = value.filter(v => !selectedTagIDs.includes(v))
const selectedTags = tagList.filter(tag => selectedTagIDs.includes(tag.id))
onCacheUpdate(selectedTags)
Promise.all([
...(addTagIDs.length ? [bind(addTagIDs)] : []),
...[removeTagIDs.length ? removeTagIDs.map(tagID => unbind(tagID)) : []],
]).finally(() => {
const operations: Promise<unknown>[] = []
if (addTagIDs.length)
operations.push(bind(addTagIDs))
if (removeTagIDs.length)
operations.push(...removeTagIDs.map(tagID => unbind(tagID)))
Promise.all(operations).finally(() => {
if (onChange)
onChange()
})

View File

@ -81,7 +81,8 @@ const VoiceInput = ({
setStartRecord(false)
setStartConvert(true)
recorder.current.stop()
drawRecordId.current && cancelAnimationFrame(drawRecordId.current)
if (drawRecordId.current)
cancelAnimationFrame(drawRecordId.current)
drawRecordId.current = null
const canvas = canvasRef.current!
const ctx = ctxRef.current!

View File

@ -34,7 +34,8 @@ const Uploader: FC<Props> = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -43,7 +44,8 @@ const Uploader: FC<Props> = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()

View File

@ -185,7 +185,8 @@ const FileUploader = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -194,7 +195,8 @@ const FileUploader = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
type FileWithPath = {
relativePath?: string

View File

@ -568,9 +568,9 @@ const StepTwo = ({
params,
{
onSuccess(data) {
updateIndexingTypeCache && updateIndexingTypeCache(indexType as string)
updateResultCache && updateResultCache(data)
updateRetrievalMethodCache && updateRetrievalMethodCache(retrievalConfig.search_method as string)
updateIndexingTypeCache?.(indexType as string)
updateResultCache?.(data)
updateRetrievalMethodCache?.(retrievalConfig.search_method as string)
},
},
)
@ -578,17 +578,18 @@ const StepTwo = ({
else {
await createDocumentMutation.mutateAsync(params, {
onSuccess(data) {
updateIndexingTypeCache && updateIndexingTypeCache(indexType as string)
updateResultCache && updateResultCache(data)
updateRetrievalMethodCache && updateRetrievalMethodCache(retrievalConfig.search_method as string)
updateIndexingTypeCache?.(indexType as string)
updateResultCache?.(data)
updateRetrievalMethodCache?.(retrievalConfig.search_method as string)
},
})
}
if (mutateDatasetRes)
mutateDatasetRes()
invalidDatasetList()
onStepChange && onStepChange(+1)
isSetting && onSave && onSave()
onStepChange?.(+1)
if (isSetting)
onSave?.()
}
useEffect(() => {
@ -1026,7 +1027,7 @@ const StepTwo = ({
{!isSetting
? (
<div className='mt-8 flex items-center py-2'>
<Button onClick={() => onStepChange && onStepChange(-1)}>
<Button onClick={() => onStepChange?.(-1)}>
<RiArrowLeftLine className='mr-1 h-4 w-4' />
{t('datasetCreation.stepTwo.previousStep')}
</Button>

View File

@ -200,7 +200,8 @@ const LocalFile = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -209,7 +210,8 @@ const LocalFile = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = useCallback((e: DragEvent) => {

View File

@ -45,10 +45,13 @@ const CrawledResult = ({
const handleItemCheckChange = useCallback((item: CrawlResultItem) => {
return (checked: boolean) => {
if (checked)
isMultipleChoice ? onSelectedChange([...checkedList, item]) : onSelectedChange([item])
else
onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url))
if (checked) {
if (isMultipleChoice)
onSelectedChange([...checkedList, item])
else
onSelectedChange([item])
}
else { onSelectedChange(checkedList.filter(checkedItem => checkedItem.source_url !== item.source_url)) }
}
}, [checkedList, onSelectedChange, isMultipleChoice])

View File

@ -326,7 +326,10 @@ const CreateFormPipeline = () => {
}, [])
const handleSubmit = useCallback((data: Record<string, any>) => {
isPreview.current ? handlePreviewChunks(data) : handleProcess(data)
if (isPreview.current)
handlePreviewChunks(data)
else
handleProcess(data)
}, [handlePreviewChunks, handleProcess])
const handlePreviewFileChange = useCallback((file: DocumentItem) => {

View File

@ -99,7 +99,8 @@ const CSVUploader: FC<Props> = ({
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
if (e.target !== dragRef.current)
setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
@ -108,7 +109,8 @@ const CSVUploader: FC<Props> = ({
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
if (e.target === dragRef.current)
setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()

View File

@ -284,7 +284,8 @@ const Completed: FC<ICompletedProps> = ({
onSuccess: () => {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
resetList()
!segId && setSelectedSegmentIds([])
if (!segId)
setSelectedSegmentIds([])
},
onError: () => {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
@ -438,7 +439,8 @@ const Completed: FC<ICompletedProps> = ({
}
else {
resetList()
currentPage !== totalPages && setCurrentPage(totalPages)
if (currentPage !== totalPages)
setCurrentPage(totalPages)
}
}, [segmentListData, limit, currentPage, resetList])
@ -491,7 +493,8 @@ const Completed: FC<ICompletedProps> = ({
}
else {
resetChildList()
currentPage !== totalPages && setCurrentPage(totalPages)
if (currentPage !== totalPages)
setCurrentPage(totalPages)
}
}, [childChunkListData, limit, currentPage, resetChildList])

View File

@ -66,7 +66,7 @@ export const FieldInfo: FC<IFieldInfoProps> = ({
? displayedValue
: inputType === 'select'
? <SimpleSelect
onSelect={({ value }) => onUpdate && onUpdate(value as string)}
onSelect={({ value }) => onUpdate?.(value as string)}
items={selectOptions}
defaultValue={value}
className={s.select}
@ -75,7 +75,7 @@ export const FieldInfo: FC<IFieldInfoProps> = ({
/>
: inputType === 'textarea'
? <AutoHeightTextarea
onChange={e => onUpdate && onUpdate(e.target.value)}
onChange={e => onUpdate?.(e.target.value)}
value={value}
className={s.textArea}
placeholder={`${t('datasetDocuments.metadata.placeholder.add')}${label}`}

View File

@ -148,7 +148,10 @@ const PipelineSettings = ({
}, [])
const handleSubmit = useCallback((data: Record<string, any>) => {
isPreview.current ? handlePreviewChunks(data) : handleProcess(data)
if (isPreview.current)
handlePreviewChunks(data)
else
handleProcess(data)
}, [handlePreviewChunks, handleProcess])
if (isFetchingLastRunData) {

View File

@ -80,7 +80,8 @@ const TextAreaWithButton = ({
onUpdateList?.()
}
setLoading(false)
_onSubmit && _onSubmit()
if (_onSubmit)
_onSubmit()
}
const externalRetrievalTestingOnSubmit = async () => {

View File

@ -157,12 +157,12 @@ const DatasetCard = ({
data-disable-nprogress={true}
onClick={(e) => {
e.preventDefault()
isExternalProvider
? push(`/datasets/${dataset.id}/hitTesting`)
// eslint-disable-next-line sonarjs/no-nested-conditional
: isPipelineUnpublished
? push(`/datasets/${dataset.id}/pipeline`)
: push(`/datasets/${dataset.id}/documents`)
if (isExternalProvider)
push(`/datasets/${dataset.id}/hitTesting`)
else if (isPipelineUnpublished)
push(`/datasets/${dataset.id}/pipeline`)
else
push(`/datasets/${dataset.id}/documents`)
}}
>
{!dataset.embedding_available && (

View File

@ -0,0 +1,3 @@
const DatasetsLoading = () => null
export default DatasetsLoading

View File

@ -0,0 +1,3 @@
const DatasetPreview = () => null
export default DatasetPreview

View File

@ -39,7 +39,7 @@ const Collapse = ({
<div className='mx-1 mb-1 rounded-lg border-t border-divider-subtle bg-components-panel-on-panel-item-bg py-1'>
{
items.map(item => (
<div key={item.key} onClick={() => onSelect && onSelect(item)}>
<div key={item.key} onClick={() => onSelect?.(item)}>
{renderItem(item)}
</div>
))

View File

@ -276,7 +276,7 @@ function Form<
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className='system-sm-semibold flex items-center justify-between py-2 text-text-secondary'>
<div className='flex items-center space-x-2'>
<span className={cn(fieldLabelClassName, 'system-sm-regular flex items-center py-2 text-text-secondary')}>{label[language] || label.en_US}</span>
<span className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>{label[language] || label.en_US}</span>
{required && (
<span className='ml-1 text-red-500'>*</span>
)}

View File

@ -49,7 +49,7 @@ const ModelLoadBalancingConfigs = ({
provider,
model,
configurationMethod,
currentCustomConfigurationModelFixedFields,
currentCustomConfigurationModelFixedFields: _currentCustomConfigurationModelFixedFields,
withSwitch = false,
className,
modelCredential,

View File

@ -33,7 +33,7 @@ type Props = {
}
const AppPicker: FC<Props> = ({
scope,
scope: _scope,
disabled,
trigger,
placement = 'right-start',
@ -90,7 +90,7 @@ const AppPicker: FC<Props> = ({
}
// Set up MutationObserver to watch DOM changes
mutationObserver = new MutationObserver((mutations) => {
mutationObserver = new MutationObserver((_mutations) => {
if (observerTarget.current) {
setupIntersectionObserver()
mutationObserver?.disconnect()

View File

@ -148,7 +148,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
})
}
}
catch (e) {
catch {
Toast.notify({ type: 'error', message: t('common.error') })
}
}

View File

@ -51,7 +51,7 @@ export const useFieldList = ({
const handleListSortChange = useCallback((list: SortableItem[]) => {
const newInputFields = list.map((item) => {
const { id, chosen, selected, ...filed } = item
const { id: _id, chosen: _chosen, selected: _selected, ...filed } = item
return filed
})
handleInputFieldsChange(newInputFields)

View File

@ -15,7 +15,8 @@ const Header = () => {
isPreparingDataSource,
setIsPreparingDataSource,
} = workflowStore.getState()
isPreparingDataSource && setIsPreparingDataSource?.(false)
if (isPreparingDataSource)
setIsPreparingDataSource?.(false)
handleCancelDebugAndPreviewPanel()
}, [workflowStore])

View File

@ -104,7 +104,7 @@ export const useNodesSyncDraft = () => {
const res = await syncWorkflowDraft(postParams)
setSyncWorkflowDraftHash(res.hash)
setDraftUpdatedAt(res.updated_at)
callback?.onSuccess && callback.onSuccess()
callback?.onSuccess?.()
}
catch (error: any) {
if (error && error.json && !error.bodyUsed) {
@ -113,10 +113,10 @@ export const useNodesSyncDraft = () => {
handleRefreshWorkflowDraft()
})
}
callback?.onError && callback.onError()
callback?.onError?.()
}
finally {
callback?.onSettled && callback.onSettled()
callback?.onSettled?.()
}
}
}, [getPostParams, getNodesReadOnly, workflowStore, handleRefreshWorkflowDraft])

View File

@ -363,7 +363,8 @@ const TextGeneration: FC<IMainProps> = ({
(async () => {
if (!appData || !appParams)
return
!isWorkflow && fetchSavedMessage()
if (!isWorkflow)
fetchSavedMessage()
const { app_id: appId, site: siteInfo, custom_config } = appData
setAppId(appId)
setSiteInfo(siteInfo as SiteInfo)

View File

@ -78,15 +78,15 @@ const Result: FC<IResultProps> = ({
setRespondingFalse()
}, [controlStopResponding])
const [completionRes, doSetCompletionRes] = useState<any>('')
const completionResRef = useRef<any>()
const setCompletionRes = (res: any) => {
const [completionRes, doSetCompletionRes] = useState<string>('')
const completionResRef = useRef<string>('')
const setCompletionRes = (res: string) => {
completionResRef.current = res
doSetCompletionRes(res)
}
const getCompletionRes = () => completionResRef.current
const [workflowProcessData, doSetWorkflowProcessData] = useState<WorkflowProcess>()
const workflowProcessDataRef = useRef<WorkflowProcess>()
const workflowProcessDataRef = useRef<WorkflowProcess | undefined>(undefined)
const setWorkflowProcessData = (data: WorkflowProcess) => {
workflowProcessDataRef.current = data
doSetWorkflowProcessData(data)

View File

@ -62,8 +62,10 @@ const SwrInitializer = ({
return
}
if (searchParams.has('access_token') || searchParams.has('refresh_token')) {
consoleToken && localStorage.setItem('console_token', consoleToken)
refreshToken && localStorage.setItem('refresh_token', refreshToken)
if (consoleToken)
localStorage.setItem('console_token', consoleToken)
if (refreshToken)
localStorage.setItem('refresh_token', refreshToken)
const redirectUrl = resolvePostLoginRedirect(searchParams)
if (redirectUrl)
location.replace(redirectUrl)

View File

@ -45,6 +45,7 @@ export const toolCredentialToFormSchemas = (parameters: ToolCredential[]) => {
return {
...parameter,
variable: parameter.name,
type: toType(parameter.type),
label: parameter.label,
tooltip: parameter.help,
show_on: [],

View File

@ -122,7 +122,7 @@ export const useNodesSyncDraft = () => {
const res = await syncWorkflowDraft(postParams)
setSyncWorkflowDraftHash(res.hash)
setDraftUpdatedAt(res.updated_at)
callback?.onSuccess && callback.onSuccess()
callback?.onSuccess?.()
}
catch (error: any) {
if (error && error.json && !error.bodyUsed) {
@ -131,10 +131,10 @@ export const useNodesSyncDraft = () => {
handleRefreshWorkflowDraft()
})
}
callback?.onError && callback.onError()
callback?.onError?.()
}
finally {
callback?.onSettled && callback.onSettled()
callback?.onSettled?.()
}
}
}, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft])

View File

@ -74,7 +74,7 @@ const Tool: FC<Props> = ({
if (isHovering && !isAllSelected) {
return (
<span className='system-xs-regular text-components-button-secondary-accent-text'
onClick={(e) => {
onClick={() => {
onSelectMultiple?.(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => {
const params: Record<string, string> = {}
if (tool.parameters) {

View File

@ -108,7 +108,8 @@ export const useShortcuts = (): void => {
const { showDebugAndPreviewPanel } = workflowStore.getState()
if (shouldHandleShortcut(e) && !showDebugAndPreviewPanel) {
e.preventDefault()
workflowHistoryShortcutsEnabled && handleHistoryBack()
if (workflowHistoryShortcutsEnabled)
handleHistoryBack()
}
}, { exactMatch: true, useCapture: true })
@ -117,7 +118,8 @@ export const useShortcuts = (): void => {
(e) => {
if (shouldHandleShortcut(e)) {
e.preventDefault()
workflowHistoryShortcutsEnabled && handleHistoryForward()
if (workflowHistoryShortcutsEnabled)
handleHistoryForward()
}
},
{ exactMatch: true, useCapture: true },

View File

@ -41,16 +41,16 @@ export const useWorkflowHistory = () => {
const { store: workflowHistoryStore } = useWorkflowHistoryStore()
const { t } = useTranslation()
const [undoCallbacks, setUndoCallbacks] = useState<any[]>([])
const [redoCallbacks, setRedoCallbacks] = useState<any[]>([])
const [undoCallbacks, setUndoCallbacks] = useState<(() => void)[]>([])
const [redoCallbacks, setRedoCallbacks] = useState<(() => void)[]>([])
const onUndo = useCallback((callback: unknown) => {
setUndoCallbacks((prev: any) => [...prev, callback])
const onUndo = useCallback((callback: () => void) => {
setUndoCallbacks(prev => [...prev, callback])
return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback))
}, [])
const onRedo = useCallback((callback: unknown) => {
setRedoCallbacks((prev: any) => [...prev, callback])
const onRedo = useCallback((callback: () => void) => {
setRedoCallbacks(prev => [...prev, callback])
return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback))
}, [])

View File

@ -386,7 +386,7 @@ export const useWorkflow = () => {
return startNodes
}, [nodesMap, getRootNodesById])
const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
const isValidConnection = useCallback(({ source, sourceHandle: _sourceHandle, target }: Connection) => {
const {
edges,
getNodes,

View File

@ -129,7 +129,7 @@ const VarReferencePicker: FC<Props> = ({
const reactflow = useReactFlow()
const startNode = availableNodes.find((node: any) => {
const startNode = availableNodes.find((node: Node) => {
return node.data.type === BlockEnum.Start
})
@ -409,7 +409,10 @@ const VarReferencePicker: FC<Props> = ({
<WrapElem onClick={() => {
if (readonly)
return
!isConstant ? setOpen(!open) : setControlFocus(Date.now())
if (!isConstant)
setOpen(!open)
else
setControlFocus(Date.now())
}} className='group/picker-trigger-wrap relative !flex'>
<>
{isAddBtnTrigger
@ -459,7 +462,10 @@ const VarReferencePicker: FC<Props> = ({
onClick={() => {
if (readonly)
return
!isConstant ? setOpen(!open) : setControlFocus(Date.now())
if (!isConstant)
setOpen(!open)
else
setControlFocus(Date.now())
}}
className='h-full grow'
>

View File

@ -137,7 +137,7 @@ const Item: FC<ItemProps> = ({
const isHovering = isItemHovering || isChildrenHovering
const open = (isObj || isStructureOutput) && isHovering
useEffect(() => {
onHovering && onHovering(isHovering)
onHovering?.(isHovering)
}, [isHovering])
const handleChosen = (e: React.MouseEvent) => {
e.stopPropagation()

View File

@ -25,12 +25,12 @@ type Props = {
} & Partial<ResultPanelProps>
const LastRun: FC<Props> = ({
appId,
appId: _appId,
nodeId,
canSingleRun,
isRunAfterSingleRun,
updateNodeRunningStatus,
nodeInfo,
nodeInfo: _nodeInfo,
runningStatus: oneStepRunRunningStatus,
onSingleRunClicked,
singleRunResult,

View File

@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next'
import type { Timeout as TimeoutPayloadType } from '../../types'
import Input from '@/app/components/base/input'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
type Props = {
readonly: boolean
@ -61,6 +63,11 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
const { t } = useTranslation()
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
// Get default config from store for max timeout values
const nodesDefaultConfigs = useStore(s => s.nodesDefaultConfigs)
const defaultConfig = nodesDefaultConfigs?.[BlockEnum.HttpRequest]
const defaultTimeout = defaultConfig?.timeout || {}
return (
<FieldCollapse title={t(`${i18nPrefix}.timeout.title`)}>
<div className='mt-2 space-y-1'>
@ -73,7 +80,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
value={connect}
onChange={v => onChange?.({ ...payload, connect: v })}
min={1}
max={max_connect_timeout || 300}
max={max_connect_timeout || defaultTimeout.max_connect_timeout || 10}
/>
<InputField
title={t('workflow.nodes.http.timeout.readLabel')!}
@ -83,7 +90,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
value={read}
onChange={v => onChange?.({ ...payload, read: v })}
min={1}
max={max_read_timeout || 600}
max={max_read_timeout || defaultTimeout.max_read_timeout || 600}
/>
<InputField
title={t('workflow.nodes.http.timeout.writeLabel')!}
@ -93,7 +100,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
value={write}
onChange={v => onChange?.({ ...payload, write: v })}
min={1}
max={max_write_timeout || 600}
max={max_write_timeout || defaultTimeout.max_write_timeout || 600}
/>
</div>
</div>

View File

@ -88,7 +88,8 @@ const OptionCard = memo(({
)}
onClick={(e) => {
e.stopPropagation()
!readonly && enableSelect && id && onClick?.(id)
if (!readonly && enableSelect && id)
onClick?.(id)
}}
>
<div className={cn(

View File

@ -120,7 +120,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
setJson(JSON.stringify(schema, null, 2))
}, [currentTab])
const handleSubmit = useCallback((schema: any) => {
const handleSubmit = useCallback((schema: Record<string, unknown>) => {
const jsonSchema = jsonToSchema(schema) as SchemaRoot
if (currentTab === SchemaView.VisualEditor)
setJsonSchema(jsonSchema)
@ -139,8 +139,10 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
const handleResetDefaults = useCallback(() => {
if (currentTab === SchemaView.VisualEditor) {
setHoveringProperty(null)
advancedEditing && setAdvancedEditing(false)
isAddingNewField && setIsAddingNewField(false)
if (advancedEditing)
setAdvancedEditing(false)
if (isAddingNewField)
setIsAddingNewField(false)
}
setJsonSchema(DEFAULT_SCHEMA)
setJson(JSON.stringify(DEFAULT_SCHEMA, null, 2))

View File

@ -87,8 +87,10 @@ const EditCard: FC<EditCardProps> = ({
})
useSubscribe('fieldChangeSuccess', () => {
isAddingNewField && setIsAddingNewField(false)
advancedEditing && setAdvancedEditing(false)
if (isAddingNewField)
setIsAddingNewField(false)
if (advancedEditing)
setAdvancedEditing(false)
})
const emitPropertyNameChange = useCallback(() => {
@ -150,14 +152,16 @@ const EditCard: FC<EditCardProps> = ({
}, [isAdvancedEditing, emitPropertyOptionsChange, currentFields])
const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => {
let enumValue: any = options.enum
if (enumValue === '') {
let enumValue: SchemaEnumType | undefined
if (options.enum === '') {
enumValue = undefined
}
else {
enumValue = options.enum.replace(/\s/g, '').split(',')
const stringArray = options.enum.replace(/\s/g, '').split(',')
if (currentFields.type === Type.number)
enumValue = (enumValue as SchemaEnumType).map(value => Number(value)).filter(num => !Number.isNaN(num))
enumValue = stringArray.map(value => Number(value)).filter(num => !Number.isNaN(num))
else
enumValue = stringArray
}
setCurrentFields(prev => ({ ...prev, enum: enumValue }))
if (isAdvancedEditing) return

View File

@ -45,8 +45,10 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
onChange(backupSchema)
setBackupSchema(null)
}
isAddingNewField && setIsAddingNewField(false)
advancedEditing && setAdvancedEditing(false)
if (isAddingNewField)
setIsAddingNewField(false)
if (advancedEditing)
setAdvancedEditing(false)
setHoveringProperty(null)
})
@ -221,7 +223,8 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
})
useSubscribe('addField', (params) => {
advancedEditing && setAdvancedEditing(false)
if (advancedEditing)
setAdvancedEditing(false)
setBackupSchema(jsonSchema)
const { path } = params as AddEventParams
setIsAddingNewField(true)

View File

@ -293,6 +293,11 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
type='string'
description={t(`${i18nPrefix}.outputVars.output`)}
/>
<VarItem
name='reasoning_content'
type='string'
description={t(`${i18nPrefix}.outputVars.reasoning_content`)}
/>
<VarItem
name='usage'
type='object'

View File

@ -22,7 +22,7 @@ type ConditionValueProps = {
}
const ConditionValue = ({
variableSelector,
labelName,
labelName: _labelName,
operator,
value,
}: ConditionValueProps) => {

View File

@ -35,7 +35,8 @@ const VariableModalTrigger = ({
open={open}
onOpenChange={() => {
setOpen(v => !v)
open && onClose()
if (open)
onClose()
}}
placement='left-start'
offset={{
@ -45,7 +46,8 @@ const VariableModalTrigger = ({
>
<PortalToFollowElemTrigger onClick={() => {
setOpen(v => !v)
open && onClose()
if (open)
onClose()
}}>
<Button variant='primary'>
<RiAddLine className='mr-1 h-4 w-4' />

View File

@ -33,7 +33,8 @@ const VariableTrigger = ({
open={open}
onOpenChange={() => {
setOpen(v => !v)
open && onClose()
if (open)
onClose()
}}
placement='left-start'
offset={{
@ -43,7 +44,8 @@ const VariableTrigger = ({
>
<PortalToFollowElemTrigger onClick={() => {
setOpen(v => !v)
open && onClose()
if (open)
onClose()
}}>
<Button variant='primary'>
<RiAddLine className='mr-1 h-4 w-4' />

View File

@ -86,9 +86,12 @@ const RunPanel: FC<RunProps> = ({
const switchTab = async (tab: string) => {
setCurrentTab(tab)
if (tab === 'RESULT')
runDetailUrl && await getResult()
tracingListUrl && await getTracingList()
if (tab === 'RESULT') {
if (runDetailUrl)
await getResult()
}
if (tracingListUrl)
await getTracingList()
}
useEffect(() => {

View File

@ -15,7 +15,7 @@ import { useNodeLoopInteractions } from './hooks'
const Node: FC<NodeProps<LoopNodeType>> = ({
id,
data,
data: _data,
}) => {
const { zoom } = useViewport()
const nodesInitialized = useNodesInitialized()

View File

@ -19,7 +19,7 @@ type MailAndPasswordAuthProps = {
allowRegistration: boolean
}
export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegistration }: MailAndPasswordAuthProps) {
export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegistration: _allowRegistration }: MailAndPasswordAuthProps) {
const { t } = useTranslation()
const { locale } = useContext(I18NContext)
const router = useRouter()