نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import * as React from "react";
|
||||
import type { AdminOrganization, AdminUsageRow, ReadinessResponse } from "@agy/contracts";
|
||||
import { Badge, Card, CardHint, CardTitle, ErrorState, LoadingState, Stat } from "@agy/ui";
|
||||
import { ApiError, api } from "@/lib/api";
|
||||
|
||||
/**
|
||||
* Platform owner overview.
|
||||
*
|
||||
* Everything on this page is invisible to normal customers by design — the whole
|
||||
* point of the AI Control Plane is that model identity never reaches a tenant.
|
||||
*/
|
||||
export default function AdminOverviewPage() {
|
||||
const [readiness, setReadiness] = React.useState<ReadinessResponse | null>(null);
|
||||
const [organizations, setOrganizations] = React.useState<AdminOrganization[]>([]);
|
||||
const [usage, setUsage] = React.useState<AdminUsageRow[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const load = React.useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [nextReadiness, nextOrganizations, nextUsage] = await Promise.all([
|
||||
api.readiness(),
|
||||
api.adminOrganizations(),
|
||||
api.adminUsage(),
|
||||
]);
|
||||
setReadiness(nextReadiness);
|
||||
setOrganizations(nextOrganizations);
|
||||
setUsage(nextUsage);
|
||||
} catch (err) {
|
||||
setError(err instanceof ApiError ? err.message : "دریافت وضعیت ناموفق بود.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
if (loading) return <LoadingState label="در حال دریافت وضعیت سامانه…" />;
|
||||
if (error) return <ErrorState message={error} onRetry={load} />;
|
||||
|
||||
const totalBilled = usage.reduce((sum, row) => sum + row.total_price, 0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<header className="flex flex-col gap-1">
|
||||
<h1 className="text-lg font-medium text-slate-100">کنسول مالک سامانه</h1>
|
||||
<p className="text-sm text-slate-400">
|
||||
مدیریت ارائهدهندهها، مدلها، سیاست مسیریابی و تعرفهها.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
<Stat label="سازمانها" value={organizations.length} />
|
||||
<Stat label="مجموع اعضای سازمانها" value={organizations.reduce((s, o) => s + o.member_count, 0)} />
|
||||
<Stat
|
||||
label="مجموع مصرف ثبتشده"
|
||||
value={totalBilled.toFixed(2)}
|
||||
hint="بر پایهٔ دفتر مصرف، به واحد پول داخلی سامانه."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardTitle>آداپتورهای فعال</CardTitle>
|
||||
<CardHint>
|
||||
این مقادیر از محیط خوانده میشوند. هر مقدار «mock» یعنی آن سرویس بیرونی هنوز
|
||||
متصل نشده است.
|
||||
</CardHint>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{Object.entries(readiness?.checks.adapters ?? {}).map(([key, value]) => (
|
||||
<Badge key={key} tone={value === "mock" ? "warning" : "success"}>
|
||||
{key}: {value}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{[
|
||||
{
|
||||
title: "ارائهدهندهها و کلیدها",
|
||||
hint: "ساخت ارائهدهنده و ذخیرهٔ کلید رمزنگاریشده. کلید خام هرگز نمایش داده نمیشود.",
|
||||
path: "/admin/providers",
|
||||
},
|
||||
{
|
||||
title: "مدلها",
|
||||
hint: "نام داخلی، تواناییها و قیمت ورودی/خروجی — فقط برای مالک سامانه.",
|
||||
path: "/admin/models",
|
||||
},
|
||||
{
|
||||
title: "سیاست مسیریابی",
|
||||
hint: "حالتهای fast / cheap / balanced / reasoning / vision / coding و اجرای آزمایشی.",
|
||||
path: "/admin/routing",
|
||||
},
|
||||
{
|
||||
title: "تعرفهها",
|
||||
hint: "قیمت مشتری و برآورد هزینهٔ داخلی به تفکیک واحد مصرف.",
|
||||
path: "/admin/rate-cards",
|
||||
},
|
||||
{
|
||||
title: "سازمانها",
|
||||
hint: "فهرست همهٔ سازمانها، تعداد اعضا و مصرف تجمیعی هرکدام.",
|
||||
path: "/admin/organizations",
|
||||
},
|
||||
].map((item) => (
|
||||
<Link key={item.path} href={item.path}>
|
||||
<Card className="h-full transition-colors hover:border-accent/60">
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
<CardHint>{item.hint}</CardHint>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user