Files
Genarrative/packages/shared/src/contracts/runtime.ts
T
k88936 16f92f2110 refactor(会员): 会员档位目录移除无用的 sort_order,统一按 rank 排序
- module-runtime catalog.rs:RuntimeProfileMembershipPlanRecord 与种子目录去掉 sort_order,目录单测改为按 rank 断言
- module-runtime domain.rs/commands.rs:后台改价入参与构建函数去掉 sort_order
- spacetime-module profile.rs:profile_membership_plan 表结构去掉排序列,membership_plan_records 改按 rank 升序读取
- shared-contracts runtime.rs:ProfileMembershipPlanResponse 与 AdminUpsertProfileMembershipPlanRequest 移除 sortOrder
- spacetime-client:客户端签名、mapper 与 module_bindings 同步移除 sortOrder
- api-server runtime_profile.rs:响应构建与测试夹具去掉 sortOrder
- admin-web:AdminMembershipPlanPage 移除「排序」输入,档位列表改为按 rank 排序,契约类型同步
- packages/shared:ProfileMembershipPlanRecord 去掉 sortOrder,充值弹层改为直接沿用后端的 rank 顺序
- docs:泥点三池与会员计费后端设计同步目录字段与后台能力说明

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-03 13:14:04 +08:00

784 lines
19 KiB
TypeScript

import type { JsonObject } from './common';
import type { ProfileMudPointBalanceResponse as ProfileMudPointBalance } from './generated/ProfileMudPointBalanceResponse';
export type { ProfileMudPointBalance };
export const SAVE_SNAPSHOT_VERSION = 2;
export const DEFAULT_MUSIC_VOLUME = 0.42;
export const DEFAULT_PLATFORM_THEME = 'light';
export const PLATFORM_THEMES = ['light', 'dark'] as const;
export type PlatformTheme = (typeof PLATFORM_THEMES)[number];
export type SavedGameSnapshot<
TGameState = unknown,
TBottomTab extends string = string,
TCurrentStory = unknown,
> = {
version: number;
savedAt: string;
gameState: TGameState;
bottomTab: TBottomTab;
currentStory: TCurrentStory | null;
};
export type SavedGameSnapshotInput<
TGameState = unknown,
TBottomTab extends string = string,
TCurrentStory = unknown,
> = Omit<
SavedGameSnapshot<TGameState, TBottomTab, TCurrentStory>,
'savedAt' | 'version'
> & {
savedAt?: string;
};
export type RuntimeSaveCheckpointInput<TBottomTab extends string = string> = {
sessionId: string;
bottomTab: TBottomTab;
savedAt?: string;
};
export type RuntimeSettings = {
musicVolume: number;
platformTheme: PlatformTheme;
};
export type BasicOkResult = {
ok: true;
};
export type ProfileDashboardCardKey = 'wallet' | 'playTime' | 'playedWorks';
export type ProfileDashboardSummary = {
walletBalance: number;
totalPlayTimeMs: number;
playedWorldCount: number;
updatedAt: string | null;
dailyFreePoints?: ProfileDailyFreePoints;
};
export type ProfileDailyFreePoints = {
dayKey: number;
grantedPoints: number;
remainingPoints: number;
resetsAt: string;
updatedAt: string;
};
export type ProfileWalletLedgerEntry = {
id: string;
amountDelta: number;
balanceAfter: number;
sourceType:
| 'snapshot_sync'
| 'new_user_registration_reward'
| 'invite_inviter_reward'
| 'invite_invitee_reward'
| 'points_recharge'
| 'membership_period_grant'
| 'membership_period_reset'
| 'membership_upgrade_grant'
| 'daily_free_grant'
| 'daily_free_reset'
| 'asset_operation_consume'
| 'llm_router_consume'
| 'asset_operation_refund'
| 'recharge_refund_recovery'
| 'redeem_code_reward'
| 'puzzle_author_incentive_claim'
| 'daily_task_reward';
createdAt: string;
reason?: string;
};
export type ProfileWalletLedgerResponse = {
entries: ProfileWalletLedgerEntry[];
};
export type ExternalApiKeyProfile = {
keyId: string;
name: string;
keyPrefix: string;
scopes: string[];
createdAt: string;
lastUsedAt: string | null;
revokedAt: string | null;
updatedAt: string;
};
export type ExternalApiKeyListResponse = {
keys: ExternalApiKeyProfile[];
};
export type ExternalApiKeyCreateResponse = {
apiKey: string;
key: ExternalApiKeyProfile;
};
export type ExternalApiKeyMutationResponse = {
key: ExternalApiKeyProfile;
};
export type ProfileRechargeProductKind = 'points' | 'membership';
export type ProfileMembershipStatus = 'normal' | 'active';
/** 会员档位:`normal` 是非会员占位档位,不可购买。 */
export type ProfileMembershipPlan =
| 'normal'
| 'starter'
| 'plus'
| 'pro'
| 'max';
/** 会员周期:有效期内不支持月年互换,到期后重新选择。 */
export type ProfileMembershipCycleKind = 'monthly' | 'yearly';
/** 模型权限:`basic` 仅基础模型,`full` 基础及高性能模型。 */
export type ProfileMembershipModelAccess = 'basic' | 'full';
export type ProfileRechargeOrderStatus =
| 'pending'
| 'paid'
| 'failed'
| 'closed'
| 'refunded'
| 'expired';
export type ProfileRechargeProduct = {
productId: string;
title: string;
priceCents: number;
kind: ProfileRechargeProductKind;
pointsAmount: number;
bonusPoints: number;
durationDays: number;
badgeLabel: string;
description: string;
};
/** 会员档位目录行;价格与权益的唯一真相源是后端 `profile_membership_plan` 表。 */
export type ProfileMembershipPlanRecord = {
plan: ProfileMembershipPlan;
title: string;
rank: number;
monthPriceCents: number;
yearPriceCents: number;
periodPoints: number;
modelAccess: ProfileMembershipModelAccess;
/** 并发上限;`128` 表示不设上限。 */
concurrentJobLimit: number;
enabled: boolean;
};
export type ProfileMembership = {
status: ProfileMembershipStatus;
/** 当前档位;非会员与已到期都是 `normal`。 */
plan: ProfileMembershipPlan;
startedAt: string | null;
expiresAt: string | null;
updatedAt: string | null;
cycleStartedAt: string | null;
cycleResetsAt: string | null;
cycleGrantedPoints: number;
cycleRemainingPoints: number;
/** 当前期序号(1-based)。 */
cycleIndex: number;
/** 本次购买总期数(月付 1 / 年付 12)。 */
cycleCount: number;
cycleKind: ProfileMembershipCycleKind;
};
/** 补差升级报价;下单时后端会用同一套规则重算,前端展示的数字不是真相源。 */
export type ProfileMembershipUpgradeQuote = {
currentPlan: ProfileMembershipPlan;
targetPlan: ProfileMembershipPlan;
cycleKind: ProfileMembershipCycleKind;
cycleIndex: number;
cycleCount: number;
/** 应付金额(分)。 */
amountCents: number;
/** 本期补点。 */
grantedPointsDelta: number;
/** 变更后月度余额 = 原剩余月度余额 + 补点。 */
monthlyBalanceAfter: number;
expiresAt: string | null;
cycleResetsAt: string | null;
remainingFullMonths: number;
};
export type ProfileRechargeOrder = {
orderId: string;
productId: string;
productTitle: string;
kind: ProfileRechargeProductKind;
amountCents: number;
status: ProfileRechargeOrderStatus;
paymentChannel: string;
paidAt: string | null;
providerTransactionId: string | null;
createdAt: string;
pointsDelta: number;
membershipExpiresAt: string | null;
expiredAt?: string | null;
expirationCheckedAt?: string | null;
expirationProviderState?: string | null;
expirationLastError?: string | null;
};
export type ProfileRechargeCenterResponse = {
// TODO mirror backend: should not nullable
mudPointBalance?: ProfileMudPointBalance;
membership: ProfileMembership;
pointProducts: ProfileRechargeProduct[];
/** 会员档位目录(后台可改价);会员购买 / 升级的价格与权益以它为准。 */
membershipPlans: ProfileMembershipPlanRecord[];
latestOrder: ProfileRechargeOrder | null;
hasPointsRecharged: boolean;
dailyFreePoints?: ProfileDailyFreePoints;
};
export type WechatMiniProgramPayParams = {
appId?: string;
timeStamp: string;
nonceStr: string;
package: string;
signType: 'RSA';
paySign: string;
};
export type WechatMiniProgramVirtualPayParams = {
mode: 'short_series_coin' | 'short_series_goods';
signData: string;
paySig: string;
signature: string;
};
export type WechatH5Payment = {
h5Url: string;
};
export type WechatNativePayment = {
codeUrl: string;
expiresAt: string;
};
export type CreateProfileRechargeOrderRequest = {
productId: string;
paymentChannel?: string;
};
export type CreateProfileRechargeOrderResponse = {
order: ProfileRechargeOrder;
center: ProfileRechargeCenterResponse;
wechatMiniProgramPayParams?:
| WechatMiniProgramPayParams
| WechatMiniProgramVirtualPayParams
| null;
wechatH5Payment?: WechatH5Payment | null;
wechatNativePayment?: WechatNativePayment | null;
};
export type ConfirmWechatProfileRechargeOrderResponse = {
order: ProfileRechargeOrder;
center: ProfileRechargeCenterResponse;
};
export type WechatProfileRechargeOrderDoneEvent = {
orderId: string;
status: ProfileRechargeOrderStatus;
};
export type WechatProfileRechargeOrderErrorEvent = {
message: string;
};
export type ProfileFeedbackStatus = 'open';
export type ProfileFeedbackEvidenceItemInput = {
fileName: string;
contentType: string;
sizeBytes: number;
dataUrl: string;
};
export type SubmitProfileFeedbackRequest = {
description: string;
contactPhone?: string | null;
evidenceItems: ProfileFeedbackEvidenceItemInput[];
};
export type ProfileFeedbackEvidenceItem = {
evidenceId: string;
fileName: string;
contentType: string;
sizeBytes: number;
};
export type ProfileFeedbackSubmission = {
feedbackId: string;
status: ProfileFeedbackStatus;
createdAt: string;
evidenceItems: ProfileFeedbackEvidenceItem[];
};
export type SubmitProfileFeedbackResponse = {
feedback: ProfileFeedbackSubmission;
};
export type ProfileReferralInviteCenterResponse = {
inviteCode: string;
inviteLinkPath: string;
invitedCount: number;
rewardedInviteCount: number;
todayInviterRewardCount: number;
todayInviterRewardRemaining: number;
rewardPoints: number;
invitedUsers: ProfileReferralInvitedUser[];
hasRedeemedCode: boolean;
boundInviterUserId: string | null;
boundAt: string | null;
updatedAt: string;
};
export type ProfileReferralInvitedUser = {
userId: string;
displayName: string;
avatarUrl: string | null;
boundAt: string;
};
export type RedeemProfileReferralInviteCodeRequest = {
inviteCode: string;
};
export type RedeemProfileReferralInviteCodeResponse = {
center: ProfileReferralInviteCenterResponse;
inviteeRewardGranted: boolean;
inviterRewardGranted: boolean;
inviteeBalanceAfter: number;
inviterBalanceAfter: number;
};
export type RedeemProfileRewardCodeRequest = {
code: string;
};
export type RedeemProfileRewardCodeResponse = {
walletBalance: number;
amountGranted: number;
ledgerEntry: ProfileWalletLedgerEntry;
};
export type ProfileTaskCycle = 'daily';
export type TrackingScopeKind = 'site' | 'work' | 'module' | 'user';
export type AnalyticsGranularity =
| 'day'
| 'week'
| 'month'
| 'quarter'
| 'year';
export type ProfileTaskStatus =
| 'incomplete'
| 'claimable'
| 'claimed'
| 'disabled';
export type ProfileTaskItem = {
taskId: string;
title: string;
description: string;
eventKey: string;
cycle: ProfileTaskCycle;
threshold: number;
progressCount: number;
rewardPoints: number;
status: ProfileTaskStatus;
dayKey: number;
claimedAt: string | null;
updatedAt: string;
};
export type ProfileTaskCenterResponse = {
dayKey: number;
walletBalance: number;
tasks: ProfileTaskItem[];
updatedAt: string;
};
export type ClaimProfileTaskRewardResponse = {
taskId: string;
dayKey: number;
rewardPoints: number;
walletBalance: number;
ledgerEntry: ProfileWalletLedgerEntry;
center: ProfileTaskCenterResponse;
};
export type ProfileTaskConfigAdminResponse = {
taskId: string;
title: string;
description: string;
eventKey: string;
cycle: ProfileTaskCycle;
scopeKind: TrackingScopeKind;
threshold: number;
rewardPoints: number;
enabled: boolean;
sortOrder: number;
createdBy: string;
createdAt: string;
updatedBy: string;
updatedAt: string;
};
export type ProfileTaskConfigAdminListResponse = {
entries: ProfileTaskConfigAdminResponse[];
};
export type AnalyticsMetricQueryRequest = {
eventKey: string;
scopeKind: TrackingScopeKind;
scopeId: string;
granularity: AnalyticsGranularity;
};
export type AnalyticsBucketMetric = {
bucketKey: string;
bucketStartDateKey: number;
bucketEndDateKey: number;
value: number;
};
export type AnalyticsMetricQueryResponse = {
buckets: AnalyticsBucketMetric[];
};
export type AdminUpsertProfileTaskConfigRequest = {
taskId: string;
title: string;
description?: string | null;
eventKey: string;
cycle: ProfileTaskCycle;
scopeKind: TrackingScopeKind;
threshold: number;
rewardPoints: number;
enabled?: boolean;
sortOrder?: number;
};
export type AdminDisableProfileTaskConfigRequest = {
taskId: string;
};
export type ProfileRedeemCodeMode = 'public' | 'unique' | 'private';
export type ProfileRedeemCodeAdminResponse = {
code: string;
mode: ProfileRedeemCodeMode;
rewardPoints: number;
maxUses: number;
globalUsedCount: number;
enabled: boolean;
allowedUserIds: string[];
startsAt?: string | null;
expiresAt?: string | null;
createdBy: string;
createdAt: string;
updatedAt: string;
};
export type ProfileCodeOperationAdminResponse = {
operationId: string;
codeKind: 'redeem' | 'invite' | string;
code: string;
action: 'create' | 'update' | 'disable' | string;
operatorUserId: string;
createdAt: string;
};
export type ProfileRedeemCodeAdminListResponse = {
entries: ProfileRedeemCodeAdminResponse[];
operations: ProfileCodeOperationAdminResponse[];
};
export type AdminUpsertProfileRedeemCodeRequest = {
code: string;
mode: ProfileRedeemCodeMode;
rewardPoints: number;
maxUses: number;
enabled?: boolean;
allowedUserIds?: string[];
allowedPublicUserCodes?: string[];
startsAt?: string | null;
expiresAt?: string | null;
};
export type AdminDisableProfileRedeemCodeRequest = {
code: string;
};
export type AdminUpsertProfileInviteCodeRequest = {
inviteCode: string;
metadata?: Record<string, unknown> | null;
startsAt?: string | null;
expiresAt?: string | null;
};
export type ProfileInviteCodeAdminResponse = {
userId: string;
inviteCode: string;
metadata: Record<string, unknown>;
startsAt?: string | null;
expiresAt?: string | null;
status: 'pending' | 'active' | 'expired';
createdAt: string;
updatedAt: string;
};
export type ProfileInviteCodeAdminListResponse = {
entries: ProfileInviteCodeAdminResponse[];
operations: ProfileCodeOperationAdminResponse[];
};
export type ProfilePlayedWorkSummary = {
worldKey: string;
ownerUserId: string | null;
profileId: string | null;
worldType: string | null;
worldTitle: string;
worldSubtitle: string;
firstPlayedAt: string;
lastPlayedAt: string;
lastObservedPlayTimeMs: number;
};
export type ProfilePlayStatsResponse = {
totalPlayTimeMs: number;
playedWorks: ProfilePlayedWorkSummary[];
updatedAt: string | null;
};
export type ProfileSaveArchiveSummary = {
worldKey: string;
ownerUserId: string | null;
profileId: string | null;
worldType: string | null;
worldName: string;
subtitle: string;
summaryText: string;
coverImageSrc: string | null;
lastPlayedAt: string;
};
export type ProfileSaveArchiveListResponse = {
entries: ProfileSaveArchiveSummary[];
};
export type ProfileSaveArchiveResumeResponse<
TGameState = unknown,
TBottomTab extends string = string,
TCurrentStory = unknown,
> = {
entry: ProfileSaveArchiveSummary;
snapshot: SavedGameSnapshot<TGameState, TBottomTab, TCurrentStory>;
};
export type CustomWorldPublicationStatus = 'draft' | 'published';
export type CustomWorldThemeMode =
| 'martial'
| 'arcane'
| 'machina'
| 'tide'
| 'rift'
| 'mythic';
export type CustomWorldProfileRecord = JsonObject & {
id?: string;
openingCg?: CustomWorldOpeningCgProfile | null;
};
export type CustomWorldOpeningCgStatus =
| 'not_started'
| 'storyboard_generating'
| 'video_generating'
| 'ready'
| 'failed';
export type CustomWorldOpeningCgProfile = {
id: string;
status: CustomWorldOpeningCgStatus;
storyboardImageSrc?: string | null;
storyboardAssetId?: string | null;
videoSrc?: string | null;
videoAssetId?: string | null;
posterImageSrc?: string | null;
posterAssetId?: string | null;
storyboardPrompt?: string | null;
videoPrompt?: string | null;
imageModel: 'gpt-image-2';
videoModel: string;
aspectRatio: '16:9';
imageSize: '2k';
videoResolution: '480p';
durationSeconds: 15;
pointCost: 80;
estimatedWaitMinutes: 10;
generatedAt?: string | null;
updatedAt: string;
errorMessage?: string | null;
};
export type CustomWorldLibraryEntry<TProfile = CustomWorldProfileRecord> = {
ownerUserId: string;
profileId: string;
publicWorkCode: string | null;
authorPublicUserCode: string | null;
profile: TProfile;
visibility: CustomWorldPublicationStatus;
publishedAt: string | null;
updatedAt: string;
authorDisplayName: string;
worldName: string;
subtitle: string;
summaryText: string;
coverImageSrc: string | null;
themeMode: CustomWorldThemeMode;
playableNpcCount: number;
landmarkCount: number;
playCount?: number;
remixCount?: number;
likeCount?: number;
recentPlayCount7d?: number;
};
export type CustomWorldGalleryCard = Omit<
CustomWorldLibraryEntry<never>,
'profile'
>;
export type CustomWorldLibraryResponse<TProfile = CustomWorldProfileRecord> = {
entries: CustomWorldLibraryEntry<TProfile>[];
};
export type CustomWorldLibraryMutationResponse<
TProfile = CustomWorldProfileRecord,
> = {
entry: CustomWorldLibraryEntry<TProfile>;
entries: CustomWorldLibraryEntry<TProfile>[];
};
export type CustomWorldGalleryResponse = {
entries: CustomWorldGalleryCard[];
};
export type CustomWorldGalleryDetailResponse<
TProfile = CustomWorldProfileRecord,
> = {
entry: CustomWorldLibraryEntry<TProfile>;
};
export type PlatformBrowseHistoryEntry = {
ownerUserId: string;
profileId: string;
worldName: string;
subtitle: string;
summaryText: string;
coverImageSrc: string | null;
themeMode: CustomWorldThemeMode;
authorDisplayName: string;
visitedAt: string;
};
export type PlatformBrowseHistoryWriteEntry = Omit<
PlatformBrowseHistoryEntry,
'visitedAt'
> & {
visitedAt?: string;
};
export type PlatformBrowseHistoryResponse = {
entries: PlatformBrowseHistoryEntry[];
};
export type PlatformBrowseHistoryBatchSyncRequest = {
entries: PlatformBrowseHistoryWriteEntry[];
};
export const CUSTOM_WORLD_GENERATION_MODES = ['fast', 'full'] as const;
export type CustomWorldGenerationMode =
(typeof CUSTOM_WORLD_GENERATION_MODES)[number];
export const CUSTOM_WORLD_SESSION_STATUSES = [
'clarifying',
'ready_to_generate',
'generating',
'completed',
'generation_error',
] as const;
export type CustomWorldSessionStatus =
(typeof CUSTOM_WORLD_SESSION_STATUSES)[number];
export type CustomWorldQuestion = {
id: string;
label: string;
question: string;
answer?: string;
};
export type CustomWorldGenerationStep = {
id: string;
label: string;
detail: string;
completed: number;
total: number;
status: 'pending' | 'active' | 'completed';
};
export type CustomWorldGenerationProgress = {
phaseId: string;
phaseLabel: string;
phaseDetail: string;
batchLabel?: string;
overallProgress: number;
completedWeight: number;
totalWeight: number;
elapsedMs: number;
estimatedRemainingMs: number | null;
activeStepIndex: number;
steps: CustomWorldGenerationStep[];
};
export type GenerateCustomWorldProfileOptions = {
onProgress?: (progress: CustomWorldGenerationProgress) => void;
signal?: AbortSignal;
};
export type GenerateCustomWorldProfileInput = {
settingText: string;
creatorIntent?: JsonObject | null;
generationMode?: CustomWorldGenerationMode;
};
export type CreateCustomWorldSessionRequest = {
settingText: string;
creatorIntent?: JsonObject | null;
generationMode: CustomWorldGenerationMode;
};
export type AnswerCustomWorldSessionQuestionRequest = {
questionId: string;
answer: string;
};
export type CustomWorldSessionSummary = {
sessionId: string;
status: CustomWorldSessionStatus;
questions: CustomWorldQuestion[];
createdAt: string;
updatedAt: string;
};
export type CustomWorldSessionRecord = CustomWorldSessionSummary & {
settingText: string;
creatorIntent?: JsonObject | null;
generationMode: CustomWorldGenerationMode;
result?: JsonObject;
lastError?: string;
};