mirror of
https://gitee.com/dify_ai/dify.git
synced 2025-12-06 19:42:42 +08:00
Compare commits
10 Commits
feat/sqlal
...
chore/chan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b072606928 | ||
|
|
6ad7633fb6 | ||
|
|
fe1f0fd5e6 | ||
|
|
5aba9b639a | ||
|
|
6f2981a833 | ||
|
|
a463c791b9 | ||
|
|
5e35322e9a | ||
|
|
9a64edd4ca | ||
|
|
0e21c93a16 | ||
|
|
b8ad9be0d5 |
@@ -5,15 +5,19 @@ import quarterOfYear from 'dayjs/plugin/quarterOfYear'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { PeriodParams } from '@/app/components/app/overview/app-chart'
|
||||
import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, MessagesChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/app-chart'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import { TIME_PERIOD_MAPPING } from '@/app/components/app/log/filter'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import TimeRangePicker from './time-range-picker'
|
||||
|
||||
dayjs.extend(quarterOfYear)
|
||||
|
||||
const today = dayjs()
|
||||
|
||||
const TIME_PERIOD_MAPPING = [
|
||||
{ value: 0, name: 'today' },
|
||||
{ value: 7, name: 'last7days' },
|
||||
{ value: 30, name: 'last30days' },
|
||||
]
|
||||
|
||||
const queryDateFormat = 'YYYY-MM-DD HH:mm'
|
||||
|
||||
export type IChartViewProps = {
|
||||
@@ -26,21 +30,7 @@ export default function ChartView({ appId, headerRight }: IChartViewProps) {
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
|
||||
const isWorkflow = appDetail?.mode === 'workflow'
|
||||
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } })
|
||||
|
||||
const onSelect = (item: Item) => {
|
||||
if (item.value === -1) {
|
||||
setPeriod({ name: item.name, query: undefined })
|
||||
}
|
||||
else if (item.value === 0) {
|
||||
const startOfToday = today.startOf('day').format(queryDateFormat)
|
||||
const endOfToday = today.endOf('day').format(queryDateFormat)
|
||||
setPeriod({ name: item.name, query: { start: startOfToday, end: endOfToday } })
|
||||
}
|
||||
else {
|
||||
setPeriod({ name: item.name, query: { start: today.subtract(item.value as number, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } })
|
||||
}
|
||||
}
|
||||
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.today'), query: { start: today.startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } })
|
||||
|
||||
if (!appDetail)
|
||||
return null
|
||||
@@ -50,20 +40,11 @@ export default function ChartView({ appId, headerRight }: IChartViewProps) {
|
||||
<div className='mb-4'>
|
||||
<div className='system-xl-semibold mb-2 text-text-primary'>{t('common.appMenus.overview')}</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex flex-row items-center'>
|
||||
<SimpleSelect
|
||||
items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))}
|
||||
className='mt-0 !w-40'
|
||||
notClearable={true}
|
||||
onSelect={(item) => {
|
||||
const id = item.value
|
||||
const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1'
|
||||
const name = item.name || t('appLog.filter.period.allTime')
|
||||
onSelect({ value, name })
|
||||
}}
|
||||
defaultValue={'2'}
|
||||
/>
|
||||
</div>
|
||||
<TimeRangePicker
|
||||
ranges={TIME_PERIOD_MAPPING}
|
||||
onSelect={setPeriod}
|
||||
queryDateFormat={queryDateFormat}
|
||||
/>
|
||||
{headerRight}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
'use client'
|
||||
import { RiCalendarLine } from '@remixicon/react'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { formatToLocalTime } from '@/utils/format'
|
||||
import { useI18N } from '@/context/i18n'
|
||||
import Picker from '@/app/components/base/date-and-time-picker/date-picker'
|
||||
import type { TriggerProps } from '@/app/components/base/date-and-time-picker/types'
|
||||
import { noop } from 'lodash-es'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
type Props = {
|
||||
start: Dayjs
|
||||
end: Dayjs
|
||||
onStartChange: (date?: Dayjs) => void
|
||||
onEndChange: (date?: Dayjs) => void
|
||||
}
|
||||
|
||||
const today = dayjs()
|
||||
const DatePicker: FC<Props> = ({
|
||||
start,
|
||||
end,
|
||||
onStartChange,
|
||||
onEndChange,
|
||||
}) => {
|
||||
const { locale } = useI18N()
|
||||
|
||||
const renderDate = useCallback(({ value, handleClickTrigger, isOpen }: TriggerProps) => {
|
||||
return (
|
||||
<div className={cn('system-sm-regular flex h-7 cursor-pointer items-center rounded-lg px-1 text-components-input-text-filled hover:bg-state-base-hover', isOpen && 'bg-state-base-hover')} onClick={handleClickTrigger}>
|
||||
{value ? formatToLocalTime(value, locale, 'MMM D') : ''}
|
||||
</div>
|
||||
)
|
||||
}, [locale])
|
||||
|
||||
const availableStartDate = end.subtract(30, 'day')
|
||||
const startDateDisabled = useCallback((date: Dayjs) => {
|
||||
if (date.isAfter(today, 'date'))
|
||||
return true
|
||||
return !((date.isAfter(availableStartDate, 'date') || date.isSame(availableStartDate, 'date')) && (date.isBefore(end, 'date') || date.isSame(end, 'date')))
|
||||
}, [availableStartDate, end])
|
||||
|
||||
const availableEndDate = start.add(30, 'day')
|
||||
const endDateDisabled = useCallback((date: Dayjs) => {
|
||||
if (date.isAfter(today, 'date'))
|
||||
return true
|
||||
return !((date.isAfter(start, 'date') || date.isSame(start, 'date')) && (date.isBefore(availableEndDate, 'date') || date.isSame(availableEndDate, 'date')))
|
||||
}, [availableEndDate, start])
|
||||
|
||||
return (
|
||||
<div className='flex h-8 items-center space-x-0.5 rounded-lg bg-components-input-bg-normal px-2'>
|
||||
<div className='p-px'>
|
||||
<RiCalendarLine className='size-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
<Picker
|
||||
value={start}
|
||||
onChange={onStartChange}
|
||||
renderTrigger={renderDate}
|
||||
needTimePicker={false}
|
||||
onClear={noop}
|
||||
noConfirm
|
||||
getIsDateDisabled={startDateDisabled}
|
||||
/>
|
||||
<span className='system-sm-regular text-text-tertiary'>-</span>
|
||||
<Picker
|
||||
value={end}
|
||||
onChange={onEndChange}
|
||||
renderTrigger={renderDate}
|
||||
needTimePicker={false}
|
||||
onClear={noop}
|
||||
noConfirm
|
||||
getIsDateDisabled={endDateDisabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
export default React.memo(DatePicker)
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client'
|
||||
import type { PeriodParams, PeriodParamsWithTimeRange } from '@/app/components/app/overview/app-chart'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import { HourglassShape } from '@/app/components/base/icons/src/vender/other'
|
||||
import RangeSelector from './range-selector'
|
||||
import DatePicker from './date-picker'
|
||||
import dayjs from 'dayjs'
|
||||
import { useI18N } from '@/context/i18n'
|
||||
import { formatToLocalTime } from '@/utils/format'
|
||||
|
||||
const today = dayjs()
|
||||
|
||||
type Props = {
|
||||
ranges: { value: number; name: string }[]
|
||||
onSelect: (payload: PeriodParams) => void
|
||||
queryDateFormat: string
|
||||
}
|
||||
|
||||
const TimeRangePicker: FC<Props> = ({
|
||||
ranges,
|
||||
onSelect,
|
||||
queryDateFormat,
|
||||
}) => {
|
||||
const { locale } = useI18N()
|
||||
|
||||
const [isCustomRange, setIsCustomRange] = useState(false)
|
||||
const [start, setStart] = useState<Dayjs>(today)
|
||||
const [end, setEnd] = useState<Dayjs>(today)
|
||||
|
||||
const handleRangeChange = useCallback((payload: PeriodParamsWithTimeRange) => {
|
||||
setIsCustomRange(false)
|
||||
setStart(payload.query!.start)
|
||||
setEnd(payload.query!.end)
|
||||
onSelect({
|
||||
name: payload.name,
|
||||
query: {
|
||||
start: payload.query!.start.format(queryDateFormat),
|
||||
end: payload.query!.end.format(queryDateFormat),
|
||||
},
|
||||
})
|
||||
}, [onSelect])
|
||||
|
||||
const handleDateChange = useCallback((type: 'start' | 'end') => {
|
||||
return (date?: Dayjs) => {
|
||||
if (!date) return
|
||||
if (type === 'start' && date.isSame(start)) return
|
||||
if (type === 'end' && date.isSame(end)) return
|
||||
if (type === 'start')
|
||||
setStart(date)
|
||||
else
|
||||
setEnd(date)
|
||||
|
||||
const currStart = type === 'start' ? date : start
|
||||
const currEnd = type === 'end' ? date : end
|
||||
onSelect({
|
||||
name: `${formatToLocalTime(currStart, locale, 'MMM D')} - ${formatToLocalTime(currEnd, locale, 'MMM D')}`,
|
||||
query: {
|
||||
start: currStart.format(queryDateFormat),
|
||||
end: currEnd.format(queryDateFormat),
|
||||
},
|
||||
})
|
||||
|
||||
setIsCustomRange(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<RangeSelector
|
||||
isCustomRange={isCustomRange}
|
||||
ranges={ranges}
|
||||
onSelect={handleRangeChange}
|
||||
/>
|
||||
<HourglassShape className='h-3.5 w-2 text-components-input-bg-normal' />
|
||||
<DatePicker
|
||||
start={start}
|
||||
end={end}
|
||||
onStartChange={handleDateChange('start')}
|
||||
onEndChange={handleDateChange('end')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(TimeRangePicker)
|
||||
@@ -0,0 +1,81 @@
|
||||
'use client'
|
||||
import type { PeriodParamsWithTimeRange, TimeRange } from '@/app/components/app/overview/app-chart'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import dayjs from 'dayjs'
|
||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const today = dayjs()
|
||||
|
||||
type Props = {
|
||||
isCustomRange: boolean
|
||||
ranges: { value: number; name: string }[]
|
||||
onSelect: (payload: PeriodParamsWithTimeRange) => void
|
||||
}
|
||||
|
||||
const RangeSelector: FC<Props> = ({
|
||||
isCustomRange,
|
||||
ranges,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleSelectRange = useCallback((item: Item) => {
|
||||
const { name, value } = item
|
||||
let period: TimeRange | null = null
|
||||
if (value === 0) {
|
||||
const startOfToday = today.startOf('day')
|
||||
const endOfToday = today.endOf('day')
|
||||
period = { start: startOfToday, end: endOfToday }
|
||||
}
|
||||
else {
|
||||
period = { start: today.subtract(item.value as number, 'day').startOf('day'), end: today.endOf('day') }
|
||||
}
|
||||
onSelect({ query: period!, name })
|
||||
}, [onSelect])
|
||||
|
||||
const renderTrigger = useCallback((item: Item | null, isOpen: boolean) => {
|
||||
return (
|
||||
<div className={cn('flex h-8 cursor-pointer items-center space-x-1.5 rounded-lg bg-components-input-bg-normal pl-3 pr-2', isOpen && 'bg-state-base-hover-alt')}>
|
||||
<div className='system-sm-regular text-components-input-text-filled'>{isCustomRange ? t('appLog.filter.period.custom') : item?.name}</div>
|
||||
<RiArrowDownSLine className={cn('size-4 text-text-quaternary', isOpen && 'text-text-secondary')} />
|
||||
</div>
|
||||
)
|
||||
}, [isCustomRange])
|
||||
|
||||
const renderOption = useCallback(({ item, selected }: { item: Item; selected: boolean }) => {
|
||||
return (
|
||||
<>
|
||||
{selected && (
|
||||
<span
|
||||
className={cn(
|
||||
'absolute left-2 top-[9px] flex items-center text-text-accent',
|
||||
)}
|
||||
>
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
<span className={cn('system-md-regular block truncate')}>{item.name}</span>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
return (
|
||||
<SimpleSelect
|
||||
items={ranges.map(v => ({ ...v, name: t(`appLog.filter.period.${v.name}`) }))}
|
||||
className='mt-0 !w-40'
|
||||
notClearable={true}
|
||||
onSelect={handleSelectRange}
|
||||
defaultValue={0}
|
||||
wrapperClassName='h-8'
|
||||
optionWrapClassName='w-[200px] translate-x-[-24px]'
|
||||
renderTrigger={renderTrigger}
|
||||
optionClassName='flex items-center py-0 pl-7 pr-2 h-8'
|
||||
renderOption={renderOption}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(RangeSelector)
|
||||
@@ -4,6 +4,7 @@ import React from 'react'
|
||||
import ReactECharts from 'echarts-for-react'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import useSWR from 'swr'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { get } from 'lodash-es'
|
||||
import Decimal from 'decimal.js'
|
||||
@@ -78,6 +79,16 @@ export type PeriodParams = {
|
||||
}
|
||||
}
|
||||
|
||||
export type TimeRange = {
|
||||
start: Dayjs
|
||||
end: Dayjs
|
||||
}
|
||||
|
||||
export type PeriodParamsWithTimeRange = {
|
||||
name: string
|
||||
query?: TimeRange
|
||||
}
|
||||
|
||||
export type IBizChartProps = {
|
||||
period: PeriodParams
|
||||
id: string
|
||||
@@ -215,9 +226,7 @@ const Chart: React.FC<IChartProps> = ({
|
||||
formatter(params) {
|
||||
return `<div style='color:#6B7280;font-size:12px'>${params.name}</div>
|
||||
<div style='font-size:14px;color:#1F2A37'>${valueFormatter((params.data as any)[yField])}
|
||||
${!CHART_TYPE_CONFIG[chartType].showTokens
|
||||
? ''
|
||||
: `<span style='font-size:12px'>
|
||||
${!CHART_TYPE_CONFIG[chartType].showTokens ? '' : `<span style='font-size:12px'>
|
||||
<span style='margin-left:4px;color:#6B7280'>(</span>
|
||||
<span style='color:#FF8A4C'>~$${get(params.data, 'total_price', 0)}</span>
|
||||
<span style='color:#6B7280'>)</span>
|
||||
|
||||
@@ -8,9 +8,10 @@ const Calendar: FC<CalendarProps> = ({
|
||||
selectedDate,
|
||||
onDateClick,
|
||||
wrapperClassName,
|
||||
getIsDateDisabled,
|
||||
}) => {
|
||||
return <div className={wrapperClassName}>
|
||||
<DaysOfWeek/>
|
||||
<DaysOfWeek />
|
||||
<div className='grid grid-cols-7 gap-0.5 p-2'>
|
||||
{
|
||||
days.map(day => <CalendarItem
|
||||
@@ -18,6 +19,7 @@ const Calendar: FC<CalendarProps> = ({
|
||||
day={day}
|
||||
selectedDate={selectedDate}
|
||||
onClick={onDateClick}
|
||||
isDisabled={getIsDateDisabled ? getIsDateDisabled(day.date) : false}
|
||||
/>)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ const Item: FC<CalendarItemProps> = ({
|
||||
day,
|
||||
selectedDate,
|
||||
onClick,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const { date, isCurrentMonth } = day
|
||||
const isSelected = selectedDate?.isSame(date, 'date')
|
||||
@@ -14,11 +15,12 @@ const Item: FC<CalendarItemProps> = ({
|
||||
|
||||
return (
|
||||
<button type="button"
|
||||
onClick={() => onClick(date)}
|
||||
onClick={() => !isDisabled && onClick(date)}
|
||||
className={cn(
|
||||
'system-sm-medium relative flex items-center justify-center rounded-lg px-1 py-2',
|
||||
isCurrentMonth ? 'text-text-secondary' : 'text-text-quaternary hover:text-text-secondary',
|
||||
isSelected ? 'system-sm-medium bg-components-button-primary-bg text-components-button-primary-text' : 'hover:bg-state-base-hover',
|
||||
isDisabled && 'cursor-not-allowed text-text-quaternary hover:bg-transparent',
|
||||
)}
|
||||
>
|
||||
{date.date()}
|
||||
|
||||
@@ -36,6 +36,8 @@ const DatePicker = ({
|
||||
renderTrigger,
|
||||
triggerWrapClassName,
|
||||
popupZIndexClassname = 'z-[11]',
|
||||
noConfirm,
|
||||
getIsDateDisabled,
|
||||
}: DatePickerProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
@@ -120,11 +122,20 @@ const DatePicker = ({
|
||||
setCurrentDate(currentDate.clone().subtract(1, 'month'))
|
||||
}, [currentDate])
|
||||
|
||||
const handleConfirmDate = (passedInSelectedDate?: Dayjs) => {
|
||||
// passedInSelectedDate may be a click event when noConfirm is false
|
||||
const nextDate = (dayjs.isDayjs(passedInSelectedDate) ? passedInSelectedDate : selectedDate)
|
||||
onChange(nextDate ? nextDate.tz(timezone) : undefined)
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleDateSelect = useCallback((day: Dayjs) => {
|
||||
const newDate = cloneTime(day, selectedDate || getDateWithTimezone({ timezone }))
|
||||
setCurrentDate(newDate)
|
||||
setSelectedDate(newDate)
|
||||
}, [selectedDate, timezone])
|
||||
if (noConfirm)
|
||||
handleConfirmDate(newDate)
|
||||
}, [selectedDate, timezone, noConfirm, handleConfirmDate])
|
||||
|
||||
const handleSelectCurrentDate = () => {
|
||||
const newDate = getDateWithTimezone({ timezone })
|
||||
@@ -134,12 +145,6 @@ const DatePicker = ({
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleConfirmDate = () => {
|
||||
// debugger
|
||||
onChange(selectedDate ? selectedDate.tz(timezone) : undefined)
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleClickTimePicker = () => {
|
||||
if (view === ViewType.date) {
|
||||
setView(ViewType.time)
|
||||
@@ -270,6 +275,7 @@ const DatePicker = ({
|
||||
days={days}
|
||||
selectedDate={selectedDate}
|
||||
onDateClick={handleDateSelect}
|
||||
getIsDateDisabled={getIsDateDisabled}
|
||||
/>
|
||||
) : view === ViewType.yearMonth ? (
|
||||
<YearAndMonthPickerOptions
|
||||
@@ -290,7 +296,7 @@ const DatePicker = ({
|
||||
|
||||
{/* Footer */}
|
||||
{
|
||||
[ViewType.date, ViewType.time].includes(view) ? (
|
||||
[ViewType.date, ViewType.time].includes(view) && !noConfirm && (
|
||||
<DatePickerFooter
|
||||
needTimePicker={needTimePicker}
|
||||
displayTime={displayTime}
|
||||
@@ -299,7 +305,10 @@ const DatePicker = ({
|
||||
handleSelectCurrentDate={handleSelectCurrentDate}
|
||||
handleConfirmDate={handleConfirmDate}
|
||||
/>
|
||||
) : (
|
||||
)
|
||||
}
|
||||
{
|
||||
![ViewType.date, ViewType.time].includes(view) && (
|
||||
<YearAndMonthPickerFooter
|
||||
handleYearMonthCancel={handleYearMonthCancel}
|
||||
handleYearMonthConfirm={handleYearMonthConfirm}
|
||||
|
||||
@@ -30,6 +30,8 @@ export type DatePickerProps = {
|
||||
renderTrigger?: (props: TriggerProps) => React.ReactNode
|
||||
minuteFilter?: (minutes: string[]) => string[]
|
||||
popupZIndexClassname?: string
|
||||
noConfirm?: boolean
|
||||
getIsDateDisabled?: (date: Dayjs) => boolean
|
||||
}
|
||||
|
||||
export type DatePickerHeaderProps = {
|
||||
@@ -80,12 +82,14 @@ export type CalendarProps = {
|
||||
selectedDate: Dayjs | undefined
|
||||
onDateClick: (date: Dayjs) => void
|
||||
wrapperClassName?: string
|
||||
getIsDateDisabled?: (date: Dayjs) => boolean
|
||||
}
|
||||
|
||||
export type CalendarItemProps = {
|
||||
day: Day
|
||||
selectedDate: Dayjs | undefined
|
||||
onClick: (date: Dayjs) => void
|
||||
isDisabled: boolean
|
||||
}
|
||||
|
||||
export type TimeOptionsProps = {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 14C8 11.7909 6.20914 10 4 10C1.79086 10 0 11.7909 0 14V0C8.05332e-08 2.20914 1.79086 4 4 4C6.20914 4 8 2.20914 8 0V14Z" fill="#C8CEDA" fill-opacity="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "8",
|
||||
"height": "14",
|
||||
"viewBox": "0 0 8 14",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M8 14C8 11.7909 6.20914 10 4 10C1.79086 10 0 11.7909 0 14V0C8.05332e-08 2.20914 1.79086 4 4 4C6.20914 4 8 2.20914 8 0V14Z",
|
||||
"fill": "currentColor",
|
||||
"fill-opacity": "1"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "HourglassShape"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './HourglassShape.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'HourglassShape'
|
||||
|
||||
export default Icon
|
||||
@@ -1,6 +1,7 @@
|
||||
export { default as AnthropicText } from './AnthropicText'
|
||||
export { default as Generator } from './Generator'
|
||||
export { default as Group } from './Group'
|
||||
export { default as HourglassShape } from './HourglassShape'
|
||||
export { default as Mcp } from './Mcp'
|
||||
export { default as NoToolPlaceholder } from './NoToolPlaceholder'
|
||||
export { default as Openai } from './Openai'
|
||||
|
||||
@@ -31,7 +31,7 @@ export type Item = {
|
||||
export type ISelectProps = {
|
||||
className?: string
|
||||
wrapperClassName?: string
|
||||
renderTrigger?: (value: Item | null) => React.JSX.Element | null
|
||||
renderTrigger?: (value: Item | null, isOpen: boolean) => React.JSX.Element | null
|
||||
items?: Item[]
|
||||
defaultValue?: number | string
|
||||
disabled?: boolean
|
||||
@@ -216,7 +216,7 @@ const SimpleSelect: FC<ISelectProps> = ({
|
||||
>
|
||||
{({ open }) => (
|
||||
<div className={classNames('group/simple-select relative h-9', wrapperClassName)}>
|
||||
{renderTrigger && <ListboxButton className='w-full'>{renderTrigger(selectedItem)}</ListboxButton>}
|
||||
{renderTrigger && <ListboxButton className='w-full'>{renderTrigger(selectedItem, open)}</ListboxButton>}
|
||||
{!renderTrigger && (
|
||||
<ListboxButton onClick={() => {
|
||||
onOpenChange?.(open)
|
||||
|
||||
@@ -59,6 +59,7 @@ const translation = {
|
||||
period: {
|
||||
today: 'Today',
|
||||
last7days: 'Last 7 Days',
|
||||
last30days: 'Last 30 Days',
|
||||
last4weeks: 'Last 4 weeks',
|
||||
last3months: 'Last 3 months',
|
||||
last12months: 'Last 12 months',
|
||||
@@ -66,6 +67,7 @@ const translation = {
|
||||
quarterToDate: 'Quarter to date',
|
||||
yearToDate: 'Year to date',
|
||||
allTime: 'All time',
|
||||
custom: 'Custom',
|
||||
},
|
||||
annotation: {
|
||||
all: 'All',
|
||||
|
||||
@@ -59,6 +59,7 @@ const translation = {
|
||||
period: {
|
||||
today: '今日',
|
||||
last7days: '過去 7 日間',
|
||||
last30days: '過去 30 日間',
|
||||
last4weeks: '過去 4 週間',
|
||||
last3months: '過去 3 ヶ月',
|
||||
last12months: '過去 12 ヶ月',
|
||||
@@ -66,6 +67,7 @@ const translation = {
|
||||
quarterToDate: '四半期初から今日まで',
|
||||
yearToDate: '年初から今日まで',
|
||||
allTime: 'すべての期間',
|
||||
custom: 'カスタム',
|
||||
},
|
||||
annotation: {
|
||||
all: 'すべて',
|
||||
|
||||
@@ -59,6 +59,7 @@ const translation = {
|
||||
period: {
|
||||
today: '今天',
|
||||
last7days: '过去 7 天',
|
||||
last30days: '过去 30 天',
|
||||
last4weeks: '过去 4 周',
|
||||
last3months: '过去 3 月',
|
||||
last12months: '过去 12 月',
|
||||
@@ -66,6 +67,7 @@ const translation = {
|
||||
quarterToDate: '本季度至今',
|
||||
yearToDate: '本年至今',
|
||||
allTime: '所有时间',
|
||||
custom: '自定义',
|
||||
},
|
||||
annotation: {
|
||||
all: '全部',
|
||||
|
||||
@@ -1,3 +1,50 @@
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import 'dayjs/locale/de'
|
||||
import 'dayjs/locale/es'
|
||||
import 'dayjs/locale/fa'
|
||||
import 'dayjs/locale/fr'
|
||||
import 'dayjs/locale/hi'
|
||||
import 'dayjs/locale/id'
|
||||
import 'dayjs/locale/it'
|
||||
import 'dayjs/locale/ja'
|
||||
import 'dayjs/locale/ko'
|
||||
import 'dayjs/locale/pl'
|
||||
import 'dayjs/locale/pt-br'
|
||||
import 'dayjs/locale/ro'
|
||||
import 'dayjs/locale/ru'
|
||||
import 'dayjs/locale/sl'
|
||||
import 'dayjs/locale/th'
|
||||
import 'dayjs/locale/tr'
|
||||
import 'dayjs/locale/uk'
|
||||
import 'dayjs/locale/vi'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import 'dayjs/locale/zh-tw'
|
||||
|
||||
const localeMap: Record<Locale, string> = {
|
||||
'en-US': 'en',
|
||||
'zh-Hans': 'zh-cn',
|
||||
'zh-Hant': 'zh-tw',
|
||||
'pt-BR': 'pt-br',
|
||||
'es-ES': 'es',
|
||||
'fr-FR': 'fr',
|
||||
'de-DE': 'de',
|
||||
'ja-JP': 'ja',
|
||||
'ko-KR': 'ko',
|
||||
'ru-RU': 'ru',
|
||||
'it-IT': 'it',
|
||||
'th-TH': 'th',
|
||||
'id-ID': 'id',
|
||||
'uk-UA': 'uk',
|
||||
'vi-VN': 'vi',
|
||||
'ro-RO': 'ro',
|
||||
'pl-PL': 'pl',
|
||||
'hi-IN': 'hi',
|
||||
'tr-TR': 'tr',
|
||||
'fa-IR': 'fa',
|
||||
'sl-SI': 'sl',
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number with comma separators.
|
||||
* @example formatNumber(1234567) will return '1,234,567'
|
||||
@@ -90,3 +137,7 @@ export const formatNumberAbbreviated = (num: number) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const formatToLocalTime = (time: Dayjs, local: string, format: string) => {
|
||||
return time.locale(localeMap[local] ?? 'en').format(format)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user