Add managed content and keyboard usage insights
Introduce versioned official content workflows and privacy-safe keyboard analytics, while preventing repeat DeviceCheck sign-ins from incorrectly restricting eligible accounts.
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
AdminOperatorCreateRequest,
|
||||
AdminOperatorProvisioning,
|
||||
AdminSecuritySummary,
|
||||
AdminHintPack,
|
||||
AdminLoginResponse,
|
||||
AuditQuery,
|
||||
AuditLogEntry,
|
||||
@@ -14,12 +15,17 @@ import type {
|
||||
Overview,
|
||||
PageResult,
|
||||
ProductAnalyticsOverview,
|
||||
CreateOfficialSkillRequest,
|
||||
OfficialSkill,
|
||||
OfficialSkillCatalog,
|
||||
ReferralsQuery,
|
||||
ReferralOverview,
|
||||
SessionResponse,
|
||||
UserDetail,
|
||||
UsersQuery,
|
||||
UserSummary,
|
||||
UpdateHintPackRequest,
|
||||
UpdateOfficialSkillRequest,
|
||||
} from "./types";
|
||||
|
||||
const API_BASE = "/v1/admin";
|
||||
@@ -73,6 +79,9 @@ function safeMessage(status: number, code?: string): string {
|
||||
ADMIN_USERNAME_CONFLICT: "该管理员用户名已存在",
|
||||
CANNOT_DISABLE_SELF: "不能停用当前登录的管理员",
|
||||
LAST_SUPER_ADMIN_REQUIRED: "必须至少保留一名启用的超级管理员",
|
||||
CONTENT_SKILL_NOT_FOUND: "未找到该官方 Skill",
|
||||
CONTENT_SKILL_CONFLICT: "该官方 Skill ID 已存在",
|
||||
CONTENT_HINT_PACK_NOT_FOUND: "该语言的 Hint pack 尚未发布",
|
||||
RATE_LIMITED: "操作过于频繁,请稍后再试",
|
||||
};
|
||||
if (code && messages[code]) return messages[code];
|
||||
@@ -187,6 +196,38 @@ export const adminApi = {
|
||||
productAnalytics: (range: string) =>
|
||||
request<ProductAnalyticsOverview>(`/analytics${encodeQuery({ range })}`),
|
||||
|
||||
contentSkills: () => request<OfficialSkillCatalog>("/content/skills"),
|
||||
|
||||
createContentSkill: (payload: CreateOfficialSkillRequest) =>
|
||||
request<OfficialSkill>("/content/skills", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
updateContentSkill: (id: string, payload: UpdateOfficialSkillRequest) =>
|
||||
request<OfficialSkill>(`/content/skills/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
setContentSkillEnabled: (id: string, enabled: boolean) =>
|
||||
request<void>(
|
||||
`/content/skills/${encodeURIComponent(id)}/${enabled ? "enable" : "disable"}`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
|
||||
contentHintPack: (locale: "zh" | "en") =>
|
||||
request<AdminHintPack>(`/content/hints/${locale}`),
|
||||
|
||||
updateContentHintPack: (
|
||||
locale: "zh" | "en",
|
||||
payload: UpdateHintPackRequest,
|
||||
) =>
|
||||
request<AdminHintPack>(`/content/hints/${locale}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
users: (value: string | UsersQuery = "", legacyCursor?: string) => {
|
||||
const params =
|
||||
typeof value === "string"
|
||||
|
||||
@@ -11,7 +11,76 @@ export type AdminAuditAction =
|
||||
| "OPERATOR_UNLOCKED"
|
||||
| "OPERATOR_CREDENTIALS_RESET"
|
||||
| "OPERATOR_SESSIONS_REVOKED"
|
||||
| "MANUAL_CREDIT_GRANTED";
|
||||
| "MANUAL_CREDIT_GRANTED"
|
||||
| "CONTENT_SKILL_CREATED"
|
||||
| "CONTENT_SKILL_UPDATED"
|
||||
| "CONTENT_SKILL_ENABLED"
|
||||
| "CONTENT_SKILL_DISABLED"
|
||||
| "CONTENT_HINT_PACK_PUBLISHED";
|
||||
|
||||
export interface SkillLocalization {
|
||||
name: string;
|
||||
summary: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export interface OfficialSkill {
|
||||
id: string;
|
||||
systemImage: string;
|
||||
sortOrder: number;
|
||||
kind: "transform";
|
||||
thinkingEnabled: boolean;
|
||||
enabled: boolean;
|
||||
localizations: {
|
||||
"zh-Hans": SkillLocalization;
|
||||
en: SkillLocalization;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OfficialSkillCatalog {
|
||||
revision: number;
|
||||
generatedAt?: string;
|
||||
skills: OfficialSkill[];
|
||||
}
|
||||
|
||||
export type CreateOfficialSkillRequest = Omit<
|
||||
OfficialSkill,
|
||||
"kind" | "enabled"
|
||||
>;
|
||||
|
||||
export type UpdateOfficialSkillRequest = Omit<
|
||||
CreateOfficialSkillRequest,
|
||||
"id"
|
||||
>;
|
||||
|
||||
export interface AIHintCard {
|
||||
id: string;
|
||||
displayText?: string;
|
||||
text?: string;
|
||||
prompt: string;
|
||||
category: string;
|
||||
priority: number;
|
||||
source: string;
|
||||
locale: "zh" | "en";
|
||||
conditions: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface AdminHintPack {
|
||||
locale: "zh" | "en";
|
||||
generatedAt?: string;
|
||||
expiresAt?: string;
|
||||
intervalHours?: number;
|
||||
version: number;
|
||||
cards: AIHintCard[];
|
||||
}
|
||||
|
||||
export interface UpdateHintPackRequest {
|
||||
generatedAt?: string;
|
||||
expiresAt?: string;
|
||||
intervalHours?: number;
|
||||
cards: AIHintCard[];
|
||||
}
|
||||
|
||||
export interface CursorPageQuery {
|
||||
cursor?: string;
|
||||
@@ -176,6 +245,25 @@ export interface ProductAnalyticsOverview {
|
||||
growthFunnel: FunnelStep[];
|
||||
retention: AnalyticsCohort[];
|
||||
aiFeatures: AnalyticsFeatureUsage[];
|
||||
keyboardUsage: {
|
||||
activeUsers: number;
|
||||
activationToInput: AnalyticsRate;
|
||||
chineseActiveUsers: number;
|
||||
englishActiveUsers: number;
|
||||
bilingualActiveUsers: number;
|
||||
totalCharacters: number;
|
||||
chineseCharacters: number;
|
||||
englishCharacters: number;
|
||||
otherCharacters: number;
|
||||
chineseSharePercent?: number;
|
||||
englishSharePercent?: number;
|
||||
inputSessions: number;
|
||||
averageCharactersPerInputSession?: number;
|
||||
chineseOnlySessions: number;
|
||||
englishOnlySessions: number;
|
||||
mixedLanguageSessions: number;
|
||||
otherOnlySessions: number;
|
||||
};
|
||||
referralFunnel: FunnelStep[];
|
||||
guardrails: {
|
||||
clientAiSuccessRate: AnalyticsRate;
|
||||
|
||||
Reference in New Issue
Block a user