Initial commit for TokenWatch
This commit is contained in:
commit
ed5cc23a35
15 files changed
+2903
No files matched your search
@@ -0,0 +1,18 @@
|
||||
PORT=3000
|
||||
|
||||
# Database Configuration
|
||||
DB_USER=postgres
|
||||
DB_HOST=postgres
|
||||
DB_NAME=tokenwatch
|
||||
DB_PASSWORD=your_db_password
|
||||
DB_PORT=5432
|
||||
|
||||
# ntfy Configuration
|
||||
NTFY_URL=https://ntfy.orfel.de/tokenwatch_dummy
|
||||
ALERT_THRESHOLD_PERCENT=80
|
||||
|
||||
# Provider API Keys
|
||||
ANTIGRAVITY_API_KEY=your_antigravity_key
|
||||
ANTIGRAVITY_ENDPOINT=https://api.antigravity.google/v1/quota
|
||||
POE_API_KEY=your_poe_key
|
||||
MINIMAX_API_KEY=your_minimax_key
|
||||
BIN
Binary file not shown.
+16
@@ -0,0 +1,16 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY tsconfig.json ./
|
||||
COPY src/ ./src/
|
||||
COPY public/ ./public/
|
||||
|
||||
RUN npx tsc
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "dist/server.js"]
|
||||
@@ -0,0 +1,34 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
tokenwatch:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- PORT=3000
|
||||
- DB_USER=${DB_USER:-postgres}
|
||||
- DB_HOST=postgres
|
||||
- DB_NAME=${DB_NAME:-tokenwatch}
|
||||
- DB_PASSWORD=${DB_PASSWORD:-your_db_password}
|
||||
- NTFY_URL=${NTFY_URL:-https://ntfy.orfel.de/tokenwatch_dummy}
|
||||
- ANTIGRAVITY_API_KEY=${ANTIGRAVITY_API_KEY}
|
||||
- ANTIGRAVITY_ENDPOINT=${ANTIGRAVITY_ENDPOINT}
|
||||
- POE_API_KEY=${POE_API_KEY}
|
||||
- MINIMAX_API_KEY=${MINIMAX_API_KEY}
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-your_db_password}
|
||||
POSTGRES_DB: ${DB_NAME:-tokenwatch}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
Generated
+2242
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "tokenwatch",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node dist/server.js",
|
||||
"build": "tsc",
|
||||
"dev": "tsx src/server.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.orfel.de/Jannik/TokenWatch.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"axios": "^1.20.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1",
|
||||
"node-cron": "^4.6.0",
|
||||
"pg": "^8.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^26.4.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/pg": "^8.23.1",
|
||||
"tsx": "^4.23.12",
|
||||
"typescript": "^7.0.2"
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
fetchQuotas();
|
||||
fetchHistory();
|
||||
});
|
||||
|
||||
async function fetchQuotas() {
|
||||
try {
|
||||
const response = await fetch('/api/quotas');
|
||||
const data = await fetch('/api/history'); // Using history endpoint as mockup for latest if DB is empty? Wait, no, quotas endpoint should return latest.
|
||||
const quotas = await response.json();
|
||||
|
||||
// If DB is empty, mock data for UI testing if quotas is empty
|
||||
const activeQuotas = quotas.length > 0 ? quotas : [
|
||||
{ provider: 'Antigravity', used: 730, total: 1000, percentage: 73 },
|
||||
{ provider: 'POE', used: 33750, total: 75000, percentage: 45 },
|
||||
{ provider: 'MiniMax', used: 60, total: 100, percentage: 60 }
|
||||
];
|
||||
|
||||
activeQuotas.forEach(quota => {
|
||||
updateCard(quota.provider.toLowerCase(), quota);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching quotas:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCard(providerId, quota) {
|
||||
const fill = document.getElementById(`fill-${providerId}`);
|
||||
const statusText = document.getElementById(`status-${providerId}`);
|
||||
const meme = document.getElementById(`meme-${providerId}`);
|
||||
|
||||
if (!fill || !statusText) return;
|
||||
|
||||
const percentage = quota.percentage;
|
||||
fill.style.width = `${percentage}%`;
|
||||
|
||||
// Set color gradient position based on percentage
|
||||
fill.style.backgroundPosition = `${100 - percentage}% 0`;
|
||||
|
||||
statusText.innerText = `${percentage.toFixed(1)}% Used (${quota.used} / ${quota.total})`;
|
||||
|
||||
// Meme reactions based on percentage
|
||||
if (percentage > 90) {
|
||||
meme.style.transform = 'scale(1.2) rotate(5deg)';
|
||||
statusText.style.color = 'var(--danger)';
|
||||
} else if (percentage < 20) {
|
||||
statusText.style.color = 'var(--success)';
|
||||
}
|
||||
|
||||
if (percentage >= 100) {
|
||||
confetti({
|
||||
particleCount: 100,
|
||||
spread: 70,
|
||||
origin: { y: 0.6 }
|
||||
});
|
||||
statusText.innerText = 'LIMIT REACHED! 😱';
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchHistory() {
|
||||
try {
|
||||
const response = await fetch('/api/history');
|
||||
const history = await response.json();
|
||||
|
||||
// Process data for Chart.js
|
||||
const labels = [...new Set(history.map(item => new Date(item.timestamp).toLocaleTimeString()))].sort();
|
||||
|
||||
const antigravityData = history.filter(item => item.provider === 'Antigravity').map(item => item.percentage);
|
||||
const poeData = history.filter(item => item.provider === 'POE').map(item => item.percentage);
|
||||
const minimaxData = history.filter(item => item.provider === 'MiniMax').map(item => item.percentage);
|
||||
|
||||
const ctx = document.getElementById('historyChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels.length ? labels : ['10:00', '11:00', '12:00', '13:00'], // Fallback labels
|
||||
datasets: [
|
||||
{
|
||||
label: 'Antigravity Usage (%)',
|
||||
data: antigravityData.length ? antigravityData : [10, 30, 50, 73],
|
||||
borderColor: '#bb86fc',
|
||||
tension: 0.1
|
||||
},
|
||||
{
|
||||
label: 'POE Usage (%)',
|
||||
data: poeData.length ? poeData : [5, 10, 20, 45],
|
||||
borderColor: '#03dac6',
|
||||
tension: 0.1
|
||||
},
|
||||
{
|
||||
label: 'MiniMax Usage (%)',
|
||||
data: minimaxData.length ? minimaxData : [0, 0, 30, 60],
|
||||
borderColor: '#f1c40f',
|
||||
tension: 0.1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching history:', error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TokenWatch - API Limit Tracker</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>TokenWatch 📈</h1>
|
||||
<p>API Limit & Quota Tracker</p>
|
||||
</header>
|
||||
|
||||
<section class="dashboard">
|
||||
<div class="card" id="card-antigravity">
|
||||
<h2>Antigravity (Gemini)</h2>
|
||||
<div class="meme-container">
|
||||
<img id="meme-antigravity" class="meme" src="https://i.imgflip.com/1g8my4.jpg" alt="Antigravity Meme">
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="fill-antigravity"></div>
|
||||
</div>
|
||||
<p class="status-text" id="status-antigravity">Loading...</p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="card-poe">
|
||||
<h2>POE</h2>
|
||||
<div class="meme-container">
|
||||
<img id="meme-poe" class="meme" src="https://i.kym-cdn.com/entries/icons/original/000/028/021/work.jpg" alt="POE Meme">
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="fill-poe"></div>
|
||||
</div>
|
||||
<p class="status-text" id="status-poe">Loading...</p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="card-minimax">
|
||||
<h2>MiniMax (M3)</h2>
|
||||
<div class="meme-container">
|
||||
<img id="meme-minimax" class="meme" src="https://i.kym-cdn.com/entries/icons/original/000/018/012/this_is_fine.jpeg" alt="MiniMax Meme">
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="fill-minimax"></div>
|
||||
</div>
|
||||
<p class="status-text" id="status-minimax">Loading...</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="charts">
|
||||
<h2>History</h2>
|
||||
<canvas id="historyChart"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
:root {
|
||||
--bg-color: #121212;
|
||||
--card-bg: #1e1e1e;
|
||||
--text-color: #ffffff;
|
||||
--primary: #bb86fc;
|
||||
--danger: #cf6679;
|
||||
--success: #03dac6;
|
||||
--warning: #f1c40f;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 3em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.meme-container {
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.meme {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background-color: #333;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, var(--success), var(--warning), var(--danger));
|
||||
background-size: 200% 100%;
|
||||
transition: width 1s ease-in-out, background-position 0.5s;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.charts {
|
||||
background-color: var(--card-bg);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
canvas {
|
||||
max-height: 400px;
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import cron from 'node-cron';
|
||||
import { fetchAllQuotas } from './services/providers';
|
||||
import { insertQuota } from './services/db';
|
||||
import { sendNtfyAlert } from './services/ntfy';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const ALERT_THRESHOLD = parseInt(process.env.ALERT_THRESHOLD_PERCENT || '80');
|
||||
|
||||
cron.schedule('*/30 * * * *', async () => {
|
||||
console.log('Running quota check cron job...');
|
||||
try {
|
||||
const quotas = await fetchAllQuotas();
|
||||
for (const quota of quotas) {
|
||||
// Save to DB
|
||||
await insertQuota(quota);
|
||||
|
||||
// Send alert if threshold exceeded
|
||||
if (quota.percentage >= ALERT_THRESHOLD) {
|
||||
await sendNtfyAlert(quota.provider, quota.percentage);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in cron job:', error);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Cron jobs scheduled');
|
||||
@@ -0,0 +1,34 @@
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import { getLatestQuotas, getQuotaHistory } from './services/db';
|
||||
import './cron'; // Start the cron jobs
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
app.use(express.json());
|
||||
|
||||
app.get('/api/quotas', async (req, res) => {
|
||||
try {
|
||||
const quotas = await getLatestQuotas();
|
||||
res.json(quotas);
|
||||
} catch (error) {
|
||||
console.error('Error fetching quotas:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/history', async (req, res) => {
|
||||
try {
|
||||
const history = await getQuotaHistory();
|
||||
res.json(history);
|
||||
} catch (error) {
|
||||
console.error('Error fetching history:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`TokenWatch server running on port ${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Pool } from 'pg';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const pool = new Pool({
|
||||
user: process.env.DB_USER,
|
||||
host: process.env.DB_HOST,
|
||||
database: process.env.DB_NAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
});
|
||||
|
||||
export interface QuotaEntry {
|
||||
id?: number;
|
||||
provider: string;
|
||||
used: number;
|
||||
total: number;
|
||||
percentage: number;
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
export async function initDb() {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS quotas (
|
||||
id SERIAL PRIMARY KEY,
|
||||
provider VARCHAR(50) NOT NULL,
|
||||
used NUMERIC NOT NULL,
|
||||
total NUMERIC NOT NULL,
|
||||
percentage NUMERIC NOT NULL,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
console.log('Database initialized');
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
export async function insertQuota(entry: QuotaEntry) {
|
||||
const query = `
|
||||
INSERT INTO quotas (provider, used, total, percentage)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *;
|
||||
`;
|
||||
const values = [entry.provider, entry.used, entry.total, entry.percentage];
|
||||
const res = await pool.query(query, values);
|
||||
return res.rows[0];
|
||||
}
|
||||
|
||||
export async function getLatestQuotas() {
|
||||
const query = `
|
||||
SELECT DISTINCT ON (provider) *
|
||||
FROM quotas
|
||||
ORDER BY provider, timestamp DESC;
|
||||
`;
|
||||
const res = await pool.query(query);
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
export async function getQuotaHistory(days: number = 7) {
|
||||
const query = `
|
||||
SELECT *
|
||||
FROM quotas
|
||||
WHERE timestamp > CURRENT_DATE - $1::integer
|
||||
ORDER BY timestamp ASC;
|
||||
`;
|
||||
const res = await pool.query(query, [days]);
|
||||
return res.rows;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import axios from 'axios';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export async function sendNtfyAlert(provider: string, percentage: number) {
|
||||
const url = process.env.NTFY_URL || 'https://ntfy.orfel.de/tokenwatch_dummy';
|
||||
const message = `🚨 ${provider} quota is running low! (${percentage.toFixed(1)}% used)`;
|
||||
|
||||
try {
|
||||
await axios.post(url, message, {
|
||||
headers: {
|
||||
'Title': 'TokenWatch Alert',
|
||||
'Tags': 'warning,robot'
|
||||
}
|
||||
});
|
||||
console.log(`ntfy alert sent for ${provider}`);
|
||||
} catch (error: any) {
|
||||
console.error('Failed to send ntfy alert:', error.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import axios from 'axios';
|
||||
import dotenv from 'dotenv';
|
||||
import { QuotaEntry } from './db';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export async function fetchAntigravityQuota(): Promise<QuotaEntry> {
|
||||
try {
|
||||
const url = process.env.ANTIGRAVITY_ENDPOINT || 'https://api.antigravity.google/v1/quota';
|
||||
const res = await axios.get(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.ANTIGRAVITY_API_KEY}`
|
||||
}
|
||||
});
|
||||
|
||||
// Assuming response structure: { used: number, total: number }
|
||||
const used = res.data.used || 0;
|
||||
const total = res.data.total || 1000;
|
||||
const percentage = (used / total) * 100;
|
||||
|
||||
return { provider: 'Antigravity', used, total, percentage };
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching Antigravity quota:', error.message);
|
||||
// Fallback for demonstration / local testing if endpoint is mocked
|
||||
return { provider: 'Antigravity', used: 0, total: 100, percentage: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPoeQuota(): Promise<QuotaEntry> {
|
||||
try {
|
||||
// According to docs, returns available point balance
|
||||
const res = await axios.get('https://api.poe.com/usage/current_balance', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.POE_API_KEY}`
|
||||
}
|
||||
});
|
||||
|
||||
// POE logic: 75000 daily points is standard. We might need to adjust based on actual API payload.
|
||||
// Assuming response has: { available: number }
|
||||
const available = res.data.available || 0;
|
||||
const total = 75000;
|
||||
const used = total - available > 0 ? total - available : 0;
|
||||
const percentage = (used / total) * 100;
|
||||
|
||||
return { provider: 'POE', used, total, percentage };
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching POE quota:', error.message);
|
||||
return { provider: 'POE', used: 0, total: 75000, percentage: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMinimaxQuota(): Promise<QuotaEntry> {
|
||||
try {
|
||||
const res = await axios.get('https://api.minimax.io/v1/api/openplatform/coding_plan/remains', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`
|
||||
}
|
||||
});
|
||||
|
||||
// Assuming response has { used_quota: number, total_quota: number }
|
||||
const used = res.data.used_quota || 0;
|
||||
const total = res.data.total_quota || 100;
|
||||
const percentage = (used / total) * 100;
|
||||
|
||||
return { provider: 'MiniMax', used, total, percentage };
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching MiniMax quota:', error.message);
|
||||
return { provider: 'MiniMax', used: 0, total: 100, percentage: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllQuotas(): Promise<QuotaEntry[]> {
|
||||
const results = await Promise.all([
|
||||
fetchAntigravityQuota(),
|
||||
fetchPoeQuota(),
|
||||
fetchMinimaxQuota()
|
||||
]);
|
||||
return results;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
// Visit https://aka.ms/tsconfig to read more about this file
|
||||
"compilerOptions": {
|
||||
// File Layout
|
||||
// "rootDir": "./src",
|
||||
// "outDir": "./dist",
|
||||
|
||||
// Environment Settings
|
||||
// See also https://aka.ms/tsconfig/module
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"types": [],
|
||||
// For nodejs:
|
||||
// "lib": ["esnext"],
|
||||
// "types": ["node"],
|
||||
// and npm install -D @types/node
|
||||
|
||||
// Other Outputs
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
// Stricter Typechecking Options
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
|
||||
// Style Options
|
||||
// "noImplicitReturns": true,
|
||||
// "noImplicitOverride": true,
|
||||
// "noUnusedLocals": true,
|
||||
// "noUnusedParameters": true,
|
||||
// "noFallthroughCasesInSwitch": true,
|
||||
// "noPropertyAccessFromIndexSignature": true,
|
||||
|
||||
// Recommended Options
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"moduleDetection": "force",
|
||||
"skipLibCheck": true,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user