Merge branch 'develop' of https://github.com/HBAI-Ltd/Toonflow-app into develop
This commit is contained in:
commit
c7091fa90d
@ -1,4 +1,5 @@
|
|||||||
# 2D扁平风(Flat Design)风格说明
|
# 2D扁平风(Flat Design)风格说明
|
||||||
|
# 2D扁平风(Flat Design)风格说明
|
||||||
|
|
||||||
本风格专为"2D扁平风(Flat Design)"题材打造,所有美术提示词、规范和生成内容均严格限定于:
|
本风格专为"2D扁平风(Flat Design)"题材打造,所有美术提示词、规范和生成内容均严格限定于:
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 389 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 624 KiB |
2210
data/web/index.html
2210
data/web/index.html
File diff suppressed because one or more lines are too long
@ -47,6 +47,7 @@ nsis:
|
|||||||
artifactName: ${productName}-${version}-win-${arch}-setup.${ext}
|
artifactName: ${productName}-${version}-win-${arch}-setup.${ext}
|
||||||
installerIcon: './scripts/logo.ico'
|
installerIcon: './scripts/logo.ico'
|
||||||
uninstallerIcon: './scripts/logo.ico'
|
uninstallerIcon: './scripts/logo.ico'
|
||||||
|
include: ./scripts/installer.nsh
|
||||||
|
|
||||||
mac:
|
mac:
|
||||||
target:
|
target:
|
||||||
|
|||||||
25
scripts/installer.nsh
Normal file
25
scripts/installer.nsh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
!macro customInstall
|
||||||
|
; Check if VC++ Redistributable is already installed
|
||||||
|
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64" "Installed"
|
||||||
|
${If} $0 != 1
|
||||||
|
DetailPrint "Downloading Visual C++ Redistributable..."
|
||||||
|
${If} ${RunningX64}
|
||||||
|
inetc::get "https://aka.ms/vs/17/release/vc_redist.x64.exe" "$TEMP\vc_redist.exe" /END
|
||||||
|
${Else}
|
||||||
|
inetc::get "https://aka.ms/vs/17/release/vc_redist.arm64.exe" "$TEMP\vc_redist.exe" /END
|
||||||
|
${EndIf}
|
||||||
|
Pop $0
|
||||||
|
${If} $0 == "OK"
|
||||||
|
DetailPrint "Installing Visual C++ Redistributable..."
|
||||||
|
nsExec::ExecToLog '"$TEMP\vc_redist.exe" /install /quiet /norestart'
|
||||||
|
Pop $0
|
||||||
|
DetailPrint "VC++ Redistributable install returned: $0"
|
||||||
|
${Else}
|
||||||
|
DetailPrint "VC++ Redistributable download failed: $0"
|
||||||
|
MessageBox MB_OK|MB_ICONEXCLAMATION "Visual C++ Redistributable download failed. Please install it manually from https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||||
|
${EndIf}
|
||||||
|
Delete "$TEMP\vc_redist.exe"
|
||||||
|
${Else}
|
||||||
|
DetailPrint "Visual C++ Redistributable is already installed."
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
@ -14,6 +14,7 @@ export default router.post(
|
|||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
const assetsData = await u.db("o_image").where("assetsId", id);
|
const assetsData = await u.db("o_image").where("assetsId", id);
|
||||||
await Promise.all(assetsData.map((i) => i.filePath && u.oss.deleteFile(i.filePath)));
|
await Promise.all(assetsData.map((i) => i.filePath && u.oss.deleteFile(i.filePath)));
|
||||||
|
await u.db("o_image").where({ assetsId: id }).delete();
|
||||||
await u.db("o_assets").where({ id }).delete();
|
await u.db("o_assets").where({ id }).delete();
|
||||||
res.status(200).send(success({ message: "删除资产成功" }));
|
res.status(200).send(success({ message: "删除资产成功" }));
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,9 @@ export default router.post(
|
|||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
await u.db("o_video").where("id", id).delete();
|
await u.db("o_video").where("id", id).delete();
|
||||||
|
await u.db("o_videoTrack").where("videoId", id).update({
|
||||||
|
videoId: null,
|
||||||
|
});
|
||||||
res.status(200).send(success({ message: "视频删除成功" }));
|
res.status(200).send(success({ message: "视频删除成功" }));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -76,9 +76,7 @@ export default router.post(
|
|||||||
const projectData = await u.db("o_project").select("*").where({ id: projectId }).first();
|
const projectData = await u.db("o_project").select("*").where({ id: projectId }).first();
|
||||||
const videoPrompt = await u.db("o_prompt").where("type", "videoPromptGeneration").first();
|
const videoPrompt = await u.db("o_prompt").where("type", "videoPromptGeneration").first();
|
||||||
const artStyle = projectData?.artStyle || "无";
|
const artStyle = projectData?.artStyle || "无";
|
||||||
const data = projectData?.directorManual || "无";
|
|
||||||
const visualManual = u.getArtPrompt(artStyle, "art_skills", "art_storyboard_video");
|
const visualManual = u.getArtPrompt(artStyle, "art_skills", "art_storyboard_video");
|
||||||
const directorManual = u.getArtPrompt(data, "story_skills", "narrative_sweet_romance");
|
|
||||||
const content = `
|
const content = `
|
||||||
**模型名称**:${modelData},
|
**模型名称**:${modelData},
|
||||||
**资产信息**(角色、场景、道具):${assets.map((i) => `[id:${i.id},type:${i.type},name:${i.name}]`).join(",")},
|
**资产信息**(角色、场景、道具):${assets.map((i) => `[id:${i.id},type:${i.type},name:${i.name}]`).join(",")},
|
||||||
@ -99,7 +97,7 @@ export default router.post(
|
|||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: `${visualManual}\n${directorManual}`,
|
content: `${visualManual}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
|
|||||||
@ -50,10 +50,10 @@ export default router.post(
|
|||||||
{ value: "art_prop_derivative", subDir: "art_prompt" },
|
{ value: "art_prop_derivative", subDir: "art_prompt" },
|
||||||
{ value: "art_scene", subDir: "art_prompt" },
|
{ value: "art_scene", subDir: "art_prompt" },
|
||||||
{ value: "art_scene_derivative", subDir: "art_prompt" },
|
{ value: "art_scene_derivative", subDir: "art_prompt" },
|
||||||
{ value: "art_storyboard", subDir: "art_prompt" },
|
{ value: "director_storyboard", subDir: "driector_skills" },
|
||||||
{ value: "art_storyboard_video", subDir: "art_prompt" },
|
{ value: "art_storyboard_video", subDir: "art_prompt" },
|
||||||
{ value: "director_planning", subDir: "driector_skills" },
|
{ value: "director_planning_style", subDir: "driector_skills" },
|
||||||
{ value: "director_storyboard_table", subDir: "driector_skills" },
|
{ value: "director_storyboard_table_style", subDir: "driector_skills" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 根据 DATA_MAP 构建 value -> subDir 的映射
|
// 根据 DATA_MAP 构建 value -> subDir 的映射
|
||||||
|
|||||||
@ -13,27 +13,47 @@ export default router.post(
|
|||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
|
//删除项目
|
||||||
|
await u.db("o_project").where("id", id).delete();
|
||||||
|
await u.db("o_agentWorkData").where("projectId", id).delete();
|
||||||
|
const novelData = await u.db("o_novel").where("projectId", id).select("id");
|
||||||
|
const novelId = novelData.map((item: any) => item.id);
|
||||||
|
if (novelId.length > 0) {
|
||||||
|
await u.db("o_outlineNovel").whereIn("novelId", novelId).delete();
|
||||||
|
}
|
||||||
|
//删除项目下的原文
|
||||||
|
await u.db("o_novel").where("projectId", id).delete();
|
||||||
|
// 删除项目下的剧本信息
|
||||||
const scriptData = await u.db("o_script").where("projectId", id).select("id");
|
const scriptData = await u.db("o_script").where("projectId", id).select("id");
|
||||||
const scriptIds = scriptData.map((item: any) => item.id);
|
const scriptIds = scriptData.map((item: any) => item.id);
|
||||||
|
if (scriptIds.length > 0) {
|
||||||
|
await u.db("o_scriptAssets").whereIn("scriptId", scriptIds).delete();
|
||||||
|
}
|
||||||
|
await u.db("o_script").where("projectId", id).delete();
|
||||||
|
await u.db("o_outline").where("projectId", id).delete();
|
||||||
|
// 删除项目下的任务
|
||||||
|
await u.db("o_tasks").where("projectId", id).delete();
|
||||||
|
// 删除项目下的分镜
|
||||||
|
const storyboardData = await u.db("o_storyboard").where("projectId", id).select("id");
|
||||||
|
const storyboardIds = storyboardData.map((item: any) => item.id);
|
||||||
|
if (storyboardIds.length > 0) {
|
||||||
|
await u.db("o_assets2Storyboard").whereIn("storyboardId", storyboardIds).delete();
|
||||||
|
}
|
||||||
|
await u.db("o_storyboard").where("projectId", id).delete();
|
||||||
|
//删除需要删除资产的归属图片
|
||||||
const assetsData = await u.db("o_assets").where("projectId", id).select("id");
|
const assetsData = await u.db("o_assets").where("projectId", id).select("id");
|
||||||
const assetsIds = assetsData.map((item: any) => item.id);
|
const assetsIds = assetsData.map((item: any) => item.id);
|
||||||
|
|
||||||
await u.db("o_project").where("id", id).delete();
|
|
||||||
await u.db("o_novel").where("projectId", id).delete();
|
|
||||||
await u.db("o_outline").where("projectId", id).delete();
|
|
||||||
await u.db("o_tasks").where("projectId", id).delete();
|
|
||||||
|
|
||||||
await u.db("o_script").where("projectId", id).delete();
|
|
||||||
await u.db("o_assets").where("projectId", id).delete();
|
|
||||||
|
|
||||||
if (assetsIds.length > 0) {
|
if (assetsIds.length > 0) {
|
||||||
await u.db("o_image").where("projectId", id).orWhereIn("assetsId", assetsIds).delete();
|
// 先将 o_assets.imageId 置空,解除对 o_image 的外键引用
|
||||||
|
await u.db("o_assets").whereIn("id", assetsIds).update({ imageId: null });
|
||||||
|
await u.db("o_image").whereIn("assetsId", assetsIds).delete();
|
||||||
}
|
}
|
||||||
|
// 删除项目下的资产
|
||||||
await u.db("o_video").whereIn("scriptId", scriptIds).delete();
|
await u.db("o_assets").where("projectId", id).delete();
|
||||||
|
//删除项目下的视频轨道和视频
|
||||||
|
await u.db("o_videoTrack").where("projectId", id).delete();
|
||||||
|
await u.db("o_video").where("projectId", id).delete();
|
||||||
|
//删除项目下的资源
|
||||||
try {
|
try {
|
||||||
await u.oss.deleteDirectory(`${id}/`);
|
await u.oss.deleteDirectory(`${id}/`);
|
||||||
console.log(`项目 ${id} 的OSS文件夹删除成功`);
|
console.log(`项目 ${id} 的OSS文件夹删除成功`);
|
||||||
|
|||||||
@ -51,10 +51,10 @@ export default router.post(
|
|||||||
{ value: "art_prop_derivative", subDir: "art_prompt" },
|
{ value: "art_prop_derivative", subDir: "art_prompt" },
|
||||||
{ value: "art_scene", subDir: "art_prompt" },
|
{ value: "art_scene", subDir: "art_prompt" },
|
||||||
{ value: "art_scene_derivative", subDir: "art_prompt" },
|
{ value: "art_scene_derivative", subDir: "art_prompt" },
|
||||||
{ value: "art_storyboard", subDir: "art_prompt" },
|
{ value: "director_storyboard", subDir: "driector_skills" },
|
||||||
{ value: "art_storyboard_video", subDir: "art_prompt" },
|
{ value: "art_storyboard_video", subDir: "art_prompt" },
|
||||||
{ value: "director_planning", subDir: "driector_skills" },
|
{ value: "director_planning_style", subDir: "driector_skills" },
|
||||||
{ value: "director_storyboard_table", subDir: "driector_skills" },
|
{ value: "director_storyboard_table_style", subDir: "driector_skills" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 根据 DATA_MAP 构建 value -> subDir 的映射
|
// 根据 DATA_MAP 构建 value -> subDir 的映射
|
||||||
|
|||||||
@ -15,10 +15,10 @@ const DATA_MAP: { label: string; value: string; subDir?: string }[] = [
|
|||||||
{ label: "道具衍生", value: "art_prop_derivative", subDir: "art_prompt" },
|
{ label: "道具衍生", value: "art_prop_derivative", subDir: "art_prompt" },
|
||||||
{ label: "场景", value: "art_scene", subDir: "art_prompt" },
|
{ label: "场景", value: "art_scene", subDir: "art_prompt" },
|
||||||
{ label: "场景衍生", value: "art_scene_derivative", subDir: "art_prompt" },
|
{ label: "场景衍生", value: "art_scene_derivative", subDir: "art_prompt" },
|
||||||
{ label: "分镜", value: "art_storyboard", subDir: "art_prompt" },
|
{ label: "分镜", value: "director_storyboard", subDir: "driector_skills" },
|
||||||
{ label: "分镜视频", value: "art_storyboard_video", subDir: "art_prompt" },
|
{ label: "分镜视频", value: "art_storyboard_video", subDir: "art_prompt" },
|
||||||
{ label: "技法-导演规划", value: "director_planning", subDir: "driector_skills" },
|
{ label: "技法-导演规划", value: "director_planning_style", subDir: "driector_skills" },
|
||||||
{ label: "技法-分镜表设计", value: "director_storyboard_table", subDir: "driector_skills" },
|
{ label: "技法-分镜表设计", value: "director_storyboard_table_style", subDir: "driector_skills" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 读取 md 文件内容,文件不存在时返回空字符串
|
// 读取 md 文件内容,文件不存在时返回空字符串
|
||||||
|
|||||||
@ -51,11 +51,11 @@ export default router.post(
|
|||||||
}
|
}
|
||||||
const tempSkillsPath = u.getPath(["temp", "skills"]);
|
const tempSkillsPath = u.getPath(["temp", "skills"]);
|
||||||
if (fs.existsSync(tempSkillsPath)) {
|
if (fs.existsSync(tempSkillsPath)) {
|
||||||
fs.cpSync(tempSkillsPath, u.getPath(["skills"]), { recursive: true, force: false });
|
fs.cpSync(tempSkillsPath, u.getPath(["skills"]), { recursive: true });
|
||||||
}
|
}
|
||||||
const tempModelsPath = u.getPath(["temp", "models"]);
|
const tempModelsPath = u.getPath(["temp", "models"]);
|
||||||
if (fs.existsSync(tempModelsPath)) {
|
if (fs.existsSync(tempModelsPath)) {
|
||||||
fs.cpSync(tempModelsPath, u.getPath(["models"]), { recursive: true, force: false });
|
fs.cpSync(tempModelsPath, u.getPath(["models"]), { recursive: true });
|
||||||
}
|
}
|
||||||
fs.rmSync(rootDir, { recursive: true, force: true });
|
fs.rmSync(rootDir, { recursive: true, force: true });
|
||||||
res.status(200).send(success("更新成功,5秒后重启"));
|
res.status(200).send(success("更新成功,5秒后重启"));
|
||||||
|
|||||||
@ -11,65 +11,48 @@ export default router.post(
|
|||||||
key: z.string().optional(),
|
key: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
await u.db("o_agentDeploy").where("key", "scriptAgent").update({
|
const { key } = req.body;
|
||||||
model: "claude-sonnet-4-6",
|
const vendorConfigData = await u.db("o_vendorConfig").where("id", "toonflow").first();
|
||||||
modelName: "toonflow:claude-sonnet-4-6",
|
if (!vendorConfigData) return res.status(500).send(error("未找到该供应商配置"));
|
||||||
vendorId: "toonflow",
|
if (!vendorConfigData.inputValues) return res.status(500).send(error("未找到模型配置数据"));
|
||||||
});
|
const inputValue = JSON.parse(vendorConfigData.inputValues!);
|
||||||
await u.db("o_agentDeploy").where("key", "productionAgent").update({
|
inputValue.apiKey = key;
|
||||||
model: "claude-sonnet-4-6",
|
await u
|
||||||
modelName: "toonflow:claude-sonnet-4-6",
|
.db("o_vendorConfig")
|
||||||
vendorId: "toonflow",
|
.where("id", "toonflow")
|
||||||
});
|
.update({
|
||||||
await u.db("o_agentDeploy").where("key", "universalAi").update({
|
inputValues: JSON.stringify(inputValue),
|
||||||
model: "claude-haiku-4-5",
|
});
|
||||||
modelName: "toonflow:claude-haiku-4-5-20251001",
|
try {
|
||||||
vendorId: "toonflow",
|
const resText = await u.Ai.Text(`toonflow:claude-haiku-4-5-20251001`).invoke({
|
||||||
});
|
prompt: "1+1等于几?,请直接回答2,不要解释",
|
||||||
res.status(200).send(success("一键填入成功"));
|
});
|
||||||
|
if (resText.text) {
|
||||||
// const { key } = req.body;
|
await u.db("o_agentDeploy").where("key", "scriptAgent").update({
|
||||||
// const vendorConfigData = await u.db("o_vendorConfig").where("id", "toonflow").first();
|
model: "claude-sonnet-4-6",
|
||||||
// if (!vendorConfigData) return res.status(500).send(error("未找到该供应商配置"));
|
modelName: "toonflow:claude-sonnet-4-6",
|
||||||
// if (!vendorConfigData.inputValues) return res.status(500).send(error("未找到模型配置数据"));
|
vendorId: "toonflow",
|
||||||
// const inputValue = JSON.parse(vendorConfigData.inputValues!);
|
});
|
||||||
// inputValue.apiKey = key;
|
await u.db("o_agentDeploy").where("key", "productionAgent").update({
|
||||||
// await u
|
model: "claude-sonnet-4-6",
|
||||||
// .db("o_vendorConfig")
|
modelName: "toonflow:claude-sonnet-4-6",
|
||||||
// .where("id", "toonflow")
|
vendorId: "toonflow",
|
||||||
// .update({
|
});
|
||||||
// inputValues: JSON.stringify(inputValue),
|
await u.db("o_agentDeploy").where("key", "universalAi").update({
|
||||||
// });
|
model: "claude-haiku-4-5",
|
||||||
// try {
|
modelName: "toonflow:claude-haiku-4-5-20251001",
|
||||||
// const resText = await u.Ai.Text(`toonflow:gpt-4.1`).invoke({
|
vendorId: "toonflow",
|
||||||
// prompt: "1+1等于几?",
|
});
|
||||||
// });
|
res.status(200).send(success("一键填入成功"));
|
||||||
// if (resText.text) {
|
}
|
||||||
// await u.db("o_agentDeploy").where("key", "scriptAgent").update({
|
} catch (err) {
|
||||||
// model: "claude-sonnet-4-6",
|
console.error(err);
|
||||||
// modelName: "toonflow:claude-sonnet-4-6",
|
inputValue.apiKey = "";
|
||||||
// vendorId: "toonflow",
|
await u
|
||||||
// });
|
.db("o_vendorConfig")
|
||||||
// await u.db("o_agentDeploy").where("key", "productionAgent").update({
|
.where("id", "toonflow")
|
||||||
// model: "claude-sonnet-4-6",
|
.update({ inputValues: JSON.stringify(inputValue) });
|
||||||
// modelName: "toonflow:claude-sonnet-4-6",
|
res.status(400).send(error("KEY无效,请重新输入"));
|
||||||
// vendorId: "toonflow",
|
}
|
||||||
// });
|
|
||||||
// await u.db("o_agentDeploy").where("key", "universalAi").update({
|
|
||||||
// model: "claude-haiku-4-5",
|
|
||||||
// modelName: "toonflow:claude-haiku-4-5-20251001",
|
|
||||||
// vendorId: "toonflow",
|
|
||||||
// });
|
|
||||||
// res.status(200).send(success("一键填入成功"));
|
|
||||||
// }
|
|
||||||
// } catch (err) {
|
|
||||||
// console.error(err);
|
|
||||||
// inputValue.apiKey = "";
|
|
||||||
// await u
|
|
||||||
// .db("o_vendorConfig")
|
|
||||||
// .where("id", "toonflow")
|
|
||||||
// .update({ inputValues: JSON.stringify(inputValue) });
|
|
||||||
// res.status(400).send(error("KEY无效,请重新输入"));
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export default router.post(
|
|||||||
"/",
|
"/",
|
||||||
validateFields({
|
validateFields({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
enable: z.number(),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { id, enable } = req.body;
|
const { id, enable } = req.body;
|
||||||
|
|||||||
@ -95,7 +95,6 @@ export default router.post(
|
|||||||
inputValues: JSON.stringify(vendor.inputValues ?? {}),
|
inputValues: JSON.stringify(vendor.inputValues ?? {}),
|
||||||
models: JSON.stringify(vendor.models ?? []),
|
models: JSON.stringify(vendor.models ?? []),
|
||||||
code: tsCode,
|
code: tsCode,
|
||||||
enable: 0,
|
|
||||||
createTime: Date.now(),
|
createTime: Date.now(),
|
||||||
});
|
});
|
||||||
res.status(200).send(success(result.data));
|
res.status(200).send(success(result.data));
|
||||||
|
|||||||
@ -67,7 +67,6 @@ export default router.post(
|
|||||||
inputs: JSON.stringify(inputs),
|
inputs: JSON.stringify(inputs),
|
||||||
inputValues: JSON.stringify(inputValues),
|
inputValues: JSON.stringify(inputValues),
|
||||||
models: JSON.stringify(models),
|
models: JSON.stringify(models),
|
||||||
enable: id == "toonflow" ? 1 : 0,
|
|
||||||
});
|
});
|
||||||
res.status(200).send(success("更新成功"));
|
res.status(200).send(success("更新成功"));
|
||||||
},
|
},
|
||||||
|
|||||||
@ -124,4 +124,7 @@ export default (nsp: Namespace) => {
|
|||||||
abortController = null;
|
abortController = null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
nsp.on("disconnect", (socket: Socket) => {
|
||||||
|
console.log("[productionAgent] 已断开连接:", socket.id);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -112,4 +112,7 @@ export default (nsp: Namespace) => {
|
|||||||
abortController = null;
|
abortController = null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
nsp.on("disconnect", (socket: Socket) => {
|
||||||
|
console.log("[scriptAgent] 已断开连接:", socket.id);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user