feat: add openai monitor

This commit is contained in:
moonrailgun 2023-12-21 01:30:48 +08:00
parent 604e6ef454
commit ef00122752
8 changed files with 174 additions and 24 deletions

View File

@ -124,7 +124,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
</div>
</Space>
<div className="text-black text-opacity-75">
<div className="text-black dark:text-gray-200 text-opacity-75">
Monitored for {dayjs().diff(dayjs(monitorInfo.createdAt), 'days')}{' '}
days
</div>

View File

@ -3,10 +3,12 @@ import { MonitorInfo } from '../../../../types';
import { pingProvider } from './ping';
import { httpProvider } from './http';
import { MonitorProvider } from './types';
import { openaiProvider } from './openai';
export const monitorProviders: MonitorProvider[] = [
pingProvider, // ping
httpProvider, // http
openaiProvider, // http
];
export function getMonitorProvider(type: string) {
@ -20,7 +22,7 @@ export function getMonitorProvider(type: string) {
export function getMonitorLink(info: MonitorInfo): React.ReactNode {
const provider = getMonitorProvider(info.type);
if (!provider) {
if (!provider || !provider.link) {
return null;
}

View File

@ -0,0 +1,63 @@
import { Form, Input } from 'antd';
import React from 'react';
import { MonitorOverviewComponent, MonitorProvider } from './types';
import { useCurrentWorkspaceId } from '../../../store/user';
import { trpc } from '../../../api/trpc';
import dayjs from 'dayjs';
import { MonitorStatsBlock } from '../MonitorStatsBlock';
import { isEmpty } from 'lodash-es';
export const MonitorOpenai: React.FC = React.memo(() => {
return (
<>
<Form.Item
label="Session Key"
name={['payload', 'sessionKey']}
rules={[{ required: true }]}
>
<Input.Password
placeholder="sess-************"
visibilityToggle={false}
/>
</Form.Item>
</>
);
});
MonitorOpenai.displayName = 'MonitorOpenai';
export const MonitorOpenaiOverview: MonitorOverviewComponent = React.memo(
(props) => {
const workspaceId = useCurrentWorkspaceId();
const { data } = trpc.monitor.getStatus.useQuery({
workspaceId,
monitorId: props.monitorId,
statusName: 'credit',
});
if (!data || !data.payload || typeof data.payload !== 'object') {
return null;
}
const payload = data.payload as Record<string, any>;
if (isEmpty(payload.certInfo)) {
return null;
}
return (
<MonitorStatsBlock
title="Usage"
desc={dayjs(data.updatedAt).format('YYYY-MM-DD')}
text={`${payload.totalUsed} / ${payload.totalGranted}`}
/>
);
}
);
MonitorOpenaiOverview.displayName = 'MonitorOpenaiOverview';
export const openaiProvider: MonitorProvider = {
label: 'OpenAI',
name: 'openai',
form: MonitorOpenai,
overview: [MonitorOpenaiOverview],
};

View File

@ -3,7 +3,7 @@ import { MonitorInfo } from '../../../../types';
export interface MonitorProvider {
label: string;
name: string;
link: (info: MonitorInfo) => React.ReactNode;
link?: (info: MonitorInfo) => React.ReactNode;
form: React.ComponentType;
overview?: MonitorOverviewComponent[];
}

View File

@ -0,0 +1,32 @@
import { prisma } from '../../_client';
export async function saveMonitorStatus(
monitorId: string,
statusName: string,
payload: Record<string, any>
) {
try {
const res = await prisma.monitorStatus.upsert({
where: {
monitorId_statusName: {
monitorId,
statusName,
},
},
update: {
payload,
},
create: {
monitorId,
statusName,
payload,
},
});
return res;
} catch (err) {
console.error(err);
return null;
}
}

View File

@ -4,6 +4,7 @@ import { logger } from '../../../utils/logger';
import dayjs from 'dayjs';
import { prisma } from '../../_client';
import https from 'https';
import { saveMonitorStatus } from './_utils';
export const http: MonitorProvider<{
url: string;
@ -81,27 +82,9 @@ export const http: MonitorProvider<{
try {
const { valid, certInfo } = checkCertificate(res);
await prisma.monitorStatus.upsert({
where: {
monitorId_statusName: {
monitorId: monitor.id,
statusName: 'tls',
},
},
update: {
payload: {
valid,
certInfo,
},
},
create: {
monitorId: monitor.id,
statusName: 'tls',
payload: {
valid,
certInfo,
},
},
await saveMonitorStatus(monitor.id, 'tls', {
valid,
certInfo,
});
} catch (err) {}
}

View File

@ -1,8 +1,10 @@
import { http } from './http';
import { ping } from './ping';
import { openai } from './openai';
import type { MonitorProvider } from './type';
export const monitorProviders: Record<string, MonitorProvider<any>> = {
ping,
http,
openai,
};

View File

@ -0,0 +1,68 @@
import { z } from 'zod';
import { MonitorProvider } from './type';
import axios from 'axios';
import { saveMonitorStatus } from './_utils';
const openaiCreditGrantsSchema = z.object({
object: z.string(),
total_granted: z.number(),
total_used: z.number(),
total_available: z.number(),
total_paid_available: z.number(),
grants: z.object({
object: z.string(),
data: z.array(
z.object({
object: z.string(),
id: z.string(),
grant_amount: z.number(),
used_amount: z.number(),
effective_at: z.number(),
expires_at: z.number(),
})
),
}),
});
export const openai: MonitorProvider<{
sessionKey: string;
}> = {
run: async (monitor) => {
if (typeof monitor.payload !== 'object') {
throw new Error('monitor.payload should be object');
}
const { sessionKey } = monitor.payload;
const res = await getBillingCreditGrants(sessionKey);
const balance = res.total_granted - res.total_used;
await saveMonitorStatus(monitor.id, 'credit', {
totalGranted: res.total_granted,
totalUsed: res.total_used,
totalAvailable: res.total_available,
totalPaidAvailable: res.total_paid_available,
});
if (balance <= 0) {
return -1;
}
return balance;
},
};
async function getBillingCreditGrants(sessionKey: string) {
const { data } = await axios.get(
'https://api.openai.com/dashboard/billing/credit_grants',
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${sessionKey}`,
},
}
);
return openaiCreditGrantsSchema.parse(data);
}