From ef043c690607053814a477bbd8fa0ea0a3165772 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 6 Nov 2025 14:53:11 +0800 Subject: [PATCH 01/13] fix: no app not show problem --- web/app/components/explore/sidebar/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/app/components/explore/sidebar/index.tsx b/web/app/components/explore/sidebar/index.tsx index a9d67ae2da..afae4c7d67 100644 --- a/web/app/components/explore/sidebar/index.tsx +++ b/web/app/components/explore/sidebar/index.tsx @@ -88,10 +88,16 @@ const SideBar: FC = ({ {!isMobile && !isFold &&
{t('explore.sidebar.title')}
} + + {installedApps.length === 0 && !isMobile && !isFold + &&
+ +
+ } + {installedApps.length > 0 && (
{!isMobile && !isFold &&

{t('explore.sidebar.webApps')}

} - {installedApps.length === 0 && !isMobile && !isFold && }
Date: Wed, 5 Nov 2025 11:00:04 +0800 Subject: [PATCH 02/13] feat: new time range picker outline --- .../[appId]/overview/chart-view.tsx | 46 ++++----------- .../[appId]/overview/time-range-picker.tsx | 59 +++++++++++++++++++ .../assets/vender/other/hourglass-shape.svg | 3 + .../src/vender/other/HourglassShape.json | 27 +++++++++ .../icons/src/vender/other/HourglassShape.tsx | 20 +++++++ .../base/icons/src/vender/other/index.ts | 1 + 6 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx create mode 100644 web/app/components/base/icons/assets/vender/other/hourglass-shape.svg create mode 100644 web/app/components/base/icons/src/vender/other/HourglassShape.json create mode 100644 web/app/components/base/icons/src/vender/other/HourglassShape.tsx diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx index 9352f7c115..4e8857a9f9 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx @@ -5,16 +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 { sendGAEvent } from '@/utils/gtag' +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 = { @@ -27,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({ 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({ name: t('appLog.filter.period.today'), query: { start: today.startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } }) if (!appDetail) return null @@ -52,21 +41,10 @@ export default function ChartView({ appId, headerRight }: IChartViewProps) {
{t('common.appMenus.overview')}
- ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} - className='mt-0 !w-40' - notClearable={true} - onSelect={(item) => { - sendGAEvent(isWorkflow ? 'filter_workflow_overview_period' : 'filter_chat_conversation_overview_period', { - period: item.value, - period_name: item.name, - }) - 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'} +
{headerRight} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx new file mode 100644 index 0000000000..2b02488ad1 --- /dev/null +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx @@ -0,0 +1,59 @@ +'use client' +import type { PeriodParams } 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 { HourglassShape } from '@/app/components/base/icons/src/vender/other' +import TimePicker from '@/app/components/base/date-and-time-picker/time-picker' +import { noop } from 'lodash-es' +import { useTranslation } from 'react-i18next' + +const today = dayjs() + +type Props = { + ranges: { value: number; name: string }[] + onSelect: (payload: PeriodParams) => void + queryDateFormat: string +} + +const TimeRangePicker: FC = ({ + ranges, + onSelect, + queryDateFormat, +}) => { + const { t } = useTranslation() + console.log(Object.entries(ranges).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))) + + const handleSelectRange = useCallback((item: Item) => { + const { name, value } = item + let period: PeriodParams['query'] | null = null + if (value === 0) { + const startOfToday = today.startOf('day').format(queryDateFormat) + const endOfToday = today.endOf('day').format(queryDateFormat) + period = { start: startOfToday, end: endOfToday } + } + else { + period = { start: today.subtract(item.value as number, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } + } + onSelect({ query: period!, name }) + }, [onSelect]) + return ( +
+ ({ ...v, name: t(`appLog.filter.period.${v.name}`) }))} + className='mt-0 !w-40' + notClearable={true} + onSelect={handleSelectRange} + defaultValue={0} + /> + + +
+ ) +} +export default React.memo(TimeRangePicker) diff --git a/web/app/components/base/icons/assets/vender/other/hourglass-shape.svg b/web/app/components/base/icons/assets/vender/other/hourglass-shape.svg new file mode 100644 index 0000000000..533c3d788e --- /dev/null +++ b/web/app/components/base/icons/assets/vender/other/hourglass-shape.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/src/vender/other/HourglassShape.json b/web/app/components/base/icons/src/vender/other/HourglassShape.json new file mode 100644 index 0000000000..4b007e2e9c --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/HourglassShape.json @@ -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": "0.25" + }, + "children": [] + } + ] + }, + "name": "HourglassShape" +} diff --git a/web/app/components/base/icons/src/vender/other/HourglassShape.tsx b/web/app/components/base/icons/src/vender/other/HourglassShape.tsx new file mode 100644 index 0000000000..a1ef8c8d5f --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/HourglassShape.tsx @@ -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 & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'HourglassShape' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/other/index.ts b/web/app/components/base/icons/src/vender/other/index.ts index 8a7bb7ae28..89cbe9033d 100644 --- a/web/app/components/base/icons/src/vender/other/index.ts +++ b/web/app/components/base/icons/src/vender/other/index.ts @@ -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' From 97f0dff9e1930b199b10b3c8e0a94dc0524cb54e Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 5 Nov 2025 11:00:21 +0800 Subject: [PATCH 03/13] chore: remove log --- .../app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx index 2b02488ad1..096a22b09a 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx @@ -24,7 +24,6 @@ const TimeRangePicker: FC = ({ queryDateFormat, }) => { const { t } = useTranslation() - console.log(Object.entries(ranges).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))) const handleSelectRange = useCallback((item: Item) => { const { name, value } = item From 2cd322af2107bd726f77f363cd26dd9bc58b91d9 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 5 Nov 2025 14:13:38 +0800 Subject: [PATCH 04/13] fix: hanle select date ui --- .../[appId]/overview/chart-view.tsx | 12 +++---- .../[appId]/overview/time-range-picker.tsx | 36 +++++++++++++++++++ .../assets/vender/other/hourglass-shape.svg | 2 +- .../src/vender/other/HourglassShape.json | 2 +- web/app/components/base/select/index.tsx | 4 +-- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx index 4e8857a9f9..d5430b03ad 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx @@ -40,13 +40,11 @@ export default function ChartView({ appId, headerRight }: IChartViewProps) {
{t('common.appMenus.overview')}
-
- -
+ {headerRight}
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx index 096a22b09a..e41277101b 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx @@ -9,6 +9,8 @@ import { HourglassShape } from '@/app/components/base/icons/src/vender/other' import TimePicker from '@/app/components/base/date-and-time-picker/time-picker' import { noop } from 'lodash-es' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' +import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' const today = dayjs() @@ -25,6 +27,35 @@ const TimeRangePicker: FC = ({ }) => { const { t } = useTranslation() + const isCustom = false + + const renderRangeTrigger = useCallback((item: Item | null, isOpen: boolean) => { + // todo: custom i18n and last 30days i18n + return ( +
+
{isCustom ? 'Custom' : item?.name}
+ +
+ ) + }, [isCustom]) + + const renderOption = useCallback(({ item, selected }: { item: Item; selected: boolean }) => { + return ( + <> + {selected && ( + + + )} + {item.name} + + ) + }, []) + const handleSelectRange = useCallback((item: Item) => { const { name, value } = item let period: PeriodParams['query'] | null = null @@ -46,6 +77,11 @@ const TimeRangePicker: FC = ({ notClearable={true} onSelect={handleSelectRange} defaultValue={0} + wrapperClassName='h-8' + optionWrapClassName='w-[200px] translate-x-[-24px]' + renderTrigger={renderRangeTrigger} + optionClassName='flex items-center py-0 pl-7 pr-2 h-8' + renderOption={renderOption} /> - + diff --git a/web/app/components/base/icons/src/vender/other/HourglassShape.json b/web/app/components/base/icons/src/vender/other/HourglassShape.json index 4b007e2e9c..27f31bbe28 100644 --- a/web/app/components/base/icons/src/vender/other/HourglassShape.json +++ b/web/app/components/base/icons/src/vender/other/HourglassShape.json @@ -17,7 +17,7 @@ "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": "0.25" + "fill-opacity": "1" }, "children": [] } diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index a1e8ac2724..f2ca32d660 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -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 = ({ > {({ open }) => (
- {renderTrigger && {renderTrigger(selectedItem)}} + {renderTrigger && {renderTrigger(selectedItem, open)}} {!renderTrigger && ( { onOpenChange?.(open) From 92ab453a5aee3311b32746319d002ecba825d9ac Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 5 Nov 2025 14:20:16 +0800 Subject: [PATCH 05/13] chore: some i18n --- .../(appDetailLayout)/[appId]/overview/time-range-picker.tsx | 3 +-- web/i18n/en-US/app-log.ts | 2 ++ web/i18n/ja-JP/app-log.ts | 1 + web/i18n/zh-Hans/app-log.ts | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx index e41277101b..fc6b70e8a0 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx @@ -30,10 +30,9 @@ const TimeRangePicker: FC = ({ const isCustom = false const renderRangeTrigger = useCallback((item: Item | null, isOpen: boolean) => { - // todo: custom i18n and last 30days i18n return (
-
{isCustom ? 'Custom' : item?.name}
+
{isCustom ? t('appLog.filter.period.custom') : item?.name}
) diff --git a/web/i18n/en-US/app-log.ts b/web/i18n/en-US/app-log.ts index 946d8ffcb7..e00e1cc675 100644 --- a/web/i18n/en-US/app-log.ts +++ b/web/i18n/en-US/app-log.ts @@ -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', diff --git a/web/i18n/ja-JP/app-log.ts b/web/i18n/ja-JP/app-log.ts index 714481c8d1..157483fc93 100644 --- a/web/i18n/ja-JP/app-log.ts +++ b/web/i18n/ja-JP/app-log.ts @@ -59,6 +59,7 @@ const translation = { period: { today: '今日', last7days: '過去 7 日間', + last30days: '過去 30 日間', last4weeks: '過去 4 週間', last3months: '過去 3 ヶ月', last12months: '過去 12 ヶ月', diff --git a/web/i18n/zh-Hans/app-log.ts b/web/i18n/zh-Hans/app-log.ts index 26c5c915c5..e4f441e25b 100644 --- a/web/i18n/zh-Hans/app-log.ts +++ b/web/i18n/zh-Hans/app-log.ts @@ -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: '全部', From 56dd17cdaad5fe186c5ee963db241ae988d5b1e1 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 5 Nov 2025 16:55:39 +0800 Subject: [PATCH 06/13] feat: range picker --- .../[appId]/overview/time-range-picker.tsx | 40 ++++++++++++--- .../date-and-time-picker/calendar/index.tsx | 4 +- .../date-and-time-picker/calendar/item.tsx | 4 +- .../date-picker/index.tsx | 2 + .../base/date-and-time-picker/types.ts | 3 ++ web/i18n/ja-JP/app-log.ts | 1 + web/utils/format.ts | 51 +++++++++++++++++++ 7 files changed, 97 insertions(+), 8 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx index fc6b70e8a0..a51d377d6f 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx @@ -4,13 +4,17 @@ 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 type { Dayjs } from 'dayjs' import dayjs from 'dayjs' import { HourglassShape } from '@/app/components/base/icons/src/vender/other' -import TimePicker from '@/app/components/base/date-and-time-picker/time-picker' import { noop } from 'lodash-es' import { useTranslation } from 'react-i18next' import cn from '@/utils/classnames' -import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' +import { RiArrowDownSLine, RiCalendarLine, RiCheckLine } from '@remixicon/react' +import DatePicker from '@/app/components/base/date-and-time-picker/date-picker' +import 'dayjs/locale/zh-cn' +import { formatToLocalTime } from '@/utils/format' +import { useI18N } from '@/context/i18n' const today = dayjs() @@ -68,6 +72,17 @@ const TimeRangePicker: FC = ({ } onSelect({ query: period!, name }) }, [onSelect]) + + const { locale } = useI18N() + + const renderDate = useCallback(({ value, handleClickTrigger, isOpen }: { value?: Dayjs, handleClickTrigger: () => void, isOpen: boolean }) => { + return ( +
+ {value ? formatToLocalTime(value, locale, 'MMM D') : ''} +
+ ) + }, [locale]) + return (
= ({ renderOption={renderOption} /> - +
+ + + - + +
+
) } diff --git a/web/app/components/base/date-and-time-picker/calendar/index.tsx b/web/app/components/base/date-and-time-picker/calendar/index.tsx index 00612fcb37..03dcb0eda3 100644 --- a/web/app/components/base/date-and-time-picker/calendar/index.tsx +++ b/web/app/components/base/date-and-time-picker/calendar/index.tsx @@ -8,9 +8,10 @@ const Calendar: FC = ({ selectedDate, onDateClick, wrapperClassName, + getIsDateDisabled, }) => { return
- +
{ days.map(day => = ({ day={day} selectedDate={selectedDate} onClick={onDateClick} + isDisabled={getIsDateDisabled ? getIsDateDisabled(day.date) : false} />) }
diff --git a/web/app/components/base/date-and-time-picker/calendar/item.tsx b/web/app/components/base/date-and-time-picker/calendar/item.tsx index 1da8b9b3b5..7132d7bdfb 100644 --- a/web/app/components/base/date-and-time-picker/calendar/item.tsx +++ b/web/app/components/base/date-and-time-picker/calendar/item.tsx @@ -7,6 +7,7 @@ const Item: FC = ({ day, selectedDate, onClick, + isDisabled, }) => { const { date, isCurrentMonth } = day const isSelected = selectedDate?.isSame(date, 'date') @@ -14,11 +15,12 @@ const Item: FC = ({ return (