feat: integrate invitation access, world NPCs, and deployment

This commit is contained in:
2026-09-08 22:22:38 +08:00
parent 2a3125075f
commit 513a3eba31
75 changed files with 9566 additions and 1070 deletions

View File

@@ -24,6 +24,7 @@ export class SkinGenerationService {
if (!apiKey || apiKey.trim().length === 0) {
throw new BadRequestException('服务端尚未配置 NOVAMAILIO_API_KEY无法生成角色皮肤');
}
await this.ensureWorkerRuntime();
if (!(await this.accountProfileService.canUseRegistrationSkinGeneration(userId))) {
throw new BadRequestException('该账号没有可用的注册角色生成机会');
}
@@ -329,6 +330,39 @@ export class SkinGenerationService {
return resolve(__dirname, '../../..');
}
private async ensureWorkerRuntime(): Promise<void> {
const scriptPath = this.getScriptPath();
if (!existsSync(scriptPath)) {
throw new BadRequestException(`服务端角色生成脚本不存在: ${scriptPath}`);
}
const pythonPath = this.getPythonPath();
const result = await new Promise<{ exitCode: number | null; stderr: string }>((resolveResult) => {
const child = spawn(
pythonPath,
[
'-c',
'import einops, kornia, numpy, scipy, timm, torch, torchvision, transformers; from PIL import Image',
],
{
cwd: this.getBackendRoot(),
env: process.env,
stdio: ['ignore', 'ignore', 'pipe'],
},
);
let stderr = '';
child.stderr.on('data', (chunk: Buffer) => {
if (stderr.length < 2000) stderr += chunk.toString('utf8');
});
child.on('error', (error) => resolveResult({ exitCode: null, stderr: error.message }));
child.on('close', (exitCode) => resolveResult({ exitCode, stderr }));
});
if (result.exitCode !== 0) {
this.logger.error(`角色生成运行时不可用: python=${pythonPath} ${result.stderr.trim()}`);
throw new BadRequestException('服务端角色生成运行时未就绪,请联系管理员');
}
}
private async saveSourceImage(base64: string, destinationPath: string): Promise<void> {
const normalized = base64.trim().replace(/^data:image\/[a-zA-Z0-9.+-]+;base64,/, '');
let buffer: Buffer;