Files
Jyotisha/frontend/src/components/admin/admin-app.tsx
T
Jesse_ChenandClaude Opus 5.5 12e315a9bb feat(reply-quality): 👎 keeps a snapshot of the turn; admin 对话质量记录
A 👎 now saves, server side, the rated turn plus the context window the
model read for it (reconstructed from the stored session with the same
consultationHistoryWindow the consult route uses), model and run facts.
👍 is only counted. Switching to 👍 or clearing deletes the snapshot.
Bodies are blanked after 90 days; the row cascades on session delete
and on account deletion.

- Optional 不满意原因 panel under the answer after a 👎 (five reasons,
  200-char note, "会把这一轮对话发给我们排查").
- Admin 对话质量记录: 👍/👎 stats by day and model, list without text,
  audited snapshot open, 处理状态 + note (support.quality.read/write).
- Privacy draft: what a 👎 keeps, why, 90 days, deletion.
- Migration 20260930050000 is add-only; set_reply_rating() replaced with
  the same signature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-09-30 09:42:11 +08:00

148 lines
6.5 KiB
TypeScript

"use client";
import {
ApiOutlined,
AuditOutlined,
BarChartOutlined,
ControlOutlined,
CreditCardOutlined,
DatabaseOutlined,
DislikeOutlined,
ExperimentOutlined,
GiftOutlined,
MessageOutlined,
SafetyCertificateOutlined,
ShoppingOutlined,
TeamOutlined,
TransactionOutlined,
UserOutlined,
} from "@ant-design/icons";
import { Authenticated, Refine } from "@refinedev/core";
import { ErrorComponent, ThemedLayout, useNotificationProvider } from "@refinedev/antd";
import routerProvider from "@refinedev/nextjs-router";
import { App as AntdApp, ConfigProvider, Spin, theme } from "antd";
import type { ReactNode } from "react";
import {
adminAccessControlProvider,
adminAuthProvider,
adminDataProvider,
} from "@/lib/admin/providers";
export function AdminApp({ children }: { children: ReactNode }) {
const notificationProvider = useNotificationProvider();
return (
<div className="admin-app-shell">
<ConfigProvider
componentSize="middle"
theme={{
algorithm: theme.defaultAlgorithm,
token: {
colorPrimary: "#85432f",
colorInfo: "#85432f",
colorSuccess: "#28633e",
colorWarning: "#8a641f",
colorError: "#9a2f2f",
colorBgBase: "#fbfaf7",
colorBgContainer: "#fbfaf7",
colorBgLayout: "#f3f2ee",
colorBorder: "#d8d6cf",
colorText: "#1d1d1f",
colorTextSecondary: "#5f5f59",
borderRadius: 6,
borderRadiusLG: 8,
boxShadowSecondary: "0 1px 2px rgba(29, 29, 31, .06)",
boxShadowTertiary: "none",
fontFamily: "var(--font-inter, Inter), system-ui, sans-serif",
},
components: {
Layout: {
bodyBg: "#f3f2ee",
siderBg: "#ebe9e3",
lightSiderBg: "#ebe9e3",
headerBg: "#fbfaf7",
triggerBg: "#ebe9e3",
lightTriggerBg: "#ebe9e3",
},
Menu: {
itemBg: "transparent",
subMenuItemBg: "transparent",
itemSelectedBg: "rgba(255, 255, 255, .72)",
itemSelectedColor: "#1d1d1f",
itemHoverBg: "rgba(255, 255, 255, .46)",
itemBorderRadius: 6,
subMenuItemBorderRadius: 6,
},
Card: {
headerBg: "transparent",
headerFontSize: 16,
headerHeight: 48,
bodyPadding: 18,
headerPadding: 18,
},
Table: {
headerBg: "#ebe9e3",
headerColor: "#5f5f59",
rowHoverBg: "#f3f2ee",
cellPaddingBlock: 10,
cellPaddingInline: 12,
},
Tag: {
defaultBg: "#f3f2ee",
defaultColor: "#5f5f59",
},
},
}}
>
<AntdApp>
<Refine
routerProvider={routerProvider}
dataProvider={adminDataProvider}
authProvider={adminAuthProvider}
accessControlProvider={adminAccessControlProvider}
notificationProvider={notificationProvider}
resources={[
{ name: "administrators", list: "/admin/administrators", meta: { label: "管理员", icon: <SafetyCertificateOutlined /> } },
{ name: "roles", list: "/admin/roles", meta: { label: "角色权限", icon: <TeamOutlined /> } },
{ name: "customers", list: "/admin/customers", meta: { label: "用户资料", icon: <UserOutlined /> } },
{ name: "account-deletions", list: "/admin/account-deletions", meta: { label: "注销申请", icon: <UserOutlined /> } },
{ name: "products", list: "/admin/products", meta: { label: "商品权益", icon: <ShoppingOutlined /> } },
{ name: "subscriptions", list: "/admin/subscriptions", meta: { label: "订阅", icon: <CreditCardOutlined /> } },
{ name: "orders", list: "/admin/orders", meta: { label: "订单", icon: <DatabaseOutlined /> } },
{ name: "codes", list: "/admin/codes", meta: { label: "兑换码", icon: <GiftOutlined /> } },
{ name: "credit-transactions", list: "/admin/credit-transactions", meta: { label: "积分流水", icon: <TransactionOutlined /> } },
{ name: "consultations", list: "/admin/consultations", meta: { label: "咨询请求", icon: <MessageOutlined /> } },
{ name: "usage", list: "/admin/usage", meta: { label: "用量与成本", icon: <ExperimentOutlined /> } },
{ name: "rectification-telemetry", list: "/admin/rectification-telemetry", meta: { label: "校正统计", icon: <BarChartOutlined /> } },
{ name: "feedback", list: "/admin/feedback", meta: { label: "反馈与投诉", icon: <MessageOutlined /> } },
{ name: "reply-quality", list: "/admin/reply-quality", meta: { label: "对话质量记录", icon: <DislikeOutlined /> } },
{ name: "models", list: "/admin/models", meta: { label: "模型配置", icon: <ApiOutlined /> } },
{ name: "feature-pricing", list: "/admin/feature-pricing", meta: { label: "功能定价", icon: <CreditCardOutlined /> } },
{ name: "pricing-simulator", list: "/admin/pricing-simulator", meta: { label: "定价测算", icon: <ExperimentOutlined /> } },
{ name: "model-releases", list: "/admin/model-releases", meta: { label: "模型发布", icon: <ControlOutlined /> } },
{ name: "feature-flags", list: "/admin/feature-flags", meta: { label: "功能开关", icon: <ControlOutlined /> } },
{ name: "security", list: "/admin/security", meta: { label: "安全验证", icon: <SafetyCertificateOutlined /> } },
{ name: "audit-logs", list: "/admin/audit-logs", meta: { label: "审计日志", icon: <AuditOutlined /> } },
{ name: "moderation-events", list: "/admin/moderation-events", meta: { label: "内容审核", icon: <AuditOutlined /> } },
]}
options={{
syncWithLocation: true,
warnWhenUnsavedChanges: true,
title: { text: "Jyotisha 后台" },
}}
>
<Authenticated
key="admin-authenticated"
loading={<div className="admin-loading"><Spin size="large" /><span>正在验证后台权限</span></div>}
>
<ThemedLayout>{children}</ThemedLayout>
</Authenticated>
</Refine>
</AntdApp>
</ConfigProvider>
</div>
);
}
export { ErrorComponent as AdminErrorComponent };