Initial WallMuse project

This commit is contained in:
fenglee
2026-05-09 09:12:41 +00:00
commit 3ea7d29827
91 changed files with 13136 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
export const API_V1_PREFIX = "/api/v1" as const;
export const apiPaths = {
appConfig: `${API_V1_PREFIX}/app/config`,
providers: `${API_V1_PREFIX}/providers`,
models: `${API_V1_PREFIX}/models`,
generations: `${API_V1_PREFIX}/generations`,
generationGroup: (id: string) => `${API_V1_PREFIX}/generation-groups/${id}`
} as const;

View File

@@ -0,0 +1,101 @@
import { z } from "zod";
import { ApiKeyModeSchema, ModelCapabilitySchema, ModelPricingSchema, ModelStatusSchema, ProviderStatusSchema } from "../model-capability";
export const UserRoleSchema = z.enum(["user", "admin", "super_admin"]);
export const PublicUserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().optional(),
roles: z.array(UserRoleSchema),
createdAt: z.string().datetime()
});
export const RegisterRequestSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(128),
name: z.string().min(1).max(80).optional()
});
export const LoginRequestSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(1).max(128)
});
export const AuthResponseSchema = z.object({
user: PublicUserSchema,
token: z.string().min(1)
});
export const CreateUserApiKeyRequestSchema = z.object({
providerId: z.string().uuid(),
name: z.string().min(1).max(80),
apiKey: z.string().min(6).max(4096),
baseUrl: z.string().url().optional(),
defaultModelId: z.string().uuid().optional()
});
export const UserApiKeyResponseSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
providerId: z.string().uuid(),
name: z.string(),
maskedKey: z.string(),
baseUrl: z.string().url().optional(),
defaultModelId: z.string().uuid().optional(),
enabled: z.boolean(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime()
});
export const CreateProviderRequestSchema = z.object({
slug: z.string().min(2).max(80).regex(/^[a-z0-9][a-z0-9-_]*$/),
displayName: z.string().min(1).max(120),
baseUrl: z.string().url().optional(),
authType: z.enum(["bearer", "api_key", "custom"]).default("bearer"),
status: ProviderStatusSchema.default("healthy"),
keyMode: ApiKeyModeSchema.default("hybrid"),
supportsUserKeys: z.boolean().default(true),
supportsPlatformKeys: z.boolean().default(true),
healthCheckPath: z.string().max(255).optional()
});
export const UpdateProviderRequestSchema = CreateProviderRequestSchema.partial().omit({ slug: true });
export const CreateModelRequestSchema = z.object({
providerId: z.string().uuid(),
slug: z.string().min(1).max(160),
displayName: z.string().min(1).max(160),
status: ModelStatusSchema.default("enabled"),
keyMode: ApiKeyModeSchema.default("hybrid"),
capability: ModelCapabilitySchema,
pricing: ModelPricingSchema.optional(),
sortOrder: z.number().int().default(0)
});
export const UpdateModelRequestSchema = CreateModelRequestSchema.partial().omit({ providerId: true });
export const ProviderCallLogSchema = z.object({
id: z.string().uuid(),
taskId: z.string().uuid().optional(),
providerId: z.string().uuid(),
modelId: z.string().uuid().optional(),
status: z.enum(["success", "failed"]),
latencyMs: z.number().int().nonnegative().optional(),
errorCode: z.string().optional(),
errorMessage: z.string().optional(),
createdAt: z.string().datetime()
});
export type UserRole = z.infer<typeof UserRoleSchema>;
export type PublicUser = z.infer<typeof PublicUserSchema>;
export type RegisterRequest = z.infer<typeof RegisterRequestSchema>;
export type LoginRequest = z.infer<typeof LoginRequestSchema>;
export type AuthResponse = z.infer<typeof AuthResponseSchema>;
export type CreateUserApiKeyRequest = z.infer<typeof CreateUserApiKeyRequestSchema>;
export type UserApiKeyResponse = z.infer<typeof UserApiKeyResponseSchema>;
export type CreateProviderRequest = z.infer<typeof CreateProviderRequestSchema>;
export type UpdateProviderRequest = z.infer<typeof UpdateProviderRequestSchema>;
export type CreateModelRequest = z.infer<typeof CreateModelRequestSchema>;
export type UpdateModelRequest = z.infer<typeof UpdateModelRequestSchema>;
export type ProviderCallLog = z.infer<typeof ProviderCallLogSchema>;

View File

@@ -0,0 +1,33 @@
import { z } from "zod";
import { AspectRatioSchema, ModelSummarySchema, ProviderSummarySchema, ResolutionTierSchema } from "../model-capability";
export const AppFeatureFlagsSchema = z.object({
authEnabled: z.boolean().default(true),
galleryEnabled: z.boolean().default(true),
userApiKeysEnabled: z.boolean().default(true),
generationEnabled: z.boolean().default(true),
darkModeEnabled: z.boolean().default(true)
});
export const AppConfigResponseSchema = z.object({
site: z.object({
name: z.string().default("WallMuse"),
tagline: z.string().optional(),
logoUrl: z.string().url().optional(),
supportEmail: z.string().email().optional()
}),
generation: z.object({
defaultModelId: z.string().uuid().optional(),
defaultAspectRatios: z.array(AspectRatioSchema).min(1).default(["16:9", "9:16"]),
defaultResolution: ResolutionTierSchema.default("2k"),
maxBatchSize: z.number().int().positive().default(4),
allowedResolutions: z.array(ResolutionTierSchema).min(1)
}),
features: AppFeatureFlagsSchema,
providers: z.array(ProviderSummarySchema),
models: z.array(ModelSummarySchema),
updatedAt: z.string().datetime()
});
export type AppFeatureFlags = z.infer<typeof AppFeatureFlagsSchema>;
export type AppConfigResponse = z.infer<typeof AppConfigResponseSchema>;

View File

@@ -0,0 +1,84 @@
import { z } from "zod";
import { AspectRatioSchema, GenerationModeSchema, GenerationQualitySchema, ResolutionTierSchema } from "../model-capability";
import { GenerationGroupStatusSchema, GenerationTaskStatusSchema } from "../status";
export const AssetKindSchema = z.enum(["reference", "master", "landscape", "portrait", "thumbnail", "preview", "download_zip"]);
export const AssetStatusSchema = z.enum(["temporary", "active", "deleted", "failed"]);
export const ModerationStatusSchema = z.enum(["pending", "passed", "rejected", "manual_review"]);
export const CreateGenerationRequestSchema = z.object({
mode: GenerationModeSchema,
modelId: z.string().uuid(),
prompt: z.string().min(1).max(4000),
negativePrompt: z.string().max(2000).optional(),
aspectRatios: z.array(AspectRatioSchema).min(1).max(3).default(["16:9", "9:16"]),
resolution: ResolutionTierSchema.default("2k"),
quality: GenerationQualitySchema.default("standard"),
batchSize: z.number().int().min(1).max(8).default(1),
seed: z.number().int().optional(),
referenceAssetId: z.string().uuid().optional(),
stylePresetId: z.string().uuid().optional(),
userApiKeyId: z.string().uuid().optional(),
publishToGallery: z.boolean().default(false),
metadata: z.record(z.unknown()).default({})
});
export const GenerationTaskSchema = z.object({
id: z.string().uuid(),
groupId: z.string().uuid(),
status: GenerationTaskStatusSchema,
mode: GenerationModeSchema,
aspectRatio: AspectRatioSchema,
resolution: ResolutionTierSchema,
quality: GenerationQualitySchema,
attempt: z.number().int().nonnegative(),
maxAttempts: z.number().int().positive(),
progress: z.number().int().min(0).max(100),
errorCode: z.string().optional(),
errorMessage: z.string().optional(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime()
});
export const GeneratedAssetSchema = z.object({
id: z.string().uuid(),
taskId: z.string().uuid().optional(),
kind: AssetKindSchema,
status: AssetStatusSchema,
width: z.number().int().positive().optional(),
height: z.number().int().positive().optional(),
mimeType: z.string().optional(),
publicUrl: z.string().url().optional(),
blurHash: z.string().optional(),
createdAt: z.string().datetime()
});
export const GenerationGroupSchema = z.object({
id: z.string().uuid(),
status: GenerationGroupStatusSchema,
modelId: z.string().uuid(),
prompt: z.string(),
negativePrompt: z.string().optional(),
tasks: z.array(GenerationTaskSchema),
assets: z.array(GeneratedAssetSchema).default([]),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime()
});
export const CreateGenerationResponseSchema = z.object({
generationGroup: GenerationGroupSchema,
pollingUrl: z.string().min(1)
});
export const GenerationGroupParamsSchema = z.object({
id: z.string().uuid()
});
export type AssetKind = z.infer<typeof AssetKindSchema>;
export type AssetStatus = z.infer<typeof AssetStatusSchema>;
export type ModerationStatus = z.infer<typeof ModerationStatusSchema>;
export type CreateGenerationRequest = z.infer<typeof CreateGenerationRequestSchema>;
export type GenerationTask = z.infer<typeof GenerationTaskSchema>;
export type GeneratedAsset = z.infer<typeof GeneratedAssetSchema>;
export type GenerationGroup = z.infer<typeof GenerationGroupSchema>;
export type CreateGenerationResponse = z.infer<typeof CreateGenerationResponseSchema>;

92
packages/shared/src/dto/web.ts Executable file
View File

@@ -0,0 +1,92 @@
import { z } from "zod";
import { AspectRatioSchema, ResolutionTierSchema } from "../model-capability";
import { GenerationGroupStatusSchema } from "../status";
export const ThemePreferenceSchema = z.enum(["system", "light", "dark"]);
export const WebWallpaperSchema = z.object({
id: z.string(),
title: z.string(),
prompt: z.string(),
imageUrl: z.string().url(),
ratio: AspectRatioSchema,
resolution: ResolutionTierSchema,
style: z.string(),
model: z.string(),
likes: z.number().int().nonnegative(),
downloads: z.number().int().nonnegative(),
colors: z.array(z.string()),
createdAt: z.string().datetime(),
featured: z.boolean().optional()
});
export const WebGenerationAssetSchema = z.object({
id: z.string(),
label: z.enum(["Desktop", "Mobile", "Master"]),
ratio: AspectRatioSchema,
width: z.number().int().positive(),
height: z.number().int().positive(),
imageUrl: z.string().url()
});
export const WebGenerationGroupSchema = z.object({
id: z.string(),
prompt: z.string(),
negativePrompt: z.string().optional(),
status: GenerationGroupStatusSchema,
style: z.string(),
model: z.string(),
resolution: ResolutionTierSchema,
consistencyScore: z.number().int().min(0).max(100),
createdAt: z.string().datetime(),
assets: z.array(WebGenerationAssetSchema)
});
export const WebUserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
avatarInitials: z.string(),
theme: ThemePreferenceSchema
});
export const WebUserApiKeySchema = z.object({
id: z.string(),
provider: z.string(),
baseUrl: z.string(),
model: z.string(),
maskedKey: z.string(),
isDefault: z.boolean(),
status: z.enum(["untested", "connected", "failed"]),
updatedAt: z.string().datetime()
});
export const WebCreateGenerationInputSchema = z.object({
mode: z.enum(["text_to_image", "image_to_image"]),
prompt: z.string().min(1),
negativePrompt: z.string().optional(),
style: z.string(),
resolution: ResolutionTierSchema,
outputPair: z.boolean(),
provider: z.string(),
model: z.string(),
privateMode: z.boolean()
});
export const WebSaveApiKeyInputSchema = z.object({
provider: z.string(),
baseUrl: z.string().url(),
apiKey: z.string().min(1),
model: z.string(),
saveToAccount: z.boolean(),
isDefault: z.boolean()
});
export type ThemePreference = z.infer<typeof ThemePreferenceSchema>;
export type WebWallpaper = z.infer<typeof WebWallpaperSchema>;
export type WebGenerationAsset = z.infer<typeof WebGenerationAssetSchema>;
export type WebGenerationGroup = z.infer<typeof WebGenerationGroupSchema>;
export type WebUser = z.infer<typeof WebUserSchema>;
export type WebUserApiKey = z.infer<typeof WebUserApiKeySchema>;
export type WebCreateGenerationInput = z.infer<typeof WebCreateGenerationInputSchema>;
export type WebSaveApiKeyInput = z.infer<typeof WebSaveApiKeyInputSchema>;

7
packages/shared/src/index.ts Executable file
View File

@@ -0,0 +1,7 @@
export * from "./api-paths";
export * from "./model-capability";
export * from "./status";
export * from "./dto/app-config";
export * from "./dto/generation";
export * from "./dto/api-management";
export * from "./dto/web";

View File

@@ -0,0 +1,84 @@
import { z } from "zod";
export const GenerationModeSchema = z.enum(["text_to_image", "image_to_image"]);
export const GenerationQualitySchema = z.enum(["standard", "hd", "ultra"]);
export const ResolutionTierSchema = z.enum(["1k", "2k", "4k"]);
export const AspectRatioSchema = z.enum(["1:1", "4:3", "3:4", "16:9", "9:16", "21:9"]);
export const ProviderStatusSchema = z.enum(["disabled", "healthy", "degraded", "error"]);
export const ModelStatusSchema = z.enum(["draft", "enabled", "disabled", "deprecated"]);
export const ApiKeyModeSchema = z.enum(["platform", "user_own", "hybrid"]);
export type GenerationMode = z.infer<typeof GenerationModeSchema>;
export type GenerationQuality = z.infer<typeof GenerationQualitySchema>;
export type ResolutionTier = z.infer<typeof ResolutionTierSchema>;
export type AspectRatio = z.infer<typeof AspectRatioSchema>;
export type ProviderStatus = z.infer<typeof ProviderStatusSchema>;
export type ModelStatus = z.infer<typeof ModelStatusSchema>;
export type ApiKeyMode = z.infer<typeof ApiKeyModeSchema>;
export const ImageSizePresetSchema = z.object({
aspectRatio: AspectRatioSchema,
resolution: ResolutionTierSchema,
width: z.number().int().positive(),
height: z.number().int().positive(),
providerSizeValue: z.string().optional(),
native: z.boolean().default(true),
requiresUpscale: z.boolean().default(false)
});
export const ModelPricingSchema = z.object({
currency: z.string().min(3).max(8).default("USD"),
unit: z.enum(["image", "megapixel", "request", "credit"]),
amount: z.number().nonnegative(),
estimatedCredits: z.number().nonnegative().optional()
});
export const ModelCapabilitySchema = z.object({
supportsTextToImage: z.boolean(),
supportsImageToImage: z.boolean(),
supportsEdit: z.boolean().default(false),
supportsNegativePrompt: z.boolean().default(false),
supportsSeed: z.boolean().default(false),
supportsBatch: z.boolean().default(false),
supportsStreaming: z.boolean().default(false),
supportsBase64Result: z.boolean().default(false),
supportsUrlResult: z.boolean().default(true),
supportsNative4k: z.boolean().default(false),
maxBatchSize: z.number().int().positive().default(1),
maxInputImages: z.number().int().nonnegative().default(0),
maxPromptLength: z.number().int().positive().default(4000),
maxNegativePromptLength: z.number().int().positive().default(2000),
maxPixels: z.number().int().positive().optional(),
supportedAspectRatios: z.array(AspectRatioSchema).min(1),
supportedResolutions: z.array(ResolutionTierSchema).min(1),
sizePresets: z.array(ImageSizePresetSchema).min(1),
defaultParams: z.record(z.unknown()).default({})
});
export type ImageSizePreset = z.infer<typeof ImageSizePresetSchema>;
export type ModelPricing = z.infer<typeof ModelPricingSchema>;
export type ModelCapability = z.infer<typeof ModelCapabilitySchema>;
export const ModelSummarySchema = z.object({
id: z.string().uuid(),
providerId: z.string().uuid(),
slug: z.string().min(1),
displayName: z.string().min(1),
status: ModelStatusSchema,
keyMode: ApiKeyModeSchema,
capability: ModelCapabilitySchema,
pricing: ModelPricingSchema.optional(),
sortOrder: z.number().int().default(0)
});
export const ProviderSummarySchema = z.object({
id: z.string().uuid(),
slug: z.string().min(1),
displayName: z.string().min(1),
status: ProviderStatusSchema,
keyMode: ApiKeyModeSchema,
modelCount: z.number().int().nonnegative().default(0)
});
export type ModelSummary = z.infer<typeof ModelSummarySchema>;
export type ProviderSummary = z.infer<typeof ProviderSummarySchema>;

59
packages/shared/src/status.ts Executable file
View File

@@ -0,0 +1,59 @@
import { z } from "zod";
export const generationGroupStatuses = [
"queued",
"running",
"partial_succeeded",
"succeeded",
"failed",
"canceled"
] as const;
export const generationTaskStatuses = [
"created",
"queued",
"dispatching",
"running",
"uploading",
"post_processing",
"moderating",
"succeeded",
"failed",
"retrying",
"canceled",
"expired"
] as const;
export const terminalGenerationTaskStatuses = [
"succeeded",
"failed",
"canceled",
"expired"
] as const;
export const retryableGenerationTaskStatuses = ["failed", "retrying"] as const;
export const GenerationGroupStatusSchema = z.enum(generationGroupStatuses);
export const GenerationTaskStatusSchema = z.enum(generationTaskStatuses);
export type GenerationGroupStatus = z.infer<typeof GenerationGroupStatusSchema>;
export type GenerationTaskStatus = z.infer<typeof GenerationTaskStatusSchema>;
export const generationTaskStateTransitions: Record<GenerationTaskStatus, readonly GenerationTaskStatus[]> = {
created: ["queued", "canceled"],
queued: ["dispatching", "canceled", "expired"],
dispatching: ["running", "retrying", "failed", "canceled"],
running: ["uploading", "retrying", "failed", "canceled"],
uploading: ["post_processing", "retrying", "failed", "canceled"],
post_processing: ["moderating", "retrying", "failed", "canceled"],
moderating: ["succeeded", "failed", "canceled"],
succeeded: [],
failed: ["retrying"],
retrying: ["queued", "failed", "canceled", "expired"],
canceled: [],
expired: []
};
export function isTerminalGenerationTaskStatus(status: GenerationTaskStatus): boolean {
return terminalGenerationTaskStatuses.includes(status as (typeof terminalGenerationTaskStatuses)[number]);
}