dinh dang so kieu Vietnam

This commit is contained in:
2026-07-08 14:14:29 +07:00
parent d83ffc4c8e
commit 1d628e435e
@@ -64,6 +64,8 @@ long g_reasoning_tokens= 0;
long g_cached_tokens = 0;
long g_failed = 0;
long g_tokens30d = 0; // tổng token 30 ngày (range=30d)
AccountStat g_accounts[8];
int g_accountCount = 0;
ModelStat g_models[6];
@@ -208,17 +210,47 @@ void tft_showFetchError() {
tftUnlock();
}
// ── Footer hiển thị IP để truy cập trang Config ──
// ── Footer hiển thị token 30 ngày + IP để truy cập trang Config ──
void drawFooter() {
// Vẽ thanh footer ở 16px dưới cùng
tft.drawFastHLine(0, SCREEN_HEIGHT-17, SCREEN_WIDTH, COLOR_HEADER);
tft.setTextSize(1); tft.setTextColor(COLOR_CYAN);
tft.setCursor(6, SCREEN_HEIGHT-13);
int footerTop = SCREEN_HEIGHT - 32;
tft.drawFastHLine(0, footerTop - 1, SCREEN_WIDTH, COLOR_HEADER);
// Dòng 1: tổng token 30 ngày
char buf[40];
tft.setTextSize(1); tft.setTextColor(COLOR_YELLOW);
tft.setCursor(6, footerTop + 3);
tft.print("30d Tokens: ");
tft.setTextColor(COLOR_ORANGE);
fmtNum(g_tokens30d, buf);
tft.print(buf);
// Dòng 2: IP config
tft.setTextColor(COLOR_CYAN);
tft.setCursor(6, footerTop + 15);
tft.print("Config: http://");
tft.setTextColor(COLOR_ORANGE);
tft.print(WiFi.localIP().toString());
}
// ── Format số kiểu EU: dấu . phân cách hàng nghìn, không thập phân ──
void fmtNum(long n, char* out) {
char tmp[20];
bool neg = n < 0;
if (neg) n = -n;
int dig = 0, i = 0;
if (n == 0) { tmp[i++] = '0'; dig = 1; }
while (n > 0) {
if (dig > 0 && dig % 3 == 0) tmp[i++] = '.';
tmp[i++] = '0' + (n % 10);
n /= 10;
dig++;
}
if (neg) tmp[i++] = '-';
int j = 0;
while (i > 0) out[j++] = tmp[--i];
out[j] = '\0';
}
// ── Vẽ 4 trang dữ liệu ───────────────────────────
void drawPage0() {
// Tổng quan
@@ -230,13 +262,13 @@ void drawPage0() {
tft.setTextSize(1); tft.setTextColor(COLOR_CYAN);
tft.setCursor(8,48); tft.print("Requests");
tft.setTextSize(4); tft.setTextColor(COLOR_GREEN);
snprintf(buf,sizeof(buf),"%ld",g_requests);
fmtNum(g_requests, buf);
tft.setCursor(8,62); tft.print(buf);
tft.setTextSize(1); tft.setTextColor(COLOR_CYAN);
tft.setCursor(8,108); tft.print("Failed");
tft.setTextSize(2); tft.setTextColor(g_failed > 0 ? COLOR_RED : COLOR_TEXT);
snprintf(buf,sizeof(buf),"%ld",g_failed);
fmtNum(g_failed, buf);
tft.setCursor(8,120); tft.print(buf);
// Cột phải: token breakdown
@@ -246,32 +278,34 @@ void drawPage0() {
tft.setTextSize(1); tft.setTextColor(COLOR_YELLOW);
tft.setCursor(x,y); tft.print("Total Tokens:"); y+=14;
tft.setTextSize(2); tft.setTextColor(COLOR_ORANGE);
snprintf(buf,sizeof(buf),"%ld",g_total_tokens);
fmtNum(g_total_tokens, buf);
tft.setCursor(x,y); tft.print(buf); y+=24;
tft.setTextSize(1);
tft.setTextColor(COLOR_CYAN); tft.setCursor(x,y); tft.print("Input : ");
tft.setTextColor(COLOR_TEXT); snprintf(buf,sizeof(buf),"%ld",g_input_tokens); tft.println(buf); y+=14;
tft.setTextColor(COLOR_TEXT); fmtNum(g_input_tokens, buf); tft.println(buf); y+=14;
tft.setTextColor(COLOR_CYAN); tft.setCursor(x,y); tft.print("Output : ");
tft.setTextColor(COLOR_TEXT); snprintf(buf,sizeof(buf),"%ld",g_output_tokens); tft.println(buf); y+=14;
tft.setTextColor(COLOR_TEXT); fmtNum(g_output_tokens, buf); tft.println(buf); y+=14;
tft.setTextColor(COLOR_CYAN); tft.setCursor(x,y); tft.print("Reasoning: ");
tft.setTextColor(COLOR_TEXT); snprintf(buf,sizeof(buf),"%ld",g_reasoning_tokens); tft.println(buf); y+=14;
tft.setTextColor(COLOR_TEXT); fmtNum(g_reasoning_tokens, buf); tft.println(buf); y+=14;
tft.setTextColor(COLOR_CYAN); tft.setCursor(x,y); tft.print("Cached : ");
tft.setTextColor(COLOR_TEXT); snprintf(buf,sizeof(buf),"%ld",g_cached_tokens); tft.println(buf);
tft.setTextColor(COLOR_TEXT); fmtNum(g_cached_tokens, buf); tft.println(buf);
}
void drawPage1() {
// Accounts
tft.fillScreen(COLOR_BG);
drawHeader("Account", 1, TOTAL_PAGES);
int y=48; char buf[48];
int y=48; char buf[48], nb[16], tb[16];
for (int i=0; i<g_accountCount && i<5; i++) {
char sn[36]; strncpy(sn,g_accounts[i].name,35); sn[35]='\0';
fmtNum(g_accounts[i].requests, nb);
fmtNum(g_accounts[i].total_tokens, tb);
tft.setTextSize(1); tft.setTextColor(COLOR_YELLOW);
tft.setCursor(6,y); tft.print(sn);
tft.setTextColor(COLOR_TEXT); tft.setCursor(6,y+12);
snprintf(buf,sizeof(buf)," %d reqs %ld tokens fail:%d",
g_accounts[i].requests, g_accounts[i].total_tokens, g_accounts[i].failed);
snprintf(buf,sizeof(buf)," %s reqs %s tokens fail:%d",
nb, tb, g_accounts[i].failed);
tft.print(buf);
tft.drawFastHLine(0,y+26,SCREEN_WIDTH,COLOR_HEADER);
y+=30;
@@ -285,13 +319,15 @@ void drawPage2() {
// Models
tft.fillScreen(COLOR_BG);
drawHeader("Model", 2, TOTAL_PAGES);
int y=48; char buf[48];
int y=48; char buf[48], nb[16], tb[16];
for (int i=0; i<g_modelCount && i<6; i++) {
fmtNum(g_models[i].requests, nb);
fmtNum(g_models[i].total_tokens, tb);
tft.setTextSize(1); tft.setTextColor(COLOR_GREEN);
tft.setCursor(6,y); tft.print(g_models[i].name);
tft.setTextColor(COLOR_TEXT); tft.setCursor(6,y+12);
snprintf(buf,sizeof(buf)," %d reqs %ld tokens fail:%d",
g_models[i].requests, g_models[i].total_tokens, g_models[i].failed);
snprintf(buf,sizeof(buf)," %s reqs %s tokens fail:%d",
nb, tb, g_models[i].failed);
tft.print(buf);
tft.drawFastHLine(0,y+26,SCREEN_WIDTH,COLOR_HEADER);
y+=30;
@@ -305,17 +341,19 @@ void drawPage3() {
// Hours (hoạt động theo giờ gần nhất)
tft.fillScreen(COLOR_BG);
drawHeader("Hourly", 3, TOTAL_PAGES);
int y=48; char buf[48];
int y=48; char buf[48], nb[16], tb[16];
int start = g_hourCount > 6 ? g_hourCount - 6 : 0; // 6 giờ gần nhất
for (int i=start; i<g_hourCount; i++) {
fmtNum(g_hours[i].requests, nb);
fmtNum(g_hours[i].total_tokens, tb);
tft.setTextSize(1); tft.setTextColor(COLOR_CYAN);
tft.setCursor(6,y); tft.print(g_hours[i].hour);
tft.setTextColor(COLOR_TEXT);
snprintf(buf,sizeof(buf)," %d reqs %ld tok", g_hours[i].requests, g_hours[i].total_tokens);
snprintf(buf,sizeof(buf)," %s reqs %s tok", nb, tb);
tft.setCursor(6,y+12); tft.print(buf);
tft.drawFastHLine(0,y+26,SCREEN_WIDTH,COLOR_HEADER);
y+=30;
if (y > SCREEN_HEIGHT - 28) break;
if (y > SCREEN_HEIGHT - 36) break;
}
if (g_hourCount == 0) {
tft.setTextColor(COLOR_TEXT); tft.setCursor(6,y); tft.print("(khong co du lieu)");
@@ -424,6 +462,47 @@ bool fetchAndParseData() {
return true;
}
// ═════════════════════════════════════════════════
// Fetch tổng token 30 ngày (range=30d)
// ═════════════════════════════════════════════════
void fetch30dTokens() {
// Tạo URL với range=30d từ g_api_url hiện tại
String url30 = String(g_api_url);
int q = url30.indexOf('?');
if (q >= 0) {
// Có query string -> thay thế/thêm range=30d
int r = url30.indexOf("range=");
if (r >= 0) {
int amp = url30.indexOf('&', r);
if (amp >= 0) url30 = url30.substring(0, r) + "range=30d" + url30.substring(amp);
else url30 = url30.substring(0, r) + "range=30d";
} else {
url30 += "&range=30d";
}
} else {
url30 += "?range=30d";
}
Serial.printf("[FETCH30] GET %s\n", url30.c_str());
HTTPClient http;
http.begin(url30);
http.setTimeout(10000);
int code = http.GET();
if (code != 200) { http.end(); return; }
String payload = http.getString();
http.end();
DynamicJsonDocument doc(4096);
DeserializationError err = deserializeJson(doc, payload);
if (err) {
Serial.printf("[FETCH30] JSON error: %s\n", err.c_str());
return;
}
g_tokens30d = doc["summary"]["total_tokens"] | 0L;
Serial.printf("[FETCH30] 30d tokens = %ld\n", g_tokens30d);
}
// ═════════════════════════════════════════════════
// HTML
// ═════════════════════════════════════════════════
@@ -610,6 +689,9 @@ void fetch_task(void* pv) {
} else {
tft_showFetchError();
}
// Lấy tổng token 30 ngày
fetch30dTokens();
drawCurrentPage();
}
// Nếu có yêu cầu fetch ngay (sau khi đổi API URL), không delay 60s
if (g_fetchNow) {