diff --git a/api/controllers/console/app/statistic.py b/api/controllers/console/app/statistic.py index bf65efeae4..81826a20d0 100644 --- a/api/controllers/console/app/statistic.py +++ b/api/controllers/console/app/statistic.py @@ -16,6 +16,60 @@ from libs.login import login_required from models.model import AppMode +class DailyMessageStatistic(Resource): + @setup_required + @login_required + @account_initialization_required + @get_app_model + def get(self, app_model): + account = current_user + + parser = reqparse.RequestParser() + parser.add_argument("start", type=datetime_string("%Y-%m-%d %H:%M"), location="args") + parser.add_argument("end", type=datetime_string("%Y-%m-%d %H:%M"), location="args") + args = parser.parse_args() + + sql_query = """ + SELECT date(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date, count(*) AS message_count + FROM messages where app_id = :app_id + """ + arg_dict = {"tz": account.timezone, "app_id": app_model.id} + + timezone = pytz.timezone(account.timezone) + utc_timezone = pytz.utc + + if args["start"]: + start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M") + start_datetime = start_datetime.replace(second=0) + + start_datetime_timezone = timezone.localize(start_datetime) + start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone) + + sql_query += " and created_at >= :start" + arg_dict["start"] = start_datetime_utc + + if args["end"]: + end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M") + end_datetime = end_datetime.replace(second=0) + + end_datetime_timezone = timezone.localize(end_datetime) + end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone) + + sql_query += " and created_at < :end" + arg_dict["end"] = end_datetime_utc + + sql_query += " GROUP BY date order by date" + + response_data = [] + + with db.engine.begin() as conn: + rs = conn.execute(db.text(sql_query), arg_dict) + for i in rs: + response_data.append({"date": str(i.date), "message_count": i.message_count}) + + return jsonify({"data": response_data}) + + class DailyConversationStatistic(Resource): @setup_required @login_required @@ -419,6 +473,7 @@ WHERE app_id = :app_id""" return jsonify({"data": response_data}) +api.add_resource(DailyMessageStatistic, "/apps//statistics/daily-messages") api.add_resource(DailyConversationStatistic, "/apps//statistics/daily-conversations") api.add_resource(DailyTerminalsStatistic, "/apps//statistics/daily-end-users") api.add_resource(DailyTokenCostStatistic, "/apps//statistics/token-costs") diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx index ff32a157fc..b01bc1b856 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx @@ -4,7 +4,7 @@ import dayjs from 'dayjs' import quarterOfYear from 'dayjs/plugin/quarterOfYear' import { useTranslation } from 'react-i18next' import type { PeriodParams } from '@/app/components/app/overview/appChart' -import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/appChart' +import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, MessagesChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/appChart' import type { Item } from '@/app/components/base/select' import { SimpleSelect } from '@/app/components/base/select' import { TIME_PERIOD_LIST } from '@/app/components/app/log/filter' @@ -79,6 +79,11 @@ export default function ChartView({ appId }: IChartViewProps) { )} + {!isWorkflow && isChatApp && ( +
+ +
+ )} {isWorkflow && (
diff --git a/web/app/components/app/overview/appChart.tsx b/web/app/components/app/overview/appChart.tsx index 7fd316a34b..e0788bcda3 100644 --- a/web/app/components/app/overview/appChart.tsx +++ b/web/app/components/app/overview/appChart.tsx @@ -10,8 +10,8 @@ import { useTranslation } from 'react-i18next' import { formatNumber } from '@/utils/format' import Basic from '@/app/components/app-sidebar/basic' import Loading from '@/app/components/base/loading' -import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppTokenCostsResponse } from '@/models/app' -import { getAppDailyConversations, getAppDailyEndUsers, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps' +import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppTokenCostsResponse } from '@/models/app' +import { getAppDailyConversations, getAppDailyEndUsers, getAppDailyMessages, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps' const valueFormatter = (v: string | number) => v const COLOR_TYPE_MAP = { @@ -36,12 +36,15 @@ const COMMON_COLOR_MAP = { } type IColorType = 'green' | 'orange' | 'blue' -type IChartType = 'conversations' | 'endUsers' | 'costs' | 'workflowCosts' +type IChartType = 'messages' | 'conversations' | 'endUsers' | 'costs' | 'workflowCosts' type IChartConfigType = { colorType: IColorType; showTokens?: boolean } const commonDateFormat = 'MMM D, YYYY' const CHART_TYPE_CONFIG: Record = { + messages: { + colorType: 'green', + }, conversations: { colorType: 'green', }, @@ -89,7 +92,7 @@ export type IChartProps = { unit?: string yMax?: number chartType: IChartType - chartData: AppDailyConversationsResponse | AppDailyEndUsersResponse | AppTokenCostsResponse | { data: Array<{ date: string; count: number }> } + chartData: AppDailyMessagesResponse | AppDailyConversationsResponse | AppDailyEndUsersResponse | AppTokenCostsResponse | { data: Array<{ date: string; count: number }> } } const Chart: React.FC = ({ @@ -258,6 +261,20 @@ const getDefaultChartData = ({ start, end, key = 'count' }: { start: string; end }) } +export const MessagesChart: FC = ({ id, period }) => { + const { t } = useTranslation() + const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-messages`, params: period.query }, getAppDailyMessages) + if (!response) + return + const noDataFlag = !response.data || response.data.length === 0 + return +} + export const ConversationsChart: FC = ({ id, period }) => { const { t } = useTranslation() const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-conversations`, params: period.query }, getAppDailyConversations) @@ -265,7 +282,7 @@ export const ConversationsChart: FC = ({ id, period }) => { return const noDataFlag = !response.data || response.data.length === 0 return +} + export type AppDailyConversationsResponse = { data: Array<{ date: string; conversation_count: number }> } diff --git a/web/service/apps.ts b/web/service/apps.ts index 6beba7bc61..84c7eebae3 100644 --- a/web/service/apps.ts +++ b/web/service/apps.ts @@ -1,6 +1,6 @@ import type { Fetcher } from 'swr' import { del, get, patch, post, put } from './base' -import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDetailResponse, AppListResponse, AppSSOResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app' +import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppSSOResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app' import type { CommonResponse } from '@/models/common' import type { AppIconType, AppMode, ModelConfig } from '@/types/app' import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' @@ -77,6 +77,10 @@ export const updateAppSiteConfig = ({ url, body }: { url: string; body: Record(url, { body }) } +export const getAppDailyMessages: Fetcher }> = ({ url, params }) => { + return get(url, { params }) +} + export const getAppDailyConversations: Fetcher }> = ({ url, params }) => { return get(url, { params }) }