ParthSadaria commited on
Commit
0650cc5
·
verified ·
1 Parent(s): dadb7d6

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +494 -410
main.py CHANGED
@@ -727,471 +727,555 @@ def generate_usage_html(usage_data: dict, days: int = 7):
727
  daily_dates = list(usage_data['daily_usage_period'].keys())
728
  daily_requests = [data['requests'] for data in usage_data['daily_usage_period'].values()]
729
  daily_unique_ips = [data['unique_ips_count'] for data in usage_data['daily_usage_period'].values()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
 
731
  model_usage_all_time_rows = "\n".join([
732
  f"""
733
- <tr>
734
- <td>{model}</td>
735
- <td>{stats['total_requests']}</td>
736
- <td>{datetime.datetime.fromisoformat(stats['first_used']).strftime("%Y-%m-%d %H:%M")}</td>
737
- <td>{datetime.datetime.fromisoformat(stats['last_used']).strftime("%Y-%m-%d %H:%M")}</td>
 
 
 
 
 
 
 
 
 
738
  </tr>
739
  """ for model, stats in usage_data['all_time_model_usage'].items()
740
  ])
741
 
742
  api_usage_all_time_rows = "\n".join([
743
  f"""
744
- <tr>
745
- <td>{endpoint}</td>
746
- <td>{stats['total_requests']}</td>
747
- <td>{datetime.datetime.fromisoformat(stats['first_used']).strftime("%Y-%m-%d %H:%M")}</td>
748
- <td>{datetime.datetime.fromisoformat(stats['last_used']).strftime("%Y-%m-%d %H:%M")}</td>
 
 
 
 
 
 
 
 
 
749
  </tr>
750
  """ for endpoint, stats in usage_data['all_time_endpoint_usage'].items()
751
  ])
752
 
753
- daily_usage_table_rows = "\n".join([
754
- f"""
755
- <tr>
756
- <td>{date}</td>
757
- <td>{data['requests']}</td>
758
- <td>{data['unique_ips_count']}</td>
759
- </tr>
760
- """ for date, data in usage_data['daily_usage_period'].items()
761
- ])
762
-
763
  recent_requests_rows = "\n".join([
764
  f"""
765
- <tr>
766
- <td>{datetime.datetime.fromisoformat(req['timestamp']).strftime("%Y-%m-%d %H:%M:%S")}</td>
767
- <td>{req['model']}</td>
768
- <td>{req['endpoint']}</td>
769
- <td>{req['ip_address']}</td>
770
- <td>{req['user_agent']}</td>
 
 
 
 
771
  </tr>
772
  """ for req in usage_data['recent_requests']
773
  ])
774
-
775
  html_content = f"""
776
- <!DOCTYPE html>
777
- <html lang="en">
778
- <head>
779
- <meta charset="UTF-8">
780
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
781
- <title>Lokiai AI - Usage Statistics</title>
782
- <link href="[https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap](https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap)" rel="stylesheet">
783
-
784
- <script src="[https://cdn.jsdelivr.net/npm/chart.js](https://cdn.jsdelivr.net/npm/chart.js)"></script>
785
- <style>
786
- :root {{
787
- --bg-dark: #0f1011;
788
- --bg-darker: #070708;
789
- --text-primary: #e6e6e6;
790
- --text-secondary: #8c8c8c;
791
- --border-color: #2c2c2c;
792
- --accent-color: #3a6ee0;
793
- --accent-hover: #4a7ef0;
794
- --chart-bg-light: rgba(58, 110, 224, 0.2);
795
- --chart-border-light: #3a6ee0;
796
- }}
797
- body {{
798
- font-family: 'Inter', sans-serif;
799
- background-color: var(--bg-dark);
800
- color: var(--text-primary);
801
- max-width: 1200px;
802
- margin: 0 auto;
803
- padding: 40px 20px;
804
- line-height: 1.6;
805
- }}
806
- .logo {{
807
- display: flex;
808
- align-items: center;
809
- justify-content: center;
810
- margin-bottom: 30px;
811
- }}
812
- .logo h1 {{
813
- font-weight: 700;
814
- font-size: 2.8em;
815
- color: var(--text-primary);
816
- margin-left: 15px;
817
- }}
818
- .logo img {{
819
- width: 70px;
820
- height: 70px;
821
- border-radius: 12px;
822
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
823
- }}
824
- .container {{
825
- background-color: var(--bg-darker);
826
- border-radius: 16px;
827
- padding: 30px;
828
- box-shadow: 0 20px 50px rgba(0,0,0,0.4);
829
- border: 1px solid var(--border-color);
830
- }}
831
- h2, h3 {{
832
- color: var(--text-primary);
833
- border-bottom: 2px solid var(--border-color);
834
- padding-bottom: 12px;
835
- margin-top: 40px;
836
- margin-bottom: 25px;
837
- font-weight: 600;
838
- font-size: 1.8em;
839
- }}
840
- .summary-grid {{
841
- display: grid;
842
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
843
- gap: 20px;
844
- margin-bottom: 30px;
845
- }}
846
- .summary-card {{
847
- background-color: var(--bg-dark);
848
- border-radius: 10px;
849
- padding: 20px;
850
- text-align: center;
851
- border: 1px solid var(--border-color);
852
- box-shadow: 0 8px 20px rgba(0,0,0,0.2);
853
- transition: transform 0.2s ease-in-out;
854
- }}
855
- .summary-card:hover {{
856
- transform: translateY(-5px);
857
- }}
858
- .summary-card h3 {{
859
- margin-top: 0;
860
- font-size: 1.1em;
861
- color: var(--text-secondary);
862
- border-bottom: none;
863
- padding-bottom: 0;
864
- margin-bottom: 10px;
865
- }}
866
- .summary-card p {{
867
- font-size: 2.2em;
868
- font-weight: 700;
869
- color: var(--accent-color);
870
- margin: 0;
871
- }}
872
- table {{
873
- width: 100%;
874
- border-collapse: separate;
875
- border-spacing: 0;
876
- margin-bottom: 40px;
877
- background-color: var(--bg-dark);
878
- border-radius: 10px;
879
- overflow: hidden;
880
- box-shadow: 0 8px 20px rgba(0,0,0,0.2);
881
- }}
882
- th, td {{
883
- border: 1px solid var(--border-color);
884
- padding: 15px;
885
- text-align: left;
886
- transition: background-color 0.3s ease;
887
- }}
888
- th {{
889
- background-color: #1a1a1a;
890
- color: var(--text-primary);
891
- font-weight: 600;
892
- text-transform: uppercase;
893
- font-size: 0.95em;
894
- }}
895
- tr:nth-child(even) {{
896
- background-color: rgba(255,255,255,0.03);
897
- }}
898
- tr:hover {{
899
- background-color: rgba(62,100,255,0.1);
900
- }}
901
- .chart-container {{
902
- background-color: var(--bg-dark);
903
- border-radius: 10px;
904
- padding: 20px;
905
- margin-bottom: 40px;
906
- border: 1px solid var(--border-color);
907
- box-shadow: 0 8px 20px rgba(0,0,0,0.2);
908
- max-height: 400px;
909
- position: relative;
910
- }}
911
- canvas {{
912
- max-width: 100% !important;
913
- height: auto !important;
914
- }}
915
- @media (max-width: 768px) {{
916
- body {{
917
- padding: 20px 10px;
918
- }}
919
- .container {{
920
- padding: 20px;
921
- }}
922
- .logo h1 {{
923
- font-size: 2em;
924
- }}
925
- .summary-card p {{
926
- font-size: 1.8em;
927
- }}
928
- h2, h3 {{
929
- font-size: 1.5em;
930
- }}
931
- table {{
932
- font-size: 0.85em;
933
- }}
934
- th, td {{
935
- padding: 10px;
936
  }}
937
- }}
938
- </style>
939
- </head>
940
- <body>
941
- <div class="container">
942
- <div class="logo">
943
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMC9zdmciPjxwYXRoIGQ9Ik0xMDAgMzVMNTAgOTBoMTAwWiIgZmlsbD0iIzNhNmVlMCIvPjxjaXJjbGUgY3g9IjEwMCIgY3k9IjE0MCIgcj0iMzAiIGZpbGw9IiMzYTZlZTAiLz48L3N2Zz4=" alt="Lokiai AI Logo">
944
- <h1>Lokiai AI Usage</h1>
945
- </div>
946
-
947
- <div class="summary-grid">
948
- <div class="summary-card">
949
- <h3>Total Requests (All Time)</h3>
950
- <p>{usage_data['total_requests']}</p>
 
 
 
 
 
 
 
 
 
951
  </div>
952
- <div class="summary-card">
953
- <h3>Unique IPs (All Time)</h3>
954
- <p>{usage_data['unique_ips_total_count']}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
  </div>
956
- <div class="summary-card">
957
- <h3>Models Used (Last {days} Days)</h3>
958
- <p>{len(usage_data['model_usage_period'])}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
  </div>
960
- <div class="summary-card">
961
- <h3>Endpoints Used (Last {days} Days)</h3>
962
- <p>{len(usage_data['endpoint_usage_period'])}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963
  </div>
964
  </div>
965
-
966
- <h2>Daily Usage (Last {days} Days)</h2>
967
- <div class="chart-container">
968
- <canvas id="dailyRequestsChart"></canvas>
969
- </div>
970
- <table>
971
- <thead>
972
- <tr>
973
- <th>Date</th>
974
- <th>Requests</th>
975
- <th>Unique IPs</th>
976
- </tr>
977
- </thead>
978
- <tbody>
979
- {daily_usage_table_rows}
980
- </tbody>
981
- </table>
982
-
983
- <h2>Model Usage (Last {days} Days)</h2>
984
- <div class="chart-container">
985
- <canvas id="modelUsageChart"></canvas>
986
- </div>
987
- <h3>Model Usage (All Time Details)</h3>
988
- <table>
989
- <thead>
990
- <tr>
991
- <th>Model</th>
992
- <th>Total Requests</th>
993
- <th>First Used</th>
994
- <th>Last Used</th>
995
- </tr>
996
- </thead>
997
- <tbody>
998
- {model_usage_all_time_rows}
999
- </tbody>
1000
- </table>
1001
-
1002
- <h2>API Endpoint Usage (Last {days} Days)</h2>
1003
- <div class="chart-container">
1004
- <canvas id="endpointUsageChart"></canvas>
1005
- </div>
1006
- <h3>API Endpoint Usage (All Time Details)</h3>
1007
- <table>
1008
- <thead>
1009
- <tr>
1010
- <th>Endpoint</th>
1011
- <th>Total Requests</th>
1012
- <th>First Used</th>
1013
- <th>Last Used</th>
1014
- </tr>
1015
- </thead>
1016
- <tbody>
1017
- {api_usage_all_time_rows}
1018
- </tbody>
1019
- </table>
1020
-
1021
- <h2>Recent Requests (Last 20)</h2>
1022
- <table>
1023
- <thead>
1024
- <tr>
1025
- <th>Timestamp</th>
1026
- <th>Model</th>
1027
- <th>Endpoint</th>
1028
- <th>IP Address</th>
1029
- <th>User Agent</th>
1030
- </tr>
1031
- </thead>
1032
- <tbody>
1033
- {recent_requests_rows}
1034
- </tbody>
1035
- </table>
1036
- </div>
1037
-
1038
- <script>
1039
- const modelLabels = {json.dumps(model_labels)};
1040
- const modelCounts = {json.dumps(model_counts)};
1041
-
1042
- const endpointLabels = {json.dumps(endpoint_labels)};
1043
- const endpointCounts = {json.dumps(endpoint_counts)};
1044
-
1045
- const dailyDates = {json.dumps(daily_dates)};
1046
- const dailyRequests = {json.dumps(daily_requests)};
1047
- const dailyUniqueIps = {json.dumps(daily_unique_ips)};
1048
-
1049
- new Chart(document.getElementById('modelUsageChart'), {{
1050
- type: 'bar',
1051
- data: {{
1052
- labels: modelLabels,
1053
- datasets: [{{
1054
- label: 'Requests',
1055
- data: modelCounts,
1056
- backgroundColor: 'var(--chart-bg-light)',
1057
- borderColor: 'var(--chart-border-light)',
1058
- borderWidth: 1,
1059
- borderRadius: 5,
1060
- }}]
1061
- }},
1062
- options: {{
1063
  responsive: true,
1064
  maintainAspectRatio: false,
1065
  plugins: {{
1066
  legend: {{
1067
  labels: {{
1068
- color: 'var(--text-primary)'
 
 
 
 
 
1069
  }}
1070
- }},
1071
- title: {{
1072
- display: true,
1073
- text: 'Model Usage',
1074
- color: 'var(--text-primary)'
1075
  }}
1076
  }},
1077
  scales: {{
1078
  x: {{
1079
  ticks: {{
1080
- color: 'var(--text-secondary)'
 
 
 
1081
  }},
1082
  grid: {{
1083
- color: 'var(--border-color)'
 
1084
  }}
1085
  }},
1086
  y: {{
1087
  beginAtZero: true,
1088
  ticks: {{
1089
- color: 'var(--text-secondary)'
 
 
 
1090
  }},
1091
  grid: {{
1092
- color: 'var(--border-color)'
 
1093
  }}
1094
  }}
1095
  }}
1096
- }}
1097
- }});
1098
-
1099
- new Chart(document.getElementById('endpointUsageChart'), {{
1100
- type: 'doughnut',
1101
- data: {{
1102
- labels: endpointLabels,
1103
- datasets: [{{
1104
- label: 'Requests',
1105
- data: endpointCounts,
1106
- backgroundColor: [
1107
- '#3a6ee0', '#5b8bff', '#8dc4ff', '#b3d8ff', '#d0e8ff',
1108
- '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF'
1109
- ],
1110
- hoverOffset: 4
1111
- }}]
1112
- }},
1113
- options: {{
1114
- responsive: true,
1115
- maintainAspectRatio: false,
1116
- plugins: {{
1117
- legend: {{
1118
- position: 'right',
1119
- labels: {{
1120
- color: 'var(--text-primary)'
1121
- }}
1122
- }},
1123
- title: {{
1124
- display: true,
1125
- text: 'API Endpoint Usage',
1126
- color: 'var(--text-primary)'
1127
- }}
1128
- }}
1129
- }}
1130
- }});
1131
-
1132
- new Chart(document.getElementById('dailyRequestsChart'), {{
1133
- type: 'line',
1134
- data: {{
1135
- labels: dailyDates,
1136
- datasets: [
1137
- {{
1138
- label: 'Total Requests',
1139
- data: dailyRequests,
1140
- borderColor: 'var(--accent-color)',
1141
- backgroundColor: 'rgba(58, 110, 224, 0.1)',
1142
- fill: true,
1143
- tension: 0.3
1144
- }},
1145
- {{
1146
- label: 'Unique IPs',
1147
- data: dailyUniqueIps,
1148
- borderColor: '#FFCE56',
1149
- backgroundColor: 'rgba(255, 206, 86, 0.1)',
1150
- fill: true,
1151
- tension: 0.3
1152
- }}
1153
- ]
1154
- }},
1155
- options: {{
1156
- responsive: true,
1157
- maintainAspectRatio: false,
1158
- plugins: {{
1159
- legend: {{
1160
- labels: {{
1161
- color: 'var(--text-primary)'
1162
  }}
1163
- }},
1164
- title: {{
1165
- display: true,
1166
- text: 'Daily Requests and Unique IPs',
1167
- color: 'var(--text-primary)'
1168
- }}
1169
  }},
1170
- scales: {{
1171
- x: {{
1172
- ticks: {{
1173
- color: 'var(--text-secondary)'
1174
- }},
1175
- grid: {{
1176
- color: 'var(--border-color)'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1177
  }}
1178
  }},
1179
- y: {{
1180
- beginAtZero: true,
1181
- ticks: {{
1182
- color: 'var(--text-secondary)'
1183
- }},
1184
- grid: {{
1185
- color: 'var(--border-color)'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1186
  }}
1187
  }}
1188
  }}
1189
- }}
1190
- }});
1191
- </script>
1192
- </body>
1193
- </html>
1194
- """
 
 
 
 
 
 
 
 
 
 
 
1195
  return html_content
1196
 
1197
  @app.on_event("startup")
 
727
  daily_dates = list(usage_data['daily_usage_period'].keys())
728
  daily_requests = [data['requests'] for data in usage_data['daily_usage_period'].values()]
729
  daily_unique_ips = [data['unique_ips_count'] for data in usage_data['daily_usage_period'].values()]
730
+ daily_usage_table_rows = "\n".join([
731
+ f"""
732
+ <tr class="hover:bg-slate-700/20 transition-colors duration-200">
733
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-slate-200">{date}</td>
734
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-300">
735
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-500/20 text-blue-300">
736
+ {data['requests']:,}
737
+ </span>
738
+ </td>
739
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-300">
740
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-emerald-500/20 text-emerald-300">
741
+ {data['unique_ips_count']:,}
742
+ </span>
743
+ </td>
744
+ </tr>
745
+ """ for date, data in usage_data['daily_usage_period'].items()
746
+ ])
747
 
748
  model_usage_all_time_rows = "\n".join([
749
  f"""
750
+ <tr class="hover:bg-slate-700/20 transition-colors duration-200">
751
+ <td class="px-6 py-4 whitespace-nowrap">
752
+ <div class="flex items-center">
753
+ <div class="w-2 h-2 bg-purple-400 rounded-full mr-3"></div>
754
+ <span class="text-sm font-medium text-slate-200">{model}</span>
755
+ </div>
756
+ </td>
757
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-300">
758
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-500/20 text-purple-300">
759
+ {stats['total_requests']:,}
760
+ </span>
761
+ </td>
762
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-400">{datetime.datetime.fromisoformat(stats['first_used']).strftime("%Y-%m-%d %H:%M")}</td>
763
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-400">{datetime.datetime.fromisoformat(stats['last_used']).strftime("%Y-%m-%d %H:%M")}</td>
764
  </tr>
765
  """ for model, stats in usage_data['all_time_model_usage'].items()
766
  ])
767
 
768
  api_usage_all_time_rows = "\n".join([
769
  f"""
770
+ <tr class="hover:bg-slate-700/20 transition-colors duration-200">
771
+ <td class="px-6 py-4 whitespace-nowrap">
772
+ <div class="flex items-center">
773
+ <div class="w-2 h-2 bg-emerald-400 rounded-full mr-3"></div>
774
+ <span class="text-sm font-medium text-slate-200">{endpoint}</span>
775
+ </div>
776
+ </td>
777
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-300">
778
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-emerald-500/20 text-emerald-300">
779
+ {stats['total_requests']:,}
780
+ </span>
781
+ </td>
782
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-400">{datetime.datetime.fromisoformat(stats['first_used']).strftime("%Y-%m-%d %H:%M")}</td>
783
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-400">{datetime.datetime.fromisoformat(stats['last_used']).strftime("%Y-%m-%d %H:%M")}</td>
784
  </tr>
785
  """ for endpoint, stats in usage_data['all_time_endpoint_usage'].items()
786
  ])
787
 
 
 
 
 
 
 
 
 
 
 
788
  recent_requests_rows = "\n".join([
789
  f"""
790
+ <tr class="hover:bg-slate-700/20 transition-colors duration-200">
791
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-slate-300">{datetime.datetime.fromisoformat(req['timestamp']).strftime("%Y-%m-%d %H:%M:%S")}</td>
792
+ <td class="px-6 py-4 whitespace-nowrap">
793
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-500/20 text-indigo-300">
794
+ {req['model']}
795
+ </span>
796
+ </td>
797
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-300">{req['endpoint']}</td>
798
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-slate-400">{req['ip_address']}</td>
799
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 truncate max-w-xs">{req['user_agent'][:50]}...</td>
800
  </tr>
801
  """ for req in usage_data['recent_requests']
802
  ])
 
803
  html_content = f"""
804
+ <!DOCTYPE html>
805
+ <html lang="en">
806
+ <head>
807
+ <meta charset="UTF-8">
808
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
809
+ <title>Lokiai AI - Usage Analytics Dashboard</title>
810
+ <script src="https://cdn.tailwindcss.com"></script>
811
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
812
+ <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
813
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
814
+ <script>
815
+ tailwind.config = {{
816
+ theme: {{
817
+ extend: {{
818
+ animation: {{
819
+ 'fade-in': 'fadeIn 0.5s ease-in-out',
820
+ 'slide-up': 'slideUp 0.6s ease-out',
821
+ 'pulse-slow': 'pulse 3s infinite',
822
+ 'bounce-gentle': 'bounceGentle 2s infinite',
823
+ }},
824
+ keyframes: {{
825
+ fadeIn: {{
826
+ '0%': {{ opacity: '0', transform: 'translateY(10px)' }},
827
+ '100%': {{ opacity: '1', transform: 'translateY(0)' }},
828
+ }},
829
+ slideUp: {{
830
+ '0%': {{ opacity: '0', transform: 'translateY(30px)' }},
831
+ '100%': {{ opacity: '1', transform: 'translateY(0)' }},
832
+ }},
833
+ bounceGentle: {{
834
+ '0%, 100%': {{ transform: 'translateY(-2px)' }},
835
+ '50%': {{ transform: 'translateY(2px)' }},
836
+ }}
837
+ }}
838
+ }}
839
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
  }}
841
+ </script>
842
+ </head>
843
+ <body class="bg-gradient-to-br from-slate-950 via-slate-900 to-indigo-950 text-white min-h-screen">
844
+ <!-- Navigation Header -->
845
+ <nav class="bg-slate-900/80 backdrop-blur-md border-b border-slate-700/50 sticky top-0 z-50">
846
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
847
+ <div class="flex justify-between items-center h-16">
848
+ <div class="flex items-center space-x-4">
849
+ <div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
850
+ <i class="fas fa-robot text-white text-lg"></i>
851
+ </div>
852
+ <div>
853
+ <h1 class="text-xl font-bold bg-gradient-to-r from-blue-400 to-indigo-400 bg-clip-text text-transparent">
854
+ Lokiai AI
855
+ </h1>
856
+ <p class="text-xs text-slate-400">Usage Analytics Dashboard</p>
857
+ </div>
858
+ </div>
859
+ <div class="flex items-center space-x-2">
860
+ <div class="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
861
+ <span class="text-sm text-slate-300">Live</span>
862
+ </div>
863
+ </div>
864
  </div>
865
+ </nav>
866
+
867
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
868
+ <!-- Hero Stats Section -->
869
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8" x-data="{{}}">
870
+ <div class="bg-gradient-to-br from-blue-500/10 to-blue-600/5 backdrop-blur-sm border border-blue-500/20 rounded-2xl p-6 hover:border-blue-400/30 transition-all duration-300 animate-fade-in">
871
+ <div class="flex items-center justify-between">
872
+ <div>
873
+ <p class="text-blue-300 text-sm font-medium">Total Requests</p>
874
+ <p class="text-3xl font-bold text-white mt-2">{usage_data['total_requests']:,}</p>
875
+ <p class="text-green-400 text-xs mt-1">
876
+ <i class="fas fa-arrow-up mr-1"></i>All Time
877
+ </p>
878
+ </div>
879
+ <div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center">
880
+ <i class="fas fa-chart-line text-blue-400 text-xl"></i>
881
+ </div>
882
+ </div>
883
+ </div>
884
+
885
+ <div class="bg-gradient-to-br from-emerald-500/10 to-emerald-600/5 backdrop-blur-sm border border-emerald-500/20 rounded-2xl p-6 hover:border-emerald-400/30 transition-all duration-300 animate-fade-in" style="animation-delay: 0.1s">
886
+ <div class="flex items-center justify-between">
887
+ <div>
888
+ <p class="text-emerald-300 text-sm font-medium">Unique Users</p>
889
+ <p class="text-3xl font-bold text-white mt-2">{usage_data['unique_ips_total_count']:,}</p>
890
+ <p class="text-green-400 text-xs mt-1">
891
+ <i class="fas fa-users mr-1"></i>All Time
892
+ </p>
893
+ </div>
894
+ <div class="w-12 h-12 bg-emerald-500/20 rounded-xl flex items-center justify-center">
895
+ <i class="fas fa-users text-emerald-400 text-xl"></i>
896
+ </div>
897
+ </div>
898
+ </div>
899
+
900
+ <div class="bg-gradient-to-br from-purple-500/10 to-purple-600/5 backdrop-blur-sm border border-purple-500/20 rounded-2xl p-6 hover:border-purple-400/30 transition-all duration-300 animate-fade-in" style="animation-delay: 0.2s">
901
+ <div class="flex items-center justify-between">
902
+ <div>
903
+ <p class="text-purple-300 text-sm font-medium">Active Models</p>
904
+ <p class="text-3xl font-bold text-white mt-2">{len(usage_data['model_usage_period'])}</p>
905
+ <p class="text-blue-400 text-xs mt-1">
906
+ <i class="fas fa-clock mr-1"></i>Last {days} Days
907
+ </p>
908
+ </div>
909
+ <div class="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center">
910
+ <i class="fas fa-brain text-purple-400 text-xl"></i>
911
+ </div>
912
+ </div>
913
+ </div>
914
+
915
+ <div class="bg-gradient-to-br from-amber-500/10 to-amber-600/5 backdrop-blur-sm border border-amber-500/20 rounded-2xl p-6 hover:border-amber-400/30 transition-all duration-300 animate-fade-in" style="animation-delay: 0.3s">
916
+ <div class="flex items-center justify-between">
917
+ <div>
918
+ <p class="text-amber-300 text-sm font-medium">API Endpoints</p>
919
+ <p class="text-3xl font-bold text-white mt-2">{len(usage_data['endpoint_usage_period'])}</p>
920
+ <p class="text-blue-400 text-xs mt-1">
921
+ <i class="fas fa-clock mr-1"></i>Last {days} Days
922
+ </p>
923
+ </div>
924
+ <div class="w-12 h-12 bg-amber-500/20 rounded-xl flex items-center justify-center">
925
+ <i class="fas fa-plug text-amber-400 text-xl"></i>
926
+ </div>
927
+ </div>
928
+ </div>
929
  </div>
930
+
931
+ <!-- Charts Section -->
932
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
933
+ <!-- Daily Usage Chart -->
934
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl p-6 animate-slide-up">
935
+ <div class="flex items-center justify-between mb-6">
936
+ <div>
937
+ <h3 class="text-xl font-semibold text-white">Daily Usage Trends</h3>
938
+ <p class="text-slate-400 text-sm">Last {days} days performance</p>
939
+ </div>
940
+ <div class="flex items-center space-x-2">
941
+ <div class="w-3 h-3 bg-blue-400 rounded-full"></div>
942
+ <span class="text-xs text-slate-300">Requests</span>
943
+ <div class="w-3 h-3 bg-amber-400 rounded-full ml-4"></div>
944
+ <span class="text-xs text-slate-300">Unique IPs</span>
945
+ </div>
946
+ </div>
947
+ <div class="h-64">
948
+ <canvas id="dailyRequestsChart" class="w-full h-full"></canvas>
949
+ </div>
950
+ </div>
951
+
952
+ <!-- Model Usage Chart -->
953
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl p-6 animate-slide-up" style="animation-delay: 0.2s">
954
+ <div class="flex items-center justify-between mb-6">
955
+ <div>
956
+ <h3 class="text-xl font-semibold text-white">Model Distribution</h3>
957
+ <p class="text-slate-400 text-sm">Usage by AI models</p>
958
+ </div>
959
+ </div>
960
+ <div class="h-64">
961
+ <canvas id="modelUsageChart" class="w-full h-full"></canvas>
962
+ </div>
963
+ </div>
964
  </div>
965
+
966
+ <!-- Endpoint Usage Chart -->
967
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl p-6 animate-slide-up" style="animation-delay: 0.4s">
968
+ <div class="flex items-center justify-between mb-6">
969
+ <div>
970
+ <h2 class="text-2xl font-bold text-white">API Endpoint Analytics</h2>
971
+ <p class="text-slate-400">Distribution of requests across different endpoints</p>
972
+ </div>
973
+ </div>
974
+ <div class="h-80">
975
+ <canvas id="endpointUsageChart" class="w-full h-full"></canvas>
976
+ </div>
977
+ </div>
978
+
979
+ <!-- Data Tables Section -->
980
+ <div class="grid grid-cols-1 xl:grid-cols-2 gap-8">
981
+ <!-- Daily Usage Table -->
982
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl animate-slide-up" style="animation-delay: 0.6s">
983
+ <div class="p-6 border-b border-slate-700/50">
984
+ <h3 class="text-xl font-semibold text-white flex items-center">
985
+ <i class="fas fa-calendar-alt mr-3 text-blue-400"></i>
986
+ Daily Breakdown
987
+ </h3>
988
+ <p class="text-slate-400 text-sm mt-1">Last {days} days detailed view</p>
989
+ </div>
990
+ <div class="overflow-x-auto max-h-96">
991
+ <table class="w-full">
992
+ <thead class="bg-slate-700/30">
993
+ <tr>
994
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Date</th>
995
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Requests</th>
996
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Unique IPs</th>
997
+ </tr>
998
+ </thead>
999
+ <tbody class="divide-y divide-slate-700/30">
1000
+ {daily_usage_table_rows}
1001
+ </tbody>
1002
+ </table>
1003
+ </div>
1004
+ </div>
1005
+
1006
+ <!-- Model Usage Table -->
1007
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl animate-slide-up" style="animation-delay: 0.8s">
1008
+ <div class="p-6 border-b border-slate-700/50">
1009
+ <h3 class="text-xl font-semibold text-white flex items-center">
1010
+ <i class="fas fa-robot mr-3 text-purple-400"></i>
1011
+ Model Statistics
1012
+ </h3>
1013
+ <p class="text-slate-400 text-sm mt-1">All-time model usage data</p>
1014
+ </div>
1015
+ <div class="overflow-x-auto max-h-96">
1016
+ <table class="w-full">
1017
+ <thead class="bg-slate-700/30">
1018
+ <tr>
1019
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Model</th>
1020
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Requests</th>
1021
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">First Used</th>
1022
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Last Used</th>
1023
+ </tr>
1024
+ </thead>
1025
+ <tbody class="divide-y divide-slate-700/30">
1026
+ {model_usage_all_time_rows}
1027
+ </tbody>
1028
+ </table>
1029
+ </div>
1030
+ </div>
1031
+ </div>
1032
+
1033
+ <!-- API Endpoints Table -->
1034
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl animate-slide-up" style="animation-delay: 1s">
1035
+ <div class="p-6 border-b border-slate-700/50">
1036
+ <h3 class="text-xl font-semibold text-white flex items-center">
1037
+ <i class="fas fa-plug mr-3 text-emerald-400"></i>
1038
+ API Endpoint Details
1039
+ </h3>
1040
+ <p class="text-slate-400 text-sm mt-1">Complete endpoint usage statistics</p>
1041
+ </div>
1042
+ <div class="overflow-x-auto">
1043
+ <table class="w-full">
1044
+ <thead class="bg-slate-700/30">
1045
+ <tr>
1046
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Endpoint</th>
1047
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Total Requests</th>
1048
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">First Used</th>
1049
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Last Used</th>
1050
+ </tr>
1051
+ </thead>
1052
+ <tbody class="divide-y divide-slate-700/30">
1053
+ {api_usage_all_time_rows}
1054
+ </tbody>
1055
+ </table>
1056
+ </div>
1057
+ </div>
1058
+
1059
+ <!-- Recent Requests -->
1060
+ <div class="bg-slate-800/40 backdrop-blur-sm border border-slate-700/50 rounded-2xl animate-slide-up" style="animation-delay: 1.2s">
1061
+ <div class="p-6 border-b border-slate-700/50">
1062
+ <h3 class="text-xl font-semibold text-white flex items-center">
1063
+ <i class="fas fa-clock mr-3 text-amber-400"></i>
1064
+ Recent Activity
1065
+ </h3>
1066
+ <p class="text-slate-400 text-sm mt-1">Last 20 requests in real-time</p>
1067
+ </div>
1068
+ <div class="overflow-x-auto">
1069
+ <table class="w-full">
1070
+ <thead class="bg-slate-700/30">
1071
+ <tr>
1072
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Timestamp</th>
1073
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Model</th>
1074
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">Endpoint</th>
1075
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">IP Address</th>
1076
+ <th class="px-6 py-4 text-left text-xs font-medium text-slate-300 uppercase tracking-wider">User Agent</th>
1077
+ </tr>
1078
+ </thead>
1079
+ <tbody class="divide-y divide-slate-700/30">
1080
+ {recent_requests_rows}
1081
+ </tbody>
1082
+ </table>
1083
+ </div>
1084
  </div>
1085
  </div>
1086
+
1087
+ <!-- Footer -->
1088
+ <footer class="border-t border-slate-700/50 mt-16">
1089
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
1090
+ <div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
1091
+ <div class="flex items-center space-x-4">
1092
+ <div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center">
1093
+ <i class="fas fa-robot text-white text-sm"></i>
1094
+ </div>
1095
+ <div>
1096
+ <p class="text-slate-300 font-medium">Lokiai AI Dashboard</p>
1097
+ <p class="text-slate-500 text-xs">Advanced Analytics & Monitoring</p>
1098
+ </div>
1099
+ </div>
1100
+ <div class="text-slate-400 text-sm">
1101
+ Last updated: <span class="text-slate-300" id="currentTime"></span>
1102
+ </div>
1103
+ </div>
1104
+ </div>
1105
+ </footer>
1106
+
1107
+ <script>
1108
+ // Update current time
1109
+ document.getElementById('currentTime').textContent = new Date().toLocaleString();
1110
+
1111
+ // Chart data
1112
+ const modelLabels = {json.dumps(model_labels)};
1113
+ const modelCounts = {json.dumps(model_counts)};
1114
+ const endpointLabels = {json.dumps(endpoint_labels)};
1115
+ const endpointCounts = {json.dumps(endpoint_counts)};
1116
+ const dailyDates = {json.dumps(daily_dates)};
1117
+ const dailyRequests = {json.dumps(daily_requests)};
1118
+ const dailyUniqueIps = {json.dumps(daily_unique_ips)};
1119
+
1120
+ // Chart options
1121
+ const chartOptions = {{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
  responsive: true,
1123
  maintainAspectRatio: false,
1124
  plugins: {{
1125
  legend: {{
1126
  labels: {{
1127
+ color: '#e2e8f0',
1128
+ padding: 20,
1129
+ font: {{
1130
+ size: 12,
1131
+ weight: 500
1132
+ }}
1133
  }}
 
 
 
 
 
1134
  }}
1135
  }},
1136
  scales: {{
1137
  x: {{
1138
  ticks: {{
1139
+ color: '#94a3b8',
1140
+ font: {{
1141
+ size: 11
1142
+ }}
1143
  }},
1144
  grid: {{
1145
+ color: 'rgba(148, 163, 184, 0.1)',
1146
+ drawBorder: false
1147
  }}
1148
  }},
1149
  y: {{
1150
  beginAtZero: true,
1151
  ticks: {{
1152
+ color: '#94a3b8',
1153
+ font: {{
1154
+ size: 11
1155
+ }}
1156
  }},
1157
  grid: {{
1158
+ color: 'rgba(148, 163, 184, 0.1)',
1159
+ drawBorder: false
1160
  }}
1161
  }}
1162
  }}
1163
+ }};
1164
+
1165
+ // Daily Requests Chart
1166
+ new Chart(document.getElementById('dailyRequestsChart'), {{
1167
+ type: 'line',
1168
+ data: {{
1169
+ labels: dailyDates,
1170
+ datasets: [
1171
+ {{
1172
+ label: 'Total Requests',
1173
+ data: dailyRequests,
1174
+ borderColor: '#3b82f6',
1175
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
1176
+ fill: true,
1177
+ tension: 0.4,
1178
+ borderWidth: 3,
1179
+ pointBackgroundColor: '#3b82f6',
1180
+ pointBorderColor: '#1e40af',
1181
+ pointBorderWidth: 2,
1182
+ pointRadius: 4,
1183
+ pointHoverRadius: 6
1184
+ }},
1185
+ {{
1186
+ label: 'Unique IPs',
1187
+ data: dailyUniqueIps,
1188
+ borderColor: '#f59e0b',
1189
+ backgroundColor: 'rgba(245, 158, 11, 0.1)',
1190
+ fill: true,
1191
+ tension: 0.4,
1192
+ borderWidth: 3,
1193
+ pointBackgroundColor: '#f59e0b',
1194
+ pointBorderColor: '#d97706',
1195
+ pointBorderWidth: 2,
1196
+ pointRadius: 4,
1197
+ pointHoverRadius: 6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
  }}
1199
+ ]
 
 
 
 
 
1200
  }},
1201
+ options: chartOptions
1202
+ }});
1203
+
1204
+ // Model Usage Chart
1205
+ new Chart(document.getElementById('modelUsageChart'), {{
1206
+ type: 'doughnut',
1207
+ data: {{
1208
+ labels: modelLabels,
1209
+ datasets: [{{
1210
+ data: modelCounts,
1211
+ backgroundColor: [
1212
+ '#3b82f6', '#8b5cf6', '#06b6d4', '#10b981', '#f59e0b',
1213
+ '#ef4444', '#ec4899', '#84cc16', '#f97316', '#6366f1'
1214
+ ],
1215
+ borderWidth: 0,
1216
+ hoverOffset: 8
1217
+ }}]
1218
+ }},
1219
+ options: {{
1220
+ responsive: true,
1221
+ maintainAspectRatio: false,
1222
+ plugins: {{
1223
+ legend: {{
1224
+ position: 'bottom',
1225
+ labels: {{
1226
+ color: '#e2e8f0',
1227
+ padding: 15,
1228
+ usePointStyle: true,
1229
+ font: {{
1230
+ size: 12
1231
+ }}
1232
+ }}
1233
  }}
1234
  }},
1235
+ cutout: '60%'
1236
+ }}
1237
+ }});
1238
+
1239
+ // Endpoint Usage Chart
1240
+ new Chart(document.getElementById('endpointUsageChart'), {{
1241
+ type: 'bar',
1242
+ data: {{
1243
+ labels: endpointLabels,
1244
+ datasets: [{{
1245
+ label: 'Requests',
1246
+ data: endpointCounts,
1247
+ backgroundColor: 'rgba(59, 130, 246, 0.8)',
1248
+ borderColor: '#3b82f6',
1249
+ borderWidth: 0,
1250
+ borderRadius: 8,
1251
+ borderSkipped: false,
1252
+ }}]
1253
+ }},
1254
+ options: {{
1255
+ ...chartOptions,
1256
+ plugins: {{
1257
+ legend: {{
1258
+ display: false
1259
  }}
1260
  }}
1261
  }}
1262
+ }});
1263
+
1264
+ // Add hover effects to table rows
1265
+ document.querySelectorAll('tbody tr').forEach(row => {{
1266
+ row.addEventListener('mouseenter', function() {{
1267
+ this.classList.add('bg-slate-700/20');
1268
+ }});
1269
+ row.addEventListener('mouseleave', function() {{
1270
+ this.classList.remove('bg-slate-700/20');
1271
+ }});
1272
+ }});
1273
+ </script>
1274
+ </body>
1275
+ </html>"""
1276
+
1277
+ # Update the table row generation with better styling
1278
+
1279
  return html_content
1280
 
1281
  @app.on_event("startup")