diff --git a/api/controllers/console/__init__.py b/api/controllers/console/__init__.py index 15e5824db0..436f5a4ca0 100644 --- a/api/controllers/console/__init__.py +++ b/api/controllers/console/__init__.py @@ -8,7 +8,7 @@ api = ExternalApi(bp) from . import admin, apikey, extension, feature, setup, version, ping # Import app controllers from .app import (advanced_prompt_template, annotation, app, audio, completion, conversation, generator, message, - model_config, site, statistic, workflow, workflow_run, workflow_app_log) + model_config, site, statistic, workflow, workflow_run, workflow_app_log, workflow_statistic) # Import auth controllers from .auth import activate, data_source_oauth, login, oauth # Import billing controllers @@ -19,4 +19,4 @@ from .datasets import data_source, datasets, datasets_document, datasets_segment from .explore import (audio, completion, conversation, installed_app, message, parameter, recommended_app, saved_message, workflow) # Import workspace controllers -from .workspace import account, members, model_providers, models, tool_providers, workspace +from .workspace import account, members, model_providers, models, tool_providers, workspace \ No newline at end of file diff --git a/api/controllers/console/app/workflow_statistic.py b/api/controllers/console/app/workflow_statistic.py new file mode 100644 index 0000000000..d901a31ef7 --- /dev/null +++ b/api/controllers/console/app/workflow_statistic.py @@ -0,0 +1,278 @@ +from datetime import datetime +from decimal import Decimal + +import pytz +from flask import jsonify +from flask_login import current_user +from flask_restful import Resource, reqparse + +from controllers.console import api +from controllers.console.app.wraps import get_app_model +from controllers.console.setup import setup_required +from controllers.console.wraps import account_initialization_required +from extensions.ext_database import db +from libs.helper import datetime_string +from libs.login import login_required +from models.model import AppMode +from models.workflow import WorkflowRunTriggeredFrom + + +class WorkflowDailyRunsStatistic(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(id) AS runs + FROM workflow_runs + WHERE app_id = :app_id + AND triggered_from = :triggered_from + ''' + arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value} + + 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), + 'runs': i.runs + }) + + return jsonify({ + 'data': response_data + }) + +class WorkflowDailyTerminalsStatistic(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(distinct workflow_runs.created_by) AS terminal_count + FROM workflow_runs + WHERE app_id = :app_id + AND triggered_from = :triggered_from + ''' + arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value} + + 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), + 'terminal_count': i.terminal_count + }) + + return jsonify({ + 'data': response_data + }) + +class WorkflowDailyTokenCostStatistic(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, + SUM(workflow_runs.total_tokens) as token_count + FROM workflow_runs + WHERE app_id = :app_id + AND triggered_from = :triggered_from + ''' + arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value} + + 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), + 'token_count': i.token_count, + }) + + return jsonify({ + 'data': response_data + }) + +class WorkflowAverageAppInteractionStatistic(Resource): + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=[AppMode.WORKFLOW]) + 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 + AVG(sub.interactions) as interactions, + sub.date + FROM + (SELECT + date(DATE_TRUNC('day', c.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date, + c.created_by, + COUNT(c.id) AS interactions + FROM workflow_runs c + WHERE c.app_id = :app_id + AND c.triggered_from = :triggered_from + {{start}} + {{end}} + GROUP BY date, c.created_by) sub + GROUP BY sub.created_by, sub.date + """ + arg_dict = {'tz': account.timezone, 'app_id': app_model.id, 'triggered_from': WorkflowRunTriggeredFrom.APP_RUN.value} + + 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 = sql_query.replace('{{start}}', ' AND c.created_at >= :start') + arg_dict['start'] = start_datetime_utc + else: + sql_query = sql_query.replace('{{start}}', '') + + 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 = sql_query.replace('{{end}}', ' and c.created_at < :end') + arg_dict['end'] = end_datetime_utc + else: + sql_query = sql_query.replace('{{end}}', '') + + 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), + 'interactions': float(i.interactions.quantize(Decimal('0.01'))) + }) + + return jsonify({ + 'data': response_data + }) + +api.add_resource(WorkflowDailyRunsStatistic, '/apps//workflow/statistics/daily-conversations') +api.add_resource(WorkflowDailyTerminalsStatistic, '/apps//workflow/statistics/daily-terminals') +api.add_resource(WorkflowDailyTokenCostStatistic, '/apps//workflow/statistics/token-costs') +api.add_resource(WorkflowAverageAppInteractionStatistic, '/apps//workflow/statistics/average-app-interactions')